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