Implement USER-WP-0024 security layer conformance
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Build and Publish Container Image / build-and-push (push) Successful in 36s

Declare Engine/PIP machine-readably, publish a total fail-closed PEP
stance map, stop minting local decision ids on engine-unavailable DENY,
bind allows to a 30s request lifetime, confine the local authorization
double, classify evidence and emit a denial/revocation heartbeat, and
prove access-control facts remain claims.

Assistant: grok
Assistant-Session: 01a04cea-f0d6-7ab3-9ffd-881eb6bea6cb
This commit is contained in:
tegwick 2026-08-29 12:53:16 +02:00
parent c7b6148a70
commit 4349758608
22 changed files with 1242 additions and 89 deletions

View file

@ -11,6 +11,13 @@ from user_engine.domain import (
AuthorizationDecision,
AuthorizationEffect,
AuthorizationRequest,
utc_now,
)
from user_engine.pep_stance import (
ALLOW_BINDING,
ALLOW_LIFETIME,
DEFAULT_STANCE_SCOPE,
UNREACHABLE_STANCE,
)
@ -86,16 +93,28 @@ class FlexAuthHTTPAdapter:
for item in body.get("obligations", ())
if isinstance(item, dict) and item.get("type")
)
lifetime = ALLOW_LIFETIME if effect in {
AuthorizationEffect.ALLOW,
AuthorizationEffect.AUDIT_ONLY,
} else None
issued_at = utc_now() if lifetime is not None else None
return AuthorizationDecision(
effect=effect,
decision_id=decision_id,
reason=reason,
obligations=obligations,
binding=ALLOW_BINDING,
lifetime=lifetime,
issued_at=issued_at,
)
except (HTTPError, URLError, TimeoutError, OSError, ValueError, KeyError, TypeError):
return AuthorizationDecision(
effect=AuthorizationEffect.DENY,
decision_id=None,
reason="authorization service unavailable",
stance_applied=UNREACHABLE_STANCE,
stance_scope=DEFAULT_STANCE_SCOPE,
binding=ALLOW_BINDING,
)
def batch_check(

View file

@ -3,6 +3,7 @@
from __future__ import annotations
import copy
import os
from contextlib import contextmanager
from dataclasses import dataclass, field
from typing import Iterable, Iterator, Mapping, cast
@ -426,18 +427,25 @@ class InMemoryUserEngineStore:
class LocalAuthorizationCheckPort:
"""Deterministic local authorization adapter.
"""Test and standalone authorization double. Not a production PDP.
Rules are action-specific. The default is allow so isolated tests and local
demos can focus on user-engine behavior while still exercising the port.
Construction fails when a production flex-auth caller token is configured,
so ``create_application()`` cannot assemble this adapter by mistake.
"""
standalone_double = True
def __init__(
self,
*,
default_effect: AuthorizationEffect = AuthorizationEffect.ALLOW,
action_effects: dict[str, AuthorizationEffect] | None = None,
) -> None:
if os.environ.get("USER_ENGINE_FLEX_AUTH_TOKEN_FILE"):
raise RuntimeError(
"LocalAuthorizationCheckPort cannot be constructed in a "
"production runtime (USER_ENGINE_FLEX_AUTH_TOKEN_FILE is set)"
)
self.default_effect = default_effect
self.action_effects = action_effects or {}
self.requests: list[AuthorizationRequest] = []
@ -445,7 +453,7 @@ class LocalAuthorizationCheckPort:
def check(self, request: AuthorizationRequest) -> AuthorizationDecision:
self.requests.append(request)
effect = self.action_effects.get(request.action, self.default_effect)
return AuthorizationDecision(effect=effect, reason="local")
return AuthorizationDecision.for_standalone(effect, reason="local")
def batch_check(
self, requests: Iterable[AuthorizationRequest]