Files
govoplan-access/src/govoplan_access/backend/semantic.py

187 lines
6.8 KiB
Python

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()
)