Reputation: 6997
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
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