Add verified browser login and human approval HTTP client
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
This commit is contained in:
parent
4e103f62a0
commit
0e48355b9f
16 changed files with 1365 additions and 39 deletions
101
tests/test_approval_component.py
Normal file
101
tests/test_approval_component.py
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
"""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 == []
|
||||
Loading…
Add table
Add a link
Reference in a new issue