DJN
DJN

Reputation: 61

How to combine/mix object level and user level permissions in DRF?

I am currently working on a DRF project where Admin users, Teacher users and Owner users should be able to have access to an objects detail-view. Basically all user types except those who are not the owner of the object or a teacher or an admin user. I am able to implement the separate permissions for each, but when I need to combine these permissions on a view I hit a roadblock because the user level perms are checked before object level perms. Thus I cannot use boolean operands to combine them and I have to write these ugly permission classes for my views.

My question is:

How can I implement these permissions on my detail-view in a cleaner way or, alternatively, is there a cleaner way to obtain my result?

As you will see, I violate DRY because I have an IsAdminOrOwner and IsAdminOrTeacherOrOwner perm. You will also note in my view that I overwrite get_permissions() to have appropriate permission classes for the respective request methods. Any comments on this and other implementations are welcome, I want critique so that I can improve upon it.

Here follows permissions.py:

from rest_framework import permissions
from rest_framework.permissions import IsAdminUser

class IsOwner(permissions.BasePermission):

    def has_permission(self, request, view):
        return True

    def has_object_permission(self, request, view, obj):
        return request.user == obj

class IsTeacher(permissions.BasePermission):

    def has_permission(self, request, view):
        return request.user.groups.filter(
            name='teacher_group').exists()

class IsAdminOrOwner(permissions.BasePermission):

    def has_object_permission(self, *args):
        is_owner = IsOwner().has_object_permission(*args)

        #convert tuple to list
        new_args = list(args)
        #remove object for non-object permission args
        new_args.pop()
        is_admin = IsAdminUser().has_permission(*new_args)

        return is_owner or is_admin

class IsAdminOrTeacherOrOwner(permissions.BasePermission):

    def has_object_permission(self, *args):
        is_owner = IsOwner().has_object_permission(*args)

        #convert tuple to list
        new_args = list(args)
        #remove object for non-object permission args
        new_args.pop()
        is_admin = IsAdminUser().has_permission(*new_args)
        is_teacher = IsTeacher().has_permission(*new_args)

        return is_admin or is_teacher or is_owner

And here follows my view:

class UserRetrieveUpdateView(APIView):
    serializer_class = UserSerializer

    def get_permissions(self):
        #the = is essential, because with each view
        #it resets the permission classes
        #if we did not implement it, the permissions
        #would have added up incorrectly
        self.permission_classes = [IsAuthenticated]

        if self.request.method == 'GET':
            self.permission_classes.append(
                IsAdminOrTeacherOrOwner)

        elif self.request.method == 'PUT':
            self.permission_classes.append(IsOwner)

        return super().get_permissions()

    def get_object(self, pk):
        try:
            user = User.objects.get(pk=pk)
            self.check_object_permissions(self.request, user)
            return user
        except User.DoesNotExist:
            raise Http404

    def get(self, request, pk, format=None):
        user = self.get_object(pk)
        serializer = self.serializer_class(user)
        return Response(serializer.data, status.HTTP_200_OK)

    def put(self, request, pk, format=None):
        user = self.get_object(pk)
        #we use partial to update only certain values
        serializer = self.serializer_class(user,
            data=request.data, partial=True)

        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data,
                status.HTTP_200_OK)

        return Response(status=status.HTTP_400_BAD_REQUEST)

Upvotes: 3

Views: 1609

Answers (1)

Ken4scholars
Ken4scholars

Reputation: 6296

Permissions in DRF can be combined using the bitwise OR operator. For example, you could do this:

permission_classes = (IsAdmin | IsOwner | IsTeacher)

In this way, you don't have to define separate classes to combine them.

Upvotes: 4

Related Questions