feat: add authorized account lookup without provisioning
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a0e747-8f27-7242-8df8-8bc44f88c929
This commit is contained in:
parent
b4cef6446b
commit
686da7c6bb
3 changed files with 195 additions and 0 deletions
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
from dataclasses import dataclass, field, replace
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Callable, Iterable, Mapping
|
||||
|
|
@ -105,6 +108,21 @@ class ReadinessReport:
|
|||
checks: Mapping[str, bool]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AccountAuthority:
|
||||
"""Current account state only; not a root entitlement or tenant lifecycle grant."""
|
||||
issuer: str
|
||||
subject: str
|
||||
tenant: str
|
||||
user_id: str
|
||||
account_id: str
|
||||
identity_id: str
|
||||
account_status: AccountStatus
|
||||
tenant_account_status: AccountStatus | None
|
||||
observed_at: datetime
|
||||
decision_id: str | None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class UserSession:
|
||||
actor: Actor
|
||||
|
|
@ -1766,6 +1784,44 @@ class UserEngineService:
|
|||
)
|
||||
return self._session(actor, user, account)
|
||||
|
||||
def lookup_account_authority(
|
||||
self, caller: Actor, *, issuer: str, subject: str, tenant: str,
|
||||
correlation_id: str | None = None,
|
||||
) -> AccountAuthority:
|
||||
"""Authorize a read of an existing identity; never call provisioning /me.
|
||||
|
||||
The host must authenticate caller independently of the lookup target.
|
||||
This action needs its own policy grant; caller roles do not grant it here.
|
||||
Missing scoped accounts are explicit, never defaulted to active.
|
||||
"""
|
||||
if any(not isinstance(value, str) or not value.strip()
|
||||
for value in (issuer, subject, tenant)):
|
||||
raise ValidationError("exact issuer, subject and tenant are required")
|
||||
correlation_id = correlation_id or new_id("corr")
|
||||
lookup_id = "sha256:" + hashlib.sha256(json.dumps(
|
||||
[issuer, subject, tenant], separators=(",", ":"),
|
||||
).encode()).hexdigest()
|
||||
decision = self._authorize(
|
||||
caller, action="account.authority.read",
|
||||
resource_type="user-engine:account-authority", resource_id=lookup_id,
|
||||
tenant=tenant, correlation_id=correlation_id,
|
||||
context={"lookup_issuer": issuer, "lookup_subject": subject},
|
||||
)
|
||||
observed_at = utc_now()
|
||||
identity = self.store.find_identity(issuer, subject)
|
||||
if identity is None:
|
||||
raise NotFoundError("identity link not found")
|
||||
user = self._require_user(identity.user_id)
|
||||
account = self._require_account(user.user_id)
|
||||
scoped = self.store.tenant_account(tenant, user.user_id)
|
||||
return AccountAuthority(
|
||||
issuer=issuer, subject=subject, tenant=tenant, user_id=user.user_id,
|
||||
account_id=account.account_id, identity_id=identity.identity_id,
|
||||
account_status=account.status,
|
||||
tenant_account_status=scoped.status if scoped is not None else None,
|
||||
observed_at=observed_at, decision_id=decision.decision_id,
|
||||
)
|
||||
|
||||
def create_user(
|
||||
self,
|
||||
actor: Actor,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue