Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

has_object_permission not called

I looked through similar questions on the same topic and I think I am following all the rules specified for has_object_permission.

This is what I have in my settings.

REST_FRAMEWORK = {
    
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
        'users.permissions.CanAccessData', # this is my custom class
    ],    
    ... 
}

This is my permission class

class CanAccessData(permissions.BasePermission):
    message = 'You do not have permission to perform this action.'

    def has_permission(self, request, view):
        print "has_permission`"
        return True

    def has_object_permission(self, request, view, obj):
        print "has_object_permission"
        return False

Here is my view structure:

class CompleteList(generics.ListCreateAPIView):
    permission_classes = (CanAccessData,)
    serializer_class = SomeSerializer
    model = Some
    filter_backends = (filters.OrderingFilter, filters.SearchFilter)
    ordering_fields = (tuple of Some fields)
    search_fields = ordering_fields
    ordering = ('-create_date')

Still, has_object_permission is not getting called, has_permission gets called though.

like image 463
Kishor Pawar Avatar asked Feb 20 '19 09:02

Kishor Pawar


2 Answers

The has_object_permission is not called for list views. The documentation says the following:

Also note that the generic views will only check the object-level permissions for views that retrieve a single model instance. If you require object-level filtering of list views, you'll need to filter the queryset separately. See the filtering documentation for more details.

like image 106
ikkuh Avatar answered Nov 19 '22 17:11

ikkuh


I ran into the same problem. The has_object_permission function is never called when listing objects.

Even if the following may not be the most efficient solution, you can override the list method in your view as follows, which is how I solved it for me:

from typing import List
import rest_framework.permissions as drf_permissions

def list(self, request, *args, **kwargs):

    # base query set
    queryset: QuerySet = self.filter_queryset(self.get_queryset())

    # check object permissions for each object individually
    valid_pks: List[int] = []  # # storage for keys of valid objects
    permissions: List[drf_permissions.BasePermission] = self.get_permissions()
    for obj in queryset:
        for permission in permissions:
            if permission.has_object_permission(request, self, obj):
                valid_pks.append(obj.pk)

    # remove not valid objects from the queryset
    queryset = queryset.filter(pk__in=valid_pks)

    # ... business as usual (original code)
    page = self.paginate_queryset(queryset)
    if page is not None:
        serializer = self.get_serializer(page, many=True)
        return self.get_paginated_response(serializer.data)

    serializer = self.get_serializer(queryset, many=True)

    return Response(serializer.data)

This basically is the original implementation except that it prefetches the relevant objects and checks the permission individually.

However, this may be DRY in the sense that you don't have to override the get_queryset() method to somehow reinvent your has_object_permission logic. But also it is slow, since it fetches objects twice. You could improve that situation by working with the already prefetched objects instead of filtering the queryset, though.

like image 37
Ric Hard Avatar answered Nov 19 '22 15:11

Ric Hard