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

@ -1,8 +1,13 @@
"""Domain schemas for user-engine."""
from user_engine.domain.models import (
AccessControlFact,
AccessMembershipRequirement,
AccessProfile,
AccessScopeType,
Account,
AccountStatus,
ActiveAccessContext,
Actor,
Application,
ApplicationBinding,
@ -49,7 +54,12 @@ from user_engine.domain.models import (
__all__ = [
"Account",
"AccessControlFact",
"AccessMembershipRequirement",
"AccessProfile",
"AccessScopeType",
"AccountStatus",
"ActiveAccessContext",
"Actor",
"Application",
"ApplicationBinding",

View file

@ -93,6 +93,14 @@ class PreparedEntitlementKind(StrEnum):
ONBOARDING_JOURNEY = "onboarding_journey"
class AccessScopeType(StrEnum):
TENANT = "tenant"
REALM = "realm"
SERVICE = "service"
ASSET = "asset"
GROUP = "group"
class ProfileScope(StrEnum):
GLOBAL = "global"
TENANT = "tenant"
@ -300,6 +308,73 @@ class Membership:
created_at: datetime = field(default_factory=utc_now)
@dataclass(frozen=True)
class AccessMembershipRequirement:
scope_type: str
scope_id: str
kind: str
@dataclass(frozen=True)
class AccessProfile:
tenant: str
display_name: str
hat: str
access_profile_id: str = field(default_factory=lambda: new_id("apf"))
scope_type: AccessScopeType = AccessScopeType.TENANT
scope_id: str | None = None
realm_id: str | None = None
service_id: str | None = None
asset_id: str | None = None
membership_requirements: tuple[AccessMembershipRequirement, ...] = ()
required_factor_types: tuple[IdentityFactorType, ...] = ()
profile_defaults: Mapping[str, Any] = field(default_factory=dict)
claims: Mapping[str, Any] = field(default_factory=dict)
group_scope_ids: tuple[str, ...] = ()
requires_approval: bool = False
created_at: datetime = field(default_factory=utc_now)
updated_at: datetime = field(default_factory=utc_now)
@dataclass(frozen=True)
class ActiveAccessContext:
user_id: str
tenant: str
access_profile_id: str
hat: str
scope_type: AccessScopeType
scope_id: str
active_context_id: str = field(default_factory=lambda: new_id("actx"))
realm_id: str | None = None
service_id: str | None = None
asset_id: str | None = None
membership_ids: tuple[str, ...] = ()
factor_ids: tuple[str, ...] = ()
group_ids: tuple[str, ...] = ()
claims: Mapping[str, Any] = field(default_factory=dict)
profile_defaults: Mapping[str, Any] = field(default_factory=dict)
selected_by_subject: str | None = None
created_at: datetime = field(default_factory=utc_now)
updated_at: datetime = field(default_factory=utc_now)
@dataclass(frozen=True)
class AccessControlFact:
tenant: str
subject_type: str
subject_id: str
scope_type: str
scope_id: str
role: str
fact_id: str = field(default_factory=lambda: new_id("acf"))
user_id: str | None = None
hat: str | None = None
access_profile_id: str | None = None
membership_id: str | None = None
evidence_refs: tuple[CanonEntityReference, ...] = ()
source_system: str = "user-engine"
@dataclass(frozen=True)
class FamilyMemberSpec:
primary_email: str