Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions adrf/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,14 +142,14 @@ async def check_async_permissions(
return_exceptions=True,
)

for has_permission in has_permissions:
for permission, has_permission in zip(permissions, has_permissions):
if isinstance(has_permission, Exception):
raise has_permission
elif not has_permission:
self.permission_denied(
request,
message=getattr(has_permission, "detail", None),
code=getattr(has_permission, "code", None),
message=getattr(permission, "message", None),
code=getattr(permission, "code", None),
)

def check_sync_permissions(
Expand All @@ -164,7 +164,7 @@ def check_sync_permissions(
if not permission.has_permission(request, self):
self.permission_denied(
request,
message=getattr(permission, "detail", None),
message=getattr(permission, "message", None),
code=getattr(permission, "code", None),
)

Expand Down Expand Up @@ -206,14 +206,16 @@ async def check_async_object_permissions(
return_exceptions=True,
)

for has_object_permission in has_object_permissions:
for permission, has_object_permission in zip(
permissions, has_object_permissions
):
if isinstance(has_object_permission, Exception):
raise has_object_permission
elif not has_object_permission:
self.permission_denied(
request,
message=getattr(has_object_permission, "detail", None),
code=getattr(has_object_permission, "code", None),
message=getattr(permission, "message", None),
code=getattr(permission, "code", None),
)

def check_sync_object_permissions(
Expand All @@ -228,7 +230,7 @@ def check_sync_object_permissions(
if not permission.has_object_permission(request, self, obj):
self.permission_denied(
request,
message=getattr(permission, "detail", None),
message=getattr(permission, "message", None),
code=getattr(permission, "code", None),
)

Expand Down
58 changes: 58 additions & 0 deletions tests/test_object_permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,61 @@ async def test_sync_object_permission_reject(self):
)(request)

self.assertEqual(response.status_code, 403)


class AsyncMessageObjectPermission(BasePermission):
message = "Async object permission denied for a specific reason."
code = "async_obj_denied"

async def has_permission(self, request, view):
return True

async def has_object_permission(self, request, view, obj):
return False


class SyncMessageObjectPermission(BasePermission):
message = "Sync object permission denied for a specific reason."
code = "sync_obj_denied"

def has_permission(self, request, view):
return True

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


class MessageObjectPermissionTestView(ObjectPermissionTestView):
# `permission_denied` short circuits to `NotAuthenticated` when the request
# carries authenticators but none succeeded, which would mask the message
# under test.
authentication_classes = ()


@override_settings(ROOT_URLCONF=__name__)
class TestObjectPermissionDeniedMessage(TestCase):
"""The denial message and code are read from the permission that denied."""

async def test_async_object_permission_denied_message(self):
request = factory.get("/async/reject")

response = await MessageObjectPermissionTestView.as_view(
permission_classes=(AsyncMessageObjectPermission,)
)(request)

self.assertEqual(response.status_code, 403)
self.assertEqual(response.data["detail"], AsyncMessageObjectPermission.message)
self.assertEqual(
response.data["detail"].code, AsyncMessageObjectPermission.code
)

async def test_sync_object_permission_denied_message(self):
request = factory.get("/sync/reject")

response = await MessageObjectPermissionTestView.as_view(
permission_classes=(SyncMessageObjectPermission,)
)(request)

self.assertEqual(response.status_code, 403)
self.assertEqual(response.data["detail"], SyncMessageObjectPermission.message)
self.assertEqual(response.data["detail"].code, SyncMessageObjectPermission.code)
71 changes: 71 additions & 0 deletions tests/test_permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,3 +214,74 @@ async def test_sync_first_complex_mixed_permission(
mock_async_accept.assert_awaited()
mock_sync_reject.assert_called()
self.assertEqual(response.status_code, 200)


class AsyncMessagePermission(AsyncBasePermission):
message = "Async permission denied for a specific reason."
code = "async_denied"

async def has_permission(self, request, view):
return False


class SyncMessagePermission(BasePermission):
message = "Sync permission denied for a specific reason."
code = "sync_denied"

def has_permission(self, request, view):
return False


class AsyncAllowPermission(AsyncBasePermission):
message = "This permission allowed the request, so its message must not be used."

async def has_permission(self, request, view):
return True


class MessageView(APIView):
# `permission_denied` short circuits to `NotAuthenticated` when the request
# carries authenticators but none succeeded, which would mask the message
# under test.
authentication_classes = ()

async def get(self, request):
return HttpResponse("ok")


@override_settings(ROOT_URLCONF=__name__)
class TestPermissionDeniedMessage(TestCase):
"""The denial message and code are read from the permission that denied."""

async def test_async_permission_denied_message(self):
request = factory.get("/view/async/reject/")

response = await MessageView.as_view(
permission_classes=(AsyncMessagePermission,)
)(request)

self.assertEqual(response.status_code, 403)
self.assertEqual(response.data["detail"], AsyncMessagePermission.message)
self.assertEqual(response.data["detail"].code, AsyncMessagePermission.code)

async def test_sync_permission_denied_message(self):
request = factory.get("/view/sync/reject/")

response = await MessageView.as_view(
permission_classes=(SyncMessagePermission,)
)(request)

self.assertEqual(response.status_code, 403)
self.assertEqual(response.data["detail"], SyncMessagePermission.message)
self.assertEqual(response.data["detail"].code, SyncMessagePermission.code)

async def test_async_permission_denied_message_of_the_denying_permission(self):
"""The message must come from the permission that actually returned False."""
request = factory.get("/view/async/reject/")

response = await MessageView.as_view(
permission_classes=(AsyncAllowPermission, AsyncMessagePermission)
)(request)

self.assertEqual(response.status_code, 403)
self.assertEqual(response.data["detail"], AsyncMessagePermission.message)
Loading