from __future__ import annotations from collections.abc import Iterable from sqlalchemy import or_ from sqlalchemy.orm import Session from govoplan_access.backend.db.models import ( Account, ExternalFunctionRoleAssignment, Function, FunctionAssignment, FunctionDelegation, FunctionRoleAssignment, Identity, IdentityAccountLink, Role, User, ) from govoplan_core.core.idm import OrganizationFunctionAssignmentRef from govoplan_core.security.time import utc_now def primary_identity_for_account(session: Session, account_id: str) -> Identity | None: return ( session.query(Identity) .join(IdentityAccountLink, IdentityAccountLink.identity_id == Identity.id) .filter( IdentityAccountLink.account_id == account_id, IdentityAccountLink.is_primary.is_(True), Identity.is_active.is_(True), ) .order_by(IdentityAccountLink.created_at.asc()) .first() ) def identity_id_for_account(session: Session, account_id: str) -> str | None: identity = primary_identity_for_account(session, account_id) return identity.id if identity is not None else None def ensure_identity_for_account(session: Session, account: Account, *, source: str = "local") -> Identity: identity = primary_identity_for_account(session, account.id) if identity is not None: return identity identity = Identity(display_name=account.display_name or account.email, source=source) session.add(identity) session.flush() session.add(IdentityAccountLink(identity_id=identity.id, account_id=account.id, is_primary=True, source=source)) session.flush() return identity def active_function_assignments_for_account( session: Session, account_id: str, *, tenant_id: str | None = None, ) -> list[FunctionAssignment]: now = utc_now() query = ( session.query(FunctionAssignment) .join(Function, Function.id == FunctionAssignment.function_id) .filter( FunctionAssignment.account_id == account_id, FunctionAssignment.is_active.is_(True), Function.is_active.is_(True), or_(FunctionAssignment.valid_from.is_(None), FunctionAssignment.valid_from <= now), or_(FunctionAssignment.valid_until.is_(None), FunctionAssignment.valid_until > now), ) .order_by(FunctionAssignment.created_at.asc()) ) if tenant_id is not None: query = query.filter(FunctionAssignment.tenant_id == tenant_id, Function.tenant_id == tenant_id) return query.all() def active_function_delegations_for_account( session: Session, account_id: str, *, tenant_id: str | None = None, modes: Iterable[str] = ("delegate",), ) -> list[FunctionDelegation]: now = utc_now() mode_set = sorted({str(mode) for mode in modes}) if not mode_set: return [] query = ( session.query(FunctionDelegation) .join(FunctionAssignment, FunctionAssignment.id == FunctionDelegation.function_assignment_id) .join(Function, Function.id == FunctionAssignment.function_id) .filter( FunctionDelegation.delegate_account_id == account_id, FunctionDelegation.mode.in_(mode_set), FunctionDelegation.is_active.is_(True), FunctionDelegation.revoked_at.is_(None), FunctionAssignment.is_active.is_(True), Function.is_active.is_(True), Function.delegable.is_(True), or_(FunctionDelegation.valid_from.is_(None), FunctionDelegation.valid_from <= now), or_(FunctionDelegation.valid_until.is_(None), FunctionDelegation.valid_until > now), or_(FunctionAssignment.valid_from.is_(None), FunctionAssignment.valid_from <= now), or_(FunctionAssignment.valid_until.is_(None), FunctionAssignment.valid_until > now), ) .order_by(FunctionDelegation.created_at.asc()) ) if tenant_id is not None: query = query.filter(FunctionDelegation.tenant_id == tenant_id, FunctionAssignment.tenant_id == tenant_id) return query.all() def collect_function_assignment_ids(session: Session, user: User) -> list[str]: ids = [item.id for item in active_function_assignments_for_account(session, user.account_id, tenant_id=user.tenant_id)] for delegation in active_function_delegations_for_account(session, user.account_id, tenant_id=user.tenant_id): ids.append(delegation.function_assignment_id) return sorted(dict.fromkeys(ids)) def collect_function_delegation_ids(session: Session, user: User) -> list[str]: return [ item.id for item in active_function_delegations_for_account( session, user.account_id, tenant_id=user.tenant_id, modes=("delegate", "act_in_place"), ) ] def collect_function_roles(session: Session, user: User) -> list[Role]: assignments = active_function_assignments_for_account(session, user.account_id, tenant_id=user.tenant_id) delegated = active_function_delegations_for_account(session, user.account_id, tenant_id=user.tenant_id, modes=("delegate",)) assignment_ids = {assignment.id for assignment in assignments} assignment_ids.update(delegation.function_assignment_id for delegation in delegated) if not assignment_ids: return [] function_ids = [ row[0] for row in session.query(FunctionAssignment.function_id) .filter(FunctionAssignment.tenant_id == user.tenant_id, FunctionAssignment.id.in_(assignment_ids)) .all() ] if not function_ids: return [] return ( session.query(Role) .join(FunctionRoleAssignment, FunctionRoleAssignment.role_id == Role.id) .filter( FunctionRoleAssignment.tenant_id == user.tenant_id, FunctionRoleAssignment.function_id.in_(function_ids), Role.tenant_id == user.tenant_id, ) .order_by(Role.name.asc()) .all() ) def collect_external_function_roles( session: Session, user: User, assignments: Iterable[OrganizationFunctionAssignmentRef], *, source_module: str = "organizations", ) -> list[Role]: function_ids = sorted({ assignment.function_id for assignment in assignments if assignment.tenant_id == user.tenant_id and assignment.status == "active" }) if not function_ids: return [] return ( session.query(Role) .join(ExternalFunctionRoleAssignment, ExternalFunctionRoleAssignment.role_id == Role.id) .filter( ExternalFunctionRoleAssignment.tenant_id == user.tenant_id, ExternalFunctionRoleAssignment.source_module == source_module, ExternalFunctionRoleAssignment.function_id.in_(function_ids), Role.tenant_id == user.tenant_id, ) .order_by(Role.name.asc()) .all() )