diff --git a/forms-flow-api-utils/src/formsflow_api_utils/utils/__init__.py b/forms-flow-api-utils/src/formsflow_api_utils/utils/__init__.py index 0d2e38dd51..ecc14ee278 100644 --- a/forms-flow-api-utils/src/formsflow_api_utils/utils/__init__.py +++ b/forms-flow-api-utils/src/formsflow_api_utils/utils/__init__.py @@ -39,6 +39,9 @@ MANAGE_USERS, MANAGE_ROLES, ADMIN, + CREATE_BPMN_FLOWS, + MANAGE_DECISION_TABLES, + MANAGE_SUBFLOWS, ) from .file_log_handler import CustomTimedRotatingFileHandler, register_log_handlers from .format import CustomFormatter diff --git a/forms-flow-api/requirements.txt b/forms-flow-api/requirements.txt index 50daee26df..d7d1199792 100644 --- a/forms-flow-api/requirements.txt +++ b/forms-flow-api/requirements.txt @@ -26,7 +26,7 @@ ecdsa==0.19.0 flask-jwt-oidc==0.7.0 flask-marshmallow==1.2.1 flask-restx==1.3.0 -formsflow_api_utils @ git+https://github.com/AOT-Technologies/forms-flow-ai.git@develop#subdirectory=forms-flow-api-utils +formsflow_api_utils @ git+https://github.com/auslin-aot/forms-flow-ai.git@feature/fwf-4093-webapi-permission-matrix-changes#subdirectory=forms-flow-api-utils gunicorn==23.0.0 h11==0.14.0 h2==4.1.0 diff --git a/forms-flow-api/requirements/prod.txt b/forms-flow-api/requirements/prod.txt index dfa553abd2..4d995e1a20 100644 --- a/forms-flow-api/requirements/prod.txt +++ b/forms-flow-api/requirements/prod.txt @@ -17,4 +17,4 @@ markupsafe PyJWT redis lxml -git+https://github.com/AOT-Technologies/forms-flow-ai.git@develop#subdirectory=forms-flow-api-utils \ No newline at end of file +git+https://github.com/auslin-aot/forms-flow-ai.git@feature/fwf-4093-webapi-permission-matrix-changes#subdirectory=forms-flow-api-utils \ No newline at end of file diff --git a/forms-flow-api/src/formsflow_api/resources/process.py b/forms-flow-api/src/formsflow_api/resources/process.py index 94ae476d33..f9c3e14cc5 100644 --- a/forms-flow-api/src/formsflow_api/resources/process.py +++ b/forms-flow-api/src/formsflow_api/resources/process.py @@ -6,6 +6,8 @@ from flask_restx import Namespace, Resource, fields from formsflow_api_utils.utils import ( CREATE_DESIGNS, + MANAGE_DECISION_TABLES, + MANAGE_SUBFLOWS, VIEW_DESIGNS, VIEW_SUBMISSIONS, VIEW_TASKS, @@ -101,7 +103,7 @@ class ProcessDataResource(Resource): """Resource to create and list process data.""" @staticmethod - @auth.has_one_of_roles([CREATE_DESIGNS]) + @auth.has_one_of_roles([MANAGE_SUBFLOWS, MANAGE_DECISION_TABLES]) @profiletime @API.doc( params={ @@ -179,7 +181,11 @@ class ProcessDataResource(Resource): ) def get(): """List all process data.""" - process_list, count = ProcessService.get_all_process(request.args) + process_list, count = ProcessService.get_all_process( + request.args, + auth.has_role([MANAGE_SUBFLOWS]), + auth.has_role([MANAGE_DECISION_TABLES]), + ) response = { "process": process_list, "totalCount": count, @@ -187,7 +193,7 @@ def get(): return response, HTTPStatus.OK @staticmethod - @auth.has_one_of_roles([CREATE_DESIGNS]) + @auth.has_one_of_roles([CREATE_DESIGNS, MANAGE_SUBFLOWS, MANAGE_DECISION_TABLES]) @profiletime @API.doc( responses={ @@ -216,7 +222,7 @@ class ProcessResourceById(Resource): """Resource for managing process by id.""" @staticmethod - @auth.has_one_of_roles([CREATE_DESIGNS]) + @auth.has_one_of_roles([CREATE_DESIGNS, MANAGE_SUBFLOWS, MANAGE_DECISION_TABLES]) @profiletime @API.doc( responses={ @@ -233,7 +239,7 @@ def get(process_id: int): return response, status @staticmethod - @auth.has_one_of_roles([CREATE_DESIGNS]) + @auth.has_one_of_roles([CREATE_DESIGNS, MANAGE_SUBFLOWS, MANAGE_DECISION_TABLES]) @profiletime @API.doc( responses={ @@ -281,7 +287,7 @@ class ProcessHistoryResource(Resource): """Resource for retrieving process history.""" @staticmethod - @auth.has_one_of_roles([CREATE_DESIGNS]) + @auth.has_one_of_roles([CREATE_DESIGNS, MANAGE_SUBFLOWS, MANAGE_DECISION_TABLES]) @profiletime @API.doc( params={ @@ -327,7 +333,7 @@ class ValidateProcess(Resource): """Resource for validating a process name or key.""" @staticmethod - @auth.has_one_of_roles([CREATE_DESIGNS]) + @auth.has_one_of_roles([CREATE_DESIGNS, MANAGE_SUBFLOWS, MANAGE_DECISION_TABLES]) @profiletime @API.response(200, "OK:- Successful request.") @API.response(400, "BAD_REQUEST:- Invalid request.") @@ -352,7 +358,7 @@ class PublishResource(Resource): """Resource to support publish sub-process/worklfow.""" @staticmethod - @auth.has_one_of_roles([CREATE_DESIGNS]) + @auth.has_one_of_roles([CREATE_DESIGNS, MANAGE_SUBFLOWS, MANAGE_DECISION_TABLES]) @profiletime @API.response(200, "OK:- Successful request.") @API.response( @@ -381,7 +387,7 @@ class UnpublishResource(Resource): """Resource to support unpublish sub-process/workflow.""" @staticmethod - @auth.has_one_of_roles([CREATE_DESIGNS]) + @auth.has_one_of_roles([CREATE_DESIGNS, MANAGE_SUBFLOWS, MANAGE_DECISION_TABLES]) @profiletime @API.response(200, "OK:- Successful request.") @API.response( @@ -411,7 +417,16 @@ class ProcessResourceByProcessKey(Resource): """Resource for managing process by process key.""" @staticmethod - @auth.has_one_of_roles([CREATE_DESIGNS, VIEW_DESIGNS, VIEW_SUBMISSIONS, VIEW_TASKS]) + @auth.has_one_of_roles( + [ + CREATE_DESIGNS, + VIEW_DESIGNS, + VIEW_SUBMISSIONS, + VIEW_TASKS, + MANAGE_SUBFLOWS, + MANAGE_DECISION_TABLES, + ] + ) @profiletime @API.doc( responses={ diff --git a/forms-flow-api/src/formsflow_api/services/import_support.py b/forms-flow-api/src/formsflow_api/services/import_support.py index b4033879e9..c279a43755 100644 --- a/forms-flow-api/src/formsflow_api/services/import_support.py +++ b/forms-flow-api/src/formsflow_api/services/import_support.py @@ -6,7 +6,7 @@ from flask import current_app from formsflow_api_utils.exceptions import BusinessException from formsflow_api_utils.services.external import FormioService -from formsflow_api_utils.utils import Cache +from formsflow_api_utils.utils import CREATE_BPMN_FLOWS, Cache from formsflow_api_utils.utils.enums import FormProcessMapperStatus from formsflow_api_utils.utils.startup import collect_role_ids from formsflow_api_utils.utils.user_context import UserContext, user_context @@ -725,6 +725,8 @@ def import_form_workflow( else: raise BusinessException(BusinessErrorCode.INVALID_FILE_TYPE) elif valid_file == ".bpmn": + if CREATE_BPMN_FLOWS not in user.roles: + raise BusinessException(BusinessErrorCode.PERMISSION_DENIED) current_app.logger.info("Workflow validated successfully.") if action == "validate": major, minor = self.determine_process_version_by_key( diff --git a/forms-flow-api/src/formsflow_api/services/process.py b/forms-flow-api/src/formsflow_api/services/process.py index 4d650cb7f0..61edd3f119 100644 --- a/forms-flow-api/src/formsflow_api/services/process.py +++ b/forms-flow-api/src/formsflow_api/services/process.py @@ -157,20 +157,28 @@ def get_subflows_dmns(cls, process_type, **kwargs): ) @classmethod - def get_all_process(cls, request_args): # pylint:disable=too-many-locals + def get_all_process( + cls, request_args, subflows_auth, decision_auth + ): # pylint:disable=too-many-locals """Get all process list.""" dict_data = ProcessListRequestSchema().load(request_args) or {} + process_type = ( + dict_data.get("process_data_type").upper() + if dict_data.get("process_data_type") + else None + ) + if ( + process_type + and (process_type == ProcessType.BPMN.value and not subflows_auth) + or (process_type == ProcessType.DMN.value and not decision_auth) + ): + raise BusinessException(BusinessErrorCode.PERMISSION_DENIED) page_no = dict_data.get("page_no") limit = dict_data.get("limit") sort_by = dict_data.get("sort_by", "") process_id = dict_data.get("process_id") process_name = dict_data.get("name") status = dict_data.get("status").upper() if dict_data.get("status") else None - process_type = ( - dict_data.get("process_data_type").upper() - if dict_data.get("process_data_type") - else None - ) created_by = dict_data.get("created_by") created_from_date = dict_data.get("created_from_date") created_to_date = dict_data.get("created_to_date") diff --git a/forms-flow-api/tests/unit/api/test_process.py b/forms-flow-api/tests/unit/api/test_process.py index 5295c57ab6..8fc6b149cb 100644 --- a/forms-flow-api/tests/unit/api/test_process.py +++ b/forms-flow-api/tests/unit/api/test_process.py @@ -3,14 +3,18 @@ from unittest.mock import MagicMock, patch import pytest -from formsflow_api_utils.utils import CREATE_DESIGNS, MANAGE_TASKS +from formsflow_api_utils.utils import ( + CREATE_DESIGNS, + MANAGE_SUBFLOWS, + MANAGE_TASKS, +) from formsflow_api.models import Process from formsflow_api.services import ProcessService from tests.utilities.base_test import ( get_process_request_payload, - get_process_request_payload_low_code, get_process_request_payload_for_dmn, + get_process_request_payload_low_code, get_token, ) @@ -150,7 +154,7 @@ def test_process_list( self, app, client, session, jwt, create_process_with_api_call ): """Testing process listing API.""" - token = get_token(jwt, role=CREATE_DESIGNS, username="designer") + token = get_token(jwt, role=MANAGE_SUBFLOWS, username="designer") headers = { "Authorization": f"Bearer {token}", "content-type": "application/json", @@ -180,7 +184,7 @@ def test_process_list_with_pagination_sorted_list( create_process_with_api_call, ): """Testing process listing API with pagination and sorted list.""" - token = get_token(jwt, role=CREATE_DESIGNS, username="designer") + token = get_token(jwt, role=MANAGE_SUBFLOWS, username="designer") headers = { "Authorization": f"Bearer {token}", "content-type": "application/json", @@ -199,7 +203,7 @@ def test_process_list_with_filters( self, app, client, session, jwt, create_process_with_api_call ): """Testing process listing API with filters.""" - token = get_token(jwt, role=CREATE_DESIGNS, username="designer") + token = get_token(jwt, role=MANAGE_SUBFLOWS, username="designer") headers = { "Authorization": f"Bearer {token}", "content-type": "application/json",