Django 如何编写自定义权限类,在api级权限之前检查对象级权限? TL;博士
如何编写自定义权限类,在api级权限(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
具有\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权限。