informed-decision/tests/test_approval_component.py

102 lines
4.6 KiB
Python
Raw Normal View History

"""Opt-in actual engine contract check, using signed synthetic identity fixtures.
INFD_APPROVAL_ENGINE_SOURCE=/path/to/approval-engine python -m pytest -q
No native credentials, human login, policy verdict or audit admission is proved.
"""
from datetime import datetime, timedelta, timezone
import io
import json
import os
from pathlib import Path
import sys
import jwt
import pytest
from informed_decision.approval_client import ApprovalEngineError
from informed_decision.approval_http import ApprovalHTTPClient
from test_browser_auth import ISSUER, IssuerFixture, finish, signing_key
from informed_decision.oidc import KeyCapeLogin
@pytest.fixture
def component(signing_key, tmp_path):
source = os.environ.get("INFD_APPROVAL_ENGINE_SOURCE")
if not source:
pytest.skip("set INFD_APPROVAL_ENGINE_SOURCE for the real-engine contract check")
assert (Path(source) / "approval_engine" / "api.py").is_file()
sys.path.insert(0, source)
from approval_engine.api import App
from approval_engine.auth import JWTAuthenticator
from approval_engine.store import Engine
issuer = IssuerFixture(signing_key)
login = KeyCapeLogin(ISSUER, transport=issuer)
sid = finish((login, issuer))
session = login.session(sid)
class Keys:
def get_signing_key_from_jwt(self, token):
# Use the exact same synthetic JWKS that the login client verified.
return jwt.PyJWK.from_dict(issuer.request("GET", ISSUER + "/jwks")[1]["keys"][0])
engine = Engine(tmp_path / "approval.db")
auth = JWTAuthenticator(issuer=ISSUER, audience="approval-engine", jwks_url=ISSUER + "/jwks", jwks_client=Keys())
app = App(engine, auth)
class Transport:
def __init__(self): self.calls = []
def request(self, method, url, *, headers=None, body=None):
self.calls.append((method, url))
payload = body or b""
environ = {"PATH_INFO": url.removeprefix("https://approval.test"), "REQUEST_METHOD": method,
"CONTENT_LENGTH": str(len(payload)), "wsgi.input": io.BytesIO(payload),
"HTTP_AUTHORIZATION": (headers or {}).get("Authorization", "")}
result = {}
def start(status, headers): result["status"] = int(status.split()[0])
raw = b"".join(app(environ, start))
return result["status"], json.loads(raw)
transport = Transport()
now = datetime.now(timezone.utc)
engine.create({"actor": "synthetic-requester", "principal": "factory-fixture",
"action": "deliver", "purpose": "local-component-proof", "target": {"resource": "fixture"}},
{"not_before": (now - timedelta(seconds=1)).isoformat(),
"expires_at": (now + timedelta(minutes=10)).isoformat()},
human_control=True, approval_id="fixture")
client = ApprovalHTTPClient("https://approval.test", session, transport=transport)
yield client, engine, transport, session, signing_key
def test_real_engine_verifies_human_access_jwt_and_duplicate_correlation(component):
client, engine, transport, session, key = component
result = client.add_entry("fixture")
assert result.subject == session.subject and result.status == "approved"
entry = engine.get("fixture").as_dict()["entries"][0]
assert result.approved_at == entry["approved_at"]
assert entry["principal_type"] == "human" and entry["evidence_ref"].startswith("jwt-sha256:")
duplicate = client.add_entry("fixture")
assert duplicate.duplicate and duplicate.correlation == result.correlation
assert len(engine.get("fixture").entries) == 1
assert not any(url.endswith("/consume") for _, url in transport.calls)
def test_real_engine_service_token_cannot_bind_declared_control(component):
client, engine, transport, session, key = component
claims = jwt.decode(session.access_token, options={"verify_signature": False})
claims["principal_type"] = "service"
service = jwt.encode(claims, key, algorithm="RS256", headers={"kid": "key-1"})
status, body = transport.request("POST", "https://approval.test/v1/approvals/fixture/entries",
headers={"Authorization": "Bearer " + service}, body=b"{}")
assert (status, body["error"]) == (403, "forbidden")
assert engine.get("fixture").entries == []
def test_real_engine_revocation_stays_a_conflict(component):
client, engine, transport, session, key = component
engine.revoke("fixture")
with pytest.raises(ApprovalEngineError) as error: client.add_entry("fixture")
assert error.value.status == 409 and error.value.reason == "conflict"
assert engine.get("fixture").entries == []