TEN-WP-0003: FlexAuthWriteAuthorizer -- gate writes through flex-auth
flex_auth.py: CheckRequest + FlexAuthCheckClient against flex-auth's real POST /v1/check contract (schemas/check_request.schema.json, decision_envelope.schema.json, read directly from the flex-auth repo, not guessed). Fail-closed by construction: only effect=="allow" authorizes; every other effect, non-200, malformed body, or transport failure resolves to deny, nothing raises past is_allowed(). authz.FlexAuthWriteAuthorizer implements the existing WriteAuthorizer Protocol. Action -> resource-type mapping coordinated with FLEX-WP-0008's planned vocabulary (both repos reference the same table). DefaultDenyWriteAuthorizer stays the fallback when no flex-auth URL is configured. config.py: Settings.from_env(), mirroring qonto-assistant's pattern. docs/flex-auth-integration.md documents the contract, fail-closed rule, and current real state (denies everything until FLEX-WP-0008 lands). 60 tests passing. Verified live twice over real HTTP between separate processes (not just MockTransport): a deny-returning flex-auth double produces 403 from POST /tenants, an allow-returning one produces 201. Also registered (not implemented) the two workplans this depends on for a complete picture: flex-auth/FLEX-WP-0008 (protected-system registration -- what makes allow reachable) and key-cape/KEY-WP-0005 (discovered key-cape emits none of iam-profile_v0.3.md's core claims yet, not just missing tenant_roles -- a bigger, security-sensitive gap flagged rather than quietly worked around). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
0b37f792a1
commit
5d57c7d488
13 changed files with 648 additions and 4 deletions
|
|
@ -7,7 +7,13 @@ from fastapi.responses import JSONResponse
|
|||
from pydantic import BaseModel
|
||||
|
||||
from tenant_engine import __version__
|
||||
from tenant_engine.authz import DefaultDenyWriteAuthorizer, WriteAuthorizationDeniedError, WriteAuthorizer
|
||||
from tenant_engine.authz import (
|
||||
DefaultDenyWriteAuthorizer,
|
||||
FlexAuthWriteAuthorizer,
|
||||
WriteAuthorizationDeniedError,
|
||||
WriteAuthorizer,
|
||||
)
|
||||
from tenant_engine.config import Settings
|
||||
from tenant_engine.domain import (
|
||||
CapabilityRole,
|
||||
GrantReason,
|
||||
|
|
@ -17,6 +23,7 @@ from tenant_engine.domain import (
|
|||
Tenant,
|
||||
create_role_grant,
|
||||
)
|
||||
from tenant_engine.flex_auth import FlexAuthCheckClient
|
||||
from tenant_engine.store import (
|
||||
GrantNotFoundError,
|
||||
InMemoryTenantStore,
|
||||
|
|
@ -57,9 +64,11 @@ def create_app(
|
|||
*,
|
||||
store: TenantStore | None = None,
|
||||
authorizer: WriteAuthorizer | None = None,
|
||||
settings: Settings | None = None,
|
||||
) -> FastAPI:
|
||||
store = store or InMemoryTenantStore()
|
||||
authorizer = authorizer or DefaultDenyWriteAuthorizer()
|
||||
settings = settings or Settings.from_env()
|
||||
authorizer = authorizer or _build_authorizer(settings)
|
||||
|
||||
app = FastAPI(title="tenant-engine", version=__version__)
|
||||
app.state.store = store
|
||||
|
|
@ -158,6 +167,16 @@ def create_app(
|
|||
return app
|
||||
|
||||
|
||||
def _build_authorizer(settings: Settings) -> WriteAuthorizer:
|
||||
if settings.flex_auth_base_url is None:
|
||||
return DefaultDenyWriteAuthorizer()
|
||||
client = FlexAuthCheckClient(
|
||||
base_url=settings.flex_auth_base_url,
|
||||
timeout_seconds=settings.flex_auth_timeout_seconds,
|
||||
)
|
||||
return FlexAuthWriteAuthorizer(client=client)
|
||||
|
||||
|
||||
def _read_roles(store: TenantStore, tenant_id: str) -> dict:
|
||||
try:
|
||||
roles = store.active_roles(tenant_id)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue