From 84bf70e2f2162a99e279eba15cd41328df948189 Mon Sep 17 00:00:00 2001 From: jasquat Date: Wed, 21 Dec 2022 12:13:33 -0500 Subject: [PATCH] do not force permissions to use the v1.0 path prefix but it can be used if desired w/ burnettk --- .../src/spiffworkflow_backend/__init__.py | 3 +- .../config/permissions/development.yml | 44 +++++++++---------- .../config/permissions/example.yml | 16 +++---- .../config/permissions/staging.yml | 38 ++++++++-------- .../terraform_deployed_environment.yml | 38 ++++++++-------- .../config/permissions/testing.yml | 10 ++--- .../helpers/api_version.py | 2 + .../spiffworkflow_backend/scripts/script.py | 8 +++- .../services/authorization_service.py | 13 ++++-- .../helpers/base_test.py | 10 ++--- .../scripts/test_add_permission.py | 2 +- 11 files changed, 96 insertions(+), 88 deletions(-) create mode 100644 spiffworkflow-backend/src/spiffworkflow_backend/helpers/api_version.py diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/__init__.py b/spiffworkflow-backend/src/spiffworkflow_backend/__init__.py index 9599116a2..1e5493496 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/__init__.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/__init__.py @@ -18,6 +18,7 @@ from werkzeug.exceptions import NotFound import spiffworkflow_backend.load_database_models # noqa: F401 from spiffworkflow_backend.config import setup_config +from spiffworkflow_backend.helpers.api_version import V1_API_PATH_PREFIX from spiffworkflow_backend.routes.admin_blueprint.admin_blueprint import admin_blueprint from spiffworkflow_backend.routes.openid_blueprint.openid_blueprint import ( openid_blueprint, @@ -117,7 +118,7 @@ def create_app() -> flask.app.Flask: ] CORS(app, origins=origins_re, max_age=3600) - connexion_app.add_api("api.yml", base_path="/v1.0") + connexion_app.add_api("api.yml", base_path=V1_API_PATH_PREFIX) mail = Mail(app) app.config["MAIL_APP"] = mail diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml index d8c43d4f0..30f315846 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml @@ -83,120 +83,120 @@ permissions: groups: [admin-ro] users: [] allowed_permissions: [create, read, update, delete] - uri: /v1.0/process-instances/* + uri: /process-instances/* tasks-crud: groups: [everybody] users: [] allowed_permissions: [create, read, update, delete] - uri: /v1.0/tasks/* + uri: /tasks/* service-tasks: groups: [everybody] users: [] allowed_permissions: [read] - uri: /v1.0/service-tasks + uri: /service-tasks user-groups-for-current-user: groups: [everybody] users: [] allowed_permissions: [read] - uri: /v1.0/user-groups/for-current-user + uri: /user-groups/for-current-user # read all for everybody read-all-process-groups: groups: [everybody] users: [] allowed_permissions: [read] - uri: /v1.0/process-groups/* + uri: /process-groups/* read-all-process-models: groups: [everybody] users: [] allowed_permissions: [read] - uri: /v1.0/process-models/* + uri: /process-models/* read-all-process-instances-for-me: groups: [everybody] users: [] allowed_permissions: [read] - uri: /v1.0/process-instances/for-me/* + uri: /process-instances/for-me/* read-process-instance-reports: groups: [everybody] users: [] allowed_permissions: [create, read, update, delete] - uri: /v1.0/process-instances/reports/* + uri: /process-instances/reports/* processes-read: groups: [everybody] users: [] allowed_permissions: [read] - uri: /v1.0/processes + uri: /processes manage-procurement-admin: groups: ["Project Lead"] users: [] allowed_permissions: [create, read, update, delete] - uri: /v1.0/process-groups/manage-procurement:* + uri: /process-groups/manage-procurement:* manage-procurement-admin-slash: groups: ["Project Lead"] users: [] allowed_permissions: [create, read, update, delete] - uri: /v1.0/process-groups/manage-procurement/* + uri: /process-groups/manage-procurement/* manage-procurement-admin-models: groups: ["Project Lead"] users: [] allowed_permissions: [create, read, update, delete] - uri: /v1.0/process-models/manage-procurement:* + uri: /process-models/manage-procurement:* manage-procurement-admin-models-slash: groups: ["Project Lead"] users: [] allowed_permissions: [create, read, update, delete] - uri: /v1.0/process-models/manage-procurement/* + uri: /process-models/manage-procurement/* manage-procurement-admin-instances: groups: ["Project Lead"] users: [] allowed_permissions: [create, read, update, delete] - uri: /v1.0/process-instances/manage-procurement:* + uri: /process-instances/manage-procurement:* manage-procurement-admin-instances-slash: groups: ["Project Lead"] users: [] allowed_permissions: [create, read, update, delete] - uri: /v1.0/process-instances/manage-procurement/* + uri: /process-instances/manage-procurement/* finance-admin: groups: ["Finance Team"] users: [] allowed_permissions: [create, read, update, delete] - uri: /v1.0/process-groups/manage-procurement:procurement:* + uri: /process-groups/manage-procurement:procurement:* manage-revenue-streams-instances: groups: ["core-contributor", "demo"] users: [] allowed_permissions: [create, read] - uri: /v1.0/process-instances/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/* + uri: /process-instances/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/* manage-procurement-invoice-instances: groups: ["core-contributor", "demo"] users: [] allowed_permissions: [create, read] - uri: /v1.0/process-instances/manage-procurement:procurement:core-contributor-invoice-management:* + uri: /process-instances/manage-procurement:procurement:core-contributor-invoice-management:* manage-procurement-instances: groups: ["core-contributor", "demo"] users: [] allowed_permissions: [create, read] - uri: /v1.0/process-instances/manage-procurement:vendor-lifecycle-management:* + uri: /process-instances/manage-procurement:vendor-lifecycle-management:* create-test-instances: groups: ["test"] users: [] allowed_permissions: [create, read] - uri: /v1.0/process-instances/misc:test:* + uri: /process-instances/misc:test:* core1-admin-instances: groups: ["core-contributor", "Finance Team"] users: [] allowed_permissions: [create, read] - uri: /v1.0/process-instances/misc:category_number_one:process-model-with-form:* + uri: /process-instances/misc:category_number_one:process-model-with-form:* core1-admin-instances-slash: groups: ["core-contributor", "Finance Team"] users: [] allowed_permissions: [create, read] - uri: /v1.0/process-instances/misc:category_number_one:process-model-with-form/* + uri: /process-instances/misc:category_number_one:process-model-with-form/* diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/example.yml b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/example.yml index 5bf57f1ac..248a400b4 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/example.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/example.yml @@ -47,44 +47,44 @@ permissions: groups: [everybody] users: [] allowed_permissions: [create, read, update, delete] - uri: /v1.0/tasks/* + uri: /tasks/* # Everyone can see everything (all groups, and processes are visible) read-all-process-groups: groups: [ everybody ] users: [ ] allowed_permissions: [ read ] - uri: /v1.0/process-groups/* + uri: /process-groups/* read-all-process-models: groups: [ everybody ] users: [ ] allowed_permissions: [ read ] - uri: /v1.0/process-models/* + uri: /process-models/* read-all-process-instance: groups: [ everybody ] users: [ ] allowed_permissions: [ read ] - uri: /v1.0/process-instances/* + uri: /process-instances/* read-process-instance-reports: groups: [ everybody ] users: [ ] allowed_permissions: [ read ] - uri: /v1.0/process-instances/reports/* + uri: /process-instances/reports/* processes-read: groups: [ everybody ] users: [ ] allowed_permissions: [ read ] - uri: /v1.0/processes + uri: /processes # Members of the Education group can change the processes under "education". education-admin: groups: ["Education", "President"] users: [] allowed_permissions: [create, read, update, delete] - uri: /v1.0/process-groups/education:* + uri: /process-groups/education:* # Anyone can start an education process. education-everybody: groups: [everybody] users: [] allowed_permissions: [create, read] - uri: /v1.0/process-instances/misc:category_number_one:process-model-with-form/* + uri: /process-instances/misc:category_number_one:process-model-with-form/* diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/staging.yml b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/staging.yml index 20635ea2e..1b8b21833 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/staging.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/staging.yml @@ -67,24 +67,24 @@ permissions: groups: [admin] users: [] allowed_permissions: [create, read, update, delete] - uri: /v1.0/process-instances/* + uri: /process-instances/* tasks-crud: groups: [everybody] users: [] allowed_permissions: [create, read, update, delete] - uri: /v1.0/tasks/* + uri: /tasks/* service-tasks: groups: [everybody] users: [] allowed_permissions: [read] - uri: /v1.0/service-tasks + uri: /service-tasks user-groups-for-current-user: groups: [everybody] users: [] allowed_permissions: [read] - uri: /v1.0/user-groups/for-current-user + uri: /user-groups/for-current-user # read all for everybody @@ -92,79 +92,79 @@ permissions: groups: [everybody] users: [] allowed_permissions: [read] - uri: /v1.0/process-groups/* + uri: /process-groups/* read-all-process-models: groups: [everybody] users: [] allowed_permissions: [read] - uri: /v1.0/process-models/* + uri: /process-models/* read-all-process-instances-for-me: groups: [everybody] users: [] allowed_permissions: [read] - uri: /v1.0/process-instances/for-me/* + uri: /process-instances/for-me/* manage-process-instance-reports: groups: [everybody] users: [] allowed_permissions: [create, read, update, delete] - uri: /v1.0/process-instances/reports/* + uri: /process-instances/reports/* processes-read: groups: [everybody] users: [] allowed_permissions: [read] - uri: /v1.0/processes + uri: /processes manage-procurement-admin-instances: groups: ["Project Lead"] users: [] allowed_permissions: [create, read, update, delete] - uri: /v1.0/process-instances/manage-procurement:* + uri: /process-instances/manage-procurement:* manage-procurement-admin-instances-slash: groups: ["Project Lead"] users: [] allowed_permissions: [create, read, update, delete] - uri: /v1.0/process-instances/manage-procurement/* + uri: /process-instances/manage-procurement/* manage-procurement-admin-instance-logs: groups: ["Project Lead"] users: [] allowed_permissions: [read] - uri: /v1.0/logs/manage-procurement:* + uri: /logs/manage-procurement:* manage-procurement-admin-instance-logs-slash: groups: ["Project Lead"] users: [] allowed_permissions: [read] - uri: /v1.0/logs/manage-procurement/* + uri: /logs/manage-procurement/* manage-revenue-streams-instances: groups: ["core-contributor", "demo"] users: [] allowed_permissions: [create, read] - uri: /v1.0/process-instances/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/* + uri: /process-instances/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/* manage-revenue-streams-instance-logs: groups: ["core-contributor", "demo"] users: [] allowed_permissions: [read] - uri: /v1.0/logs/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/* + uri: /logs/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/* manage-procurement-invoice-instances: groups: ["core-contributor", "demo"] users: [] allowed_permissions: [create, read] - uri: /v1.0/process-instances/manage-procurement:procurement:core-contributor-invoice-management:* + uri: /process-instances/manage-procurement:procurement:core-contributor-invoice-management:* manage-procurement-invoice-instance-logs: groups: ["core-contributor", "demo"] users: [] allowed_permissions: [read] - uri: /v1.0/logs/manage-procurement:procurement:core-contributor-invoice-management:* + uri: /logs/manage-procurement:procurement:core-contributor-invoice-management:* manage-procurement-instances: groups: ["core-contributor", "demo"] users: [] allowed_permissions: [create, read] - uri: /v1.0/process-instances/manage-procurement:vendor-lifecycle-management:* + uri: /process-instances/manage-procurement:vendor-lifecycle-management:* manage-procurement-instance-logs: groups: ["core-contributor", "demo"] users: [] allowed_permissions: [read] - uri: /v1.0/logs/manage-procurement:vendor-lifecycle-management:* + uri: /logs/manage-procurement:vendor-lifecycle-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 fc118b900..fac5cf300 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 @@ -68,18 +68,18 @@ permissions: groups: [everybody] users: [] allowed_permissions: [create, read, update, delete] - uri: /v1.0/tasks/* + uri: /tasks/* service-tasks: groups: [everybody] users: [] allowed_permissions: [read] - uri: /v1.0/service-tasks + uri: /service-tasks user-groups-for-current-user: groups: [everybody] users: [] allowed_permissions: [read] - uri: /v1.0/user-groups/for-current-user + uri: /user-groups/for-current-user # read all for everybody @@ -87,86 +87,86 @@ permissions: groups: [everybody] users: [] allowed_permissions: [read] - uri: /v1.0/process-groups/* + uri: /process-groups/* read-all-process-models: groups: [everybody] users: [] allowed_permissions: [read] - uri: /v1.0/process-models/* + uri: /process-models/* read-all-process-instances-for-me: groups: [everybody] users: [] allowed_permissions: [read] - uri: /v1.0/process-instances/for-me/* + uri: /process-instances/for-me/* read-process-instance-reports: groups: [everybody] users: [] allowed_permissions: [create, read, update, delete] - uri: /v1.0/process-instances/reports/* + uri: /process-instances/reports/* processes-read: groups: [everybody] users: [] allowed_permissions: [read] - uri: /v1.0/processes + uri: /processes manage-procurement-admin: groups: ["Project Lead"] users: [] allowed_permissions: [create, read, update, delete] - uri: /v1.0/process-groups/manage-procurement:* + uri: /process-groups/manage-procurement:* manage-procurement-admin-slash: groups: ["Project Lead"] users: [] allowed_permissions: [create, read, update, delete] - uri: /v1.0/process-groups/manage-procurement/* + uri: /process-groups/manage-procurement/* manage-procurement-admin-models: groups: ["Project Lead"] users: [] allowed_permissions: [create, read, update, delete] - uri: /v1.0/process-models/manage-procurement:* + uri: /process-models/manage-procurement:* manage-procurement-admin-models-slash: groups: ["Project Lead"] users: [] allowed_permissions: [create, read, update, delete] - uri: /v1.0/process-models/manage-procurement/* + uri: /process-models/manage-procurement/* manage-procurement-admin-instances: groups: ["Project Lead"] users: [] allowed_permissions: [create, read, update, delete] - uri: /v1.0/process-instances/manage-procurement:* + uri: /process-instances/manage-procurement:* manage-procurement-admin-instances-slash: groups: ["Project Lead"] users: [] allowed_permissions: [create, read, update, delete] - uri: /v1.0/process-instances/manage-procurement/* + uri: /process-instances/manage-procurement/* finance-admin: groups: ["Finance Team"] users: [] allowed_permissions: [create, read, update, delete] - uri: /v1.0/process-groups/manage-procurement:procurement:* + uri: /process-groups/manage-procurement:procurement:* manage-revenue-streams-instances: groups: ["core-contributor", "demo"] users: [] allowed_permissions: [create, read] - uri: /v1.0/process-instances/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/* + uri: /process-instances/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/* manage-procurement-invoice-instances: groups: ["core-contributor", "demo"] users: [] allowed_permissions: [create, read] - uri: /v1.0/process-instances/manage-procurement:procurement:core-contributor-invoice-management:* + uri: /process-instances/manage-procurement:procurement:core-contributor-invoice-management:* manage-procurement-instances: groups: ["core-contributor", "demo"] users: [] allowed_permissions: [create, read] - uri: /v1.0/process-instances/manage-procurement:vendor-lifecycle-management:* + uri: /process-instances/manage-procurement:vendor-lifecycle-management:* create-test-instances: groups: ["test"] users: [] allowed_permissions: [create, read] - uri: /v1.0/process-instances/misc:test:* + uri: /process-instances/misc:test:* diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/testing.yml b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/testing.yml index 429aacdf9..31724599f 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/testing.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/testing.yml @@ -34,29 +34,29 @@ permissions: groups: [everybody] users: [] allowed_permissions: [create, read, update, delete] - uri: /v1.0/tasks/* + uri: /tasks/* # TODO: all uris should really have the same structure finance-admin-group: groups: ["Finance Team"] users: [testuser4] allowed_permissions: [create, read, update, delete] - uri: /v1.0/process-groups/finance/* + uri: /process-groups/finance/* finance-admin-model: groups: ["Finance Team"] users: [testuser4] allowed_permissions: [create, read, update, delete] - uri: /v1.0/process-models/finance/* + uri: /process-models/finance/* finance-admin-model-lanes: groups: ["Finance Team"] users: [testuser4] allowed_permissions: [create, read, update, delete] - uri: /v1.0/process-models/finance:model_with_lanes/* + uri: /process-models/finance:model_with_lanes/* finance-admin-instance-run: groups: ["Finance Team"] users: [testuser4] allowed_permissions: [create, read, update, delete] - uri: /v1.0/process-instances/* + uri: /process-instances/* diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/helpers/api_version.py b/spiffworkflow-backend/src/spiffworkflow_backend/helpers/api_version.py new file mode 100644 index 000000000..607b6c16b --- /dev/null +++ b/spiffworkflow-backend/src/spiffworkflow_backend/helpers/api_version.py @@ -0,0 +1,2 @@ +"""Api_version.""" +V1_API_PATH_PREFIX = "/v1.0" diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/script.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/script.py index 9a96988ad..9e5836d6f 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/script.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/script.py @@ -52,7 +52,11 @@ class Script: @staticmethod def requires_privileged_permissions() -> bool: - """It seems safer to default to True and make safe functions opt in for any user to run them.""" + """It seems safer to default to True and make safe functions opt in for any user to run them. + + To give access to script for a given user, add a 'create' permission with following target-uri: + '/can-run-privileged-script/{script_name}' + """ return True @staticmethod @@ -88,7 +92,7 @@ class Script: """Check_script_permission.""" if subclass.requires_privileged_permissions(): script_function_name = get_script_function_name(subclass) - uri = f"/v1.0/can-run-privileged-script/{script_function_name}" + uri = f"/can-run-privileged-script/{script_function_name}" process_instance = ProcessInstanceModel.query.filter_by( id=script_attributes_context.process_instance_id ).first() diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py index 563a9bba0..52ea3d826 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py @@ -19,6 +19,7 @@ from SpiffWorkflow.task import Task as SpiffTask # type: ignore from sqlalchemy import or_ from sqlalchemy import text +from spiffworkflow_backend.helpers.api_version import V1_API_PATH_PREFIX from spiffworkflow_backend.models.group import GroupModel from spiffworkflow_backend.models.human_task import HumanTaskModel from spiffworkflow_backend.models.permission_assignment import PermissionAssignmentModel @@ -75,6 +76,7 @@ class AuthorizationService: ) -> bool: """Has_permission.""" principal_ids = [p.id for p in principals] + target_uri_normalized = target_uri.removeprefix(V1_API_PATH_PREFIX) permission_assignments = ( PermissionAssignmentModel.query.filter( @@ -84,10 +86,12 @@ class AuthorizationService: .join(PermissionTargetModel) .filter( or_( - text(f"'{target_uri}' LIKE permission_target.uri"), + text(f"'{target_uri_normalized}' LIKE permission_target.uri"), # to check for exact matches as well # see test_user_can_access_base_path_when_given_wildcard_permission unit test - text(f"'{target_uri}' = replace(permission_target.uri, '/%', '')"), + text( + f"'{target_uri_normalized}' = replace(permission_target.uri, '/%', '')" + ), ) ) .all() @@ -221,11 +225,12 @@ class AuthorizationService: def find_or_create_permission_target(cls, uri: str) -> PermissionTargetModel: """Find_or_create_permission_target.""" uri_with_percent = re.sub(r"\*", "%", uri) + target_uri_normalized = uri_with_percent.removeprefix(V1_API_PATH_PREFIX) permission_target: Optional[ PermissionTargetModel - ] = PermissionTargetModel.query.filter_by(uri=uri_with_percent).first() + ] = PermissionTargetModel.query.filter_by(uri=target_uri_normalized).first() if permission_target is None: - permission_target = PermissionTargetModel(uri=uri_with_percent) + permission_target = PermissionTargetModel(uri=target_uri_normalized) db.session.add(permission_target) db.session.commit() return permission_target diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py b/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py index 4310fba51..8a314f2cf 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py @@ -324,13 +324,9 @@ class BaseTest: permission_names: Optional[list[str]] = None, ) -> UserModel: """Add_permissions_to_user.""" - permission_target = PermissionTargetModel.query.filter_by( - uri=target_uri - ).first() - if permission_target is None: - permission_target = PermissionTargetModel(uri=target_uri) - db.session.add(permission_target) - db.session.commit() + permission_target = AuthorizationService.find_or_create_permission_target( + target_uri + ) if permission_names is None: permission_names = [member.name for member in Permission] diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_permission.py b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_permission.py index 1ae7f5711..45ff4b253 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_permission.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_permission.py @@ -76,7 +76,7 @@ class TestAddPermission(BaseTest): privileged_user = self.find_or_create_user("privileged_user") self.add_permissions_to_user( privileged_user, - target_uri="/v1.0/can-run-privileged-script/add_permission", + target_uri="/can-run-privileged-script/add_permission", permission_names=["create"], ) process_model = load_test_spec(