From 9d9b0aac1ac7a5e443f805a5c165ad4187a89e75 Mon Sep 17 00:00:00 2001 From: stumpylog <797416+stumpylog@users.noreply.github.com> Date: Mon, 3 Aug 2026 08:31:07 -0700 Subject: [PATCH] fix: normalize qualified permission strings in permitted_document_ids Guardian's UserObjectPermission/GroupObjectPermission always store a bare codename, but has_perm()-style callers commonly pass the qualified "app_label.codename" form. Passing that qualified form here previously matched zero rows, silently under-permitting. content_type already disambiguates the codename, so just strip any prefix instead. Co-Authored-By: Claude Sonnet 5 --- src/documents/permissions.py | 6 ++++++ .../tests/test_permission_filtering_security.py | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/documents/permissions.py b/src/documents/permissions.py index 65609618a..a97df8ec4 100644 --- a/src/documents/permissions.py +++ b/src/documents/permissions.py @@ -188,6 +188,12 @@ def permitted_document_ids( if getattr(user, "is_superuser", False): return base_docs.values_list("id", flat=True) + # Guardian's UserObjectPermission/GroupObjectPermission always store a bare + # codename, but has_perm()-style callers commonly pass the qualified + # "app_label.codename" form. content_type already disambiguates the + # codename, so just drop any prefix rather than silently under-permitting. + perm = perm.rsplit(".", 1)[-1] + document_ct = ContentType.objects.get_for_model(Document) perm_filter = { "permission__codename": perm, diff --git a/src/documents/tests/test_permission_filtering_security.py b/src/documents/tests/test_permission_filtering_security.py index bf6ab26ca..4ec6cf13c 100644 --- a/src/documents/tests/test_permission_filtering_security.py +++ b/src/documents/tests/test_permission_filtering_security.py @@ -241,6 +241,18 @@ class TestPermittedDocumentIdsArbitraryPermission: expected_hidden=[doc.pk], ) + def test_qualified_permission_string_is_normalized_to_codename(self): + owner = User.objects.create_user(username="owner") + editor = User.objects.create_user(username="editor") + doc = DocumentFactory(owner=owner) + assign_perm("change_document", editor, doc) + + assert_visible_document_ids( + permitted_document_ids(editor, perm="documents.change_document"), + expected_visible=[doc.pk], + expected_hidden=[], + ) + def test_delete_permission_with_include_deleted_for_trash_restore(self): owner = User.objects.create_user(username="owner") stranger = User.objects.create_user(username="mallory")