From 4a8b07e98dbf4644d81a6f068f12ade52182f9f1 Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 12 Dec 2022 15:43:19 -0500 Subject: [PATCH 01/29] Removing two fields from user table that were not used (uid, name) Request email from open id clients, as this would provide a handy way to uniquely reference users when assigning to groups. During Login do a lookup on email if possible -- so that permissions assignments based on email can be connected when sigining in through openid. Don't use "open_id" for the service name on user accounts, use the iss string provided through open id, this will allow us to support more than one open id platform. Update the KeyCloak configuration so it is able to return email addresses for users -- which will make permission assignment easier in the future. Removed several unused commands in the user_service class. --- .../bin/spiffworkflow-realm.json | 27 ++++++++++ .../{4d75421c0af0_.py => e1d0d593c621_.py} | 10 ++-- .../config/permissions/development.yml | 3 +- .../config/permissions/example.yml | 6 ++- .../config/permissions/testing.yml | 7 +++ .../src/spiffworkflow_backend/models/user.py | 23 +-------- .../src/spiffworkflow_backend/routes/user.py | 10 ++-- .../services/authentication_service.py | 2 +- .../services/authorization_service.py | 22 +++++---- .../services/user_service.py | 49 ------------------- .../integration/test_openid_blueprint.py | 19 +++++-- .../unit/test_authorization_service.py | 2 +- 12 files changed, 79 insertions(+), 101 deletions(-) rename spiffworkflow-backend/migrations/versions/{4d75421c0af0_.py => e1d0d593c621_.py} (98%) diff --git a/spiffworkflow-backend/bin/spiffworkflow-realm.json b/spiffworkflow-backend/bin/spiffworkflow-realm.json index a30f53c14..3181284e0 100644 --- a/spiffworkflow-backend/bin/spiffworkflow-realm.json +++ b/spiffworkflow-backend/bin/spiffworkflow-realm.json @@ -424,6 +424,7 @@ "enabled" : true, "totp" : false, "emailVerified" : false, + "email": "admin@status.im", "firstName" : "", "lastName" : "", "credentials" : [ { @@ -446,6 +447,7 @@ "enabled" : true, "totp" : false, "emailVerified" : false, + "email": "alex@sartography.com", "credentials" : [ { "id" : "81a61a3b-228d-42b3-b39a-f62d8e7f57ca", "type" : "password", @@ -465,6 +467,7 @@ "enabled" : true, "totp" : false, "emailVerified" : false, + "email": "amir@status.im", "credentials" : [ { "id" : "e589f3ad-bf7b-4756-89f7-7894c03c2831", "type" : "password", @@ -484,6 +487,7 @@ "enabled" : true, "totp" : false, "emailVerified" : false, + "email": "ciadmin1@status.im", "credentials" : [ { "id" : "111b5ea1-c2ab-470a-a16b-2373bc94de7a", "type" : "password", @@ -506,6 +510,7 @@ "enabled" : true, "totp" : false, "emailVerified" : false, + "email": "ciuser1@status.im", "credentials" : [ { "id" : "762f36e9-47af-44da-8520-cf09d752497a", "type" : "password", @@ -528,6 +533,7 @@ "enabled" : true, "totp" : false, "emailVerified" : false, + "email": "core@status.im", "firstName" : "", "lastName" : "", "credentials" : [ { @@ -550,6 +556,7 @@ "enabled" : true, "totp" : false, "emailVerified" : false, + "email": "dan@sartography.com", "credentials" : [ { "id" : "d517c520-f500-4542-80e5-7144daef1e32", "type" : "password", @@ -569,6 +576,7 @@ "enabled" : true, "totp" : false, "emailVerified" : false, + "email": "daniel@sartography.com", "credentials" : [ { "id" : "f240495c-265b-42fc-99db-46928580d07d", "type" : "password", @@ -588,6 +596,7 @@ "enabled" : true, "totp" : false, "emailVerified" : false, + "email": "elizabeth@sartography.com", "credentials" : [ { "id" : "ae951ec8-9fc9-4f1b-b340-bbbe463ae5c2", "type" : "password", @@ -607,6 +616,7 @@ "enabled" : true, "totp" : false, "emailVerified" : false, + "email": "fin@status.im", "firstName" : "", "lastName" : "", "credentials" : [ { @@ -629,6 +639,7 @@ "enabled" : true, "totp" : false, "emailVerified" : false, + "email": "fin1@status.im", "firstName" : "", "lastName" : "", "credentials" : [ { @@ -651,6 +662,7 @@ "enabled" : true, "totp" : false, "emailVerified" : false, + "email": "finance_user1@status.im", "credentials" : [ { "id" : "f14722ec-13a7-4d35-a4ec-0475d405ae58", "type" : "password", @@ -670,6 +682,7 @@ "enabled" : true, "totp" : false, "emailVerified" : false, + "email": "harmeet@status.im", "credentials" : [ { "id" : "89c26090-9bd3-46ac-b038-883d02e3f125", "type" : "password", @@ -689,6 +702,7 @@ "enabled" : true, "totp" : false, "emailVerified" : false, + "email": "j@status.im", "firstName" : "", "lastName" : "", "credentials" : [ { @@ -711,6 +725,7 @@ "enabled" : true, "totp" : false, "emailVerified" : false, + "email": "jakub@status.im", "credentials" : [ { "id" : "ce141fa5-b8d5-4bbe-93e7-22e7119f97c2", "type" : "password", @@ -730,6 +745,7 @@ "enabled" : true, "totp" : false, "emailVerified" : false, + "email": "jarrad@status.im", "credentials" : [ { "id" : "113e0343-1069-476d-83f9-21d98edb9cfa", "type" : "password", @@ -749,6 +765,7 @@ "enabled" : true, "totp" : false, "emailVerified" : false, + "email": "jason@sartography.com", "credentials" : [ { "id" : "40abf32e-f0cc-4a17-8231-1a69a02c1b0b", "type" : "password", @@ -768,6 +785,7 @@ "enabled" : true, "totp" : false, "emailVerified" : false, + "email": "jon@sartography.com", "credentials" : [ { "id" : "8b520e01-5b9b-44ab-9ee8-505bd0831a45", "type" : "password", @@ -787,6 +805,7 @@ "enabled" : true, "totp" : false, "emailVerified" : false, + "email": "kb@sartography.com", "credentials" : [ { "id" : "2c0be363-038f-48f1-86d6-91fdd28657cf", "type" : "password", @@ -806,6 +825,7 @@ "enabled" : true, "totp" : false, "emailVerified" : false, + "email": "lead@status.im", "firstName" : "", "lastName" : "", "credentials" : [ { @@ -828,6 +848,7 @@ "enabled" : true, "totp" : false, "emailVerified" : false, + "email": "lead1@status.im", "firstName" : "", "lastName" : "", "credentials" : [ { @@ -850,6 +871,7 @@ "enabled" : true, "totp" : false, "emailVerified" : false, + "email": "manuchehr@status.im", "credentials" : [ { "id" : "07dabf55-b5d3-4f98-abba-3334086ecf5e", "type" : "password", @@ -869,6 +891,7 @@ "enabled" : true, "totp" : false, "emailVerified" : false, + "email": "mike@sartography.com", "credentials" : [ { "id" : "1ed375fb-0f1a-4c2a-9243-2477242cf7bd", "type" : "password", @@ -888,6 +911,7 @@ "enabled" : true, "totp" : false, "emailVerified" : false, + "email": "natalia@sartography.com", "credentials" : [ { "id" : "b6aa9936-39cc-4931-bfeb-60e6753de5ba", "type" : "password", @@ -907,6 +931,7 @@ "enabled" : true, "totp" : false, "emailVerified" : false, + "email": "sasha@status.im", "credentials" : [ { "id" : "4a170af4-6f0c-4e7b-b70c-e674edf619df", "type" : "password", @@ -926,6 +951,7 @@ "enabled" : true, "totp" : false, "emailVerified" : false, + "email": "service-account@status.im", "serviceAccountClientId" : "spiffworkflow-backend", "credentials" : [ ], "disableableCredentialTypes" : [ ], @@ -943,6 +969,7 @@ "enabled" : true, "totp" : false, "emailVerified" : false, + "email": "service-account-withauth@status.im", "serviceAccountClientId" : "withAuth", "credentials" : [ ], "disableableCredentialTypes" : [ ], diff --git a/spiffworkflow-backend/migrations/versions/4d75421c0af0_.py b/spiffworkflow-backend/migrations/versions/e1d0d593c621_.py similarity index 98% rename from spiffworkflow-backend/migrations/versions/4d75421c0af0_.py rename to spiffworkflow-backend/migrations/versions/e1d0d593c621_.py index 34fa1e974..cbe697cc5 100644 --- a/spiffworkflow-backend/migrations/versions/4d75421c0af0_.py +++ b/spiffworkflow-backend/migrations/versions/e1d0d593c621_.py @@ -1,8 +1,8 @@ """empty message -Revision ID: 4d75421c0af0 +Revision ID: e1d0d593c621 Revises: -Create Date: 2022-12-06 17:42:56.417673 +Create Date: 2022-12-12 14:23:44.643766 """ from alembic import op @@ -10,7 +10,7 @@ import sqlalchemy as sa # revision identifiers, used by Alembic. -revision = '4d75421c0af0' +revision = 'e1d0d593c621' down_revision = None branch_labels = None depends_on = None @@ -72,14 +72,12 @@ def upgrade(): op.create_table('user', sa.Column('id', sa.Integer(), nullable=False), sa.Column('username', sa.String(length=255), nullable=False), - sa.Column('uid', sa.String(length=50), nullable=True), sa.Column('service', sa.String(length=50), nullable=False), sa.Column('service_id', sa.String(length=255), nullable=False), - sa.Column('name', sa.String(length=255), nullable=True), sa.Column('email', sa.String(length=255), nullable=True), sa.PrimaryKeyConstraint('id'), sa.UniqueConstraint('service', 'service_id', name='service_key'), - sa.UniqueConstraint('uid') + sa.UniqueConstraint('username') ) op.create_table('message_correlation_property', sa.Column('id', sa.Integer(), nullable=False), diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml index 419c925fa..a0d030f8a 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml @@ -63,6 +63,7 @@ groups: harmeet, ] +# permission "admin" permissions: admin: groups: [admin] @@ -70,6 +71,7 @@ permissions: allowed_permissions: [create, read, update, delete] uri: /* + # permission: "basic" tasks-crud: groups: [everybody] users: [] @@ -81,7 +83,6 @@ permissions: allowed_permissions: [read] uri: /v1.0/service-tasks - # read all for everybody read-all-process-groups: groups: [everybody] diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/example.yml b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/example.yml index 79bfed81d..493aab1f1 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/example.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/example.yml @@ -2,14 +2,17 @@ default_group: everybody users: admin: + service: local_open_id email: admin@spiffworkflow.org password: admin preferred_username: Admin nelson: + service: local_open_id email: nelson@spiffworkflow.org password: nelson preferred_username: Nelson malala: + service: local_open_id email: malala@spiffworkflow.org password: malala preferred_username: Malala @@ -72,8 +75,7 @@ permissions: users: [ ] allowed_permissions: [ read ] uri: /v1.0/processes - - # Members of the Education group can change they processes work. + # Members of the Education group can change the processes under "education". education-admin: groups: ["Education", "President"] users: [] diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/testing.yml b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/testing.yml index c678205df..429aacdf9 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/testing.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/testing.yml @@ -1,5 +1,12 @@ default_group: everybody +users: + testadmin1: + service: https://testing/openid/thing + email: testadmin1@spiffworkflow.org + password: admin + preferred_username: El administrador de la muerte + groups: admin: users: [testadmin1, testadmin2] diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/user.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/user.py index b8c83d0f7..60d40e9d3 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/user.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/user.py @@ -28,14 +28,10 @@ class UserModel(SpiffworkflowBaseDBModel): __tablename__ = "user" __table_args__ = (db.UniqueConstraint("service", "service_id", name="service_key"),) - id = db.Column(db.Integer, primary_key=True) - # server and service id must be unique, not username. - username = db.Column(db.String(255), nullable=False, unique=False) - uid = db.Column(db.String(50), unique=True) - service = db.Column(db.String(50), nullable=False, unique=False) + username = db.Column(db.String(255), nullable=False, unique=True) # should always be an email address. + service = db.Column(db.String(50), nullable=False, unique=False) # not 'openid' -- google, aws service_id = db.Column(db.String(255), nullable=False, unique=False) - name = db.Column(db.String(255)) email = db.Column(db.String(255)) user_group_assignments = relationship("UserGroupAssignmentModel", cascade="delete") # type: ignore @@ -47,21 +43,6 @@ class UserModel(SpiffworkflowBaseDBModel): ) principal = relationship("PrincipalModel", uselist=False) # type: ignore - @validates("service") - def validate_service(self, key: str, value: Any) -> str: - """Validate_service.""" - try: - ap_type = getattr(AuthenticationProviderTypes, value, None) - except Exception as e: - raise ValueError(f"invalid service type: {value}") from e - if ap_type is not None: - ap_value: str = ap_type.value - return ap_value - raise ApiError( - error_code="invalid_service", - message=f"Could not validate service with value: {value}", - ) - def encode_auth_token(self) -> str: """Generate the Auth Token. diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/user.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/user.py index 2bbbc1374..b1ca7b904 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/user.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/user.py @@ -75,7 +75,7 @@ def verify_token( except ApiError as ae: # API Error is only thrown in the token is outdated. # Try to refresh the token user = UserService.get_user_by_service_and_service_id( - "open_id", decoded_token["sub"] + decoded_token["iss"], decoded_token["sub"] ) if user: refresh_token = AuthenticationService.get_refresh_token(user.id) @@ -107,7 +107,7 @@ def verify_token( user_info is not None and "error" not in user_info ): # not sure what to test yet user_model = ( - UserModel.query.filter(UserModel.service == "open_id") + UserModel.query.filter(UserModel.service == user_info["iss"]) .filter(UserModel.service_id == user_info["sub"]) .first() ) @@ -340,9 +340,5 @@ def get_user_from_decoded_internal_token(decoded_token: dict) -> Optional[UserMo ) if user: return user - user = UserModel( - username=service_id, - service=service, - service_id=service_id, - ) + user = UserService.create_user(service, service_id, username=service_id) return user diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/authentication_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/authentication_service.py index f4bd357b1..42cf6a8e8 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/authentication_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/authentication_service.py @@ -89,7 +89,7 @@ class AuthenticationService: + f"?state={state}&" + "response_type=code&" + f"client_id={self.client_id()}&" - + "scope=openid&" + + "scope=openid email&" + f"redirect_uri={return_redirect_url}" ) return login_redirect_url diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py index 9456f8f14..35c94afe6 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py @@ -450,18 +450,23 @@ class AuthorizationService: def create_user_from_sign_in(cls, user_info: dict) -> UserModel: """Create_user_from_sign_in.""" is_new_user = False - user_model = ( - UserModel.query.filter(UserModel.service == "open_id") - .filter(UserModel.service_id == user_info["sub"]) - .first() - ) + if user_info.get('email', None) is not None: + user_model = ( + UserModel.query.filter(UserModel.email == user_info["email"]).first() + ) + else: + user_model = ( + UserModel.query.filter(UserModel.service == user_info["iss"]) + .filter(UserModel.service_id == user_info["sub"]) + .first() + ) if user_model is None: current_app.logger.debug("create_user in login_return") is_new_user = True - name = username = email = "" + username = email = "" if "name" in user_info: - name = user_info["name"] + username = user_info["name"] if "username" in user_info: username = user_info["username"] elif "preferred_username" in user_info: @@ -469,9 +474,8 @@ class AuthorizationService: if "email" in user_info: email = user_info["email"] user_model = UserService().create_user( - service="open_id", + service=user_info["iss"], service_id=user_info["sub"], - name=name, username=username, email=email, ) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/user_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/user_service.py index 0e8e65c2c..b5898c13f 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/user_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/user_service.py @@ -23,7 +23,6 @@ class UserService: cls, service: str, service_id: str, - name: Optional[str] = "", username: Optional[str] = "", email: Optional[str] = "", ) -> UserModel: @@ -41,7 +40,6 @@ class UserService: username=username, service=service, service_id=service_id, - name=name, email=email, ) db.session.add(user_model) @@ -69,45 +67,12 @@ class UserService: ) ) - @classmethod - def find_or_create_user( - cls, - service: str, - service_id: str, - name: Optional[str] = None, - username: Optional[str] = None, - email: Optional[str] = None, - ) -> UserModel: - """Find_or_create_user.""" - user_model: UserModel - try: - user_model = cls.create_user( - service=service, - service_id=service_id, - name=name, - username=username, - email=email, - ) - except ApiError: - user_model = ( - UserModel.query.filter(UserModel.service == service) - .filter(UserModel.service_id == service_id) - .first() - ) - return user_model - # Returns true if the current user is logged in. @staticmethod def has_user() -> bool: """Has_user.""" return "token" in g and bool(g.token) and "user" in g and bool(g.user) - # Returns true if the given user uid is different from the current user's uid. - @staticmethod - def is_different_user(uid: str) -> bool: - """Is_different_user.""" - return UserService.has_user() and uid is not None and uid is not g.user.uid - @staticmethod def current_user() -> Any: """Current_user.""" @@ -117,20 +82,6 @@ class UserService: ) return g.user - @staticmethod - def in_list(uids: list[str]) -> bool: - """Returns true if the current user's id is in the given list of ids. - - False if there is no user, or the user is not in the list. - """ - if ( - UserService.has_user() - ): # If someone is logged in, lock tasks that don't belong to them. - user = UserService.current_user() - if user.uid in uids: - return True - return False - @staticmethod def get_principal_by_user_id(user_id: int) -> PrincipalModel: """Get_principal_by_user_id.""" diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_openid_blueprint.py b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_openid_blueprint.py index 20a0bb67b..54130c932 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_openid_blueprint.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_openid_blueprint.py @@ -1,4 +1,8 @@ """Test_authentication.""" +import base64 +import time + +import jwt from flask import Flask from flask.testing import FlaskClient from tests.spiffworkflow_backend.helpers.base_test import BaseTest @@ -44,13 +48,16 @@ class TestFlaskOpenId(BaseTest): client: FlaskClient, with_db_and_bpmn_file_cleanup: None, ) -> None: + + code = ("testadmin1:1234123412341234") + """It should be possible to get a token.""" - code = ( - "c3BpZmZ3b3JrZmxvdy1iYWNrZW5kOkpYZVFFeG0wSmhRUEx1bWdIdElJcWY1MmJEYWxIejBx" - ) + backend_basic_auth_string = code + backend_basic_auth_bytes = bytes(backend_basic_auth_string, encoding="ascii") + backend_basic_auth = base64.b64encode(backend_basic_auth_bytes) headers = { "Content-Type": "application/x-www-form-urlencoded", - "Authorization": f"Basic {code}", + "Authorization": f"Basic {backend_basic_auth.decode('utf-8')}", } data = { "grant_type": "authorization_code", @@ -59,3 +66,7 @@ class TestFlaskOpenId(BaseTest): } response = client.post("/openid/token", data=data, headers=headers) assert response + assert response.is_json + assert 'access_token' in response.json + assert 'id_token' in response.json + assert 'refresh_token' in response.json diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py index 00622a1f7..11108cd65 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py @@ -134,7 +134,7 @@ class TestAuthorizationService(BaseTest): active_task.task_name, processor.bpmn_process_instance ) finance_user = AuthorizationService.create_user_from_sign_in( - {"username": "testuser2", "sub": "open_id"} + {"username": "testuser2", "sub": "open_id", "iss": "https://test.stuff"} ) ProcessInstanceService.complete_form_task( processor, spiff_task, {}, finance_user, active_task From bcfbd9a6eabd2d3766418f1e68434c031dfbab79 Mon Sep 17 00:00:00 2001 From: Dan Date: Tue, 13 Dec 2022 08:14:44 -0500 Subject: [PATCH 02/29] Assure our open-id system can return emails. Update our data from Open ID Systems when users log in --- .../openid_blueprint/openid_blueprint.py | 1 + .../services/authorization_service.py | 24 ++++++++++++------- .../integration/test_openid_blueprint.py | 5 ++++ 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/openid_blueprint/openid_blueprint.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/openid_blueprint/openid_blueprint.py index f812ab034..0432b1e5e 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/openid_blueprint/openid_blueprint.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/openid_blueprint/openid_blueprint.py @@ -111,6 +111,7 @@ def token() -> dict: "iat": time.time(), "exp": time.time() + 86400, # Expire after a day. "sub": user_name, + "email": user_details['email'], "preferred_username": user_details.get("preferred_username", user_name), }, client_secret, diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py index 35c94afe6..f32ad789f 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py @@ -460,25 +460,31 @@ class AuthorizationService: .filter(UserModel.service_id == user_info["sub"]) .first() ) + username = email = "" + if "name" in user_info: + username = user_info["name"] + if "username" in user_info: + username = user_info["username"] + elif "preferred_username" in user_info: + username = user_info["preferred_username"] + if "email" in user_info: + email = user_info["email"] if user_model is None: current_app.logger.debug("create_user in login_return") is_new_user = True - username = email = "" - if "name" in user_info: - username = user_info["name"] - if "username" in user_info: - username = user_info["username"] - elif "preferred_username" in user_info: - username = user_info["preferred_username"] - if "email" in user_info: - email = user_info["email"] user_model = UserService().create_user( service=user_info["iss"], service_id=user_info["sub"], username=username, email=email, ) + else : + # Update with the latest information + user_model.username = username + user_model.email = email + user_model.service = user_info["iss"] + user_model.service_id = user_info["sub"] # this may eventually get too slow. # when it does, be careful about backgrounding, because diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_openid_blueprint.py b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_openid_blueprint.py index 54130c932..23ceb97d5 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_openid_blueprint.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_openid_blueprint.py @@ -70,3 +70,8 @@ class TestFlaskOpenId(BaseTest): assert 'access_token' in response.json assert 'id_token' in response.json assert 'refresh_token' in response.json + + decoded_token = jwt.decode(response.json['id_token'], options={"verify_signature": False}) + assert 'iss' in decoded_token + assert 'email' in decoded_token + From 5f33b51e1068c0fdd7b00fe6fe6ee0b77f6156d3 Mon Sep 17 00:00:00 2001 From: Jon Herron Date: Wed, 14 Dec 2022 18:23:38 -0500 Subject: [PATCH 03/29] Fix api endpoints for script unit tests --- .../src/spiffworkflow_backend/api.yml | 20 ++++--------------- .../routes/process_api_blueprint.py | 6 +++--- spiffworkflow-frontend/package-lock.json | 5 ++--- 3 files changed, 9 insertions(+), 22 deletions(-) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml index 84ada2341..c559ba98a 100755 --- a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml @@ -610,15 +610,9 @@ paths: items: $ref: "#/components/schemas/Workflow" - /process-models/{process_group_id}/{process_model_id}/script-unit-tests: + /process-models/{modified_process_model_identifier}/script-unit-tests: parameters: - - name: process_group_id - in: path - required: true - description: The unique id of an existing process group - schema: - type: string - - name: process_model_id + - name: modified_process_model_identifier in: path required: true description: The unique id of an existing process model. @@ -637,15 +631,9 @@ paths: schema: $ref: "#/components/schemas/Workflow" - /process-models/{process_group_id}/{process_model_id}/script-unit-tests/run: + /process-models/{modified_process_model_identifier}/script-unit-tests/run: parameters: - - name: process_group_id - in: path - required: true - description: The unique id of an existing process group - schema: - type: string - - name: process_model_id + - name: modified_process_model_identifier in: path required: true description: The unique id of an existing process model. 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 74e5a7e74..9a6168361 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py @@ -1613,7 +1613,7 @@ def task_submit( def script_unit_test_create( - process_group_id: str, process_model_id: str, body: Dict[str, Union[str, bool, int]] + modified_process_model_identifier: str, body: Dict[str, Union[str, bool, int]] ) -> flask.wrappers.Response: """Script_unit_test_create.""" bpmn_task_identifier = _get_required_parameter_or_raise( @@ -1624,7 +1624,7 @@ def script_unit_test_create( "expected_output_json", body ) - process_model_identifier = f"{process_group_id}/{process_model_id}" + process_model_identifier = modified_process_model_identifier.replace(":", "/") process_model = get_process_model(process_model_identifier) file = SpecFileService.get_files(process_model, process_model.primary_file_name)[0] if file is None: @@ -1702,7 +1702,7 @@ def script_unit_test_create( def script_unit_test_run( - process_group_id: str, process_model_id: str, body: Dict[str, Union[str, bool, int]] + modified_process_model_identifier: str, body: Dict[str, Union[str, bool, int]] ) -> flask.wrappers.Response: """Script_unit_test_run.""" # FIXME: We should probably clear this somewhere else but this works diff --git a/spiffworkflow-frontend/package-lock.json b/spiffworkflow-frontend/package-lock.json index 4ccea1922..e1ef20868 100644 --- a/spiffworkflow-frontend/package-lock.json +++ b/spiffworkflow-frontend/package-lock.json @@ -48174,7 +48174,7 @@ "@csstools/postcss-text-decoration-shorthand": "^1.0.0", "@csstools/postcss-trigonometric-functions": "^1.0.2", "@csstools/postcss-unset-value": "^1.0.2", - "autoprefixer": "10.4.5", + "autoprefixer": "10.4.8", "browserslist": "^4.21.3", "css-blank-pseudo": "^3.0.3", "css-has-pseudo": "^3.0.4", @@ -48212,8 +48212,7 @@ }, "dependencies": { "autoprefixer": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.5.tgz", + "version": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.5.tgz", "integrity": "sha512-Fvd8yCoA7lNX/OUllvS+aS1I7WRBclGXsepbvT8ZaPgrH24rgXpZzF0/6Hh3ZEkwg+0AES/Osd196VZmYoEFtw==", "requires": { "browserslist": "^4.20.2", From 5c3708ceba459024a096d90456d2789268a9e150 Mon Sep 17 00:00:00 2001 From: Jon Herron Date: Wed, 14 Dec 2022 18:26:17 -0500 Subject: [PATCH 04/29] Revert package-lock.json changes --- spiffworkflow-frontend/package-lock.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spiffworkflow-frontend/package-lock.json b/spiffworkflow-frontend/package-lock.json index e1ef20868..4ccea1922 100644 --- a/spiffworkflow-frontend/package-lock.json +++ b/spiffworkflow-frontend/package-lock.json @@ -48174,7 +48174,7 @@ "@csstools/postcss-text-decoration-shorthand": "^1.0.0", "@csstools/postcss-trigonometric-functions": "^1.0.2", "@csstools/postcss-unset-value": "^1.0.2", - "autoprefixer": "10.4.8", + "autoprefixer": "10.4.5", "browserslist": "^4.21.3", "css-blank-pseudo": "^3.0.3", "css-has-pseudo": "^3.0.4", @@ -48212,7 +48212,8 @@ }, "dependencies": { "autoprefixer": { - "version": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.5.tgz", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.5.tgz", "integrity": "sha512-Fvd8yCoA7lNX/OUllvS+aS1I7WRBclGXsepbvT8ZaPgrH24rgXpZzF0/6Hh3ZEkwg+0AES/Osd196VZmYoEFtw==", "requires": { "browserslist": "^4.20.2", From 160e19bb8cb7bd89f581bfab6813a5eedc4f4012 Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 15 Dec 2022 14:40:31 -0500 Subject: [PATCH 05/29] When loading permissions and the user does not exist, add records to the UserGroupAssignmentWaiting table that can be picked up later. Request "profile" scope over OpenID so we can get a few more bits of information when avilable. Add a "clear_perissions" script Add an "add_permissions" script Add an "add_permissions" script When logging in for the first time, check for any awaiting permissions and assign them. Add "enumerate" as a whitelisted function to React Schema Add a "display_name" to the user table Add a test for adding a new permission Add a test for adding a user to group Adding a test for deleting all permissions. Adding a display name for the user table --- .../bin/delete_and_import_all_permissions.py | 3 +- spiffworkflow-backend/migrations/env.py | 2 + .../migrations/versions/e1d0d593c621_.py | 331 ------------------ .../config/permissions/example.yml | 6 +- .../src/spiffworkflow_backend/models/group.py | 1 + .../src/spiffworkflow_backend/models/user.py | 3 +- .../models/user_group_assignment_waiting.py | 24 ++ .../routes/admin_blueprint/admin_blueprint.py | 2 +- .../src/spiffworkflow_backend/routes/user.py | 2 +- .../scripts/add_permission.py | 38 ++ .../scripts/add_user_to_group.py | 21 +- .../scripts/clear_permissions.py | 30 ++ .../services/authentication_service.py | 2 +- .../services/authorization_service.py | 69 ++-- .../services/process_instance_processor.py | 1 + .../services/user_service.py | 27 +- .../helpers/base_test.py | 2 +- .../scripts/test_add_permission.py | 61 ++++ .../scripts/test_add_user_to_group.py | 68 ++++ .../scripts/test_delete_permissions.py | 64 ++++ .../unit/test_authorization_service.py | 2 +- 21 files changed, 372 insertions(+), 387 deletions(-) delete mode 100644 spiffworkflow-backend/migrations/versions/e1d0d593c621_.py create mode 100644 spiffworkflow-backend/src/spiffworkflow_backend/models/user_group_assignment_waiting.py create mode 100644 spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_permission.py create mode 100644 spiffworkflow-backend/src/spiffworkflow_backend/scripts/clear_permissions.py create mode 100644 spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_permission.py create mode 100644 spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_user_to_group.py create mode 100644 spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_delete_permissions.py diff --git a/spiffworkflow-backend/bin/delete_and_import_all_permissions.py b/spiffworkflow-backend/bin/delete_and_import_all_permissions.py index a55e36e7f..966ec5a11 100644 --- a/spiffworkflow-backend/bin/delete_and_import_all_permissions.py +++ b/spiffworkflow-backend/bin/delete_and_import_all_permissions.py @@ -7,7 +7,8 @@ def main() -> None: """Main.""" app = get_hacked_up_app_for_script() with app.app_context(): - AuthorizationService.delete_all_permissions_and_recreate() + AuthorizationService.delete_all_permissions() + AuthorizationService.import_permissions_from_yaml_file() if __name__ == "__main__": diff --git a/spiffworkflow-backend/migrations/env.py b/spiffworkflow-backend/migrations/env.py index 630e381ad..68feded2a 100644 --- a/spiffworkflow-backend/migrations/env.py +++ b/spiffworkflow-backend/migrations/env.py @@ -1,3 +1,5 @@ +from __future__ import with_statement + import logging from logging.config import fileConfig diff --git a/spiffworkflow-backend/migrations/versions/e1d0d593c621_.py b/spiffworkflow-backend/migrations/versions/e1d0d593c621_.py deleted file mode 100644 index cbe697cc5..000000000 --- a/spiffworkflow-backend/migrations/versions/e1d0d593c621_.py +++ /dev/null @@ -1,331 +0,0 @@ -"""empty message - -Revision ID: e1d0d593c621 -Revises: -Create Date: 2022-12-12 14:23:44.643766 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'e1d0d593c621' -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('group', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('name', sa.String(length=255), nullable=True), - sa.Column('identifier', sa.String(length=255), nullable=True), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('message_model', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('identifier', sa.String(length=50), nullable=True), - sa.Column('name', sa.String(length=50), nullable=True), - sa.PrimaryKeyConstraint('id') - ) - op.create_index(op.f('ix_message_model_identifier'), 'message_model', ['identifier'], unique=True) - op.create_index(op.f('ix_message_model_name'), 'message_model', ['name'], unique=True) - op.create_table('permission_target', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('uri', sa.String(length=255), nullable=False), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('uri') - ) - op.create_table('spec_reference_cache', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('identifier', sa.String(length=255), nullable=True), - sa.Column('display_name', sa.String(length=255), nullable=True), - sa.Column('process_model_id', sa.String(length=255), nullable=True), - sa.Column('type', sa.String(length=255), nullable=True), - sa.Column('file_name', sa.String(length=255), nullable=True), - sa.Column('relative_path', sa.String(length=255), nullable=True), - sa.Column('has_lanes', sa.Boolean(), nullable=True), - sa.Column('is_executable', sa.Boolean(), nullable=True), - sa.Column('is_primary', sa.Boolean(), nullable=True), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('identifier', 'type', name='_identifier_type_unique') - ) - op.create_index(op.f('ix_spec_reference_cache_display_name'), 'spec_reference_cache', ['display_name'], unique=False) - op.create_index(op.f('ix_spec_reference_cache_identifier'), 'spec_reference_cache', ['identifier'], unique=False) - op.create_index(op.f('ix_spec_reference_cache_type'), 'spec_reference_cache', ['type'], unique=False) - op.create_table('spiff_logging', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('process_instance_id', sa.Integer(), nullable=False), - sa.Column('bpmn_process_identifier', sa.String(length=255), nullable=False), - sa.Column('bpmn_task_identifier', sa.String(length=255), nullable=False), - sa.Column('bpmn_task_name', sa.String(length=255), nullable=True), - sa.Column('bpmn_task_type', sa.String(length=255), nullable=True), - sa.Column('spiff_task_guid', sa.String(length=50), nullable=False), - sa.Column('timestamp', sa.DECIMAL(precision=17, scale=6), nullable=False), - sa.Column('message', sa.String(length=255), nullable=True), - sa.Column('current_user_id', sa.Integer(), nullable=True), - sa.Column('spiff_step', sa.Integer(), nullable=False), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('user', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('username', sa.String(length=255), nullable=False), - sa.Column('service', sa.String(length=50), nullable=False), - sa.Column('service_id', sa.String(length=255), nullable=False), - sa.Column('email', sa.String(length=255), nullable=True), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('service', 'service_id', name='service_key'), - sa.UniqueConstraint('username') - ) - op.create_table('message_correlation_property', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('identifier', sa.String(length=50), nullable=True), - sa.Column('message_model_id', sa.Integer(), nullable=False), - sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), - sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['message_model_id'], ['message_model.id'], ), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('identifier', 'message_model_id', name='message_correlation_property_unique') - ) - op.create_index(op.f('ix_message_correlation_property_identifier'), 'message_correlation_property', ['identifier'], unique=False) - op.create_table('message_triggerable_process_model', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('message_model_id', sa.Integer(), nullable=False), - sa.Column('process_model_identifier', sa.String(length=50), nullable=False), - sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), - sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['message_model_id'], ['message_model.id'], ), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('message_model_id') - ) - op.create_index(op.f('ix_message_triggerable_process_model_process_model_identifier'), 'message_triggerable_process_model', ['process_model_identifier'], unique=False) - op.create_table('principal', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('user_id', sa.Integer(), nullable=True), - sa.Column('group_id', sa.Integer(), nullable=True), - sa.CheckConstraint('NOT(user_id IS NULL AND group_id IS NULL)'), - sa.ForeignKeyConstraint(['group_id'], ['group.id'], ), - sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('group_id'), - sa.UniqueConstraint('user_id') - ) - op.create_table('process_instance', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('process_model_identifier', sa.String(length=255), nullable=False), - sa.Column('process_model_display_name', sa.String(length=255), nullable=False), - sa.Column('process_initiator_id', sa.Integer(), nullable=False), - sa.Column('bpmn_json', sa.JSON(), nullable=True), - sa.Column('start_in_seconds', sa.Integer(), nullable=True), - sa.Column('end_in_seconds', sa.Integer(), nullable=True), - sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), - sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), - sa.Column('status', sa.String(length=50), nullable=True), - sa.Column('bpmn_version_control_type', sa.String(length=50), nullable=True), - sa.Column('bpmn_version_control_identifier', sa.String(length=255), nullable=True), - sa.Column('spiff_step', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['process_initiator_id'], ['user.id'], ), - sa.PrimaryKeyConstraint('id') - ) - op.create_index(op.f('ix_process_instance_process_model_display_name'), 'process_instance', ['process_model_display_name'], unique=False) - op.create_index(op.f('ix_process_instance_process_model_identifier'), 'process_instance', ['process_model_identifier'], unique=False) - op.create_table('process_instance_report', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('identifier', sa.String(length=50), nullable=False), - sa.Column('report_metadata', sa.JSON(), nullable=True), - sa.Column('created_by_id', sa.Integer(), nullable=False), - sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), - sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['created_by_id'], ['user.id'], ), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('created_by_id', 'identifier', name='process_instance_report_unique') - ) - op.create_index(op.f('ix_process_instance_report_created_by_id'), 'process_instance_report', ['created_by_id'], unique=False) - op.create_index(op.f('ix_process_instance_report_identifier'), 'process_instance_report', ['identifier'], unique=False) - op.create_table('refresh_token', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('user_id', sa.Integer(), nullable=False), - sa.Column('token', sa.String(length=1024), nullable=False), - sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('user_id') - ) - op.create_table('secret', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('key', sa.String(length=50), nullable=False), - sa.Column('value', sa.Text(), nullable=False), - sa.Column('user_id', sa.Integer(), nullable=False), - sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), - sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('key') - ) - op.create_table('user_group_assignment', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('user_id', sa.Integer(), nullable=False), - sa.Column('group_id', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['group_id'], ['group.id'], ), - sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('user_id', 'group_id', name='user_group_assignment_unique') - ) - op.create_table('active_task', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('process_instance_id', sa.Integer(), nullable=False), - sa.Column('actual_owner_id', sa.Integer(), nullable=True), - sa.Column('lane_assignment_id', sa.Integer(), nullable=True), - sa.Column('form_file_name', sa.String(length=50), nullable=True), - sa.Column('ui_form_file_name', sa.String(length=50), nullable=True), - sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), - sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), - sa.Column('task_id', sa.String(length=50), nullable=True), - sa.Column('task_name', sa.String(length=50), nullable=True), - sa.Column('task_title', sa.String(length=50), nullable=True), - sa.Column('task_type', sa.String(length=50), nullable=True), - sa.Column('task_status', sa.String(length=50), nullable=True), - sa.Column('process_model_display_name', sa.String(length=255), nullable=True), - sa.ForeignKeyConstraint(['actual_owner_id'], ['user.id'], ), - sa.ForeignKeyConstraint(['lane_assignment_id'], ['group.id'], ), - sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.id'], ), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('task_id', 'process_instance_id', name='active_task_unique') - ) - op.create_table('message_correlation', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('process_instance_id', sa.Integer(), nullable=False), - sa.Column('message_correlation_property_id', sa.Integer(), nullable=False), - sa.Column('name', sa.String(length=255), nullable=False), - sa.Column('value', sa.String(length=255), nullable=False), - sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), - sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['message_correlation_property_id'], ['message_correlation_property.id'], ), - sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.id'], ), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('process_instance_id', 'message_correlation_property_id', 'name', name='message_instance_id_name_unique') - ) - op.create_index(op.f('ix_message_correlation_message_correlation_property_id'), 'message_correlation', ['message_correlation_property_id'], unique=False) - op.create_index(op.f('ix_message_correlation_name'), 'message_correlation', ['name'], unique=False) - op.create_index(op.f('ix_message_correlation_process_instance_id'), 'message_correlation', ['process_instance_id'], unique=False) - op.create_index(op.f('ix_message_correlation_value'), 'message_correlation', ['value'], unique=False) - op.create_table('message_instance', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('process_instance_id', sa.Integer(), nullable=False), - sa.Column('message_model_id', sa.Integer(), nullable=False), - sa.Column('message_type', sa.String(length=20), nullable=False), - sa.Column('payload', sa.JSON(), nullable=True), - sa.Column('status', sa.String(length=20), nullable=False), - sa.Column('failure_cause', sa.Text(), nullable=True), - sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), - sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['message_model_id'], ['message_model.id'], ), - sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.id'], ), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('permission_assignment', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('principal_id', sa.Integer(), nullable=False), - sa.Column('permission_target_id', sa.Integer(), nullable=False), - sa.Column('grant_type', sa.String(length=50), nullable=False), - sa.Column('permission', sa.String(length=50), nullable=False), - sa.ForeignKeyConstraint(['permission_target_id'], ['permission_target.id'], ), - sa.ForeignKeyConstraint(['principal_id'], ['principal.id'], ), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('principal_id', 'permission_target_id', 'permission', name='permission_assignment_uniq') - ) - op.create_table('process_instance_metadata', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('process_instance_id', sa.Integer(), nullable=False), - sa.Column('key', sa.String(length=255), nullable=False), - sa.Column('value', sa.String(length=255), nullable=False), - sa.Column('updated_at_in_seconds', sa.Integer(), nullable=False), - sa.Column('created_at_in_seconds', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.id'], ), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('process_instance_id', 'key', name='process_instance_metadata_unique') - ) - op.create_index(op.f('ix_process_instance_metadata_key'), 'process_instance_metadata', ['key'], unique=False) - op.create_table('spiff_step_details', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('process_instance_id', sa.Integer(), nullable=False), - sa.Column('spiff_step', sa.Integer(), nullable=False), - sa.Column('task_json', sa.JSON(), nullable=False), - sa.Column('timestamp', sa.DECIMAL(precision=17, scale=6), nullable=False), - sa.Column('completed_by_user_id', sa.Integer(), nullable=True), - sa.Column('lane_assignment_id', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['lane_assignment_id'], ['group.id'], ), - sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.id'], ), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('active_task_user', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('active_task_id', sa.Integer(), nullable=False), - sa.Column('user_id', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['active_task_id'], ['active_task.id'], ), - sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('active_task_id', 'user_id', name='active_task_user_unique') - ) - op.create_index(op.f('ix_active_task_user_active_task_id'), 'active_task_user', ['active_task_id'], unique=False) - op.create_index(op.f('ix_active_task_user_user_id'), 'active_task_user', ['user_id'], unique=False) - op.create_table('message_correlation_message_instance', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('message_instance_id', sa.Integer(), nullable=False), - sa.Column('message_correlation_id', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['message_correlation_id'], ['message_correlation.id'], ), - sa.ForeignKeyConstraint(['message_instance_id'], ['message_instance.id'], ), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('message_instance_id', 'message_correlation_id', name='message_correlation_message_instance_unique') - ) - op.create_index(op.f('ix_message_correlation_message_instance_message_correlation_id'), 'message_correlation_message_instance', ['message_correlation_id'], unique=False) - op.create_index(op.f('ix_message_correlation_message_instance_message_instance_id'), 'message_correlation_message_instance', ['message_instance_id'], unique=False) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_index(op.f('ix_message_correlation_message_instance_message_instance_id'), table_name='message_correlation_message_instance') - op.drop_index(op.f('ix_message_correlation_message_instance_message_correlation_id'), table_name='message_correlation_message_instance') - op.drop_table('message_correlation_message_instance') - op.drop_index(op.f('ix_active_task_user_user_id'), table_name='active_task_user') - op.drop_index(op.f('ix_active_task_user_active_task_id'), table_name='active_task_user') - op.drop_table('active_task_user') - op.drop_table('spiff_step_details') - op.drop_index(op.f('ix_process_instance_metadata_key'), table_name='process_instance_metadata') - op.drop_table('process_instance_metadata') - op.drop_table('permission_assignment') - op.drop_table('message_instance') - op.drop_index(op.f('ix_message_correlation_value'), table_name='message_correlation') - op.drop_index(op.f('ix_message_correlation_process_instance_id'), table_name='message_correlation') - op.drop_index(op.f('ix_message_correlation_name'), table_name='message_correlation') - op.drop_index(op.f('ix_message_correlation_message_correlation_property_id'), table_name='message_correlation') - op.drop_table('message_correlation') - op.drop_table('active_task') - op.drop_table('user_group_assignment') - op.drop_table('secret') - op.drop_table('refresh_token') - op.drop_index(op.f('ix_process_instance_report_identifier'), table_name='process_instance_report') - op.drop_index(op.f('ix_process_instance_report_created_by_id'), table_name='process_instance_report') - op.drop_table('process_instance_report') - op.drop_index(op.f('ix_process_instance_process_model_identifier'), table_name='process_instance') - op.drop_index(op.f('ix_process_instance_process_model_display_name'), table_name='process_instance') - op.drop_table('process_instance') - op.drop_table('principal') - op.drop_index(op.f('ix_message_triggerable_process_model_process_model_identifier'), table_name='message_triggerable_process_model') - op.drop_table('message_triggerable_process_model') - op.drop_index(op.f('ix_message_correlation_property_identifier'), table_name='message_correlation_property') - op.drop_table('message_correlation_property') - op.drop_table('user') - op.drop_table('spiff_logging') - op.drop_index(op.f('ix_spec_reference_cache_type'), table_name='spec_reference_cache') - op.drop_index(op.f('ix_spec_reference_cache_identifier'), table_name='spec_reference_cache') - op.drop_index(op.f('ix_spec_reference_cache_display_name'), table_name='spec_reference_cache') - op.drop_table('spec_reference_cache') - op.drop_table('permission_target') - op.drop_index(op.f('ix_message_model_name'), table_name='message_model') - op.drop_index(op.f('ix_message_model_identifier'), table_name='message_model') - op.drop_table('message_model') - op.drop_table('group') - # ### end Alembic commands ### diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/example.yml b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/example.yml index 493aab1f1..5bf57f1ac 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/example.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/example.yml @@ -21,17 +21,17 @@ groups: admin: users: [ - admin, + admin@spiffworkflow.org, ] Education: users: [ - malala + malala@spiffworkflow.org ] President: users: [ - nelson + nelson@spiffworkflow.org ] permissions: diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/group.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/group.py index 3b7edd6ce..eb9d9e1ca 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/group.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/group.py @@ -27,6 +27,7 @@ class GroupModel(FlaskBpmnGroupModel): identifier = db.Column(db.String(255)) user_group_assignments = relationship("UserGroupAssignmentModel", cascade="delete") + user_group_assignments_waiting = relationship("UserGroupAssignmentWaitingModel", cascade="delete") users = relationship( # type: ignore "UserModel", viewonly=True, diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/user.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/user.py index 60d40e9d3..6c1cc1356 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/user.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/user.py @@ -29,9 +29,10 @@ class UserModel(SpiffworkflowBaseDBModel): __tablename__ = "user" __table_args__ = (db.UniqueConstraint("service", "service_id", name="service_key"),) id = db.Column(db.Integer, primary_key=True) - username = db.Column(db.String(255), nullable=False, unique=True) # should always be an email address. + username = db.Column(db.String(255), nullable=False, unique=True) # should always be a unique value service = db.Column(db.String(50), nullable=False, unique=False) # not 'openid' -- google, aws service_id = db.Column(db.String(255), nullable=False, unique=False) + display_name = db.Column(db.String(255)) email = db.Column(db.String(255)) user_group_assignments = relationship("UserGroupAssignmentModel", cascade="delete") # type: ignore diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/user_group_assignment_waiting.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/user_group_assignment_waiting.py new file mode 100644 index 000000000..78f811a0a --- /dev/null +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/user_group_assignment_waiting.py @@ -0,0 +1,24 @@ +"""UserGroupAssignment.""" +from flask_bpmn.models.db import db +from flask_bpmn.models.db import SpiffworkflowBaseDBModel +from sqlalchemy import ForeignKey +from sqlalchemy.orm import relationship + +from spiffworkflow_backend.models.group import GroupModel +from spiffworkflow_backend.models.user import UserModel + + +class UserGroupAssignmentWaitingModel(SpiffworkflowBaseDBModel): + """UserGroupAssignmentsWaitingModel - When a user is assinged to a group, but that user has not yet logged into + the system, this caches that assignment, so it can be applied at the time the user logs in.""" + + __tablename__ = "user_group_assignment_waiting" + __table_args__ = ( + db.UniqueConstraint("username", "group_id", name="user_group_assignment_staged_unique"), + ) + + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(255), nullable=False) + group_id = db.Column(ForeignKey(GroupModel.id), nullable=False) + + group = relationship("GroupModel", overlaps="groups,user_group_assignment_waiting,users") # type: ignore diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/admin_blueprint/admin_blueprint.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/admin_blueprint/admin_blueprint.py index f1223ae0d..5cb0ae89b 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/admin_blueprint/admin_blueprint.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/admin_blueprint/admin_blueprint.py @@ -141,7 +141,7 @@ def process_model_save(process_model_id: str, file_name: str) -> Union[str, Resp @admin_blueprint.route("/process-models//run", methods=["GET"]) def process_model_run(process_model_id: str) -> Union[str, Response]: """Process_model_run.""" - user = UserService.create_user("internal", "Mr. Test", username="Mr. Test") + user = UserService.create_user("Mr. Test", "internal", "Mr. Test") process_instance = ( ProcessInstanceService.create_process_instance_from_process_model_identifier( process_model_id, user diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/user.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/user.py index 88e55cbaa..a690a1606 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/user.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/user.py @@ -341,5 +341,5 @@ def get_user_from_decoded_internal_token(decoded_token: dict) -> Optional[UserMo ) if user: return user - user = UserService.create_user(service, service_id, username=service_id) + user = UserService.create_user(service_id, service, service_id) return user diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_permission.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_permission.py new file mode 100644 index 000000000..6c9d97a53 --- /dev/null +++ b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_permission.py @@ -0,0 +1,38 @@ +"""Get_env.""" +from typing import Any + +from spiffworkflow_backend.models.group import GroupModel +from spiffworkflow_backend.models.group import GroupNotFoundError +from spiffworkflow_backend.models.script_attributes_context import ( + ScriptAttributesContext, +) +from spiffworkflow_backend.models.user import UserModel +from spiffworkflow_backend.models.user import UserNotFoundError +from spiffworkflow_backend.scripts.script import Script +from spiffworkflow_backend.services.authorization_service import AuthorizationService +from spiffworkflow_backend.services.group_service import GroupService +from spiffworkflow_backend.services.user_service import UserService + +# add_permission("read", "test/*", "Editors") +class AddPermission(Script): + """AddUserToGroup.""" + + def get_description(self) -> str: + """Get_description.""" + return """Add a permission to a group. ex: add_permission("read", "test/*", "Editors") """ + + def run( + self, + script_attributes_context: ScriptAttributesContext, + *args: Any, + **kwargs: Any, + ) -> Any: + """Run.""" + allowed_permission = args[0] + uri = args[1] + group_identifier = args[2] + group = GroupService.find_or_create_group(group_identifier) + target = AuthorizationService.find_or_create_permission_target(uri) + AuthorizationService.create_permission_for_principal( + group.principal, target, allowed_permission + ) \ No newline at end of file diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_user_to_group.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_user_to_group.py index d3c777118..3342b5c02 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_user_to_group.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_user_to_group.py @@ -9,6 +9,7 @@ from spiffworkflow_backend.models.script_attributes_context import ( from spiffworkflow_backend.models.user import UserModel from spiffworkflow_backend.models.user import UserNotFoundError from spiffworkflow_backend.scripts.script import Script +from spiffworkflow_backend.services.group_service import GroupService from spiffworkflow_backend.services.user_service import UserService @@ -17,7 +18,7 @@ class AddUserToGroup(Script): def get_description(self) -> str: """Get_description.""" - return """Add a given user to a given group.""" + return """Add a given user to a given group. Ex. add_user_to_group(group='Education', service_id='1234123')""" def run( self, @@ -28,16 +29,10 @@ class AddUserToGroup(Script): """Run.""" username = args[0] group_identifier = args[1] + + group = GroupService.find_or_create_group(group_identifier) user = UserModel.query.filter_by(username=username).first() - if user is None: - raise UserNotFoundError( - f"Script 'add_user_to_group' could not find a user with username: {username}" - ) - - group = GroupModel.query.filter_by(identifier=group_identifier).first() - if group is None: - raise GroupNotFoundError( - f"Script 'add_user_to_group' could not find group with identifier '{group_identifier}'." - ) - - UserService.add_user_to_group(user, group) + if user: + UserService.add_user_to_group(user, group) + else: + UserService.add_waiting_group_assignment(username, group) \ No newline at end of file diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/clear_permissions.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/clear_permissions.py new file mode 100644 index 000000000..b210a6723 --- /dev/null +++ b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/clear_permissions.py @@ -0,0 +1,30 @@ +"""Get_env.""" +from typing import Any +from flask_bpmn.models.db import db +from spiffworkflow_backend.models.group import GroupModel +from spiffworkflow_backend.models.group import GroupNotFoundError +from spiffworkflow_backend.models.script_attributes_context import ( + ScriptAttributesContext, +) +from spiffworkflow_backend.models.user import UserModel +from spiffworkflow_backend.models.user import UserNotFoundError +from spiffworkflow_backend.scripts.script import Script +from spiffworkflow_backend.services.authorization_service import AuthorizationService +from spiffworkflow_backend.services.user_service import UserService + + +class ClearPermissions(Script): + """Clear all permissions across the system .""" + + def get_description(self) -> str: + """Get_description.""" + return """Remove all groups and permissions from the database.""" + + def run( + self, + script_attributes_context: ScriptAttributesContext, + *args: Any, + **kwargs: Any, + ) -> Any: + """Run.""" + AuthorizationService.delete_all_permissions() \ No newline at end of file diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/authentication_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/authentication_service.py index b0e61f372..fd2bdb898 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/authentication_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/authentication_service.py @@ -93,7 +93,7 @@ class AuthenticationService: + f"?state={state}&" + "response_type=code&" + f"client_id={self.client_id()}&" - + "scope=openid email&" + + "scope=openid profile email&" + f"redirect_uri={return_redirect_url}" ) return login_redirect_url diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py index f32ad789f..1ad50b1c4 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py @@ -127,17 +127,15 @@ class AuthorizationService: return cls.has_permission(principals, permission, target_uri) @classmethod - def delete_all_permissions_and_recreate(cls) -> None: - """Delete_all_permissions_and_recreate.""" + def delete_all_permissions(cls) -> None: + """Delete_all_permissions_and_recreate. EXCEPT For permissions for the current user?""" for model in [PermissionAssignmentModel, PermissionTargetModel]: db.session.query(model).delete() # cascading to principals doesn't seem to work when attempting to delete all so do it like this instead for group in GroupModel.query.all(): db.session.delete(group) - db.session.commit() - cls.import_permissions_from_yaml_file() @classmethod def associate_user_with_group(cls, user: UserModel, group: GroupModel) -> None: @@ -193,14 +191,7 @@ class AuthorizationService: "permissions" ].items(): uri = permission_config["uri"] - uri_with_percent = re.sub(r"\*", "%", uri) - permission_target = PermissionTargetModel.query.filter_by( - uri=uri_with_percent - ).first() - if permission_target is None: - permission_target = PermissionTargetModel(uri=uri_with_percent) - db.session.add(permission_target) - db.session.commit() + permission_target = cls.find_or_create_permission_target(uri) for allowed_permission in permission_config["allowed_permissions"]: if "groups" in permission_config: @@ -226,6 +217,18 @@ class AuthorizationService: for user in UserModel.query.all(): cls.associate_user_with_group(user, default_group) + @classmethod + def find_or_create_permission_target(cls, uri): + uri_with_percent = re.sub(r"\*", "%", uri) + permission_target = PermissionTargetModel.query.filter_by( + uri=uri_with_percent + ).first() + if permission_target is None: + permission_target = PermissionTargetModel(uri=uri_with_percent) + db.session.add(permission_target) + db.session.commit() + return permission_target + @classmethod def create_permission_for_principal( cls, @@ -449,40 +452,46 @@ class AuthorizationService: @classmethod def create_user_from_sign_in(cls, user_info: dict) -> UserModel: """Create_user_from_sign_in.""" + """name, family_name, given_name, middle_name, nickname, preferred_username,""" + """profile, picture, website, gender, birthdate, zoneinfo, locale, and updated_at. """ + """email""" is_new_user = False - if user_info.get('email', None) is not None: - user_model = ( - UserModel.query.filter(UserModel.email == user_info["email"]).first() - ) - else: - user_model = ( - UserModel.query.filter(UserModel.service == user_info["iss"]) - .filter(UserModel.service_id == user_info["sub"]) - .first() - ) - username = email = "" - if "name" in user_info: - username = user_info["name"] - if "username" in user_info: - username = user_info["username"] - elif "preferred_username" in user_info: - username = user_info["preferred_username"] + user_model = ( + UserModel.query.filter(UserModel.service == user_info["iss"]) + .filter(UserModel.service_id == user_info["sub"]) + .first() + ) + email = display_name = username = "" if "email" in user_info: + username = user_info["email"] email = user_info["email"] + else: # we fall back to the sub, which may be very ugly. + username = user_info["sub"] + "@" + user_info["iss"] + + if "preferred_username" in user_info: + display_name = user_info["preferred_username"] + elif "nickname" in user_info: + display_name = user_info["nickname"] + elif "name" in user_info: + display_name = user_info["name"] if user_model is None: current_app.logger.debug("create_user in login_return") is_new_user = True user_model = UserService().create_user( + username=username, service=user_info["iss"], service_id=user_info["sub"], - username=username, email=email, + display_name = display_name ) + UserService().apply_waiting_group_assignments(user_model) + else : # Update with the latest information user_model.username = username user_model.email = email + user_model.display_name = display_name user_model.service = user_info["iss"] user_model.service_id = user_info["sub"] diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py index 5edc526cf..6d110a7dc 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py @@ -151,6 +151,7 @@ class CustomBpmnScriptEngine(PythonScriptEngine): # type: ignore "time": time, "decimal": decimal, "_strptime": _strptime, + "enumerate": enumerate, } # This will overwrite the standard builtins diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/user_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/user_service.py index b5898c13f..720d9ff8e 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/user_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/user_service.py @@ -13,6 +13,7 @@ from spiffworkflow_backend.models.group import GroupModel from spiffworkflow_backend.models.principal import PrincipalModel from spiffworkflow_backend.models.user import UserModel from spiffworkflow_backend.models.user_group_assignment import UserGroupAssignmentModel +from spiffworkflow_backend.models.user_group_assignment_waiting import UserGroupAssignmentWaitingModel class UserService: @@ -21,10 +22,11 @@ class UserService: @classmethod def create_user( cls, + username: str, service: str, service_id: str, - username: Optional[str] = "", email: Optional[str] = "", + display_name: Optional[str] = "" ) -> UserModel: """Create_user.""" user_model: Optional[UserModel] = ( @@ -41,6 +43,7 @@ class UserService: service=service, service_id=service_id, email=email, + display_name=display_name ) db.session.add(user_model) @@ -124,8 +127,26 @@ class UserService: @classmethod def add_user_to_group(cls, user: UserModel, group: GroupModel) -> None: """Add_user_to_group.""" - ugam = UserGroupAssignmentModel(user_id=user.id, group_id=group.id) - db.session.add(ugam) + exists = UserGroupAssignmentModel().query.filter_by(user_id=user.id).filter_by(group_id=group.id).count() + if not exists: + ugam = UserGroupAssignmentModel(user_id=user.id, group_id=group.id) + db.session.add(ugam) + db.session.commit() + + @classmethod + def add_waiting_group_assignment(cls, username: str, group: GroupModel) -> None: + exists = UserGroupAssignmentWaitingModel().query.filter_by(username=username).filter_by(group_id=group.id).count() + if not exists: + wugam = UserGroupAssignmentWaitingModel(username=username, group_id=group.id) + db.session.add(wugam) + db.session.commit() + + @classmethod + def apply_waiting_group_assignments(cls, user: UserModel) -> None: + waiting = UserGroupAssignmentWaitingModel().query.filter(UserGroupAssignmentWaitingModel.username == user.username).all() + for assignment in waiting: + cls.add_user_to_group(user, assignment.group) + db.session.delete(assignment) db.session.commit() @staticmethod diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py b/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py index 48982fc60..1084cc6d6 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py @@ -41,7 +41,7 @@ class BaseTest: if isinstance(user, UserModel): return user - user = UserService.create_user("internal", username, username=username) + user = UserService.create_user(username, "internal", username) if isinstance(user, UserModel): return user diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_permission.py b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_permission.py new file mode 100644 index 000000000..91546735d --- /dev/null +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_permission.py @@ -0,0 +1,61 @@ +"""Test_get_localtime.""" +from flask.app import Flask +from flask.testing import FlaskClient +from flask_bpmn.models.db import db + +from spiffworkflow_backend.models.permission_assignment import PermissionAssignmentModel +from spiffworkflow_backend.models.permission_target import PermissionTargetModel +from spiffworkflow_backend.models.script_attributes_context import ScriptAttributesContext +from spiffworkflow_backend.scripts.add_permission import AddPermission +from spiffworkflow_backend.scripts.clear_permissions import ClearPermissions +from spiffworkflow_backend.services.authorization_service import AuthorizationService +from spiffworkflow_backend.services.group_service import GroupService +from tests.spiffworkflow_backend.helpers.base_test import BaseTest +from tests.spiffworkflow_backend.helpers.test_data import load_test_spec + +from spiffworkflow_backend.models.group import GroupModel +from spiffworkflow_backend.models.user import UserModel +from spiffworkflow_backend.services.process_instance_processor import ( + ProcessInstanceProcessor, +) +from spiffworkflow_backend.services.user_service import UserService + + +class TestAddPermission(BaseTest): + """TestAddPermission.""" + + def test_can_add_permission ( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + with_super_admin_user: UserModel, + ) -> None: + """Test_can_get_members_of_a_group.""" + test_user = self.find_or_create_user("test_user") + + # now that we have everything, try to clear it out... + script_attributes_context = ScriptAttributesContext( + task=None, + environment_identifier="testing", + process_instance_id=1, + process_model_identifier="my_test_user", + ) + + group = GroupModel.query.filter(GroupModel.identifier == 'my_test_group').first() + permission_target = PermissionTargetModel.query.filter(PermissionTargetModel.uri == '/test_add_permission/%').first() + assert(group is None) + assert(permission_target is None) + + result = AddPermission().run( + script_attributes_context, + "read", + "/test_add_permission/*", + "my_test_group" + ) + group = GroupModel.query.filter(GroupModel.identifier == 'my_test_group').first() + permission_target = PermissionTargetModel.query.filter(PermissionTargetModel.uri == '/test_add_permission/%').first() + permission_assignments = PermissionAssignmentModel.query.filter(PermissionAssignmentModel.principal_id == group.principal.id).all() + assert(group is not None) + assert(permission_target is not None) + assert(len(permission_assignments) == 1) diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_user_to_group.py b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_user_to_group.py new file mode 100644 index 000000000..0f26e9c3d --- /dev/null +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_user_to_group.py @@ -0,0 +1,68 @@ +"""Test_get_localtime.""" +from flask.app import Flask +from flask.testing import FlaskClient +from flask_bpmn.models.db import db + +from spiffworkflow_backend.models.script_attributes_context import ScriptAttributesContext +from spiffworkflow_backend.models.user_group_assignment_waiting import UserGroupAssignmentWaitingModel +from spiffworkflow_backend.scripts.add_user_to_group import AddUserToGroup +from tests.spiffworkflow_backend.helpers.base_test import BaseTest +from tests.spiffworkflow_backend.helpers.test_data import load_test_spec + +from spiffworkflow_backend.models.group import GroupModel +from spiffworkflow_backend.models.user import UserModel +from spiffworkflow_backend.services.process_instance_processor import ( + ProcessInstanceProcessor, +) +from spiffworkflow_backend.services.user_service import UserService + + +class TestAddUserToGroup(BaseTest): + """TestGetGroupMembers.""" + + def test_can_add_existing_user_to_existing_group( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + with_super_admin_user: UserModel, + ) -> None: + """Test_can_get_members_of_a_group.""" + my_user = self.find_or_create_user("my_user") + my_group = GroupModel(identifier="my_group") + db.session.add(my_group) + script_attributes_context = ScriptAttributesContext( + task=None, + environment_identifier="testing", + process_instance_id=1, + process_model_identifier="my_test_user", + ) + result = AddUserToGroup().run( + script_attributes_context, + my_user.username, + my_group.identifier + ) + assert(my_user in my_group.users) + + def test_can_add_non_existent_user_to_non_existent_group( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + with_super_admin_user: UserModel, + ) -> None: + script_attributes_context = ScriptAttributesContext( + task=None, + environment_identifier="testing", + process_instance_id=1, + process_model_identifier="my_test_user", + ) + result = AddUserToGroup().run( + script_attributes_context, + "dan@sartography.com", + "competent-joes" + ) + my_group = GroupModel.query.filter(GroupModel.identifier == "competent-joes").first() + assert (my_group is not None) + waiting_assignments = UserGroupAssignmentWaitingModel().query.filter_by(username="dan@sartography.com").first() + assert (waiting_assignments is not None) diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_delete_permissions.py b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_delete_permissions.py new file mode 100644 index 000000000..3fdec995c --- /dev/null +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_delete_permissions.py @@ -0,0 +1,64 @@ +"""Test_get_localtime.""" +from flask.app import Flask +from flask.testing import FlaskClient +from flask_bpmn.models.db import db + +from spiffworkflow_backend.models.permission_target import PermissionTargetModel +from spiffworkflow_backend.models.script_attributes_context import ScriptAttributesContext +from spiffworkflow_backend.scripts.clear_permissions import ClearPermissions +from spiffworkflow_backend.services.authorization_service import AuthorizationService +from spiffworkflow_backend.services.group_service import GroupService +from tests.spiffworkflow_backend.helpers.base_test import BaseTest +from tests.spiffworkflow_backend.helpers.test_data import load_test_spec + +from spiffworkflow_backend.models.group import GroupModel +from spiffworkflow_backend.models.user import UserModel +from spiffworkflow_backend.services.process_instance_processor import ( + ProcessInstanceProcessor, +) +from spiffworkflow_backend.services.user_service import UserService + + +class TestDeletePermissions(BaseTest): + """TestGetGroupMembers.""" + + def test_can_delete_members ( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + with_super_admin_user: UserModel, + ) -> None: + """Test_can_get_members_of_a_group.""" + initiator_user = self.find_or_create_user("initiator_user") + testuser1 = self.find_or_create_user("testuser1") + testuser2 = self.find_or_create_user("testuser2") + testuser3 = self.find_or_create_user("testuser3") + group_a = GroupService.find_or_create_group('groupA') + group_b = GroupService.find_or_create_group('groupB') + db.session.add(group_a) + db.session.add(group_b) + db.session.commit() + UserService.add_user_to_group(testuser1, group_a) + UserService.add_user_to_group(testuser2, group_a) + UserService.add_user_to_group(testuser3, group_b) + + target = PermissionTargetModel('test/*') + db.session.add(target) + db.session.commit() + AuthorizationService.create_permission_for_principal(group_a.principal, + target, + "read") + # now that we have everything, try to clear it out... + script_attributes_context = ScriptAttributesContext( + task=None, + environment_identifier="testing", + process_instance_id=1, + process_model_identifier="my_test_user", + ) + result = ClearPermissions().run( + script_attributes_context + ) + + groups = GroupModel.query.all() + assert(0 == len(groups)) diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py index 11108cd65..f0fe1fff0 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py @@ -134,7 +134,7 @@ class TestAuthorizationService(BaseTest): active_task.task_name, processor.bpmn_process_instance ) finance_user = AuthorizationService.create_user_from_sign_in( - {"username": "testuser2", "sub": "open_id", "iss": "https://test.stuff"} + {"username": "testuser2", "sub": "testuser2", "iss": "https://test.stuff", "email": "testuser2"} ) ProcessInstanceService.complete_form_task( processor, spiff_task, {}, finance_user, active_task From de6558fea07124e9c1b484134358fba431c58c5b Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 15 Dec 2022 14:43:09 -0500 Subject: [PATCH 06/29] Updated migrations. --- .../migrations/versions/10e376f55dc2_.py | 341 ++++++++++++++++++ 1 file changed, 341 insertions(+) create mode 100644 spiffworkflow-backend/migrations/versions/10e376f55dc2_.py diff --git a/spiffworkflow-backend/migrations/versions/10e376f55dc2_.py b/spiffworkflow-backend/migrations/versions/10e376f55dc2_.py new file mode 100644 index 000000000..bde9ba2ae --- /dev/null +++ b/spiffworkflow-backend/migrations/versions/10e376f55dc2_.py @@ -0,0 +1,341 @@ +"""empty message + +Revision ID: 10e376f55dc2 +Revises: +Create Date: 2022-12-15 14:11:10.965454 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '10e376f55dc2' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('group', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=255), nullable=True), + sa.Column('identifier', sa.String(length=255), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('message_model', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('identifier', sa.String(length=50), nullable=True), + sa.Column('name', sa.String(length=50), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_message_model_identifier'), 'message_model', ['identifier'], unique=True) + op.create_index(op.f('ix_message_model_name'), 'message_model', ['name'], unique=True) + op.create_table('permission_target', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('uri', sa.String(length=255), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('uri') + ) + op.create_table('spec_reference_cache', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('identifier', sa.String(length=255), nullable=True), + sa.Column('display_name', sa.String(length=255), nullable=True), + sa.Column('process_model_id', sa.String(length=255), nullable=True), + sa.Column('type', sa.String(length=255), nullable=True), + sa.Column('file_name', sa.String(length=255), nullable=True), + sa.Column('relative_path', sa.String(length=255), nullable=True), + sa.Column('has_lanes', sa.Boolean(), nullable=True), + sa.Column('is_executable', sa.Boolean(), nullable=True), + sa.Column('is_primary', sa.Boolean(), nullable=True), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('identifier', 'type', name='_identifier_type_unique') + ) + op.create_index(op.f('ix_spec_reference_cache_display_name'), 'spec_reference_cache', ['display_name'], unique=False) + op.create_index(op.f('ix_spec_reference_cache_identifier'), 'spec_reference_cache', ['identifier'], unique=False) + op.create_index(op.f('ix_spec_reference_cache_type'), 'spec_reference_cache', ['type'], unique=False) + op.create_table('spiff_logging', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('process_instance_id', sa.Integer(), nullable=False), + sa.Column('bpmn_process_identifier', sa.String(length=255), nullable=False), + sa.Column('bpmn_task_identifier', sa.String(length=255), nullable=False), + sa.Column('bpmn_task_name', sa.String(length=255), nullable=True), + sa.Column('bpmn_task_type', sa.String(length=255), nullable=True), + sa.Column('spiff_task_guid', sa.String(length=50), nullable=False), + sa.Column('timestamp', sa.DECIMAL(precision=17, scale=6), nullable=False), + sa.Column('message', sa.String(length=255), nullable=True), + sa.Column('current_user_id', sa.Integer(), nullable=True), + sa.Column('spiff_step', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('user', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('username', sa.String(length=255), nullable=False), + sa.Column('service', sa.String(length=50), nullable=False), + sa.Column('service_id', sa.String(length=255), nullable=False), + sa.Column('display_name', sa.String(length=255), nullable=True), + sa.Column('email', sa.String(length=255), nullable=True), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('service', 'service_id', name='service_key'), + sa.UniqueConstraint('username') + ) + op.create_table('message_correlation_property', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('identifier', sa.String(length=50), nullable=True), + sa.Column('message_model_id', sa.Integer(), nullable=False), + sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), + sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['message_model_id'], ['message_model.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('identifier', 'message_model_id', name='message_correlation_property_unique') + ) + op.create_index(op.f('ix_message_correlation_property_identifier'), 'message_correlation_property', ['identifier'], unique=False) + op.create_table('message_triggerable_process_model', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('message_model_id', sa.Integer(), nullable=False), + sa.Column('process_model_identifier', sa.String(length=50), nullable=False), + sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), + sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['message_model_id'], ['message_model.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('message_model_id') + ) + op.create_index(op.f('ix_message_triggerable_process_model_process_model_identifier'), 'message_triggerable_process_model', ['process_model_identifier'], unique=False) + op.create_table('principal', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.Column('group_id', sa.Integer(), nullable=True), + sa.CheckConstraint('NOT(user_id IS NULL AND group_id IS NULL)'), + sa.ForeignKeyConstraint(['group_id'], ['group.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('group_id'), + sa.UniqueConstraint('user_id') + ) + op.create_table('process_instance', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('process_model_identifier', sa.String(length=255), nullable=False), + sa.Column('process_model_display_name', sa.String(length=255), nullable=False), + sa.Column('process_initiator_id', sa.Integer(), nullable=False), + sa.Column('bpmn_json', sa.JSON(), nullable=True), + sa.Column('start_in_seconds', sa.Integer(), nullable=True), + sa.Column('end_in_seconds', sa.Integer(), nullable=True), + sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), + sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), + sa.Column('status', sa.String(length=50), nullable=True), + sa.Column('bpmn_version_control_type', sa.String(length=50), nullable=True), + sa.Column('bpmn_version_control_identifier', sa.String(length=255), nullable=True), + sa.Column('spiff_step', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['process_initiator_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_process_instance_process_model_display_name'), 'process_instance', ['process_model_display_name'], unique=False) + op.create_index(op.f('ix_process_instance_process_model_identifier'), 'process_instance', ['process_model_identifier'], unique=False) + op.create_table('process_instance_report', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('identifier', sa.String(length=50), nullable=False), + sa.Column('report_metadata', sa.JSON(), nullable=True), + sa.Column('created_by_id', sa.Integer(), nullable=False), + sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), + sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['created_by_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('created_by_id', 'identifier', name='process_instance_report_unique') + ) + op.create_index(op.f('ix_process_instance_report_created_by_id'), 'process_instance_report', ['created_by_id'], unique=False) + op.create_index(op.f('ix_process_instance_report_identifier'), 'process_instance_report', ['identifier'], unique=False) + op.create_table('refresh_token', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('token', sa.String(length=1024), nullable=False), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('user_id') + ) + op.create_table('secret', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('key', sa.String(length=50), nullable=False), + sa.Column('value', sa.Text(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), + sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('key') + ) + op.create_table('user_group_assignment', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('group_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['group_id'], ['group.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('user_id', 'group_id', name='user_group_assignment_unique') + ) + op.create_table('user_group_assignment_waiting', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('username', sa.String(length=255), nullable=False), + sa.Column('group_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['group_id'], ['group.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('username', 'group_id', name='user_group_assignment_staged_unique') + ) + op.create_table('active_task', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('process_instance_id', sa.Integer(), nullable=False), + sa.Column('actual_owner_id', sa.Integer(), nullable=True), + sa.Column('lane_assignment_id', sa.Integer(), nullable=True), + sa.Column('form_file_name', sa.String(length=50), nullable=True), + sa.Column('ui_form_file_name', sa.String(length=50), nullable=True), + sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), + sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), + sa.Column('task_id', sa.String(length=50), nullable=True), + sa.Column('task_name', sa.String(length=50), nullable=True), + sa.Column('task_title', sa.String(length=50), nullable=True), + sa.Column('task_type', sa.String(length=50), nullable=True), + sa.Column('task_status', sa.String(length=50), nullable=True), + sa.Column('process_model_display_name', sa.String(length=255), nullable=True), + sa.ForeignKeyConstraint(['actual_owner_id'], ['user.id'], ), + sa.ForeignKeyConstraint(['lane_assignment_id'], ['group.id'], ), + sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('task_id', 'process_instance_id', name='active_task_unique') + ) + op.create_table('message_correlation', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('process_instance_id', sa.Integer(), nullable=False), + sa.Column('message_correlation_property_id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=255), nullable=False), + sa.Column('value', sa.String(length=255), nullable=False), + sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), + sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['message_correlation_property_id'], ['message_correlation_property.id'], ), + sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('process_instance_id', 'message_correlation_property_id', 'name', name='message_instance_id_name_unique') + ) + op.create_index(op.f('ix_message_correlation_message_correlation_property_id'), 'message_correlation', ['message_correlation_property_id'], unique=False) + op.create_index(op.f('ix_message_correlation_name'), 'message_correlation', ['name'], unique=False) + op.create_index(op.f('ix_message_correlation_process_instance_id'), 'message_correlation', ['process_instance_id'], unique=False) + op.create_index(op.f('ix_message_correlation_value'), 'message_correlation', ['value'], unique=False) + op.create_table('message_instance', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('process_instance_id', sa.Integer(), nullable=False), + sa.Column('message_model_id', sa.Integer(), nullable=False), + sa.Column('message_type', sa.String(length=20), nullable=False), + sa.Column('payload', sa.JSON(), nullable=True), + sa.Column('status', sa.String(length=20), nullable=False), + sa.Column('failure_cause', sa.Text(), nullable=True), + sa.Column('updated_at_in_seconds', sa.Integer(), nullable=True), + sa.Column('created_at_in_seconds', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['message_model_id'], ['message_model.id'], ), + sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('permission_assignment', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('principal_id', sa.Integer(), nullable=False), + sa.Column('permission_target_id', sa.Integer(), nullable=False), + sa.Column('grant_type', sa.String(length=50), nullable=False), + sa.Column('permission', sa.String(length=50), nullable=False), + sa.ForeignKeyConstraint(['permission_target_id'], ['permission_target.id'], ), + sa.ForeignKeyConstraint(['principal_id'], ['principal.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('principal_id', 'permission_target_id', 'permission', name='permission_assignment_uniq') + ) + op.create_table('process_instance_metadata', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('process_instance_id', sa.Integer(), nullable=False), + sa.Column('key', sa.String(length=255), nullable=False), + sa.Column('value', sa.String(length=255), nullable=False), + sa.Column('updated_at_in_seconds', sa.Integer(), nullable=False), + sa.Column('created_at_in_seconds', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('process_instance_id', 'key', name='process_instance_metadata_unique') + ) + op.create_index(op.f('ix_process_instance_metadata_key'), 'process_instance_metadata', ['key'], unique=False) + op.create_table('spiff_step_details', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('process_instance_id', sa.Integer(), nullable=False), + sa.Column('spiff_step', sa.Integer(), nullable=False), + sa.Column('task_json', sa.JSON(), nullable=False), + sa.Column('timestamp', sa.DECIMAL(precision=17, scale=6), nullable=False), + sa.Column('completed_by_user_id', sa.Integer(), nullable=True), + sa.Column('lane_assignment_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['lane_assignment_id'], ['group.id'], ), + sa.ForeignKeyConstraint(['process_instance_id'], ['process_instance.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('active_task_user', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('active_task_id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['active_task_id'], ['active_task.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('active_task_id', 'user_id', name='active_task_user_unique') + ) + op.create_index(op.f('ix_active_task_user_active_task_id'), 'active_task_user', ['active_task_id'], unique=False) + op.create_index(op.f('ix_active_task_user_user_id'), 'active_task_user', ['user_id'], unique=False) + op.create_table('message_correlation_message_instance', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('message_instance_id', sa.Integer(), nullable=False), + sa.Column('message_correlation_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['message_correlation_id'], ['message_correlation.id'], ), + sa.ForeignKeyConstraint(['message_instance_id'], ['message_instance.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('message_instance_id', 'message_correlation_id', name='message_correlation_message_instance_unique') + ) + op.create_index(op.f('ix_message_correlation_message_instance_message_correlation_id'), 'message_correlation_message_instance', ['message_correlation_id'], unique=False) + op.create_index(op.f('ix_message_correlation_message_instance_message_instance_id'), 'message_correlation_message_instance', ['message_instance_id'], unique=False) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f('ix_message_correlation_message_instance_message_instance_id'), table_name='message_correlation_message_instance') + op.drop_index(op.f('ix_message_correlation_message_instance_message_correlation_id'), table_name='message_correlation_message_instance') + op.drop_table('message_correlation_message_instance') + op.drop_index(op.f('ix_active_task_user_user_id'), table_name='active_task_user') + op.drop_index(op.f('ix_active_task_user_active_task_id'), table_name='active_task_user') + op.drop_table('active_task_user') + op.drop_table('spiff_step_details') + op.drop_index(op.f('ix_process_instance_metadata_key'), table_name='process_instance_metadata') + op.drop_table('process_instance_metadata') + op.drop_table('permission_assignment') + op.drop_table('message_instance') + op.drop_index(op.f('ix_message_correlation_value'), table_name='message_correlation') + op.drop_index(op.f('ix_message_correlation_process_instance_id'), table_name='message_correlation') + op.drop_index(op.f('ix_message_correlation_name'), table_name='message_correlation') + op.drop_index(op.f('ix_message_correlation_message_correlation_property_id'), table_name='message_correlation') + op.drop_table('message_correlation') + op.drop_table('active_task') + op.drop_table('user_group_assignment_waiting') + op.drop_table('user_group_assignment') + op.drop_table('secret') + op.drop_table('refresh_token') + op.drop_index(op.f('ix_process_instance_report_identifier'), table_name='process_instance_report') + op.drop_index(op.f('ix_process_instance_report_created_by_id'), table_name='process_instance_report') + op.drop_table('process_instance_report') + op.drop_index(op.f('ix_process_instance_process_model_identifier'), table_name='process_instance') + op.drop_index(op.f('ix_process_instance_process_model_display_name'), table_name='process_instance') + op.drop_table('process_instance') + op.drop_table('principal') + op.drop_index(op.f('ix_message_triggerable_process_model_process_model_identifier'), table_name='message_triggerable_process_model') + op.drop_table('message_triggerable_process_model') + op.drop_index(op.f('ix_message_correlation_property_identifier'), table_name='message_correlation_property') + op.drop_table('message_correlation_property') + op.drop_table('user') + op.drop_table('spiff_logging') + op.drop_index(op.f('ix_spec_reference_cache_type'), table_name='spec_reference_cache') + op.drop_index(op.f('ix_spec_reference_cache_identifier'), table_name='spec_reference_cache') + op.drop_index(op.f('ix_spec_reference_cache_display_name'), table_name='spec_reference_cache') + op.drop_table('spec_reference_cache') + op.drop_table('permission_target') + op.drop_index(op.f('ix_message_model_name'), table_name='message_model') + op.drop_index(op.f('ix_message_model_identifier'), table_name='message_model') + op.drop_table('message_model') + op.drop_table('group') + # ### end Alembic commands ### From db29bcde574935bd28dba7b48600f6f84724d7ed Mon Sep 17 00:00:00 2001 From: Dan Date: Fri, 16 Dec 2022 15:43:49 -0500 Subject: [PATCH 07/29] Bug fixes for Script Unit Test user interface -- don't bug out on invalid json. --- .../src/routes/ProcessModelEditDiagram.tsx | 71 +++++++++++-------- 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/spiffworkflow-frontend/src/routes/ProcessModelEditDiagram.tsx b/spiffworkflow-frontend/src/routes/ProcessModelEditDiagram.tsx index 5f8655cca..a17d8eeb9 100644 --- a/spiffworkflow-frontend/src/routes/ProcessModelEditDiagram.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessModelEditDiagram.tsx @@ -70,10 +70,10 @@ export default function ProcessModelEditDiagram() { interface ScriptUnitTestResult { result: boolean; - context: object; - error: string; - line_number: number; - offset: number; + context?: object; + error?: string; + line_number?: number; + offset?: number; } const [currentScriptUnitTest, setCurrentScriptUnitTest] = @@ -468,6 +468,16 @@ export default function ProcessModelEditDiagram() { const runCurrentUnitTest = () => { if (currentScriptUnitTest && scriptElement) { + let inputJson = ''; + let expectedJson = ''; + try { + inputJson = JSON.parse(currentScriptUnitTest.inputJson.value); + expectedJson = JSON.parse(currentScriptUnitTest.expectedOutputJson.value); + } catch (e) { + setScriptUnitTestResult({ result:false, error:"The JSON provided contains a formatting error."}) + return; + } + resetUnitTextResult(); HttpService.makeCallToBackend({ path: `/process-models/${modifiedProcessModelId}/script-unit-tests/run`, @@ -476,10 +486,8 @@ export default function ProcessModelEditDiagram() { postBody: { bpmn_task_identifier: (scriptElement as any).id, python_script: scriptText, - input_json: JSON.parse(currentScriptUnitTest.inputJson.value), - expected_output_json: JSON.parse( - currentScriptUnitTest.expectedOutputJson.value - ), + input_json: inputJson, + expected_output_json: expectedJson }, }); } @@ -488,19 +496,20 @@ export default function ProcessModelEditDiagram() { const unitTestFailureElement = () => { if ( scriptUnitTestResult && - scriptUnitTestResult.result === false && - !scriptUnitTestResult.line_number + scriptUnitTestResult.result === false ) { - let errorStringElement = null; - if (scriptUnitTestResult.error) { - errorStringElement = ( - - Received error when running script:{' '} - {JSON.stringify(scriptUnitTestResult.error)} - - ); + let errorMessage = ''; + if (scriptUnitTestResult.context) { + errorMessage = 'Unexpected result. Please see the comparison below.'; + } else if (scriptUnitTestResult.line_number) { + errorMessage = `Error encountered running the script. Please check the code around line ${ scriptUnitTestResult.line_number}` + } else { + errorMessage = `Error encountered running the script. ${JSON.stringify(scriptUnitTestResult.error)}` } + let errorStringElement = { errorMessage }; + let errorContextElement = null; + if (scriptUnitTestResult.context) { errorStringElement = ( Unexpected result. Please see the comparison below. @@ -571,16 +580,22 @@ export default function ProcessModelEditDiagram() { ); } - const inputJson = JSON.stringify( - JSON.parse(currentScriptUnitTest.inputJson.value), - null, - ' ' - ); - const outputJson = JSON.stringify( - JSON.parse(currentScriptUnitTest.expectedOutputJson.value), - null, - ' ' - ); + let inputJson = currentScriptUnitTest.inputJson.value; + let outputJson = currentScriptUnitTest.expectedOutputJson.value; + try { + inputJson = JSON.stringify( + JSON.parse(currentScriptUnitTest.inputJson.value), + null, + ' ' + ); + outputJson = JSON.stringify( + JSON.parse(currentScriptUnitTest.expectedOutputJson.value), + null, + ' ' + ); + } catch (e) { + // Attemping to format the json failed -- it's invalid. + } return (
From ef316483d9b65d2605b002828498e622153f56c7 Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 19 Dec 2022 10:05:19 -0500 Subject: [PATCH 08/29] Adding tests for the user service , and closing a few logic errors. Adding support for a single wild card for matching all users. --- .../models/user_group_assignment_waiting.py | 13 +++-- .../services/authorization_service.py | 1 - .../services/user_service.py | 15 ++++-- .../unit/test_user_service.py | 50 +++++++++++++++++++ 4 files changed, 71 insertions(+), 8 deletions(-) create mode 100644 spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_user_service.py diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/user_group_assignment_waiting.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/user_group_assignment_waiting.py index 78f811a0a..2d14e54c5 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/user_group_assignment_waiting.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/user_group_assignment_waiting.py @@ -7,11 +7,11 @@ from sqlalchemy.orm import relationship from spiffworkflow_backend.models.group import GroupModel from spiffworkflow_backend.models.user import UserModel - class UserGroupAssignmentWaitingModel(SpiffworkflowBaseDBModel): - """UserGroupAssignmentsWaitingModel - When a user is assinged to a group, but that user has not yet logged into - the system, this caches that assignment, so it can be applied at the time the user logs in.""" - + """UserGroupAssignmentsWaitingModel - When a user is assinged to a group, but that username does not exist, + we cache it here to be applied in the event the user does log into the system. + """ + MATCH_ALL_USERS = "*" __tablename__ = "user_group_assignment_waiting" __table_args__ = ( db.UniqueConstraint("username", "group_id", name="user_group_assignment_staged_unique"), @@ -22,3 +22,8 @@ class UserGroupAssignmentWaitingModel(SpiffworkflowBaseDBModel): group_id = db.Column(ForeignKey(GroupModel.id), nullable=False) group = relationship("GroupModel", overlaps="groups,user_group_assignment_waiting,users") # type: ignore + + def is_match_all(self): + if self.username == self.MATCH_ALL_USERS: + return True + return False diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py index 1ad50b1c4..429a65e1b 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py @@ -485,7 +485,6 @@ class AuthorizationService: email=email, display_name = display_name ) - UserService().apply_waiting_group_assignments(user_model) else : # Update with the latest information diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/user_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/user_service.py index 720d9ff8e..8003c9e80 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/user_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/user_service.py @@ -56,6 +56,7 @@ class UserService: message=f"Could not add user {username}", ) from e cls.create_principal(user_model.id) + UserService().apply_waiting_group_assignments(user_model) return user_model else: @@ -135,18 +136,26 @@ class UserService: @classmethod def add_waiting_group_assignment(cls, username: str, group: GroupModel) -> None: - exists = UserGroupAssignmentWaitingModel().query.filter_by(username=username).filter_by(group_id=group.id).count() - if not exists: + wugam = UserGroupAssignmentWaitingModel().query.filter_by(username=username).filter_by(group_id=group.id).first() + if not wugam: wugam = UserGroupAssignmentWaitingModel(username=username, group_id=group.id) db.session.add(wugam) db.session.commit() + if wugam.is_match_all(): + for user in UserModel.query.all(): + cls.add_user_to_group(user, group) @classmethod def apply_waiting_group_assignments(cls, user: UserModel) -> None: - waiting = UserGroupAssignmentWaitingModel().query.filter(UserGroupAssignmentWaitingModel.username == user.username).all() + waiting = UserGroupAssignmentWaitingModel().query.\ + filter(UserGroupAssignmentWaitingModel.username == user.username).all() for assignment in waiting: cls.add_user_to_group(user, assignment.group) db.session.delete(assignment) + wildcard = UserGroupAssignmentWaitingModel().query.\ + filter(UserGroupAssignmentWaitingModel.username == UserGroupAssignmentWaitingModel.MATCH_ALL_USERS).all() + for assignment in wildcard: + cls.add_user_to_group(user, assignment.group) db.session.commit() @staticmethod diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_user_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_user_service.py new file mode 100644 index 000000000..5dfaf2b0d --- /dev/null +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_user_service.py @@ -0,0 +1,50 @@ +"""Process Model.""" +from flask.app import Flask +from flask.testing import FlaskClient + +from spiffworkflow_backend.models.user_group_assignment_waiting import UserGroupAssignmentWaitingModel +from spiffworkflow_backend.services.group_service import GroupService +from spiffworkflow_backend.services.user_service import UserService +from tests.spiffworkflow_backend.helpers.base_test import BaseTest + +from spiffworkflow_backend.services.git_service import GitService + + +class TestUserService(BaseTest): + """TestUserService.""" + + def test_assigning_a_group_to_a_user_before_the_user_is_created ( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + ) -> None: + """Test_waiting_group_assignments""" + aTestGroup = GroupService.find_or_create_group("aTestGroup") + UserService.add_group_assignment("initiator_user", aTestGroup) + initiator_user = self.find_or_create_user("initiator_user") + assert initiator_user.groups[0] == aTestGroup + + def test_assigning_a_group_to_all_users_updates_new_users ( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + ) -> None: + """Test_waiting_group_assignments""" + everybody_group = GroupService.find_or_create_group("everybodyGroup") + UserService.add_group_assignment(UserGroupAssignmentWaitingModel.MATCH_ALL_USERS, everybody_group) + initiator_user = self.find_or_create_user("initiator_user") + assert initiator_user.groups[0] == everybody_group + + def test_assigning_a_group_to_all_users_updates_existing_users ( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + ) -> None: + """Test_waiting_group_assignments""" + initiator_user = self.find_or_create_user("initiator_user") + everybody_group = GroupService.find_or_create_group("everybodyGroup") + UserService.add_group_assignment(UserGroupAssignmentWaitingModel.MATCH_ALL_USERS, everybody_group) + assert initiator_user.groups[0] == everybody_group From 89377ea881d0da95bb8f79b92bdc1c5e06511028 Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 19 Dec 2022 10:09:19 -0500 Subject: [PATCH 09/29] Fixing some tests. --- .../tests/spiffworkflow_backend/unit/test_user_service.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_user_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_user_service.py index 5dfaf2b0d..02d391e7a 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_user_service.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_user_service.py @@ -21,7 +21,7 @@ class TestUserService(BaseTest): ) -> None: """Test_waiting_group_assignments""" aTestGroup = GroupService.find_or_create_group("aTestGroup") - UserService.add_group_assignment("initiator_user", aTestGroup) + UserService.add_waiting_group_assignment("initiator_user", aTestGroup) initiator_user = self.find_or_create_user("initiator_user") assert initiator_user.groups[0] == aTestGroup @@ -33,7 +33,7 @@ class TestUserService(BaseTest): ) -> None: """Test_waiting_group_assignments""" everybody_group = GroupService.find_or_create_group("everybodyGroup") - UserService.add_group_assignment(UserGroupAssignmentWaitingModel.MATCH_ALL_USERS, everybody_group) + UserService.add_waiting_group_assignment(UserGroupAssignmentWaitingModel.MATCH_ALL_USERS, everybody_group) initiator_user = self.find_or_create_user("initiator_user") assert initiator_user.groups[0] == everybody_group @@ -46,5 +46,5 @@ class TestUserService(BaseTest): """Test_waiting_group_assignments""" initiator_user = self.find_or_create_user("initiator_user") everybody_group = GroupService.find_or_create_group("everybodyGroup") - UserService.add_group_assignment(UserGroupAssignmentWaitingModel.MATCH_ALL_USERS, everybody_group) + UserService.add_waiting_group_assignment(UserGroupAssignmentWaitingModel.MATCH_ALL_USERS, everybody_group) assert initiator_user.groups[0] == everybody_group From 92258c6f9a2bfb080292234ba85fbf8c809a63f7 Mon Sep 17 00:00:00 2001 From: jasquat Date: Tue, 20 Dec 2022 15:47:30 -0500 Subject: [PATCH 10/29] pyl w/ burnettk --- spiffworkflow-backend/migrations/env.py | 2 - .../src/spiffworkflow_backend/models/group.py | 4 +- .../src/spiffworkflow_backend/models/user.py | 15 ++--- .../models/user_group_assignment_waiting.py | 8 ++- .../openid_blueprint/openid_blueprint.py | 2 +- .../scripts/add_permission.py | 9 +-- .../scripts/add_user_to_group.py | 5 +- .../scripts/clear_permissions.py | 9 +-- .../services/authorization_service.py | 11 ++-- .../services/user_service.py | 45 ++++++++++---- .../integration/test_openid_blueprint.py | 20 +++---- .../scripts/test_add_permission.py | 59 +++++++++---------- .../scripts/test_add_user_to_group.py | 41 ++++++------- .../scripts/test_delete_permissions.py | 37 +++++------- .../unit/test_authorization_service.py | 7 ++- .../unit/test_user_service.py | 30 ++++++---- .../src/routes/ProcessModelEditDiagram.tsx | 41 ++++++++----- 17 files changed, 189 insertions(+), 156 deletions(-) diff --git a/spiffworkflow-backend/migrations/env.py b/spiffworkflow-backend/migrations/env.py index 68feded2a..630e381ad 100644 --- a/spiffworkflow-backend/migrations/env.py +++ b/spiffworkflow-backend/migrations/env.py @@ -1,5 +1,3 @@ -from __future__ import with_statement - import logging from logging.config import fileConfig diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/group.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/group.py index eb9d9e1ca..34f4b4a6b 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/group.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/group.py @@ -27,7 +27,9 @@ class GroupModel(FlaskBpmnGroupModel): identifier = db.Column(db.String(255)) user_group_assignments = relationship("UserGroupAssignmentModel", cascade="delete") - user_group_assignments_waiting = relationship("UserGroupAssignmentWaitingModel", cascade="delete") + user_group_assignments_waiting = relationship( + "UserGroupAssignmentWaitingModel", cascade="delete" + ) users = relationship( # type: ignore "UserModel", viewonly=True, diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/user.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/user.py index ab520ea79..f3dc69bd3 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/user.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/user.py @@ -1,22 +1,15 @@ """User.""" from __future__ import annotations -from typing import Any - import jwt import marshmallow from flask import current_app -from flask_bpmn.api.api_error import ApiError from flask_bpmn.models.db import db from flask_bpmn.models.db import SpiffworkflowBaseDBModel from marshmallow import Schema from sqlalchemy.orm import relationship -from sqlalchemy.orm import validates from spiffworkflow_backend.models.group import GroupModel -from spiffworkflow_backend.services.authentication_service import ( - AuthenticationProviderTypes, -) class UserNotFoundError(Exception): @@ -29,8 +22,12 @@ class UserModel(SpiffworkflowBaseDBModel): __tablename__ = "user" __table_args__ = (db.UniqueConstraint("service", "service_id", name="service_key"),) id = db.Column(db.Integer, primary_key=True) - username = db.Column(db.String(255), nullable=False, unique=True) # should always be a unique value - service = db.Column(db.String(50), nullable=False, unique=False) # not 'openid' -- google, aws + username = db.Column( + db.String(255), nullable=False, unique=True + ) # should always be a unique value + service = db.Column( + db.String(50), nullable=False, unique=False + ) # not 'openid' -- google, aws service_id = db.Column(db.String(255), nullable=False, unique=False) display_name = db.Column(db.String(255)) email = db.Column(db.String(255)) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/user_group_assignment_waiting.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/user_group_assignment_waiting.py index 2d14e54c5..73fbc9707 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/user_group_assignment_waiting.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/user_group_assignment_waiting.py @@ -5,16 +5,19 @@ from sqlalchemy import ForeignKey from sqlalchemy.orm import relationship from spiffworkflow_backend.models.group import GroupModel -from spiffworkflow_backend.models.user import UserModel + class UserGroupAssignmentWaitingModel(SpiffworkflowBaseDBModel): """UserGroupAssignmentsWaitingModel - When a user is assinged to a group, but that username does not exist, we cache it here to be applied in the event the user does log into the system. """ + MATCH_ALL_USERS = "*" __tablename__ = "user_group_assignment_waiting" __table_args__ = ( - db.UniqueConstraint("username", "group_id", name="user_group_assignment_staged_unique"), + db.UniqueConstraint( + "username", "group_id", name="user_group_assignment_staged_unique" + ), ) id = db.Column(db.Integer, primary_key=True) @@ -24,6 +27,7 @@ class UserGroupAssignmentWaitingModel(SpiffworkflowBaseDBModel): group = relationship("GroupModel", overlaps="groups,user_group_assignment_waiting,users") # type: ignore def is_match_all(self): + """Is_match_all.""" if self.username == self.MATCH_ALL_USERS: return True return False diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/openid_blueprint/openid_blueprint.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/openid_blueprint/openid_blueprint.py index 0432b1e5e..f25100eed 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/openid_blueprint/openid_blueprint.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/openid_blueprint/openid_blueprint.py @@ -111,7 +111,7 @@ def token() -> dict: "iat": time.time(), "exp": time.time() + 86400, # Expire after a day. "sub": user_name, - "email": user_details['email'], + "email": user_details["email"], "preferred_username": user_details.get("preferred_username", user_name), }, client_secret, diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_permission.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_permission.py index 6c9d97a53..806fd991f 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_permission.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_permission.py @@ -1,19 +1,16 @@ """Get_env.""" from typing import Any -from spiffworkflow_backend.models.group import GroupModel -from spiffworkflow_backend.models.group import GroupNotFoundError from spiffworkflow_backend.models.script_attributes_context import ( ScriptAttributesContext, ) -from spiffworkflow_backend.models.user import UserModel -from spiffworkflow_backend.models.user import UserNotFoundError from spiffworkflow_backend.scripts.script import Script from spiffworkflow_backend.services.authorization_service import AuthorizationService from spiffworkflow_backend.services.group_service import GroupService -from spiffworkflow_backend.services.user_service import UserService # add_permission("read", "test/*", "Editors") + + class AddPermission(Script): """AddUserToGroup.""" @@ -35,4 +32,4 @@ class AddPermission(Script): target = AuthorizationService.find_or_create_permission_target(uri) AuthorizationService.create_permission_for_principal( group.principal, target, allowed_permission - ) \ No newline at end of file + ) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_user_to_group.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_user_to_group.py index 3342b5c02..814cf6dc9 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_user_to_group.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_user_to_group.py @@ -1,13 +1,10 @@ """Get_env.""" from typing import Any -from spiffworkflow_backend.models.group import GroupModel -from spiffworkflow_backend.models.group import GroupNotFoundError from spiffworkflow_backend.models.script_attributes_context import ( ScriptAttributesContext, ) from spiffworkflow_backend.models.user import UserModel -from spiffworkflow_backend.models.user import UserNotFoundError from spiffworkflow_backend.scripts.script import Script from spiffworkflow_backend.services.group_service import GroupService from spiffworkflow_backend.services.user_service import UserService @@ -35,4 +32,4 @@ class AddUserToGroup(Script): if user: UserService.add_user_to_group(user, group) else: - UserService.add_waiting_group_assignment(username, group) \ No newline at end of file + UserService.add_waiting_group_assignment(username, group) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/clear_permissions.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/clear_permissions.py index b210a6723..bf81acfc3 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/clear_permissions.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/clear_permissions.py @@ -1,16 +1,11 @@ """Get_env.""" from typing import Any -from flask_bpmn.models.db import db -from spiffworkflow_backend.models.group import GroupModel -from spiffworkflow_backend.models.group import GroupNotFoundError + from spiffworkflow_backend.models.script_attributes_context import ( ScriptAttributesContext, ) -from spiffworkflow_backend.models.user import UserModel -from spiffworkflow_backend.models.user import UserNotFoundError from spiffworkflow_backend.scripts.script import Script from spiffworkflow_backend.services.authorization_service import AuthorizationService -from spiffworkflow_backend.services.user_service import UserService class ClearPermissions(Script): @@ -27,4 +22,4 @@ class ClearPermissions(Script): **kwargs: Any, ) -> Any: """Run.""" - AuthorizationService.delete_all_permissions() \ No newline at end of file + AuthorizationService.delete_all_permissions() diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py index 8c3b98ed1..c8ecf82a5 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py @@ -219,6 +219,7 @@ class AuthorizationService: @classmethod def find_or_create_permission_target(cls, uri): + """Find_or_create_permission_target.""" uri_with_percent = re.sub(r"\*", "%", uri) permission_target = PermissionTargetModel.query.filter_by( uri=uri_with_percent @@ -452,9 +453,9 @@ class AuthorizationService: @classmethod def create_user_from_sign_in(cls, user_info: dict) -> UserModel: """Create_user_from_sign_in.""" - """name, family_name, given_name, middle_name, nickname, preferred_username,""" - """profile, picture, website, gender, birthdate, zoneinfo, locale, and updated_at. """ - """email""" + """Name, family_name, given_name, middle_name, nickname, preferred_username,""" + """Profile, picture, website, gender, birthdate, zoneinfo, locale, and updated_at. """ + """Email.""" is_new_user = False user_model = ( UserModel.query.filter(UserModel.service == user_info["iss"]) @@ -483,10 +484,10 @@ class AuthorizationService: service=user_info["iss"], service_id=user_info["sub"], email=email, - display_name = display_name + display_name=display_name, ) - else : + else: # Update with the latest information user_model.username = username user_model.email = email diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/user_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/user_service.py index a3d83e6cc..20412e549 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/user_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/user_service.py @@ -13,7 +13,9 @@ from spiffworkflow_backend.models.human_task_user import HumanTaskUserModel from spiffworkflow_backend.models.principal import PrincipalModel from spiffworkflow_backend.models.user import UserModel from spiffworkflow_backend.models.user_group_assignment import UserGroupAssignmentModel -from spiffworkflow_backend.models.user_group_assignment_waiting import UserGroupAssignmentWaitingModel +from spiffworkflow_backend.models.user_group_assignment_waiting import ( + UserGroupAssignmentWaitingModel, +) class UserService: @@ -26,7 +28,7 @@ class UserService: service: str, service_id: str, email: Optional[str] = "", - display_name: Optional[str] = "" + display_name: Optional[str] = "", ) -> UserModel: """Create_user.""" user_model: Optional[UserModel] = ( @@ -43,7 +45,7 @@ class UserService: service=service, service_id=service_id, email=email, - display_name=display_name + display_name=display_name, ) db.session.add(user_model) @@ -128,7 +130,12 @@ class UserService: @classmethod def add_user_to_group(cls, user: UserModel, group: GroupModel) -> None: """Add_user_to_group.""" - exists = UserGroupAssignmentModel().query.filter_by(user_id=user.id).filter_by(group_id=group.id).count() + exists = ( + UserGroupAssignmentModel() + .query.filter_by(user_id=user.id) + .filter_by(group_id=group.id) + .count() + ) if not exists: ugam = UserGroupAssignmentModel(user_id=user.id, group_id=group.id) db.session.add(ugam) @@ -136,9 +143,17 @@ class UserService: @classmethod def add_waiting_group_assignment(cls, username: str, group: GroupModel) -> None: - wugam = UserGroupAssignmentWaitingModel().query.filter_by(username=username).filter_by(group_id=group.id).first() + """Add_waiting_group_assignment.""" + wugam = ( + UserGroupAssignmentWaitingModel() + .query.filter_by(username=username) + .filter_by(group_id=group.id) + .first() + ) if not wugam: - wugam = UserGroupAssignmentWaitingModel(username=username, group_id=group.id) + wugam = UserGroupAssignmentWaitingModel( + username=username, group_id=group.id + ) db.session.add(wugam) db.session.commit() if wugam.is_match_all(): @@ -147,13 +162,23 @@ class UserService: @classmethod def apply_waiting_group_assignments(cls, user: UserModel) -> None: - waiting = UserGroupAssignmentWaitingModel().query.\ - filter(UserGroupAssignmentWaitingModel.username == user.username).all() + """Apply_waiting_group_assignments.""" + waiting = ( + UserGroupAssignmentWaitingModel() + .query.filter(UserGroupAssignmentWaitingModel.username == user.username) + .all() + ) for assignment in waiting: cls.add_user_to_group(user, assignment.group) db.session.delete(assignment) - wildcard = UserGroupAssignmentWaitingModel().query.\ - filter(UserGroupAssignmentWaitingModel.username == UserGroupAssignmentWaitingModel.MATCH_ALL_USERS).all() + wildcard = ( + UserGroupAssignmentWaitingModel() + .query.filter( + UserGroupAssignmentWaitingModel.username + == UserGroupAssignmentWaitingModel.MATCH_ALL_USERS + ) + .all() + ) for assignment in wildcard: cls.add_user_to_group(user, assignment.group) db.session.commit() diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_openid_blueprint.py b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_openid_blueprint.py index 23ceb97d5..ce1655cb9 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_openid_blueprint.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_openid_blueprint.py @@ -1,6 +1,5 @@ """Test_authentication.""" import base64 -import time import jwt from flask import Flask @@ -48,8 +47,8 @@ class TestFlaskOpenId(BaseTest): client: FlaskClient, with_db_and_bpmn_file_cleanup: None, ) -> None: - - code = ("testadmin1:1234123412341234") + """Test_get_token.""" + code = "testadmin1:1234123412341234" """It should be possible to get a token.""" backend_basic_auth_string = code @@ -67,11 +66,12 @@ class TestFlaskOpenId(BaseTest): response = client.post("/openid/token", data=data, headers=headers) assert response assert response.is_json - assert 'access_token' in response.json - assert 'id_token' in response.json - assert 'refresh_token' in response.json - - decoded_token = jwt.decode(response.json['id_token'], options={"verify_signature": False}) - assert 'iss' in decoded_token - assert 'email' in decoded_token + assert "access_token" in response.json + assert "id_token" in response.json + assert "refresh_token" in response.json + decoded_token = jwt.decode( + response.json["id_token"], options={"verify_signature": False} + ) + assert "iss" in decoded_token + assert "email" in decoded_token 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 91546735d..1fc739883 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_permission.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_permission.py @@ -1,30 +1,22 @@ """Test_get_localtime.""" from flask.app import Flask from flask.testing import FlaskClient -from flask_bpmn.models.db import db - -from spiffworkflow_backend.models.permission_assignment import PermissionAssignmentModel -from spiffworkflow_backend.models.permission_target import PermissionTargetModel -from spiffworkflow_backend.models.script_attributes_context import ScriptAttributesContext -from spiffworkflow_backend.scripts.add_permission import AddPermission -from spiffworkflow_backend.scripts.clear_permissions import ClearPermissions -from spiffworkflow_backend.services.authorization_service import AuthorizationService -from spiffworkflow_backend.services.group_service import GroupService from tests.spiffworkflow_backend.helpers.base_test import BaseTest -from tests.spiffworkflow_backend.helpers.test_data import load_test_spec from spiffworkflow_backend.models.group import GroupModel -from spiffworkflow_backend.models.user import UserModel -from spiffworkflow_backend.services.process_instance_processor import ( - ProcessInstanceProcessor, +from spiffworkflow_backend.models.permission_assignment import PermissionAssignmentModel +from spiffworkflow_backend.models.permission_target import PermissionTargetModel +from spiffworkflow_backend.models.script_attributes_context import ( + ScriptAttributesContext, ) -from spiffworkflow_backend.services.user_service import UserService +from spiffworkflow_backend.models.user import UserModel +from spiffworkflow_backend.scripts.add_permission import AddPermission class TestAddPermission(BaseTest): """TestAddPermission.""" - def test_can_add_permission ( + def test_can_add_permission( self, app: Flask, client: FlaskClient, @@ -32,7 +24,7 @@ class TestAddPermission(BaseTest): with_super_admin_user: UserModel, ) -> None: """Test_can_get_members_of_a_group.""" - test_user = self.find_or_create_user("test_user") + self.find_or_create_user("test_user") # now that we have everything, try to clear it out... script_attributes_context = ScriptAttributesContext( @@ -42,20 +34,27 @@ class TestAddPermission(BaseTest): process_model_identifier="my_test_user", ) - group = GroupModel.query.filter(GroupModel.identifier == 'my_test_group').first() - permission_target = PermissionTargetModel.query.filter(PermissionTargetModel.uri == '/test_add_permission/%').first() - assert(group is None) - assert(permission_target is None) + group = GroupModel.query.filter( + GroupModel.identifier == "my_test_group" + ).first() + permission_target = PermissionTargetModel.query.filter( + PermissionTargetModel.uri == "/test_add_permission/%" + ).first() + assert group is None + assert permission_target is None result = AddPermission().run( - script_attributes_context, - "read", - "/test_add_permission/*", - "my_test_group" + script_attributes_context, "read", "/test_add_permission/*", "my_test_group" ) - group = GroupModel.query.filter(GroupModel.identifier == 'my_test_group').first() - permission_target = PermissionTargetModel.query.filter(PermissionTargetModel.uri == '/test_add_permission/%').first() - permission_assignments = PermissionAssignmentModel.query.filter(PermissionAssignmentModel.principal_id == group.principal.id).all() - assert(group is not None) - assert(permission_target is not None) - assert(len(permission_assignments) == 1) + group = GroupModel.query.filter( + GroupModel.identifier == "my_test_group" + ).first() + permission_target = PermissionTargetModel.query.filter( + PermissionTargetModel.uri == "/test_add_permission/%" + ).first() + permission_assignments = PermissionAssignmentModel.query.filter( + PermissionAssignmentModel.principal_id == group.principal.id + ).all() + assert group is not None + assert permission_target is not None + assert len(permission_assignments) == 1 diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_user_to_group.py b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_user_to_group.py index 0f26e9c3d..fb0adafd4 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_user_to_group.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_user_to_group.py @@ -2,19 +2,17 @@ from flask.app import Flask from flask.testing import FlaskClient from flask_bpmn.models.db import db - -from spiffworkflow_backend.models.script_attributes_context import ScriptAttributesContext -from spiffworkflow_backend.models.user_group_assignment_waiting import UserGroupAssignmentWaitingModel -from spiffworkflow_backend.scripts.add_user_to_group import AddUserToGroup from tests.spiffworkflow_backend.helpers.base_test import BaseTest -from tests.spiffworkflow_backend.helpers.test_data import load_test_spec from spiffworkflow_backend.models.group import GroupModel -from spiffworkflow_backend.models.user import UserModel -from spiffworkflow_backend.services.process_instance_processor import ( - ProcessInstanceProcessor, +from spiffworkflow_backend.models.script_attributes_context import ( + ScriptAttributesContext, ) -from spiffworkflow_backend.services.user_service import UserService +from spiffworkflow_backend.models.user import UserModel +from spiffworkflow_backend.models.user_group_assignment_waiting import ( + UserGroupAssignmentWaitingModel, +) +from spiffworkflow_backend.scripts.add_user_to_group import AddUserToGroup class TestAddUserToGroup(BaseTest): @@ -38,11 +36,9 @@ class TestAddUserToGroup(BaseTest): process_model_identifier="my_test_user", ) result = AddUserToGroup().run( - script_attributes_context, - my_user.username, - my_group.identifier + script_attributes_context, my_user.username, my_group.identifier ) - assert(my_user in my_group.users) + assert my_user in my_group.users def test_can_add_non_existent_user_to_non_existent_group( self, @@ -51,6 +47,7 @@ class TestAddUserToGroup(BaseTest): with_db_and_bpmn_file_cleanup: None, with_super_admin_user: UserModel, ) -> None: + """Test_can_add_non_existent_user_to_non_existent_group.""" script_attributes_context = ScriptAttributesContext( task=None, environment_identifier="testing", @@ -58,11 +55,15 @@ class TestAddUserToGroup(BaseTest): process_model_identifier="my_test_user", ) result = AddUserToGroup().run( - script_attributes_context, - "dan@sartography.com", - "competent-joes" + script_attributes_context, "dan@sartography.com", "competent-joes" ) - my_group = GroupModel.query.filter(GroupModel.identifier == "competent-joes").first() - assert (my_group is not None) - waiting_assignments = UserGroupAssignmentWaitingModel().query.filter_by(username="dan@sartography.com").first() - assert (waiting_assignments is not None) + my_group = GroupModel.query.filter( + GroupModel.identifier == "competent-joes" + ).first() + assert my_group is not None + waiting_assignments = ( + UserGroupAssignmentWaitingModel() + .query.filter_by(username="dan@sartography.com") + .first() + ) + assert waiting_assignments is not None diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_delete_permissions.py b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_delete_permissions.py index 3fdec995c..8e180ec45 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_delete_permissions.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_delete_permissions.py @@ -2,27 +2,24 @@ from flask.app import Flask from flask.testing import FlaskClient from flask_bpmn.models.db import db +from tests.spiffworkflow_backend.helpers.base_test import BaseTest +from spiffworkflow_backend.models.group import GroupModel from spiffworkflow_backend.models.permission_target import PermissionTargetModel -from spiffworkflow_backend.models.script_attributes_context import ScriptAttributesContext +from spiffworkflow_backend.models.script_attributes_context import ( + ScriptAttributesContext, +) +from spiffworkflow_backend.models.user import UserModel from spiffworkflow_backend.scripts.clear_permissions import ClearPermissions from spiffworkflow_backend.services.authorization_service import AuthorizationService from spiffworkflow_backend.services.group_service import GroupService -from tests.spiffworkflow_backend.helpers.base_test import BaseTest -from tests.spiffworkflow_backend.helpers.test_data import load_test_spec - -from spiffworkflow_backend.models.group import GroupModel -from spiffworkflow_backend.models.user import UserModel -from spiffworkflow_backend.services.process_instance_processor import ( - ProcessInstanceProcessor, -) from spiffworkflow_backend.services.user_service import UserService class TestDeletePermissions(BaseTest): """TestGetGroupMembers.""" - def test_can_delete_members ( + def test_can_delete_members( self, app: Flask, client: FlaskClient, @@ -30,12 +27,12 @@ class TestDeletePermissions(BaseTest): with_super_admin_user: UserModel, ) -> None: """Test_can_get_members_of_a_group.""" - initiator_user = self.find_or_create_user("initiator_user") + self.find_or_create_user("initiator_user") testuser1 = self.find_or_create_user("testuser1") testuser2 = self.find_or_create_user("testuser2") testuser3 = self.find_or_create_user("testuser3") - group_a = GroupService.find_or_create_group('groupA') - group_b = GroupService.find_or_create_group('groupB') + group_a = GroupService.find_or_create_group("groupA") + group_b = GroupService.find_or_create_group("groupB") db.session.add(group_a) db.session.add(group_b) db.session.commit() @@ -43,12 +40,12 @@ class TestDeletePermissions(BaseTest): UserService.add_user_to_group(testuser2, group_a) UserService.add_user_to_group(testuser3, group_b) - target = PermissionTargetModel('test/*') + target = PermissionTargetModel("test/*") db.session.add(target) db.session.commit() - AuthorizationService.create_permission_for_principal(group_a.principal, - target, - "read") + AuthorizationService.create_permission_for_principal( + group_a.principal, target, "read" + ) # now that we have everything, try to clear it out... script_attributes_context = ScriptAttributesContext( task=None, @@ -56,9 +53,7 @@ class TestDeletePermissions(BaseTest): process_instance_id=1, process_model_identifier="my_test_user", ) - result = ClearPermissions().run( - script_attributes_context - ) + result = ClearPermissions().run(script_attributes_context) groups = GroupModel.query.all() - assert(0 == len(groups)) + assert 0 == len(groups) diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py index f85692d64..f42746289 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py @@ -134,7 +134,12 @@ class TestAuthorizationService(BaseTest): human_task.task_name, processor.bpmn_process_instance ) finance_user = AuthorizationService.create_user_from_sign_in( - {"username": "testuser2", "sub": "testuser2", "iss": "https://test.stuff", "email": "testuser2"} + { + "username": "testuser2", + "sub": "testuser2", + "iss": "https://test.stuff", + "email": "testuser2", + } ) ProcessInstanceService.complete_form_task( processor, spiff_task, {}, finance_user, human_task diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_user_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_user_service.py index 02d391e7a..887d14509 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_user_service.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_user_service.py @@ -1,50 +1,54 @@ """Process Model.""" from flask.app import Flask from flask.testing import FlaskClient - -from spiffworkflow_backend.models.user_group_assignment_waiting import UserGroupAssignmentWaitingModel -from spiffworkflow_backend.services.group_service import GroupService -from spiffworkflow_backend.services.user_service import UserService from tests.spiffworkflow_backend.helpers.base_test import BaseTest -from spiffworkflow_backend.services.git_service import GitService +from spiffworkflow_backend.models.user_group_assignment_waiting import ( + UserGroupAssignmentWaitingModel, +) +from spiffworkflow_backend.services.group_service import GroupService +from spiffworkflow_backend.services.user_service import UserService class TestUserService(BaseTest): """TestUserService.""" - def test_assigning_a_group_to_a_user_before_the_user_is_created ( + def test_assigning_a_group_to_a_user_before_the_user_is_created( self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None, ) -> None: - """Test_waiting_group_assignments""" + """Test_waiting_group_assignments.""" aTestGroup = GroupService.find_or_create_group("aTestGroup") UserService.add_waiting_group_assignment("initiator_user", aTestGroup) initiator_user = self.find_or_create_user("initiator_user") assert initiator_user.groups[0] == aTestGroup - def test_assigning_a_group_to_all_users_updates_new_users ( + def test_assigning_a_group_to_all_users_updates_new_users( self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None, ) -> None: - """Test_waiting_group_assignments""" + """Test_waiting_group_assignments.""" everybody_group = GroupService.find_or_create_group("everybodyGroup") - UserService.add_waiting_group_assignment(UserGroupAssignmentWaitingModel.MATCH_ALL_USERS, everybody_group) + UserService.add_waiting_group_assignment( + UserGroupAssignmentWaitingModel.MATCH_ALL_USERS, everybody_group + ) initiator_user = self.find_or_create_user("initiator_user") assert initiator_user.groups[0] == everybody_group - def test_assigning_a_group_to_all_users_updates_existing_users ( + def test_assigning_a_group_to_all_users_updates_existing_users( self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None, ) -> None: - """Test_waiting_group_assignments""" + """Test_waiting_group_assignments.""" initiator_user = self.find_or_create_user("initiator_user") everybody_group = GroupService.find_or_create_group("everybodyGroup") - UserService.add_waiting_group_assignment(UserGroupAssignmentWaitingModel.MATCH_ALL_USERS, everybody_group) + UserService.add_waiting_group_assignment( + UserGroupAssignmentWaitingModel.MATCH_ALL_USERS, everybody_group + ) assert initiator_user.groups[0] == everybody_group diff --git a/spiffworkflow-frontend/src/routes/ProcessModelEditDiagram.tsx b/spiffworkflow-frontend/src/routes/ProcessModelEditDiagram.tsx index a17d8eeb9..563d73038 100644 --- a/spiffworkflow-frontend/src/routes/ProcessModelEditDiagram.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessModelEditDiagram.tsx @@ -6,7 +6,16 @@ import { useSearchParams, } from 'react-router-dom'; // @ts-ignore -import { Button, Modal, Content, Tabs, TabList, Tab, TabPanels, TabPanel } from '@carbon/react'; +import { + Button, + Modal, + Content, + Tabs, + TabList, + Tab, + TabPanels, + TabPanel, +} from '@carbon/react'; import Row from 'react-bootstrap/Row'; import Col from 'react-bootstrap/Col'; @@ -403,10 +412,10 @@ export default function ProcessModelEditDiagram() { const jsonEditorOptions = () => { return Object.assign(generalEditorOptions(), { - minimap: { enabled: false }, - folding: true + minimap: { enabled: false }, + folding: true, }); - } + }; const setPreviousScriptUnitTest = () => { resetUnitTextResult(); @@ -472,9 +481,14 @@ export default function ProcessModelEditDiagram() { let expectedJson = ''; try { inputJson = JSON.parse(currentScriptUnitTest.inputJson.value); - expectedJson = JSON.parse(currentScriptUnitTest.expectedOutputJson.value); + expectedJson = JSON.parse( + currentScriptUnitTest.expectedOutputJson.value + ); } catch (e) { - setScriptUnitTestResult({ result:false, error:"The JSON provided contains a formatting error."}) + setScriptUnitTestResult({ + result: false, + error: 'The JSON provided contains a formatting error.', + }); return; } @@ -487,26 +501,25 @@ export default function ProcessModelEditDiagram() { bpmn_task_identifier: (scriptElement as any).id, python_script: scriptText, input_json: inputJson, - expected_output_json: expectedJson + expected_output_json: expectedJson, }, }); } }; const unitTestFailureElement = () => { - if ( - scriptUnitTestResult && - scriptUnitTestResult.result === false - ) { + if (scriptUnitTestResult && scriptUnitTestResult.result === false) { let errorMessage = ''; if (scriptUnitTestResult.context) { errorMessage = 'Unexpected result. Please see the comparison below.'; } else if (scriptUnitTestResult.line_number) { - errorMessage = `Error encountered running the script. Please check the code around line ${ scriptUnitTestResult.line_number}` + errorMessage = `Error encountered running the script. Please check the code around line ${scriptUnitTestResult.line_number}`; } else { - errorMessage = `Error encountered running the script. ${JSON.stringify(scriptUnitTestResult.error)}` + errorMessage = `Error encountered running the script. ${JSON.stringify( + scriptUnitTestResult.error + )}`; } - let errorStringElement = { errorMessage }; + let errorStringElement = {errorMessage}; let errorContextElement = null; From 3e4ab0145e973c76c37805a4939d32c1dc9d5ae3 Mon Sep 17 00:00:00 2001 From: jasquat Date: Tue, 20 Dec 2022 16:14:55 -0500 Subject: [PATCH 11/29] pyl fixed w/ burnettk --- .../src/spiffworkflow_backend/models/group.py | 2 +- .../models/user_group_assignment_waiting.py | 9 +++++---- .../services/authorization_service.py | 8 ++++---- .../spiffworkflow_backend/scripts/test_add_permission.py | 2 +- .../scripts/test_add_user_to_group.py | 4 ++-- .../scripts/test_delete_permissions.py | 2 +- .../spiffworkflow_backend/unit/test_user_service.py | 6 +++--- 7 files changed, 17 insertions(+), 16 deletions(-) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/group.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/group.py index 34f4b4a6b..980fc9302 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/group.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/group.py @@ -27,7 +27,7 @@ class GroupModel(FlaskBpmnGroupModel): identifier = db.Column(db.String(255)) user_group_assignments = relationship("UserGroupAssignmentModel", cascade="delete") - user_group_assignments_waiting = relationship( + user_group_assignments_waiting = relationship( # type: ignore "UserGroupAssignmentWaitingModel", cascade="delete" ) users = relationship( # type: ignore diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/user_group_assignment_waiting.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/user_group_assignment_waiting.py index 73fbc9707..ac2747c85 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/user_group_assignment_waiting.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/user_group_assignment_waiting.py @@ -8,8 +8,9 @@ from spiffworkflow_backend.models.group import GroupModel class UserGroupAssignmentWaitingModel(SpiffworkflowBaseDBModel): - """UserGroupAssignmentsWaitingModel - When a user is assinged to a group, but that username does not exist, - we cache it here to be applied in the event the user does log into the system. + """When a user is assigned to a group, but that username does not exist. + + We cache it here to be applied in the event the user does log in to the system. """ MATCH_ALL_USERS = "*" @@ -24,9 +25,9 @@ class UserGroupAssignmentWaitingModel(SpiffworkflowBaseDBModel): username = db.Column(db.String(255), nullable=False) group_id = db.Column(ForeignKey(GroupModel.id), nullable=False) - group = relationship("GroupModel", overlaps="groups,user_group_assignment_waiting,users") # type: ignore + group = relationship("GroupModel", overlaps="groups,user_group_assignments_waiting,users") # type: ignore - def is_match_all(self): + def is_match_all(self) -> bool: """Is_match_all.""" if self.username == self.MATCH_ALL_USERS: return True diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py index c8ecf82a5..563a9bba0 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py @@ -218,12 +218,12 @@ class AuthorizationService: cls.associate_user_with_group(user, default_group) @classmethod - def find_or_create_permission_target(cls, uri): + def find_or_create_permission_target(cls, uri: str) -> PermissionTargetModel: """Find_or_create_permission_target.""" uri_with_percent = re.sub(r"\*", "%", uri) - permission_target = PermissionTargetModel.query.filter_by( - uri=uri_with_percent - ).first() + permission_target: Optional[ + PermissionTargetModel + ] = PermissionTargetModel.query.filter_by(uri=uri_with_percent).first() if permission_target is None: permission_target = PermissionTargetModel(uri=uri_with_percent) db.session.add(permission_target) 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 1fc739883..cb70c4bc5 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_permission.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_permission.py @@ -43,7 +43,7 @@ class TestAddPermission(BaseTest): assert group is None assert permission_target is None - result = AddPermission().run( + AddPermission().run( script_attributes_context, "read", "/test_add_permission/*", "my_test_group" ) group = GroupModel.query.filter( diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_user_to_group.py b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_user_to_group.py index fb0adafd4..2e665a16e 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_user_to_group.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_user_to_group.py @@ -35,7 +35,7 @@ class TestAddUserToGroup(BaseTest): process_instance_id=1, process_model_identifier="my_test_user", ) - result = AddUserToGroup().run( + AddUserToGroup().run( script_attributes_context, my_user.username, my_group.identifier ) assert my_user in my_group.users @@ -54,7 +54,7 @@ class TestAddUserToGroup(BaseTest): process_instance_id=1, process_model_identifier="my_test_user", ) - result = AddUserToGroup().run( + AddUserToGroup().run( script_attributes_context, "dan@sartography.com", "competent-joes" ) my_group = GroupModel.query.filter( diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_delete_permissions.py b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_delete_permissions.py index 8e180ec45..41d6c0157 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_delete_permissions.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_delete_permissions.py @@ -53,7 +53,7 @@ class TestDeletePermissions(BaseTest): process_instance_id=1, process_model_identifier="my_test_user", ) - result = ClearPermissions().run(script_attributes_context) + ClearPermissions().run(script_attributes_context) groups = GroupModel.query.all() assert 0 == len(groups) diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_user_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_user_service.py index 887d14509..959975d5b 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_user_service.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_user_service.py @@ -20,10 +20,10 @@ class TestUserService(BaseTest): with_db_and_bpmn_file_cleanup: None, ) -> None: """Test_waiting_group_assignments.""" - aTestGroup = GroupService.find_or_create_group("aTestGroup") - UserService.add_waiting_group_assignment("initiator_user", aTestGroup) + a_test_group = GroupService.find_or_create_group("aTestGroup") + UserService.add_waiting_group_assignment("initiator_user", a_test_group) initiator_user = self.find_or_create_user("initiator_user") - assert initiator_user.groups[0] == aTestGroup + assert initiator_user.groups[0] == a_test_group def test_assigning_a_group_to_all_users_updates_new_users( self, From da603ffef7715b156dcaf69345f40db131bdc048 Mon Sep 17 00:00:00 2001 From: jasquat Date: Wed, 21 Dec 2022 11:24:38 -0500 Subject: [PATCH 12/29] added permission to run privileged scripts w/ burnettk --- .../config/permissions/development.yml | 6 +-- .../src/spiffworkflow_backend/routes/user.py | 2 +- .../scripts/add_permission.py | 4 ++ .../spiffworkflow_backend/scripts/script.py | 47 ++++++++++++++++--- .../services/process_model_service.py | 4 +- .../script_add_permission/add_permission.bpmn | 39 +++++++++++++++ .../scripts/test_add_permission.py | 38 +++++++++++++++ .../src/routes/ProcessModelEditDiagram.tsx | 2 +- 8 files changed, 128 insertions(+), 14 deletions(-) create mode 100644 spiffworkflow-backend/tests/data/script_add_permission/add_permission.bpmn diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml index 6ab24699c..d8c43d4f0 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml @@ -16,7 +16,7 @@ groups: alex, dan, mike, - jason, + jason@sartography.com, jarrad, elizabeth, jon, @@ -29,7 +29,7 @@ groups: alex, dan, mike, - jason, + jason@sartography.com, amir, jarrad, elizabeth, @@ -46,7 +46,7 @@ groups: fin, fin1, harmeet, - jason, + jason@sartography.com, sasha, manuchehr, lead, diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/user.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/user.py index a690a1606..48e30eed3 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/user.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/user.py @@ -105,7 +105,7 @@ def verify_token( ) from e if ( - user_info is not None and "error" not in user_info + user_info is not None and "error" not in user_info and 'iss' in user_info ): # not sure what to test yet user_model = ( UserModel.query.filter(UserModel.service == user_info["iss"]) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_permission.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_permission.py index 806fd991f..de78b524b 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_permission.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_permission.py @@ -14,6 +14,10 @@ from spiffworkflow_backend.services.group_service import GroupService class AddPermission(Script): """AddUserToGroup.""" + @staticmethod + def requires_privileged_permissions() -> bool: + return True + def get_description(self) -> str: """Get_description.""" return """Add a permission to a group. ex: add_permission("read", "test/*", "Editors") """ diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/script.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/script.py index b744694a2..0da73764d 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/script.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/script.py @@ -1,5 +1,7 @@ """Script.""" from __future__ import annotations +from spiffworkflow_backend.models.process_instance import ProcessInstanceModel, ProcessInstanceNotFoundError +from spiffworkflow_backend.services.authorization_service import AuthorizationService import importlib import os @@ -20,6 +22,10 @@ from spiffworkflow_backend.models.script_attributes_context import ( SCRIPT_SUB_CLASSES = None +class ScriptUnauthorizedForUserError(Exception): + pass + + class Script: """Provides an abstract class that defines how scripts should work, this must be extended in all Script Tasks.""" @@ -43,6 +49,10 @@ class Script: + "does not properly implement the run function.", ) + @staticmethod + def requires_privileged_permissions() -> bool: + return True + @staticmethod def generate_augmented_list( script_attributes_context: ScriptAttributesContext, @@ -71,18 +81,41 @@ class Script: that we created. """ instance = subclass() - return lambda *ar, **kw: subclass.run( - instance, - script_attributes_context, - *ar, - **kw, - ) + + def run_subclass(*ar: Any, **kw: Any) -> Any: + if subclass.requires_privileged_permissions(): + script_function_name = get_script_function_name(subclass) + uri = f"/v1.0/can-run-privileged-script/{script_function_name}" + process_instance = ProcessInstanceModel.query.filter_by(id=script_attributes_context.process_instance_id).first() + if process_instance is None: + raise ProcessInstanceNotFoundError( + f"Could not find a process instance with id '{script_attributes_context.process_instance_id}' " + f"when running script '{script_function_name}'" + ) + user = process_instance.process_initiator + has_permission = AuthorizationService.user_has_permission( + user=user, permission="create", target_uri=uri + ) + if not has_permission: + raise ScriptUnauthorizedForUserError( + f"User {user.username} does not have access to run privileged script '{script_function_name}'" + ) + return subclass.run( + instance, + script_attributes_context, + *ar, + **kw, + ) + return run_subclass + + def get_script_function_name(subclass: type[Script]) -> str: + return subclass.__module__.split(".")[-1] execlist = {} subclasses = Script.get_all_subclasses() for x in range(len(subclasses)): subclass = subclasses[x] - execlist[subclass.__module__.split(".")[-1]] = make_closure( + execlist[get_script_function_name(subclass)] = make_closure( subclass, script_attributes_context=script_attributes_context ) return execlist 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 67be986e1..714cd7991 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_model_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_model_service.py @@ -224,10 +224,10 @@ class ProcessModelService(FileSystemService): new_process_model_list = [] for process_model in process_models: uri = f"/v1.0/process-instances/{process_model.id.replace('/', ':')}" - result = AuthorizationService.user_has_permission( + has_permission = AuthorizationService.user_has_permission( user=user, permission="create", target_uri=uri ) - if result: + if has_permission: new_process_model_list.append(process_model) return new_process_model_list diff --git a/spiffworkflow-backend/tests/data/script_add_permission/add_permission.bpmn b/spiffworkflow-backend/tests/data/script_add_permission/add_permission.bpmn new file mode 100644 index 000000000..73070f728 --- /dev/null +++ b/spiffworkflow-backend/tests/data/script_add_permission/add_permission.bpmn @@ -0,0 +1,39 @@ + + + + + Flow_01cweoc + + + + Flow_1xle2yo + + + + Flow_01cweoc + Flow_1xle2yo + add_permission('read', '/v1.0/test_permission_uri', "test_group") + + + + + + + + + + + + + + + + + + + + + + + + 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 cb70c4bc5..a9e7bd267 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_permission.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_permission.py @@ -1,6 +1,11 @@ """Test_get_localtime.""" from flask.app import Flask +from flask_bpmn.api.api_error import ApiError +import pytest +from spiffworkflow_backend.scripts.script import ScriptUnauthorizedForUserError +from tests.spiffworkflow_backend.helpers.test_data import load_test_spec from flask.testing import FlaskClient +from spiffworkflow_backend.services.process_instance_processor import ProcessInstanceProcessor from tests.spiffworkflow_backend.helpers.base_test import BaseTest from spiffworkflow_backend.models.group import GroupModel @@ -58,3 +63,36 @@ class TestAddPermission(BaseTest): assert group is not None assert permission_target is not None assert len(permission_assignments) == 1 + + def test_add_permission_script_through_bpmn( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + ) -> None: + basic_user = self.find_or_create_user("basic_user") + privileged_user = self.find_or_create_user("privileged_user") + self.add_permissions_to_user( + privileged_user, + target_uri="/v1.0/can-run-privileged-script/*", + permission_names=["create"], + ) + process_model = load_test_spec( + process_model_id="add_permission", + process_model_source_directory="script_add_permission", + ) + process_instance = self.create_process_instance_from_process_model( + process_model=process_model, user=basic_user + ) + processor = ProcessInstanceProcessor(process_instance) + + with pytest.raises(ApiError) as exception: + processor.do_engine_steps(save=True) + assert "ScriptUnauthorizedForUserError" in str(exception) + + process_instance = self.create_process_instance_from_process_model( + process_model=process_model, user=privileged_user + ) + processor = ProcessInstanceProcessor(process_instance) + processor.do_engine_steps(save=True) + assert process_instance.status == "complete" diff --git a/spiffworkflow-frontend/src/routes/ProcessModelEditDiagram.tsx b/spiffworkflow-frontend/src/routes/ProcessModelEditDiagram.tsx index 563d73038..ad99ee818 100644 --- a/spiffworkflow-frontend/src/routes/ProcessModelEditDiagram.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessModelEditDiagram.tsx @@ -5,7 +5,6 @@ import { useParams, useSearchParams, } from 'react-router-dom'; -// @ts-ignore import { Button, Modal, @@ -15,6 +14,7 @@ import { Tab, TabPanels, TabPanel, +// @ts-ignore } from '@carbon/react'; import Row from 'react-bootstrap/Row'; import Col from 'react-bootstrap/Col'; From 1c7285024591c9ef0aaef552879825d770dad703 Mon Sep 17 00:00:00 2001 From: jasquat Date: Wed, 21 Dec 2022 11:39:12 -0500 Subject: [PATCH 13/29] default requiring permissions to run a script to True w/ burnettk --- .../src/spiffworkflow_backend/scripts/add_permission.py | 4 ---- .../src/spiffworkflow_backend/scripts/fact_service.py | 5 +++++ .../src/spiffworkflow_backend/scripts/get_current_user.py | 5 +++++ .../src/spiffworkflow_backend/scripts/get_env.py | 5 +++++ .../src/spiffworkflow_backend/scripts/get_frontend_url.py | 5 +++++ .../spiffworkflow_backend/scripts/get_group_members.py | 5 +++++ .../src/spiffworkflow_backend/scripts/get_localtime.py | 5 +++++ .../src/spiffworkflow_backend/scripts/get_process_info.py | 5 +++++ .../src/spiffworkflow_backend/scripts/script.py | 8 ++++++-- 9 files changed, 41 insertions(+), 6 deletions(-) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_permission.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_permission.py index de78b524b..806fd991f 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_permission.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_permission.py @@ -14,10 +14,6 @@ from spiffworkflow_backend.services.group_service import GroupService class AddPermission(Script): """AddUserToGroup.""" - @staticmethod - def requires_privileged_permissions() -> bool: - return True - def get_description(self) -> str: """Get_description.""" return """Add a permission to a group. ex: add_permission("read", "test/*", "Editors") """ diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/fact_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/fact_service.py index ee86a84a7..6e8a23c2c 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/fact_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/fact_service.py @@ -10,6 +10,11 @@ from spiffworkflow_backend.scripts.script import Script class FactService(Script): """FactService.""" + @staticmethod + def requires_privileged_permissions() -> bool: + """We have deemed this function safe to run without elevated permissions.""" + return False + def get_description(self) -> str: """Get_description.""" return """Just your basic class that can pull in data from a few api endpoints and diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_current_user.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_current_user.py index a1a1b47e9..66d21a4ca 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_current_user.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_current_user.py @@ -12,6 +12,11 @@ from spiffworkflow_backend.scripts.script import Script class GetCurrentUser(Script): """GetCurrentUser.""" + @staticmethod + def requires_privileged_permissions() -> bool: + """We have deemed this function safe to run without elevated permissions.""" + return False + def get_description(self) -> str: """Get_description.""" return """Return the current user.""" diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_env.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_env.py index cd586ae00..7a6b0f44c 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_env.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_env.py @@ -10,6 +10,11 @@ from spiffworkflow_backend.scripts.script import Script class GetEnv(Script): """GetEnv.""" + @staticmethod + def requires_privileged_permissions() -> bool: + """We have deemed this function safe to run without elevated permissions.""" + return False + def get_description(self) -> str: """Get_description.""" return """Returns the current environment - ie testing, staging, production.""" diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_frontend_url.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_frontend_url.py index 9490df95a..b128214ab 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_frontend_url.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_frontend_url.py @@ -12,6 +12,11 @@ from spiffworkflow_backend.scripts.script import Script class GetFrontendUrl(Script): """GetFrontendUrl.""" + @staticmethod + def requires_privileged_permissions() -> bool: + """We have deemed this function safe to run without elevated permissions.""" + return False + def get_description(self) -> str: """Get_description.""" return """Return the url to the frontend.""" diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_group_members.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_group_members.py index 243a8c524..8b179a6dd 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_group_members.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_group_members.py @@ -12,6 +12,11 @@ from spiffworkflow_backend.scripts.script import Script class GetGroupMembers(Script): """GetGroupMembers.""" + @staticmethod + def requires_privileged_permissions() -> bool: + """We have deemed this function safe to run without elevated permissions.""" + return False + def get_description(self) -> str: """Get_description.""" return """Return the list of usernames of the users in the given group.""" diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_localtime.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_localtime.py index 689b86d8c..7c688e56f 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_localtime.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_localtime.py @@ -14,6 +14,11 @@ from spiffworkflow_backend.scripts.script import Script class GetLocaltime(Script): """GetLocaltime.""" + @staticmethod + def requires_privileged_permissions() -> bool: + """We have deemed this function safe to run without elevated permissions.""" + return False + def get_description(self) -> str: """Get_description.""" return """Converts a Datetime object into a Datetime object for a specific timezone. diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_process_info.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_process_info.py index 45c70d6ba..138a19ac8 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_process_info.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_process_info.py @@ -10,6 +10,11 @@ from spiffworkflow_backend.scripts.script import Script class GetProcessInfo(Script): """GetProcessInfo.""" + @staticmethod + def requires_privileged_permissions() -> bool: + """We have deemed this function safe to run without elevated permissions.""" + return False + def get_description(self) -> str: """Get_description.""" return """Returns a dictionary of information about the currently running process.""" diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/script.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/script.py index 0da73764d..bb83ed60c 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/script.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/script.py @@ -51,6 +51,7 @@ 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.""" return True @staticmethod @@ -82,7 +83,7 @@ class Script: """ instance = subclass() - def run_subclass(*ar: Any, **kw: Any) -> Any: + def check_script_permission() -> None: if subclass.requires_privileged_permissions(): script_function_name = get_script_function_name(subclass) uri = f"/v1.0/can-run-privileged-script/{script_function_name}" @@ -100,13 +101,16 @@ class Script: raise ScriptUnauthorizedForUserError( f"User {user.username} does not have access to run privileged script '{script_function_name}'" ) + + def run_script_if_allowed(*ar: Any, **kw: Any) -> Any: + check_script_permission() return subclass.run( instance, script_attributes_context, *ar, **kw, ) - return run_subclass + return run_script_if_allowed def get_script_function_name(subclass: type[Script]) -> str: return subclass.__module__.split(".")[-1] From 74eccab994f051991c3065db463e5805e3a268a4 Mon Sep 17 00:00:00 2001 From: jasquat Date: Wed, 21 Dec 2022 11:42:45 -0500 Subject: [PATCH 14/29] pyl w/ burnettk --- .../src/spiffworkflow_backend/routes/user.py | 4 +++- .../src/spiffworkflow_backend/scripts/script.py | 15 +++++++++++---- .../services/process_instance_report_service.py | 11 +++++++---- .../scripts/test_add_permission.py | 14 ++++++++------ .../test_save_process_instance_metadata.py | 3 +-- .../src/routes/ProcessModelEditDiagram.tsx | 2 +- 6 files changed, 31 insertions(+), 18 deletions(-) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/user.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/user.py index 48e30eed3..9b63f9035 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/user.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/user.py @@ -105,7 +105,9 @@ def verify_token( ) from e if ( - user_info is not None and "error" not in user_info and 'iss' in user_info + user_info is not None + and "error" not in user_info + and "iss" in user_info ): # not sure what to test yet user_model = ( UserModel.query.filter(UserModel.service == user_info["iss"]) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/script.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/script.py index bb83ed60c..9a96988ad 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/script.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/script.py @@ -1,7 +1,5 @@ """Script.""" from __future__ import annotations -from spiffworkflow_backend.models.process_instance import ProcessInstanceModel, ProcessInstanceNotFoundError -from spiffworkflow_backend.services.authorization_service import AuthorizationService import importlib import os @@ -12,9 +10,12 @@ from typing import Callable from flask_bpmn.api.api_error import ApiError +from spiffworkflow_backend.models.process_instance import ProcessInstanceModel +from spiffworkflow_backend.models.process_instance import ProcessInstanceNotFoundError from spiffworkflow_backend.models.script_attributes_context import ( ScriptAttributesContext, ) +from spiffworkflow_backend.services.authorization_service import AuthorizationService # Generally speaking, having some global in a flask app is TERRIBLE. # This is here, because after loading the application this will never change under @@ -23,7 +24,7 @@ SCRIPT_SUB_CLASSES = None class ScriptUnauthorizedForUserError(Exception): - pass + """ScriptUnauthorizedForUserError.""" class Script: @@ -84,10 +85,13 @@ class Script: instance = subclass() def check_script_permission() -> None: + """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}" - process_instance = ProcessInstanceModel.query.filter_by(id=script_attributes_context.process_instance_id).first() + process_instance = ProcessInstanceModel.query.filter_by( + id=script_attributes_context.process_instance_id + ).first() if process_instance is None: raise ProcessInstanceNotFoundError( f"Could not find a process instance with id '{script_attributes_context.process_instance_id}' " @@ -103,6 +107,7 @@ class Script: ) def run_script_if_allowed(*ar: Any, **kw: Any) -> Any: + """Run_script_if_allowed.""" check_script_permission() return subclass.run( instance, @@ -110,9 +115,11 @@ class Script: *ar, **kw, ) + return run_script_if_allowed def get_script_function_name(subclass: type[Script]) -> str: + """Get_script_function_name.""" return subclass.__module__.split(".")[-1] execlist = {} diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py index 82a35fc5c..18c1bef6b 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py @@ -414,13 +414,16 @@ class ProcessInstanceReportService: ) if report_filter.with_tasks_assigned_to_my_group is True: - group_model_join_conditions = [GroupModel.id == HumanTaskModel.lane_assignment_id] + group_model_join_conditions = [ + GroupModel.id == HumanTaskModel.lane_assignment_id + ] if report_filter.user_group_identifier: - group_model_join_conditions.append(GroupModel.identifier == report_filter.user_group_identifier) + group_model_join_conditions.append( + GroupModel.identifier == report_filter.user_group_identifier + ) process_instance_query = process_instance_query.join(HumanTaskModel) process_instance_query = process_instance_query.join( - GroupModel, - and_(*group_model_join_conditions) + GroupModel, and_(*group_model_join_conditions) ) process_instance_query = process_instance_query.join( UserGroupAssignmentModel, 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 a9e7bd267..1ae7f5711 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_permission.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_permission.py @@ -1,12 +1,10 @@ """Test_get_localtime.""" -from flask.app import Flask -from flask_bpmn.api.api_error import ApiError import pytest -from spiffworkflow_backend.scripts.script import ScriptUnauthorizedForUserError -from tests.spiffworkflow_backend.helpers.test_data import load_test_spec +from flask.app import Flask from flask.testing import FlaskClient -from spiffworkflow_backend.services.process_instance_processor import ProcessInstanceProcessor +from flask_bpmn.api.api_error import ApiError from tests.spiffworkflow_backend.helpers.base_test import BaseTest +from tests.spiffworkflow_backend.helpers.test_data import load_test_spec from spiffworkflow_backend.models.group import GroupModel from spiffworkflow_backend.models.permission_assignment import PermissionAssignmentModel @@ -16,6 +14,9 @@ from spiffworkflow_backend.models.script_attributes_context import ( ) from spiffworkflow_backend.models.user import UserModel from spiffworkflow_backend.scripts.add_permission import AddPermission +from spiffworkflow_backend.services.process_instance_processor import ( + ProcessInstanceProcessor, +) class TestAddPermission(BaseTest): @@ -70,11 +71,12 @@ class TestAddPermission(BaseTest): client: FlaskClient, with_db_and_bpmn_file_cleanup: None, ) -> None: + """Test_add_permission_script_through_bpmn.""" basic_user = self.find_or_create_user("basic_user") privileged_user = self.find_or_create_user("privileged_user") self.add_permissions_to_user( privileged_user, - target_uri="/v1.0/can-run-privileged-script/*", + target_uri="/v1.0/can-run-privileged-script/add_permission", permission_names=["create"], ) process_model = load_test_spec( diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_save_process_instance_metadata.py b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_save_process_instance_metadata.py index 96eb62970..5f66fa72d 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_save_process_instance_metadata.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_save_process_instance_metadata.py @@ -24,7 +24,6 @@ class TestSaveProcessInstanceMetadata(BaseTest): with_super_admin_user: UserModel, ) -> None: """Test_can_save_process_instance_metadata.""" - initiator_user = self.find_or_create_user("initiator_user") self.create_process_group( client, with_super_admin_user, "test_group", "test_group" ) @@ -34,7 +33,7 @@ class TestSaveProcessInstanceMetadata(BaseTest): process_model_source_directory="save_process_instance_metadata", ) process_instance = self.create_process_instance_from_process_model( - process_model=process_model, user=initiator_user + process_model=process_model, user=with_super_admin_user ) processor = ProcessInstanceProcessor(process_instance) processor.do_engine_steps(save=True) diff --git a/spiffworkflow-frontend/src/routes/ProcessModelEditDiagram.tsx b/spiffworkflow-frontend/src/routes/ProcessModelEditDiagram.tsx index ad99ee818..03879d818 100644 --- a/spiffworkflow-frontend/src/routes/ProcessModelEditDiagram.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessModelEditDiagram.tsx @@ -14,7 +14,7 @@ import { Tab, TabPanels, TabPanel, -// @ts-ignore + // @ts-ignore } from '@carbon/react'; import Row from 'react-bootstrap/Row'; import Col from 'react-bootstrap/Col'; From 84bf70e2f2162a99e279eba15cd41328df948189 Mon Sep 17 00:00:00 2001 From: jasquat Date: Wed, 21 Dec 2022 12:13:33 -0500 Subject: [PATCH 15/29] 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( From f7eca09b19bfa80a20ee24a1d67da416d5443d6c Mon Sep 17 00:00:00 2001 From: jasquat Date: Wed, 21 Dec 2022 13:51:49 -0500 Subject: [PATCH 16/29] minor tweak to test bpmn w/ burnettk --- .../tests/data/script_add_permission/add_permission.bpmn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spiffworkflow-backend/tests/data/script_add_permission/add_permission.bpmn b/spiffworkflow-backend/tests/data/script_add_permission/add_permission.bpmn index 73070f728..50a56b1f6 100644 --- a/spiffworkflow-backend/tests/data/script_add_permission/add_permission.bpmn +++ b/spiffworkflow-backend/tests/data/script_add_permission/add_permission.bpmn @@ -12,7 +12,7 @@ Flow_01cweoc Flow_1xle2yo - add_permission('read', '/v1.0/test_permission_uri', "test_group") + add_permission('read', '/test_permission_uri', "test_group") From 2f2dc6f98cc5ca06ca52a7f50a32f40ec121405d Mon Sep 17 00:00:00 2001 From: jasquat Date: Wed, 21 Dec 2022 17:14:11 -0500 Subject: [PATCH 17/29] added method to add permissions based on macros w/ burnettk --- .../config/permissions/development.yml | 31 ------ .../terraform_deployed_environment.yml | 104 +++++++----------- .../services/authorization_service.py | 103 +++++++++++++++++ .../services/process_instance_service.py | 5 +- .../src/routes/ProcessModelEditDiagram.tsx | 7 +- 5 files changed, 150 insertions(+), 100 deletions(-) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml index 30f315846..d192a7dea 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml @@ -129,37 +129,6 @@ permissions: uri: /processes - manage-procurement-admin: - groups: ["Project Lead"] - users: [] - allowed_permissions: [create, read, update, delete] - uri: /process-groups/manage-procurement:* - manage-procurement-admin-slash: - groups: ["Project Lead"] - users: [] - allowed_permissions: [create, read, update, delete] - uri: /process-groups/manage-procurement/* - manage-procurement-admin-models: - groups: ["Project Lead"] - users: [] - allowed_permissions: [create, read, update, delete] - uri: /process-models/manage-procurement:* - manage-procurement-admin-models-slash: - groups: ["Project Lead"] - users: [] - allowed_permissions: [create, read, update, delete] - uri: /process-models/manage-procurement/* - manage-procurement-admin-instances: - groups: ["Project Lead"] - users: [] - allowed_permissions: [create, read, update, delete] - uri: /process-instances/manage-procurement:* - manage-procurement-admin-instances-slash: - groups: ["Project Lead"] - users: [] - allowed_permissions: [create, read, update, delete] - uri: /process-instances/manage-procurement/* - finance-admin: groups: ["Finance Team"] users: [] 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 fac5cf300..d51b10f9b 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 @@ -48,12 +48,6 @@ groups: lead1 ] - core-contributor: - users: - [ - core, - harmeet, - ] test: users: [natalia] @@ -64,25 +58,7 @@ permissions: allowed_permissions: [create, read, update, delete] uri: /* - tasks-crud: - groups: [everybody] - users: [] - allowed_permissions: [create, read, update, delete] - uri: /tasks/* - - service-tasks: - groups: [everybody] - users: [] - allowed_permissions: [read] - uri: /service-tasks - user-groups-for-current-user: - groups: [everybody] - users: [] - allowed_permissions: [read] - uri: /user-groups/for-current-user - - - # read all for everybody + # open system defaults for everybody read-all-process-groups: groups: [everybody] users: [] @@ -93,6 +69,8 @@ permissions: users: [] allowed_permissions: [read] uri: /process-models/* + + # basic perms for everybody read-all-process-instances-for-me: groups: [everybody] users: [] @@ -108,39 +86,23 @@ permissions: users: [] allowed_permissions: [read] uri: /processes + service-tasks: + groups: [everybody] + users: [] + allowed_permissions: [read] + uri: /service-tasks + tasks-crud: + groups: [everybody] + users: [] + allowed_permissions: [create, read, update, delete] + uri: /tasks/* + user-groups-for-current-user: + groups: [everybody] + users: [] + allowed_permissions: [read] + uri: /user-groups/for-current-user - manage-procurement-admin: - groups: ["Project Lead"] - users: [] - allowed_permissions: [create, read, update, delete] - uri: /process-groups/manage-procurement:* - manage-procurement-admin-slash: - groups: ["Project Lead"] - users: [] - allowed_permissions: [create, read, update, delete] - uri: /process-groups/manage-procurement/* - manage-procurement-admin-models: - groups: ["Project Lead"] - users: [] - allowed_permissions: [create, read, update, delete] - uri: /process-models/manage-procurement:* - manage-procurement-admin-models-slash: - groups: ["Project Lead"] - users: [] - allowed_permissions: [create, read, update, delete] - uri: /process-models/manage-procurement/* - manage-procurement-admin-instances: - groups: ["Project Lead"] - users: [] - allowed_permissions: [create, read, update, delete] - uri: /process-instances/manage-procurement:* - manage-procurement-admin-instances-slash: - groups: ["Project Lead"] - users: [] - allowed_permissions: [create, read, update, delete] - uri: /process-instances/manage-procurement/* - finance-admin: groups: ["Finance Team"] users: [] @@ -148,23 +110,37 @@ permissions: uri: /process-groups/manage-procurement:procurement:* manage-revenue-streams-instances: - groups: ["core-contributor", "demo"] + groups: ["demo"] users: [] - allowed_permissions: [create, read] + allowed_permissions: [create] uri: /process-instances/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/* - manage-procurement-invoice-instances: - groups: ["core-contributor", "demo"] + groups: ["demo"] users: [] - allowed_permissions: [create, read] + allowed_permissions: [create] uri: /process-instances/manage-procurement:procurement:core-contributor-invoice-management:* - manage-procurement-instances: - groups: ["core-contributor", "demo"] + groups: ["demo"] users: [] - allowed_permissions: [create, read] + allowed_permissions: [create] uri: /process-instances/manage-procurement:vendor-lifecycle-management:* + manage-revenue-streams-instances-for-me: + groups: ["demo"] + users: [] + allowed_permissions: [read] + uri: /process-instances/for-me/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/* + manage-procurement-invoice-instances-for-me: + groups: ["demo"] + users: [] + allowed_permissions: [read] + uri: /process-instances/for-me/manage-procurement:procurement:core-contributor-invoice-management:* + manage-procurement-instances-for-me: + groups: ["demo"] + users: [] + allowed_permissions: [read] + uri: /process-instances/for-me/manage-procurement:vendor-lifecycle-management:* + create-test-instances: groups: ["test"] users: [] diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py index 52ea3d826..ac6ac4c52 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py @@ -1,4 +1,5 @@ """Authorization_service.""" +from dataclasses import dataclass import inspect import re from hashlib import sha256 @@ -46,6 +47,21 @@ class UserDoesNotHaveAccessToTaskError(Exception): """UserDoesNotHaveAccessToTaskError.""" +@dataclass +class PermissionToAssign: + permission: str + target_uri: str + + +PATH_SEGMENTS_FOR_PERMISSION_ALL = [ + '/logs', + '/process-instances', + '/process-instance-suspend', + '/process-instance-terminate', + '/task-data', +] + + class AuthorizationService: """Determine whether a user has permission to perform their request.""" @@ -514,6 +530,93 @@ class AuthorizationService: # this cannot be None so ignore mypy return user_model # type: ignore + @classmethod + def get_permissions_to_assign(cls, permission: str, process_related_path_segment: str, target_uris: list[str]) -> list[PermissionToAssign]: + permissions = [permission] + if permission == "all": + permissions = ['create', 'read', 'update', 'delete'] + + permissions_to_assign: list[PermissionToAssign] = [] + + # we were thinking that if you can start an instance, you ought to be able to view your own instances. + if permission == "start": + target_uri = f"/process-instances/{process_related_path_segment}" + permissions_to_assign.append(PermissionToAssign(permission='create', target_uri=target_uri)) + target_uri = f"/process-instances/for-me/{process_related_path_segment}" + permissions_to_assign.append(PermissionToAssign(permission='read', target_uri=target_uri)) + + else: + if permission == 'all': + for path_segment in PATH_SEGMENTS_FOR_PERMISSION_ALL: + target_uris.append(f"{path_segment}/{process_related_path_segment}") + + for target_uri in target_uris: + for permission in permissions: + permissions_to_assign.append(PermissionToAssign(permission=permission, target_uri=target_uri)) + + return permissions_to_assign + + @classmethod + def explode_permissions(cls, permission: str, target: str) -> list[PermissionToAssign]: + """Explodes given permissions to and returns list of PermissionToAssign objects. + + These can be used to then iterate through and inserted into the database. + Target Macros: + PG:[process_group_identifier] + * affects given process-group and all sub process-groups and process-models + PM:[process_model_identifier] + * affects given process-model + BASIC + * Basic access to complete tasks and use the site + + Permission Macros: + all - create, read, update, delete + start - create process-instances (aka instantiate or start a process-model) + """ + permissions_to_assign: list[PermissionToAssign] = [] + if target.startswith("PG:"): + process_group_identifier = target.removeprefix("PG:").replace(":", "/") + process_related_path_segment = f"{process_group_identifier}/*" + target_uris = [] + 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, process_related_path_segment, target_uris) + + elif target.startswith("PM:"): + process_model_identifier = target.removeprefix("PM:").replace(":", "/") + process_related_path_segment = f"{process_model_identifier}/*" + target_uris = [] + 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, process_related_path_segment, target_uris) + + 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/*")) + else: + permissions_to_assign.append(PermissionToAssign(permission=permission, target_uri=target)) + + return permissions_to_assign + + @classmethod + def add_permission_from_uri_or_macro(cls, group_identifier: str, permission: str, target: str) -> None: + """Add_permission_from_uri_or_macro.""" + group = GroupService.find_or_create_group(group_identifier) + permissions_to_assign = cls.explode_permissions(permission, target) + for permission_to_assign in permissions_to_assign: + permission_target = AuthorizationService.find_or_create_permission_target(permission_to_assign.target_uri) + AuthorizationService.create_permission_for_principal( + group.principal, permission_target, permission_to_assign.permission + ) + class KeycloakAuthorization: """Interface with Keycloak server.""" 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 3427b47bb..0dec5e44d 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py @@ -17,7 +17,8 @@ from spiffworkflow_backend.models.task import MultiInstanceType from spiffworkflow_backend.models.task import Task from spiffworkflow_backend.models.user import UserModel from spiffworkflow_backend.services.authorization_service import AuthorizationService -from spiffworkflow_backend.services.git_service import GitService, GitCommandError +from spiffworkflow_backend.services.git_service import GitCommandError +from spiffworkflow_backend.services.git_service import GitService from spiffworkflow_backend.services.process_instance_processor import ( ProcessInstanceProcessor, ) @@ -38,7 +39,7 @@ class ProcessInstanceService: """Get_process_instance_from_spec.""" try: current_git_revision = GitService.get_current_revision() - except GitCommandError as ge: + except GitCommandError: current_git_revision = "" process_instance_model = ProcessInstanceModel( status=ProcessInstanceStatus.not_started.value, diff --git a/spiffworkflow-frontend/src/routes/ProcessModelEditDiagram.tsx b/spiffworkflow-frontend/src/routes/ProcessModelEditDiagram.tsx index 03879d818..a6fd282f9 100644 --- a/spiffworkflow-frontend/src/routes/ProcessModelEditDiagram.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessModelEditDiagram.tsx @@ -56,6 +56,8 @@ export default function ProcessModelEditDiagram() { const [processSearchEventBus, setProcessSearchEventBus] = useState(null); const [processSearchElement, setProcessSearchElement] = useState(null); const [processes, setProcesses] = useState([]); + const [displaySaveFileMessage, setDisplaySaveFileMessage] = + useState(false); const handleShowMarkdownEditor = () => setShowMarkdownEditor(true); @@ -157,6 +159,7 @@ export default function ProcessModelEditDiagram() { }; const navigateToProcessModelFile = (_result: any) => { + setDisplaySaveFileMessage(true); if (!params.file_name) { const fileNameWithExtension = `${newFileName}.${searchParams.get( 'file_type' @@ -167,9 +170,8 @@ export default function ProcessModelEditDiagram() { } }; - const [displaySaveFileMessage, setDisplaySaveFileMessage] = - useState(false); const saveDiagram = (bpmnXML: any, fileName = params.file_name) => { + setDisplaySaveFileMessage(false); setErrorMessage(null); setBpmnXmlForDiagramRendering(bpmnXML); @@ -204,7 +206,6 @@ export default function ProcessModelEditDiagram() { // after saving the file, make sure we null out newFileName // so it does not get used over the params setNewFileName(''); - setDisplaySaveFileMessage(true); }; const onDeleteFile = (fileName = params.file_name) => { From 22c894c70cc4ce7b30eafe2fc5003dcf2c0eee39 Mon Sep 17 00:00:00 2001 From: jasquat Date: Wed, 21 Dec 2022 17:38:56 -0500 Subject: [PATCH 18/29] added test for perm macros w/ burnettk --- .../services/authorization_service.py | 12 +++--- .../unit/test_authorization_service.py | 41 +++++++++++++++++++ 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py index ac6ac4c52..80fbc627a 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py @@ -575,21 +575,21 @@ class AuthorizationService: """ permissions_to_assign: list[PermissionToAssign] = [] if target.startswith("PG:"): - process_group_identifier = target.removeprefix("PG:").replace(":", "/") + process_group_identifier = target.removeprefix("PG:").replace(":", "/").removeprefix('/') process_related_path_segment = f"{process_group_identifier}/*" - target_uris = [] 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}"] + 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, process_related_path_segment, target_uris) elif target.startswith("PM:"): - process_model_identifier = target.removeprefix("PM:").replace(":", "/") + process_model_identifier = target.removeprefix("PM:").replace(":", "/").removeprefix('/') process_related_path_segment = f"{process_model_identifier}/*" - target_uris = [] + if process_model_identifier == "ALL": process_related_path_segment = "*" - target_uris = [f"/process-models/{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, process_related_path_segment, target_uris) elif target.startswith("BASIC"): diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py index f42746289..ee77a2425 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py @@ -144,3 +144,44 @@ class TestAuthorizationService(BaseTest): ProcessInstanceService.complete_form_task( processor, spiff_task, {}, finance_user, human_task ) + + def test_explode_permissions_all_on_process_model( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + with_super_admin_user: UserModel, + ) -> None: + expected_permissions = [ + ('/logs/some-process-group/some-process-model/*', 'create'), + ('/logs/some-process-group/some-process-model/*', 'delete'), + ('/logs/some-process-group/some-process-model/*', 'read'), + ('/logs/some-process-group/some-process-model/*', 'update'), + ('/process-groups/some-process-group/some-process-model/*', 'create'), + ('/process-groups/some-process-group/some-process-model/*', 'delete'), + ('/process-groups/some-process-group/some-process-model/*', 'read'), + ('/process-groups/some-process-group/some-process-model/*', 'update'), + ('/process-instance-suspend/some-process-group/some-process-model/*', 'create'), + ('/process-instance-suspend/some-process-group/some-process-model/*', 'delete'), + ('/process-instance-suspend/some-process-group/some-process-model/*', 'read'), + ('/process-instance-suspend/some-process-group/some-process-model/*', 'update'), + ('/process-instance-terminate/some-process-group/some-process-model/*', 'create'), + ('/process-instance-terminate/some-process-group/some-process-model/*', 'delete'), + ('/process-instance-terminate/some-process-group/some-process-model/*', 'read'), + ('/process-instance-terminate/some-process-group/some-process-model/*', 'update'), + ('/process-instances/some-process-group/some-process-model/*', 'create'), + ('/process-instances/some-process-group/some-process-model/*', 'delete'), + ('/process-instances/some-process-group/some-process-model/*', 'read'), + ('/process-instances/some-process-group/some-process-model/*', 'update'), + ('/process-models/some-process-group/some-process-model/*', 'create'), + ('/process-models/some-process-group/some-process-model/*', 'delete'), + ('/process-models/some-process-group/some-process-model/*', 'read'), + ('/process-models/some-process-group/some-process-model/*', 'update'), + ('/task-data/some-process-group/some-process-model/*', 'create'), + ('/task-data/some-process-group/some-process-model/*', 'delete'), + ('/task-data/some-process-group/some-process-model/*', 'read'), + ('/task-data/some-process-group/some-process-model/*', 'update'), + ] + permissions_to_assign = AuthorizationService.explode_permissions('all', 'PG:/some-process-group/some-process-model') + permissions_to_assign_tuples = sorted([(p.target_uri, p.permission) for p in permissions_to_assign]) + assert permissions_to_assign_tuples == expected_permissions From 2519c9f952a83971a5d78b65d07159f3a6d09b27 Mon Sep 17 00:00:00 2001 From: jasquat Date: Thu, 22 Dec 2022 09:29:31 -0500 Subject: [PATCH 19/29] added remaining tests for current permission macros --- .../unit/test_authorization_service.py | 91 ++++++++++++++++++- 1 file changed, 89 insertions(+), 2 deletions(-) diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py index ee77a2425..035d779dd 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py @@ -145,12 +145,11 @@ class TestAuthorizationService(BaseTest): processor, spiff_task, {}, finance_user, human_task ) - def test_explode_permissions_all_on_process_model( + def test_explode_permissions_all_on_process_group( self, app: Flask, client: FlaskClient, with_db_and_bpmn_file_cleanup: None, - with_super_admin_user: UserModel, ) -> None: expected_permissions = [ ('/logs/some-process-group/some-process-model/*', 'create'), @@ -185,3 +184,91 @@ class TestAuthorizationService(BaseTest): permissions_to_assign = AuthorizationService.explode_permissions('all', 'PG:/some-process-group/some-process-model') permissions_to_assign_tuples = sorted([(p.target_uri, p.permission) for p in permissions_to_assign]) assert permissions_to_assign_tuples == expected_permissions + + def test_explode_permissions_start_on_process_group( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + ) -> None: + expected_permissions = [ + ('/process-instances/for-me/some-process-group/some-process-model/*', 'read'), + ('/process-instances/some-process-group/some-process-model/*', 'create'), + ] + permissions_to_assign = AuthorizationService.explode_permissions('start', 'PG:/some-process-group/some-process-model') + permissions_to_assign_tuples = sorted([(p.target_uri, p.permission) for p in permissions_to_assign]) + assert permissions_to_assign_tuples == expected_permissions + + def test_explode_permissions_all_on_process_model( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + ) -> None: + expected_permissions = [ + ('/logs/some-process-group/some-process-model/*', 'create'), + ('/logs/some-process-group/some-process-model/*', 'delete'), + ('/logs/some-process-group/some-process-model/*', 'read'), + ('/logs/some-process-group/some-process-model/*', 'update'), + ('/process-instance-suspend/some-process-group/some-process-model/*', 'create'), + ('/process-instance-suspend/some-process-group/some-process-model/*', 'delete'), + ('/process-instance-suspend/some-process-group/some-process-model/*', 'read'), + ('/process-instance-suspend/some-process-group/some-process-model/*', 'update'), + ('/process-instance-terminate/some-process-group/some-process-model/*', 'create'), + ('/process-instance-terminate/some-process-group/some-process-model/*', 'delete'), + ('/process-instance-terminate/some-process-group/some-process-model/*', 'read'), + ('/process-instance-terminate/some-process-group/some-process-model/*', 'update'), + ('/process-instances/some-process-group/some-process-model/*', 'create'), + ('/process-instances/some-process-group/some-process-model/*', 'delete'), + ('/process-instances/some-process-group/some-process-model/*', 'read'), + ('/process-instances/some-process-group/some-process-model/*', 'update'), + ('/process-models/some-process-group/some-process-model/*', 'create'), + ('/process-models/some-process-group/some-process-model/*', 'delete'), + ('/process-models/some-process-group/some-process-model/*', 'read'), + ('/process-models/some-process-group/some-process-model/*', 'update'), + ('/task-data/some-process-group/some-process-model/*', 'create'), + ('/task-data/some-process-group/some-process-model/*', 'delete'), + ('/task-data/some-process-group/some-process-model/*', 'read'), + ('/task-data/some-process-group/some-process-model/*', 'update'), + ] + permissions_to_assign = AuthorizationService.explode_permissions('all', 'PM:/some-process-group/some-process-model') + permissions_to_assign_tuples = sorted([(p.target_uri, p.permission) for p in permissions_to_assign]) + assert permissions_to_assign_tuples == expected_permissions + + def test_explode_permissions_start_on_process_model( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + ) -> None: + expected_permissions = [ + ('/process-instances/for-me/some-process-group/some-process-model/*', 'read'), + ('/process-instances/some-process-group/some-process-model/*', 'create'), + ] + permissions_to_assign = AuthorizationService.explode_permissions('start', 'PM:/some-process-group/some-process-model') + permissions_to_assign_tuples = sorted([(p.target_uri, p.permission) for p in permissions_to_assign]) + assert permissions_to_assign_tuples == expected_permissions + + def test_explode_permissions_basic( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + ) -> None: + expected_permissions = [ + ('/process-instances/for-me', 'read'), + ('/process-instances/reports/*', 'create'), + ('/process-instances/reports/*', 'delete'), + ('/process-instances/reports/*', 'read'), + ('/process-instances/reports/*', 'update'), + ('/processes', 'read'), + ('/service-tasks', 'read'), + ('/tasks/*', 'create'), + ('/tasks/*', 'delete'), + ('/tasks/*', 'read'), + ('/tasks/*', 'update'), + ('/user-groups/for-current-user', 'read'), + ] + permissions_to_assign = AuthorizationService.explode_permissions('all', 'BASIC') + permissions_to_assign_tuples = sorted([(p.target_uri, p.permission) for p in permissions_to_assign]) + assert permissions_to_assign_tuples == expected_permissions From 1f6f20a734077d351385f411b6a0c4e2de73e885 Mon Sep 17 00:00:00 2001 From: jasquat Date: Thu, 22 Dec 2022 09:57:13 -0500 Subject: [PATCH 20/29] added ALL macro for easier use with admin groups and some failure test cases --- .../services/authorization_service.py | 47 +++++++++++++---- .../unit/test_authorization_service.py | 52 ++++++++++++++++++- 2 files changed, 87 insertions(+), 12 deletions(-) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py index 80fbc627a..f4a3adeb8 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py @@ -47,6 +47,10 @@ class UserDoesNotHaveAccessToTaskError(Exception): """UserDoesNotHaveAccessToTaskError.""" +class InvalidPermissionError(Exception): + pass + + @dataclass class PermissionToAssign: permission: str @@ -531,22 +535,22 @@ class AuthorizationService: return user_model # type: ignore @classmethod - def get_permissions_to_assign(cls, permission: str, process_related_path_segment: str, target_uris: list[str]) -> list[PermissionToAssign]: - permissions = [permission] - if permission == "all": + def get_permissions_to_assign(cls, permission_set: str, process_related_path_segment: str, target_uris: list[str]) -> list[PermissionToAssign]: + permissions = permission_set.split(',') + if permission_set == "all": permissions = ['create', 'read', 'update', 'delete'] permissions_to_assign: list[PermissionToAssign] = [] # we were thinking that if you can start an instance, you ought to be able to view your own instances. - if permission == "start": + if permission_set == "start": target_uri = f"/process-instances/{process_related_path_segment}" permissions_to_assign.append(PermissionToAssign(permission='create', target_uri=target_uri)) target_uri = f"/process-instances/for-me/{process_related_path_segment}" permissions_to_assign.append(PermissionToAssign(permission='read', target_uri=target_uri)) else: - if permission == 'all': + if permission_set == 'all': for path_segment in PATH_SEGMENTS_FOR_PERMISSION_ALL: target_uris.append(f"{path_segment}/{process_related_path_segment}") @@ -557,11 +561,13 @@ class AuthorizationService: return permissions_to_assign @classmethod - def explode_permissions(cls, permission: str, target: str) -> list[PermissionToAssign]: + def explode_permissions(cls, permission_set: str, target: str) -> list[PermissionToAssign]: """Explodes given permissions to and returns list of PermissionToAssign objects. These can be used to then iterate through and inserted into the database. Target Macros: + ALL + * gives access to ALL api endpoints - useful to give admin-like permissions PG:[process_group_identifier] * affects given process-group and all sub process-groups and process-models PM:[process_model_identifier] @@ -570,17 +576,24 @@ class AuthorizationService: * Basic access to complete tasks and use the site Permission Macros: - all - create, read, update, delete - start - create process-instances (aka instantiate or start a process-model) + all + * create, read, update, delete + start + * create process-instances (aka instantiate or start a process-model) + * only works with PG and PM target macros """ permissions_to_assign: list[PermissionToAssign] = [] + permissions = permission_set.split(',') + if permission_set == "all": + 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, process_related_path_segment, target_uris) + permissions_to_assign = permissions_to_assign + cls.get_permissions_to_assign(permission_set, process_related_path_segment, target_uris) elif target.startswith("PM:"): process_model_identifier = target.removeprefix("PM:").replace(":", "/").removeprefix('/') @@ -590,7 +603,10 @@ class AuthorizationService: 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, process_related_path_segment, target_uris) + permissions_to_assign = permissions_to_assign + cls.get_permissions_to_assign(permission_set, process_related_path_segment, target_uris) + + 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")) @@ -601,8 +617,17 @@ class AuthorizationService: 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/*")) + elif target == "ALL": + for permission in permissions: + permissions_to_assign.append(PermissionToAssign(permission=permission, target_uri='/*')) + elif target.startswith('/'): + for permission in permissions: + permissions_to_assign.append(PermissionToAssign(permission=permission, target_uri=target)) else: - permissions_to_assign.append(PermissionToAssign(permission=permission, target_uri=target)) + raise InvalidPermissionError( + f"Target uri '{target}' with permission set '{permission_set}' is invalid. " + f"The target uri must either be a macro of PG, PM, BASIC, or ALL or an api uri." + ) return permissions_to_assign diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py index 035d779dd..37a366aad 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py @@ -6,7 +6,7 @@ from tests.spiffworkflow_backend.helpers.base_test import BaseTest from spiffworkflow_backend.models.user import UserModel from spiffworkflow_backend.models.user import UserNotFoundError -from spiffworkflow_backend.services.authorization_service import AuthorizationService +from spiffworkflow_backend.services.authorization_service import AuthorizationService, InvalidPermissionError from spiffworkflow_backend.services.process_instance_processor import ( ProcessInstanceProcessor, ) @@ -272,3 +272,53 @@ class TestAuthorizationService(BaseTest): permissions_to_assign = AuthorizationService.explode_permissions('all', 'BASIC') permissions_to_assign_tuples = sorted([(p.target_uri, p.permission) for p in permissions_to_assign]) assert permissions_to_assign_tuples == expected_permissions + + def test_explode_permissions_all( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + ) -> None: + expected_permissions = [ + ('/*', 'create'), + ('/*', 'delete'), + ('/*', 'read'), + ('/*', 'update'), + ] + permissions_to_assign = AuthorizationService.explode_permissions('all', 'ALL') + permissions_to_assign_tuples = sorted([(p.target_uri, p.permission) for p in permissions_to_assign]) + assert permissions_to_assign_tuples == expected_permissions + + def test_explode_permissions_with_target_uri( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + ) -> None: + expected_permissions = [ + ('/hey/model', 'create'), + ('/hey/model', 'delete'), + ('/hey/model', 'read'), + ('/hey/model', 'update'), + ] + permissions_to_assign = AuthorizationService.explode_permissions('all', '/hey/model') + permissions_to_assign_tuples = sorted([(p.target_uri, p.permission) for p in permissions_to_assign]) + assert permissions_to_assign_tuples == expected_permissions + + def test_explode_permissions_with_invalid_target_uri( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + ) -> None: + with pytest.raises(InvalidPermissionError): + AuthorizationService.explode_permissions('all', 'BAD_MACRO') + + def test_explode_permissions_with_start_to_incorrect_target( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + ) -> None: + with pytest.raises(InvalidPermissionError): + AuthorizationService.explode_permissions('start', '/hey/model') From 99839a3e1b3766caaf2bc7ae0d5e3d1b93a2b209 Mon Sep 17 00:00:00 2001 From: jasquat Date: Thu, 22 Dec 2022 09:59:55 -0500 Subject: [PATCH 21/29] pyl --- .../terraform_deployed_environment.yml | 2 +- .../services/authorization_service.py | 131 ++++++--- .../unit/test_authorization_service.py | 274 ++++++++++++------ 3 files changed, 278 insertions(+), 129 deletions(-) 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 d51b10f9b..37b8ca36a 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 @@ -69,7 +69,7 @@ permissions: users: [] allowed_permissions: [read] uri: /process-models/* - + # basic perms for everybody read-all-process-instances-for-me: groups: [everybody] diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py index f4a3adeb8..94c07d4c1 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py @@ -1,7 +1,7 @@ """Authorization_service.""" -from dataclasses import dataclass import inspect import re +from dataclasses import dataclass from hashlib import sha256 from hmac import compare_digest from hmac import HMAC @@ -48,21 +48,23 @@ class UserDoesNotHaveAccessToTaskError(Exception): class InvalidPermissionError(Exception): - pass + """InvalidPermissionError.""" @dataclass class PermissionToAssign: + """PermissionToAssign.""" + permission: str target_uri: str PATH_SEGMENTS_FOR_PERMISSION_ALL = [ - '/logs', - '/process-instances', - '/process-instance-suspend', - '/process-instance-terminate', - '/task-data', + "/logs", + "/process-instances", + "/process-instance-suspend", + "/process-instance-terminate", + "/task-data", ] @@ -535,33 +537,47 @@ class AuthorizationService: return user_model # type: ignore @classmethod - def get_permissions_to_assign(cls, permission_set: str, process_related_path_segment: str, target_uris: list[str]) -> list[PermissionToAssign]: - permissions = permission_set.split(',') + def get_permissions_to_assign( + cls, + permission_set: str, + process_related_path_segment: str, + target_uris: list[str], + ) -> list[PermissionToAssign]: + """Get_permissions_to_assign.""" + permissions = permission_set.split(",") if permission_set == "all": - permissions = ['create', 'read', 'update', 'delete'] + permissions = ["create", "read", "update", "delete"] permissions_to_assign: list[PermissionToAssign] = [] # we were thinking that if you can start an instance, you ought to be able to view your own instances. if permission_set == "start": target_uri = f"/process-instances/{process_related_path_segment}" - permissions_to_assign.append(PermissionToAssign(permission='create', target_uri=target_uri)) + permissions_to_assign.append( + PermissionToAssign(permission="create", target_uri=target_uri) + ) target_uri = f"/process-instances/for-me/{process_related_path_segment}" - permissions_to_assign.append(PermissionToAssign(permission='read', target_uri=target_uri)) + permissions_to_assign.append( + PermissionToAssign(permission="read", target_uri=target_uri) + ) else: - if permission_set == 'all': + if permission_set == "all": for path_segment in PATH_SEGMENTS_FOR_PERMISSION_ALL: target_uris.append(f"{path_segment}/{process_related_path_segment}") for target_uri in target_uris: for permission in permissions: - permissions_to_assign.append(PermissionToAssign(permission=permission, target_uri=target_uri)) + permissions_to_assign.append( + PermissionToAssign(permission=permission, target_uri=target_uri) + ) return permissions_to_assign @classmethod - def explode_permissions(cls, permission_set: str, target: str) -> list[PermissionToAssign]: + def explode_permissions( + cls, permission_set: str, target: str + ) -> list[PermissionToAssign]: """Explodes given permissions to and returns list of PermissionToAssign objects. These can be used to then iterate through and inserted into the database. @@ -583,46 +599,87 @@ class AuthorizationService: * only works with PG and PM target macros """ permissions_to_assign: list[PermissionToAssign] = [] - permissions = permission_set.split(',') + permissions = permission_set.split(",") if permission_set == "all": - permissions = ['create', 'read', 'update', 'delete'] + permissions = ["create", "read", "update", "delete"] if target.startswith("PG:"): - process_group_identifier = target.removeprefix("PG:").replace(":", "/").removeprefix('/') + 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) + 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 + ) + ) elif target.startswith("PM:"): - process_model_identifier = target.removeprefix("PM:").replace(":", "/").removeprefix('/') + 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 = ( + permissions_to_assign + + cls.get_permissions_to_assign( + permission_set, process_related_path_segment, target_uris + ) + ) elif permission_set == "start": - raise InvalidPermissionError("Permission 'start' is only available for macros PM and PG.") + 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")) + 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/*")) + 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/*") + ) elif target == "ALL": for permission in permissions: - permissions_to_assign.append(PermissionToAssign(permission=permission, target_uri='/*')) - elif target.startswith('/'): + permissions_to_assign.append( + PermissionToAssign(permission=permission, target_uri="/*") + ) + elif target.startswith("/"): for permission in permissions: - permissions_to_assign.append(PermissionToAssign(permission=permission, target_uri=target)) + permissions_to_assign.append( + PermissionToAssign(permission=permission, target_uri=target) + ) else: raise InvalidPermissionError( f"Target uri '{target}' with permission set '{permission_set}' is invalid. " @@ -632,12 +689,16 @@ class AuthorizationService: return permissions_to_assign @classmethod - def add_permission_from_uri_or_macro(cls, group_identifier: str, permission: str, target: str) -> None: + def add_permission_from_uri_or_macro( + cls, group_identifier: str, permission: str, target: str + ) -> None: """Add_permission_from_uri_or_macro.""" group = GroupService.find_or_create_group(group_identifier) permissions_to_assign = cls.explode_permissions(permission, target) for permission_to_assign in permissions_to_assign: - permission_target = AuthorizationService.find_or_create_permission_target(permission_to_assign.target_uri) + permission_target = AuthorizationService.find_or_create_permission_target( + permission_to_assign.target_uri + ) AuthorizationService.create_permission_for_principal( group.principal, permission_target, permission_to_assign.permission ) diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py index 37a366aad..300b99ade 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py @@ -6,7 +6,8 @@ from tests.spiffworkflow_backend.helpers.base_test import BaseTest from spiffworkflow_backend.models.user import UserModel from spiffworkflow_backend.models.user import UserNotFoundError -from spiffworkflow_backend.services.authorization_service import AuthorizationService, InvalidPermissionError +from spiffworkflow_backend.services.authorization_service import AuthorizationService +from spiffworkflow_backend.services.authorization_service import InvalidPermissionError from spiffworkflow_backend.services.process_instance_processor import ( ProcessInstanceProcessor, ) @@ -151,38 +152,67 @@ class TestAuthorizationService(BaseTest): client: FlaskClient, with_db_and_bpmn_file_cleanup: None, ) -> None: + """Test_explode_permissions_all_on_process_group.""" expected_permissions = [ - ('/logs/some-process-group/some-process-model/*', 'create'), - ('/logs/some-process-group/some-process-model/*', 'delete'), - ('/logs/some-process-group/some-process-model/*', 'read'), - ('/logs/some-process-group/some-process-model/*', 'update'), - ('/process-groups/some-process-group/some-process-model/*', 'create'), - ('/process-groups/some-process-group/some-process-model/*', 'delete'), - ('/process-groups/some-process-group/some-process-model/*', 'read'), - ('/process-groups/some-process-group/some-process-model/*', 'update'), - ('/process-instance-suspend/some-process-group/some-process-model/*', 'create'), - ('/process-instance-suspend/some-process-group/some-process-model/*', 'delete'), - ('/process-instance-suspend/some-process-group/some-process-model/*', 'read'), - ('/process-instance-suspend/some-process-group/some-process-model/*', 'update'), - ('/process-instance-terminate/some-process-group/some-process-model/*', 'create'), - ('/process-instance-terminate/some-process-group/some-process-model/*', 'delete'), - ('/process-instance-terminate/some-process-group/some-process-model/*', 'read'), - ('/process-instance-terminate/some-process-group/some-process-model/*', 'update'), - ('/process-instances/some-process-group/some-process-model/*', 'create'), - ('/process-instances/some-process-group/some-process-model/*', 'delete'), - ('/process-instances/some-process-group/some-process-model/*', 'read'), - ('/process-instances/some-process-group/some-process-model/*', 'update'), - ('/process-models/some-process-group/some-process-model/*', 'create'), - ('/process-models/some-process-group/some-process-model/*', 'delete'), - ('/process-models/some-process-group/some-process-model/*', 'read'), - ('/process-models/some-process-group/some-process-model/*', 'update'), - ('/task-data/some-process-group/some-process-model/*', 'create'), - ('/task-data/some-process-group/some-process-model/*', 'delete'), - ('/task-data/some-process-group/some-process-model/*', 'read'), - ('/task-data/some-process-group/some-process-model/*', 'update'), + ("/logs/some-process-group/some-process-model/*", "create"), + ("/logs/some-process-group/some-process-model/*", "delete"), + ("/logs/some-process-group/some-process-model/*", "read"), + ("/logs/some-process-group/some-process-model/*", "update"), + ("/process-groups/some-process-group/some-process-model/*", "create"), + ("/process-groups/some-process-group/some-process-model/*", "delete"), + ("/process-groups/some-process-group/some-process-model/*", "read"), + ("/process-groups/some-process-group/some-process-model/*", "update"), + ( + "/process-instance-suspend/some-process-group/some-process-model/*", + "create", + ), + ( + "/process-instance-suspend/some-process-group/some-process-model/*", + "delete", + ), + ( + "/process-instance-suspend/some-process-group/some-process-model/*", + "read", + ), + ( + "/process-instance-suspend/some-process-group/some-process-model/*", + "update", + ), + ( + "/process-instance-terminate/some-process-group/some-process-model/*", + "create", + ), + ( + "/process-instance-terminate/some-process-group/some-process-model/*", + "delete", + ), + ( + "/process-instance-terminate/some-process-group/some-process-model/*", + "read", + ), + ( + "/process-instance-terminate/some-process-group/some-process-model/*", + "update", + ), + ("/process-instances/some-process-group/some-process-model/*", "create"), + ("/process-instances/some-process-group/some-process-model/*", "delete"), + ("/process-instances/some-process-group/some-process-model/*", "read"), + ("/process-instances/some-process-group/some-process-model/*", "update"), + ("/process-models/some-process-group/some-process-model/*", "create"), + ("/process-models/some-process-group/some-process-model/*", "delete"), + ("/process-models/some-process-group/some-process-model/*", "read"), + ("/process-models/some-process-group/some-process-model/*", "update"), + ("/task-data/some-process-group/some-process-model/*", "create"), + ("/task-data/some-process-group/some-process-model/*", "delete"), + ("/task-data/some-process-group/some-process-model/*", "read"), + ("/task-data/some-process-group/some-process-model/*", "update"), ] - permissions_to_assign = AuthorizationService.explode_permissions('all', 'PG:/some-process-group/some-process-model') - permissions_to_assign_tuples = sorted([(p.target_uri, p.permission) for p in permissions_to_assign]) + permissions_to_assign = AuthorizationService.explode_permissions( + "all", "PG:/some-process-group/some-process-model" + ) + permissions_to_assign_tuples = sorted( + [(p.target_uri, p.permission) for p in permissions_to_assign] + ) assert permissions_to_assign_tuples == expected_permissions def test_explode_permissions_start_on_process_group( @@ -191,12 +221,20 @@ class TestAuthorizationService(BaseTest): client: FlaskClient, with_db_and_bpmn_file_cleanup: None, ) -> None: + """Test_explode_permissions_start_on_process_group.""" expected_permissions = [ - ('/process-instances/for-me/some-process-group/some-process-model/*', 'read'), - ('/process-instances/some-process-group/some-process-model/*', 'create'), + ( + "/process-instances/for-me/some-process-group/some-process-model/*", + "read", + ), + ("/process-instances/some-process-group/some-process-model/*", "create"), ] - permissions_to_assign = AuthorizationService.explode_permissions('start', 'PG:/some-process-group/some-process-model') - permissions_to_assign_tuples = sorted([(p.target_uri, p.permission) for p in permissions_to_assign]) + permissions_to_assign = AuthorizationService.explode_permissions( + "start", "PG:/some-process-group/some-process-model" + ) + permissions_to_assign_tuples = sorted( + [(p.target_uri, p.permission) for p in permissions_to_assign] + ) assert permissions_to_assign_tuples == expected_permissions def test_explode_permissions_all_on_process_model( @@ -205,34 +243,63 @@ class TestAuthorizationService(BaseTest): client: FlaskClient, with_db_and_bpmn_file_cleanup: None, ) -> None: + """Test_explode_permissions_all_on_process_model.""" expected_permissions = [ - ('/logs/some-process-group/some-process-model/*', 'create'), - ('/logs/some-process-group/some-process-model/*', 'delete'), - ('/logs/some-process-group/some-process-model/*', 'read'), - ('/logs/some-process-group/some-process-model/*', 'update'), - ('/process-instance-suspend/some-process-group/some-process-model/*', 'create'), - ('/process-instance-suspend/some-process-group/some-process-model/*', 'delete'), - ('/process-instance-suspend/some-process-group/some-process-model/*', 'read'), - ('/process-instance-suspend/some-process-group/some-process-model/*', 'update'), - ('/process-instance-terminate/some-process-group/some-process-model/*', 'create'), - ('/process-instance-terminate/some-process-group/some-process-model/*', 'delete'), - ('/process-instance-terminate/some-process-group/some-process-model/*', 'read'), - ('/process-instance-terminate/some-process-group/some-process-model/*', 'update'), - ('/process-instances/some-process-group/some-process-model/*', 'create'), - ('/process-instances/some-process-group/some-process-model/*', 'delete'), - ('/process-instances/some-process-group/some-process-model/*', 'read'), - ('/process-instances/some-process-group/some-process-model/*', 'update'), - ('/process-models/some-process-group/some-process-model/*', 'create'), - ('/process-models/some-process-group/some-process-model/*', 'delete'), - ('/process-models/some-process-group/some-process-model/*', 'read'), - ('/process-models/some-process-group/some-process-model/*', 'update'), - ('/task-data/some-process-group/some-process-model/*', 'create'), - ('/task-data/some-process-group/some-process-model/*', 'delete'), - ('/task-data/some-process-group/some-process-model/*', 'read'), - ('/task-data/some-process-group/some-process-model/*', 'update'), + ("/logs/some-process-group/some-process-model/*", "create"), + ("/logs/some-process-group/some-process-model/*", "delete"), + ("/logs/some-process-group/some-process-model/*", "read"), + ("/logs/some-process-group/some-process-model/*", "update"), + ( + "/process-instance-suspend/some-process-group/some-process-model/*", + "create", + ), + ( + "/process-instance-suspend/some-process-group/some-process-model/*", + "delete", + ), + ( + "/process-instance-suspend/some-process-group/some-process-model/*", + "read", + ), + ( + "/process-instance-suspend/some-process-group/some-process-model/*", + "update", + ), + ( + "/process-instance-terminate/some-process-group/some-process-model/*", + "create", + ), + ( + "/process-instance-terminate/some-process-group/some-process-model/*", + "delete", + ), + ( + "/process-instance-terminate/some-process-group/some-process-model/*", + "read", + ), + ( + "/process-instance-terminate/some-process-group/some-process-model/*", + "update", + ), + ("/process-instances/some-process-group/some-process-model/*", "create"), + ("/process-instances/some-process-group/some-process-model/*", "delete"), + ("/process-instances/some-process-group/some-process-model/*", "read"), + ("/process-instances/some-process-group/some-process-model/*", "update"), + ("/process-models/some-process-group/some-process-model/*", "create"), + ("/process-models/some-process-group/some-process-model/*", "delete"), + ("/process-models/some-process-group/some-process-model/*", "read"), + ("/process-models/some-process-group/some-process-model/*", "update"), + ("/task-data/some-process-group/some-process-model/*", "create"), + ("/task-data/some-process-group/some-process-model/*", "delete"), + ("/task-data/some-process-group/some-process-model/*", "read"), + ("/task-data/some-process-group/some-process-model/*", "update"), ] - permissions_to_assign = AuthorizationService.explode_permissions('all', 'PM:/some-process-group/some-process-model') - permissions_to_assign_tuples = sorted([(p.target_uri, p.permission) for p in permissions_to_assign]) + permissions_to_assign = AuthorizationService.explode_permissions( + "all", "PM:/some-process-group/some-process-model" + ) + permissions_to_assign_tuples = sorted( + [(p.target_uri, p.permission) for p in permissions_to_assign] + ) assert permissions_to_assign_tuples == expected_permissions def test_explode_permissions_start_on_process_model( @@ -241,12 +308,20 @@ class TestAuthorizationService(BaseTest): client: FlaskClient, with_db_and_bpmn_file_cleanup: None, ) -> None: + """Test_explode_permissions_start_on_process_model.""" expected_permissions = [ - ('/process-instances/for-me/some-process-group/some-process-model/*', 'read'), - ('/process-instances/some-process-group/some-process-model/*', 'create'), + ( + "/process-instances/for-me/some-process-group/some-process-model/*", + "read", + ), + ("/process-instances/some-process-group/some-process-model/*", "create"), ] - permissions_to_assign = AuthorizationService.explode_permissions('start', 'PM:/some-process-group/some-process-model') - permissions_to_assign_tuples = sorted([(p.target_uri, p.permission) for p in permissions_to_assign]) + permissions_to_assign = AuthorizationService.explode_permissions( + "start", "PM:/some-process-group/some-process-model" + ) + permissions_to_assign_tuples = sorted( + [(p.target_uri, p.permission) for p in permissions_to_assign] + ) assert permissions_to_assign_tuples == expected_permissions def test_explode_permissions_basic( @@ -255,22 +330,25 @@ class TestAuthorizationService(BaseTest): client: FlaskClient, with_db_and_bpmn_file_cleanup: None, ) -> None: + """Test_explode_permissions_basic.""" expected_permissions = [ - ('/process-instances/for-me', 'read'), - ('/process-instances/reports/*', 'create'), - ('/process-instances/reports/*', 'delete'), - ('/process-instances/reports/*', 'read'), - ('/process-instances/reports/*', 'update'), - ('/processes', 'read'), - ('/service-tasks', 'read'), - ('/tasks/*', 'create'), - ('/tasks/*', 'delete'), - ('/tasks/*', 'read'), - ('/tasks/*', 'update'), - ('/user-groups/for-current-user', 'read'), + ("/process-instances/for-me", "read"), + ("/process-instances/reports/*", "create"), + ("/process-instances/reports/*", "delete"), + ("/process-instances/reports/*", "read"), + ("/process-instances/reports/*", "update"), + ("/processes", "read"), + ("/service-tasks", "read"), + ("/tasks/*", "create"), + ("/tasks/*", "delete"), + ("/tasks/*", "read"), + ("/tasks/*", "update"), + ("/user-groups/for-current-user", "read"), ] - permissions_to_assign = AuthorizationService.explode_permissions('all', 'BASIC') - permissions_to_assign_tuples = sorted([(p.target_uri, p.permission) for p in permissions_to_assign]) + permissions_to_assign = AuthorizationService.explode_permissions("all", "BASIC") + permissions_to_assign_tuples = sorted( + [(p.target_uri, p.permission) for p in permissions_to_assign] + ) assert permissions_to_assign_tuples == expected_permissions def test_explode_permissions_all( @@ -279,14 +357,17 @@ class TestAuthorizationService(BaseTest): client: FlaskClient, with_db_and_bpmn_file_cleanup: None, ) -> None: + """Test_explode_permissions_all.""" expected_permissions = [ - ('/*', 'create'), - ('/*', 'delete'), - ('/*', 'read'), - ('/*', 'update'), + ("/*", "create"), + ("/*", "delete"), + ("/*", "read"), + ("/*", "update"), ] - permissions_to_assign = AuthorizationService.explode_permissions('all', 'ALL') - permissions_to_assign_tuples = sorted([(p.target_uri, p.permission) for p in permissions_to_assign]) + permissions_to_assign = AuthorizationService.explode_permissions("all", "ALL") + permissions_to_assign_tuples = sorted( + [(p.target_uri, p.permission) for p in permissions_to_assign] + ) assert permissions_to_assign_tuples == expected_permissions def test_explode_permissions_with_target_uri( @@ -295,14 +376,19 @@ class TestAuthorizationService(BaseTest): client: FlaskClient, with_db_and_bpmn_file_cleanup: None, ) -> None: + """Test_explode_permissions_with_target_uri.""" expected_permissions = [ - ('/hey/model', 'create'), - ('/hey/model', 'delete'), - ('/hey/model', 'read'), - ('/hey/model', 'update'), + ("/hey/model", "create"), + ("/hey/model", "delete"), + ("/hey/model", "read"), + ("/hey/model", "update"), ] - permissions_to_assign = AuthorizationService.explode_permissions('all', '/hey/model') - permissions_to_assign_tuples = sorted([(p.target_uri, p.permission) for p in permissions_to_assign]) + permissions_to_assign = AuthorizationService.explode_permissions( + "all", "/hey/model" + ) + permissions_to_assign_tuples = sorted( + [(p.target_uri, p.permission) for p in permissions_to_assign] + ) assert permissions_to_assign_tuples == expected_permissions def test_explode_permissions_with_invalid_target_uri( @@ -311,8 +397,9 @@ class TestAuthorizationService(BaseTest): client: FlaskClient, with_db_and_bpmn_file_cleanup: None, ) -> None: + """Test_explode_permissions_with_invalid_target_uri.""" with pytest.raises(InvalidPermissionError): - AuthorizationService.explode_permissions('all', 'BAD_MACRO') + AuthorizationService.explode_permissions("all", "BAD_MACRO") def test_explode_permissions_with_start_to_incorrect_target( self, @@ -320,5 +407,6 @@ class TestAuthorizationService(BaseTest): client: FlaskClient, with_db_and_bpmn_file_cleanup: None, ) -> None: + """Test_explode_permissions_with_start_to_incorrect_target.""" with pytest.raises(InvalidPermissionError): - AuthorizationService.explode_permissions('start', '/hey/model') + AuthorizationService.explode_permissions("start", "/hey/model") From ed2a744502bead4fd72e1913c65dccb64561a9b2 Mon Sep 17 00:00:00 2001 From: jasquat Date: Thu, 22 Dec 2022 10:34:19 -0500 Subject: [PATCH 22/29] fixed slashes to colons in permission macros w/ burnettk --- .../config/permissions/development.yml | 190 +++++++++--------- .../config/permissions/testing.yml | 2 +- .../models/permission_assignment.py | 8 - .../services/authorization_service.py | 8 +- .../unit/test_authorization_service.py | 112 +++++------ 5 files changed, 156 insertions(+), 164 deletions(-) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml index d192a7dea..f5052ff65 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml @@ -74,98 +74,98 @@ permissions: users: [] allowed_permissions: [create, read, update, delete] uri: /* - admin-readonly: - groups: [admin-ro] - users: [] - allowed_permissions: [read] - uri: /* - admin-process-instances-for-readonly: - groups: [admin-ro] - users: [] - allowed_permissions: [create, read, update, delete] - uri: /process-instances/* - - tasks-crud: - groups: [everybody] - users: [] - allowed_permissions: [create, read, update, delete] - uri: /tasks/* - service-tasks: - groups: [everybody] - users: [] - allowed_permissions: [read] - uri: /service-tasks - user-groups-for-current-user: - groups: [everybody] - users: [] - allowed_permissions: [read] - uri: /user-groups/for-current-user - - # read all for everybody - read-all-process-groups: - groups: [everybody] - users: [] - allowed_permissions: [read] - uri: /process-groups/* - read-all-process-models: - groups: [everybody] - users: [] - allowed_permissions: [read] - uri: /process-models/* - read-all-process-instances-for-me: - groups: [everybody] - users: [] - allowed_permissions: [read] - uri: /process-instances/for-me/* - read-process-instance-reports: - groups: [everybody] - users: [] - allowed_permissions: [create, read, update, delete] - uri: /process-instances/reports/* - processes-read: - groups: [everybody] - users: [] - allowed_permissions: [read] - uri: /processes - - - finance-admin: - groups: ["Finance Team"] - users: [] - allowed_permissions: [create, read, update, delete] - uri: /process-groups/manage-procurement:procurement:* - - manage-revenue-streams-instances: - groups: ["core-contributor", "demo"] - users: [] - allowed_permissions: [create, read] - 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: /process-instances/manage-procurement:procurement:core-contributor-invoice-management:* - - manage-procurement-instances: - groups: ["core-contributor", "demo"] - users: [] - allowed_permissions: [create, read] - uri: /process-instances/manage-procurement:vendor-lifecycle-management:* - - create-test-instances: - groups: ["test"] - users: [] - allowed_permissions: [create, read] - uri: /process-instances/misc:test:* - - core1-admin-instances: - groups: ["core-contributor", "Finance Team"] - users: [] - allowed_permissions: [create, read] - 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: /process-instances/misc:category_number_one:process-model-with-form/* + # admin-readonly: + # groups: [admin-ro] + # users: [] + # allowed_permissions: [read] + # uri: /* + # admin-process-instances-for-readonly: + # groups: [admin-ro] + # users: [] + # allowed_permissions: [create, read, update, delete] + # uri: /process-instances/* + # + # tasks-crud: + # groups: [everybody] + # users: [] + # allowed_permissions: [create, read, update, delete] + # uri: /tasks/* + # service-tasks: + # groups: [everybody] + # users: [] + # allowed_permissions: [read] + # uri: /service-tasks + # user-groups-for-current-user: + # groups: [everybody] + # users: [] + # allowed_permissions: [read] + # uri: /user-groups/for-current-user + # + # # read all for everybody + # read-all-process-groups: + # groups: [everybody] + # users: [] + # allowed_permissions: [read] + # uri: /process-groups/* + # read-all-process-models: + # groups: [everybody] + # users: [] + # allowed_permissions: [read] + # uri: /process-models/* + # read-all-process-instances-for-me: + # groups: [everybody] + # users: [] + # allowed_permissions: [read] + # uri: /process-instances/for-me/* + # read-process-instance-reports: + # groups: [everybody] + # users: [] + # allowed_permissions: [create, read, update, delete] + # uri: /process-instances/reports/* + # processes-read: + # groups: [everybody] + # users: [] + # allowed_permissions: [read] + # uri: /processes + # + # + # finance-admin: + # groups: ["Finance Team"] + # users: [] + # allowed_permissions: [create, read, update, delete] + # uri: /process-groups/manage-procurement:procurement:* + # + # manage-revenue-streams-instances: + # groups: ["core-contributor", "demo"] + # users: [] + # allowed_permissions: [create, read] + # 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: /process-instances/manage-procurement:procurement:core-contributor-invoice-management:* + # + # manage-procurement-instances: + # groups: ["core-contributor", "demo"] + # users: [] + # allowed_permissions: [create, read] + # uri: /process-instances/manage-procurement:vendor-lifecycle-management:* + # + # create-test-instances: + # groups: ["test"] + # users: [] + # allowed_permissions: [create, read] + # uri: /process-instances/misc:test:* + # + # core1-admin-instances: + # groups: ["core-contributor", "Finance Team"] + # users: [] + # allowed_permissions: [create, read] + # 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: /process-instances/misc:category_number_one:process-model-with-form/* diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/testing.yml b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/testing.yml index 31724599f..79a137104 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/testing.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/testing.yml @@ -21,7 +21,7 @@ permissions: admin: groups: [admin] users: [] - allowed_permissions: [create, read, update, delete, list, instantiate] + allowed_permissions: [create, read, update, delete] uri: /* read-all: diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/permission_assignment.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/permission_assignment.py index 63295f74e..04dfb5fac 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/permission_assignment.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/permission_assignment.py @@ -32,14 +32,6 @@ class Permission(enum.Enum): update = "update" delete = "delete" - # maybe read to GET process_model/process-instances instead? - list = "list" - - # maybe use create instead on - # POST http://localhost:7000/v1.0/process-models/category_number_one/call-activity/process-instances/* - # POST http://localhost:7000/v1.0/process-models/category_number_one/call-activity/process-instances/332/run - instantiate = "instantiate" # this is something you do to a process model - class PermissionAssignmentModel(SpiffworkflowBaseDBModel): """PermissionAssignmentModel.""" diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py index 94c07d4c1..4ebba797d 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py @@ -112,7 +112,7 @@ class AuthorizationService: # to check for exact matches as well # see test_user_can_access_base_path_when_given_wildcard_permission unit test text( - f"'{target_uri_normalized}' = replace(permission_target.uri, '/%', '')" + f"'{target_uri_normalized}' = replace(replace(permission_target.uri, '/%', ''), ':%', '')" ), ) ) @@ -605,9 +605,9 @@ class AuthorizationService: if target.startswith("PG:"): process_group_identifier = ( - target.removeprefix("PG:").replace(":", "/").removeprefix("/") + target.removeprefix("PG:").replace("/", ":").removeprefix(":") ) - process_related_path_segment = f"{process_group_identifier}/*" + process_related_path_segment = f"{process_group_identifier}:*" if process_group_identifier == "ALL": process_related_path_segment = "*" target_uris = [ @@ -623,7 +623,7 @@ class AuthorizationService: elif target.startswith("PM:"): process_model_identifier = ( - target.removeprefix("PM:").replace(":", "/").removeprefix("/") + target.removeprefix("PM:").replace("/", ":").removeprefix(":") ) process_related_path_segment = f"{process_model_identifier}/*" diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py index 300b99ade..d03f2637b 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py @@ -154,58 +154,58 @@ class TestAuthorizationService(BaseTest): ) -> None: """Test_explode_permissions_all_on_process_group.""" expected_permissions = [ - ("/logs/some-process-group/some-process-model/*", "create"), - ("/logs/some-process-group/some-process-model/*", "delete"), - ("/logs/some-process-group/some-process-model/*", "read"), - ("/logs/some-process-group/some-process-model/*", "update"), - ("/process-groups/some-process-group/some-process-model/*", "create"), - ("/process-groups/some-process-group/some-process-model/*", "delete"), - ("/process-groups/some-process-group/some-process-model/*", "read"), - ("/process-groups/some-process-group/some-process-model/*", "update"), + ("/logs/some-process-group:some-process-model:*", "create"), + ("/logs/some-process-group:some-process-model:*", "delete"), + ("/logs/some-process-group:some-process-model:*", "read"), + ("/logs/some-process-group:some-process-model:*", "update"), + ("/process-groups/some-process-group:some-process-model:*", "create"), + ("/process-groups/some-process-group:some-process-model:*", "delete"), + ("/process-groups/some-process-group:some-process-model:*", "read"), + ("/process-groups/some-process-group:some-process-model:*", "update"), ( - "/process-instance-suspend/some-process-group/some-process-model/*", + "/process-instance-suspend/some-process-group:some-process-model:*", "create", ), ( - "/process-instance-suspend/some-process-group/some-process-model/*", + "/process-instance-suspend/some-process-group:some-process-model:*", "delete", ), ( - "/process-instance-suspend/some-process-group/some-process-model/*", + "/process-instance-suspend/some-process-group:some-process-model:*", "read", ), ( - "/process-instance-suspend/some-process-group/some-process-model/*", + "/process-instance-suspend/some-process-group:some-process-model:*", "update", ), ( - "/process-instance-terminate/some-process-group/some-process-model/*", + "/process-instance-terminate/some-process-group:some-process-model:*", "create", ), ( - "/process-instance-terminate/some-process-group/some-process-model/*", + "/process-instance-terminate/some-process-group:some-process-model:*", "delete", ), ( - "/process-instance-terminate/some-process-group/some-process-model/*", + "/process-instance-terminate/some-process-group:some-process-model:*", "read", ), ( - "/process-instance-terminate/some-process-group/some-process-model/*", + "/process-instance-terminate/some-process-group:some-process-model:*", "update", ), - ("/process-instances/some-process-group/some-process-model/*", "create"), - ("/process-instances/some-process-group/some-process-model/*", "delete"), - ("/process-instances/some-process-group/some-process-model/*", "read"), - ("/process-instances/some-process-group/some-process-model/*", "update"), - ("/process-models/some-process-group/some-process-model/*", "create"), - ("/process-models/some-process-group/some-process-model/*", "delete"), - ("/process-models/some-process-group/some-process-model/*", "read"), - ("/process-models/some-process-group/some-process-model/*", "update"), - ("/task-data/some-process-group/some-process-model/*", "create"), - ("/task-data/some-process-group/some-process-model/*", "delete"), - ("/task-data/some-process-group/some-process-model/*", "read"), - ("/task-data/some-process-group/some-process-model/*", "update"), + ("/process-instances/some-process-group:some-process-model:*", "create"), + ("/process-instances/some-process-group:some-process-model:*", "delete"), + ("/process-instances/some-process-group:some-process-model:*", "read"), + ("/process-instances/some-process-group:some-process-model:*", "update"), + ("/process-models/some-process-group:some-process-model:*", "create"), + ("/process-models/some-process-group:some-process-model:*", "delete"), + ("/process-models/some-process-group:some-process-model:*", "read"), + ("/process-models/some-process-group:some-process-model:*", "update"), + ("/task-data/some-process-group:some-process-model:*", "create"), + ("/task-data/some-process-group:some-process-model:*", "delete"), + ("/task-data/some-process-group:some-process-model:*", "read"), + ("/task-data/some-process-group:some-process-model:*", "update"), ] permissions_to_assign = AuthorizationService.explode_permissions( "all", "PG:/some-process-group/some-process-model" @@ -224,10 +224,10 @@ class TestAuthorizationService(BaseTest): """Test_explode_permissions_start_on_process_group.""" expected_permissions = [ ( - "/process-instances/for-me/some-process-group/some-process-model/*", + "/process-instances/for-me/some-process-group:some-process-model:*", "read", ), - ("/process-instances/some-process-group/some-process-model/*", "create"), + ("/process-instances/some-process-group:some-process-model:*", "create"), ] permissions_to_assign = AuthorizationService.explode_permissions( "start", "PG:/some-process-group/some-process-model" @@ -245,54 +245,54 @@ class TestAuthorizationService(BaseTest): ) -> None: """Test_explode_permissions_all_on_process_model.""" expected_permissions = [ - ("/logs/some-process-group/some-process-model/*", "create"), - ("/logs/some-process-group/some-process-model/*", "delete"), - ("/logs/some-process-group/some-process-model/*", "read"), - ("/logs/some-process-group/some-process-model/*", "update"), + ("/logs/some-process-group:some-process-model/*", "create"), + ("/logs/some-process-group:some-process-model/*", "delete"), + ("/logs/some-process-group:some-process-model/*", "read"), + ("/logs/some-process-group:some-process-model/*", "update"), ( - "/process-instance-suspend/some-process-group/some-process-model/*", + "/process-instance-suspend/some-process-group:some-process-model/*", "create", ), ( - "/process-instance-suspend/some-process-group/some-process-model/*", + "/process-instance-suspend/some-process-group:some-process-model/*", "delete", ), ( - "/process-instance-suspend/some-process-group/some-process-model/*", + "/process-instance-suspend/some-process-group:some-process-model/*", "read", ), ( - "/process-instance-suspend/some-process-group/some-process-model/*", + "/process-instance-suspend/some-process-group:some-process-model/*", "update", ), ( - "/process-instance-terminate/some-process-group/some-process-model/*", + "/process-instance-terminate/some-process-group:some-process-model/*", "create", ), ( - "/process-instance-terminate/some-process-group/some-process-model/*", + "/process-instance-terminate/some-process-group:some-process-model/*", "delete", ), ( - "/process-instance-terminate/some-process-group/some-process-model/*", + "/process-instance-terminate/some-process-group:some-process-model/*", "read", ), ( - "/process-instance-terminate/some-process-group/some-process-model/*", + "/process-instance-terminate/some-process-group:some-process-model/*", "update", ), - ("/process-instances/some-process-group/some-process-model/*", "create"), - ("/process-instances/some-process-group/some-process-model/*", "delete"), - ("/process-instances/some-process-group/some-process-model/*", "read"), - ("/process-instances/some-process-group/some-process-model/*", "update"), - ("/process-models/some-process-group/some-process-model/*", "create"), - ("/process-models/some-process-group/some-process-model/*", "delete"), - ("/process-models/some-process-group/some-process-model/*", "read"), - ("/process-models/some-process-group/some-process-model/*", "update"), - ("/task-data/some-process-group/some-process-model/*", "create"), - ("/task-data/some-process-group/some-process-model/*", "delete"), - ("/task-data/some-process-group/some-process-model/*", "read"), - ("/task-data/some-process-group/some-process-model/*", "update"), + ("/process-instances/some-process-group:some-process-model/*", "create"), + ("/process-instances/some-process-group:some-process-model/*", "delete"), + ("/process-instances/some-process-group:some-process-model/*", "read"), + ("/process-instances/some-process-group:some-process-model/*", "update"), + ("/process-models/some-process-group:some-process-model/*", "create"), + ("/process-models/some-process-group:some-process-model/*", "delete"), + ("/process-models/some-process-group:some-process-model/*", "read"), + ("/process-models/some-process-group:some-process-model/*", "update"), + ("/task-data/some-process-group:some-process-model/*", "create"), + ("/task-data/some-process-group:some-process-model/*", "delete"), + ("/task-data/some-process-group:some-process-model/*", "read"), + ("/task-data/some-process-group:some-process-model/*", "update"), ] permissions_to_assign = AuthorizationService.explode_permissions( "all", "PM:/some-process-group/some-process-model" @@ -311,10 +311,10 @@ class TestAuthorizationService(BaseTest): """Test_explode_permissions_start_on_process_model.""" expected_permissions = [ ( - "/process-instances/for-me/some-process-group/some-process-model/*", + "/process-instances/for-me/some-process-group:some-process-model/*", "read", ), - ("/process-instances/some-process-group/some-process-model/*", "create"), + ("/process-instances/some-process-group:some-process-model/*", "create"), ] permissions_to_assign = AuthorizationService.explode_permissions( "start", "PM:/some-process-group/some-process-model" From 5522100bfcf124a4734a4343d408ae5a26685361 Mon Sep 17 00:00:00 2001 From: jasquat Date: Thu, 22 Dec 2022 10:46:40 -0500 Subject: [PATCH 23/29] added test to for permission to process group w/ burnettk --- .../unit/test_authorization_service.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py index d03f2637b..413e26015 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py @@ -1,5 +1,7 @@ """Test_message_service.""" import pytest +from spiffworkflow_backend.services.group_service import GroupService +from spiffworkflow_backend.services.user_service import UserService from flask import Flask from flask.testing import FlaskClient from tests.spiffworkflow_backend.helpers.base_test import BaseTest @@ -391,6 +393,24 @@ class TestAuthorizationService(BaseTest): ) assert permissions_to_assign_tuples == expected_permissions + def test_granting_access_to_group_gives_access_to_group_and_subgroups( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + ) -> None: + """Test_granting_access_to_group_gives_access_to_group_and_subgroups.""" + user = self.find_or_create_user(username='user_one') + user_group = GroupService.find_or_create_group('group_one') + UserService.add_user_to_group(user, user_group) + AuthorizationService.add_permission_from_uri_or_macro(user_group.identifier, "read", "PG:hey") + self.assert_user_has_permission( + user, "read", "/v1.0/process-groups/hey" + ) + self.assert_user_has_permission( + user, "read", "/v1.0/process-groups/hey:yo" + ) + def test_explode_permissions_with_invalid_target_uri( self, app: Flask, From a855df858b296e18f786c6ed8dd994194479a507 Mon Sep 17 00:00:00 2001 From: jasquat Date: Thu, 22 Dec 2022 12:20:34 -0500 Subject: [PATCH 24/29] added script to get all permissions for the confirmation page when adding permissions from a process model w/ burnettk --- .../config/permissions/development.yml | 190 +++++++++--------- .../scripts/add_permission.py | 6 +- .../scripts/get_all_permissions.py | 52 +++++ .../scripts/test_get_all_permissions.py | 55 +++++ 4 files changed, 204 insertions(+), 99 deletions(-) create mode 100644 spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_all_permissions.py create mode 100644 spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_get_all_permissions.py diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml index f5052ff65..d192a7dea 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml @@ -74,98 +74,98 @@ permissions: users: [] allowed_permissions: [create, read, update, delete] uri: /* - # admin-readonly: - # groups: [admin-ro] - # users: [] - # allowed_permissions: [read] - # uri: /* - # admin-process-instances-for-readonly: - # groups: [admin-ro] - # users: [] - # allowed_permissions: [create, read, update, delete] - # uri: /process-instances/* - # - # tasks-crud: - # groups: [everybody] - # users: [] - # allowed_permissions: [create, read, update, delete] - # uri: /tasks/* - # service-tasks: - # groups: [everybody] - # users: [] - # allowed_permissions: [read] - # uri: /service-tasks - # user-groups-for-current-user: - # groups: [everybody] - # users: [] - # allowed_permissions: [read] - # uri: /user-groups/for-current-user - # - # # read all for everybody - # read-all-process-groups: - # groups: [everybody] - # users: [] - # allowed_permissions: [read] - # uri: /process-groups/* - # read-all-process-models: - # groups: [everybody] - # users: [] - # allowed_permissions: [read] - # uri: /process-models/* - # read-all-process-instances-for-me: - # groups: [everybody] - # users: [] - # allowed_permissions: [read] - # uri: /process-instances/for-me/* - # read-process-instance-reports: - # groups: [everybody] - # users: [] - # allowed_permissions: [create, read, update, delete] - # uri: /process-instances/reports/* - # processes-read: - # groups: [everybody] - # users: [] - # allowed_permissions: [read] - # uri: /processes - # - # - # finance-admin: - # groups: ["Finance Team"] - # users: [] - # allowed_permissions: [create, read, update, delete] - # uri: /process-groups/manage-procurement:procurement:* - # - # manage-revenue-streams-instances: - # groups: ["core-contributor", "demo"] - # users: [] - # allowed_permissions: [create, read] - # 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: /process-instances/manage-procurement:procurement:core-contributor-invoice-management:* - # - # manage-procurement-instances: - # groups: ["core-contributor", "demo"] - # users: [] - # allowed_permissions: [create, read] - # uri: /process-instances/manage-procurement:vendor-lifecycle-management:* - # - # create-test-instances: - # groups: ["test"] - # users: [] - # allowed_permissions: [create, read] - # uri: /process-instances/misc:test:* - # - # core1-admin-instances: - # groups: ["core-contributor", "Finance Team"] - # users: [] - # allowed_permissions: [create, read] - # 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: /process-instances/misc:category_number_one:process-model-with-form/* + admin-readonly: + groups: [admin-ro] + users: [] + allowed_permissions: [read] + uri: /* + admin-process-instances-for-readonly: + groups: [admin-ro] + users: [] + allowed_permissions: [create, read, update, delete] + uri: /process-instances/* + + tasks-crud: + groups: [everybody] + users: [] + allowed_permissions: [create, read, update, delete] + uri: /tasks/* + service-tasks: + groups: [everybody] + users: [] + allowed_permissions: [read] + uri: /service-tasks + user-groups-for-current-user: + groups: [everybody] + users: [] + allowed_permissions: [read] + uri: /user-groups/for-current-user + + # read all for everybody + read-all-process-groups: + groups: [everybody] + users: [] + allowed_permissions: [read] + uri: /process-groups/* + read-all-process-models: + groups: [everybody] + users: [] + allowed_permissions: [read] + uri: /process-models/* + read-all-process-instances-for-me: + groups: [everybody] + users: [] + allowed_permissions: [read] + uri: /process-instances/for-me/* + read-process-instance-reports: + groups: [everybody] + users: [] + allowed_permissions: [create, read, update, delete] + uri: /process-instances/reports/* + processes-read: + groups: [everybody] + users: [] + allowed_permissions: [read] + uri: /processes + + + finance-admin: + groups: ["Finance Team"] + users: [] + allowed_permissions: [create, read, update, delete] + uri: /process-groups/manage-procurement:procurement:* + + manage-revenue-streams-instances: + groups: ["core-contributor", "demo"] + users: [] + allowed_permissions: [create, read] + 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: /process-instances/manage-procurement:procurement:core-contributor-invoice-management:* + + manage-procurement-instances: + groups: ["core-contributor", "demo"] + users: [] + allowed_permissions: [create, read] + uri: /process-instances/manage-procurement:vendor-lifecycle-management:* + + create-test-instances: + groups: ["test"] + users: [] + allowed_permissions: [create, read] + uri: /process-instances/misc:test:* + + core1-admin-instances: + groups: ["core-contributor", "Finance Team"] + users: [] + allowed_permissions: [create, read] + 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: /process-instances/misc:category_number_one:process-model-with-form/* diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_permission.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_permission.py index 806fd991f..ce365fe95 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_permission.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_permission.py @@ -28,8 +28,6 @@ class AddPermission(Script): allowed_permission = args[0] uri = args[1] group_identifier = args[2] - group = GroupService.find_or_create_group(group_identifier) - target = AuthorizationService.find_or_create_permission_target(uri) - AuthorizationService.create_permission_for_principal( - group.principal, target, allowed_permission + AuthorizationService.add_permission_from_uri_or_macro( + group_identifier=group_identifier, target=uri, permission=allowed_permission ) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_all_permissions.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_all_permissions.py new file mode 100644 index 000000000..5a7d87f9b --- /dev/null +++ b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_all_permissions.py @@ -0,0 +1,52 @@ +"""Get_env.""" +from typing import Any, Set +from typing import Union +from spiffworkflow_backend.models.group import GroupModel +from spiffworkflow_backend.models.permission_target import PermissionTargetModel +from spiffworkflow_backend.models.principal import PrincipalModel +from spiffworkflow_backend.models.permission_assignment import PermissionAssignmentModel + +from spiffworkflow_backend.models.script_attributes_context import ( + ScriptAttributesContext, +) +from spiffworkflow_backend.scripts.script import Script +from spiffworkflow_backend.services.authorization_service import AuthorizationService +from spiffworkflow_backend.services.group_service import GroupService + +from collections import OrderedDict + + +# add_permission("read", "test/*", "Editors") + + +class GetAllPermissions(Script): + + def get_description(self) -> str: + """Get_description.""" + return """Get all permissions currently in the system.""" + + def run( + self, + script_attributes_context: ScriptAttributesContext, + *args: Any, + **kwargs: Any, + ) -> Any: + """Run.""" + permission_assignments = ( + PermissionAssignmentModel.query + .join(PrincipalModel, PrincipalModel.id == PermissionAssignmentModel.principal_id) + .join(GroupModel, GroupModel.id == PrincipalModel.group_id) + .join(PermissionTargetModel, PermissionTargetModel.id == PermissionAssignmentModel.permission_target_id) + .add_columns( + PermissionAssignmentModel.permission, + PermissionTargetModel.uri, + GroupModel.identifier.label('group_identifier') + ) + ) + + permissions: OrderedDict[tuple[str, str], list[str]] = OrderedDict() + for pa in permission_assignments: + permissions.setdefault((pa.group_identifier, pa.uri), []).append(pa.permission) + + return [{'group_identifier': k[0], 'uri': k[1], 'permissions': sorted(v)} + for k, v in permissions.items()] diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_get_all_permissions.py b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_get_all_permissions.py new file mode 100644 index 000000000..d6a5a178c --- /dev/null +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_get_all_permissions.py @@ -0,0 +1,55 @@ +"""Test_get_localtime.""" +import pytest +from flask.app import Flask +from flask.testing import FlaskClient +from flask_bpmn.api.api_error import ApiError +from spiffworkflow_backend.scripts.get_all_permissions import GetAllPermissions +from tests.spiffworkflow_backend.helpers.base_test import BaseTest +from tests.spiffworkflow_backend.helpers.test_data import load_test_spec + +from spiffworkflow_backend.models.group import GroupModel +from spiffworkflow_backend.models.permission_assignment import PermissionAssignmentModel +from spiffworkflow_backend.models.permission_target import PermissionTargetModel +from spiffworkflow_backend.models.script_attributes_context import ( + ScriptAttributesContext, +) +from spiffworkflow_backend.models.user import UserModel +from spiffworkflow_backend.scripts.add_permission import AddPermission +from spiffworkflow_backend.services.process_instance_processor import ( + ProcessInstanceProcessor, +) + + +class TestGetAllPermissions(BaseTest): + + def test_can_get_all_permissions( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + with_super_admin_user: UserModel, + ) -> None: + self.find_or_create_user("test_user") + + # now that we have everything, try to clear it out... + script_attributes_context = ScriptAttributesContext( + task=None, + environment_identifier="testing", + process_instance_id=1, + process_model_identifier="my_test_user", + ) + AddPermission().run( + script_attributes_context, "start", "PG:hey:group", "my_test_group" + ) + AddPermission().run( + script_attributes_context, "all", "/tasks", "my_test_group" + ) + + expected_permissions = [ + {'group_identifier': 'my_test_group', 'uri': '/process-instances/hey:group:%', 'permissions': ['create']}, + {'group_identifier': 'my_test_group', 'uri': '/process-instances/for-me/hey:group:%', 'permissions': ['read']}, + {'group_identifier': 'my_test_group', 'uri': '/tasks', 'permissions': ['create', 'delete', 'read', 'update']} + ] + + permissions = GetAllPermissions().run(script_attributes_context) + assert permissions == expected_permissions From ff61026ff53e0ecbd95ab875e8b29676a85962ad Mon Sep 17 00:00:00 2001 From: jasquat Date: Thu, 22 Dec 2022 12:32:26 -0500 Subject: [PATCH 25/29] pyl w/ burnettk --- .../scripts/add_permission.py | 1 - .../scripts/get_all_permissions.py | 37 +++++++++++-------- .../scripts/test_get_all_permissions.py | 35 ++++++++++-------- .../unit/test_authorization_service.py | 18 ++++----- 4 files changed, 49 insertions(+), 42 deletions(-) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_permission.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_permission.py index ce365fe95..113a92dab 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_permission.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_permission.py @@ -6,7 +6,6 @@ from spiffworkflow_backend.models.script_attributes_context import ( ) from spiffworkflow_backend.scripts.script import Script from spiffworkflow_backend.services.authorization_service import AuthorizationService -from spiffworkflow_backend.services.group_service import GroupService # add_permission("read", "test/*", "Editors") diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_all_permissions.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_all_permissions.py index 5a7d87f9b..83a7e582f 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_all_permissions.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_all_permissions.py @@ -1,25 +1,22 @@ """Get_env.""" -from typing import Any, Set -from typing import Union +from collections import OrderedDict +from typing import Any + from spiffworkflow_backend.models.group import GroupModel +from spiffworkflow_backend.models.permission_assignment import PermissionAssignmentModel from spiffworkflow_backend.models.permission_target import PermissionTargetModel from spiffworkflow_backend.models.principal import PrincipalModel -from spiffworkflow_backend.models.permission_assignment import PermissionAssignmentModel - from spiffworkflow_backend.models.script_attributes_context import ( ScriptAttributesContext, ) from spiffworkflow_backend.scripts.script import Script -from spiffworkflow_backend.services.authorization_service import AuthorizationService -from spiffworkflow_backend.services.group_service import GroupService - -from collections import OrderedDict # add_permission("read", "test/*", "Editors") class GetAllPermissions(Script): + """GetAllPermissions.""" def get_description(self) -> str: """Get_description.""" @@ -33,20 +30,30 @@ class GetAllPermissions(Script): ) -> Any: """Run.""" permission_assignments = ( - PermissionAssignmentModel.query - .join(PrincipalModel, PrincipalModel.id == PermissionAssignmentModel.principal_id) + PermissionAssignmentModel.query.join( + PrincipalModel, + PrincipalModel.id == PermissionAssignmentModel.principal_id, + ) .join(GroupModel, GroupModel.id == PrincipalModel.group_id) - .join(PermissionTargetModel, PermissionTargetModel.id == PermissionAssignmentModel.permission_target_id) + .join( + PermissionTargetModel, + PermissionTargetModel.id + == PermissionAssignmentModel.permission_target_id, + ) .add_columns( PermissionAssignmentModel.permission, PermissionTargetModel.uri, - GroupModel.identifier.label('group_identifier') + GroupModel.identifier.label("group_identifier"), ) ) permissions: OrderedDict[tuple[str, str], list[str]] = OrderedDict() for pa in permission_assignments: - permissions.setdefault((pa.group_identifier, pa.uri), []).append(pa.permission) + permissions.setdefault((pa.group_identifier, pa.uri), []).append( + pa.permission + ) - return [{'group_identifier': k[0], 'uri': k[1], 'permissions': sorted(v)} - for k, v in permissions.items()] + return [ + {"group_identifier": k[0], "uri": k[1], "permissions": sorted(v)} + for k, v in permissions.items() + ] diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_get_all_permissions.py b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_get_all_permissions.py index d6a5a178c..9f1594999 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_get_all_permissions.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_get_all_permissions.py @@ -1,26 +1,18 @@ """Test_get_localtime.""" -import pytest from flask.app import Flask from flask.testing import FlaskClient -from flask_bpmn.api.api_error import ApiError -from spiffworkflow_backend.scripts.get_all_permissions import GetAllPermissions from tests.spiffworkflow_backend.helpers.base_test import BaseTest -from tests.spiffworkflow_backend.helpers.test_data import load_test_spec -from spiffworkflow_backend.models.group import GroupModel -from spiffworkflow_backend.models.permission_assignment import PermissionAssignmentModel -from spiffworkflow_backend.models.permission_target import PermissionTargetModel from spiffworkflow_backend.models.script_attributes_context import ( ScriptAttributesContext, ) from spiffworkflow_backend.models.user import UserModel from spiffworkflow_backend.scripts.add_permission import AddPermission -from spiffworkflow_backend.services.process_instance_processor import ( - ProcessInstanceProcessor, -) +from spiffworkflow_backend.scripts.get_all_permissions import GetAllPermissions class TestGetAllPermissions(BaseTest): + """TestGetAllPermissions.""" def test_can_get_all_permissions( self, @@ -29,6 +21,7 @@ class TestGetAllPermissions(BaseTest): with_db_and_bpmn_file_cleanup: None, with_super_admin_user: UserModel, ) -> None: + """Test_can_get_all_permissions.""" self.find_or_create_user("test_user") # now that we have everything, try to clear it out... @@ -41,14 +34,24 @@ class TestGetAllPermissions(BaseTest): AddPermission().run( script_attributes_context, "start", "PG:hey:group", "my_test_group" ) - AddPermission().run( - script_attributes_context, "all", "/tasks", "my_test_group" - ) + AddPermission().run(script_attributes_context, "all", "/tasks", "my_test_group") expected_permissions = [ - {'group_identifier': 'my_test_group', 'uri': '/process-instances/hey:group:%', 'permissions': ['create']}, - {'group_identifier': 'my_test_group', 'uri': '/process-instances/for-me/hey:group:%', 'permissions': ['read']}, - {'group_identifier': 'my_test_group', 'uri': '/tasks', 'permissions': ['create', 'delete', 'read', 'update']} + { + "group_identifier": "my_test_group", + "uri": "/process-instances/hey:group:%", + "permissions": ["create"], + }, + { + "group_identifier": "my_test_group", + "uri": "/process-instances/for-me/hey:group:%", + "permissions": ["read"], + }, + { + "group_identifier": "my_test_group", + "uri": "/tasks", + "permissions": ["create", "delete", "read", "update"], + }, ] permissions = GetAllPermissions().run(script_attributes_context) diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py index 413e26015..b149ac540 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py @@ -1,7 +1,5 @@ """Test_message_service.""" import pytest -from spiffworkflow_backend.services.group_service import GroupService -from spiffworkflow_backend.services.user_service import UserService from flask import Flask from flask.testing import FlaskClient from tests.spiffworkflow_backend.helpers.base_test import BaseTest @@ -10,6 +8,7 @@ from spiffworkflow_backend.models.user import UserModel from spiffworkflow_backend.models.user import UserNotFoundError from spiffworkflow_backend.services.authorization_service import AuthorizationService from spiffworkflow_backend.services.authorization_service import InvalidPermissionError +from spiffworkflow_backend.services.group_service import GroupService from spiffworkflow_backend.services.process_instance_processor import ( ProcessInstanceProcessor, ) @@ -17,6 +16,7 @@ from spiffworkflow_backend.services.process_instance_service import ( ProcessInstanceService, ) from spiffworkflow_backend.services.process_model_service import ProcessModelService +from spiffworkflow_backend.services.user_service import UserService class TestAuthorizationService(BaseTest): @@ -400,16 +400,14 @@ class TestAuthorizationService(BaseTest): with_db_and_bpmn_file_cleanup: None, ) -> None: """Test_granting_access_to_group_gives_access_to_group_and_subgroups.""" - user = self.find_or_create_user(username='user_one') - user_group = GroupService.find_or_create_group('group_one') + user = self.find_or_create_user(username="user_one") + user_group = GroupService.find_or_create_group("group_one") UserService.add_user_to_group(user, user_group) - AuthorizationService.add_permission_from_uri_or_macro(user_group.identifier, "read", "PG:hey") - self.assert_user_has_permission( - user, "read", "/v1.0/process-groups/hey" - ) - self.assert_user_has_permission( - user, "read", "/v1.0/process-groups/hey:yo" + AuthorizationService.add_permission_from_uri_or_macro( + user_group.identifier, "read", "PG:hey" ) + self.assert_user_has_permission(user, "read", "/v1.0/process-groups/hey") + self.assert_user_has_permission(user, "read", "/v1.0/process-groups/hey:yo") def test_explode_permissions_with_invalid_target_uri( self, From aa6b46e8079ff68193b50eb7b9ea6ccb91ed8df8 Mon Sep 17 00:00:00 2001 From: jasquat Date: Thu, 22 Dec 2022 16:14:52 -0500 Subject: [PATCH 26/29] added script to refresh permissions w/ burnettk --- .../scripts/refresh_permissions.py | 40 ++++++++++++ .../services/authorization_service.py | 62 ++++++++++++++++--- .../services/group_service.py | 10 +++ .../unit/test_authorization_service.py | 45 ++++++++++++++ 4 files changed, 148 insertions(+), 9 deletions(-) create mode 100644 spiffworkflow-backend/src/spiffworkflow_backend/scripts/refresh_permissions.py diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/refresh_permissions.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/refresh_permissions.py new file mode 100644 index 000000000..b7e46dd68 --- /dev/null +++ b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/refresh_permissions.py @@ -0,0 +1,40 @@ +"""Get_env.""" +from typing import Any + +from spiffworkflow_backend.models.script_attributes_context import ( + ScriptAttributesContext, +) +from spiffworkflow_backend.scripts.script import Script +from spiffworkflow_backend.services.authorization_service import AuthorizationService + +# add_permission("read", "test/*", "Editors") + + +class RecreatePermissions(Script): + + def get_description(self) -> str: + """Get_description.""" + return """Add permissions using a dict. + group_info: [ + { + 'name': group_identifier, + 'users': array_of_users, + 'permissions': [ + { + 'actions': array_of_actions - create, read, etc, + 'uri': target_uri + } + ] + } + ] + """ + + def run( + self, + script_attributes_context: ScriptAttributesContext, + *args: Any, + **kwargs: Any, + ) -> Any: + """Run.""" + group_info = args[0] + AuthorizationService.refresh_permissions(group_info) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py index 4ebba797d..6ab240d0a 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py @@ -1,5 +1,7 @@ """Authorization_service.""" import inspect +from typing import TypedDict +from typing import Any, Set import re from dataclasses import dataclass from hashlib import sha256 @@ -21,6 +23,7 @@ from sqlalchemy import or_ from sqlalchemy import text from spiffworkflow_backend.helpers.api_version import V1_API_PATH_PREFIX +from spiffworkflow_backend.models import permission_assignment from spiffworkflow_backend.models.group import GroupModel from spiffworkflow_backend.models.human_task import HumanTaskModel from spiffworkflow_backend.models.permission_assignment import PermissionAssignmentModel @@ -68,6 +71,11 @@ PATH_SEGMENTS_FOR_PERMISSION_ALL = [ ] +class DesiredPermissionDict(TypedDict): + group_identifiers: Set[str] + permission_assignments: list[PermissionAssignmentModel] + + class AuthorizationService: """Determine whether a user has permission to perform their request.""" @@ -179,7 +187,7 @@ class AuthorizationService: @classmethod def import_permissions_from_yaml_file( cls, raise_if_missing_user: bool = False - ) -> None: + ) -> DesiredPermissionDict: """Import_permissions_from_yaml_file.""" if current_app.config["SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME"] is None: raise ( @@ -193,13 +201,16 @@ class AuthorizationService: permission_configs = yaml.safe_load(file) default_group = None + unique_user_group_identifiers: Set[str] = set() if "default_group" in permission_configs: default_group_identifier = permission_configs["default_group"] default_group = GroupService.find_or_create_group(default_group_identifier) + unique_user_group_identifiers.add(default_group_identifier) if "groups" in permission_configs: for group_identifier, group_config in permission_configs["groups"].items(): group = GroupService.find_or_create_group(group_identifier) + unique_user_group_identifiers.add(group_identifier) for username in group_config["users"]: user = UserModel.query.filter_by(username=username).first() if user is None: @@ -212,6 +223,7 @@ class AuthorizationService: continue cls.associate_user_with_group(user, group) + permission_assignments = [] if "permissions" in permission_configs: for _permission_identifier, permission_config in permission_configs[ "permissions" @@ -223,9 +235,10 @@ class AuthorizationService: if "groups" in permission_config: for group_identifier in permission_config["groups"]: group = GroupService.find_or_create_group(group_identifier) - cls.create_permission_for_principal( + unique_user_group_identifiers.add(group_identifier) + permission_assignments.append(cls.create_permission_for_principal( group.principal, permission_target, allowed_permission - ) + )) if "users" in permission_config: for username in permission_config["users"]: user = UserModel.query.filter_by(username=username).first() @@ -235,14 +248,16 @@ class AuthorizationService: .filter(UserModel.username == username) .first() ) - cls.create_permission_for_principal( + permission_assignments.append(cls.create_permission_for_principal( principal, permission_target, allowed_permission - ) + )) if default_group is not None: for user in UserModel.query.all(): cls.associate_user_with_group(user, default_group) + return { 'group_identifiers': unique_user_group_identifiers, 'permission_assignments': permission_assignments } + @classmethod def find_or_create_permission_target(cls, uri: str) -> PermissionTargetModel: """Find_or_create_permission_target.""" @@ -691,17 +706,46 @@ class AuthorizationService: @classmethod def add_permission_from_uri_or_macro( cls, group_identifier: str, permission: str, target: str - ) -> None: + ) -> list[PermissionAssignmentModel]: """Add_permission_from_uri_or_macro.""" group = GroupService.find_or_create_group(group_identifier) permissions_to_assign = cls.explode_permissions(permission, target) + permission_assignments = [] for permission_to_assign in permissions_to_assign: - permission_target = AuthorizationService.find_or_create_permission_target( + permission_target = cls.find_or_create_permission_target( permission_to_assign.target_uri ) - AuthorizationService.create_permission_for_principal( + permission_assignments.append(cls.create_permission_for_principal( group.principal, permission_target, permission_to_assign.permission - ) + )) + return permission_assignments + + @classmethod + def refresh_permissions(cls, group_info: list[dict[str, Any]]) -> None: + """Adds new permission assignments and deletes old ones.""" + initial_permission_assignments = PermissionAssignmentModel.query.all() + result = cls.import_permissions_from_yaml_file() + desired_permission_assignments = result['permission_assignments'] + desired_group_identifiers = result['group_identifiers'] + + for group in group_info: + for username in group['users']: + GroupService.add_user_to_group_or_add_to_waiting(username, group['name']) + for permission in group['permissions']: + for crud_op in permission['actions']: + desired_permission_assignments.extend(cls.add_permission_from_uri_or_macro( + group_identifier=group['name'], target=permission['uri'], permission=crud_op + )) + desired_group_identifiers.add(group['name']) + + for ipa in initial_permission_assignments: + if ipa not in desired_permission_assignments: + db.session.delete(ipa) + + groups_to_delete = GroupModel.query.filter(GroupModel.identifier.not_in(desired_group_identifiers)).all() + for gtd in groups_to_delete: + db.session.delete(gtd) + db.session.commit() class KeycloakAuthorization: diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/group_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/group_service.py index aa560009e..85f6441ff 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/group_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/group_service.py @@ -1,5 +1,6 @@ """Group_service.""" from typing import Optional +from spiffworkflow_backend.models.user import UserModel from flask_bpmn.models.db import db @@ -22,3 +23,12 @@ class GroupService: db.session.commit() UserService.create_principal(group.id, id_column_name="group_id") return group + + @classmethod + def add_user_to_group_or_add_to_waiting(cls, username: str, group_identifier: str) -> None: + group = cls.find_or_create_group(group_identifier) + user = UserModel.query.filter_by(username=username).first() + if user: + UserService.add_user_to_group(user, group) + else: + UserService.add_waiting_group_assignment(username, group) diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py index b149ac540..7d000c9db 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py @@ -1,5 +1,6 @@ """Test_message_service.""" import pytest +from spiffworkflow_backend.models.group import GroupModel from flask import Flask from flask.testing import FlaskClient from tests.spiffworkflow_backend.helpers.base_test import BaseTest @@ -428,3 +429,47 @@ class TestAuthorizationService(BaseTest): """Test_explode_permissions_with_start_to_incorrect_target.""" with pytest.raises(InvalidPermissionError): AuthorizationService.explode_permissions("start", "/hey/model") + + def test_can_refresh_permissions( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + ) -> None: + user = self.find_or_create_user(username="user_one") + admin_user = self.find_or_create_user(username="testadmin1") + + # this group is not mentioned so it will get deleted + GroupService.find_or_create_group("group_two") + assert GroupModel.query.filter_by(identifier="group_two").first() is not None + + group_info = [{ + 'users': ['user_one'], + 'name': 'group_one', + 'permissions': [{ + 'actions': ['create', 'read'], + 'uri': 'PG:hey' + }] + }] + AuthorizationService.refresh_permissions(group_info) + assert GroupModel.query.filter_by(identifier="group_two").first() is None + assert GroupModel.query.filter_by(identifier="group_one").first() is not None + self.assert_user_has_permission(admin_user, "create", "/anything-they-want") + self.assert_user_has_permission(user, "read", "/v1.0/process-groups/hey") + self.assert_user_has_permission(user, "read", "/v1.0/process-groups/hey:yo") + self.assert_user_has_permission(user, "create", "/v1.0/process-groups/hey:yo") + + group_info = [{ + 'users': ['user_one'], + 'name': 'group_one', + 'permissions': [{ + 'actions': ['read'], + 'uri': 'PG:hey' + }] + }] + AuthorizationService.refresh_permissions(group_info) + assert GroupModel.query.filter_by(identifier="group_one").first() is not None + self.assert_user_has_permission(user, "read", "/v1.0/process-groups/hey") + self.assert_user_has_permission(user, "read", "/v1.0/process-groups/hey:yo") + self.assert_user_has_permission(user, "create", "/v1.0/process-groups/hey:yo", expected_result=False) + self.assert_user_has_permission(admin_user, "create", "/anything-they-want") From 9234af5e344d4a3f82908e494643c31b9e4731e4 Mon Sep 17 00:00:00 2001 From: jasquat Date: Thu, 22 Dec 2022 16:19:21 -0500 Subject: [PATCH 27/29] removed scripts for permissions since that code has been mostly moved and superseded by refresh_permissions w/ burnettk --- .../src/.coverage.jason-Gazelle.473795.719220 | Bin 0 -> 77824 bytes .../src/.coverage.jason-Gazelle.475245.497833 | Bin 0 -> 77824 bytes .../src/.coverage.jason-Gazelle.476451.578823 | Bin 0 -> 77824 bytes .../scripts/add_permission.py | 32 ---------------- .../scripts/add_user_to_group.py | 35 ------------------ .../scripts/clear_permissions.py | 25 ------------- 6 files changed, 92 deletions(-) create mode 100644 spiffworkflow-backend/src/.coverage.jason-Gazelle.473795.719220 create mode 100644 spiffworkflow-backend/src/.coverage.jason-Gazelle.475245.497833 create mode 100644 spiffworkflow-backend/src/.coverage.jason-Gazelle.476451.578823 delete mode 100644 spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_permission.py delete mode 100644 spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_user_to_group.py delete mode 100644 spiffworkflow-backend/src/spiffworkflow_backend/scripts/clear_permissions.py diff --git a/spiffworkflow-backend/src/.coverage.jason-Gazelle.473795.719220 b/spiffworkflow-backend/src/.coverage.jason-Gazelle.473795.719220 new file mode 100644 index 0000000000000000000000000000000000000000..3c5fc7087c6bf5119e52f8ca861578c24a7e868e GIT binary patch literal 77824 zcmeI532+=&na5vG_uMnxx)1AW*|KFt$4Ig*+p=uQS4<$AKrqCCjYq9fk2LmN>F$v& zJGRu5a4lQK7F$I~kzlHJ1G}&lwnAYxwUB5x1p$hfgkrM}MPe1HqF4e3Qbq|Rk@kCU zx<{kSD;DL6xL(_u@Adiq@AvMbXWsw5TVsYQM^jotG30Hi2yq-5kY$9B5B^Hw&)(eN z5bPbWT!DKfrw?`9`R<_fK@@O(21&<*$E5baBf*gWCxJ2V-}}GhKH-hS3AQl;i~u9R z2rvSKfcd_FyRy2P%bqiop}1;jN<`IipTUE79NvHQuzYm?fm;vDxnuHHpA1`jyS!i4 zQm5p!s>#tS-f)03M>oMw0jnd8>7Mi>#?pRa27@RnMu|8XIo$wcI!1b61v@ zavxJkm=rAn|NRR>0|(}Va!V8*6iI2rSpATuBqO7$-X<&g5Jpm<^R$r*t(HoJjFcRY zB}p)odd!HWlCpYQjbsdUc-a89VQBUS@Cri%&lBla3@CYqC(#1J4qkx8U+bO}ZF6YC$AUbX2I7V}K zWs+ek2ysWur00#d+czpHY0DTg3cs;BjP;?VF4D;Xn2_QMeQ;V$?H z++XZ*S2i?oAG?Pn6pSZ46f^X^!@D5SMe(?tijpjY@0N3s z1F5+%&xC9kiiR_qoU$$irR$p7uvpL_55-eM3)31&F}eyF1j+*YvS=n5!C6JeVTh#0 zR81LCJFK(0Vqv`#5+t22udDKK!%9gfp9I2Pd8f~;cDXBSYPhVIBqJ-fEaP5~gzT7H zrWmzPB~(SvXm&wzy+YEyW4@HM^N4i@g46DHRqn4@DLB@3;V#SJG2Mc@vb>x#b2$Q+ zOZd;ZAQ1S-d=^+#5v*Q*S}{iDrP}df5Z>%`RqiQYNjR1e?!uq0`|fZM$scnQ`UxoX z@gKiHGCndd`911*`hgqQ%eHnM3)04-lk>^sb|`0| zkyDsFFuMXAcsJ(*F4J-`IFzGcUu89Md@Vsx$w_Y@_|G;*fDvE>7y(9r5nu!u0Y-ok zU<4QeMt~8x@d)sogLC8lzaZ^EQd$a22f@KMMt~7u1Q-EEfDvE>7y(9r5nu!u0Y-ok z$Pow#e4S)3o9NuY^X1#{vVrc-?(X)k?)L6(xvQtQYiDow4#~mSR9chnJsmqbI?W&_ z)MX`my~Kx`L!+sL8akoqsbr`lqQJs{P~K~-ku)c*3s1!ipPfFc4IV^B)hN*P;F1cwXmj1$0t z7=d>JfdF5}*<%xehcD;w&;;-2YdC8h!r|xZy!HTu6N2QBW^lktq&ISocS0zcMn-@U zU<4QeMt~7u1Q-EEfDvE>7y(9r5m*I*;y$>jsKEiB4)GWGhEge+bKo7eF#?PLBftnS z0*nA7zz8q`i~u9R2rvTgPy&*Pge=b$F1Sx?sS|3%&_g3JV>B}q(iP1}jc7`GbUe4< zz8${YG^s4V_&%g-k%ec%*0TJNu4-d3`2Jt~+_1F+%kB39InfLV*%~S;5~*P|4$J7y(9r5nu!ufz=V< zIUWV^`~RHuCW8NLV+0rhMt~7u1Q-EEfDvE>7y(9r5nu!ufg6wj&j-DD{{M*QERudH z{Y?4^d<)>i(%(rRkxob-mfkPDOX`zaq&g`m{z`mVyeR&I_*wB&;yFN3{u%!_{EzrQ?LY6=`~&_T zf2+UF_onZf?|I)>e1GFT@BJ%pySK_KdS3DTyXT)g9|1XRV+0rhMt~7u1Q>xElYr-l zz)|2?q<$C;yw>~5hJ2Qu`{S_JvpkKKg4`N>qUVY}F&KhzSe@vjs288zI z_WL{s;F6L9$(gy*{ovZV|E15zkq9zg=g=VPsULuY`v%TWJorrXZ@VUo(No+sLY?^L zL4>LXL;Y}muz&Wghn;ZKf{iPJpM)+#92}fP4rI#UD3Y_@eNN7Mf62bt-^|TDFtP9U zSLZG?^?&YR0bh5(BTw>Z7r5MLikrfR5SqqvJYc5L>ku{p9t#KHKb)>b(>SeMwIOHsnUKjOr1-U;v+(E}G(mH*^;p_z5!WVbLv8L{;m+lY3EeOR? zXHgd%+SoO7_oGi9o|5DIu5Eie;mEz6v#oQF4e~sK|5AUI6hJcY?1RLP!E~bO|KL%l zYlXUdvp zG|BDW1kR#OpE+B+5gbh$XYTI#@3~JR<&}vB-rI;TYaDz0>4aCtPI*i}_tQWFI5#w0 zePnv}-@e8B>S3><{`qHqe-Bqt2kwr#CqDPq+{EnM#9z;S`#g&C$ZfXOLV&Z!YG>yl zx5XcV_1=RQt;t{%G`ll&n#;*keXW1kGg z3vy&#H4qH3rn_YW*rOX}&g~xVsVKhs^c5Vpm_DQf5+hR}rU>L9*;kDzRKN7#SDdb3 z6}W4wu0HdFsju~?BA!avEvY>I{EnguaBZwOKjH5=`{;#m1WE34I8s!8sn=Tuj z`lsB3el+=_yBI&SILbE^fn%WP;@sJ2bYkY>H?Gv6!O7yTI6t6p=wE}+x_j&pV^9JmK|&yUhQ@ z3y*UyA2>yyBj^QN>vg=V`+H5_(}ije>~8m5oqOZnV+dt=I}$ud_PX)S?&BkKoZkh` zD%S^Z1Fr6EPS_cB&b~F7r zI9xooyu;T69!!;n*+BB{#Li0A*0h{XuL`|q0cYw73G73s3{ob-L^ zDe0Ti*QH0LFG*jJ9+W;UeNwt#Iwy@uDQQ$vqz}R9z^&3@X`i%H>Vz?ZjZ%$NCIuyz zI48a?{!aWQj2HY={4ep6__R1BJ}y2centF8@nP{ZFn;iH(G<_X7(!fB#pB|g;(Ntg z!~^0UaXXACY!Ms8Dv-lAMt~7u1Q-EEfDvE>7y(9r5nu!ufw!9g?tgMeju3P6&BPo& zOw6G}#2h?G%z*>M?B7q!zyLA*{lx6sN6g;6#PszMvu6)6y}iWj-c8J|UBv9%NlZ@< zF*|k;vwb@;+qMzY-Azna7crfk#Dqe`baW8Y-cC$g8!@e|#N2cfF)b~`Y~4ysb2Bkp zwh*&!5}e#05N_tRKWRsWY6m*#^WKz?Iy7y(9r5nu!u0Y-okU<7Vp0(kzP z_5W|+2xdWJ1Q-EEfDvE>7y(9r5nu!u0Y-okU<7~we*d5K|5;LC1Q-EEfDvE>7y(9r z5nu!u0Y-okU<7V#0(kzvQg{!Nz9@BxUl;3xpAR+!9u746|JmQ^d(7A2{h~MMx!3)g zJL9_I`hfF?&O3x>h4=8^;=3GQb+mIAxCZo4@b*>nWp3>Bs%WEv&l>!QAN$jiwT6=!BxDk|C(IPN)$>4~@i((acatS2QCvqABUo z@mvzRZp#`6%TRUPLc;z3T5d1$_}!m?Zw(lC{=qpWT$9EnPW(i0E|>`XE->tW)_=hF zFTx4_Rqi=o7q5CB;r4nP9Y-CPJr8hy@IVVvw=2Qf;<7{vu>B-hHFPap%-8WIKR z0}^IGy-uO!vEA*$by{Ki+()ljVT}>!xIrF{r$$B~VE~Jr^fGB)ff@;GYE;$KWCXYC zN~tw1Yz1mWQ;l5H3jE)0283)4l?-YNh-k625mpT7nh)WUD-6}3p`OOI;3n!x0^&kb z_0h1AI;rBORu6RrcJ?56K*2`SCuA#W#)z%J9ut5BnoemrJ~q%REb_%S?(l^5aotc8 z1rEE2MtsAL|i&LeGeGK3VK9~TdwQesA~A<|B-oV}hRNF;u0&C&uP;BSz& zzj!PY{4Ft^LN-VR$@B^_im;r0IwD2b#;q(kB};%&i8!P^4g4}8G? zWB)PVCEo|UFM0<(Kl9w}e#sqmz3jTf`JD4s;iAySf05tdxQ}}kdjB(U`zkln*&v{- zw00vIx)#ZecbrOTC!_JysW9#!tI6Sz70t^JWSd+)=x|fiv2-ANtWH4YJrpHaBf6$o z3t?}h;c>n-v81JE9U#MbW?2mgYNsLSPZ?!2y&FJQB@J0tT3tdZ-ySykYS7}Rsb!gY zUDI6!DoWQ!#eDq*#g&@$$jtWqkRgc} z$BkG93aFGXE0^(w)4C=q1wl@VNc8lg(U!3i%4NOP$~adH{!Y*c`Lgp@rlW{*hFDyL zLVCPc0tV-3FtC#2d{qy=pB2IPvlM;Dqww}}ps^t3WM|FMp9+A#I>kWSv*f1R4+)ND z4Yh0!3*!BZ4-kw~L{QMrobUn!DpkM=)8@Jd0)LW5;0r=+a&8Fr8n)^245>9Czod6z z7kBDuC6R`4L%ehVV*R`eB&=blZZ#6{HF^J?^!+C*Ca?$y|2Y97ehurJ)r7ww31oQi zKDBn~?t0!AcpG+rw+GkS+X4^n;=qHQYv)11{6Fsh*P(}yG$Otr9t(aycrftYK%f7c z{sCD1U+ev>x7>4|dk$9r|Bq|b`J(ffa9MaS|3m&Z$B!Jha!+!taO>N)nSnk?&UI@S z?v>lqEB92bGmyB=;yoaznTDJd#xa+Ut(v}G&{VySnhKJs$?pa+RC=lT__8Kd3ubK2 z?1IcfrRH6_I%r}i0H9LyT5Zk6Gs_b_lm#rF$26TgKmnBzzI+-W-L2g8U2d5Su8FsU zj5Vx@S1vm2m{>T!Z92Ar04iev^BiQ8wQ92ZeAS;z}Yj)?QOmu+~nmNPn z3Ff;7-cC?JWjHU-!qz~NNI?;0xos#WZ41SQWg%1%h%fC)x@D6Yl@}ef3D15PvV83;s4330w)><$ub* z1N#57y(A$FOq;+d<2@URHi)g10ny{8;&Nr88lHDoUyvx zWN4=F4KmYzm{#8+k0sab3}^7m@np(Z%OsQJ^$@f45U8OtOp{l$q_2}(s#rM2$sYt& zbSH!C{!PIU7Iy$t&>i1g{4T2L+YcJ3%+clJhrBAk+`FizZ-Ba(72ic2>xV%bDodaX zaxxwpaPI>MRL0*ekL~=j_+8YAy�jFiAdXz&8dIWK;b9e~IhMNV->?abFTu_padc z!FRj99Qbyi*Z-J51n&Uc>Ambd=J~Pb=wD zy$%ct7vehC_>0R{gP>qmTo@C~)EdrAU(fpjZ}~yNJhf^0kwN3cA~U^uFjdA3savSO>@($F=KFPcB`kynC(dsa52l#Y?}7q>K_sBJKCy zbdN@tS1igCalN)R-|O@J-|yW=&%FPAw?@;d97$?%B`tTO0OB|_Aj=3L5B!zDpS3x` zAy_+LxdQiUP7i9o^WA>wgUILj43bXxk4tU7NBlwWPkdwUzxRI0dD0z&6KrDy7y(9r z5nu!e0poo>XGK*NmpzwOhGJ@3Q^Km2`}7~WK;swPLGF;$KxhNEF69ZihL=~30XI-{$@xWy5%dz$Z?^f)U@ zO1O`yBut7HhX3A0p@9PnLAfOY4+VXS^gQxf4(Rd1D*d~CiG}JnoP**X*HZltHUb>uoXkIHh@d2Q?g(C;<$a$Pju6E~Y_okC(VAmWsE5zkOco#*dg&fCd z?ygKCdQV2pM}DikaAzwCMk3iN<3K?z?8q)PR)eoqVI~#Xpo0u$)Uw*jieY|g$x2^fpu9VlL+IiqT?`x zlVhr;jHvDA*<7)((FqBXPN&;Zaio5=q?1nqq0YS1V^leu71h;T)=iR;8C#}tE=odH zOs-IjTBqWwqGvR#Ah}*4Y2C32MiN!C6sO#u>RB zfy*WQXI~Tud}JXDEU5_AEO|X^5`)qtr9?HzvQP%+Y5*BuIIO_M%1|J(hMAA`JRWh-#R<4R)Ktfp}7n|mr0`8ZEve{y99+$c9eUeqCHE_tT) zQ1dJkNqIcG&n3uk1^JeVt9LORstamA8t?%EcmjU^`+lF98* z&O#%nFnM5h1vv0-&Ieqk-$yzqiv5Du)y797su8yv*w$84$t}eN=r?+!g@6K+?##dLElkPq3JKH-9 zKPS{?C2PIJhns?<$+#Lksp!c>usy86!hm3EB9(+Sj}yVMsFuzsF*Px!2lZ4m5*bfw zry{ZBc-xQ?KBXpx+dkBF`mXn%zEf{YO?0<+b_Qe7A-Fv~noN*gSOuuIrQljv)1>1Q zs-6ypLUARU2!&v^MAO}Pk-s4Q2uVMZu1UX;uCOR(1Q-EEfDvE>7y(9r5nu!u0Y-ok zU<4R}cLD()U&~oz6M~B`1|ea*lUGD49k^ zfDvE>7y(9r5nu!u0Y-okU<4QeMt~7m1A(GGxF}F>gHN0I3w%R~l*l>o4%-+3Mt~7u z1Q-EEfDvE>7y(9r5nu!ufp;hY$v{Gu=L#3xr?lirHJsLiBhmC|W++Si@gKy`ik}kCiMlu}9v9oh zt^P6p3I84b!~Q+~c7L_M*zffH+V@f4dEb~X;XCHL#kbv8>nrundcWa)#QSOQd9UUj z@b-9HytSS;J=Z+Xd%oiN8~1tlU%A`dm2T1XitFE9|K$1z$YC2Jzz8q`i~u9R2;7(i zTt@}YU3|2s^3@3 zv_H4s=Q;?N6dz2?&X*hj*Y*Q1eLjXnknuW)22oGl036&uaDMW^XCi;wIaP$7;${%) zz&8&fR5=*zhx3E|b8kIthnp5{TmXI&I)FGhIE8G;kiijvgvKgC4c0Xe_U@g1;HSeKm1x>F1z&U33jg3N+~DYivtqBUeh>C>&-n`QBt77g;hY!rT3dP6 zw;L|lv^()29I^%6yI`+$SK{hC=js7xanIOHZ^uq>4edOC_UGN+e&p*j#h~e@c(`d0 zIiBj6;&{midxIpU40?9J9pyV_waJTIaW}ZPbzg)8-w$Ue;TFEA3yw8*UA=U_A8tV? zhB^YBaAbCb-9fRpWGylP( z4#zYXm;%7lJOYww&QufCQ4BuWfns1yosi6F9qWvi;`C??DLQ zG+cx~4%r;da7JvlIkscF-R5Y*w#lJxgKS{DdmChfxyeW*VSA_w5t1Ofa@x|3;Axk6 z0ww1yek$Ik5zfU^L>OduY;*Y^pTt`qRx!m@ZpFg3&c1U0559v2r^zY&;j45v!kN;> zSxs^}w}3OSuH#hz4i+pP4QoFerIm}!bb@_Q>%pf22cvs{}!it*X|m8 zcnZlViyZ|SvZF7@@3)vmF4`+|7vT$`0LpEFfhp|xIQMb#bM@G5o8k|{5tlrG8v0}? zUXU&8sD@yO)m_b-z#iK)dv4EgPkGVRr?23+#q=Q^kQkW;F#(W+WKR{QQ1#M-U$Hy< zmEf+ay!y-!roYyo47(~|x47cO^E(6O;M!b%e$v}>_R$NWFp`{Qa3oN6sn=Z!j?&T- zvoG^SCE#oZFUW z3FCG?xhIj_X5MrV#o)GU8-z(lC z9u)VAJ77d%n^-Saf*iIn0*nA7zz8q`i~u9R2rvSS03*N%yxjzF|C2j{=x#=cinwyE)zMYt+CSta2 zBWCMXVj3HX*|LS0&6|m7Xdp(GiK(wArml{d+FD|2YKW<>CT7znVydc$sjMWXqJo(6 za$?HLh$$^4rlf?J;$mWoiiimWh>;{>M3ET3pBSHy7%v$r;5;6(=XMk0auMTn660_X zW499{2*mI_F*ch3;{jy;ABJq$#t1M1i~u9R2rvSS03*N%FanGKBftnS0yi)LJpa%7 z|2J?1v!F2oi~u9R2rvSS03*N%FanGKBftnS0zd%2|IhmWEGaMoi~u9R2rvSS03*N% zFanGKBftnS0yj1RJpW%Iya!2Nlsd()i*^3b`|EuV`x?Ce?CtP8=4p3-(d~EL>wL|b zaa?hH!2Uz~9m2E1d-!khowl#q+PDi`J$lH0`x+S;4k0V8{ihG%J0-rl#wWAO7y(9L zV+k0%kae#;cc-F}$p7=_Rag08Q}{j80KMOMT_R#cOJ-7_pz~hAI7^p8G!c%blo)s} z8iJ7ZQuSTc;!$0XCKI8wqK&9&P*G*Lglx$MsZc=7NFuH#u#!rnRLHi_R1$|@+Eqr> zP+E(QjHsG26jMXi5Ac!{1F9;hCsIq5rJ#&@UbPfhKSB%`$_+`#Hf%)Xgp-=4#*{Qp zwGcTeRZCBRpi-kk$aZeDpcP2Te`3Sw&o5dn5e1|GP`zOjvQCZ68cd!2E~w``#t%Pn#jb#`=Uu9%Te`SNkv0r zpaV)q@W06@6S8eo1gNLOY6=&~3%^$nRd_T#5lSS}p-3{57!IkLmc&IsHMKMuP9`GJ zkzhfZ+?^JTQ&fl)cjcOSX0tMy7zvrDAwG{9oRGCs(F7_W5e0F*92$yc)RYz_jqaQ2 zwG#4J)VLCj6<|1Mc!g{g6$}d+aPbrxRT9Io#c^_A1H>qq^e8Ss!?{-OLIN3}){rPj zACNHn>2(TCkF9POuG0$B=YD$43UiD=#|`pOEIBd)2?JQ{qL)eY3e<2&QzNRTCc?N~ zS3<36VJT3=Q^}x~fUp)#r9(;@y5>W;XBmr@u zsrqOrojj%DrdAJi1y=STctF8s!y{xXXvT=8z#0>P1e!`}I6gMfD=hNGH}3F+^a(w! z#tSNv;09D?;f$V6#zQGZ193p3onFl3Y2f-JsYTB$tvLAIKzf}TP%y(X!tMj4Jr69tHt>;s{_^qN1bXet2I zOReu{YANu0yAcqwTc~6nv(6)Jb25Y!S{N4(jVsY~t|8J!ubjP}B1j}&YR%FDA>gl{ zw!e5R6Z|bU>_XO0GcDvAp-{c0Lc?krx=T99XvFLP>rnzpk4T5bXT;n5m;AT+zVG{h z_s8Djo=ct&xLR3LIJyt6q;~t6<%n@Bf ztbwpM(C|3lnpoD-GY^pAJfpN41hvr+^rwt68tzRXtAd6sGp#P8ly46kd=+T%($uoT zysqJ_1QjJ4qhg`{f?~@ksQ@J{6qQ)z$o1xR4O=;=TE}tPHJ5Hy4+_vsmVpL-1IUm> zjN?Wu1qD>fm(|Po!f9Otm4F~SMI?G^$!N=1G3Bz}Y-OA)0)HoIgnY&ME5jC`oFSGL zp^zT$m4LxH8VtIoX+W^y5D8SEm?gYnI$_dLhBl ztf5xyVL`l~@c@DeiU ztl~~Rt;AC>Zitr-K&+p4fP{7I)U8DVz9#R#oxcBM#RL`s;Xfxp#IIw0vzG7|B!LVM z-Y3^D-CfW70&hb$@b=Jpdt2baT^x9@YyCVZnE%K9|624Al19WA#N+<&`w#iP>+AD= z(>nmG|7+Zzb(gvBbI!x+|Nn7}+F!IE7cL9$<$uWEX8V!tR_;lz1#W%YHpABk$+>p@ z!o7NXdi9>Fc?J@!8P#? zkg<+6@#;l~6%&i+w+&l22%s_+u)rZFp`gTab%BU78l^*l2(nTsw`O-i%48=fp_wzR zo?yOf;O+ngREG2NEG!Kqi4+u3hBF9SsB}~ET2`LkHn?_BvW|`B)$>VS$!$v%#};eto40 zcf;EZs;X%um(^4iw0X{Kr`6h6KA(KP2|!S(#0N1r1*RBod37}uu(*Sug6{a{(sxk}&jHXtWsWW%Kjc;UmEJ`)JOk9ltoknMSU(KfP+0<9 zkdyJ)fO9`Upfdh$dTiyFrSGCn?gJrIhDq{C1HLh!Ae-X%|BD@8M$*0Ftn-qnI(Pe@ z_rKfmW#6}bz23*XL3jt?PWNT^ao3Ms$NnO#C?<*#U<4QeM&Je~K<_=J-K|L@}Bjsr`C{zmM-yX`CtDa17iRH literal 0 HcmV?d00001 diff --git a/spiffworkflow-backend/src/.coverage.jason-Gazelle.476451.578823 b/spiffworkflow-backend/src/.coverage.jason-Gazelle.476451.578823 new file mode 100644 index 0000000000000000000000000000000000000000..ef7f5c499e4d86a1876c0ad2756b2056f2b7908f GIT binary patch literal 77824 zcmeI532+=&na5vG_uMnxqg%4BmMvRWbc{x}Wm}dl`HBf-69|Slu<@uh>XF8tE8R1) zWyh9!60T*d*kY>)DH2T8ZeSO-!d58ErWO+IrXWBOlTd8dp-8MERTN9WK*}hAB+`EG zP4{SYdBvhU5!Y*5^SwUb|NY*5^vwI;cWW%8%F&dTP%`p16hIt@24oo_~9zcfQ*%eGvH^pFz@b|1qh<_lQ5}{fTeP{rBE4IZwFbaDr`&03*N% zFanGKAz-}E=d7x!;d19P%1~U*Xi7xY@}K^LcO2e-^ssz%|AAW%%lTvSR*wu@M~A#$ z)>5bBw5rL`SX`B3$>CT;$;6T)a%NPuuFmS}Fs||77}Rd{AzQ!h<3yZ5XQ`(v)OmRMp#Mr4YhM3Ur>%nQDm`OdBiKUXVdRmQSGwSfN0c^+6tPS85hX$TChyyg3zti-qh|c`Y%iL*RZr&UP zamkd0b^b=|HQY=zC$otmKz<^dNaM(XJMtc9QY+oL(!H6K8Q4|F%`$O0AKnGgX(7ik zn!hWXjNOw}3z6S0&)?Zjf{{$M%Qz5_0ycXcRlCbL1gTOzt=|)ehF4h0W>RE743QrW z!H4hu5|^{8v61`OJtU!EJmH~OMlU$r3ldG?@Cr!>pIFeukIShj$ujtE zIUhNYnv3&H(1M|8IHSoa^FmO%uBi=+1r730JTc)H;<=6+Nq21|9i``MRr(z}HSVB02KOOhoVIz`1<|OnJ zQ0U`7et~3sWM1-n)bWlnB@U%YT8U|pWuXkt*8noUa9Dwhm7!ENL((taCCL-lJ@b0= z518M#*uzz=I?Z_>oijE5(y+s~MrQ)iR%vT9;;Vn9N71L$n_0r0yfBqTz zIDW>+SPPM2)hW1iKMHxx%2wb=B$VW^Sxw>EH~&;9@^PNT{^ZLJxKVC_yr@IYT=Y!q zq2^g!P+8CKOalu1)Pype&*eBSik^6CR9*78d?j>9$*7rFLY46)L<~t!rxaa|Xeyk9 zDzvqQzh3O)ND7DWC+;P?a6`su1Q zs-6jk!wDsp42NO0M9bZHk-s4Q2uVMZu1UX;uCOR(1Q-EEfDvE>7y(9r5nu!u0Y-ok zU<4R}cLD()U(Z=%6M~Dc;PB7{@8xSba~#6v7y(9r5nu!u0Y-okU<4QeMt~7m1%Z-2xG2zQgHN0I3w%SFl*~Ku4%-+3Mt~7u z1Q-EEfDvE>7y(9r5nu!ufp;hY$v{Gm=ZY8Hr?u1xHImVTBeBeAb||PTS|&B3De2Mi z{DS)q_;S;vvi#!vpsqz0o(Y@F@`Jjnjm6;mfAMp}<_;{k-wWhK!zbiwsi;V#hSfMM z``1z+MGwc4dIr8lSPfg>CJBm~*!TavLsK)8%?L08i~u9R2rvSS03*N%FanGKBfto( zjsVZ`$dBLu=cG3g{AU{@zz8q`i~u9R2rvSS03*N%FanGKBftpUfCPBn@5b~0M_gx- z^h@by(of)903VkAPWp&+Li(`ue(7COpVTVVOMdZJ;>+Si@gKy`ik}kCiMlu}9uqsn z&Hgd}asM6uL;l_VPJgYx)bI5D+V@f4dEb~X={xGX#kbX0?<@Dtc)#I&#QSOQd9UUj z@b-Ayy!DbV>vD`)97^wn*fi61MnYC*P&?~Tr|a> zM$$AUkl)8g@Nv9N%}MYFLB={JrD^_k?8LsLCjlFBB3VWa2wyOOPfQ1HFGkJB6bM=6;v}bI(w`&KuhIX7k`}1wye&p*j#h|IDc(`d0 zIiBj8w26yc={9h0*>({Ud_SC>fLr*IZaCK5ef84)ez*mp zIO+<7;Lyg<%-xSZd3Z{W^Sid~?SdoscFnfUJvPYm2>wg`)shd%z_Sk$I|kE*rvHOS zU5+U(FbROCcmyQzcA2XS!tK4m*{gT`9PyFK(A3~ypsU(3=&N=h-;}cx&IUR!IouuK zDDQZ}*LY`RJ2(g0ufCQ4FuWftu1yo&q6F9qXvi;`C??DLQ zG+cx~4%!^8a7JvkIksZE)#hlyw#A`tfox#BdkbWP*@OduZ1edapTt`qRx!y{Z^ptl&%AQ~559v2r^qS%;j4Bx!&P*eQ?c=YHyI1m}jv ztB*|2{@b^BPXp{#Ha!2#@9*I%>%rYw|HS9snwyxNoA~RwZ=XkT9yyKnItXy?Sl#R# zWc-m=FTB=r;Sc?Rcr@yG>uH#hz4i+pP4ZuGerI;>!bb@_Q>%pf22dI`{uZZs*RDEz zcoNAdhaE*3va2u8@3)vmKH97E7vT$`0LpKHfl2K6IQMb#bM@G5o8%9{5tlrGn)+lY zUXU&4sD)sNwcV{7z#iQ&b8h!=Pi4u~r?23+#q=Q^kQkW)F#(W+WKRvIQ1jA*U$Hy< z)!?qHzWU4$roPsninyv^x3uc`^E(2S;M!Pue!|;x_R$OB2$Gx?a3oN1sn=Z&j`H&3 zGcWTcW#DWsn|Y9*z3}3k16|}?ZKZJNSm_gA8yTFv^4*Ds&%fON$2n5gV zdZ(O&UNrflvjji0B+54iz%dZGICnN0otU}!jVrZiaIz#6=LZxH{j2|3hbX}X4bsKe zgj2~EX3sl3BJ5X+Gtd0s*NzfDxSRbm54`Y=-`oj)pbuAjeE78Qd3*GgC){3emwTUh z;c?F40jKD(`Q2da+_rahf3Nv_x=`bS-R-Wcb8p;x451uvMS=^-ZYRFkd3`12Yhdt)yeXq3|q0d5in zn}f%ew>fOs+HB>V;C8oPeXTh#3_0Z~lB!%yc>e#0Sc2fY|E@{DmVPc>kuFQmN#B>A zlD;W@U3ygdlJo`XLFv=dC#CzPbJCcUl13#(`Vfo`+$tTG_DMUXE*K-&DAh{kl3#L& zbK>jb@5Eoic)?G_{}L~WPm5FH7y(9r5nu!u0Y-okc)JPU{wH_j2r)O`Ow8fK z#2h+A%)x`i95_JC{{6%Z3=q@bPt3l3#O&QmOkW={d-f31+e^&u-NfwLMa<5f#PswK zvttJ_+qV<5Z5uJ&-Nb}K#B_BL6ATj5*-1=C2QlsK#I&^$bJI=4w6+qnbt^F~EyQfu zLd@pP#56Y(vuP7C8#fZu)I^Ld6Vup8OhW@P_4UNm)e%!$OU#B1#MIOfQ(a9=RTVLn zmBdt35K~@GOj#K*rKQA_ln@gL5F<&%h$1n5KQTTZFbUD z|8L+3Wcdicn^}kD22qYiw*wI`x|`^`wL|b zbzE_L!2Uz~9m2E1d-!khA=_7N9oz-35k2I;eU*$1hmez2|I>%?of2PN<&)WEi~u9B zz61IXsbn~#Xd`L{RMZ$QAy>9eDijbil1!*ctfbl~7jkVhl_cPoc9jt| zoY7(7c-UUN9!8gnm?wLouZ1e@{NFCbJ3fzGOnkaa6rm(y{Ou z=zx+D{BJQTgj@#|0qW_9n#Kk4{O{F66&}lshm)yHIGV~Phr_C-rEn2YOD#=CQpsp+ zBv_OtcVz_QBo!h>p1ST5jGgY_XeyxwPbhjS8H8Hvgc`}{!I4;IG&>a36)lq*(UkP) zcs>bTw`Glkl~HxvLc;z3I&Lp=d7YntZw(l?|G_>cT$9EnPW*&_&Y$r8&Nu9R)_cJ7 zFTx4_Rqi=Yh*#Z@aC_ZNwxhPot_Qe3a%WcC?I2%Lx!Q}@{yN8-=u>O^RHIbLwNOb6 zQ1^|+BB~x7QX(gDodDUvY*xmSBVqG2#OD!%6LNMcnm`34q9Cr9!$a|`n$}{Z(S0+$ zRzd-bnowf#A`Ax%uaK*uf?-|*E}p`pN^&^9Fi!TbgBT^78N~%?B;U%NPap%-8WKh6 z0}^IGy-uO&vDNLuby{)y+()ljVU7{#xIrF{r$$B~VE~Jr^fGBdff@;GYE;$KWCXYC z%BVFhECp&rQ#1Ld75Km1@CmtEDjC!g5Yb}kOjyZ4*L(<*M zoV0c$8M+q9k9V9(X(yxc)TuD;AgjsYpc&1}4`dr$1L$y4)Uk9Rd#qkS#yu1zm?OG| zSO;NmqTz9&HL;|nXC5HKc}96H2o1;v(8Rs~AhC@Qhak?YOt8n#MMwT9!gt1jKF9u%ONr~nQ8I*=iW z7{`rR4hpE0FDsYv#nZY5Dg!}wib(YIqS2PIQp#n$*~&Oq0{%|W2>G(}SB5P>IYTTi zLLoigD*=OZG#Hr4alWbt-_MHR`&o*<<575PInbD&a4gMG zvxZu>hXwI|#sdh(DIzH9XHK{Q0+lLYg=ur$1%W?FBk)C`HaI5)dkx$4c!t!RkYCcf zu!=kNw30}}xFKFT0I`1F0TR}*Q@0uk_?m+McKZI46%$wlg#VlX5x<7@&1%A5lmxOo zc%NFkbay@Pi@Xinz}tgs?QM|t(m;WJuo9#!oTe&B>Hn{a|+YDbHB6Lq`<{3!bM#&zK(?Ube3gehd$5stbFKDV+M@>b^)Zll67%IKgLVTH%szoz4 zXLdnmp;GfMT^%&B697=DdCj)w;+f@%9?Ak1&tn?)9iV{92wx!$knUD~`Yyjr2G_*f zLB<-^#48saR!l6M-!^R9Kme7ofO!si2}LEAs~bdA&?p^>M39wA`8B)qQYJ#6gl5jL zdV+$sO%s}m+h{}jjfeO)N?%~D;*G4^J@)Lzc8p>`04OB*1%?8in`Ss-{ z+zoFlsH&xrTvk$5)aE&}l~!wG>3s6}763t|jucrY?6XNg1bN=7@BfcxXySz_% zcR>HY;ce^Mvj;N*i~u9R2rvSS03*N%{6!KlN{&FYmCBSyVIbuHdc)B`H-jcBgEMBA zn+(kqzd>er57X*f6tLvGo#8BgIi5`UYT0CxydGkd9Rf8}hG`0Fmh^S9Lhx7Qc%+u@{6;873(t4fw`@qHK!a|1WiX8AfGgj z-v4gLmwn&%^?D!k2H_omJKdMv$6P;l9sP@}qL?T~fDvE>7=as{0KNBY3svRHZSKSL z-bF5`vDy&U=)L*6d{Je7h~C#f7E~>5b01veua>ypDokr)lJtnoxfaJ-zp}p?0gDy_ zJJ*3h@j_ht8h>%wY7i97iVI_cky^u<>Far4dB=Gm3OaoJ++D)w0MbE+y4Up&;w)u literal 0 HcmV?d00001 diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_permission.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_permission.py deleted file mode 100644 index 113a92dab..000000000 --- a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_permission.py +++ /dev/null @@ -1,32 +0,0 @@ -"""Get_env.""" -from typing import Any - -from spiffworkflow_backend.models.script_attributes_context import ( - ScriptAttributesContext, -) -from spiffworkflow_backend.scripts.script import Script -from spiffworkflow_backend.services.authorization_service import AuthorizationService - -# add_permission("read", "test/*", "Editors") - - -class AddPermission(Script): - """AddUserToGroup.""" - - def get_description(self) -> str: - """Get_description.""" - return """Add a permission to a group. ex: add_permission("read", "test/*", "Editors") """ - - def run( - self, - script_attributes_context: ScriptAttributesContext, - *args: Any, - **kwargs: Any, - ) -> Any: - """Run.""" - allowed_permission = args[0] - uri = args[1] - group_identifier = args[2] - AuthorizationService.add_permission_from_uri_or_macro( - group_identifier=group_identifier, target=uri, permission=allowed_permission - ) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_user_to_group.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_user_to_group.py deleted file mode 100644 index 814cf6dc9..000000000 --- a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/add_user_to_group.py +++ /dev/null @@ -1,35 +0,0 @@ -"""Get_env.""" -from typing import Any - -from spiffworkflow_backend.models.script_attributes_context import ( - ScriptAttributesContext, -) -from spiffworkflow_backend.models.user import UserModel -from spiffworkflow_backend.scripts.script import Script -from spiffworkflow_backend.services.group_service import GroupService -from spiffworkflow_backend.services.user_service import UserService - - -class AddUserToGroup(Script): - """AddUserToGroup.""" - - def get_description(self) -> str: - """Get_description.""" - return """Add a given user to a given group. Ex. add_user_to_group(group='Education', service_id='1234123')""" - - def run( - self, - script_attributes_context: ScriptAttributesContext, - *args: Any, - **kwargs: Any, - ) -> Any: - """Run.""" - username = args[0] - group_identifier = args[1] - - group = GroupService.find_or_create_group(group_identifier) - user = UserModel.query.filter_by(username=username).first() - if user: - UserService.add_user_to_group(user, group) - else: - UserService.add_waiting_group_assignment(username, group) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/clear_permissions.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/clear_permissions.py deleted file mode 100644 index bf81acfc3..000000000 --- a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/clear_permissions.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Get_env.""" -from typing import Any - -from spiffworkflow_backend.models.script_attributes_context import ( - ScriptAttributesContext, -) -from spiffworkflow_backend.scripts.script import Script -from spiffworkflow_backend.services.authorization_service import AuthorizationService - - -class ClearPermissions(Script): - """Clear all permissions across the system .""" - - def get_description(self) -> str: - """Get_description.""" - return """Remove all groups and permissions from the database.""" - - def run( - self, - script_attributes_context: ScriptAttributesContext, - *args: Any, - **kwargs: Any, - ) -> Any: - """Run.""" - AuthorizationService.delete_all_permissions() From 197a82322025c2a7411db12ede7dcd96aff9e543 Mon Sep 17 00:00:00 2001 From: jasquat Date: Thu, 22 Dec 2022 16:42:52 -0500 Subject: [PATCH 28/29] pyl passes w/ burnettk --- .../scripts/get_all_permissions.py | 3 - .../scripts/refresh_permissions.py | 3 +- .../services/authorization_service.py | 69 +++++++----- .../services/group_service.py | 7 +- .../script_add_permission/add_permission.bpmn | 39 ------- .../scripts/test_add_permission.py | 100 ------------------ .../scripts/test_add_user_to_group.py | 69 ------------ .../scripts/test_delete_permissions.py | 59 ----------- .../scripts/test_get_all_permissions.py | 10 +- .../unit/test_authorization_service.py | 37 +++---- 10 files changed, 76 insertions(+), 320 deletions(-) delete mode 100644 spiffworkflow-backend/tests/data/script_add_permission/add_permission.bpmn delete mode 100644 spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_permission.py delete mode 100644 spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_user_to_group.py delete mode 100644 spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_delete_permissions.py diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_all_permissions.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_all_permissions.py index 83a7e582f..7cdcf3601 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_all_permissions.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/get_all_permissions.py @@ -12,9 +12,6 @@ from spiffworkflow_backend.models.script_attributes_context import ( from spiffworkflow_backend.scripts.script import Script -# add_permission("read", "test/*", "Editors") - - class GetAllPermissions(Script): """GetAllPermissions.""" diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/refresh_permissions.py b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/refresh_permissions.py index b7e46dd68..8c97fe60d 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/scripts/refresh_permissions.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/scripts/refresh_permissions.py @@ -7,10 +7,9 @@ from spiffworkflow_backend.models.script_attributes_context import ( from spiffworkflow_backend.scripts.script import Script from spiffworkflow_backend.services.authorization_service import AuthorizationService -# add_permission("read", "test/*", "Editors") - class RecreatePermissions(Script): + """RecreatePermissions.""" def get_description(self) -> str: """Get_description.""" diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py index 6ab240d0a..cd125ee53 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/authorization_service.py @@ -1,13 +1,14 @@ """Authorization_service.""" import inspect -from typing import TypedDict -from typing import Any, Set import re from dataclasses import dataclass from hashlib import sha256 from hmac import compare_digest from hmac import HMAC +from typing import Any from typing import Optional +from typing import Set +from typing import TypedDict from typing import Union import jwt @@ -23,7 +24,6 @@ from sqlalchemy import or_ from sqlalchemy import text from spiffworkflow_backend.helpers.api_version import V1_API_PATH_PREFIX -from spiffworkflow_backend.models import permission_assignment from spiffworkflow_backend.models.group import GroupModel from spiffworkflow_backend.models.human_task import HumanTaskModel from spiffworkflow_backend.models.permission_assignment import PermissionAssignmentModel @@ -72,6 +72,8 @@ PATH_SEGMENTS_FOR_PERMISSION_ALL = [ class DesiredPermissionDict(TypedDict): + """DesiredPermissionDict.""" + group_identifiers: Set[str] permission_assignments: list[PermissionAssignmentModel] @@ -236,9 +238,13 @@ class AuthorizationService: for group_identifier in permission_config["groups"]: group = GroupService.find_or_create_group(group_identifier) unique_user_group_identifiers.add(group_identifier) - permission_assignments.append(cls.create_permission_for_principal( - group.principal, permission_target, allowed_permission - )) + permission_assignments.append( + cls.create_permission_for_principal( + group.principal, + permission_target, + allowed_permission, + ) + ) if "users" in permission_config: for username in permission_config["users"]: user = UserModel.query.filter_by(username=username).first() @@ -248,15 +254,20 @@ class AuthorizationService: .filter(UserModel.username == username) .first() ) - permission_assignments.append(cls.create_permission_for_principal( - principal, permission_target, allowed_permission - )) + permission_assignments.append( + cls.create_permission_for_principal( + principal, permission_target, allowed_permission + ) + ) if default_group is not None: for user in UserModel.query.all(): cls.associate_user_with_group(user, default_group) - return { 'group_identifiers': unique_user_group_identifiers, 'permission_assignments': permission_assignments } + return { + "group_identifiers": unique_user_group_identifiers, + "permission_assignments": permission_assignments, + } @classmethod def find_or_create_permission_target(cls, uri: str) -> PermissionTargetModel: @@ -715,9 +726,11 @@ class AuthorizationService: permission_target = cls.find_or_create_permission_target( permission_to_assign.target_uri ) - permission_assignments.append(cls.create_permission_for_principal( - group.principal, permission_target, permission_to_assign.permission - )) + permission_assignments.append( + cls.create_permission_for_principal( + group.principal, permission_target, permission_to_assign.permission + ) + ) return permission_assignments @classmethod @@ -725,24 +738,32 @@ class AuthorizationService: """Adds new permission assignments and deletes old ones.""" initial_permission_assignments = PermissionAssignmentModel.query.all() result = cls.import_permissions_from_yaml_file() - desired_permission_assignments = result['permission_assignments'] - desired_group_identifiers = result['group_identifiers'] + desired_permission_assignments = result["permission_assignments"] + desired_group_identifiers = result["group_identifiers"] for group in group_info: - for username in group['users']: - GroupService.add_user_to_group_or_add_to_waiting(username, group['name']) - for permission in group['permissions']: - for crud_op in permission['actions']: - desired_permission_assignments.extend(cls.add_permission_from_uri_or_macro( - group_identifier=group['name'], target=permission['uri'], permission=crud_op - )) - desired_group_identifiers.add(group['name']) + for username in group["users"]: + GroupService.add_user_to_group_or_add_to_waiting( + username, group["name"] + ) + for permission in group["permissions"]: + for crud_op in permission["actions"]: + desired_permission_assignments.extend( + cls.add_permission_from_uri_or_macro( + group_identifier=group["name"], + target=permission["uri"], + permission=crud_op, + ) + ) + desired_group_identifiers.add(group["name"]) for ipa in initial_permission_assignments: if ipa not in desired_permission_assignments: db.session.delete(ipa) - groups_to_delete = GroupModel.query.filter(GroupModel.identifier.not_in(desired_group_identifiers)).all() + groups_to_delete = GroupModel.query.filter( + GroupModel.identifier.not_in(desired_group_identifiers) + ).all() for gtd in groups_to_delete: db.session.delete(gtd) db.session.commit() diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/group_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/group_service.py index 85f6441ff..911d41ac4 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/group_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/group_service.py @@ -1,10 +1,10 @@ """Group_service.""" from typing import Optional -from spiffworkflow_backend.models.user import UserModel from flask_bpmn.models.db import db from spiffworkflow_backend.models.group import GroupModel +from spiffworkflow_backend.models.user import UserModel from spiffworkflow_backend.services.user_service import UserService @@ -25,7 +25,10 @@ class GroupService: return group @classmethod - def add_user_to_group_or_add_to_waiting(cls, username: str, group_identifier: str) -> None: + def add_user_to_group_or_add_to_waiting( + cls, username: str, group_identifier: str + ) -> None: + """Add_user_to_group_or_add_to_waiting.""" group = cls.find_or_create_group(group_identifier) user = UserModel.query.filter_by(username=username).first() if user: diff --git a/spiffworkflow-backend/tests/data/script_add_permission/add_permission.bpmn b/spiffworkflow-backend/tests/data/script_add_permission/add_permission.bpmn deleted file mode 100644 index 50a56b1f6..000000000 --- a/spiffworkflow-backend/tests/data/script_add_permission/add_permission.bpmn +++ /dev/null @@ -1,39 +0,0 @@ - - - - - Flow_01cweoc - - - - Flow_1xle2yo - - - - Flow_01cweoc - Flow_1xle2yo - add_permission('read', '/test_permission_uri', "test_group") - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_permission.py b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_permission.py deleted file mode 100644 index 45ff4b253..000000000 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_permission.py +++ /dev/null @@ -1,100 +0,0 @@ -"""Test_get_localtime.""" -import pytest -from flask.app import Flask -from flask.testing import FlaskClient -from flask_bpmn.api.api_error import ApiError -from tests.spiffworkflow_backend.helpers.base_test import BaseTest -from tests.spiffworkflow_backend.helpers.test_data import load_test_spec - -from spiffworkflow_backend.models.group import GroupModel -from spiffworkflow_backend.models.permission_assignment import PermissionAssignmentModel -from spiffworkflow_backend.models.permission_target import PermissionTargetModel -from spiffworkflow_backend.models.script_attributes_context import ( - ScriptAttributesContext, -) -from spiffworkflow_backend.models.user import UserModel -from spiffworkflow_backend.scripts.add_permission import AddPermission -from spiffworkflow_backend.services.process_instance_processor import ( - ProcessInstanceProcessor, -) - - -class TestAddPermission(BaseTest): - """TestAddPermission.""" - - def test_can_add_permission( - self, - app: Flask, - client: FlaskClient, - with_db_and_bpmn_file_cleanup: None, - with_super_admin_user: UserModel, - ) -> None: - """Test_can_get_members_of_a_group.""" - self.find_or_create_user("test_user") - - # now that we have everything, try to clear it out... - script_attributes_context = ScriptAttributesContext( - task=None, - environment_identifier="testing", - process_instance_id=1, - process_model_identifier="my_test_user", - ) - - group = GroupModel.query.filter( - GroupModel.identifier == "my_test_group" - ).first() - permission_target = PermissionTargetModel.query.filter( - PermissionTargetModel.uri == "/test_add_permission/%" - ).first() - assert group is None - assert permission_target is None - - AddPermission().run( - script_attributes_context, "read", "/test_add_permission/*", "my_test_group" - ) - group = GroupModel.query.filter( - GroupModel.identifier == "my_test_group" - ).first() - permission_target = PermissionTargetModel.query.filter( - PermissionTargetModel.uri == "/test_add_permission/%" - ).first() - permission_assignments = PermissionAssignmentModel.query.filter( - PermissionAssignmentModel.principal_id == group.principal.id - ).all() - assert group is not None - assert permission_target is not None - assert len(permission_assignments) == 1 - - def test_add_permission_script_through_bpmn( - self, - app: Flask, - client: FlaskClient, - with_db_and_bpmn_file_cleanup: None, - ) -> None: - """Test_add_permission_script_through_bpmn.""" - basic_user = self.find_or_create_user("basic_user") - privileged_user = self.find_or_create_user("privileged_user") - self.add_permissions_to_user( - privileged_user, - target_uri="/can-run-privileged-script/add_permission", - permission_names=["create"], - ) - process_model = load_test_spec( - process_model_id="add_permission", - process_model_source_directory="script_add_permission", - ) - process_instance = self.create_process_instance_from_process_model( - process_model=process_model, user=basic_user - ) - processor = ProcessInstanceProcessor(process_instance) - - with pytest.raises(ApiError) as exception: - processor.do_engine_steps(save=True) - assert "ScriptUnauthorizedForUserError" in str(exception) - - process_instance = self.create_process_instance_from_process_model( - process_model=process_model, user=privileged_user - ) - processor = ProcessInstanceProcessor(process_instance) - processor.do_engine_steps(save=True) - assert process_instance.status == "complete" diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_user_to_group.py b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_user_to_group.py deleted file mode 100644 index 2e665a16e..000000000 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_add_user_to_group.py +++ /dev/null @@ -1,69 +0,0 @@ -"""Test_get_localtime.""" -from flask.app import Flask -from flask.testing import FlaskClient -from flask_bpmn.models.db import db -from tests.spiffworkflow_backend.helpers.base_test import BaseTest - -from spiffworkflow_backend.models.group import GroupModel -from spiffworkflow_backend.models.script_attributes_context import ( - ScriptAttributesContext, -) -from spiffworkflow_backend.models.user import UserModel -from spiffworkflow_backend.models.user_group_assignment_waiting import ( - UserGroupAssignmentWaitingModel, -) -from spiffworkflow_backend.scripts.add_user_to_group import AddUserToGroup - - -class TestAddUserToGroup(BaseTest): - """TestGetGroupMembers.""" - - def test_can_add_existing_user_to_existing_group( - self, - app: Flask, - client: FlaskClient, - with_db_and_bpmn_file_cleanup: None, - with_super_admin_user: UserModel, - ) -> None: - """Test_can_get_members_of_a_group.""" - my_user = self.find_or_create_user("my_user") - my_group = GroupModel(identifier="my_group") - db.session.add(my_group) - script_attributes_context = ScriptAttributesContext( - task=None, - environment_identifier="testing", - process_instance_id=1, - process_model_identifier="my_test_user", - ) - AddUserToGroup().run( - script_attributes_context, my_user.username, my_group.identifier - ) - assert my_user in my_group.users - - def test_can_add_non_existent_user_to_non_existent_group( - self, - app: Flask, - client: FlaskClient, - with_db_and_bpmn_file_cleanup: None, - with_super_admin_user: UserModel, - ) -> None: - """Test_can_add_non_existent_user_to_non_existent_group.""" - script_attributes_context = ScriptAttributesContext( - task=None, - environment_identifier="testing", - process_instance_id=1, - process_model_identifier="my_test_user", - ) - AddUserToGroup().run( - script_attributes_context, "dan@sartography.com", "competent-joes" - ) - my_group = GroupModel.query.filter( - GroupModel.identifier == "competent-joes" - ).first() - assert my_group is not None - waiting_assignments = ( - UserGroupAssignmentWaitingModel() - .query.filter_by(username="dan@sartography.com") - .first() - ) - assert waiting_assignments is not None diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_delete_permissions.py b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_delete_permissions.py deleted file mode 100644 index 41d6c0157..000000000 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_delete_permissions.py +++ /dev/null @@ -1,59 +0,0 @@ -"""Test_get_localtime.""" -from flask.app import Flask -from flask.testing import FlaskClient -from flask_bpmn.models.db import db -from tests.spiffworkflow_backend.helpers.base_test import BaseTest - -from spiffworkflow_backend.models.group import GroupModel -from spiffworkflow_backend.models.permission_target import PermissionTargetModel -from spiffworkflow_backend.models.script_attributes_context import ( - ScriptAttributesContext, -) -from spiffworkflow_backend.models.user import UserModel -from spiffworkflow_backend.scripts.clear_permissions import ClearPermissions -from spiffworkflow_backend.services.authorization_service import AuthorizationService -from spiffworkflow_backend.services.group_service import GroupService -from spiffworkflow_backend.services.user_service import UserService - - -class TestDeletePermissions(BaseTest): - """TestGetGroupMembers.""" - - def test_can_delete_members( - self, - app: Flask, - client: FlaskClient, - with_db_and_bpmn_file_cleanup: None, - with_super_admin_user: UserModel, - ) -> None: - """Test_can_get_members_of_a_group.""" - self.find_or_create_user("initiator_user") - testuser1 = self.find_or_create_user("testuser1") - testuser2 = self.find_or_create_user("testuser2") - testuser3 = self.find_or_create_user("testuser3") - group_a = GroupService.find_or_create_group("groupA") - group_b = GroupService.find_or_create_group("groupB") - db.session.add(group_a) - db.session.add(group_b) - db.session.commit() - UserService.add_user_to_group(testuser1, group_a) - UserService.add_user_to_group(testuser2, group_a) - UserService.add_user_to_group(testuser3, group_b) - - target = PermissionTargetModel("test/*") - db.session.add(target) - db.session.commit() - AuthorizationService.create_permission_for_principal( - group_a.principal, target, "read" - ) - # now that we have everything, try to clear it out... - script_attributes_context = ScriptAttributesContext( - task=None, - environment_identifier="testing", - process_instance_id=1, - process_model_identifier="my_test_user", - ) - ClearPermissions().run(script_attributes_context) - - groups = GroupModel.query.all() - assert 0 == len(groups) diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_get_all_permissions.py b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_get_all_permissions.py index 9f1594999..3c3bce506 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_get_all_permissions.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/scripts/test_get_all_permissions.py @@ -7,8 +7,8 @@ from spiffworkflow_backend.models.script_attributes_context import ( ScriptAttributesContext, ) from spiffworkflow_backend.models.user import UserModel -from spiffworkflow_backend.scripts.add_permission import AddPermission from spiffworkflow_backend.scripts.get_all_permissions import GetAllPermissions +from spiffworkflow_backend.services.authorization_service import AuthorizationService class TestGetAllPermissions(BaseTest): @@ -31,10 +31,12 @@ class TestGetAllPermissions(BaseTest): process_instance_id=1, process_model_identifier="my_test_user", ) - AddPermission().run( - script_attributes_context, "start", "PG:hey:group", "my_test_group" + AuthorizationService.add_permission_from_uri_or_macro( + permission="start", target="PG:hey:group", group_identifier="my_test_group" + ) + AuthorizationService.add_permission_from_uri_or_macro( + permission="all", target="/tasks", group_identifier="my_test_group" ) - AddPermission().run(script_attributes_context, "all", "/tasks", "my_test_group") expected_permissions = [ { diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py index 7d000c9db..a0c140f14 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_authorization_service.py @@ -1,10 +1,10 @@ """Test_message_service.""" import pytest -from spiffworkflow_backend.models.group import GroupModel from flask import Flask from flask.testing import FlaskClient from tests.spiffworkflow_backend.helpers.base_test import BaseTest +from spiffworkflow_backend.models.group import GroupModel from spiffworkflow_backend.models.user import UserModel from spiffworkflow_backend.models.user import UserNotFoundError from spiffworkflow_backend.services.authorization_service import AuthorizationService @@ -436,6 +436,7 @@ class TestAuthorizationService(BaseTest): client: FlaskClient, with_db_and_bpmn_file_cleanup: None, ) -> None: + """Test_can_refresh_permissions.""" user = self.find_or_create_user(username="user_one") admin_user = self.find_or_create_user(username="testadmin1") @@ -443,14 +444,13 @@ class TestAuthorizationService(BaseTest): GroupService.find_or_create_group("group_two") assert GroupModel.query.filter_by(identifier="group_two").first() is not None - group_info = [{ - 'users': ['user_one'], - 'name': 'group_one', - 'permissions': [{ - 'actions': ['create', 'read'], - 'uri': 'PG:hey' - }] - }] + group_info = [ + { + "users": ["user_one"], + "name": "group_one", + "permissions": [{"actions": ["create", "read"], "uri": "PG:hey"}], + } + ] AuthorizationService.refresh_permissions(group_info) assert GroupModel.query.filter_by(identifier="group_two").first() is None assert GroupModel.query.filter_by(identifier="group_one").first() is not None @@ -459,17 +459,18 @@ class TestAuthorizationService(BaseTest): self.assert_user_has_permission(user, "read", "/v1.0/process-groups/hey:yo") self.assert_user_has_permission(user, "create", "/v1.0/process-groups/hey:yo") - group_info = [{ - 'users': ['user_one'], - 'name': 'group_one', - 'permissions': [{ - 'actions': ['read'], - 'uri': 'PG:hey' - }] - }] + group_info = [ + { + "users": ["user_one"], + "name": "group_one", + "permissions": [{"actions": ["read"], "uri": "PG:hey"}], + } + ] AuthorizationService.refresh_permissions(group_info) assert GroupModel.query.filter_by(identifier="group_one").first() is not None self.assert_user_has_permission(user, "read", "/v1.0/process-groups/hey") self.assert_user_has_permission(user, "read", "/v1.0/process-groups/hey:yo") - self.assert_user_has_permission(user, "create", "/v1.0/process-groups/hey:yo", expected_result=False) + self.assert_user_has_permission( + user, "create", "/v1.0/process-groups/hey:yo", expected_result=False + ) self.assert_user_has_permission(admin_user, "create", "/anything-they-want") From 46dbb5e187126e539d3a7389c2c010a5913f0f2e Mon Sep 17 00:00:00 2001 From: jasquat Date: Thu, 22 Dec 2022 16:55:19 -0500 Subject: [PATCH 29/29] updated permission yamls to have email addresses and look more the same w/ burnettk --- .../config/permissions/development.yml | 147 ++++++++------- .../config/permissions/staging.yml | 173 ++++++++---------- .../terraform_deployed_environment.yml | 66 +++---- 3 files changed, 179 insertions(+), 207 deletions(-) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml index d192a7dea..1bbefdaf4 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/development.yml @@ -10,64 +10,59 @@ groups: admin: users: [ - admin, - jakub, - kb, - alex, - dan, - mike, + admin@status.im, + jakub@status.im, + jarrad@status.im, + kb@sartography.com, + alex@sartography.com, + dan@sartography.com, + mike@sartography.com, jason@sartography.com, - jarrad, - elizabeth, - jon, + j@sartography.com, + elizabeth@sartography.com, + jon@sartography.com, ] Finance Team: users: [ - jakub, - alex, - dan, - mike, + jakub@status.im, + amir@status.im, + jarrad@status.im, + sasha@status.im, + fin@sartography.com, + fin1@sartography.com, + alex@sartography.com, + dan@sartography.com, + mike@sartography.com, jason@sartography.com, - amir, - jarrad, - elizabeth, - jon, - sasha, - fin, - fin1, + j@sartography.com, + elizabeth@sartography.com, + jon@sartography.com, ] demo: users: [ - core, - fin, - fin1, - harmeet, - jason@sartography.com, - sasha, - manuchehr, - lead, - lead1 + harmeet@status.im, + sasha@status.im, + manuchehr@status.im, + core@status.im, + fin@status.im, + fin1@status.im, + lead@status.im, + lead1@status.im ] - core-contributor: - users: - [ - core, - harmeet, - ] + test: + users: [natalia@sartography.com] + admin-ro: users: [ j, ] - test: - users: [natalia] - permissions: admin: groups: [admin] @@ -85,23 +80,7 @@ permissions: allowed_permissions: [create, read, update, delete] uri: /process-instances/* - tasks-crud: - groups: [everybody] - users: [] - allowed_permissions: [create, read, update, delete] - uri: /tasks/* - service-tasks: - groups: [everybody] - users: [] - allowed_permissions: [read] - uri: /service-tasks - user-groups-for-current-user: - groups: [everybody] - users: [] - allowed_permissions: [read] - uri: /user-groups/for-current-user - - # read all for everybody + # open system defaults for everybody read-all-process-groups: groups: [everybody] users: [] @@ -112,6 +91,8 @@ permissions: users: [] allowed_permissions: [read] uri: /process-models/* + + # basic perms for everybody read-all-process-instances-for-me: groups: [everybody] users: [] @@ -127,6 +108,21 @@ permissions: users: [] allowed_permissions: [read] uri: /processes + service-tasks: + groups: [everybody] + users: [] + allowed_permissions: [read] + uri: /service-tasks + tasks-crud: + groups: [everybody] + users: [] + allowed_permissions: [create, read, update, delete] + uri: /tasks/* + user-groups-for-current-user: + groups: [everybody] + users: [] + allowed_permissions: [read] + uri: /user-groups/for-current-user finance-admin: @@ -136,36 +132,39 @@ permissions: uri: /process-groups/manage-procurement:procurement:* manage-revenue-streams-instances: - groups: ["core-contributor", "demo"] + groups: ["demo"] users: [] - allowed_permissions: [create, read] + allowed_permissions: [create] uri: /process-instances/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/* - manage-procurement-invoice-instances: - groups: ["core-contributor", "demo"] + groups: ["demo"] users: [] - allowed_permissions: [create, read] + allowed_permissions: [create] uri: /process-instances/manage-procurement:procurement:core-contributor-invoice-management:* - manage-procurement-instances: - groups: ["core-contributor", "demo"] + groups: ["demo"] users: [] - allowed_permissions: [create, read] + allowed_permissions: [create] uri: /process-instances/manage-procurement:vendor-lifecycle-management:* + manage-revenue-streams-instances-for-me: + groups: ["demo"] + users: [] + allowed_permissions: [read] + uri: /process-instances/for-me/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/* + manage-procurement-invoice-instances-for-me: + groups: ["demo"] + users: [] + allowed_permissions: [read] + uri: /process-instances/for-me/manage-procurement:procurement:core-contributor-invoice-management:* + manage-procurement-instances-for-me: + groups: ["demo"] + users: [] + allowed_permissions: [read] + uri: /process-instances/for-me/manage-procurement:vendor-lifecycle-management:* + create-test-instances: groups: ["test"] users: [] allowed_permissions: [create, read] uri: /process-instances/misc:test:* - - core1-admin-instances: - groups: ["core-contributor", "Finance Team"] - users: [] - allowed_permissions: [create, read] - 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: /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 1b8b21833..e1be926fb 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/staging.yml +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/permissions/staging.yml @@ -4,57 +4,48 @@ groups: admin: users: [ - admin, - jakub, - kb, - alex, - dan, - mike, - jason, - j, - jarrad, - elizabeth, - jon, - natalia, + admin@status.im, + jakub@status.im, + jarrad@status.im, + kb@sartography.com, + alex@sartography.com, + dan@sartography.com, + mike@sartography.com, + jason@sartography.com, + j@sartography.com, + elizabeth@sartography.com, + jon@sartography.com, ] Finance Team: users: [ - jakub, - alex, - dan, - mike, - jason, - j, - amir, - jarrad, - elizabeth, - jon, - natalia, - sasha, - fin, - fin1, + jakub@status.im, + amir@status.im, + jarrad@status.im, + sasha@status.im, + fin@sartography.com, + fin1@sartography.com, + alex@sartography.com, + dan@sartography.com, + mike@sartography.com, + jason@sartography.com, + j@sartography.com, + elizabeth@sartography.com, + jon@sartography.com, ] demo: users: [ - core, - fin, - fin1, - harmeet, - sasha, - manuchehr, - lead, - lead1 - ] - - core-contributor: - users: - [ - core, - harmeet, + harmeet@status.im, + sasha@status.im, + manuchehr@status.im, + core@status.im, + fin@status.im, + fin1@status.im, + lead@status.im, + lead1@status.im ] permissions: @@ -69,25 +60,7 @@ permissions: allowed_permissions: [create, read, update, delete] uri: /process-instances/* - tasks-crud: - groups: [everybody] - users: [] - allowed_permissions: [create, read, update, delete] - uri: /tasks/* - - service-tasks: - groups: [everybody] - users: [] - allowed_permissions: [read] - uri: /service-tasks - user-groups-for-current-user: - groups: [everybody] - users: [] - allowed_permissions: [read] - uri: /user-groups/for-current-user - - - # read all for everybody + # open system defaults for everybody read-all-process-groups: groups: [everybody] users: [] @@ -98,12 +71,14 @@ permissions: users: [] allowed_permissions: [read] uri: /process-models/* + + # basic perms for everybody read-all-process-instances-for-me: groups: [everybody] users: [] allowed_permissions: [read] uri: /process-instances/for-me/* - manage-process-instance-reports: + read-process-instance-reports: groups: [everybody] users: [] allowed_permissions: [create, read, update, delete] @@ -113,58 +88,56 @@ permissions: users: [] allowed_permissions: [read] uri: /processes - - - manage-procurement-admin-instances: - groups: ["Project Lead"] - users: [] - allowed_permissions: [create, read, update, delete] - uri: /process-instances/manage-procurement:* - manage-procurement-admin-instances-slash: - groups: ["Project Lead"] - users: [] - allowed_permissions: [create, read, update, delete] - uri: /process-instances/manage-procurement/* - manage-procurement-admin-instance-logs: - groups: ["Project Lead"] + service-tasks: + groups: [everybody] users: [] allowed_permissions: [read] - uri: /logs/manage-procurement:* - manage-procurement-admin-instance-logs-slash: - groups: ["Project Lead"] + uri: /service-tasks + tasks-crud: + groups: [everybody] + users: [] + allowed_permissions: [create, read, update, delete] + uri: /tasks/* + user-groups-for-current-user: + groups: [everybody] users: [] allowed_permissions: [read] - uri: /logs/manage-procurement/* + uri: /user-groups/for-current-user manage-revenue-streams-instances: - groups: ["core-contributor", "demo"] + groups: ["demo"] users: [] - allowed_permissions: [create, read] + allowed_permissions: [create] 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: /logs/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/* - manage-procurement-invoice-instances: - groups: ["core-contributor", "demo"] + groups: ["demo"] users: [] - allowed_permissions: [create, read] + allowed_permissions: [create] uri: /process-instances/manage-procurement:procurement:core-contributor-invoice-management:* - manage-procurement-invoice-instance-logs: - groups: ["core-contributor", "demo"] + manage-procurement-instances: + groups: ["demo"] + users: [] + allowed_permissions: [create] + uri: /process-instances/manage-procurement:vendor-lifecycle-management:* + + manage-revenue-streams-instances-for-me: + groups: ["demo"] users: [] allowed_permissions: [read] - uri: /logs/manage-procurement:procurement:core-contributor-invoice-management:* + uri: /process-instances/for-me/manage-revenue-streams:product-revenue-streams:customer-contracts-trade-terms/* + manage-procurement-invoice-instances-for-me: + groups: ["demo"] + users: [] + allowed_permissions: [read] + uri: /process-instances/for-me/manage-procurement:procurement:core-contributor-invoice-management:* + manage-procurement-instances-for-me: + groups: ["demo"] + users: [] + allowed_permissions: [read] + uri: /process-instances/for-me/manage-procurement:vendor-lifecycle-management:* - manage-procurement-instances: - groups: ["core-contributor", "demo"] + create-test-instances: + groups: ["test"] users: [] allowed_permissions: [create, read] - uri: /process-instances/manage-procurement:vendor-lifecycle-management:* - manage-procurement-instance-logs: - groups: ["core-contributor", "demo"] - users: [] - allowed_permissions: [read] - uri: /logs/manage-procurement:vendor-lifecycle-management:* + uri: /process-instances/misc:test:* 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 37b8ca36a..083b529d1 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 @@ -4,52 +4,52 @@ groups: admin: users: [ - admin, - jakub, - kb, - alex, - dan, - mike, - jason, - j, - jarrad, - elizabeth, - jon, + admin@status.im, + jakub@status.im, + jarrad@status.im, + kb@sartography.com, + alex@sartography.com, + dan@sartography.com, + mike@sartography.com, + jason@sartography.com, + j@sartography.com, + elizabeth@sartography.com, + jon@sartography.com, ] Finance Team: users: [ - jakub, - alex, - dan, - mike, - jason, - j, - amir, - jarrad, - elizabeth, - jon, - sasha, - fin, - fin1, + jakub@status.im, + amir@status.im, + jarrad@status.im, + sasha@status.im, + fin@sartography.com, + fin1@sartography.com, + alex@sartography.com, + dan@sartography.com, + mike@sartography.com, + jason@sartography.com, + j@sartography.com, + elizabeth@sartography.com, + jon@sartography.com, ] demo: users: [ - core, - fin, - fin1, - harmeet, - sasha, - manuchehr, - lead, - lead1 + harmeet@status.im, + sasha@status.im, + manuchehr@status.im, + core@status.im, + fin@status.im, + fin1@status.im, + lead@status.im, + lead1@status.im ] test: - users: [natalia] + users: [natalia@sartography.com] permissions: admin: