Mo J. Mughrabi
Mo J. Mughrabi

Reputation: 6997

django rest framework has_object_permission raising PermissionDenied exception

Am trying to build a permission class for my django rest framework view, which should either allow or disallow the user access.

class UserPermissions(permissions.DjangoModelPermissions):
    """
    """
    def has_object_permission(self, request, view, obj):
        # if safe methods (get, retrieve) we check permissions from django perms
        if request.method in permissions.SAFE_METHODS:
            return super(UserPermissions, self).has_object_permission(request, view, obj)

        # else if methods (post, put, patch), we match user with obj and if obj is owner we grant him access
        return request.user == obj

the above code seems to be working fine, but while I was build test cases. I built one case where I try to intentionally update user x using user y credentials

def test_patching_someones_else_profile(self):
    """ Test patching someone's else profile """
    response = self.c.post("/api/account/api-token-auth/", {'username': 'lion', 'password': 'password'})
    self.assertEqual(response.status_code, 200, "User couldn't log in")
    token = response.data['token']
    user_id = response.data['id']
    header = {'HTTP_AUTHORIZATION': 'Token {}'.format(token)}

    response = self.c.patch(reverse('user-detail', args=['1', ]), {'last_name': 'mooz'}, **header)

the problem am seeing now, its suddenly throws an exception PermissionDenied(), it does not return any status_code to handle, instead the application is crashing with following exception

======================================================================
ERROR: test_patching_someones_else_profile (dlap.apps.account.tests.UserTestCase)
Test patching someone's else profile
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/mo/Projects/pythonic/dl-env/dlap/apps/account/tests.py", line 217, in test_patching_someones_else_profile
    response = self.c.patch(reverse('user-detail', args=['1', ]), {'last_name': 'mooz'}, **header)
  File "/Users/mo/Projects/pythonic/dl-env/lib/python2.7/site-packages/rest_framework/test.py", line 84, in patch
    return self.generic('PATCH', path, data, content_type, **extra)
  File "/Users/mo/Projects/pythonic/dl-env/lib/python2.7/site-packages/rest_framework/compat.py", line 487, in generic
    return self.request(**r)
  File "/Users/mo/Projects/pythonic/dl-env/lib/python2.7/site-packages/rest_framework/test.py", line 143, in request
    return super(APIClient, self).request(**kwargs)
  File "/Users/mo/Projects/pythonic/dl-env/lib/python2.7/site-packages/rest_framework/test.py", line 95, in request
    request = super(APIRequestFactory, self).request(**kwargs)
  File "/Users/mo/Projects/pythonic/dl-env/lib/python2.7/site-packages/django/test/client.py", line 444, in request
    six.reraise(*exc_info)
  File "/Users/mo/Projects/pythonic/dl-env/lib/python2.7/site-packages/django/core/handlers/base.py", line 139, in get_response
    response = response.render()
  File "/Users/mo/Projects/pythonic/dl-env/lib/python2.7/site-packages/django/template/response.py", line 105, in render
    self.content = self.rendered_content
  File "/Users/mo/Projects/pythonic/dl-env/lib/python2.7/site-packages/rest_framework/response.py", line 59, in rendered_content
    ret = renderer.render(self.data, media_type, context)
  File "/Users/mo/Projects/pythonic/dl-env/lib/python2.7/site-packages/rest_framework/renderers.py", line 577, in render
    context = self.get_context(data, accepted_media_type, renderer_context)
  File "/Users/mo/Projects/pythonic/dl-env/lib/python2.7/site-packages/rest_framework/renderers.py", line 537, in get_context
    raw_data_put_form = self.get_raw_data_form(view, 'PUT', request)
  File "/Users/mo/Projects/pythonic/dl-env/lib/python2.7/site-packages/rest_framework/renderers.py", line 475, in get_raw_data_form
    serializer = view.get_serializer(instance=obj)
  File "/Users/mo/Projects/pythonic/dl-env/lib/python2.7/site-packages/rest_framework/generics.py", line 99, in get_serializer
    serializer_class = self.get_serializer_class()
  File "/Users/mo/Projects/pythonic/dl-env/dlap/apps/account/views.py", line 96, in get_serializer_class
    obj = self.get_object()
  File "/Users/mo/Projects/pythonic/dl-env/dlap/apps/account/views.py", line 107, in get_object
    return super(UserViewSet, self).get_object(queryset=queryset)
  File "/Users/mo/Projects/pythonic/dl-env/lib/python2.7/site-packages/rest_framework/generics.py", line 321, in get_object
    self.check_object_permissions(self.request, obj)
  File "/Users/mo/Projects/pythonic/dl-env/lib/python2.7/site-packages/rest_framework/views.py", line 284, in check_object_permissions
    self.permission_denied(request)
  File "/Users/mo/Projects/pythonic/dl-env/lib/python2.7/site-packages/rest_framework/views.py", line 135, in permission_denied
    raise exceptions.PermissionDenied()
PermissionDenied

Any one faced this issue before? is there a way to return 403 with permission denied message without the app crashing?

Update:

As suggested below that get_seralizer_class() could be causing the problem, it is highly the reason why am facing this error. But, what alternatives do I have to build a dynamic seralizer class method similar to the below?

def get_serializer_class(self):
    # decide on which seralizer to use
    if u'create_guest' in self.action_map.itervalues():
        return UserGuestSerializer

    if u'create_user' in self.action_map.itervalues():
        return PublicUserSerializer
    obj = self.get_object()
    if self.request.user.id == obj.id:
        if u'set_password' in self.action_map.itervalues():
            return PasswordSerializer

        return PrivateUserSerializer
    return PublicUserSerializer

Upvotes: 0

Views: 1542

Answers (1)

Tom Christie
Tom Christie

Reputation: 33901

You appear to be using a custom .get_serializer_class() method that is calling .get_object() for some reason.

File "/Users/mo/Projects/pythonic/dl-env/dlap/apps/account/views.py", line 96, in get_serializer_class
obj = self.get_object()

This is causing the per-object permission checks to be re-run when the Browsable API response is being rendered. Is it possible to reconsider your get_serializer_class implementation?

Edit: Note that you can probably simply inspect self.kargs['pk'] rather than actually calling through to get_object. It doesn't look like you need to perform the full object retrieval in order to make the ownership check.

Upvotes: 1

Related Questions