Files
kefu/wechat_rpa/test_group_authorization.py
T
2026-11-04 10:14:23 +08:00

485 lines
27 KiB
Python

"""Real FastAPI + isolated public Python copies + synthetic SQLite/key only."""
from __future__ import annotations
import atexit
import faulthandler
import hashlib
import hmac
import json
import os
import secrets
import shutil
import sys
import tempfile
import unittest
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
from unittest import mock
MIRROR = Path(__file__).resolve().parent
SOURCE = Path(os.environ.get("IAM_KEFU_SOURCE_ROOT", "C:/kefu")) / "wechat_rpa"
TEST_RUNTIME = MIRROR.parents[2] / ".test-runtime"
TEST_RUNTIME.mkdir(parents=True, exist_ok=True)
RUNTIME = tempfile.TemporaryDirectory(prefix="iam-kefu-authorization-", dir=TEST_RUNTIME)
ISOLATED = Path(RUNTIME.name)
def cleanup_runtime():
os.chdir(TEST_RUNTIME)
RUNTIME.cleanup()
atexit.register(cleanup_runtime)
# Copy no settings, .env, database, production key, or assets from the real app.
for source in SOURCE.glob("*.py"):
shutil.copyfile(source, ISOLATED / source.name)
for name in ("admin_api.py", "admin_backend.py", "rbac.py", "group_sso.py", "group_access.py", "group_authorization.py"):
shutil.copyfile(MIRROR / name, ISOLATED / name)
os.chdir(ISOLATED)
RUNTIME_ENV = {key: value for key, value in os.environ.items()
if key.upper() in {"SYSTEMROOT", "WINDIR", "TEMP", "TMP"}}
os.environ.clear()
os.environ.update(RUNTIME_ENV)
sys.path.insert(0, str(ISOLATED))
from fastapi.testclient import TestClient
import admin_api
import admin_backend as backend
import group_access
import group_authorization as authorization
import rbac
KEY = bytes.fromhex("34" * 32)
NOW = 1_800_000_000
GID = "gid_" + "a" * 32
OTHER_GID = "gid_" + "b" * 32
PASSWORD = "Fixture-Authorization-Password-2026"
class GroupAuthorizationTests(unittest.TestCase):
def setUp(self):
self.directory = tempfile.TemporaryDirectory(dir=ISOLATED)
self.addCleanup(self.directory.cleanup)
self.root = Path(self.directory.name)
self.key_file = self.root / "synthetic.key"
self.key_file.write_text(KEY.hex(), encoding="ascii")
self.env = mock.patch.dict(os.environ, {"ZYT_GROUP_METADATA_KEY_FILE": str(self.key_file)}, clear=True)
self.env.start()
self.addCleanup(self.env.stop)
self.db = backend.Database(self.root / "synthetic.sqlite")
self.clock = mock.Mock(return_value=NOW)
with mock.patch.object(backend, "load_initial_config", return_value=backend.CONFIG_DEFAULTS.copy()):
self.db.initialize(PASSWORD)
self.service = authorization.GroupAuthorization(self.db, clock=self.clock)
with mock.patch.object(authorization, "GroupAuthorization", return_value=self.service):
self.client = TestClient(admin_api.create_app(self.db.path))
self.addCleanup(self.client.close)
def identity(self, gid=GID, source_id=7):
return {"client_id": "kefu", "gid": gid, "source_admin_id": source_id, "company_id": 2}
def enroll(self, gid=GID, source_id=7):
return self.db.group_sso_user({**self.identity(gid, source_id),
"account": "same-account", "name": "合成集团用户"})
def raw(self, value):
return json.dumps(value, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
def headers(self, raw, path, *, nonce=None, timestamp=None, key=KEY):
nonce = nonce or secrets.token_hex(16)
timestamp = str(NOW if timestamp is None else timestamp)
message = f"POST\n{path}\n{timestamp}\n{nonce}\n{hashlib.sha256(raw).hexdigest()}"
return {"Content-Type": "application/json", "X-Group-Timestamp": timestamp,
"X-Group-Nonce": nonce,
"X-Group-Signature": hmac.new(key, message.encode("ascii"), hashlib.sha256).hexdigest()}
def fetch(self, body, path=authorization.OPTIONS_PATH, *, raw=None, query="", **signing):
raw = self.raw(body) if raw is None else raw
headers = self.headers(raw, path, **signing)
response = self.client.post("/api/v2" + path + query, content=raw, headers=headers)
self.assertEqual(response.headers.get("Cache-Control"), "no-store")
if response.status_code not in {401, 413} and "X-Group-Response-Signature" in response.headers:
self.assertEqual(response.headers["X-Group-Response-Signature"],
group_access.response_signature(KEY, response.content, headers["X-Group-Nonce"]))
if response.status_code != 200 and response.status_code not in {401, 413}:
self.assertEqual(response.json(), {"version": 1, "client_id": "kefu", "generated_at": int(self.clock()),
"error": {"code": response.status_code, "message": response.json()["error"]["message"]}})
self.assertIn("X-Group-Response-Signature", response.headers)
return response
def options(self, identity=None):
result = self.fetch(identity or self.identity())
self.assertEqual(result.status_code, 200, result.text)
return result.json()
def mutation(self, revision=None, role="operator", permissions=None, identity=None, **changes):
return {**(identity or self.identity()), "actor_source_admin_id": 9,
"request_id": secrets.token_hex(16), "expected_revision": revision or self.options(identity)["revision"],
"role_code": role, "permissions": permissions, **changes}
def update(self, body=None, **changes):
return self.fetch(body or self.mutation(**changes), authorization.UPDATE_PATH)
def fingerprint(self):
tables = ("users", "roles", "role_permissions", "auth_tokens", "permissions", "group_sso_bindings",
"audit_log", "group_authorization_audit", "group_authorization_private_roles", "group_authorization_requests")
with self.db.connect() as db:
return {table: [tuple(row) for row in db.execute(f"SELECT * FROM {table} ORDER BY rowid")]
for table in tables}
def token_headers(self, user_id):
token, _ = self.db.create_token(user_id, "api", "existing-synthetic-session", 3600)
return {"Authorization": "Bearer " + token}
def test_options_exact_native_projection_is_readonly(self):
user = self.enroll()
self.db.update_user(user["id"], "operator", True, "", 1, "fixture")
with self.db.connect() as db:
db.execute("UPDATE permissions SET enabled=0 WHERE code='config:read'")
db.commit()
before = self.fingerprint()
data = self.options()
self.assertEqual(set(data), {"version", "client_id", "generated_at", "subject", "revision", "roles", "catalog"})
self.assertRegex(data["revision"], r"^[0-9a-f]{64}$")
subject = data["subject"]
self.assertEqual(set(subject), {"gid", "found", "source_admin_id", "company_id", "user"})
self.assertEqual(subject["gid"], GID)
self.assertEqual(subject["user"]["permissions"], sorted(self.db.permissions_for_user(user["id"])))
self.assertIn("config:read", subject["user"]["permissions"])
for role in data["roles"]:
self.assertEqual(set(role), {"code", "name", "permissions"})
for item in data["catalog"]["permissions"]:
self.assertEqual(set(item), {"code", "name", "group", "page", "kind", "source", "enabled", "sort"})
text = self.raw(data).decode("utf-8")
for forbidden in ("password_salt", "password_digest", "link_url", "user_count", str(self.key_file), KEY.hex()):
self.assertNotIn(forbidden, text)
self.assertEqual(before, self.fingerprint())
def test_role_assignment_existing_token_gains_and_loses_without_login(self):
user = self.enroll()
other = self.enroll(OTHER_GID, 8)
self.db.update_user(other["id"], "viewer", True, "", 1, "fixture")
headers = self.token_headers(user["id"])
other_headers = self.token_headers(other["id"])
initial = self.fingerprint()
other_before = self.client.get("/api/v2/me", headers=other_headers).json()
self.assertEqual(self.client.get("/api/v2/stats/model-calls", headers=headers).status_code, 403)
self.assertEqual(self.client.get("/api/v2/config", headers=headers).status_code, 403)
first = self.update()
self.assertEqual(first.status_code, 200, first.text)
self.assertEqual(first.json()["subject"]["user"]["role"]["code"], "operator")
self.assertEqual(self.client.get("/api/v2/stats/model-calls", headers=headers).status_code, 200)
self.assertEqual(self.client.get("/api/v2/config", headers=headers).status_code, 200)
self.assertEqual(self.client.get("/api/v2/me", headers=headers).json()["role"], "operator")
second = self.update(role="zyt_basic", revision=first.json()["revision"])
self.assertEqual(second.status_code, 200, second.text)
self.assertEqual(self.client.get("/api/v2/stats/model-calls", headers=headers).status_code, 403)
self.assertEqual(self.client.get("/api/v2/config", headers=headers).status_code, 403)
self.assertEqual(self.client.get("/api/v2/me", headers=headers).json()["permissions"], [])
self.assertEqual(self.client.get("/api/v2/me", headers=other_headers).json(), other_before)
final = self.fingerprint()
for table in ("roles", "role_permissions", "permissions", "group_sso_bindings"):
self.assertEqual(initial[table], final[table])
# session() updates last_used_at; identity, token/CSRF digests, expiry,
# credentials, status, password policy and all session rows are retained.
self.assertEqual([row[:-1] for row in initial["auth_tokens"]], [row[:-1] for row in final["auth_tokens"]])
for before, after in zip(initial["users"], final["users"]):
self.assertEqual(before[:-1], after[:-1]) # updated_at only may move
def test_explicit_permissions_are_private_empty_is_valid_and_other_users_unchanged(self):
user = self.enroll()
other = self.enroll(OTHER_GID, 8)
shared = self.fingerprint()["role_permissions"]
granted = ["config:read", "stats:read"]
result = self.update(permissions=granted)
self.assertEqual(result.status_code, 200, result.text)
first_role = result.json()["subject"]["user"]["role"]["code"]
self.assertNotEqual(first_role, "operator")
self.assertEqual(self.db.permissions_for_user(user["id"]), set(granted))
self.assertEqual(self.db.permissions_for_user(other["id"]), set())
with self.db.connect() as db:
unchanged = [tuple(row) for row in db.execute("SELECT * FROM role_permissions WHERE role_code!=? ORDER BY rowid", (first_role,))]
self.assertEqual(shared, unchanged)
second = self.update(role="operator", permissions=granted, identity=self.identity(OTHER_GID, 8))
self.assertEqual(second.status_code, 200, second.text)
self.assertNotEqual(first_role, second.json()["subject"]["user"]["role"]["code"])
self.assertNotIn(first_role, {role["code"] for role in second.json()["roles"]})
attempt = self.update(role=first_role, identity=self.identity(OTHER_GID, 8))
self.assertEqual(attempt.status_code, 422)
empty = self.update(permissions=[])
self.assertEqual(empty.status_code, 200, empty.text)
self.assertEqual(empty.json()["subject"]["user"]["permissions"], [])
self.assertEqual(self.db.permissions_for_user(other["id"]), set(granted))
def test_reuses_matching_private_role_but_forks_when_natively_shared_or_edited(self):
user = self.enroll()
other = self.enroll(OTHER_GID, 8)
first = self.update(permissions=["config:read"]).json()
role = first["subject"]["user"]["role"]["code"]
again = self.update(permissions=["config:read"]).json()
self.assertEqual(again["subject"]["user"]["role"]["code"], role)
self.db.update_user(other["id"], role, True, "", 1, "fixture")
shared_before = self.db.permissions_for_user(other["id"])
shared_fork = self.update(permissions=["config:read"]).json()["subject"]["user"]["role"]["code"]
self.assertNotEqual(shared_fork, role)
self.assertEqual(self.db.permissions_for_user(other["id"]), shared_before)
self.db.save_role(shared_fork, "KF 原生改名", ["stats:read"], 1, "fixture")
edited_fork = self.update(permissions=["config:read"]).json()["subject"]["user"]["role"]["code"]
self.assertNotIn(edited_fork, {role, shared_fork})
with self.db.connect() as db:
self.assertEqual(rbac.permissions_of(db, shared_fork), {"stats:read"})
def test_inactive_user_stays_inactive_and_revision_binds_assigned_permissions(self):
user = self.enroll()
self.db.update_user(user["id"], "viewer", False, "", 1, "fixture")
before = self.options()
result = self.update(revision=before["revision"], permissions=["config:read"])
self.assertEqual(result.status_code, 200, result.text)
self.assertFalse(result.json()["subject"]["user"]["active"])
self.assertEqual(result.json()["subject"]["user"]["permissions"], [])
role = result.json()["subject"]["user"]["role"]["code"]
self.assertEqual(next(item["permissions"] for item in result.json()["roles"] if item["code"] == role), ["config:read"])
self.assertNotEqual(before["revision"], result.json()["revision"])
def test_last_active_native_admin_is_preserved(self):
user = self.enroll()
self.db.update_user(user["id"], "admin", True, "", 1, "fixture")
with self.db.connect() as db:
db.execute("UPDATE users SET active=0 WHERE id=1")
db.commit()
before = self.fingerprint()
for permissions, role in ((None, "viewer"), ([], "admin"), (list(self.db.permissions_for_user(user["id"])), "admin")):
self.assertEqual(self.update(role=role, permissions=permissions).status_code, 409)
self.assertEqual(before, self.fingerprint())
self.assertEqual(self.update(role="admin").status_code, 200)
def test_missing_native_same_name_or_mismatched_binding_never_enrolls(self):
self.db.create_user("same-account", PASSWORD, "admin", 1, "fixture")
before = self.fingerprint()
with mock.patch.object(backend.Database, "group_sso_user", side_effect=AssertionError("No enrollment")):
self.assertEqual(self.fetch(self.identity()).status_code, 404)
self.assertEqual(self.update(self.mutation(revision="0" * 64)).status_code, 404)
self.assertEqual(before, self.fingerprint())
self.enroll()
before = self.fingerprint()
for changed in ({"company_id": 3}, {"source_admin_id": 8}):
identity = {**self.identity(), **changed}
self.assertEqual(self.fetch(identity).status_code, 409)
self.assertEqual(self.update(self.mutation(revision="0" * 64, identity=identity)).status_code, 409)
self.assertEqual(before, self.fingerprint())
with self.db.connect() as db:
db.execute("UPDATE group_sso_bindings SET provider='unrelated' WHERE gid=?", (GID,))
db.commit()
self.assertEqual(self.fetch(self.identity()).status_code, 404)
def test_unknown_role_permission_and_stale_revision_rollback(self):
self.enroll()
for changes, expected in (({"role": "missing-role"}, 422), ({"permissions": ["missing:permission"]}, 422),
({"revision": "0" * 64}, 409)):
body = self.mutation(**changes)
before = self.fingerprint()
self.assertEqual(self.update(body).status_code, expected)
self.assertEqual(before, self.fingerprint())
def test_revision_detects_native_role_permission_active_and_identity_changes(self):
user = self.enroll()
self.db.update_user(user["id"], "viewer", True, "", 1, "fixture")
sql_changes = [("UPDATE role_permissions SET permission_code='config:write' WHERE role_code='viewer' AND permission_code='config:read'", ()),
("UPDATE users SET active=0 WHERE id=?", (user["id"],)),
("UPDATE users SET role='operator' WHERE id=?", (user["id"],)),
("UPDATE group_sso_bindings SET source_name='更新姓名' WHERE gid=?", (GID,))]
for sql, parameters in sql_changes:
body = self.mutation()
with self.db.connect() as db:
db.execute(sql, parameters)
db.commit()
before = self.fingerprint()
self.assertEqual(self.update(body).status_code, 409)
self.assertEqual(before, self.fingerprint())
def test_idempotency_is_durable_exact_body_and_revalidates_binding(self):
self.enroll()
body = self.mutation(permissions=["stats:read"])
first = self.update(body)
self.assertEqual(first.status_code, 200, first.text)
fingerprint = self.fingerprint()
# Simulate another worker/restart using the same SQLite file.
restarted = authorization.GroupAuthorization(self.db, clock=self.clock)
self.service._reserve_nonce = restarted._reserve_nonce
second = self.update(body)
self.assertEqual(second.status_code, 200, second.text)
self.assertEqual(first.content, second.content)
self.assertNotEqual(first.headers["X-Group-Response-Signature"], second.headers["X-Group-Response-Signature"])
self.assertEqual(fingerprint, self.fingerprint())
for changed in ({"role_code": "viewer"}, {"actor_source_admin_id": 10}, {"permissions": []}):
self.assertEqual(self.update({**body, **changed}).status_code, 409)
self.assertEqual(fingerprint, self.fingerprint())
with self.db.connect() as db:
db.execute("UPDATE group_sso_bindings SET company_id=3 WHERE gid=?", (GID,))
db.commit()
self.assertEqual(self.update(body).status_code, 409)
def test_idempotent_success_never_crosses_a_reassigned_binding(self):
self.enroll()
replacement = self.enroll(OTHER_GID, 8)
body = self.mutation(permissions=["stats:read"])
self.assertEqual(self.update(body).status_code, 200)
with self.db.connect() as db:
db.execute("DELETE FROM group_sso_bindings WHERE gid=?", (GID,))
db.execute("UPDATE group_sso_bindings SET gid=?,source_admin_id=7 WHERE gid=?", (GID, OTHER_GID))
db.commit()
before = self.fingerprint()
self.assertEqual(self.update(body).status_code, 409)
self.assertEqual(self.db.permissions_for_user(replacement["id"]), set())
self.assertEqual(before, self.fingerprint())
def test_atomic_compare_and_swap_allows_only_one_concurrent_writer(self):
self.enroll()
revision = self.options()["revision"]
bodies = [self.mutation(revision=revision, role="operator"), self.mutation(revision=revision, role="viewer")]
with ThreadPoolExecutor(max_workers=2) as pool:
results = list(pool.map(self.update, bodies))
self.assertEqual(sorted(result.status_code for result in results), [200, 409])
with self.db.connect() as db:
self.assertEqual(db.execute("SELECT COUNT(*) FROM group_authorization_requests").fetchone()[0], 1)
self.assertEqual(db.execute("SELECT COUNT(*) FROM group_authorization_audit").fetchone()[0], 1)
def test_both_audits_are_transactional_and_preserve_asserted_actor(self):
user = self.enroll()
body = self.mutation(permissions=["stats:read"])
result = self.update(body)
self.assertEqual(result.status_code, 200, result.text)
with self.db.connect() as db:
audit = db.execute("SELECT * FROM group_authorization_audit").fetchone()
detail = json.loads(audit["detail_json"])
native = db.execute("SELECT user_id,detail FROM audit_log WHERE action='group.authorization.update'").fetchone()
self.assertEqual(audit["actor_source_admin_id"], 9)
self.assertEqual(audit["target_user_id"], user["id"])
self.assertEqual(detail["request_id"], body["request_id"])
self.assertEqual(detail["before_permissions"], [])
self.assertEqual(detail["after_permissions"], ["stats:read"])
self.assertEqual(detail["after_revision"], result.json()["revision"])
self.assertIsNone(native["user_id"])
self.assertEqual(json.loads(native["detail"])["actor_source_admin_id"], 9)
def test_audit_failure_rolls_back_role_grants_assignment_and_idempotency(self):
self.enroll()
for table in ("audit_log", "group_authorization_audit", "group_authorization_requests"):
body = self.mutation(permissions=["stats:read"])
with self.db.connect() as db:
db.execute(f"CREATE TRIGGER synthetic_audit_failure BEFORE INSERT ON {table} BEGIN SELECT RAISE(ABORT,'synthetic-secret-error'); END")
db.commit()
before = self.fingerprint()
response = self.update(body)
self.assertEqual(response.status_code, 503)
self.assertNotIn("synthetic-secret-error", response.text)
self.assertEqual(before, self.fingerprint())
with self.db.connect() as db:
db.execute("DROP TRIGGER synthetic_audit_failure")
db.commit()
def test_response_bound_failure_rolls_back_mutation(self):
self.enroll()
body = self.mutation(permissions=["stats:read"])
before = self.fingerprint()
with mock.patch.object(authorization, "MAX_RESPONSE_BYTES", 1024):
response = self.update(body)
self.assertEqual(response.status_code, 503)
self.assertEqual(before, self.fingerprint())
def test_bearer_is_never_machine_auth_and_wrong_path_or_signature_fail_before_parse(self):
self.enroll()
token, _ = self.db.create_token(1, "api", "fixture", 3600)
for path in (authorization.OPTIONS_PATH, authorization.UPDATE_PATH):
response = self.client.post("/api/v2" + path, json=self.identity(), headers={"Authorization": "Bearer " + token})
self.assertEqual(response.status_code, 401)
raw = self.raw(self.identity())
for key, signed_path, changed in ((b"x" * 32, path, raw), (KEY, "/api/v2" + path, raw),
(KEY, authorization.UPDATE_PATH if path == authorization.OPTIONS_PATH else authorization.OPTIONS_PATH, raw),
(KEY, path, b"invalid-json")):
headers = self.headers(raw, signed_path, key=key)
with mock.patch.object(authorization, "parse_request", side_effect=AssertionError("Before auth")):
response = self.client.post("/api/v2" + path, content=changed, headers=headers)
self.assertEqual(response.status_code, 401)
def test_nonce_replay_persists_across_instances_failures_and_concurrent_workers(self):
self.enroll()
nonce = "d" * 32
self.assertEqual(self.fetch(self.identity(), nonce=nonce).status_code, 200)
restarted = authorization.GroupAuthorization(self.db, clock=self.clock)
self.service._reserve_nonce = restarted._reserve_nonce
self.assertEqual(self.fetch(self.identity(), nonce=nonce).status_code, 401)
invalid = {**self.identity(), "extra": 1}
self.assertEqual(self.fetch(invalid, nonce="e" * 32).status_code, 422)
self.assertEqual(self.fetch(invalid, nonce="e" * 32).status_code, 401)
raw = self.raw(self.identity())
headers = self.headers(raw, authorization.OPTIONS_PATH, nonce="f" * 32)
with ThreadPoolExecutor(max_workers=4) as pool:
statuses = list(pool.map(lambda _: self.client.post("/api/v2" + authorization.OPTIONS_PATH,
content=raw, headers=headers).status_code, range(8)))
self.assertEqual(statuses.count(200), 1)
self.assertEqual(statuses.count(401), 7)
def test_clock_bounds_and_bounded_nonce_store(self):
self.enroll()
for offset in (-91, 91):
self.assertEqual(self.fetch(self.identity(), timestamp=NOW + offset).status_code, 401)
for offset in (-90, 90):
self.assertEqual(self.fetch(self.identity(), timestamp=NOW + offset).status_code, 200)
with self.db.connect() as db:
count = db.execute("SELECT COUNT(*) FROM group_authorization_nonces").fetchone()[0]
self.service.max_nonces = count
self.assertEqual(self.fetch(self.identity()).status_code, 503)
self.clock.return_value = NOW + 181
self.assertEqual(self.fetch(self.identity(), timestamp=NOW + 181).status_code, 200)
def test_strict_json_fields_and_types_signed_safe_errors(self):
self.enroll()
mutation = self.mutation()
invalid_options = [[], {}, {**self.identity(), "role_code": "admin"},
{**self.identity(), "client_id": "hospital"}, {**self.identity(), "source_admin_id": True},
{**self.identity(), "company_id": 0}, {**self.identity(), "gid": "gid_" + "A" * 32}]
for body in invalid_options:
self.assertEqual(self.fetch(body).status_code, 422)
invalid_updates = [{**mutation, "actor_source_admin_id": True}, {**mutation, "request_id": "A" * 32},
{**mutation, "expected_revision": "A" * 64}, {**mutation, "role_code": ""},
{**mutation, "permissions": "stats:read"}, {**mutation, "permissions": [True]},
{**mutation, "permissions": ["stats:read", "stats:read"]},
{**mutation, "permissions": [f"fixture:{i}" for i in range(2001)]},
{**mutation, "actor_id": 1}]
for body in invalid_updates:
self.assertEqual(self.update(body).status_code, 422)
for raw in (b' {"client_id":"kefu","client_id":"kefu","gid":"x","source_admin_id":7,"company_id":2}',
b'{"client_id":NaN}', b"\xff", b"not-json"):
self.assertEqual(self.fetch(None, raw=raw).status_code, 422)
def test_duplicate_security_headers_query_and_oversize_requests(self):
self.enroll()
raw = self.raw(self.identity())
for name in ("X-Group-Nonce", "X-Group-Timestamp", "X-Group-Signature"):
headers = list(self.headers(raw, authorization.OPTIONS_PATH).items())
headers.append((name, "f" * 32))
self.assertEqual(self.client.post("/api/v2" + authorization.OPTIONS_PATH, content=raw, headers=headers).status_code, 401)
self.assertEqual(self.fetch(self.identity(), query="?identity=extra").status_code, 422)
self.assertEqual(self.fetch(None, raw=b" " * (authorization.MAX_REQUEST_BYTES + 1)).status_code, 413)
def test_invalid_key_fails_closed_without_paths_or_key_material(self):
for configured in ("", "relative.key", str(self.root / "missing.key")):
with mock.patch.dict(os.environ, {"ZYT_GROUP_METADATA_KEY_FILE": configured}):
raw = self.raw(self.identity())
response = self.client.post("/api/v2" + authorization.OPTIONS_PATH,
content=raw, headers=self.headers(raw, authorization.OPTIONS_PATH))
self.assertEqual(response.status_code, 503)
self.assertNotIn(str(self.root), response.text)
self.assertNotIn(KEY.hex(), response.text)
if __name__ == "__main__":
faulthandler.dump_traceback_later(90, exit=True)
try:
unittest.main(verbosity=2)
finally:
faulthandler.cancel_dump_traceback_later()