feat: implement access profiles and hats

This commit is contained in:
tegwick 2026-06-15 23:12:25 +02:00
parent 97cd03b551
commit 660ce24995
14 changed files with 1329 additions and 20 deletions

View file

@ -9,6 +9,8 @@ from typing import Iterable, Iterator, Mapping, cast
from user_engine.domain import (
Account,
AccessProfile,
ActiveAccessContext,
Application,
ApplicationBinding,
AuditRecord,
@ -56,6 +58,10 @@ class InMemoryUserEngineStore:
)
identity_factors: dict[str, IdentityFactor] = field(default_factory=dict)
prepared_accounts: dict[str, PreparedAccount] = field(default_factory=dict)
access_profiles: dict[str, AccessProfile] = field(default_factory=dict)
active_access_contexts: dict[
tuple[str, str], ActiveAccessContext
] = field(default_factory=dict)
profile_values: dict[
tuple[str, str, ProfileScope, str | None], ProfileValue
] = field(default_factory=dict)
@ -198,6 +204,36 @@ class InMemoryUserEngineStore:
if account.tenant == tenant
)
def save_access_profile(self, profile: AccessProfile) -> None:
self.access_profiles[profile.access_profile_id] = profile
def access_profile(self, access_profile_id: str) -> AccessProfile | None:
return self.access_profiles.get(access_profile_id)
def access_profiles_for_tenant(self, tenant: str) -> tuple[AccessProfile, ...]:
return tuple(
profile
for profile in self.access_profiles.values()
if profile.tenant == tenant
)
def save_active_access_context(self, context: ActiveAccessContext) -> None:
self.active_access_contexts[(context.user_id, context.tenant)] = context
def active_access_context(
self, user_id: str, tenant: str
) -> ActiveAccessContext | None:
return self.active_access_contexts.get((user_id, tenant))
def active_access_contexts_for_tenant(
self, tenant: str
) -> tuple[ActiveAccessContext, ...]:
return tuple(
context
for context in self.active_access_contexts.values()
if context.tenant == tenant
)
def save_profile_value(self, value: ProfileValue) -> None:
self.profile_values[
(value.user_id, value.attribute_key, value.scope, value.scope_id)
@ -258,6 +294,8 @@ class InMemoryUserEngineStore:
"registration_sessions": len(self.registration_sessions),
"identity_factors": len(self.identity_factors),
"prepared_accounts": len(self.prepared_accounts),
"access_profiles": len(self.access_profiles),
"active_access_contexts": len(self.active_access_contexts),
"profile_values": len(self.profile_values),
"audit_records": len(self.audit_records),
"pending_outbox_events": len(self.outbox_events),
@ -277,6 +315,8 @@ class InMemoryUserEngineStore:
"registration_sessions": copy.deepcopy(self.registration_sessions),
"identity_factors": copy.deepcopy(self.identity_factors),
"prepared_accounts": copy.deepcopy(self.prepared_accounts),
"access_profiles": copy.deepcopy(self.access_profiles),
"active_access_contexts": copy.deepcopy(self.active_access_contexts),
"profile_values": copy.deepcopy(self.profile_values),
"audit_records": copy.deepcopy(self.audit_records),
"outbox_events": copy.deepcopy(self.outbox_events),
@ -305,6 +345,10 @@ class InMemoryUserEngineStore:
] # type: ignore[assignment]
self.identity_factors = snapshot["identity_factors"] # type: ignore[assignment]
self.prepared_accounts = snapshot["prepared_accounts"] # type: ignore[assignment]
self.access_profiles = snapshot["access_profiles"] # type: ignore[assignment]
self.active_access_contexts = snapshot[
"active_access_contexts"
] # type: ignore[assignment]
self.profile_values = snapshot["profile_values"] # type: ignore[assignment]
self.audit_records = [*snapshot_audit_records, *denied_audit_records]
self.outbox_events = snapshot["outbox_events"] # type: ignore[assignment]