feat: enforce effective appearance policy

This commit is contained in:
2026-08-20 07:03:28 +02:00
parent 566b3b83ad
commit 6052dde760
2 changed files with 53 additions and 2 deletions
+34 -2
View File
@@ -16,6 +16,7 @@ from govoplan_core.api.v1.schemas import (
AuthShellResponse,
AuthSessionResponse,
AuthSessionUserInfo,
EffectiveAppearanceInfo,
GroupInfo,
LoginRequest,
LoginResponse,
@@ -31,6 +32,7 @@ from govoplan_core.api.v1.schemas import (
UserUiPreferences,
)
from govoplan_core.core.access import AuthMethod, PrincipalRef
from govoplan_core.core.appearance import resolve_effective_appearance
from govoplan_core.core.identity import CAPABILITY_IDENTITY_DIRECTORY, IdentityDirectory
from govoplan_core.core.registry import PlatformRegistry
from govoplan_core.core.principal_cache import invalidate_auth_principals
@@ -255,12 +257,23 @@ def _user_ui_preferences(settings_payload: object) -> UserUiPreferences:
return UserUiPreferences()
def _effective_appearance_info(session: Session, *, tenant: Tenant, user: User) -> EffectiveAppearanceInfo:
system_item = get_system_settings(session)
decision = resolve_effective_appearance(
system_settings=system_item.settings,
tenant_settings=tenant.settings,
user_settings=user.settings,
)
return EffectiveAppearanceInfo.model_validate(decision.as_dict())
def _user_info(
user: User,
account: Account,
*,
preferred_language: str | None = None,
enabled_language_codes: list[str] | None = None,
appearance: EffectiveAppearanceInfo | None = None,
) -> UserInfo:
return UserInfo(
id=user.id,
@@ -273,6 +286,7 @@ def _user_info(
preferred_language=preferred_language,
enabled_language_codes=enabled_language_codes or [],
ui_preferences=_user_ui_preferences(user.settings),
appearance=appearance or EffectiveAppearanceInfo(),
)
@@ -593,7 +607,7 @@ def _shell_response(
]
)
return AuthShellResponse(
user=_user_info(user, account),
user=_user_info(user, account, appearance=_effective_appearance_info(session, tenant=tenant, user=user)),
tenant=active_tenant,
active_tenant=active_tenant,
tenants=memberships,
@@ -669,6 +683,7 @@ def _profile_response(session: Session, context: AuthContext) -> AuthProfileResp
context.account,
preferred_language=preferred_language,
enabled_language_codes=user_enabled,
appearance=_effective_appearance_info(session, tenant=context.tenant, user=context.user),
),
tenant=active_tenant,
active_tenant=active_tenant,
@@ -783,7 +798,13 @@ def _me_response(
)
]
return MeResponse(
user=_user_info(user, account, preferred_language=preferred_language, enabled_language_codes=user_enabled),
user=_user_info(
user,
account,
preferred_language=preferred_language,
enabled_language_codes=user_enabled,
appearance=_effective_appearance_info(session, tenant=tenant, user=user),
),
tenant=active_tenant,
active_tenant=active_tenant,
tenants=memberships,
@@ -991,6 +1012,17 @@ def update_profile(
if payload.ui_preferences is None:
next_settings["ui"] = UserUiPreferences().model_dump()
else:
appearance = _effective_appearance_info(session, tenant=context.tenant, user=context.user)
stored_palette = _user_ui_preferences(context.user.settings).palette
if (
appearance.locked
and payload.ui_preferences.palette is not None
and payload.ui_preferences.palette != stored_palette
):
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
detail="The effective appearance policy locks the palette.",
)
if (
payload.ui_preferences.navigation is not None
and payload.ui_preferences.navigation.locked