clients: expand Go, Python, Rust client APIs

- Add TOTP enrollment/confirmation/removal to all clients
- Add password change and admin set-password endpoints
- Add account listing, status update, and tag management
- Add audit log listing with filter support
- Add policy rule CRUD operations
- Expand test coverage for all new endpoints across clients
- Fix .gitignore to exclude built binaries

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 20:29:11 -07:00
parent ec7c966ad2
commit cbcb1a0533
11 changed files with 1938 additions and 255 deletions

View File

@@ -1,6 +1,6 @@
"""Data models for MCIAS API responses."""
from dataclasses import dataclass, field
from typing import cast
from typing import Any, cast
@dataclass
@@ -74,3 +74,73 @@ class PGCreds:
username=str(d["username"]),
password=str(d["password"]),
)
@dataclass
class RuleBody:
"""Match conditions and effect of a policy rule."""
effect: str
roles: list[str] = field(default_factory=list)
account_types: list[str] = field(default_factory=list)
subject_uuid: str | None = None
actions: list[str] = field(default_factory=list)
resource_type: str | None = None
owner_matches_subject: bool | None = None
service_names: list[str] = field(default_factory=list)
required_tags: list[str] = field(default_factory=list)
@classmethod
def from_dict(cls, d: dict[str, object]) -> "RuleBody":
return cls(
effect=str(d["effect"]),
roles=[str(r) for r in cast(list[Any], d.get("roles") or [])],
account_types=[str(t) for t in cast(list[Any], d.get("account_types") or [])],
subject_uuid=str(d["subject_uuid"]) if d.get("subject_uuid") is not None else None,
actions=[str(a) for a in cast(list[Any], d.get("actions") or [])],
resource_type=str(d["resource_type"]) if d.get("resource_type") is not None else None,
owner_matches_subject=bool(d["owner_matches_subject"]) if d.get("owner_matches_subject") is not None else None,
service_names=[str(s) for s in cast(list[Any], d.get("service_names") or [])],
required_tags=[str(t) for t in cast(list[Any], d.get("required_tags") or [])],
)
def to_dict(self) -> dict[str, Any]:
"""Serialise to a JSON-compatible dict, omitting None/empty fields."""
out: dict[str, Any] = {"effect": self.effect}
if self.roles:
out["roles"] = self.roles
if self.account_types:
out["account_types"] = self.account_types
if self.subject_uuid is not None:
out["subject_uuid"] = self.subject_uuid
if self.actions:
out["actions"] = self.actions
if self.resource_type is not None:
out["resource_type"] = self.resource_type
if self.owner_matches_subject is not None:
out["owner_matches_subject"] = self.owner_matches_subject
if self.service_names:
out["service_names"] = self.service_names
if self.required_tags:
out["required_tags"] = self.required_tags
return out
@dataclass
class PolicyRule:
"""An operator-defined policy rule."""
id: int
priority: int
description: str
rule: RuleBody
enabled: bool
created_at: str
updated_at: str
not_before: str | None = None
expires_at: str | None = None
@classmethod
def from_dict(cls, d: dict[str, object]) -> "PolicyRule":
return cls(
id=int(cast(int, d["id"])),
priority=int(cast(int, d["priority"])),
description=str(d["description"]),
rule=RuleBody.from_dict(cast(dict[str, object], d["rule"])),
enabled=bool(d["enabled"]),
created_at=str(d["created_at"]),
updated_at=str(d["updated_at"]),
not_before=str(d["not_before"]) if d.get("not_before") is not None else None,
expires_at=str(d["expires_at"]) if d.get("expires_at") is not None else None,
)