Skip to content

Commit 8329411

Browse files
committed
Merge pull request #2539 from donewell/permission-detail
add message to custom permission
2 parents 8d4c96e + 3d25dad commit 8329411

File tree

3 files changed

+104
-4
lines changed

3 files changed

+104
-4
lines changed

docs/api-guide/permissions.md

+10
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,16 @@ If you need to test if a request is a read operation or a write operation, you s
190190

191191
---
192192

193+
Custom permissions will raise a `PermissionDenied` exception if the test fails. To change the error message associated with the exception, implement a `message` attribute directly on your custom permission. Otherwise the `default_detail` attribute from `PermissionDenied` will be used.
194+
195+
from rest_framework import permissions
196+
197+
class CustomerAccessPermission(permissions.BasePermission):
198+
message = 'Adding customers not allowed.'
199+
200+
def has_permission(self, request, view):
201+
...
202+
193203
## Examples
194204

195205
The following is an example of a permission class that checks the incoming request's IP address against a blacklist, and denies the request if the IP has been blacklisted.

rest_framework/views.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -144,13 +144,13 @@ def http_method_not_allowed(self, request, *args, **kwargs):
144144
"""
145145
raise exceptions.MethodNotAllowed(request.method)
146146

147-
def permission_denied(self, request):
147+
def permission_denied(self, request, message=None):
148148
"""
149149
If request is not permitted, determine what kind of exception to raise.
150150
"""
151151
if not request.successful_authenticator:
152152
raise exceptions.NotAuthenticated()
153-
raise exceptions.PermissionDenied()
153+
raise exceptions.PermissionDenied(detail=message)
154154

155155
def throttled(self, request, wait):
156156
"""
@@ -302,7 +302,9 @@ def check_permissions(self, request):
302302
"""
303303
for permission in self.get_permissions():
304304
if not permission.has_permission(request, self):
305-
self.permission_denied(request)
305+
self.permission_denied(
306+
request, message=getattr(permission, 'message', None)
307+
)
306308

307309
def check_object_permissions(self, request, obj):
308310
"""
@@ -311,7 +313,9 @@ def check_object_permissions(self, request, obj):
311313
"""
312314
for permission in self.get_permissions():
313315
if not permission.has_object_permission(request, self, obj):
314-
self.permission_denied(request)
316+
self.permission_denied(
317+
request, message=getattr(permission, 'message', None)
318+
)
315319

316320
def check_throttles(self, request):
317321
"""

tests/test_permissions.py

+86
Original file line numberDiff line numberDiff line change
@@ -376,3 +376,89 @@ def test_cannot_read_list_permissions(self):
376376
response = object_permissions_list_view(request)
377377
self.assertEqual(response.status_code, status.HTTP_200_OK)
378378
self.assertListEqual(response.data, [])
379+
380+
381+
class BasicPerm(permissions.BasePermission):
382+
def has_permission(self, request, view):
383+
return False
384+
385+
386+
class BasicPermWithDetail(permissions.BasePermission):
387+
message = 'Custom: You cannot access this resource'
388+
389+
def has_permission(self, request, view):
390+
return False
391+
392+
393+
class BasicObjectPerm(permissions.BasePermission):
394+
def has_object_permission(self, request, view, obj):
395+
return False
396+
397+
398+
class BasicObjectPermWithDetail(permissions.BasePermission):
399+
message = 'Custom: You cannot access this resource'
400+
401+
def has_object_permission(self, request, view, obj):
402+
return False
403+
404+
405+
class PermissionInstanceView(generics.RetrieveUpdateDestroyAPIView):
406+
queryset = BasicModel.objects.all()
407+
serializer_class = BasicSerializer
408+
409+
410+
class DeniedView(PermissionInstanceView):
411+
permission_classes = (BasicPerm,)
412+
413+
414+
class DeniedViewWithDetail(PermissionInstanceView):
415+
permission_classes = (BasicPermWithDetail,)
416+
417+
418+
class DeniedObjectView(PermissionInstanceView):
419+
permission_classes = (BasicObjectPerm,)
420+
421+
422+
class DeniedObjectViewWithDetail(PermissionInstanceView):
423+
permission_classes = (BasicObjectPermWithDetail,)
424+
425+
denied_view = DeniedView.as_view()
426+
427+
denied_view_with_detail = DeniedViewWithDetail.as_view()
428+
429+
denied_object_view = DeniedObjectView.as_view()
430+
431+
denied_object_view_with_detail = DeniedObjectViewWithDetail.as_view()
432+
433+
434+
class CustomPermissionsTests(TestCase):
435+
def setUp(self):
436+
BasicModel(text='foo').save()
437+
User.objects.create_user('username', '[email protected]', 'password')
438+
credentials = basic_auth_header('username', 'password')
439+
self.request = factory.get('/1', format='json', HTTP_AUTHORIZATION=credentials)
440+
self.custom_message = 'Custom: You cannot access this resource'
441+
442+
def test_permission_denied(self):
443+
response = denied_view(self.request, pk=1)
444+
detail = response.data.get('detail')
445+
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
446+
self.assertNotEqual(detail, self.custom_message)
447+
448+
def test_permission_denied_with_custom_detail(self):
449+
response = denied_view_with_detail(self.request, pk=1)
450+
detail = response.data.get('detail')
451+
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
452+
self.assertEqual(detail, self.custom_message)
453+
454+
def test_permission_denied_for_object(self):
455+
response = denied_object_view(self.request, pk=1)
456+
detail = response.data.get('detail')
457+
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
458+
self.assertNotEqual(detail, self.custom_message)
459+
460+
def test_permission_denied_for_object_with_custom_detail(self):
461+
response = denied_object_view_with_detail(self.request, pk=1)
462+
detail = response.data.get('detail')
463+
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
464+
self.assertEqual(detail, self.custom_message)

0 commit comments

Comments
 (0)