188 lines
7.2 KiB
Python
Raw Normal View History

"""User_service."""
from typing import Any
from typing import Optional
from flask import current_app
from flask import g
from spiffworkflow_backend.exceptions.api_error import ApiError
from spiffworkflow_backend.models.db import db
from spiffworkflow_backend.models.group import GroupModel
from spiffworkflow_backend.models.human_task import HumanTaskModel
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
2022-12-20 15:47:30 -05:00
from spiffworkflow_backend.models.user_group_assignment_waiting import (
UserGroupAssignmentWaitingModel,
)
class UserService:
"""Provides common tools for working with users."""
@classmethod
def create_user(
cls,
username: str,
service: str,
service_id: str,
email: Optional[str] = "",
2022-12-20 15:47:30 -05:00
display_name: Optional[str] = "",
tenant_specific_field_1: Optional[str] = None,
tenant_specific_field_2: Optional[str] = None,
tenant_specific_field_3: Optional[str] = None,
) -> UserModel:
"""Create_user."""
user_model: Optional[UserModel] = (
UserModel.query.filter(UserModel.service == service).filter(UserModel.service_id == service_id).first()
)
if user_model is None:
if username == "":
username = service_id
user_model = UserModel(
username=username,
service=service,
service_id=service_id,
email=email,
2022-12-20 15:47:30 -05:00
display_name=display_name,
tenant_specific_field_1=tenant_specific_field_1,
tenant_specific_field_2=tenant_specific_field_2,
tenant_specific_field_3=tenant_specific_field_3,
)
db.session.add(user_model)
try:
db.session.commit()
except Exception as e:
db.session.rollback()
raise ApiError(
error_code="add_user_error",
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:
# TODO: username may be ''.
# not sure what to send in error message.
# Don't really want to send service_id.
raise (
ApiError(
error_code="user_already_exists",
message=f"User already exists: {username}",
status_code=409,
)
)
# 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)
@staticmethod
def current_user() -> Any:
"""Current_user."""
if not UserService.has_user():
raise ApiError("logged_out", "You are no longer logged in.", status_code=401)
return g.user
@staticmethod
def get_principal_by_user_id(user_id: int) -> PrincipalModel:
"""Get_principal_by_user_id."""
principal = db.session.query(PrincipalModel).filter(PrincipalModel.user_id == user_id).first()
if isinstance(principal, PrincipalModel):
return principal
raise ApiError(
error_code="no_principal_found",
message=f"No principal was found for user_id: {user_id}",
)
@classmethod
def create_principal(cls, child_id: int, id_column_name: str = "user_id") -> PrincipalModel:
"""Create_principal."""
Squashed 'spiffworkflow-backend/' changes from 03bf7a61..10c443a2 10c443a2 Merge pull request #130 from sartography/feature/data 71c803aa allow passing in the log level into the app w/ burnettk daeb82d9 Merge pull request #126 from sartography/dependabot/pip/typing-extensions-4.4.0 14c8f52c Merge pull request #123 from sartography/dependabot/pip/dot-github/workflows/poetry-1.2.2 92d204e6 Merge remote-tracking branch 'origin/main' into feature/data 1cb77901 run the save all bpmn script on server boot w/ burnettk 16a6f476 Bump typing-extensions from 4.3.0 to 4.4.0 d8ac61fc Bump poetry from 1.2.1 to 1.2.2 in /.github/workflows 3be27786 Merge pull request #131 from sartography/feature/permissions2 1fd8fc78 Merge remote-tracking branch 'origin/main' into feature/permissions2 d29621ae data setup on app boot 0b21a5d4 refactor bin/save_all_bpmn.py into service code 02fb9d61 lint c95db461 refactor scripts 98628fc2 This caused a problem with scopes when token timed out. d8b2323b merged in main and resolved conflicts d01b4fc7 updated sentry-sdk to resolve deprecation warnings 5851ddf5 update for mypy in python 3.9 508f9900 merged in main and resolved conflicts 68d69978 precommit w/ burnettk 85a4ee16 removed debug print statements w/ burnettk 93eb91f4 added keycloak configs and user perms for staging w/ burnettk e4ded8fc added method to import permissions from yml file w/ burnettk 22ba89ae use percents instead of asterisks to better support db syntax w/ burnettk 0c116ae8 postgres does not use backticks w/ burnettk 621ad3ef attempting to see if sql like statement works in other dbs as well w/ burnettk git-subtree-dir: spiffworkflow-backend git-subtree-split: 10c443a2d82752e8ed9d1679afe6409d81029006
2022-10-12 15:28:52 -04:00
column = PrincipalModel.__table__.columns[id_column_name]
principal: Optional[PrincipalModel] = PrincipalModel.query.filter(column == child_id).first()
if principal is None:
Squashed 'spiffworkflow-backend/' changes from 03bf7a61..10c443a2 10c443a2 Merge pull request #130 from sartography/feature/data 71c803aa allow passing in the log level into the app w/ burnettk daeb82d9 Merge pull request #126 from sartography/dependabot/pip/typing-extensions-4.4.0 14c8f52c Merge pull request #123 from sartography/dependabot/pip/dot-github/workflows/poetry-1.2.2 92d204e6 Merge remote-tracking branch 'origin/main' into feature/data 1cb77901 run the save all bpmn script on server boot w/ burnettk 16a6f476 Bump typing-extensions from 4.3.0 to 4.4.0 d8ac61fc Bump poetry from 1.2.1 to 1.2.2 in /.github/workflows 3be27786 Merge pull request #131 from sartography/feature/permissions2 1fd8fc78 Merge remote-tracking branch 'origin/main' into feature/permissions2 d29621ae data setup on app boot 0b21a5d4 refactor bin/save_all_bpmn.py into service code 02fb9d61 lint c95db461 refactor scripts 98628fc2 This caused a problem with scopes when token timed out. d8b2323b merged in main and resolved conflicts d01b4fc7 updated sentry-sdk to resolve deprecation warnings 5851ddf5 update for mypy in python 3.9 508f9900 merged in main and resolved conflicts 68d69978 precommit w/ burnettk 85a4ee16 removed debug print statements w/ burnettk 93eb91f4 added keycloak configs and user perms for staging w/ burnettk e4ded8fc added method to import permissions from yml file w/ burnettk 22ba89ae use percents instead of asterisks to better support db syntax w/ burnettk 0c116ae8 postgres does not use backticks w/ burnettk 621ad3ef attempting to see if sql like statement works in other dbs as well w/ burnettk git-subtree-dir: spiffworkflow-backend git-subtree-split: 10c443a2d82752e8ed9d1679afe6409d81029006
2022-10-12 15:28:52 -04:00
principal = PrincipalModel()
setattr(principal, id_column_name, child_id)
db.session.add(principal)
try:
db.session.commit()
except Exception as e:
db.session.rollback()
current_app.logger.error(f"Exception in create_principal: {e}")
raise ApiError(
error_code="add_principal_error",
Squashed 'spiffworkflow-backend/' changes from 03bf7a61..10c443a2 10c443a2 Merge pull request #130 from sartography/feature/data 71c803aa allow passing in the log level into the app w/ burnettk daeb82d9 Merge pull request #126 from sartography/dependabot/pip/typing-extensions-4.4.0 14c8f52c Merge pull request #123 from sartography/dependabot/pip/dot-github/workflows/poetry-1.2.2 92d204e6 Merge remote-tracking branch 'origin/main' into feature/data 1cb77901 run the save all bpmn script on server boot w/ burnettk 16a6f476 Bump typing-extensions from 4.3.0 to 4.4.0 d8ac61fc Bump poetry from 1.2.1 to 1.2.2 in /.github/workflows 3be27786 Merge pull request #131 from sartography/feature/permissions2 1fd8fc78 Merge remote-tracking branch 'origin/main' into feature/permissions2 d29621ae data setup on app boot 0b21a5d4 refactor bin/save_all_bpmn.py into service code 02fb9d61 lint c95db461 refactor scripts 98628fc2 This caused a problem with scopes when token timed out. d8b2323b merged in main and resolved conflicts d01b4fc7 updated sentry-sdk to resolve deprecation warnings 5851ddf5 update for mypy in python 3.9 508f9900 merged in main and resolved conflicts 68d69978 precommit w/ burnettk 85a4ee16 removed debug print statements w/ burnettk 93eb91f4 added keycloak configs and user perms for staging w/ burnettk e4ded8fc added method to import permissions from yml file w/ burnettk 22ba89ae use percents instead of asterisks to better support db syntax w/ burnettk 0c116ae8 postgres does not use backticks w/ burnettk 621ad3ef attempting to see if sql like statement works in other dbs as well w/ burnettk git-subtree-dir: spiffworkflow-backend git-subtree-split: 10c443a2d82752e8ed9d1679afe6409d81029006
2022-10-12 15:28:52 -04:00
message=f"Could not create principal {child_id}",
) from e
return principal
@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()
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:
2022-12-20 15:47:30 -05:00
"""Add_waiting_group_assignment."""
wugam = (
UserGroupAssignmentWaitingModel().query.filter_by(username=username).filter_by(group_id=group.id).first()
2022-12-20 15:47:30 -05:00
)
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:
2022-12-20 15:47:30 -05:00
"""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)
2022-12-20 15:47:30 -05:00
wildcard = (
UserGroupAssignmentWaitingModel()
.query.filter(UserGroupAssignmentWaitingModel.username == UserGroupAssignmentWaitingModel.MATCH_ALL_USERS)
2022-12-20 15:47:30 -05:00
.all()
)
for assignment in wildcard:
cls.add_user_to_group(user, assignment.group)
db.session.commit()
Squashed 'spiffworkflow-backend/' changes from f9c2fa21e..5225a8b4c 5225a8b4c pyl 259f74a1e Merge branch 'main' into bug/refresh-token d452208ef Merge pull request #135 from sartography/feature/permissions3 8e1075406 Merge branch 'main' into bug/refresh-token 2b01d2fe7 fixed authentication_callback and getting the user w/ burnettk 476e36c7d mypy changes 6403e62c0 Fix migration after merging main 594a32b67 merged in main and resolved conflicts w/ burnettk b285ba1a1 added updated columns to secrets and updated flask-bpmn 7c53fc9fa Merge remote-tracking branch 'origin/main' into feature/permissions3 201a6918a pyl changes a6112f7fb Merge branch 'main' into bug/refresh-token 87f65a6c6 auth_token should be dictionary, not string f163de61c pyl 1f443bb94 PublicAuthenticationService -> AuthenticationService 6c491a3df Don't refresh token here. They just logged in. We are validating the returned token. If it is bad, raise an error. 91b8649f8 id_token -> auth_token fc94774bb Move `store_refresh_token` to authentication_service 00d66e9c5 mypy c4e415dbe mypy 1e75716eb Pre commit a72b03e09 Rename method. We pass it auth_tokens, not id_tokens 9a6700a6d Too many things expect g.token. Reverting my change 74883fb23 Noe store refresh_token, and try to use it if auth_token is expired Renamed some methods to use correct token type be0557013 Cleanup - remove unused code cf01f0d51 Add refresh_token model 1c0c937af added method to delete all permissions so we can recreate them w/ burnettk aaeaac879 Merge remote-tracking branch 'origin/main' into feature/permissions3 44856fce2 added api endpoint to check if user has permissions based on given target uris w/ burnettk ae830054d precommit w/ burnettk 94d50efb1 created common method to check whether an api method should have auth w/ burnettk c955335d0 precommit w/ burnettk 37caf1a69 added a finance user to keycloak and fixed up the staging permission yml w/ burnettk 93c456294 merged in main and resolved conflicts w/ burnettk 06a7c6485 remaining tests are now passing w/ burnettk 50529d04c added test to make sure api gives a 403 if a permission is not found w/ burnettk 6a9d0a68a api calls are somewhat respecting permissions now and the process api tests are passing d07fbbeff attempting to respect permissions w/ burnettk git-subtree-dir: spiffworkflow-backend git-subtree-split: 5225a8b4c101133567d4f7efa33632d36c29c81d
2022-10-20 16:00:12 -04:00
@staticmethod
def get_user_by_service_and_service_id(service: str, service_id: str) -> Optional[UserModel]:
Squashed 'spiffworkflow-backend/' changes from f9c2fa21e..5225a8b4c 5225a8b4c pyl 259f74a1e Merge branch 'main' into bug/refresh-token d452208ef Merge pull request #135 from sartography/feature/permissions3 8e1075406 Merge branch 'main' into bug/refresh-token 2b01d2fe7 fixed authentication_callback and getting the user w/ burnettk 476e36c7d mypy changes 6403e62c0 Fix migration after merging main 594a32b67 merged in main and resolved conflicts w/ burnettk b285ba1a1 added updated columns to secrets and updated flask-bpmn 7c53fc9fa Merge remote-tracking branch 'origin/main' into feature/permissions3 201a6918a pyl changes a6112f7fb Merge branch 'main' into bug/refresh-token 87f65a6c6 auth_token should be dictionary, not string f163de61c pyl 1f443bb94 PublicAuthenticationService -> AuthenticationService 6c491a3df Don't refresh token here. They just logged in. We are validating the returned token. If it is bad, raise an error. 91b8649f8 id_token -> auth_token fc94774bb Move `store_refresh_token` to authentication_service 00d66e9c5 mypy c4e415dbe mypy 1e75716eb Pre commit a72b03e09 Rename method. We pass it auth_tokens, not id_tokens 9a6700a6d Too many things expect g.token. Reverting my change 74883fb23 Noe store refresh_token, and try to use it if auth_token is expired Renamed some methods to use correct token type be0557013 Cleanup - remove unused code cf01f0d51 Add refresh_token model 1c0c937af added method to delete all permissions so we can recreate them w/ burnettk aaeaac879 Merge remote-tracking branch 'origin/main' into feature/permissions3 44856fce2 added api endpoint to check if user has permissions based on given target uris w/ burnettk ae830054d precommit w/ burnettk 94d50efb1 created common method to check whether an api method should have auth w/ burnettk c955335d0 precommit w/ burnettk 37caf1a69 added a finance user to keycloak and fixed up the staging permission yml w/ burnettk 93c456294 merged in main and resolved conflicts w/ burnettk 06a7c6485 remaining tests are now passing w/ burnettk 50529d04c added test to make sure api gives a 403 if a permission is not found w/ burnettk 6a9d0a68a api calls are somewhat respecting permissions now and the process api tests are passing d07fbbeff attempting to respect permissions w/ burnettk git-subtree-dir: spiffworkflow-backend git-subtree-split: 5225a8b4c101133567d4f7efa33632d36c29c81d
2022-10-20 16:00:12 -04:00
"""Get_user_by_service_and_service_id."""
user: UserModel = (
UserModel.query.filter(UserModel.service == service).filter(UserModel.service_id == service_id).first()
Squashed 'spiffworkflow-backend/' changes from f9c2fa21e..5225a8b4c 5225a8b4c pyl 259f74a1e Merge branch 'main' into bug/refresh-token d452208ef Merge pull request #135 from sartography/feature/permissions3 8e1075406 Merge branch 'main' into bug/refresh-token 2b01d2fe7 fixed authentication_callback and getting the user w/ burnettk 476e36c7d mypy changes 6403e62c0 Fix migration after merging main 594a32b67 merged in main and resolved conflicts w/ burnettk b285ba1a1 added updated columns to secrets and updated flask-bpmn 7c53fc9fa Merge remote-tracking branch 'origin/main' into feature/permissions3 201a6918a pyl changes a6112f7fb Merge branch 'main' into bug/refresh-token 87f65a6c6 auth_token should be dictionary, not string f163de61c pyl 1f443bb94 PublicAuthenticationService -> AuthenticationService 6c491a3df Don't refresh token here. They just logged in. We are validating the returned token. If it is bad, raise an error. 91b8649f8 id_token -> auth_token fc94774bb Move `store_refresh_token` to authentication_service 00d66e9c5 mypy c4e415dbe mypy 1e75716eb Pre commit a72b03e09 Rename method. We pass it auth_tokens, not id_tokens 9a6700a6d Too many things expect g.token. Reverting my change 74883fb23 Noe store refresh_token, and try to use it if auth_token is expired Renamed some methods to use correct token type be0557013 Cleanup - remove unused code cf01f0d51 Add refresh_token model 1c0c937af added method to delete all permissions so we can recreate them w/ burnettk aaeaac879 Merge remote-tracking branch 'origin/main' into feature/permissions3 44856fce2 added api endpoint to check if user has permissions based on given target uris w/ burnettk ae830054d precommit w/ burnettk 94d50efb1 created common method to check whether an api method should have auth w/ burnettk c955335d0 precommit w/ burnettk 37caf1a69 added a finance user to keycloak and fixed up the staging permission yml w/ burnettk 93c456294 merged in main and resolved conflicts w/ burnettk 06a7c6485 remaining tests are now passing w/ burnettk 50529d04c added test to make sure api gives a 403 if a permission is not found w/ burnettk 6a9d0a68a api calls are somewhat respecting permissions now and the process api tests are passing d07fbbeff attempting to respect permissions w/ burnettk git-subtree-dir: spiffworkflow-backend git-subtree-split: 5225a8b4c101133567d4f7efa33632d36c29c81d
2022-10-20 16:00:12 -04:00
)
if user:
return user
return None
@classmethod
def add_user_to_human_tasks_if_appropriate(cls, user: UserModel) -> None:
"""Add_user_to_human_tasks_if_appropriate."""
group_ids = [g.id for g in user.groups]
human_tasks = HumanTaskModel.query.filter(
HumanTaskModel.lane_assignment_id.in_(group_ids) # type: ignore
).all()
for human_task in human_tasks:
human_task_user = HumanTaskUserModel(user_id=user.id, human_task_id=human_task.id)
db.session.add(human_task_user)
db.session.commit()