Implement TEN-WP-0011 security layer conformance
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 37s
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 37s
Engine/PIP declaration is now checkable (layer.yaml plus a Tooling-client scan). Writes persist a decision record or the published fail-closed stance, live-lookup freshness is published, events_for is tenant-scoped, and mutation evidence drains to audit-core from a local outbox without blocking the mutation. Sender registration is requested as AUDIT-IN-0002. Boundary-contract amendment is requested as NET-IN-0002. Assistant: grok Assistant-Session: 01a04cea-e5e8-7081-a0fc-808ebbc35fa9
This commit is contained in:
parent
80961af91e
commit
672cf4da6e
40 changed files with 2285 additions and 361 deletions
|
|
@ -2,32 +2,15 @@
|
|||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from helpers import AllowAllAuthorizer, ScopedAuthorizer
|
||||
|
||||
from tenant_engine.app import create_app
|
||||
from tenant_engine.authz import WriteAuthorizationDeniedError, WriteAuthorizer
|
||||
from tenant_engine.store import InMemoryTenantStore, StoreUnavailableError
|
||||
|
||||
HEADERS = {"Idempotency-Key": "idem-1", "If-Match": '"1"'}
|
||||
BODY = {"actor": "portal", "reason": "operator request", "correlation_id": "corr-1"}
|
||||
|
||||
|
||||
class _AllowAllAuthorizer(WriteAuthorizer):
|
||||
def authorize(self, *, action: str, tenant_id: str, actor: str) -> None:
|
||||
return None
|
||||
|
||||
|
||||
class _ScopedAuthorizer(WriteAuthorizer):
|
||||
"""Allows only the listed actions -- stands in for a flex-auth policy that
|
||||
grants an operator metadata edits but not retirement."""
|
||||
|
||||
def __init__(self, *allowed: str) -> None:
|
||||
self._allowed = set(allowed)
|
||||
|
||||
def authorize(self, *, action: str, tenant_id: str, actor: str) -> None:
|
||||
if action not in self._allowed:
|
||||
raise WriteAuthorizationDeniedError(action, "not permitted")
|
||||
|
||||
|
||||
class _BrokenStore(InMemoryTenantStore):
|
||||
def mutate_tenant(self, **kwargs):
|
||||
raise StoreUnavailableError("connection to /var/lib/tenant-engine/tenant.db refused")
|
||||
|
|
@ -38,7 +21,7 @@ class _BrokenStore(InMemoryTenantStore):
|
|||
|
||||
@pytest.fixture
|
||||
def client() -> TestClient:
|
||||
app = create_app(store=InMemoryTenantStore(), authorizer=_AllowAllAuthorizer())
|
||||
app = create_app(store=InMemoryTenantStore(), authorizer=AllowAllAuthorizer())
|
||||
test_client = TestClient(app)
|
||||
test_client.post(
|
||||
"/tenants",
|
||||
|
|
@ -79,9 +62,7 @@ def test_get_tenant_returns_record_and_etag(client) -> None:
|
|||
|
||||
|
||||
def test_get_tenant_resolves_by_identifier(client) -> None:
|
||||
response = client.get(
|
||||
"/tenants/tenant:friendly:binky", params={"actor": "tenant-engine"}
|
||||
)
|
||||
response = client.get("/tenants/tenant:friendly:binky", params={"actor": "tenant-engine"})
|
||||
assert response.status_code == 200
|
||||
assert response.json()["tenant_id"] == "t-1"
|
||||
|
||||
|
|
@ -278,7 +259,7 @@ def test_lifecycle_mutations_are_denied_by_default() -> None:
|
|||
def test_update_permission_does_not_imply_retire_permission() -> None:
|
||||
app = create_app(
|
||||
store=InMemoryTenantStore(),
|
||||
authorizer=_ScopedAuthorizer("tenant.create", "tenant.update"),
|
||||
authorizer=ScopedAuthorizer("tenant.create", "tenant.update"),
|
||||
)
|
||||
client = TestClient(app)
|
||||
client.post(
|
||||
|
|
@ -292,7 +273,7 @@ def test_update_permission_does_not_imply_retire_permission() -> None:
|
|||
|
||||
|
||||
def test_store_outage_is_a_redacted_503() -> None:
|
||||
client = TestClient(create_app(store=_BrokenStore(), authorizer=_AllowAllAuthorizer()))
|
||||
client = TestClient(create_app(store=_BrokenStore(), authorizer=AllowAllAuthorizer()))
|
||||
|
||||
read = client.get("/tenants/t-1", params={"actor": "tenant-engine"})
|
||||
write = client.patch(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue