Complete service-account credential administration

This commit is contained in:
2026-08-04 01:04:39 +02:00
parent 1409dbf94d
commit 998d47ae94
15 changed files with 1672 additions and 17 deletions
+42 -4
View File
@@ -3780,7 +3780,14 @@ def _api_key_items_for_response(session: Session, keys: list[ApiKey]) -> list[Ap
def _full_api_keys_delta_response(session: Session, tenant: Tenant, *, include_revoked: bool, cursor: tuple[int, int] | None = None, limit: int = 500) -> ApiKeyListDeltaResponse:
query = session.query(ApiKey).filter(ApiKey.tenant_id == tenant.id)
query = (
session.query(ApiKey)
.join(User, User.id == ApiKey.user_id)
.filter(
ApiKey.tenant_id == tenant.id,
User.auth_provider != "service_account",
)
)
if not include_revoked:
query = query.filter(ApiKey.revoked_at.is_(None))
snapshot_sequence = cursor[1] if cursor is not None else max_sequence_id(session, tenant_id=tenant.id, module_id=ACCESS_MODULE_ID, collections=(ACCESS_API_KEYS_COLLECTION,))
@@ -3807,7 +3814,14 @@ def _api_keys_delta_response(session: Session, tenant: Tenant, *, include_revoke
if entries is None:
return _full_api_keys_delta_response(session, tenant, include_revoked=include_revoked, limit=limit)
changed_ids = _changed_ids(entries, "access_api_key")
query = session.query(ApiKey).filter(ApiKey.tenant_id == tenant.id)
query = (
session.query(ApiKey)
.join(User, User.id == ApiKey.user_id)
.filter(
ApiKey.tenant_id == tenant.id,
User.auth_provider != "service_account",
)
)
if not include_revoked:
query = query.filter(ApiKey.revoked_at.is_(None))
visible = {
@@ -3857,7 +3871,14 @@ def list_api_keys(
principal: ApiPrincipal = Depends(require_scope("admin:api_keys:read")),
):
tenant = _resolve_tenant(session, principal, tenant_id)
query = session.query(ApiKey).filter(ApiKey.tenant_id == tenant.id)
query = (
session.query(ApiKey)
.join(User, User.id == ApiKey.user_id)
.filter(
ApiKey.tenant_id == tenant.id,
User.auth_provider != "service_account",
)
)
if not include_revoked:
query = query.filter(ApiKey.revoked_at.is_(None))
keys, pagination = _page_query(query.order_by(ApiKey.created_at.desc()), page=page, page_size=page_size)
@@ -3880,6 +3901,14 @@ def create_tenant_api_key(
user = session.query(User).filter(User.id == user_id, User.tenant_id == tenant.id, User.is_active.is_(True)).one_or_none()
if user is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Active user not found")
if user.auth_provider == "service_account":
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=(
"Use the service-account credential API so revision, scope "
"ceiling, rotation, and audit guarantees remain enforced"
),
)
user_scopes = _user_item_for_response(
session,
user,
@@ -3930,7 +3959,16 @@ def revoke_api_key(
principal: ApiPrincipal = Depends(require_scope("admin:api_keys:revoke")),
):
tenant = _resolve_tenant(session, principal, tenant_id)
item = session.query(ApiKey).filter(ApiKey.id == api_key_id, ApiKey.tenant_id == tenant.id).one_or_none()
item = (
session.query(ApiKey)
.join(User, User.id == ApiKey.user_id)
.filter(
ApiKey.id == api_key_id,
ApiKey.tenant_id == tenant.id,
User.auth_provider != "service_account",
)
.one_or_none()
)
if item is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="API key not found")
if item.revoked_at is None: