test_security_governance_api.py 3.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. from __future__ import annotations
  2. USER_UID = "01900000-0000-7000-8000-000000018801"
  3. FINDING_UID = "01900000-0000-7000-8000-000000018802"
  4. class FakeSecurityService:
  5. def __init__(self):
  6. self.calls = []
  7. def dashboard(self):
  8. return {"pending_classification_reviews": 1, "open_vulnerability_count": 2}
  9. def list_classification_findings(self, **filters):
  10. self.calls.append(("findings", filters))
  11. return [{"uid": FINDING_UID, "status": "pending_review"}]
  12. def scan_sensitive_sample(self, payload, actor_uid):
  13. self.calls.append(("scan", payload, actor_uid))
  14. return {"uid": "scan-1", "sample_retained": False, "findings": []}
  15. def evaluate_access(self, payload):
  16. self.calls.append(("evaluate", payload))
  17. return {"uid": "decision-1", "decision": "denied", "reason_code": "default_deny"}
  18. def review_classification_finding(self, uid, payload, expected_version, actor_uid):
  19. self.calls.append(("review", uid, payload, expected_version, actor_uid))
  20. return {"uid": uid, "status": "confirmed", "current_version": 2}
  21. def _headers(role, **extra):
  22. return {"Authorization": f"Bearer {role}", **extra}
  23. def _client(monkeypatch):
  24. from app import create_app
  25. from app.api.system import security_governance
  26. service = FakeSecurityService()
  27. monkeypatch.setattr(security_governance, "_service", lambda: service)
  28. monkeypatch.setattr(
  29. "app.core.system.auth.load_identity_from_token",
  30. lambda token, secret: (
  31. {"id": USER_UID, "username": token, "roles": [token]}
  32. if token in {"viewer", "editor", "admin"}
  33. else None
  34. ),
  35. )
  36. app = create_app()
  37. app.config.update(TESTING=True)
  38. return app.test_client(), service
  39. def test_security_governance_role_boundaries_and_server_bound_identity(monkeypatch):
  40. client, service = _client(monkeypatch)
  41. assert client.get(
  42. "/api/system/security-governance/dashboard", headers=_headers("viewer")
  43. ).status_code == 200
  44. assert client.get(
  45. "/api/system/security-governance/findings?status=pending_review",
  46. headers=_headers("viewer"),
  47. ).status_code == 200
  48. forbidden = client.post(
  49. "/api/system/security-governance/scans", json={}, headers=_headers("viewer")
  50. )
  51. assert forbidden.status_code == 403
  52. assert client.post(
  53. "/api/system/security-governance/scans", json={}, headers=_headers("editor")
  54. ).status_code == 201
  55. decision = client.post(
  56. "/api/system/security-governance/access/evaluate",
  57. json={"user_uid": "forged", "roles": ["admin"]},
  58. headers=_headers("editor"),
  59. )
  60. assert decision.status_code == 201
  61. body = service.calls[-1][1]
  62. assert body["user_uid"] == USER_UID
  63. assert body["roles"] == ["editor"]
  64. def test_independent_review_is_admin_only_and_requires_etag(monkeypatch):
  65. client, service = _client(monkeypatch)
  66. path = f"/api/system/security-governance/findings/{FINDING_UID}/review"
  67. assert client.post(path, json={}, headers=_headers("editor")).status_code == 403
  68. assert client.post(path, json={}, headers=_headers("admin")).status_code == 400
  69. response = client.post(
  70. path,
  71. json={"decision": "confirm"},
  72. headers=_headers("admin", **{"If-Match": '"1"'}),
  73. )
  74. assert response.status_code == 200
  75. assert response.headers["ETag"] == '"2"'
  76. assert service.calls[-1][3] == 1