Django-rest-framework: Object permissions are not checked on custom viewset routes

Created on 23 May 2016  路  4Comments  路  Source: encode/django-rest-framework

Steps to reproduce

class MyViewSet(viewsets.ModelViewSet):
    queryset = MyModel.objects.all()
    serializer_class = MySerializer
    authentication_classes = (MyAuth, )
    permission_classes = (MyPermissions, )

    @detail_route(methods=["GET", ])
    def custom(self, request, pk=None):
        return Response('whatever')

from rest_framework import routers
router = routers.SimpleRouter() 
router.register('mymodel', MyViewSet)
urlpatterns = [
    url(r'^api/', include(router.urls)),
]

make a GET request to /api/mymodel/123/custom/

Expected behavior

has_object_permission() should be called with MyModel.objects.get(pk=123) as object.

Actual behavior

request to the custom route in the form of /api/mymodel/123/custom/ will call has_permission() in MyPermissions, but not has_object_permission() even though it is a detail route.

Documentation

Most helpful comment

We need to document the fact that you'll only get the has_object_permission called only if you call the get_object.

All 4 comments

We need to document the fact that you'll only get the has_object_permission called only if you call the get_object.

This seems counter-intuitive. Calls like /api/object/123/method/ are generally used to call some method on the object instance with ID 123 and it seems that checking object permission would be an expected behavior. Otherwise every time someone makes custom detail routes, they would have to retrieve the object, manually check permissions and proceed. At that point, calling has_object_permission() makes no sense anyway. So in every scenario where object level permissions are used some boilerplate like this would have to be used

class CustomPermission(BasePermission):
    def has_permission(self, request, view):
        """we need to do all permission checking here, since has_object_permission() is not guaranteed to be called"""
        if 'pk' in view.kwargs and view.kwargs['pk']:
            obj = view.get_queryset()[0]
            # check object permissions here
        else:
            # check model permissions here

    def has_object_permission(self, request, view, obj):
        """ nothing to do here, we already checked everything, so ignore """
        return True

I think I get it now. If I call get_object() in my custom routes, has_object_permission() will get called. Still seems counter-intuitive, but at least there is a clear solution.

I'd be happy to consider a concrete pull-request for documentation, but as it stands I think what we've got is about adequate in http://www.django-rest-framework.org/api-guide/permissions/#object-level-permissions

Closing this, but as noted specfic rephrasing/additions _could_ be considered.

Was this page helpful?
0 / 5 - 0 ratings