Django 如何编写自定义权限类,在api级权限之前检查对象级权限? TL;博士

Django 如何编写自定义权限类,在api级权限之前检查对象级权限? TL;博士,django,django-rest-framework,permissions,Django,Django Rest Framework,Permissions,如何编写自定义权限类,在api级权限(具有\u权限)之前检查对象级权限(具有\u权限) 定义 假设我们正在构建一个在线注释工具。我们有属于项目和用户的图像,这些图像用于注释图像。用户有一个角色字段——来宾具有只读访问权限,开发人员可以更改图像字段,所有者可以同时更改项目和图像 模型: class Image(models.Model): data = JSONField() project = models.ForeignKey(Project) class Project(m

如何编写自定义权限类,在api级权限(
具有\u权限
)之前检查对象级权限(
具有\u权限

定义 假设我们正在构建一个在线注释工具。我们有属于项目和用户的图像,这些图像用于注释图像。用户有一个
角色
字段——来宾具有只读访问权限,开发人员可以更改图像字段,所有者可以同时更改项目和图像

模型:

class Image(models.Model):
    data = JSONField()
    project = models.ForeignKey(Project)

class Project(models.Model):
    pass

class User(AbstractUser):
    ROLE_GUEST, ROLE_DEVELOPER, ROLE_OWNER = range(3)
    ROLE_CHOICES = [(ROLE_GUEST, "Guest"), (ROLE_DEVELOPER, "Developer"), (ROLE_OWNER, "Owner")]
    role = models.IntegerField(default=ROLE_GUEST, choices=ROLE_CHOICES)
假设我们希望限制对某些项目的访问。我们正在添加m2m型号UserProjectRole:

class UserProjectRole(models.Model):
    user = models.ForeignKey(User)
    project = models.ForeignKey(Project)
    project_role = ... # same as in User
现在,我们希望以这样的方式设置权限:如果用户请求访问有关某个项目的API端点:

  • 如果此用户和此项目没有UserProjectRole,则权限默认为其全局
    角色
  • 如果存在此类UserProjectRole,则权限将使用他的
    project\u角色
    而不是
    角色
问题 我们正在实现自定义权限类:

# permissions.py
from rest_framework import permissions

class AtLeastDeveloper(permissions.BasePermission):
    def has_permission(self, request, view):
        return request.user.is_staff or request.user.role >= User.ROLE_DEVELOPER

class AtLeastOwner(permissions.BasePermission):
    def has_permission(self, request, view):
        return request.user.is_staff or request.user.role >= User.ROLE_OWNER

class InProjectPermissions(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
         if isinstance(obj, Project):
             project = obj
         elif isinstance(obj, Image):
             project = obj.project
         else:
             raise ValueError(obj)
         upr = UserProjectRole.objects.filter(user = request.user, project = project).first()
         if upr is None:
             return False
         if isinstance(obj, Project):
             return upr.role == User.ROLE_OWNER
         return upr.role >= User.ROLE_DEVELOPER
class AbstractPermission(permissions.BasePermission):
    def has_permission(self, request, view):
        return True

    def has_permission_by_global_role(self, request, view):
        raise NotImplementedError()

    def has_object_permission(self, request, view, obj):
        if isinstance(obj, Project):
             project = obj
         elif isinstance(obj, Image):
             project = obj.project
         else:
             raise ValueError(obj)

         # if user has global role big enough -- permit
         if self.has_permission_by_global_role(request, view):
              return True

         # otherwise -- assess his 'local' role (as before)
         upr = UserProjectRole.objects.filter(user = request.user, project = project).first()
         if upr is None:
             return False
         if isinstance(obj, Project):
             return upr.role == User.ROLE_OWNER
         return upr.role >= User.ROLE_DEVELOPER
这里的问题是,
拥有_权限
拥有_对象_权限
之前被称为(如中所述)。因此,如果用户的全局
角色
不足,他将被拒绝访问(
isatelastsomething.has_permissions
将返回False,因此
InProjectPermissions.has_object_permissions
将不被执行)


请建议如何编写自定义权限类,以便在api级权限之前检查对象级权限。

TLDR-尝试覆盖
检查对象权限(请求,obj)

根据DRF文件:

在运行视图的主体之前,请查看列表中的每个权限 检查过了。如果任何权限检查失败,则 exceptions.PermissionDenied或exceptions.NotAuthenticated异常 将被提升,并且视图的主体将不会运行

这意味着我们在
权限\u类中提到的所有权限必须通过。如果其中任何一个也失败了,那么它将不会进行身份验证

  • 因此,对于我们的自定义需求,我们可以覆盖
    检查对象权限(请求,obj)
    并编写自定义身份验证

  • 所以,这就是我想到的

    正如@Hafnernuss所建议的,我可以通过
    视图。get_object(id)
    (从
    请求中获取
    id
    。get
    /
    POST
    ),访问所讨论的对象,但这正是我希望避免的:手动处理结构的内部

    @umair mohammad建议重写
    视图。检查\u object\u permissions
    ,但我还是希望避免,因为这意味着我的所有视图都应该从一个基类继承。在大多数情况下,这是可以的,谢谢您的回答--这确实解决了问题--但我真的想在不涉及视图的情况下将此逻辑分离为
    permissions.py

    因此,让我们重写基本权限类:

    # permissions.py
    from rest_framework import permissions
    
    class AtLeastDeveloper(permissions.BasePermission):
        def has_permission(self, request, view):
            return request.user.is_staff or request.user.role >= User.ROLE_DEVELOPER
    
    class AtLeastOwner(permissions.BasePermission):
        def has_permission(self, request, view):
            return request.user.is_staff or request.user.role >= User.ROLE_OWNER
    
    class InProjectPermissions(permissions.BasePermission):
        def has_object_permission(self, request, view, obj):
             if isinstance(obj, Project):
                 project = obj
             elif isinstance(obj, Image):
                 project = obj.project
             else:
                 raise ValueError(obj)
             upr = UserProjectRole.objects.filter(user = request.user, project = project).first()
             if upr is None:
                 return False
             if isinstance(obj, Project):
                 return upr.role == User.ROLE_OWNER
             return upr.role >= User.ROLE_DEVELOPER
    
    class AbstractPermission(permissions.BasePermission):
        def has_permission(self, request, view):
            return True
    
        def has_permission_by_global_role(self, request, view):
            raise NotImplementedError()
    
        def has_object_permission(self, request, view, obj):
            if isinstance(obj, Project):
                 project = obj
             elif isinstance(obj, Image):
                 project = obj.project
             else:
                 raise ValueError(obj)
    
             # if user has global role big enough -- permit
             if self.has_permission_by_global_role(request, view):
                  return True
    
             # otherwise -- assess his 'local' role (as before)
             upr = UserProjectRole.objects.filter(user = request.user, project = project).first()
             if upr is None:
                 return False
             if isinstance(obj, Project):
                 return upr.role == User.ROLE_OWNER
             return upr.role >= User.ROLE_DEVELOPER
    
    然后我们继承我们的AtLeastSomething类,通过全局角色将
    拥有权限更改为
    拥有权限:

    class AtLeastOwner(AbstractPermission):
        def has_permission_by_global_role(self, request, view):
            return request.user.is_staff or request.user.role >= User.ROLE_OWNER
    
    ...
    
    因此,在views.py中,我们只需要基于一个允许的全局角色指定permission_类——在我们的例子中,即Project的AtLeastOwner和Image的AtLeastDeveloper


    我相信这是最具python风格的方法。通过为每个模型提供类似于
    .allowed\u roles()
    方法的内容,可以进一步简化此过程。

    您可以从has\u permission方法内部手动调用has\u object\u permission?@Hafnernuss no,因为has\u permission无权访问
    obj
    True,但您可以自己检索对象,由于您有权访问中的视图/请求,因此您具有\u权限。