Add identity trust administration surfaces
This commit is contained in:
@@ -31,6 +31,10 @@ class IdentityTrustError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
class IdentityTrustAccessDenied(IdentityTrustError):
|
||||
pass
|
||||
|
||||
|
||||
class SqlIdentityTrustService:
|
||||
def register_device_key(
|
||||
self,
|
||||
@@ -148,10 +152,8 @@ class SqlIdentityTrustService:
|
||||
) -> tuple[DeviceKeyRef, ...]:
|
||||
db = _session(session)
|
||||
_require_tenant(principal, tenant_id)
|
||||
if account_id != _account_id(principal) and not _has_scope(
|
||||
principal, "identity_trust:device:read_all"
|
||||
):
|
||||
raise IdentityTrustError(
|
||||
if not _may_read_account(principal, account_id):
|
||||
raise IdentityTrustAccessDenied(
|
||||
"The acting account cannot inspect these device keys."
|
||||
)
|
||||
statement = select(DevicePublicKey).where(
|
||||
@@ -170,6 +172,100 @@ class SqlIdentityTrustService:
|
||||
)
|
||||
)
|
||||
|
||||
def list_assurance_evidence(
|
||||
self,
|
||||
session: object,
|
||||
principal: object,
|
||||
*,
|
||||
tenant_id: str,
|
||||
account_id: str,
|
||||
active_only: bool = False,
|
||||
limit: int = 200,
|
||||
) -> tuple[AssuranceEvidence, ...]:
|
||||
db = _session(session)
|
||||
_require_tenant(principal, tenant_id)
|
||||
if not _may_read_account(principal, account_id):
|
||||
raise IdentityTrustAccessDenied(
|
||||
"The acting account cannot inspect this assurance evidence."
|
||||
)
|
||||
statement = select(AssuranceEvidence).where(
|
||||
AssuranceEvidence.tenant_id == tenant_id,
|
||||
AssuranceEvidence.account_id == account_id,
|
||||
)
|
||||
if active_only:
|
||||
statement = statement.where(
|
||||
AssuranceEvidence.expires_at >= _as_utc(utcnow())
|
||||
)
|
||||
return tuple(
|
||||
db.scalars(
|
||||
statement.order_by(
|
||||
AssuranceEvidence.verified_at.desc(),
|
||||
AssuranceEvidence.id,
|
||||
).limit(max(1, min(int(limit), 500)))
|
||||
)
|
||||
)
|
||||
|
||||
def list_epochs(
|
||||
self,
|
||||
session: object,
|
||||
principal: object,
|
||||
*,
|
||||
tenant_id: str,
|
||||
subject_kind: str,
|
||||
subject_id: str,
|
||||
limit: int = 200,
|
||||
) -> tuple[KeyEpochRef, ...]:
|
||||
db = _session(session)
|
||||
_require_tenant(principal, tenant_id)
|
||||
if not _has_scope(principal, "identity_trust:device:admin"):
|
||||
raise IdentityTrustAccessDenied(
|
||||
"Identity Trust administration is required to inspect key epochs."
|
||||
)
|
||||
values = db.scalars(
|
||||
select(TrustKeyEpoch)
|
||||
.where(
|
||||
TrustKeyEpoch.tenant_id == tenant_id,
|
||||
TrustKeyEpoch.subject_kind == subject_kind,
|
||||
TrustKeyEpoch.subject_id == subject_id,
|
||||
)
|
||||
.order_by(TrustKeyEpoch.epoch.desc())
|
||||
.limit(max(1, min(int(limit), 500)))
|
||||
)
|
||||
return tuple(_epoch_ref(value) for value in values)
|
||||
|
||||
def list_key_access_decisions(
|
||||
self,
|
||||
session: object,
|
||||
principal: object,
|
||||
*,
|
||||
tenant_id: str,
|
||||
account_id: str,
|
||||
limit: int = 200,
|
||||
) -> tuple[KeyAccessDecisionRecord, ...]:
|
||||
db = _session(session)
|
||||
_require_tenant(principal, tenant_id)
|
||||
if not (
|
||||
_has_scope(principal, "identity_trust:key_access:approve")
|
||||
or _has_scope(principal, "identity_trust:device:admin")
|
||||
):
|
||||
raise IdentityTrustAccessDenied(
|
||||
"Key-access decision authority is required to inspect decisions."
|
||||
)
|
||||
return tuple(
|
||||
db.scalars(
|
||||
select(KeyAccessDecisionRecord)
|
||||
.where(
|
||||
KeyAccessDecisionRecord.tenant_id == tenant_id,
|
||||
KeyAccessDecisionRecord.account_id == account_id,
|
||||
)
|
||||
.order_by(
|
||||
KeyAccessDecisionRecord.created_at.desc(),
|
||||
KeyAccessDecisionRecord.id,
|
||||
)
|
||||
.limit(max(1, min(int(limit), 500)))
|
||||
)
|
||||
)
|
||||
|
||||
def rotate_epoch(
|
||||
self,
|
||||
session: object,
|
||||
@@ -592,7 +688,9 @@ def _session(value: object) -> Session:
|
||||
|
||||
def _require_tenant(principal: object, tenant_id: str) -> None:
|
||||
if str(getattr(principal, "tenant_id", "")) != tenant_id:
|
||||
raise IdentityTrustError("Cross-tenant identity-trust access is denied.")
|
||||
raise IdentityTrustAccessDenied(
|
||||
"Cross-tenant identity-trust access is denied."
|
||||
)
|
||||
|
||||
|
||||
def _account_id(principal: object) -> str:
|
||||
@@ -605,6 +703,14 @@ def _has_scope(principal: object, scope: str) -> bool:
|
||||
return scope in set(getattr(principal, "scopes", ()))
|
||||
|
||||
|
||||
def _may_read_account(principal: object, account_id: str) -> bool:
|
||||
return (
|
||||
account_id == _account_id(principal)
|
||||
or _has_scope(principal, "identity_trust:device:read_all")
|
||||
or _has_scope(principal, "identity_trust:device:admin")
|
||||
)
|
||||
|
||||
|
||||
def _required(value: str, label: str) -> str:
|
||||
cleaned = value.strip()
|
||||
if not cleaned:
|
||||
@@ -630,6 +736,7 @@ def _as_utc(value: datetime) -> datetime:
|
||||
|
||||
|
||||
__all__ = [
|
||||
"IdentityTrustAccessDenied",
|
||||
"IdentityTrustError",
|
||||
"SqlIdentityTrustService",
|
||||
"record_assurance_evidence",
|
||||
|
||||
Reference in New Issue
Block a user