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 1f95b7f4eb..82bd16cdea 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 @@ -677,6 +677,7 @@ describe('BulkEditorComponent', () => { fixture.detectChanges() expect(workflowService.list).toHaveBeenCalledWith(1, 100000, null, null, { trigger_type: WorkflowTriggerType.Manual, + enabled: true, }) expect(component.manualWorkflows).toHaveLength(1) expect(component.manualWorkflows[0].id).toEqual(5) diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py index 537db40d83..14193aa2de 100644 --- a/src/documents/serialisers.py +++ b/src/documents/serialisers.py @@ -2167,6 +2167,12 @@ class BulkEditSerializer( workflow = Workflow.objects.get(id=parameters["workflow"]) except (Workflow.DoesNotExist, ValueError, TypeError): raise serializers.ValidationError("Workflow does not exist") + request = self.context.get("request") + user = getattr(request, "user", None) + if user is not None and not user.has_perm("documents.view_workflow"): + raise PermissionDenied( + "Insufficient permissions to run this workflow", + ) if not workflow.enabled: raise serializers.ValidationError("Workflow is not enabled") if not workflow.triggers.filter( diff --git a/src/documents/tests/test_api_bulk_edit.py b/src/documents/tests/test_api_bulk_edit.py index 10f9429a31..d1717e0fa4 100644 --- a/src/documents/tests/test_api_bulk_edit.py +++ b/src/documents/tests/test_api_bulk_edit.py @@ -637,9 +637,16 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase): type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_ADDED, ), ) - disabled = self._make_manual_workflow() - disabled.enabled = False - disabled.save() + disabled = Workflow.objects.create( + name="Disabled manual", + order=0, + enabled=False, + ) + disabled.triggers.add( + WorkflowTrigger.objects.create( + type=WorkflowTrigger.WorkflowTriggerType.MANUAL, + ), + ) for label, parameters in ( ("missing workflow", {}), ("nonexistent workflow", {"workflow": 9999}), @@ -670,6 +677,21 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase): self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) m.assert_not_called() + @mock.patch("documents.views.bulk_edit.run_workflow") + def test_run_workflow_requires_workflow_permission(self, m) -> None: + """Owning the documents is not enough without view_workflow permission.""" + self.setup_mock(m, "run_workflow") + user = UserFactory(username="no_wf_perms") + grant_global(user, "change_document") + doc = DocumentFactory(owner=user) + workflow = self._make_manual_workflow() + self.client.force_authenticate(user=user) + + response = self._run_workflow({"workflow": workflow.id}, documents=[doc.id]) + + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + m.assert_not_called() + @mock.patch("documents.serialisers.bulk_edit.set_storage_path") def test_api_set_storage_path(self, m) -> None: """ diff --git a/src/documents/tests/test_api_workflows.py b/src/documents/tests/test_api_workflows.py index 8ce554a225..60fcea92f0 100644 --- a/src/documents/tests/test_api_workflows.py +++ b/src/documents/tests/test_api_workflows.py @@ -124,6 +124,23 @@ class TestApiWorkflows(DirectoriesMixin, APITestCase): self.assertEqual(response.data["count"], 1) self.assertEqual(response.data["results"][0]["id"], manual_workflow.id) + def test_api_get_workflows_filter_enabled(self) -> None: + """ + GIVEN: + - An enabled workflow and a disabled workflow + WHEN: + - Workflows are requested filtered by enabled + THEN: + - Only enabled workflows are returned + """ + Workflow.objects.create(name="Disabled", order=1, enabled=False) + + response = self.client.get(f"{self.ENDPOINT}?enabled=true", format="json") + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data["count"], 1) + self.assertEqual(response.data["results"][0]["id"], self.workflow.id) + def test_api_get_workflow_actions_ordered(self) -> None: """ GIVEN: