【发布时间】:2020-05-19 02:07:52
【问题描述】:
TL;DR
如何编写自定义权限类以在 api 级权限 (has_permission) 之前检查对象级权限 (has_object_permission)?
定义
假设我们正在构建一个在线注释工具。我们有属于项目和用户的图像,它们正在对图像进行注释。用户有一个role 字段——来宾具有只读访问权限,开发人员可以更改图像字段,所有者可以更改项目和图像。
模型:
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,则权限默认为他的全局
role - 如果有这样的UserProjectRole,权限是使用他的
project_role而不是role
问题
我们正在实现自定义权限类:
# 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
# views.py
class ProjectGetDeleteUpdateView(RetrieveUpdateDestroyAPIView):
permission_classes = [IsAuthenticated, AtLeastOwner, InProjectPermissions]
...
class ImageGetDeleteUpdateView(RetrieveUpdateDestroyAPIView):
permission_classes = [IsAuthenticated, AtLeastDeveloper, InProjectPermissions]
...
这里的问题是has_permission 被称为之前 has_object_permissions(如https://www.django-rest-framework.org/api-guide/permissions/#custom-permissions 中所述)。因此,如果用户的全局role不够用,就会被拒绝访问(IsAtLeastSomething.has_permissions会返回False,因此InProjectPermissions.has_object_permissions不会被执行)。
请告知如何编写自定义权限类以在 api 级权限之前检查对象级权限。
【问题讨论】:
-
您可以从您的 has_permission 方法中手动调用 has_object_permission 吗?
-
@Hafnernuss 否,因为 has_permission 无权访问
obj -
是的,但您可以自己检索对象,因为您可以访问 has_permission 中的视图/请求。
标签: django django-rest-framework permissions