188 lines
7.2 KiB
Python

"""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
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] = "",
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,
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."""
column = PrincipalModel.__table__.columns[id_column_name]
principal: Optional[PrincipalModel] = PrincipalModel.query.filter(column == child_id).first()
if principal is None:
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",
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:
"""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)
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:
"""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()
)
for assignment in wildcard:
cls.add_user_to_group(user, assignment.group)
db.session.commit()
@staticmethod
def get_user_by_service_and_service_id(service: str, service_id: str) -> Optional[UserModel]:
"""Get_user_by_service_and_service_id."""
user: UserModel = (
UserModel.query.filter(UserModel.service == service).filter(UserModel.service_id == service_id).first()
)
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()