Разрешение chainig в Django rest framework ViewSet

class UserViewSet(viewsets.ModelViewSet):
    def list(self, request):
        users = User.objects.all()
        serializer = UserSerializer(users, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)

    def create(self, request):
        serializer = UserSerializer(data=request.data)

        if serializer.is_valid(raise_exception=True):
            pass

    def retrieve(self, request, pk):
        user = get_object_or_404(User, pk=pk)
        self.check_object_permissions(request, user)
        serializer = UserSerializer(user)
        return Response(serializer.data, status=status.HTTP_200_OK)

    def get_permissions(self):
        if self.action == "list":
            permission_classes = [
                IsAdminUser,
            ]
        elif self.action == "create":
            permission_classes = [AllowAny]
        else:
            permission_classes = [AccountOwnerPermission, IsAdminUser ]

        return [permission() for permission in permission_classes]

и пользовательское разрешение:

class AccountOwnerPermission(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        print(object)
        print(request.user)
        return obj == request.user

сначала я не получаю разрешение на объект, но с помощью @brian-destura в этом вопросе я исправил эту часть предыдущего вопроса . теперь проблема в том, что когда я связываю 2 разрешения вместе, они ведут себя как AllowAny я проверяю их по одному и оба разрешения работают нормально, одно из них allow admin и одно из них allow owner но когда они вместе, это все портит

При цепочке разрешений типа

permission_classes = [AccountOwnerPermission, IsAdminUser]

он ведет себя как оператор AND между классами разрешений

Лучшим вариантом является создание нового разрешения, которое позволяет либо логику разрешения.

class AdminOrAccountOwnerPermission(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        return obj == request.user or request.user.id_admin

или это, когда используемые разрешения имеют длинный сложный код, чтобы сохранить код DRY:

class AdminOrAccountOwnerPermission(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        return AccountOwnerPermission().has_object_permission(request, view, obj) or IsAdminUser().has_object_permission(request, view, obj)

@Kyell уже описал проблему, и его ответ должен быть принят

Но я попытаюсь добавить некоторые детали:

  1. Когда мы связываем два класса разрешений, DRF создает один новый класс OR:
>>> from rest_framework.permissions import IsAdminUser 
>>> or_class = [IsAdminUser | IsAdminUser]
>>> len(or_class)
1
>>> print(or_class)
[<rest_framework.permissions.OperandHolder object at 0x1096d5fa0>]
>>> 
  1. Django документация говорит, что метод has_object_permission (проверка разрешений для точного объекта), запущенный после has_permission (проверка разрешений для всего класса представления)

  2. Посмотрим, как выглядят эти методы внутри цепочки OR класса:

>>> import inspect
>>> or_instance = or_class[0]()
>>> print(inspect.getsource(or_instance.has_permission))
    def has_permission(self, request, view):
        return (
            self.op1.has_permission(request, view) or
            self.op2.has_permission(request, view)
        )

>>> print(inspect.getsource(or_instance.has_object_permission))
    def has_object_permission(self, request, view, obj):
        return (
            self.op1.has_object_permission(request, view, obj) or
            self.op2.has_object_permission(request, view, obj)
        )

Итак, мы видим, что DRF проверяет оба из has_permission и после этого оба из has_object_permission

Проверка

has_permission может быть пропущена, так как мы запускаем has_object_permission после.

Но! has_object_permission не реализован внутри класса разрешения IsAdminUser, но он реализован внутри родительского класса BasePermission и выглядит следующим образом:

class BasePermission(metaclass=BasePermissionMetaclass):
    def has_object_permission(self, request, view, obj):
        return True

Поэтому IsAdminUser всегда возвращает True на has_object_permission. В обычных случаях IsAdminUser должен отказать на has_permission, но в вашем OR классе has_permission прошел, потому что он не реализован внутри AccountOwnerPermission класса

Самым простым решением будет добавить has_permission метод в AccountOwnerPermission класс:

class AccountOwnerPermission(permissions.BasePermission):
    def has_permission(self, request, view, obj):
        return False
Вернуться на верх