176 lines
6.8 KiB
Python
176 lines
6.8 KiB
Python
from __future__ import annotations
|
|
|
|
from collections.abc import Mapping, Sequence
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
from govoplan_access.backend.admin.service import ensure_default_roles, get_or_create_account
|
|
from govoplan_access.backend.db.models import Account, SystemRoleAssignment, User, UserRoleAssignment
|
|
from govoplan_access.backend.security.api_keys import create_api_key
|
|
from govoplan_access.backend.security.passwords import hash_password
|
|
from govoplan_core.admin.common import AdminValidationError
|
|
from govoplan_core.core.access import CreatedApiKeyRef, DevelopmentBootstrapRef, TenantAccessProvisioner, TenantOwnerCandidateRef, UserRef
|
|
|
|
|
|
class LegacyTenantAccessProvisioner(TenantAccessProvisioner):
|
|
def ensure_default_roles(self, session: object, tenant: object | None = None) -> Mapping[str, object]:
|
|
return ensure_default_roles(session, tenant) # type: ignore[arg-type]
|
|
|
|
def tenant_owner_candidates(self, session: object) -> tuple[TenantOwnerCandidateRef, ...]:
|
|
db = _session(session)
|
|
accounts = db.query(Account).filter(Account.is_active.is_(True)).order_by(Account.email.asc()).all()
|
|
return tuple(
|
|
TenantOwnerCandidateRef(account_id=account.id, email=account.email, display_name=account.display_name)
|
|
for account in accounts
|
|
)
|
|
|
|
def ensure_tenant_owner_membership(
|
|
self,
|
|
session: object,
|
|
*,
|
|
tenant: object,
|
|
owner_account_id: str,
|
|
) -> str:
|
|
db = _session(session)
|
|
tenant_id = getattr(tenant, "id", None)
|
|
if not tenant_id:
|
|
raise AdminValidationError("Tenant owner membership requires a persisted tenant.")
|
|
|
|
roles = ensure_default_roles(db, tenant) # type: ignore[arg-type]
|
|
owner_role = roles.get("owner")
|
|
if owner_role is None:
|
|
raise AdminValidationError("Tenant owner role is not available.")
|
|
|
|
owner_account = db.get(Account, owner_account_id)
|
|
if owner_account is None or not owner_account.is_active:
|
|
raise AdminValidationError("The selected tenant owner account is missing or inactive.")
|
|
|
|
membership = (
|
|
db.query(User)
|
|
.filter(User.tenant_id == tenant_id, User.account_id == owner_account.id)
|
|
.one_or_none()
|
|
)
|
|
if membership is None:
|
|
membership = User(
|
|
tenant_id=tenant_id,
|
|
account_id=owner_account.id,
|
|
email=owner_account.email,
|
|
display_name=owner_account.display_name,
|
|
is_active=True,
|
|
auth_provider=owner_account.auth_provider,
|
|
password_hash=owner_account.password_hash,
|
|
)
|
|
db.add(membership)
|
|
db.flush()
|
|
|
|
existing_assignment = (
|
|
db.query(UserRoleAssignment)
|
|
.filter(
|
|
UserRoleAssignment.tenant_id == tenant_id,
|
|
UserRoleAssignment.user_id == membership.id,
|
|
UserRoleAssignment.role_id == owner_role.id,
|
|
)
|
|
.one_or_none()
|
|
)
|
|
if existing_assignment is None:
|
|
db.add(UserRoleAssignment(tenant_id=tenant_id, user_id=membership.id, role_id=owner_role.id))
|
|
db.flush()
|
|
return membership.id
|
|
|
|
def ensure_development_admin(
|
|
self,
|
|
session: object,
|
|
*,
|
|
tenant: object,
|
|
user_email: str,
|
|
user_password: str,
|
|
api_key_secret: str | None,
|
|
scopes: Sequence[str],
|
|
) -> DevelopmentBootstrapRef:
|
|
db = _session(session)
|
|
tenant_id = getattr(tenant, "id", None)
|
|
if not tenant_id:
|
|
raise AdminValidationError("Development bootstrap requires a persisted tenant.")
|
|
|
|
tenant_roles = ensure_default_roles(db, tenant) # type: ignore[arg-type]
|
|
system_roles = ensure_default_roles(db, None)
|
|
account, _, _ = get_or_create_account(
|
|
db,
|
|
email=user_email,
|
|
display_name="Development Admin",
|
|
password=user_password,
|
|
password_reset_required=False,
|
|
)
|
|
if not account.password_hash:
|
|
account.password_hash = hash_password(user_password)
|
|
account.is_active = True
|
|
db.add(account)
|
|
|
|
user = db.query(User).filter(User.tenant_id == tenant_id, User.account_id == account.id).one_or_none()
|
|
if user is None:
|
|
user = User(
|
|
tenant_id=tenant_id,
|
|
account_id=account.id,
|
|
email=account.email,
|
|
display_name="Development Admin",
|
|
is_tenant_admin=True,
|
|
auth_provider=account.auth_provider,
|
|
password_hash=account.password_hash,
|
|
)
|
|
db.add(user)
|
|
db.flush()
|
|
else:
|
|
user.email = account.email
|
|
user.password_hash = account.password_hash
|
|
user.is_active = True
|
|
db.add(user)
|
|
|
|
owner_role = tenant_roles["owner"]
|
|
existing_assignment = db.query(UserRoleAssignment).filter(
|
|
UserRoleAssignment.tenant_id == tenant_id,
|
|
UserRoleAssignment.user_id == user.id,
|
|
UserRoleAssignment.role_id == owner_role.id,
|
|
).one_or_none()
|
|
if existing_assignment is None:
|
|
db.add(UserRoleAssignment(tenant_id=tenant_id, user_id=user.id, role_id=owner_role.id))
|
|
|
|
system_owner = system_roles["system_owner"]
|
|
existing_system_assignment = db.query(SystemRoleAssignment).filter(
|
|
SystemRoleAssignment.account_id == account.id,
|
|
SystemRoleAssignment.role_id == system_owner.id,
|
|
).one_or_none()
|
|
if existing_system_assignment is None:
|
|
db.add(SystemRoleAssignment(account_id=account.id, role_id=system_owner.id))
|
|
|
|
created_api_key = None
|
|
if api_key_secret:
|
|
existing = [key for key in user.api_keys if key.name == "Development API key" and key.revoked_at is None]
|
|
if not existing:
|
|
api_key = create_api_key(
|
|
db,
|
|
user=user,
|
|
name="Development API key",
|
|
scopes=list(scopes),
|
|
secret=api_key_secret,
|
|
)
|
|
created_api_key = CreatedApiKeyRef(id=api_key.model.id, secret=api_key.secret)
|
|
|
|
db.flush()
|
|
return DevelopmentBootstrapRef(
|
|
user=UserRef(
|
|
id=user.id,
|
|
account_id=account.id,
|
|
tenant_id=tenant_id,
|
|
email=user.email,
|
|
display_name=user.display_name,
|
|
status="active" if user.is_active else "inactive",
|
|
),
|
|
created_api_key=created_api_key,
|
|
)
|
|
|
|
|
|
def _session(session: object) -> Session:
|
|
if not isinstance(session, Session):
|
|
raise TypeError("Tenant access provisioner requires a SQLAlchemy Session")
|
|
return session
|