Merge pull request #266 from sartography/feature/support_macros_in_permission_yaml
Feature/support macros in permission yaml
This commit is contained in:
commit
9ff0169fd3
|
@ -1122,7 +1122,7 @@ paths:
|
||||||
- Process Instances
|
- Process Instances
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: Empty ok true response on successful resume.
|
description: Empty ok true response on successful reset.
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
|
|
|
@ -143,6 +143,7 @@ SPIFFWORKFLOW_BACKEND_ALLOW_CONFISCATING_LOCK_AFTER_SECONDS = int(
|
||||||
environ.get("SPIFFWORKFLOW_BACKEND_ALLOW_CONFISCATING_LOCK_AFTER_SECONDS", default="600")
|
environ.get("SPIFFWORKFLOW_BACKEND_ALLOW_CONFISCATING_LOCK_AFTER_SECONDS", default="600")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# FIXME: do not default this but we will need to coordinate release of it since it is a breaking change
|
||||||
SPIFFWORKFLOW_BACKEND_DEFAULT_USER_GROUP = environ.get("SPIFFWORKFLOW_BACKEND_DEFAULT_USER_GROUP", default="everybody")
|
SPIFFWORKFLOW_BACKEND_DEFAULT_USER_GROUP = environ.get("SPIFFWORKFLOW_BACKEND_DEFAULT_USER_GROUP", default="everybody")
|
||||||
|
|
||||||
SPIFFWORKFLOW_BACKEND_ENGINE_STEP_DEFAULT_STRATEGY_BACKGROUND = environ.get(
|
SPIFFWORKFLOW_BACKEND_ENGINE_STEP_DEFAULT_STRATEGY_BACKGROUND = environ.get(
|
||||||
|
|
|
@ -12,6 +12,5 @@ groups:
|
||||||
permissions:
|
permissions:
|
||||||
admin:
|
admin:
|
||||||
groups: [admin]
|
groups: [admin]
|
||||||
users: []
|
|
||||||
allowed_permissions: [create, read, update, delete]
|
allowed_permissions: [create, read, update, delete]
|
||||||
uri: /*
|
uri: /*
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
default_group: everybody
|
|
||||||
|
|
||||||
groups:
|
groups:
|
||||||
admin:
|
admin:
|
||||||
users:
|
users:
|
||||||
|
@ -19,6 +17,5 @@ groups:
|
||||||
permissions:
|
permissions:
|
||||||
admin:
|
admin:
|
||||||
groups: [admin, tech_writers]
|
groups: [admin, tech_writers]
|
||||||
users: []
|
|
||||||
allowed_permissions: [create, read, update, delete]
|
allowed_permissions: [create, read, update, delete]
|
||||||
uri: /*
|
uri: /*
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
default_group: everybody
|
|
||||||
|
|
||||||
users:
|
users:
|
||||||
admin:
|
admin:
|
||||||
|
@ -41,52 +40,43 @@ permissions:
|
||||||
# Admins have access to everything.
|
# Admins have access to everything.
|
||||||
admin:
|
admin:
|
||||||
groups: [admin]
|
groups: [admin]
|
||||||
users: []
|
|
||||||
allowed_permissions: [create, read, update, delete]
|
allowed_permissions: [create, read, update, delete]
|
||||||
uri: /*
|
uri: /*
|
||||||
|
|
||||||
# Everybody can participate in tasks assigned to them.
|
# Everybody can participate in tasks assigned to them.
|
||||||
tasks-crud:
|
tasks-crud:
|
||||||
groups: [everybody]
|
groups: [everybody]
|
||||||
users: []
|
|
||||||
allowed_permissions: [create, read, update, delete]
|
allowed_permissions: [create, read, update, delete]
|
||||||
uri: /tasks/*
|
uri: /tasks/*
|
||||||
|
|
||||||
# Everybody can start all intstances
|
# Everybody can start all intstances
|
||||||
create-test-instances:
|
create-test-instances:
|
||||||
groups: [ everybody ]
|
groups: [ everybody ]
|
||||||
users: [ ]
|
|
||||||
allowed_permissions: [ create ]
|
allowed_permissions: [ create ]
|
||||||
uri: /process-instances/*
|
uri: /process-instances/*
|
||||||
|
|
||||||
# Everyone can see everything (all groups, and processes are visible)
|
# Everyone can see everything (all groups, and processes are visible)
|
||||||
read-all-process-groups:
|
read-all-process-groups:
|
||||||
groups: [ everybody ]
|
groups: [ everybody ]
|
||||||
users: [ ]
|
|
||||||
allowed_permissions: [ read ]
|
allowed_permissions: [ read ]
|
||||||
uri: /process-groups/*
|
uri: /process-groups/*
|
||||||
read-all-process-models:
|
read-all-process-models:
|
||||||
groups: [ everybody ]
|
groups: [ everybody ]
|
||||||
users: [ ]
|
|
||||||
allowed_permissions: [ read ]
|
allowed_permissions: [ read ]
|
||||||
uri: /process-models/*
|
uri: /process-models/*
|
||||||
read-all-process-instance:
|
read-all-process-instance:
|
||||||
groups: [ everybody ]
|
groups: [ everybody ]
|
||||||
users: [ ]
|
|
||||||
allowed_permissions: [ read ]
|
allowed_permissions: [ read ]
|
||||||
uri: /process-instances/*
|
uri: /process-instances/*
|
||||||
read-process-instance-reports:
|
read-process-instance-reports:
|
||||||
groups: [ everybody ]
|
groups: [ everybody ]
|
||||||
users: [ ]
|
|
||||||
allowed_permissions: [ read ]
|
allowed_permissions: [ read ]
|
||||||
uri: /process-instances/reports/*
|
uri: /process-instances/reports/*
|
||||||
processes-read:
|
processes-read:
|
||||||
groups: [ everybody ]
|
groups: [ everybody ]
|
||||||
users: [ ]
|
|
||||||
allowed_permissions: [ read ]
|
allowed_permissions: [ read ]
|
||||||
uri: /processes
|
uri: /processes
|
||||||
groups-everybody:
|
groups-everybody:
|
||||||
groups: [everybody]
|
groups: [everybody]
|
||||||
users: []
|
|
||||||
allowed_permissions: [create, read]
|
allowed_permissions: [create, read]
|
||||||
uri: /v1.0/user-groups/for-current-user
|
uri: /v1.0/user-groups/for-current-user
|
||||||
|
|
|
@ -1,84 +1,17 @@
|
||||||
default_group: everybody
|
|
||||||
|
|
||||||
groups:
|
groups:
|
||||||
admin:
|
admin:
|
||||||
users: [admin@spiffworkflow.org]
|
users: [admin@spiffworkflow.org]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
admin:
|
process-groups-ro:
|
||||||
groups: [admin]
|
groups: [admin]
|
||||||
users: []
|
|
||||||
allowed_permissions: [read]
|
allowed_permissions: [read]
|
||||||
uri: /*
|
uri: PG:ALL
|
||||||
|
basic:
|
||||||
tasks-crud:
|
|
||||||
groups: [admin]
|
groups: [admin]
|
||||||
users: []
|
allowed_permissions: [ALL]
|
||||||
allowed_permissions: [create, update, delete]
|
uri: BASIC
|
||||||
uri: /tasks/*
|
elevated-operations:
|
||||||
|
|
||||||
process-instances-crud:
|
|
||||||
groups: [ admin ]
|
|
||||||
users: [ ]
|
|
||||||
allowed_permissions: [create, update, delete]
|
|
||||||
uri: /process-instances/*
|
|
||||||
|
|
||||||
suspend:
|
|
||||||
groups: [admin]
|
groups: [admin]
|
||||||
users: []
|
allowed_permissions: [ALL]
|
||||||
allowed_permissions: [create]
|
uri: ELEVATED
|
||||||
uri: /v1.0/process-instance-suspend
|
|
||||||
|
|
||||||
terminate:
|
|
||||||
groups: [admin]
|
|
||||||
users: []
|
|
||||||
allowed_permissions: [create]
|
|
||||||
uri: /v1.0/process-instance-terminate
|
|
||||||
|
|
||||||
resume:
|
|
||||||
groups: [admin]
|
|
||||||
users: []
|
|
||||||
allowed_permissions: [create]
|
|
||||||
uri: /v1.0/process-instance-resume
|
|
||||||
|
|
||||||
reset:
|
|
||||||
groups: [admin]
|
|
||||||
users: []
|
|
||||||
allowed_permissions: [create]
|
|
||||||
uri: /v1.0/process-instance-reset
|
|
||||||
|
|
||||||
users-exist:
|
|
||||||
groups: [admin]
|
|
||||||
users: []
|
|
||||||
allowed_permissions: [create]
|
|
||||||
uri: /v1.0/users/exists/by-username
|
|
||||||
|
|
||||||
send-event:
|
|
||||||
groups: [admin]
|
|
||||||
users: []
|
|
||||||
allowed_permissions: [create]
|
|
||||||
uri: /v1.0/send-event/*
|
|
||||||
|
|
||||||
task-complete:
|
|
||||||
groups: [admin]
|
|
||||||
users: []
|
|
||||||
allowed_permissions: [create]
|
|
||||||
uri: /v1.0/task-complete/*
|
|
||||||
|
|
||||||
messages:
|
|
||||||
groups: [admin]
|
|
||||||
users: []
|
|
||||||
allowed_permissions: [create]
|
|
||||||
uri: /v1.0/messages/*
|
|
||||||
|
|
||||||
secrets:
|
|
||||||
groups: [admin]
|
|
||||||
users: []
|
|
||||||
allowed_permissions: [create, update, delete]
|
|
||||||
uri: /v1.0/secrets/*
|
|
||||||
|
|
||||||
task-data:
|
|
||||||
groups: [admin]
|
|
||||||
users: []
|
|
||||||
allowed_permissions: [update]
|
|
||||||
uri: /v1.0/task-data/*
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
default_group: everybody
|
|
||||||
|
|
||||||
groups:
|
groups:
|
||||||
admin:
|
admin:
|
||||||
|
@ -11,6 +10,5 @@ groups:
|
||||||
permissions:
|
permissions:
|
||||||
admin:
|
admin:
|
||||||
groups: [admin, group1, group2]
|
groups: [admin, group1, group2]
|
||||||
users: []
|
|
||||||
allowed_permissions: [create, read, update, delete]
|
allowed_permissions: [create, read, update, delete]
|
||||||
uri: /*
|
uri: /*
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
default_group: everybody
|
|
||||||
|
|
||||||
groups:
|
groups:
|
||||||
admin:
|
admin:
|
||||||
|
@ -7,6 +6,5 @@ groups:
|
||||||
permissions:
|
permissions:
|
||||||
admin:
|
admin:
|
||||||
groups: [admin]
|
groups: [admin]
|
||||||
users: []
|
|
||||||
allowed_permissions: [create, read, update, delete]
|
allowed_permissions: [create, read, update, delete]
|
||||||
uri: /*
|
uri: /*
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
default_group: everybody
|
|
||||||
|
|
||||||
users:
|
users:
|
||||||
testadmin1:
|
testadmin1:
|
||||||
service: https://testing/openid/thing
|
service: https://testing/openid/thing
|
||||||
|
@ -20,49 +18,40 @@ groups:
|
||||||
permissions:
|
permissions:
|
||||||
admin:
|
admin:
|
||||||
groups: [admin]
|
groups: [admin]
|
||||||
users: []
|
|
||||||
allowed_permissions: [create, read, update, delete]
|
allowed_permissions: [create, read, update, delete]
|
||||||
uri: /*
|
uri: /*
|
||||||
|
|
||||||
read-all:
|
read-all:
|
||||||
groups: ["Finance Team", hr, admin]
|
groups: ["Finance Team", hr, admin]
|
||||||
users: []
|
|
||||||
allowed_permissions: [read]
|
allowed_permissions: [read]
|
||||||
uri: /*
|
uri: /*
|
||||||
|
|
||||||
process-instances-find-by-id:
|
process-instances-find-by-id:
|
||||||
groups: [everybody]
|
groups: [everybody]
|
||||||
users: []
|
|
||||||
allowed_permissions: [read]
|
allowed_permissions: [read]
|
||||||
uri: /process-instances/find-by-id/*
|
uri: /process-instances/find-by-id/*
|
||||||
|
|
||||||
tasks-crud:
|
tasks-crud:
|
||||||
groups: [everybody]
|
groups: [everybody]
|
||||||
users: []
|
|
||||||
allowed_permissions: [create, read, update, delete]
|
allowed_permissions: [create, read, update, delete]
|
||||||
uri: /tasks/*
|
uri: /tasks/*
|
||||||
|
|
||||||
# TODO: all uris should really have the same structure
|
|
||||||
finance-admin-group:
|
finance-admin-group:
|
||||||
groups: ["Finance Team"]
|
groups: ["Finance Team"]
|
||||||
users: [testuser4]
|
allowed_permissions: [all]
|
||||||
allowed_permissions: [create, read, update, delete]
|
uri: PG:finance
|
||||||
uri: /process-groups/finance/*
|
|
||||||
|
|
||||||
finance-admin-model:
|
finance-hr-start:
|
||||||
groups: ["Finance Team"]
|
groups: ["hr"]
|
||||||
users: [testuser4]
|
allowed_permissions: [start]
|
||||||
allowed_permissions: [create, read, update, delete]
|
uri: PG:finance
|
||||||
uri: /process-models/finance/*
|
|
||||||
|
|
||||||
finance-admin-model-lanes:
|
finance-admin-model-lanes:
|
||||||
groups: ["Finance Team"]
|
groups: ["Finance Team"]
|
||||||
users: [testuser4]
|
|
||||||
allowed_permissions: [create, read, update, delete]
|
allowed_permissions: [create, read, update, delete]
|
||||||
uri: /process-models/finance:model_with_lanes/*
|
uri: /process-models/finance:model_with_lanes/*
|
||||||
|
|
||||||
finance-admin-instance-run:
|
finance-admin-instance-run:
|
||||||
groups: ["Finance Team"]
|
groups: ["Finance Team"]
|
||||||
users: [testuser4]
|
|
||||||
allowed_permissions: [create, read, update, delete]
|
allowed_permissions: [create, read, update, delete]
|
||||||
uri: /process-instances/*
|
uri: /process-instances/*
|
||||||
|
|
|
@ -34,6 +34,5 @@ class RefreshPermissions(Script):
|
||||||
*args: Any,
|
*args: Any,
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
"""Run."""
|
|
||||||
group_info = args[0]
|
group_info = args[0]
|
||||||
AuthorizationService.refresh_permissions(group_info)
|
AuthorizationService.refresh_permissions(group_info)
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
"""Authorization_service."""
|
|
||||||
import inspect
|
import inspect
|
||||||
import re
|
import re
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
from hmac import compare_digest
|
from hmac import compare_digest
|
||||||
from hmac import HMAC
|
from hmac import HMAC
|
||||||
from typing import Any
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from typing import Set
|
from typing import Set
|
||||||
from typing import TypedDict
|
from typing import TypedDict
|
||||||
|
@ -29,7 +27,6 @@ from spiffworkflow_backend.models.permission_target import PermissionTargetModel
|
||||||
from spiffworkflow_backend.models.principal import MissingPrincipalError
|
from spiffworkflow_backend.models.principal import MissingPrincipalError
|
||||||
from spiffworkflow_backend.models.principal import PrincipalModel
|
from spiffworkflow_backend.models.principal import PrincipalModel
|
||||||
from spiffworkflow_backend.models.user import UserModel
|
from spiffworkflow_backend.models.user import UserModel
|
||||||
from spiffworkflow_backend.models.user import UserNotFoundError
|
|
||||||
from spiffworkflow_backend.models.user_group_assignment import UserGroupAssignmentModel
|
from spiffworkflow_backend.models.user_group_assignment import UserGroupAssignmentModel
|
||||||
from spiffworkflow_backend.routes.openid_blueprint import openid_blueprint
|
from spiffworkflow_backend.routes.openid_blueprint import openid_blueprint
|
||||||
from spiffworkflow_backend.services.authentication_service import NotAuthorizedError
|
from spiffworkflow_backend.services.authentication_service import NotAuthorizedError
|
||||||
|
@ -42,25 +39,23 @@ from spiffworkflow_backend.services.user_service import UserService
|
||||||
|
|
||||||
|
|
||||||
class PermissionsFileNotSetError(Exception):
|
class PermissionsFileNotSetError(Exception):
|
||||||
"""PermissionsFileNotSetError."""
|
pass
|
||||||
|
|
||||||
|
|
||||||
class HumanTaskNotFoundError(Exception):
|
class HumanTaskNotFoundError(Exception):
|
||||||
"""HumanTaskNotFoundError."""
|
pass
|
||||||
|
|
||||||
|
|
||||||
class UserDoesNotHaveAccessToTaskError(Exception):
|
class UserDoesNotHaveAccessToTaskError(Exception):
|
||||||
"""UserDoesNotHaveAccessToTaskError."""
|
pass
|
||||||
|
|
||||||
|
|
||||||
class InvalidPermissionError(Exception):
|
class InvalidPermissionError(Exception):
|
||||||
"""InvalidPermissionError."""
|
pass
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PermissionToAssign:
|
class PermissionToAssign:
|
||||||
"""PermissionToAssign."""
|
|
||||||
|
|
||||||
permission: str
|
permission: str
|
||||||
target_uri: str
|
target_uri: str
|
||||||
|
|
||||||
|
@ -93,21 +88,29 @@ class UserToGroupDict(TypedDict):
|
||||||
group_identifier: str
|
group_identifier: str
|
||||||
|
|
||||||
|
|
||||||
class DesiredPermissionDict(TypedDict):
|
class AddedPermissionDict(TypedDict):
|
||||||
"""DesiredPermissionDict."""
|
|
||||||
|
|
||||||
group_identifiers: Set[str]
|
group_identifiers: Set[str]
|
||||||
permission_assignments: list[PermissionAssignmentModel]
|
permission_assignments: list[PermissionAssignmentModel]
|
||||||
user_to_group_identifiers: list[UserToGroupDict]
|
user_to_group_identifiers: list[UserToGroupDict]
|
||||||
|
|
||||||
|
|
||||||
|
class DesiredGroupPermissionDict(TypedDict):
|
||||||
|
actions: list[str]
|
||||||
|
uri: str
|
||||||
|
|
||||||
|
|
||||||
|
class GroupPermissionsDict(TypedDict):
|
||||||
|
users: list[str]
|
||||||
|
name: str
|
||||||
|
permissions: list[DesiredGroupPermissionDict]
|
||||||
|
|
||||||
|
|
||||||
class AuthorizationService:
|
class AuthorizationService:
|
||||||
"""Determine whether a user has permission to perform their request."""
|
"""Determine whether a user has permission to perform their request."""
|
||||||
|
|
||||||
# https://stackoverflow.com/a/71320673/6090676
|
# https://stackoverflow.com/a/71320673/6090676
|
||||||
@classmethod
|
@classmethod
|
||||||
def verify_sha256_token(cls, auth_header: Optional[str]) -> None:
|
def verify_sha256_token(cls, auth_header: Optional[str]) -> None:
|
||||||
"""Verify_sha256_token."""
|
|
||||||
if auth_header is None:
|
if auth_header is None:
|
||||||
raise TokenNotProvidedError(
|
raise TokenNotProvidedError(
|
||||||
"unauthorized",
|
"unauthorized",
|
||||||
|
@ -123,7 +126,6 @@ class AuthorizationService:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def has_permission(cls, principals: list[PrincipalModel], permission: str, target_uri: str) -> bool:
|
def has_permission(cls, principals: list[PrincipalModel], permission: str, target_uri: str) -> bool:
|
||||||
"""Has_permission."""
|
|
||||||
principal_ids = [p.id for p in principals]
|
principal_ids = [p.id for p in principals]
|
||||||
target_uri_normalized = target_uri.removeprefix(V1_API_PATH_PREFIX)
|
target_uri_normalized = target_uri.removeprefix(V1_API_PATH_PREFIX)
|
||||||
|
|
||||||
|
@ -153,7 +155,6 @@ class AuthorizationService:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def user_has_permission(cls, user: UserModel, permission: str, target_uri: str) -> bool:
|
def user_has_permission(cls, user: UserModel, permission: str, target_uri: str) -> bool:
|
||||||
"""User_has_permission."""
|
|
||||||
if user.principal is None:
|
if user.principal is None:
|
||||||
raise MissingPrincipalError(f"Missing principal for user with id: {user.id}")
|
raise MissingPrincipalError(f"Missing principal for user with id: {user.id}")
|
||||||
|
|
||||||
|
@ -179,7 +180,6 @@ class AuthorizationService:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def associate_user_with_group(cls, user: UserModel, group: GroupModel) -> None:
|
def associate_user_with_group(cls, user: UserModel, group: GroupModel) -> None:
|
||||||
"""Associate_user_with_group."""
|
|
||||||
user_group_assignemnt = UserGroupAssignmentModel.query.filter_by(user_id=user.id, group_id=group.id).first()
|
user_group_assignemnt = UserGroupAssignmentModel.query.filter_by(user_id=user.id, group_id=group.id).first()
|
||||||
if user_group_assignemnt is None:
|
if user_group_assignemnt is None:
|
||||||
user_group_assignemnt = UserGroupAssignmentModel(user_id=user.id, group_id=group.id)
|
user_group_assignemnt = UserGroupAssignmentModel(user_id=user.id, group_id=group.id)
|
||||||
|
@ -187,88 +187,13 @@ class AuthorizationService:
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def import_permissions_from_yaml_file(cls, raise_if_missing_user: bool = False) -> DesiredPermissionDict:
|
def import_permissions_from_yaml_file(cls, user_model: Optional[UserModel] = None) -> AddedPermissionDict:
|
||||||
"""Import_permissions_from_yaml_file."""
|
group_permissions = cls.parse_permissions_yaml_into_group_info()
|
||||||
if current_app.config["SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME"] is None:
|
result = cls.add_permissions_from_group_permissions(group_permissions, user_model)
|
||||||
raise (
|
return result
|
||||||
PermissionsFileNotSetError(
|
|
||||||
"SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME needs to be set in order to import permissions"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
permission_configs = None
|
|
||||||
with open(current_app.config["SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_ABSOLUTE_PATH"]) as file:
|
|
||||||
permission_configs = yaml.safe_load(file)
|
|
||||||
|
|
||||||
default_group = None
|
|
||||||
unique_user_group_identifiers: Set[str] = set()
|
|
||||||
user_to_group_identifiers: list[UserToGroupDict] = []
|
|
||||||
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:
|
|
||||||
if raise_if_missing_user:
|
|
||||||
raise (UserNotFoundError(f"Could not find a user with name: {username}"))
|
|
||||||
continue
|
|
||||||
user_to_group_dict: UserToGroupDict = {
|
|
||||||
"username": user.username,
|
|
||||||
"group_identifier": group_identifier,
|
|
||||||
}
|
|
||||||
user_to_group_identifiers.append(user_to_group_dict)
|
|
||||||
cls.associate_user_with_group(user, group)
|
|
||||||
|
|
||||||
permission_assignments = []
|
|
||||||
if "permissions" in permission_configs:
|
|
||||||
for _permission_identifier, permission_config in permission_configs["permissions"].items():
|
|
||||||
uri = permission_config["uri"]
|
|
||||||
permission_target = cls.find_or_create_permission_target(uri)
|
|
||||||
|
|
||||||
for allowed_permission in permission_config["allowed_permissions"]:
|
|
||||||
if "groups" in permission_config:
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if "users" in permission_config:
|
|
||||||
for username in permission_config["users"]:
|
|
||||||
user = UserModel.query.filter_by(username=username).first()
|
|
||||||
if user is not None:
|
|
||||||
principal = (
|
|
||||||
PrincipalModel.query.join(UserModel).filter(UserModel.username == username).first()
|
|
||||||
)
|
|
||||||
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,
|
|
||||||
"user_to_group_identifiers": user_to_group_identifiers,
|
|
||||||
}
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def find_or_create_permission_target(cls, uri: str) -> PermissionTargetModel:
|
def find_or_create_permission_target(cls, uri: str) -> PermissionTargetModel:
|
||||||
"""Find_or_create_permission_target."""
|
|
||||||
uri_with_percent = re.sub(r"\*", "%", uri)
|
uri_with_percent = re.sub(r"\*", "%", uri)
|
||||||
target_uri_normalized = uri_with_percent.removeprefix(V1_API_PATH_PREFIX)
|
target_uri_normalized = uri_with_percent.removeprefix(V1_API_PATH_PREFIX)
|
||||||
permission_target: Optional[PermissionTargetModel] = PermissionTargetModel.query.filter_by(
|
permission_target: Optional[PermissionTargetModel] = PermissionTargetModel.query.filter_by(
|
||||||
|
@ -287,7 +212,6 @@ class AuthorizationService:
|
||||||
permission_target: PermissionTargetModel,
|
permission_target: PermissionTargetModel,
|
||||||
permission: str,
|
permission: str,
|
||||||
) -> PermissionAssignmentModel:
|
) -> PermissionAssignmentModel:
|
||||||
"""Create_permission_for_principal."""
|
|
||||||
permission_assignment: Optional[PermissionAssignmentModel] = PermissionAssignmentModel.query.filter_by(
|
permission_assignment: Optional[PermissionAssignmentModel] = PermissionAssignmentModel.query.filter_by(
|
||||||
principal_id=principal.id,
|
principal_id=principal.id,
|
||||||
permission_target_id=permission_target.id,
|
permission_target_id=permission_target.id,
|
||||||
|
@ -306,7 +230,6 @@ class AuthorizationService:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def should_disable_auth_for_request(cls) -> bool:
|
def should_disable_auth_for_request(cls) -> bool:
|
||||||
"""Should_disable_auth_for_request."""
|
|
||||||
swagger_functions = ["get_json_spec"]
|
swagger_functions = ["get_json_spec"]
|
||||||
authentication_exclusion_list = [
|
authentication_exclusion_list = [
|
||||||
"status",
|
"status",
|
||||||
|
@ -344,7 +267,6 @@ class AuthorizationService:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_permission_from_http_method(cls, http_method: str) -> Optional[str]:
|
def get_permission_from_http_method(cls, http_method: str) -> Optional[str]:
|
||||||
"""Get_permission_from_request_method."""
|
|
||||||
request_method_mapper = {
|
request_method_mapper = {
|
||||||
"POST": "create",
|
"POST": "create",
|
||||||
"GET": "read",
|
"GET": "read",
|
||||||
|
@ -363,7 +285,6 @@ class AuthorizationService:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def check_for_permission(cls) -> None:
|
def check_for_permission(cls) -> None:
|
||||||
"""Check_for_permission."""
|
|
||||||
if cls.should_disable_auth_for_request():
|
if cls.should_disable_auth_for_request():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -397,11 +318,6 @@ class AuthorizationService:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def decode_auth_token(auth_token: str) -> dict[str, Union[str, None]]:
|
def decode_auth_token(auth_token: str) -> dict[str, Union[str, None]]:
|
||||||
"""Decode the auth token.
|
|
||||||
|
|
||||||
:param auth_token:
|
|
||||||
:return: integer|string
|
|
||||||
"""
|
|
||||||
secret_key = current_app.config.get("SECRET_KEY")
|
secret_key = current_app.config.get("SECRET_KEY")
|
||||||
if secret_key is None:
|
if secret_key is None:
|
||||||
raise KeyError("we need current_app.config to have a SECRET_KEY")
|
raise KeyError("we need current_app.config to have a SECRET_KEY")
|
||||||
|
@ -445,10 +361,11 @@ class AuthorizationService:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_user_from_sign_in(cls, user_info: dict) -> UserModel:
|
def create_user_from_sign_in(cls, user_info: dict) -> UserModel:
|
||||||
"""Create_user_from_sign_in."""
|
"""Fields from user_info.
|
||||||
"""Name, family_name, given_name, middle_name, nickname, preferred_username,"""
|
|
||||||
"""Profile, picture, website, gender, birthdate, zoneinfo, locale, and updated_at. """
|
name, family_name, given_name, middle_name, nickname, preferred_username,
|
||||||
"""Email."""
|
profile, picture, website, gender, birthdate, zoneinfo, locale,updated_at, email.
|
||||||
|
"""
|
||||||
is_new_user = False
|
is_new_user = False
|
||||||
user_attributes = {}
|
user_attributes = {}
|
||||||
|
|
||||||
|
@ -506,7 +423,7 @@ class AuthorizationService:
|
||||||
# we are also a little apprehensive about pre-creating users
|
# we are also a little apprehensive about pre-creating users
|
||||||
# before the user signs in, because we won't know things like
|
# before the user signs in, because we won't know things like
|
||||||
# the external service user identifier.
|
# the external service user identifier.
|
||||||
cls.import_permissions_from_yaml_file()
|
cls.import_permissions_from_yaml_file(user_model)
|
||||||
|
|
||||||
if is_new_user:
|
if is_new_user:
|
||||||
UserService.add_user_to_human_tasks_if_appropriate(user_model)
|
UserService.add_user_to_human_tasks_if_appropriate(user_model)
|
||||||
|
@ -521,11 +438,6 @@ class AuthorizationService:
|
||||||
process_related_path_segment: str,
|
process_related_path_segment: str,
|
||||||
target_uris: list[str],
|
target_uris: list[str],
|
||||||
) -> list[PermissionToAssign]:
|
) -> list[PermissionToAssign]:
|
||||||
"""Get_permissions_to_assign."""
|
|
||||||
permissions = permission_set.split(",")
|
|
||||||
if permission_set == "all":
|
|
||||||
permissions = ["create", "read", "update", "delete"]
|
|
||||||
|
|
||||||
permissions_to_assign: list[PermissionToAssign] = []
|
permissions_to_assign: list[PermissionToAssign] = []
|
||||||
|
|
||||||
# we were thinking that if you can start an instance, you ought to be able to:
|
# we were thinking that if you can start an instance, you ought to be able to:
|
||||||
|
@ -556,7 +468,9 @@ class AuthorizationService:
|
||||||
]:
|
]:
|
||||||
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri=target_uri))
|
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri=target_uri))
|
||||||
else:
|
else:
|
||||||
|
permissions = permission_set.split(",")
|
||||||
if permission_set == "all":
|
if permission_set == "all":
|
||||||
|
permissions = ["create", "read", "update", "delete"]
|
||||||
for path_segment_dict in PATH_SEGMENTS_FOR_PERMISSION_ALL:
|
for path_segment_dict in PATH_SEGMENTS_FOR_PERMISSION_ALL:
|
||||||
target_uri = f"{path_segment_dict['path']}/{process_related_path_segment}"
|
target_uri = f"{path_segment_dict['path']}/{process_related_path_segment}"
|
||||||
relevant_permissions = path_segment_dict["relevant_permissions"]
|
relevant_permissions = path_segment_dict["relevant_permissions"]
|
||||||
|
@ -571,7 +485,6 @@ class AuthorizationService:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def set_basic_permissions(cls) -> list[PermissionToAssign]:
|
def set_basic_permissions(cls) -> list[PermissionToAssign]:
|
||||||
"""Set_basic_permissions."""
|
|
||||||
permissions_to_assign: list[PermissionToAssign] = []
|
permissions_to_assign: list[PermissionToAssign] = []
|
||||||
permissions_to_assign.append(PermissionToAssign(permission="create", target_uri="/process-instances/for-me"))
|
permissions_to_assign.append(PermissionToAssign(permission="create", target_uri="/process-instances/for-me"))
|
||||||
permissions_to_assign.append(
|
permissions_to_assign.append(
|
||||||
|
@ -597,9 +510,31 @@ class AuthorizationService:
|
||||||
permissions_to_assign.append(PermissionToAssign(permission=permission, target_uri="/tasks/*"))
|
permissions_to_assign.append(PermissionToAssign(permission=permission, target_uri="/tasks/*"))
|
||||||
return permissions_to_assign
|
return permissions_to_assign
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def set_elevated_permissions(cls) -> list[PermissionToAssign]:
|
||||||
|
permissions_to_assign: list[PermissionToAssign] = []
|
||||||
|
for process_instance_action in ["resume", "terminate", "suspend", "reset"]:
|
||||||
|
permissions_to_assign.append(
|
||||||
|
PermissionToAssign(permission="create", target_uri=f"/process-instances-{process_instance_action}/*")
|
||||||
|
)
|
||||||
|
|
||||||
|
# FIXME: we need to fix so that user that can start a process-model
|
||||||
|
# can also start through messages as well
|
||||||
|
permissions_to_assign.append(PermissionToAssign(permission="create", target_uri="/messages/*"))
|
||||||
|
|
||||||
|
permissions_to_assign.append(PermissionToAssign(permission="create", target_uri="/send-event/*"))
|
||||||
|
permissions_to_assign.append(PermissionToAssign(permission="create", target_uri="/task-complete/*"))
|
||||||
|
|
||||||
|
# read comes from PG and PM permissions
|
||||||
|
permissions_to_assign.append(PermissionToAssign(permission="update", target_uri="/task-data/*"))
|
||||||
|
|
||||||
|
for permission in ["create", "read", "update", "delete"]:
|
||||||
|
permissions_to_assign.append(PermissionToAssign(permission=permission, target_uri="/process-instances/*"))
|
||||||
|
permissions_to_assign.append(PermissionToAssign(permission=permission, target_uri="/secrets/*"))
|
||||||
|
return permissions_to_assign
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def set_process_group_permissions(cls, target: str, permission_set: str) -> list[PermissionToAssign]:
|
def set_process_group_permissions(cls, target: str, permission_set: str) -> list[PermissionToAssign]:
|
||||||
"""Set_process_group_permissions."""
|
|
||||||
permissions_to_assign: list[PermissionToAssign] = []
|
permissions_to_assign: list[PermissionToAssign] = []
|
||||||
process_group_identifier = target.removeprefix("PG:").replace("/", ":").removeprefix(":")
|
process_group_identifier = target.removeprefix("PG:").replace("/", ":").removeprefix(":")
|
||||||
process_related_path_segment = f"{process_group_identifier}:*"
|
process_related_path_segment = f"{process_group_identifier}:*"
|
||||||
|
@ -616,7 +551,6 @@ class AuthorizationService:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def set_process_model_permissions(cls, target: str, permission_set: str) -> list[PermissionToAssign]:
|
def set_process_model_permissions(cls, target: str, permission_set: str) -> list[PermissionToAssign]:
|
||||||
"""Set_process_model_permissions."""
|
|
||||||
permissions_to_assign: list[PermissionToAssign] = []
|
permissions_to_assign: list[PermissionToAssign] = []
|
||||||
process_model_identifier = target.removeprefix("PM:").replace("/", ":").removeprefix(":")
|
process_model_identifier = target.removeprefix("PM:").replace("/", ":").removeprefix(":")
|
||||||
process_related_path_segment = f"{process_model_identifier}/*"
|
process_related_path_segment = f"{process_model_identifier}/*"
|
||||||
|
@ -644,6 +578,8 @@ class AuthorizationService:
|
||||||
* affects given process-model
|
* affects given process-model
|
||||||
BASIC
|
BASIC
|
||||||
* Basic access to complete tasks and use the site
|
* Basic access to complete tasks and use the site
|
||||||
|
ELEVATED
|
||||||
|
* Operations that require elevated permissions
|
||||||
|
|
||||||
Permission Macros:
|
Permission Macros:
|
||||||
all
|
all
|
||||||
|
@ -666,6 +602,8 @@ class AuthorizationService:
|
||||||
|
|
||||||
elif target.startswith("BASIC"):
|
elif target.startswith("BASIC"):
|
||||||
permissions_to_assign += cls.set_basic_permissions()
|
permissions_to_assign += cls.set_basic_permissions()
|
||||||
|
elif target.startswith("ELEVATED"):
|
||||||
|
permissions_to_assign += cls.set_elevated_permissions()
|
||||||
elif target == "ALL":
|
elif target == "ALL":
|
||||||
for permission in permissions:
|
for permission in permissions:
|
||||||
permissions_to_assign.append(PermissionToAssign(permission=permission, target_uri="/*"))
|
permissions_to_assign.append(PermissionToAssign(permission=permission, target_uri="/*"))
|
||||||
|
@ -685,7 +623,6 @@ class AuthorizationService:
|
||||||
def add_permission_from_uri_or_macro(
|
def add_permission_from_uri_or_macro(
|
||||||
cls, group_identifier: str, permission: str, target: str
|
cls, group_identifier: str, permission: str, target: str
|
||||||
) -> list[PermissionAssignmentModel]:
|
) -> list[PermissionAssignmentModel]:
|
||||||
"""Add_permission_from_uri_or_macro."""
|
|
||||||
group = GroupService.find_or_create_group(group_identifier)
|
group = GroupService.find_or_create_group(group_identifier)
|
||||||
permissions_to_assign = cls.explode_permissions(permission, target)
|
permissions_to_assign = cls.explode_permissions(permission, target)
|
||||||
permission_assignments = []
|
permission_assignments = []
|
||||||
|
@ -699,38 +636,106 @@ class AuthorizationService:
|
||||||
return permission_assignments
|
return permission_assignments
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def refresh_permissions(cls, group_info: list[dict[str, Any]]) -> None:
|
def parse_permissions_yaml_into_group_info(cls) -> list[GroupPermissionsDict]:
|
||||||
"""Adds new permission assignments and deletes old ones."""
|
if current_app.config["SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME"] is None:
|
||||||
initial_permission_assignments = PermissionAssignmentModel.query.all()
|
raise (
|
||||||
initial_user_to_group_assignments = UserGroupAssignmentModel.query.all()
|
PermissionsFileNotSetError(
|
||||||
result = cls.import_permissions_from_yaml_file()
|
"SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME needs to be set in order to import permissions"
|
||||||
desired_permission_assignments = result["permission_assignments"]
|
)
|
||||||
desired_group_identifiers = result["group_identifiers"]
|
)
|
||||||
desired_user_to_group_identifiers = result["user_to_group_identifiers"]
|
|
||||||
|
|
||||||
for group in group_info:
|
permission_configs = None
|
||||||
|
with open(current_app.config["SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_ABSOLUTE_PATH"]) as file:
|
||||||
|
permission_configs = yaml.safe_load(file)
|
||||||
|
|
||||||
|
group_permissions_by_group: dict[str, GroupPermissionsDict] = {}
|
||||||
|
if current_app.config["SPIFFWORKFLOW_BACKEND_DEFAULT_USER_GROUP"]:
|
||||||
|
default_group_identifier = current_app.config["SPIFFWORKFLOW_BACKEND_DEFAULT_USER_GROUP"]
|
||||||
|
group_permissions_by_group[default_group_identifier] = {
|
||||||
|
"name": default_group_identifier,
|
||||||
|
"users": [],
|
||||||
|
"permissions": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
if "groups" in permission_configs:
|
||||||
|
for group_identifier, group_config in permission_configs["groups"].items():
|
||||||
|
group_info: GroupPermissionsDict = {"name": group_identifier, "users": [], "permissions": []}
|
||||||
|
for username in group_config["users"]:
|
||||||
|
group_info["users"].append(username)
|
||||||
|
group_permissions_by_group[group_identifier] = group_info
|
||||||
|
|
||||||
|
if "permissions" in permission_configs:
|
||||||
|
for _permission_identifier, permission_config in permission_configs["permissions"].items():
|
||||||
|
uri = permission_config["uri"]
|
||||||
|
for group_identifier in permission_config["groups"]:
|
||||||
|
group_permissions_by_group[group_identifier]["permissions"].append(
|
||||||
|
{"actions": permission_config["allowed_permissions"], "uri": uri}
|
||||||
|
)
|
||||||
|
|
||||||
|
return list(group_permissions_by_group.values())
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add_permissions_from_group_permissions(
|
||||||
|
cls, group_permissions: list[GroupPermissionsDict], user_model: Optional[UserModel] = None
|
||||||
|
) -> AddedPermissionDict:
|
||||||
|
unique_user_group_identifiers: Set[str] = set()
|
||||||
|
user_to_group_identifiers: list[UserToGroupDict] = []
|
||||||
|
permission_assignments = []
|
||||||
|
|
||||||
|
default_group = None
|
||||||
|
default_group_identifier = current_app.config["SPIFFWORKFLOW_BACKEND_DEFAULT_USER_GROUP"]
|
||||||
|
if default_group_identifier:
|
||||||
|
default_group = GroupService.find_or_create_group(default_group_identifier)
|
||||||
|
unique_user_group_identifiers.add(default_group_identifier)
|
||||||
|
|
||||||
|
for group in group_permissions:
|
||||||
group_identifier = group["name"]
|
group_identifier = group["name"]
|
||||||
|
GroupService.find_or_create_group(group_identifier)
|
||||||
for username in group["users"]:
|
for username in group["users"]:
|
||||||
user_to_group_dict: UserToGroupDict = {
|
user_to_group_dict: UserToGroupDict = {
|
||||||
"username": username,
|
"username": username,
|
||||||
"group_identifier": group_identifier,
|
"group_identifier": group_identifier,
|
||||||
}
|
}
|
||||||
desired_user_to_group_identifiers.append(user_to_group_dict)
|
user_to_group_identifiers.append(user_to_group_dict)
|
||||||
GroupService.add_user_to_group_or_add_to_waiting(username, group_identifier)
|
GroupService.add_user_to_group_or_add_to_waiting(username, group_identifier)
|
||||||
desired_group_identifiers.add(group_identifier)
|
unique_user_group_identifiers.add(group_identifier)
|
||||||
for permission in group["permissions"]:
|
for permission in group["permissions"]:
|
||||||
for crud_op in permission["actions"]:
|
for crud_op in permission["actions"]:
|
||||||
desired_permission_assignments.extend(
|
permission_assignments.extend(
|
||||||
cls.add_permission_from_uri_or_macro(
|
cls.add_permission_from_uri_or_macro(
|
||||||
group_identifier=group_identifier,
|
group_identifier=group_identifier,
|
||||||
target=permission["uri"],
|
target=permission["uri"],
|
||||||
permission=crud_op,
|
permission=crud_op,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
desired_group_identifiers.add(group_identifier)
|
unique_user_group_identifiers.add(group_identifier)
|
||||||
|
|
||||||
|
if default_group is not None:
|
||||||
|
if user_model:
|
||||||
|
cls.associate_user_with_group(user_model, default_group)
|
||||||
|
else:
|
||||||
|
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,
|
||||||
|
"user_to_group_identifiers": user_to_group_identifiers,
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def remove_old_permissions_from_added_permissions(
|
||||||
|
cls,
|
||||||
|
added_permissions: AddedPermissionDict,
|
||||||
|
initial_permission_assignments: list[PermissionAssignmentModel],
|
||||||
|
initial_user_to_group_assignments: list[UserGroupAssignmentModel],
|
||||||
|
) -> None:
|
||||||
|
added_permission_assignments = added_permissions["permission_assignments"]
|
||||||
|
added_group_identifiers = added_permissions["group_identifiers"]
|
||||||
|
added_user_to_group_identifiers = added_permissions["user_to_group_identifiers"]
|
||||||
|
|
||||||
for ipa in initial_permission_assignments:
|
for ipa in initial_permission_assignments:
|
||||||
if ipa not in desired_permission_assignments:
|
if ipa not in added_permission_assignments:
|
||||||
db.session.delete(ipa)
|
db.session.delete(ipa)
|
||||||
|
|
||||||
for iutga in initial_user_to_group_assignments:
|
for iutga in initial_user_to_group_assignments:
|
||||||
|
@ -743,19 +748,23 @@ class AuthorizationService:
|
||||||
"username": iutga.user.username,
|
"username": iutga.user.username,
|
||||||
"group_identifier": iutga.group.identifier,
|
"group_identifier": iutga.group.identifier,
|
||||||
}
|
}
|
||||||
if current_user_dict not in desired_user_to_group_identifiers:
|
if current_user_dict not in added_user_to_group_identifiers:
|
||||||
db.session.delete(iutga)
|
db.session.delete(iutga)
|
||||||
|
|
||||||
# do not remove the default user group
|
# do not remove the default user group
|
||||||
desired_group_identifiers.add(current_app.config["SPIFFWORKFLOW_BACKEND_DEFAULT_USER_GROUP"])
|
added_group_identifiers.add(current_app.config["SPIFFWORKFLOW_BACKEND_DEFAULT_USER_GROUP"])
|
||||||
groups_to_delete = GroupModel.query.filter(GroupModel.identifier.not_in(desired_group_identifiers)).all()
|
groups_to_delete = GroupModel.query.filter(GroupModel.identifier.not_in(added_group_identifiers)).all()
|
||||||
for gtd in groups_to_delete:
|
for gtd in groups_to_delete:
|
||||||
db.session.delete(gtd)
|
db.session.delete(gtd)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
class KeycloakAuthorization:
|
def refresh_permissions(cls, group_permissions: list[GroupPermissionsDict]) -> None:
|
||||||
"""Interface with Keycloak server."""
|
"""Adds new permission assignments and deletes old ones."""
|
||||||
|
initial_permission_assignments = PermissionAssignmentModel.query.all()
|
||||||
|
initial_user_to_group_assignments = UserGroupAssignmentModel.query.all()
|
||||||
# class KeycloakClient:
|
group_permissions = group_permissions + cls.parse_permissions_yaml_into_group_info()
|
||||||
|
added_permissions = cls.add_permissions_from_group_permissions(group_permissions)
|
||||||
|
cls.remove_old_permissions_from_added_permissions(
|
||||||
|
added_permissions, initial_permission_assignments, initial_user_to_group_assignments
|
||||||
|
)
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
"""Group_service."""
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from spiffworkflow_backend.models.db import db
|
from spiffworkflow_backend.models.db import db
|
||||||
|
@ -8,11 +7,8 @@ from spiffworkflow_backend.services.user_service import UserService
|
||||||
|
|
||||||
|
|
||||||
class GroupService:
|
class GroupService:
|
||||||
"""GroupService."""
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def find_or_create_group(cls, group_identifier: str) -> GroupModel:
|
def find_or_create_group(cls, group_identifier: str) -> GroupModel:
|
||||||
"""Find_or_create_group."""
|
|
||||||
group: Optional[GroupModel] = GroupModel.query.filter_by(identifier=group_identifier).first()
|
group: Optional[GroupModel] = GroupModel.query.filter_by(identifier=group_identifier).first()
|
||||||
if group is None:
|
if group is None:
|
||||||
group = GroupModel(identifier=group_identifier)
|
group = GroupModel(identifier=group_identifier)
|
||||||
|
@ -23,7 +19,6 @@ class GroupService:
|
||||||
|
|
||||||
@classmethod
|
@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)
|
group = cls.find_or_create_group(group_identifier)
|
||||||
user = UserModel.query.filter_by(username=username).first()
|
user = UserModel.query.filter_by(username=username).first()
|
||||||
if user:
|
if user:
|
||||||
|
|
|
@ -2349,7 +2349,6 @@ class TestProcessApi(BaseTest):
|
||||||
with_db_and_bpmn_file_cleanup: None,
|
with_db_and_bpmn_file_cleanup: None,
|
||||||
with_super_admin_user: UserModel,
|
with_super_admin_user: UserModel,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test_correct_user_can_get_and_update_a_task."""
|
|
||||||
initiator_user = self.find_or_create_user("testuser4")
|
initiator_user = self.find_or_create_user("testuser4")
|
||||||
finance_user = self.find_or_create_user("testuser2")
|
finance_user = self.find_or_create_user("testuser2")
|
||||||
assert initiator_user.principal is not None
|
assert initiator_user.principal is not None
|
||||||
|
@ -2372,15 +2371,8 @@ class TestProcessApi(BaseTest):
|
||||||
bpmn_file_location=bpmn_file_location,
|
bpmn_file_location=bpmn_file_location,
|
||||||
)
|
)
|
||||||
|
|
||||||
# process_model = load_test_spec(
|
|
||||||
# process_model_id="model_with_lanes",
|
|
||||||
# bpmn_file_name="lanes.bpmn",
|
|
||||||
# process_group_id="finance",
|
|
||||||
# )
|
|
||||||
|
|
||||||
response = self.create_process_instance_from_process_model_id_with_api(
|
response = self.create_process_instance_from_process_model_id_with_api(
|
||||||
client,
|
client,
|
||||||
# process_model.process_group_id,
|
|
||||||
process_model_identifier,
|
process_model_identifier,
|
||||||
headers=self.logged_in_headers(initiator_user),
|
headers=self.logged_in_headers(initiator_user),
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,8 +6,8 @@ from tests.spiffworkflow_backend.helpers.base_test import BaseTest
|
||||||
|
|
||||||
from spiffworkflow_backend.models.group import GroupModel
|
from spiffworkflow_backend.models.group import GroupModel
|
||||||
from spiffworkflow_backend.models.user import UserModel
|
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
|
||||||
|
from spiffworkflow_backend.services.authorization_service import GroupPermissionsDict
|
||||||
from spiffworkflow_backend.services.authorization_service import InvalidPermissionError
|
from spiffworkflow_backend.services.authorization_service import InvalidPermissionError
|
||||||
from spiffworkflow_backend.services.group_service import GroupService
|
from spiffworkflow_backend.services.group_service import GroupService
|
||||||
from spiffworkflow_backend.services.process_instance_processor import (
|
from spiffworkflow_backend.services.process_instance_processor import (
|
||||||
|
@ -21,19 +21,10 @@ from spiffworkflow_backend.services.user_service import UserService
|
||||||
|
|
||||||
|
|
||||||
class TestAuthorizationService(BaseTest):
|
class TestAuthorizationService(BaseTest):
|
||||||
"""TestAuthorizationService."""
|
|
||||||
|
|
||||||
def test_can_raise_if_missing_user(self, app: Flask, with_db_and_bpmn_file_cleanup: None) -> None:
|
|
||||||
"""Test_can_raise_if_missing_user."""
|
|
||||||
with pytest.raises(UserNotFoundError):
|
|
||||||
AuthorizationService.import_permissions_from_yaml_file(raise_if_missing_user=True)
|
|
||||||
|
|
||||||
def test_does_not_fail_if_user_not_created(self, app: Flask, with_db_and_bpmn_file_cleanup: None) -> None:
|
def test_does_not_fail_if_user_not_created(self, app: Flask, with_db_and_bpmn_file_cleanup: None) -> None:
|
||||||
"""Test_does_not_fail_if_user_not_created."""
|
|
||||||
AuthorizationService.import_permissions_from_yaml_file()
|
AuthorizationService.import_permissions_from_yaml_file()
|
||||||
|
|
||||||
def test_can_import_permissions_from_yaml(self, app: Flask, with_db_and_bpmn_file_cleanup: None) -> None:
|
def test_can_import_permissions_from_yaml(self, app: Flask, with_db_and_bpmn_file_cleanup: None) -> None:
|
||||||
"""Test_can_import_permissions_from_yaml."""
|
|
||||||
usernames = [
|
usernames = [
|
||||||
"testadmin1",
|
"testadmin1",
|
||||||
"testadmin2",
|
"testadmin2",
|
||||||
|
@ -56,15 +47,13 @@ class TestAuthorizationService(BaseTest):
|
||||||
assert testuser1_group_identifiers == ["Finance Team", "everybody"]
|
assert testuser1_group_identifiers == ["Finance Team", "everybody"]
|
||||||
assert len(users["testuser2"].groups) == 3
|
assert len(users["testuser2"].groups) == 3
|
||||||
|
|
||||||
self.assert_user_has_permission(users["testuser1"], "update", "/v1.0/process-groups/finance/model1")
|
self.assert_user_has_permission(users["testuser1"], "update", "/v1.0/process-groups/finance:model1")
|
||||||
self.assert_user_has_permission(users["testuser1"], "update", "/v1.0/process-groups/finance/")
|
self.assert_user_has_permission(users["testuser1"], "update", "/v1.0/process-groups/finance")
|
||||||
self.assert_user_has_permission(users["testuser1"], "update", "/v1.0/process-groups/", expected_result=False)
|
self.assert_user_has_permission(users["testuser1"], "update", "/v1.0/process-groups/", expected_result=False)
|
||||||
self.assert_user_has_permission(users["testuser4"], "update", "/v1.0/process-groups/finance/model1")
|
self.assert_user_has_permission(users["testuser4"], "read", "/v1.0/process-groups/finance:model1")
|
||||||
# via the user, not the group
|
self.assert_user_has_permission(users["testuser2"], "update", "/v1.0/process-groups/finance:model1")
|
||||||
self.assert_user_has_permission(users["testuser4"], "read", "/v1.0/process-groups/finance/model1")
|
self.assert_user_has_permission(users["testuser2"], "update", "/v1.0/process-groups", expected_result=False)
|
||||||
self.assert_user_has_permission(users["testuser2"], "update", "/v1.0/process-groups/finance/model1")
|
self.assert_user_has_permission(users["testuser2"], "read", "/v1.0/process-groups")
|
||||||
self.assert_user_has_permission(users["testuser2"], "update", "/v1.0/process-groups/", expected_result=False)
|
|
||||||
self.assert_user_has_permission(users["testuser2"], "read", "/v1.0/process-groups/")
|
|
||||||
|
|
||||||
def test_user_can_be_added_to_human_task_on_first_login(
|
def test_user_can_be_added_to_human_task_on_first_login(
|
||||||
self,
|
self,
|
||||||
|
@ -121,7 +110,6 @@ class TestAuthorizationService(BaseTest):
|
||||||
client: FlaskClient,
|
client: FlaskClient,
|
||||||
with_db_and_bpmn_file_cleanup: None,
|
with_db_and_bpmn_file_cleanup: None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test_explode_permissions_all_on_process_group."""
|
|
||||||
expected_permissions = sorted(
|
expected_permissions = sorted(
|
||||||
[
|
[
|
||||||
("/event-error-details/some-process-group:some-process-model:*", "read"),
|
("/event-error-details/some-process-group:some-process-model:*", "read"),
|
||||||
|
@ -313,6 +301,34 @@ class TestAuthorizationService(BaseTest):
|
||||||
permissions_to_assign_tuples = sorted([(p.target_uri, p.permission) for p in permissions_to_assign])
|
permissions_to_assign_tuples = sorted([(p.target_uri, p.permission) for p in permissions_to_assign])
|
||||||
assert permissions_to_assign_tuples == expected_permissions
|
assert permissions_to_assign_tuples == expected_permissions
|
||||||
|
|
||||||
|
def test_explode_permissions_elevated(
|
||||||
|
self,
|
||||||
|
app: Flask,
|
||||||
|
client: FlaskClient,
|
||||||
|
with_db_and_bpmn_file_cleanup: None,
|
||||||
|
) -> None:
|
||||||
|
expected_permissions = [
|
||||||
|
("/messages/*", "create"),
|
||||||
|
("/process-instances-reset/*", "create"),
|
||||||
|
("/process-instances-resume/*", "create"),
|
||||||
|
("/process-instances-suspend/*", "create"),
|
||||||
|
("/process-instances-terminate/*", "create"),
|
||||||
|
("/process-instances/*", "create"),
|
||||||
|
("/process-instances/*", "delete"),
|
||||||
|
("/process-instances/*", "read"),
|
||||||
|
("/process-instances/*", "update"),
|
||||||
|
("/secrets/*", "create"),
|
||||||
|
("/secrets/*", "delete"),
|
||||||
|
("/secrets/*", "read"),
|
||||||
|
("/secrets/*", "update"),
|
||||||
|
("/send-event/*", "create"),
|
||||||
|
("/task-complete/*", "create"),
|
||||||
|
("/task-data/*", "update"),
|
||||||
|
]
|
||||||
|
permissions_to_assign = AuthorizationService.explode_permissions("all", "ELEVATED")
|
||||||
|
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(
|
def test_explode_permissions_all(
|
||||||
self,
|
self,
|
||||||
app: Flask,
|
app: Flask,
|
||||||
|
@ -387,7 +403,6 @@ class TestAuthorizationService(BaseTest):
|
||||||
client: FlaskClient,
|
client: FlaskClient,
|
||||||
with_db_and_bpmn_file_cleanup: None,
|
with_db_and_bpmn_file_cleanup: None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test_can_refresh_permissions."""
|
|
||||||
user = self.find_or_create_user(username="user_one")
|
user = self.find_or_create_user(username="user_one")
|
||||||
user_two = self.find_or_create_user(username="user_two")
|
user_two = self.find_or_create_user(username="user_two")
|
||||||
admin_user = self.find_or_create_user(username="testadmin1")
|
admin_user = self.find_or_create_user(username="testadmin1")
|
||||||
|
@ -399,7 +414,7 @@ class TestAuthorizationService(BaseTest):
|
||||||
GroupService.find_or_create_group("group_three")
|
GroupService.find_or_create_group("group_three")
|
||||||
assert GroupModel.query.filter_by(identifier="group_three").first() is not None
|
assert GroupModel.query.filter_by(identifier="group_three").first() is not None
|
||||||
|
|
||||||
group_info = [
|
group_info: list[GroupPermissionsDict] = [
|
||||||
{
|
{
|
||||||
"users": ["user_one", "user_two"],
|
"users": ["user_one", "user_two"],
|
||||||
"name": "group_one",
|
"name": "group_one",
|
||||||
|
@ -410,6 +425,11 @@ class TestAuthorizationService(BaseTest):
|
||||||
"name": "group_three",
|
"name": "group_three",
|
||||||
"permissions": [{"actions": ["create", "read"], "uri": "PG:hey2"}],
|
"permissions": [{"actions": ["create", "read"], "uri": "PG:hey2"}],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"users": [],
|
||||||
|
"name": "everybody",
|
||||||
|
"permissions": [{"actions": ["read"], "uri": "PG:hey2everybody"}],
|
||||||
|
},
|
||||||
]
|
]
|
||||||
AuthorizationService.refresh_permissions(group_info)
|
AuthorizationService.refresh_permissions(group_info)
|
||||||
assert GroupModel.query.filter_by(identifier="group_two").first() is None
|
assert GroupModel.query.filter_by(identifier="group_two").first() is None
|
||||||
|
@ -418,6 +438,7 @@ class TestAuthorizationService(BaseTest):
|
||||||
self.assert_user_has_permission(user, "read", "/v1.0/process-groups/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")
|
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")
|
self.assert_user_has_permission(user, "create", "/v1.0/process-groups/hey:yo")
|
||||||
|
self.assert_user_has_permission(user, "read", "/v1.0/process-groups/hey2everybody:yo")
|
||||||
|
|
||||||
self.assert_user_has_permission(user_two, "read", "/v1.0/process-groups/hey")
|
self.assert_user_has_permission(user_two, "read", "/v1.0/process-groups/hey")
|
||||||
self.assert_user_has_permission(user_two, "read", "/v1.0/process-groups/hey:yo")
|
self.assert_user_has_permission(user_two, "read", "/v1.0/process-groups/hey:yo")
|
||||||
|
|
Loading…
Reference in New Issue