Search code examples
pythondjangodjango-rest-framework

Chaninig permissions in 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]

and custom permission is:

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

first i dont get object permission but with help of @brian-destura at this question i fixed that part the previous question now the problem is when i chain 2 permission together it behave like AllowAny i check them one by one and both permissions work fine, one of them allow admin and one of them allow owner but when they are or together it mess everything up


Solution

  • When chaining permissions like

    permission_classes = [AccountOwnerPermission, IsAdminUser]
    

    it behaves like an AND operator between the permission classes

    The best option is to create a new permission that allows either the permission logic.

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

    or this when the permissions used have long complex code to keep code 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)
    

    EDIT: Address the question from the comments, the reason why it behaves like AllowAny. AccountOwnerPermission has has_object_permission but no has_permission. On the other hand, IsAdminUser has has_permission but no has_object_permission implemented.

    When those functions are not implemented, the functions return True by default(from BasePermission). As a result, when running has_permission, AccountOwnerPermission always returns True When running has_object_permission, IsAdminUser is always returning True.

    Implementing AccountOwnerPermission.has_permission would give the expected behavior.