#python #django #permissions #django-rest-framework
#python #django #разрешения #django-rest-framework
Вопрос:
Я создаю DRF API, и я хотел бы разрешить сотрудникам ( is_staff == True
) доступ ко всем конечным точкам REST, при этом по-прежнему предоставляя пользовательскую проверку разрешений для каждого ViewSet. В идеале, это был бы глобальный параметр, но я не против настройки его для каждого ViewSet.
Вот что я пробовал:
Вариант 1: Проверьте каждое пользовательское разрешение
from rest_framework import permissions
class SomeModelPermission(permissions.BasePermission):
def has_permission(self, request, view):
if request.user.is_staff:
return True
# other logic
def has_object_permission(self, request, view, obj):
if request.user.is_staff:
return True
# other logic
Это работает, но я бы предпочел не повторять так много кода.
Вариант 2: Побитовые операторы
Я попытался удалить is_staff
логику из пользовательского разрешения выше и добавить это в ViewSet:
from rest_framework import permissions, viewsets
class SomeModelViewSet(viewsets.ModelViewSet):
permission_classes = (permissions.IsAdminUser|SomeModelPermission,)
Однако на самом деле это не обеспечивает принудительное применение разрешений, как хотелось бы, потому что IsAdminUser
наследуется от BasePermission
, который определяется как:
class BasePermission(object):
def has_permission(self, request, view):
return True
def has_object_permission(self, request, view, obj):
return True
IsAdminUser
не определяет свой собственный, has_object_permission
поэтому он всегда будет возвращаться True
при проверке разрешений объекта, что приводит к непреднамеренному доступу к объекту.
Есть идеи? Я надеялся, что каким-то образом я мог бы установить глобальную проверку разрешений, которая возвращалась бы True
, когда пользователь является сотрудником, и в противном случае зависела бы от пользовательских разрешений. Но, прочитав, как определяются разрешения, я не уверен, что это возможно.
Ответ №1:
Побитовое решение:
Как насчет создания своего собственного, IsAdminUser
который также определяет has_object_permission
? Вы могли бы просто наследовать от существующего:
from rest_framework.permissions import IsAdminUser as BaseIsAdminUser
class IsAdminUser(BaseIsAdminUser):
def has_object_permission(self, request, view, obj):
# Just reuse the same logic as `has_permission`...
return self.has_permission(request, view)
Затем вы можете сделать то, что пытались выше, с помощью побитового оператора:
from rest_framework import permissions, viewsets
from your_own_project.permissions import IsAdminUser
class SomeModelViewSet(viewsets.ModelViewSet):
permission_classes = (IsAdminUser|SomeModelPermission,)
Другое решение:
В некотором смысле это немного «хакерски», но вы могли бы попробовать создавать свои собственные типы разрешений «на лету».
Таким образом, конечный результат будет выглядеть примерно так:
class SomeModelViewSet(viewsets.ModelViewSet):
permission_classes = skip_for_staff((SomeModelPermission, SomeOtherPermission, ...))
С реализацией, похожей на:
class StaffAllowedMixin:
def has_permission(self, request, view):
if request.user.is_staff:
return True
return super().has_permission(request, view)
def has_object_permission(self, request, view, obj):
if request.user.is_staff:
return True
return super().has_object_permission(request, view, obj)
def skip_for_staff(permission_classes):
# You can probably also use a comprehension here, but for clarity:
staff_allowed_classes = []
for permission_class in permissions(
staff_allowed_classes.append(
# Create a new type (class) with name StaffAllowed<...>
type(f"StaffAllowed{permission_class}",
# Inherit from the mixin above, and from the original class
(StaffAllowedMixin, permission_class),
# empty dictionary means you don't want to override any attributes
{})
)
return tuple(staff_allowed_classes)
По сути, для каждого класса разрешений вы создаете новый класс с дополнительным микшином, который имеет приоритет и проверяет, является ли пользователь персоналом.
Но вы делаете это на лету, где используются ваши разрешения, вместо того, чтобы предварительно определять это для каждого разрешения.
Ответ №2:
Существует класс разрешений для пользователей-администраторов. Вот пример этого:
class deletecompletedreopenjobAPIView(RetrieveUpdateAPIView):
queryset = Job.objects._all()
serializer_class = JobCompletedDeleteStatusSerializer
lookup_field = 'job_id'
permission_classes = [IsOwnerOrReadOnly | **IsAdminUser**]
authentication_classes = (authentication.TokenAuthentication,)
Мой пользовательский IsOwnerOrReadOnly заключается в следующем:
class IsOwnerOrReadOnly(BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in SAFE_METHODS:
return True
return obj.user == request.user