fix: use the permission's message for denial responses - #91
Open
mmohajer9 wants to merge 1 commit into
Open
Conversation
Permission denials on `adrf.views.APIView` always fell back to DRF's generic "You do not have permission to perform this action.", discarding any custom `message` set on the permission class. Two separate causes, across the four leaf checks: 1. All four read `permission.detail`, but DRF permission classes carry the denial text on `.message` (see `BasePermission.message` and `rest_framework.views.APIView.check_permissions`). `.detail` is the attribute on `APIException`, not on permissions, so the getattr always returned `None`. 2. `check_async_permissions` and `check_async_object_permissions` additionally read the attribute off the *boolean result* of `asyncio.gather` rather than off the permission object, so even the correct attribute name could never have resolved. The results are now zipped back with their permissions. The `code` attribute was affected by (2) in the same way. This makes async views report the same denial reason as the equivalent sync DRF view. Existing tests only asserted `status_code == 403` and never inspected the response body, which is how this went unnoticed; the added tests assert the message and code for the sync, async, object and non-object paths.
mmohajer9
force-pushed
the
fix/permission-denied-message
branch
from
August 7, 2026 18:52
bcd2b15 to
46746fc
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Permission denials on
adrf.views.APIViewalways render DRF's generic "You do not have permission to perform this action.", silently discarding any custommessageset on the permission class. The equivalent sync DRF view reports the real reason, so moving a viewset to adrf quietly degrades every custom denial message.There are two distinct causes, spread across the four leaf checks in
adrf/views.py:1. Wrong attribute name (all four methods). They read
getattr(permission, "detail", None), but DRF permission classes carry the denial text on.message:and
rest_framework.views.APIView.check_permissionsreads it accordingly:.detailis the attribute onAPIException, not on permissions, so thegetattralways fell through toNone.2. Wrong object (async methods only).
check_async_permissionsandcheck_async_object_permissionsread the attribute offhas_permission— the boolean result ofasyncio.gather— rather than off the permission instance:Since the loop had already discarded the permission objects, even the correct attribute name could not have resolved here.
codewas affected the same way.Fix
Read
message/codefrom the permission object in all four methods, and zip the gathered results back with their permissions in the two async ones. This makes the behaviour identical torest_framework.views.APIView.check_permissions.Why this wasn't caught
tests/test_permissions.pyandtests/test_object_permissions.pyonly assertstatus_code == 403and never inspect the response body, so a denial with the wrong message passes.This PR adds tests asserting the resulting
detailand itscodefor all four paths — sync, async, object and non-object — plus one that verifies the message comes from the permission that actually returnedFalsewhen an allowing permission precedes it (which is what the missing zip broke).Note the new test views set
authentication_classes = ():permission_deniedshort circuits toNotAuthenticatedwhen the request carries authenticators but none succeeded, which would otherwise mask the message under test.Verification
(129 before this change, 5 new.) All 5 new tests fail on
mainwith the generic message and pass with the fix.ruff checkandruff format --checkare clean under the pinnedv0.5.5from.pre-commit-config.yaml.Compatibility
No public API change. A permission that never set
.messagekeeps producing the generic default. Projects that had worked around this by setting.detailon a permission class would lose that workaround — but that attribute was never read by DRF itself and only ever resolved toNonein the async paths here.