From 158cbb4bfd8d11315a940ddc44a08f411e6e848a Mon Sep 17 00:00:00 2001 From: jasquat Date: Thu, 5 Jan 2023 14:59:59 -0500 Subject: [PATCH] basic support to find a process instance by id w/ burnettk --- src/spiffworkflow_backend/api.yml | 21 +++ .../config/permissions/testing.yml | 6 + .../models/process_model.py | 8 + .../routes/process_instances_controller.py | 48 ++++-- .../services/authorization_service.py | 139 ++++++++++-------- .../helpers/base_test.py | 6 +- .../test_process_instances_controller.py | 51 +++++++ .../unit/test_authorization_service.py | 1 + 8 files changed, 201 insertions(+), 79 deletions(-) create mode 100644 tests/spiffworkflow_backend/integration/test_process_instances_controller.py diff --git a/src/spiffworkflow_backend/api.yml b/src/spiffworkflow_backend/api.yml index d96de3db..6c720265 100755 --- a/src/spiffworkflow_backend/api.yml +++ b/src/spiffworkflow_backend/api.yml @@ -946,6 +946,27 @@ paths: schema: $ref: "#/components/schemas/Workflow" + /process-instances/find-by-id/{process_instance_id}: + parameters: + - name: process_instance_id + in: path + required: true + description: The unique id of an existing process instance. + schema: + type: integer + get: + operationId: spiffworkflow_backend.routes.process_instances_controller.process_instance_find_by_id + summary: Find a process instance based on its id only + tags: + - Process Instances + responses: + "200": + description: One Process Instance + content: + application/json: + schema: + $ref: "#/components/schemas/Workflow" + /process-instances/{modified_process_model_identifier}/{process_instance_id}: parameters: - name: modified_process_model_identifier diff --git a/src/spiffworkflow_backend/config/permissions/testing.yml b/src/spiffworkflow_backend/config/permissions/testing.yml index 79a13710..d3edf0a8 100644 --- a/src/spiffworkflow_backend/config/permissions/testing.yml +++ b/src/spiffworkflow_backend/config/permissions/testing.yml @@ -30,6 +30,12 @@ permissions: allowed_permissions: [read] uri: /* + process-instances-find-by-id: + groups: [everybody] + users: [] + allowed_permissions: [read] + uri: /process-instances/find-by-id/* + tasks-crud: groups: [everybody] users: [] diff --git a/src/spiffworkflow_backend/models/process_model.py b/src/spiffworkflow_backend/models/process_model.py index c737b274..5e0ba6ca 100644 --- a/src/spiffworkflow_backend/models/process_model.py +++ b/src/spiffworkflow_backend/models/process_model.py @@ -58,6 +58,14 @@ class ProcessModelInfo: """Id_for_file_path.""" return self.id.replace("/", os.sep) + @classmethod + def modify_process_identifier_for_path_param(cls, identifier: str) -> str: + """Identifier.""" + if "\\" in identifier: + raise Exception(f"Found backslash in identifier: {identifier}") + + return identifier.replace("/", ":") + class ProcessModelInfoSchema(Schema): """ProcessModelInfoSchema.""" diff --git a/src/spiffworkflow_backend/routes/process_instances_controller.py b/src/spiffworkflow_backend/routes/process_instances_controller.py index ed27f2b2..64e0f133 100644 --- a/src/spiffworkflow_backend/routes/process_instances_controller.py +++ b/src/spiffworkflow_backend/routes/process_instances_controller.py @@ -1,5 +1,7 @@ """APIs for dealing with process groups, process models, and process instances.""" import json +from spiffworkflow_backend.services.authorization_service import AuthorizationService +from spiffworkflow_backend.models.process_model import ProcessModelInfo from typing import Any from typing import Dict from typing import Optional @@ -88,9 +90,7 @@ def process_instance_run( do_engine_steps: bool = True, ) -> flask.wrappers.Response: """Process_instance_run.""" - process_instance = ProcessInstanceService().get_process_instance( - process_instance_id - ) + process_instance = _find_process_instance_by_id_or_raise(process_instance_id) if process_instance.status != "not_started": raise ApiError( error_code="process_instance_not_runnable", @@ -138,9 +138,7 @@ def process_instance_terminate( modified_process_model_identifier: str, ) -> flask.wrappers.Response: """Process_instance_run.""" - process_instance = ProcessInstanceService().get_process_instance( - process_instance_id - ) + process_instance = _find_process_instance_by_id_or_raise(process_instance_id) processor = ProcessInstanceProcessor(process_instance) processor.terminate() return Response(json.dumps({"ok": True}), status=200, mimetype="application/json") @@ -151,9 +149,7 @@ def process_instance_suspend( modified_process_model_identifier: str, ) -> flask.wrappers.Response: """Process_instance_suspend.""" - process_instance = ProcessInstanceService().get_process_instance( - process_instance_id - ) + process_instance = _find_process_instance_by_id_or_raise(process_instance_id) processor = ProcessInstanceProcessor(process_instance) processor.suspend() return Response(json.dumps({"ok": True}), status=200, mimetype="application/json") @@ -164,9 +160,7 @@ def process_instance_resume( modified_process_model_identifier: str, ) -> flask.wrappers.Response: """Process_instance_resume.""" - process_instance = ProcessInstanceService().get_process_instance( - process_instance_id - ) + process_instance = _find_process_instance_by_id_or_raise(process_instance_id) processor = ProcessInstanceProcessor(process_instance) processor.resume() return Response(json.dumps({"ok": True}), status=200, mimetype="application/json") @@ -575,14 +569,38 @@ def process_instance_reset( spiff_step: int = 0, ) -> flask.wrappers.Response: """Reset a process instance to a particular step.""" - process_instance = ProcessInstanceService().get_process_instance( - process_instance_id - ) + process_instance = _find_process_instance_by_id_or_raise(process_instance_id) processor = ProcessInstanceProcessor(process_instance) processor.reset_process(spiff_step) return Response(json.dumps({"ok": True}), status=200, mimetype="application/json") +def process_instance_find_by_id( + process_instance_id: int, +) -> flask.wrappers.Response: + + process_instance = _find_process_instance_by_id_or_raise(process_instance_id) + modified_process_model_identifier = ProcessModelInfo.modify_process_identifier_for_path_param(process_instance.process_model_identifier) + process_instance_uri = f'/process-instances/{modified_process_model_identifier}/{process_instance.id}' + has_permission = AuthorizationService.user_has_permission( + user=g.user, + permission='read', + target_uri=process_instance_uri, + ) + + uri_type = None + if not has_permission: + process_instance = _find_process_instance_for_me_or_raise(process_instance_id) + uri_type = 'for-me' + + response_json = { + "process_instance": process_instance, + "uri_type": uri_type, + + } + return make_response(jsonify(response_json), 200) + + def _get_process_instance( modified_process_model_identifier: str, process_instance: ProcessInstanceModel, diff --git a/src/spiffworkflow_backend/services/authorization_service.py b/src/spiffworkflow_backend/services/authorization_service.py index 69d19cb7..ebc26aea 100644 --- a/src/spiffworkflow_backend/services/authorization_service.py +++ b/src/spiffworkflow_backend/services/authorization_service.py @@ -624,6 +624,83 @@ class AuthorizationService: return permissions_to_assign + @classmethod + def set_basic_permissions(cls) -> list[PermissionToAssign]: + permissions_to_assign: list[PermissionToAssign] = [] + permissions_to_assign.append( + PermissionToAssign( + permission="read", target_uri="/process-instances/for-me" + ) + ) + permissions_to_assign.append( + PermissionToAssign(permission="read", target_uri="/processes") + ) + permissions_to_assign.append( + PermissionToAssign(permission="read", target_uri="/service-tasks") + ) + permissions_to_assign.append( + PermissionToAssign( + permission="read", target_uri="/user-groups/for-current-user" + ) + ) + permissions_to_assign.append( + PermissionToAssign( + permission="read", target_uri="/process-instances/find-by-id/*" + ) + ) + + for permission in ["create", "read", "update", "delete"]: + permissions_to_assign.append( + PermissionToAssign( + permission=permission, target_uri="/process-instances/reports/*" + ) + ) + permissions_to_assign.append( + PermissionToAssign(permission=permission, target_uri="/tasks/*") + ) + return permissions_to_assign + + @classmethod + def set_process_group_permissions(cls, target: str, permission_set: str) -> list[PermissionToAssign]: + permissions_to_assign: list[PermissionToAssign] = [] + process_group_identifier = ( + target.removeprefix("PG:").replace("/", ":").removeprefix(":") + ) + process_related_path_segment = f"{process_group_identifier}:*" + if process_group_identifier == "ALL": + process_related_path_segment = "*" + target_uris = [ + f"/process-groups/{process_related_path_segment}", + f"/process-models/{process_related_path_segment}", + ] + permissions_to_assign = ( + permissions_to_assign + + cls.get_permissions_to_assign( + permission_set, process_related_path_segment, target_uris + ) + ) + return permissions_to_assign + + @classmethod + def set_process_model_permissions(cls, target: str, permission_set: str) -> list[PermissionToAssign]: + permissions_to_assign: list[PermissionToAssign] = [] + process_model_identifier = ( + target.removeprefix("PM:").replace("/", ":").removeprefix(":") + ) + process_related_path_segment = f"{process_model_identifier}/*" + + if process_model_identifier == "ALL": + process_related_path_segment = "*" + + target_uris = [f"/process-models/{process_related_path_segment}"] + permissions_to_assign = ( + permissions_to_assign + + cls.get_permissions_to_assign( + permission_set, process_related_path_segment, target_uris + ) + ) + return permissions_to_assign + @classmethod def explode_permissions( cls, permission_set: str, target: str @@ -654,72 +731,16 @@ class AuthorizationService: permissions = ["create", "read", "update", "delete"] if target.startswith("PG:"): - process_group_identifier = ( - target.removeprefix("PG:").replace("/", ":").removeprefix(":") - ) - process_related_path_segment = f"{process_group_identifier}:*" - if process_group_identifier == "ALL": - process_related_path_segment = "*" - target_uris = [ - f"/process-groups/{process_related_path_segment}", - f"/process-models/{process_related_path_segment}", - ] - permissions_to_assign = ( - permissions_to_assign - + cls.get_permissions_to_assign( - permission_set, process_related_path_segment, target_uris - ) - ) - + permissions_to_assign += cls.set_process_group_permissions(target, permission_set) elif target.startswith("PM:"): - process_model_identifier = ( - target.removeprefix("PM:").replace("/", ":").removeprefix(":") - ) - process_related_path_segment = f"{process_model_identifier}/*" - - if process_model_identifier == "ALL": - process_related_path_segment = "*" - - target_uris = [f"/process-models/{process_related_path_segment}"] - permissions_to_assign = ( - permissions_to_assign - + cls.get_permissions_to_assign( - permission_set, process_related_path_segment, target_uris - ) - ) - + permissions_to_assign += cls.set_process_model_permissions(target, permission_set) elif permission_set == "start": raise InvalidPermissionError( "Permission 'start' is only available for macros PM and PG." ) elif target.startswith("BASIC"): - permissions_to_assign.append( - PermissionToAssign( - permission="read", target_uri="/process-instances/for-me" - ) - ) - permissions_to_assign.append( - PermissionToAssign(permission="read", target_uri="/processes") - ) - permissions_to_assign.append( - PermissionToAssign(permission="read", target_uri="/service-tasks") - ) - permissions_to_assign.append( - PermissionToAssign( - permission="read", target_uri="/user-groups/for-current-user" - ) - ) - - for permission in ["create", "read", "update", "delete"]: - permissions_to_assign.append( - PermissionToAssign( - permission=permission, target_uri="/process-instances/reports/*" - ) - ) - permissions_to_assign.append( - PermissionToAssign(permission=permission, target_uri="/tasks/*") - ) + permissions_to_assign += cls.set_basic_permissions() elif target == "ALL": for permission in permissions: permissions_to_assign.append( diff --git a/tests/spiffworkflow_backend/helpers/base_test.py b/tests/spiffworkflow_backend/helpers/base_test.py index 47cf2d87..1f0e9dc1 100644 --- a/tests/spiffworkflow_backend/helpers/base_test.py +++ b/tests/spiffworkflow_backend/helpers/base_test.py @@ -354,11 +354,7 @@ class BaseTest: assert has_permission is expected_result def modify_process_identifier_for_path_param(self, identifier: str) -> str: - """Identifier.""" - if "\\" in identifier: - raise Exception(f"Found backslash in identifier: {identifier}") - - return identifier.replace("/", ":") + return ProcessModelInfo.modify_process_identifier_for_path_param(identifier) def un_modify_modified_process_identifier_for_path_param( self, modified_identifier: str diff --git a/tests/spiffworkflow_backend/integration/test_process_instances_controller.py b/tests/spiffworkflow_backend/integration/test_process_instances_controller.py new file mode 100644 index 00000000..2577ff51 --- /dev/null +++ b/tests/spiffworkflow_backend/integration/test_process_instances_controller.py @@ -0,0 +1,51 @@ +"""Test_users_controller.""" +from flask.app import Flask +from tests.spiffworkflow_backend.helpers.test_data import load_test_spec +from flask.testing import FlaskClient +from tests.spiffworkflow_backend.helpers.base_test import BaseTest + +from spiffworkflow_backend.models.user import UserModel + + +class TestProcessInstancesController(BaseTest): + def test_find_by_id( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + with_super_admin_user: UserModel, + ) -> None: + """Test_user_search_returns_a_user.""" + user_one = self.create_user_with_permission(username="user_one", target_uri="/process-instances/find-by-id/*") + user_two = self.create_user_with_permission(username="user_two", target_uri="/process-instances/find-by-id/*") + + process_model = load_test_spec( + process_model_id="group/sample", + bpmn_file_name="sample.bpmn", + process_model_source_directory="sample", + ) + process_instance = self.create_process_instance_from_process_model( + process_model=process_model, user=user_one + ) + + response = client.get( + f"/v1.0/process-instances/find-by-id/{process_instance.id}", + headers=self.logged_in_headers(user_one), + ) + assert response.status_code == 200 + assert response.json + assert response.json['id'] == process_instance.id + + response = client.get( + f"/v1.0/process-instances/find-by-id/{process_instance.id}", + headers=self.logged_in_headers(user_two), + ) + assert response.status_code == 400 + + response = client.get( + f"/v1.0/process-instances/find-by-id/{process_instance.id}", + headers=self.logged_in_headers(with_super_admin_user), + ) + assert response.status_code == 200 + assert response.json + assert response.json['id'] == process_instance.id diff --git a/tests/spiffworkflow_backend/unit/test_authorization_service.py b/tests/spiffworkflow_backend/unit/test_authorization_service.py index 83ed7fd8..9e7af5d0 100644 --- a/tests/spiffworkflow_backend/unit/test_authorization_service.py +++ b/tests/spiffworkflow_backend/unit/test_authorization_service.py @@ -277,6 +277,7 @@ class TestAuthorizationService(BaseTest): ) -> None: """Test_explode_permissions_basic.""" expected_permissions = [ + ("/process-instances/find-by-id/*", "read"), ("/process-instances/for-me", "read"), ("/process-instances/reports/*", "create"), ("/process-instances/reports/*", "delete"),