Платформа Django REST Framework — позволяет персоналу получать доступ ко всем конечным точкам

#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