diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py index add3610af..8499cf359 100644 --- a/src/documents/serialisers.py +++ b/src/documents/serialisers.py @@ -39,7 +39,6 @@ from drf_spectacular.utils import extend_schema_field from drf_spectacular.utils import extend_schema_serializer from drf_writable_nested.serializers import NestedUpdateMixin from guardian.core import ObjectPermissionChecker -from guardian.shortcuts import get_objects_for_user from guardian.shortcuts import get_users_with_perms from guardian.utils import get_group_obj_perms_model from guardian.utils import get_user_obj_perms_model @@ -80,8 +79,8 @@ from documents.models import WorkflowTrigger from documents.parsers import is_mime_type_supported from documents.permissions import get_document_count_filter_for_user from documents.permissions import get_groups_with_only_permission -from documents.permissions import get_objects_for_user_owner_aware from documents.permissions import has_perms_owner_aware +from documents.permissions import permitted_document_ids from documents.permissions import set_permissions_for_object from documents.regex import validate_regex_pattern from documents.templating.filepath import validate_filepath_template_and_render @@ -1011,13 +1010,8 @@ def _get_viewable_duplicates( ).exclude(pk=document.pk) duplicates = duplicates.filter(root_document__isnull=True) duplicates = duplicates.order_by("-created") - allowed = get_objects_for_user_owner_aware( - user, - "documents.view_document", - Document, - include_deleted=True, - ) - return duplicates.filter(id__in=allowed) + allowed_ids = permitted_document_ids(user, include_deleted=True) + return duplicates.filter(id__in=allowed_ids) class DuplicateDocumentSummarySerializer(serializers.Serializer[dict[str, Any]]): @@ -2659,13 +2653,8 @@ class TaskSerializerV9(serializers.ModelSerializer[PaperlessTask]): user = request.user qs = Document.global_objects.filter(pk=dup_of) if not user.is_staff: - with_perms = get_objects_for_user( - user, - "documents.view_document", - qs, - accept_global_perms=False, - ) - qs = with_perms | qs.filter(owner=user) | qs.filter(owner__isnull=True) + allowed_ids = permitted_document_ids(user, include_deleted=True) + qs = qs.filter(pk__in=allowed_ids) return list(qs.values("id", "title", "deleted_at")) @@ -3515,8 +3504,6 @@ class StoragePathTestSerializer(SerializerWithPerms): document_field = self.fields.get("document") if not isinstance(document_field, serializers.PrimaryKeyRelatedField): return - document_field.queryset = get_objects_for_user_owner_aware( - user, - "documents.view_document", - Document, + document_field.queryset = Document.objects.filter( + id__in=permitted_document_ids(user), ) diff --git a/src/documents/tests/test_permission_filtering_security.py b/src/documents/tests/test_permission_filtering_security.py index 7d84bcb86..c64d907df 100644 --- a/src/documents/tests/test_permission_filtering_security.py +++ b/src/documents/tests/test_permission_filtering_security.py @@ -12,6 +12,7 @@ from guardian.shortcuts import assign_perm from rest_framework.test import APIClient from documents.permissions import permitted_document_ids +from documents.serialisers import _get_viewable_duplicates from documents.tests.factories import DocumentFactory @@ -193,3 +194,21 @@ class TestAiChatAllDocumentsPermissionBoundary: visible_ids = {doc.pk for doc in kwargs["documents"]} assert shared.pk in visible_ids assert not_shared.pk not in visible_ids + + +@pytest.mark.django_db +class TestDuplicateDocumentsPermissionBoundary: + def test_get_viewable_duplicates_includes_soft_deleted_but_respects_perms(self): + owner = User.objects.create_user(username="owner") + stranger = User.objects.create_user(username="mallory") + original = DocumentFactory(owner=owner, checksum="dupe-checksum") + dup_visible = DocumentFactory(owner=owner, checksum="dupe-checksum") + dup_hidden = DocumentFactory(owner=owner, checksum="dupe-checksum") + dup_hidden.delete() # soft delete, should still be found (include_deleted=True) + assign_perm("view_document", stranger, dup_visible) + + result_owner = _get_viewable_duplicates(original, owner) + assert {d.pk for d in result_owner} == {dup_visible.pk, dup_hidden.pk} + + result_stranger = _get_viewable_duplicates(original, stranger) + assert {d.pk for d in result_stranger} == {dup_visible.pk}