如何根据 Graphene/Django 上的用户类型限制模型上的字段访问?

Posted

技术标签:

【中文标题】如何根据 Graphene/Django 上的用户类型限制模型上的字段访问?【英文标题】:How to limit field access on a model based on user type on Graphene/Django? 【发布时间】:2018-08-11 13:34:10 【问题描述】:

假设我有一个模型:

class Employee(models.Model):
    first_name = models.CharField(max_length=40)
    last_name = models.CharField(max_length=60)
    salary = models.DecimalField(decimal_places=2)

我希望任何人都能够访问 first_name 和 last_name,但只希望某些用户能够读取薪水,因为这是机密数据。

然后我想将薪水的写入/更新限制为甚至不同类型的用户。

如何根据请求用户限制字​​段读/写/更新?

编辑:

这是在 GraphQL API 上下文中。我正在使用石墨烯。我希望在解析器功能中看到可扩展的解决方案。

【问题讨论】:

最好和最简单的建议是您需要创建一个组,然后添加自定义权限并将特定成员包含到该组中。 我想看看你如何访问reducer 的请求,如何在带有未授权字段的查询中发送错误,如何在reducer 中处理身份验证。问题更多在于身份验证的 GraphQL 集成,而不是 Django 端。 当你说“reducer”时,你是指 React/Redux 意义上的“reducer”,还是真正的“resolver”? 是的,它是解析器,我的错。 这个问题有两个部分,因为石墨烯-python 实现需要单独的功能来读取(即查询)与写入(即突变)。 【参考方案1】:

查询

假设你有

    查询定义如下
    employees = graphene.List(EmployeeType)
    类似查询的解析器
    def resolve_employees(self, info, **kwargs):
        return Employee.objects.all()

    Employee 模型的权限称为 can_view_salarycan_edit_salary

然后您需要定义 EmployeeType 的值 salary 取决于用户。类似的东西

from graphene_django.types import DjangoObjectType
from myapp.models import Employee

class EmployeeType(DjangoObjectType):
    class Meta:
        model = Employee
        
    def resolve_salary(self, info):
        if info.context.user.has_perm('myapp.can_view_salary'):
            return self.salary
        return None

重要的一点是,您正在为根据权限值切换的薪水创建自定义 resolve 函数。您无需为 first_namelast_name 创建任何其他解析器。


突变

Read the documentation first. 但是没有更新的例子。

简而言之,您可以采取以下方法:

    创建一个方法以在您的 Mutation 方法中设置员工
class MyMutations(graphene.ObjectType):
     set_employee = SetEmployee.Field()
    SetEmployee 创建一个获取Employee 对象并更新它的方法。某些用户的薪水字段会被忽略。
class SetEmployee(graphene.Mutation):
    
    class Arguments:
        id = graphene.ID()
        first_name = graphene.String()
        last_name = graphene.String()
        salary = graphene.String()
    
    employee = graphene.Field(lambda: EmployeeType)
    
    
    @classmethod
    def mutate(cls, root, info, **args):
        employee_id = args.get('employee_id')
        
        # Fetch the employee object by id
        employee = Employee.objects.get(id=employee_id)
        first_name = args.get('first_name')
        last_name = args.get('last_name')
        salary = args.get('salary')
        
        # Update the employee fields from the mutation inputs
        if first_name:
            employee.first_name = first_name
        if last_name:
            employee.last_name = last_name
        if salary and info.context.user.has_perm('myapp.can_edit_salary'):
            employee.salary = salary
        employee.save()
        return SetEmployee(employee=employee)

注意:最初编写此答案时,Graphene Django 中没有可用的小数字段——我通过将字符串作为输入来避免此问题。

【讨论】:

这对我来说似乎违反直觉。通过定义上面resolve_salary() 之类的函数,我们实际上是在创建一个不允许用户查看的字段的黑名单。当一个新的特权字段被添加到Employee 模型中并且开发人员不可避免地忘记还创建resolve_newfield() 函数来限制它的访问时,这在将来可能会很麻烦。因此通过忘记定义函数来创建安全漏洞。 -- 有没有办法屏蔽所有字段,除非我们明确允许(而不是明确屏蔽)? @PKKid 似乎有一些方法可以在源代码github.com/graphql-python/graphene-django/blob/master/… 中明确限制字段,但据我所知,它们没有记录,并且它们不允许基于条件的访问跨度> 谢谢。它没有正式记录,但错误中的讨论讨论了查询中引用的 only_fields。单独讨论让我对提前使用它感觉好一点。【参考方案2】:

很好的回应@MarkChackerian。但就我个人而言,我认为在未经授权的访问时为字段返回 null 值可能会产生歧义,因此我个人从 resolve 方法中引发了一个异常,如下所示:

class UnauthorisedAccessError(GraphQLError):
    def __init__(self, message, *args, **kwargs):
        super(UnauthorisedAccessError, self).__init__(message, *args, **kwargs)

def resolve_salary(self, info):
        if info.context.user.has_perm('myapp.can_view_salary'):
            return self.salary
        raise UnauthorisedAccessError(message='No permissions to see the salary!')

【讨论】:

这取决于用例。如果用户正在编写自己的查询,那么最好引发异常,如您所示。但是,我认为更常见的用例是 graphQL 查询嵌入在 javascript 中,在这种情况下,拥有一个适用于两类用户(具有访问权限的用户和没有访问权限的用户)的单个 graphQL 查询更为实用。如果您引发异常,您将无法拥有适用于两种用户的单一查询。

以上是关于如何根据 Graphene/Django 上的用户类型限制模型上的字段访问?的主要内容,如果未能解决你的问题,请参考以下文章

在 graphene/graphene_django 中扩展查询参数

Graphene/Django (GraphQL):如何使用查询参数来排除与特定过滤器匹配的节点?

如何删除 Graphene Django 突变查询(中继)中的嵌套输入对象?

Graphene Django 查询 Elasticsearch

JWT 身份验证:使用 UI 令牌对 Graphene/Django (GraphQL) 查询进行身份验证?

在 Graphene Django 中查询多个模型