from __future__ import annotations from typing import Iterable from models import Project, Role, Scenario, User from services.exceptions import AuthorizationError, EntityNotFoundError from services.repositories import ProjectRepository, ScenarioRepository from services.unit_of_work import UnitOfWork READ_ROLES: frozenset[str] = frozenset( {"viewer", "analyst", "project_manager", "admin"} ) MANAGE_ROLES: frozenset[str] = frozenset({"project_manager", "admin"}) def _user_role_names(user: User) -> set[str]: roles: Iterable[Role] = getattr(user, "roles", []) or [] return {role.name for role in roles} def _require_project_repo(uow: UnitOfWork) -> ProjectRepository: if not uow.projects: raise RuntimeError("Project repository not initialised") return uow.projects def _require_scenario_repo(uow: UnitOfWork) -> ScenarioRepository: if not uow.scenarios: raise RuntimeError("Scenario repository not initialised") return uow.scenarios def _assert_user_can_access(user: User, *, require_manage: bool) -> None: if not user.is_active: raise AuthorizationError("User account is disabled.") if user.is_superuser: return allowed = MANAGE_ROLES if require_manage else READ_ROLES if not _user_role_names(user) & allowed: raise AuthorizationError( "Insufficient role permissions for this action.") def ensure_project_access( uow: UnitOfWork, *, project_id: int, user: User, require_manage: bool = False, ) -> Project: """Resolve a project and ensure the user holds the required permissions.""" repo = _require_project_repo(uow) project = repo.get(project_id) _assert_user_can_access(user, require_manage=require_manage) return project def ensure_scenario_access( uow: UnitOfWork, *, scenario_id: int, user: User, require_manage: bool = False, with_children: bool = False, ) -> Scenario: """Resolve a scenario and ensure the user holds the required permissions.""" repo = _require_scenario_repo(uow) scenario = repo.get(scenario_id, with_children=with_children) _assert_user_can_access(user, require_manage=require_manage) return scenario def ensure_scenario_in_project( uow: UnitOfWork, *, project_id: int, scenario_id: int, user: User, require_manage: bool = False, with_children: bool = False, ) -> Scenario: """Resolve a scenario ensuring it belongs to the project and the user may access it.""" project = ensure_project_access( uow, project_id=project_id, user=user, require_manage=require_manage, ) scenario = ensure_scenario_access( uow, scenario_id=scenario_id, user=user, require_manage=require_manage, with_children=with_children, ) if scenario.project_id != project.id: raise EntityNotFoundError( f"Scenario {scenario_id} does not belong to project {project_id}." ) return scenario