has_object_permission not called
Asked Answered
B

3

10

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.

Boyish answered 20/2, 2019 at 9:55 Comment(0)
B
11

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.

Baccy answered 20/2, 2019 at 10:1 Comment(3)
Thanks. I overlooked that. Any other idea on how do I filter queryset generally?Boyish
Just to follow up here, I believe to filter the queryset you would implement: def get_queryset(self): in the view. I am a DRF newbie though, so take that into account in trusting my answer.Amick
If you are using a list view then how can you call this method?Ares
O
2

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.

Ostensorium answered 5/11, 2020 at 10:29 Comment(0)
S
0

has_object_permission method will only be called if the view-level has_permission checks have already passed.

look this real-world example

olympia project on github (https://github.com/mozilla/addons-server/blob/master/src/olympia/ratings/permissions.py)

class CanDeleteRatingPermission(BasePermission):
  """A DRF permission class wrapping user_can_delete_rating()."""

    def has_permission(self, request, view):
        return request.user.is_authenticated

    def has_object_permission(self, request, view, obj):
        return user_can_delete_rating(request, obj)

when use this permission class, if has_permission is passed then has_object_permission ( user_can_delete_rating() function ) is called.

Shoran answered 20/11, 2021 at 11:39 Comment(1)
This solution didn't work for me.Halden

© 2022 - 2024 — McMap. All rights reserved.