From ad86ea01afd76f7eb8b08b9443792ca405e5f7a2 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Thu, 13 Aug 2026 14:32:48 -0700 Subject: [PATCH] Enforce delete_document too --- .../bulk-editor/bulk-editor.component.html | 2 +- .../bulk-editor/bulk-editor.component.ts | 7 +++++ .../tests/test_merge_documents_as_versions.py | 27 +++++++++++++++++++ src/documents/views.py | 3 +++ 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.html b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.html index a5e5d2cc0..39f93d0b2 100644 --- a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.html +++ b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.html @@ -95,7 +95,7 @@ - diff --git a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts index 497bf0de7..6c216217d 100644 --- a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts +++ b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts @@ -172,6 +172,13 @@ export class BulkEditorComponent ) } + get userCanDelete(): boolean { + return this.permissionService.currentUserCan( + PermissionAction.Delete, + PermissionType.Document + ) + } + ngOnInit() { if ( this.permissionService.currentUserCan( diff --git a/src/documents/tests/test_merge_documents_as_versions.py b/src/documents/tests/test_merge_documents_as_versions.py index 7bc993857..5fe55cb0b 100644 --- a/src/documents/tests/test_merge_documents_as_versions.py +++ b/src/documents/tests/test_merge_documents_as_versions.py @@ -405,6 +405,7 @@ class TestMergeDocumentsAsVersionsAPI(APITestCase): self.user.user_permissions.add( Permission.objects.get(codename="change_document"), Permission.objects.get(codename="view_document"), + Permission.objects.get(codename="delete_document"), ) self.doc1 = Document.objects.create( checksum="A", @@ -466,6 +467,32 @@ class TestMergeDocumentsAsVersionsAPI(APITestCase): self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) merge_mock.assert_not_called() + @mock.patch("documents.views.bulk_edit.merge_as_versions") + def test_requires_delete_permission(self, merge_mock) -> None: + merge_mock.__name__ = "merge_as_versions" + # Owns them and may change them, but may not make them stop being documents + user = User.objects.create_user(username="no-delete") + user.user_permissions.add( + Permission.objects.get(codename="change_document"), + Permission.objects.get(codename="view_document"), + ) + for doc in (self.doc1, self.doc2): + doc.owner = user + doc.save() + self.client.force_authenticate(user=user) + + response = self.client.post( + "/api/documents/merge_as_versions/", + { + "documents": [self.doc1.id, self.doc2.id], + "root_document_id": self.doc1.id, + }, + format="json", + ) + + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + merge_mock.assert_not_called() + @mock.patch("documents.views.bulk_edit.merge_as_versions") def test_rejects_unselected_root(self, merge_mock) -> None: doc3 = Document.objects.create( diff --git a/src/documents/views.py b/src/documents/views.py index 84568b38b..fd3960586 100644 --- a/src/documents/views.py +++ b/src/documents/views.py @@ -2843,6 +2843,9 @@ class DocumentOperationPermissionMixin(PassUserMixin, DocumentSelectionMixin): has_perms and ( method == bulk_edit.delete + # Sources stop being documents of their own, and removing one + # again afterwards needs delete_document + or method == bulk_edit.merge_as_versions or ( method in [bulk_edit.merge, bulk_edit.split] and parameters.get("delete_originals")