diff --git a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.spec.ts b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.spec.ts index b5e455dfc5..7a95003efa 100644 --- a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.spec.ts +++ b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.spec.ts @@ -728,6 +728,27 @@ describe('BulkEditorComponent', () => { ) // listAllFilteredIds }) + it('should require ownership of selected documents to run a workflow', () => { + component.manualWorkflows = [{ id: 5 }] as any + jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true) + jest + .spyOn(permissionsService, 'currentUserHasObjectPermissions') + .mockReturnValue(true) + const ownsDocument = jest + .spyOn(permissionsService, 'currentUserOwnsObject') + .mockReturnValue(false) + jest + .spyOn(documentListViewService, 'documents', 'get') + .mockReturnValue([{ id: 3 }]) + jest + .spyOn(documentListViewService, 'selected', 'get') + .mockReturnValue(new Set([3])) + + expect(component.canRunWorkflow).toBe(false) + ownsDocument.mockReturnValue(true) + expect(component.canRunWorkflow).toBe(true) + }) + it('should set modal dialog text accordingly for tag edit confirmation', () => { let modal: NgbModalRef modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1])) 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 5bea36804a..6e7ab2bf7a 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 @@ -308,7 +308,9 @@ export class BulkEditorComponent } get canRunWorkflow(): boolean { - return this.userCanEditAll && this.manualWorkflows.length > 0 + return ( + this.userCanEditAll && this.userOwnsAll && this.manualWorkflows.length > 0 + ) } runWorkflowSelected() { diff --git a/src/documents/permissions.py b/src/documents/permissions.py index 196b145c5c..1b7537061b 100644 --- a/src/documents/permissions.py +++ b/src/documents/permissions.py @@ -789,6 +789,7 @@ def has_document_operation_permissions( bulk_edit.edit_pdf, bulk_edit.merge_as_versions, bulk_edit.remove_password, + bulk_edit.run_workflow, ] ) or ( diff --git a/src/documents/tests/test_api_bulk_edit.py b/src/documents/tests/test_api_bulk_edit.py index 1f8700a113..6a389be8ca 100644 --- a/src/documents/tests/test_api_bulk_edit.py +++ b/src/documents/tests/test_api_bulk_edit.py @@ -703,11 +703,12 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase): @mock.patch("documents.views.bulk_edit.run_workflow") def test_run_workflow_requires_ownership(self, m) -> None: - """A non-owner without document ownership is forbidden (actions mutate docs).""" + """A non-owner with change permission cannot run mutating workflow actions.""" self.setup_mock(m, "run_workflow") - user = UserFactory(username="no_perms") + user = UserFactory(username="non_owner") grant_all_global(user) doc = DocumentFactory(owner=UserFactory(username="other")) + grant_object(user, doc, "change_document") workflow = self._make_manual_workflow() self.client.force_authenticate(user=user)