From 5dbca5c3494c9129d7802ca73e411aaf92e8413a Mon Sep 17 00:00:00 2001 From: jasquat Date: Tue, 22 Nov 2022 16:21:16 -0500 Subject: [PATCH] filter process models based on user permissions on the backend if specified w/ burnettk --- .../src/spiffworkflow_backend/api.yml | 8 +- .../config/permissions/development.yml | 62 +++++++++----- .../terraform_deployed_environment.yml | 42 ++++++++- .../routes/process_api_blueprint.py | 7 +- .../services/authorization_service.py | 11 +-- .../services/process_instance_service.py | 2 +- .../services/process_model_service.py | 20 ++++- .../src/components/ProcessInstanceRun.tsx | 24 ++++-- .../src/components/ProcessModelListTiles.tsx | 4 +- spiffworkflow-frontend/src/index.css | 4 + spiffworkflow-frontend/src/routes/MyTasks.tsx | 4 +- .../src/routes/ProcessGroupShow.tsx | 85 +++++++++---------- .../src/services/HttpService.ts | 4 +- 13 files changed, 186 insertions(+), 91 deletions(-) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml index a3e0eeaac..e5e0db974 100755 --- a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml @@ -278,7 +278,13 @@ paths: required: false description: Get all sub process models recursively if true schema: - type: string + type: boolean + - name: filter_runnable_by_user + in: query + required: false + description: Get only the process models that the user can run + schema: + type: boolean - name: page in: query required: false diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml index 904fdc94a..8a5881a2b 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml @@ -53,8 +53,8 @@ groups: lead, ] - hr: - users: [manuchehr] + core-contributor: + users: [core] permissions: tasks-crud: @@ -62,26 +62,6 @@ permissions: users: [] allowed_permissions: [create, read, update, delete] uri: /v1.0/tasks/* - process-model-read-all: - groups: [everybody] - users: [] - allowed_permissions: [read] - uri: /v1.0/process-models/* - process-group-read-all: - groups: [everybody] - users: [] - allowed_permissions: [read] - uri: /v1.0/process-groups/* - process-instance-list: - groups: [everybody] - users: [] - allowed_permissions: [read] - uri: /v1.0/process-instances - process-instance-report-list: - groups: [everybody] - users: [] - allowed_permissions: [read] - uri: /v1.0/process-instances/reports admin: groups: [admin] @@ -90,7 +70,7 @@ permissions: uri: /* read-all: - groups: ["Finance Team", "Project Lead", hr, admin] + groups: ["Finance Team", "Project Lead", admin] users: [] allowed_permissions: [read] uri: /* @@ -156,3 +136,39 @@ permissions: users: [] allowed_permissions: [create, read, update, delete] uri: /v1.0/process-instances/manage-procurement:procurement:vendor-invoice-management/* + + core-admin: + groups: ["core-contributor"] + users: [] + allowed_permissions: [read] + uri: /v1.0/process-groups/manage-procurement:procurement:vendor-invoice-management:* + core-admin-slash: + groups: ["core-contributor"] + users: [] + allowed_permissions: [read] + uri: /v1.0/process-groups/manage-procurement:procurement:vendor-invoice-management/* + core-admin-models: + groups: ["core-contributor"] + users: [] + allowed_permissions: [read] + uri: /v1.0/process-models/manage-procurement:procurement:vendor-invoice-management:* + core-admin-models-slash: + groups: ["core-contributor"] + users: [] + allowed_permissions: [read] + uri: /v1.0/process-models/manage-procurement:procurement:vendor-invoice-management/* + core-admin-models-instantiate: + groups: ["core-contributor"] + users: [] + allowed_permissions: [create] + uri: /v1.0/process-models/manage-procurement:procurement:vendor-invoice-management:invoice-approval/process-instances + core-admin-instances: + groups: ["core-contributor"] + users: [] + allowed_permissions: [create] + uri: /v1.0/process-instances/manage-procurement:procurement:vendor-invoice-management:* + core-admin-instances-slash: + groups: ["core-contributor"] + users: [] + allowed_permissions: [create] + uri: /v1.0/process-instances/manage-procurement:procurement:vendor-invoice-management/* diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/terraform_deployed_environment.yml b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/terraform_deployed_environment.yml index 7c32ebec2..8a5881a2b 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/terraform_deployed_environment.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/terraform_deployed_environment.yml @@ -53,8 +53,8 @@ groups: lead, ] - hr: - users: [manuchehr] + core-contributor: + users: [core] permissions: tasks-crud: @@ -70,7 +70,7 @@ permissions: uri: /* read-all: - groups: ["Finance Team", "Project Lead", hr, admin] + groups: ["Finance Team", "Project Lead", admin] users: [] allowed_permissions: [read] uri: /* @@ -136,3 +136,39 @@ permissions: users: [] allowed_permissions: [create, read, update, delete] uri: /v1.0/process-instances/manage-procurement:procurement:vendor-invoice-management/* + + core-admin: + groups: ["core-contributor"] + users: [] + allowed_permissions: [read] + uri: /v1.0/process-groups/manage-procurement:procurement:vendor-invoice-management:* + core-admin-slash: + groups: ["core-contributor"] + users: [] + allowed_permissions: [read] + uri: /v1.0/process-groups/manage-procurement:procurement:vendor-invoice-management/* + core-admin-models: + groups: ["core-contributor"] + users: [] + allowed_permissions: [read] + uri: /v1.0/process-models/manage-procurement:procurement:vendor-invoice-management:* + core-admin-models-slash: + groups: ["core-contributor"] + users: [] + allowed_permissions: [read] + uri: /v1.0/process-models/manage-procurement:procurement:vendor-invoice-management/* + core-admin-models-instantiate: + groups: ["core-contributor"] + users: [] + allowed_permissions: [create] + uri: /v1.0/process-models/manage-procurement:procurement:vendor-invoice-management:invoice-approval/process-instances + core-admin-instances: + groups: ["core-contributor"] + users: [] + allowed_permissions: [create] + uri: /v1.0/process-instances/manage-procurement:procurement:vendor-invoice-management:* + core-admin-instances-slash: + groups: ["core-contributor"] + users: [] + allowed_permissions: [create] + uri: /v1.0/process-instances/manage-procurement:procurement:vendor-invoice-management/* diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py index 1652cf884..9026abb0e 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py @@ -349,12 +349,15 @@ def process_model_move( def process_model_list( process_group_identifier: Optional[str] = None, recursive: Optional[bool] = False, + filter_runnable_by_user: Optional[bool] = False, page: int = 1, per_page: int = 100, ) -> flask.wrappers.Response: """Process model list!""" process_models = ProcessModelService().get_process_models( - process_group_id=process_group_identifier, recursive=recursive + process_group_id=process_group_identifier, + recursive=recursive, + filter_runnable_by_user=filter_runnable_by_user, ) batch = ProcessModelService().get_batch( process_models, page=page, per_page=per_page @@ -1319,7 +1322,7 @@ def task_submit( task_id, process_instance, processor=processor ) AuthorizationService.assert_user_can_complete_spiff_task( - processor, spiff_task, principal.user + process_instance.id, spiff_task, principal.user ) if spiff_task.state != TaskState.READY: diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py index 29ee78845..ea488f7a9 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py @@ -24,9 +24,6 @@ from spiffworkflow_backend.models.user import UserModel from spiffworkflow_backend.models.user import UserNotFoundError from spiffworkflow_backend.models.user_group_assignment import UserGroupAssignmentModel from spiffworkflow_backend.services.group_service import GroupService -from spiffworkflow_backend.services.process_instance_processor import ( - ProcessInstanceProcessor, -) from spiffworkflow_backend.services.user_service import UserService @@ -393,25 +390,25 @@ class AuthorizationService: @staticmethod def assert_user_can_complete_spiff_task( - processor: ProcessInstanceProcessor, + process_instance_id: int, spiff_task: SpiffTask, user: UserModel, ) -> bool: """Assert_user_can_complete_spiff_task.""" active_task = ActiveTaskModel.query.filter_by( task_name=spiff_task.task_spec.name, - process_instance_id=processor.process_instance_model.id, + process_instance_id=process_instance_id, ).first() if active_task is None: raise ActiveTaskNotFoundError( f"Could find an active task with task name '{spiff_task.task_spec.name}'" - f" for process instance '{processor.process_instance_model.id}'" + f" for process instance '{process_instance_id}'" ) if user not in active_task.potential_owners: raise UserDoesNotHaveAccessToTaskError( f"User {user.username} does not have access to update task'{spiff_task.task_spec.name}'" - f" for process instance '{processor.process_instance_model.id}'" + f" for process instance '{process_instance_id}'" ) return True diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py index 3c81e1b12..34ec6a261 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py @@ -197,7 +197,7 @@ class ProcessInstanceService: a multi-instance task. """ AuthorizationService.assert_user_can_complete_spiff_task( - processor, spiff_task, user + processor.process_instance_model.id, spiff_task, user ) dot_dct = ProcessInstanceService.create_dot_dict(data) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_model_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_model_service.py index f9788658f..aa7da7990 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_model_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_model_service.py @@ -18,7 +18,9 @@ from spiffworkflow_backend.models.process_group import ProcessGroupSchema from spiffworkflow_backend.models.process_instance import ProcessInstanceModel from spiffworkflow_backend.models.process_model import ProcessModelInfo from spiffworkflow_backend.models.process_model import ProcessModelInfoSchema +from spiffworkflow_backend.services.authorization_service import AuthorizationService from spiffworkflow_backend.services.file_system_service import FileSystemService +from spiffworkflow_backend.services.user_service import UserService T = TypeVar("T") @@ -179,7 +181,10 @@ class ProcessModelService(FileSystemService): raise ProcessEntityNotFoundError("process_model_not_found") def get_process_models( - self, process_group_id: Optional[str] = None, recursive: Optional[bool] = False + self, + process_group_id: Optional[str] = None, + recursive: Optional[bool] = False, + filter_runnable_by_user: Optional[bool] = False, ) -> List[ProcessModelInfo]: """Get process models.""" process_models = [] @@ -201,6 +206,19 @@ class ProcessModelService(FileSystemService): ) process_models.append(process_model) process_models.sort() + + if filter_runnable_by_user: + user = UserService.current_user() + new_process_model_list = [] + for process_model in process_models: + uri = f"/v1.0/process-models/{process_model.id.replace('/', ':')}/process-instances" + result = AuthorizationService.user_has_permission( + user=user, permission="create", target_uri=uri + ) + if result: + new_process_model_list.append(process_model) + return new_process_model_list + return process_models def get_process_groups( diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceRun.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceRun.tsx index 3494ea0bb..c5d79abd7 100644 --- a/spiffworkflow-frontend/src/components/ProcessInstanceRun.tsx +++ b/spiffworkflow-frontend/src/components/ProcessInstanceRun.tsx @@ -4,10 +4,16 @@ import { Button, // @ts-ignore } from '@carbon/react'; -import { ProcessModel, RecentProcessModel } from '../interfaces'; +import { Can } from '@casl/react'; +import { + PermissionsToCheck, + ProcessModel, + RecentProcessModel, +} from '../interfaces'; import HttpService from '../services/HttpService'; import ErrorContext from '../contexts/ErrorContext'; import { modifyProcessIdentifierForPathParam } from '../helpers'; +import { usePermissionFetcher } from '../hooks/PermissionService'; const storeRecentProcessModelInLocalStorage = ( processModelForStorage: ProcessModel @@ -75,6 +81,12 @@ export default function ProcessInstanceRun({ processModel.id ); + const processInstanceActionPath = `/v1.0/process-models/${modifiedProcessModelId}/process-instances`; + const permissionRequestData: PermissionsToCheck = { + [processInstanceActionPath]: ['POST'], + }; + const { ability } = usePermissionFetcher(permissionRequestData); + const onProcessInstanceRun = (processInstance: any) => { // FIXME: ensure that the task is actually for the current user as well const processInstanceId = (processInstance as any).id; @@ -98,15 +110,17 @@ export default function ProcessInstanceRun({ const processInstanceCreateAndRun = () => { HttpService.makeCallToBackend({ - path: `/process-models/${modifiedProcessModelId}/process-instances`, + path: processInstanceActionPath, successCallback: processModelRun, httpMethod: 'POST', }); }; return ( - + + + ); } diff --git a/spiffworkflow-frontend/src/components/ProcessModelListTiles.tsx b/spiffworkflow-frontend/src/components/ProcessModelListTiles.tsx index be5185a76..3b0292bb9 100644 --- a/spiffworkflow-frontend/src/components/ProcessModelListTiles.tsx +++ b/spiffworkflow-frontend/src/components/ProcessModelListTiles.tsx @@ -33,11 +33,11 @@ export default function ProcessModelListTiles({ setProcessModels(result.results); }; // only allow 10 for now until we get the backend only returning certain models for user execution - let queryParams = '?per_page=1000'; + let queryParams = '?per_page=20'; if (processGroup) { queryParams = `${queryParams}&process_group_identifier=${processGroup.id}`; } else { - queryParams = `${queryParams}&recursive=true`; + queryParams = `${queryParams}&recursive=true&filter_runnable_by_user=true`; } HttpService.makeCallToBackend({ path: `/process-models${queryParams}`, diff --git a/spiffworkflow-frontend/src/index.css b/spiffworkflow-frontend/src/index.css index 8d389ad06..00f947689 100644 --- a/spiffworkflow-frontend/src/index.css +++ b/spiffworkflow-frontend/src/index.css @@ -256,3 +256,7 @@ in on this with the react-jsonschema-form repo. This is just a patch fix to allo .clear-left { clear: left; } + +td.actions-cell { + width: 1em; +} diff --git a/spiffworkflow-frontend/src/routes/MyTasks.tsx b/spiffworkflow-frontend/src/routes/MyTasks.tsx index b33e459b1..34ca7e9e5 100644 --- a/spiffworkflow-frontend/src/routes/MyTasks.tsx +++ b/spiffworkflow-frontend/src/routes/MyTasks.tsx @@ -18,7 +18,7 @@ import { import ProcessInstanceRun from '../components/ProcessInstanceRun'; const PER_PAGE_FOR_TASKS_ON_HOME_PAGE = 5; -const REFRESH_INTERVAL = 10; +const REFRESH_INTERVAL = 5; const REFRESH_TIMEOUT = 600; export default function MyTasks() { @@ -158,7 +158,7 @@ export default function MyTasks() { {row.processModelDisplayName} - + (null); - const [processModels, setProcessModels] = useState([]); + // const [processModels, setProcessModels] = useState([]); const [modelPagination, setModelPagination] = useState(null); @@ -55,7 +54,7 @@ export default function ProcessGroupShow() { const { page, perPage } = getPageInfoFromSearchParams(searchParams); const setProcessModelFromResult = (result: any) => { - setProcessModels(result.results); + // setProcessModels(result.results); setModelPagination(result.pagination); }; const processResult = (result: any) => { @@ -74,42 +73,42 @@ export default function ProcessGroupShow() { }); }, [params, searchParams]); - const buildModelTable = () => { - if (processGroup === null) { - return null; - } - const rows = processModels.map((row: ProcessModel) => { - const modifiedProcessModelId: String = - modifyProcessIdentifierForPathParam((row as any).id); - return ( - - - - {row.id} - - - {row.display_name} - - ); - }); - return ( -
-

Process Models

- - - - - - - - {rows} -
Process Model IdDisplay Name
-
- ); - }; + // const buildModelTable = () => { + // if (processGroup === null) { + // return null; + // } + // const rows = processModels.map((row: ProcessModel) => { + // const modifiedProcessModelId: String = + // modifyProcessIdentifierForPathParam((row as any).id); + // return ( + // + // + // + // {row.id} + // + // + // {row.display_name} + // + // ); + // }); + // return ( + //
+ //

Process Models

+ // + // + // + // + // + // + // + // {rows} + //
Process Model IdDisplay Name
+ //
+ // ); + // }; const navigateToProcessGroups = (_result: any) => { navigate(`/admin/process-groups`); @@ -128,7 +127,7 @@ export default function ProcessGroupShow() { }; if (processGroup && modelPagination) { - const { page, perPage } = getPageInfoFromSearchParams(searchParams); + // const { page, perPage } = getPageInfoFromSearchParams(searchParams); const modifiedProcessGroupId = modifyProcessIdentifierForPathParam( processGroup.id ); diff --git a/spiffworkflow-frontend/src/services/HttpService.ts b/spiffworkflow-frontend/src/services/HttpService.ts index 72a92a98e..119765a7b 100644 --- a/spiffworkflow-frontend/src/services/HttpService.ts +++ b/spiffworkflow-frontend/src/services/HttpService.ts @@ -66,9 +66,11 @@ backendCallProps) => { method: httpMethod, }); + const updatedPath = path.replace(/^\/v1\.0/, ''); + let isSuccessful = true; let is403 = false; - fetch(`${BACKEND_BASE_URL}${path}`, httpArgs) + fetch(`${BACKEND_BASE_URL}${updatedPath}`, httpArgs) .then((response) => { if (response.status === 401) { UserService.doLogin();