Prefer canonical identity and organization directories

This commit is contained in:
2026-07-11 01:13:53 +02:00
parent 5b8baa6cde
commit 002d12e417
8 changed files with 322 additions and 31 deletions

View File

@@ -18,6 +18,8 @@ from govoplan_core.api.v1.schemas import (
UserUiPreferences,
)
from govoplan_core.core.access import AuthMethod, PrincipalRef
from govoplan_core.core.identity import CAPABILITY_IDENTITY_DIRECTORY, IdentityDirectory
from govoplan_core.core.registry import PlatformRegistry
from govoplan_access.backend.auth.dependencies import ApiPrincipal, get_api_principal
from govoplan_core.admin.settings import get_system_settings
from govoplan_core.audit.logging import audit_from_principal
@@ -176,6 +178,16 @@ def _language_context(session: Session, *, tenant: Tenant, user: User) -> dict[s
}
def _identity_directory_from_request(request: Request) -> IdentityDirectory | None:
registry = getattr(request.app.state, "govoplan_registry", None)
if not isinstance(registry, PlatformRegistry) or not registry.has_capability(CAPABILITY_IDENTITY_DIRECTORY):
return None
capability = registry.require_capability(CAPABILITY_IDENTITY_DIRECTORY)
if not isinstance(capability, IdentityDirectory):
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Invalid capability: {CAPABILITY_IDENTITY_DIRECTORY}")
return capability
def _resolve_login_user(session: Session, payload: LoginRequest) -> tuple[Account, User, Tenant]:
account = (
session.query(Account)
@@ -215,6 +227,8 @@ def _me_response(
service_account_id: str | None = None,
include_system: bool = True,
include_all_memberships: bool = True,
identity_directory: IdentityDirectory | None = None,
identity_id: str | None = None,
) -> MeResponse:
tenant_roles = collect_user_roles(session, user)
system_roles = collect_system_roles(session, account) if include_system else []
@@ -248,7 +262,7 @@ def _me_response(
account_id=account.id,
membership_id=user.id,
tenant_id=tenant.id,
identity_id=identity_id_for_account(session, account.id),
identity_id=identity_id or identity_id_for_account(session, account.id, identity_directory=identity_directory),
scopes=frozenset(scopes),
group_ids=frozenset(group.id for group in groups),
role_ids=frozenset(role.id for role in tenant_roles + system_roles),
@@ -271,7 +285,8 @@ def _me_response(
@router.post("/login", response_model=LoginResponse)
def login(payload: LoginRequest, request: Request, response: Response, session: Session = Depends(get_session)):
account, user, tenant = _resolve_login_user(session, payload)
me_payload = _me_response(session, account=account, user=user, tenant=tenant)
identity_directory = _identity_directory_from_request(request)
me_payload = _me_response(session, account=account, user=user, tenant=tenant, identity_directory=identity_directory)
maintenance_mode = saved_maintenance_mode(session)
if maintenance_mode.enabled and not scopes_grant(me_payload.scopes, MAINTENANCE_ACCESS_SCOPE):
raise HTTPException(
@@ -300,6 +315,7 @@ def login(payload: LoginRequest, request: Request, response: Response, session:
effective_scopes=me_payload.scopes,
auth_method="session",
session_id=created.model.id,
identity_directory=identity_directory,
).model_dump(),
)
@@ -321,6 +337,7 @@ def me(principal: ApiPrincipal = Depends(get_api_principal), session: Session =
service_account_id=principal.principal.service_account_id,
include_system=principal.auth_session is not None,
include_all_memberships=principal.auth_session is not None,
identity_id=principal.principal.identity_id,
)
@@ -401,6 +418,7 @@ def update_profile(
session_id=principal.session_id,
include_system=True,
include_all_memberships=True,
identity_id=principal.principal.identity_id,
)
@@ -430,6 +448,7 @@ def switch_tenant(
tenant=tenant,
auth_method="session",
session_id=principal.session_id,
identity_id=principal.principal.identity_id,
)