test_phase3_wp02_enterprise_identity_contract.py 5.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. from pathlib import Path
  2. ROOT = Path(__file__).resolve().parents[1]
  3. def test_enterprise_identity_migration_is_normalized_forward_only():
  4. content = (ROOT / "migrations/versions/20260802_470_enterprise_identity.py").read_text()
  5. assert 'revision = "20260802_470"' in content
  6. assert 'down_revision = "20260802_460"' in content
  7. for table in (
  8. "identity_idp_config_versions", "identity_oidc_flows", "enterprise_identity_links",
  9. "identity_organization_nodes", "identity_directory_events", "identity_sessions",
  10. "identity_exchange_codes", "identity_emergency_requests", "identity_emergency_approvals",
  11. "identity_audit_events", "identity_directory_checkpoints", "identity_directory_conflicts",
  12. ):
  13. assert f"CREATE TABLE public.{table}" in content
  14. hardening = (ROOT / "migrations/versions/20260802_471_enterprise_identity_hardening.py").read_text()
  15. assert 'revision = "20260802_471"' in hardening
  16. assert 'down_revision = "20260802_470"' in hardening
  17. assert "^env:DATAOPS_OIDC_[A-Z0-9_]+$" in hardening
  18. assert "refresh_hash CHAR(64) NOT NULL UNIQUE" in content
  19. assert "emergency_request_uid UUID" in content
  20. assert "Forward-only migration" in content
  21. def test_identity_api_uses_exact_public_routes_and_scoped_permissions():
  22. permissions = (ROOT / "app/core/system/permissions.py").read_text()
  23. api = (ROOT / "app/api/system/enterprise_identity.py").read_text()
  24. for permission in ("identity:read", "identity:manage", "identity:operate"):
  25. assert permission in permissions
  26. for path in ("providers", "authorize", "callback", "exchange", "refresh"):
  27. assert f'"/api/system/identity/{path}"' in permissions
  28. assert 'path.startswith("/api/system/auth/")' not in permissions
  29. for endpoint in ("/identity/providers", "/identity/authorize", "/identity/callback", "/identity/exchange",
  30. "/identity/refresh", "/identity/logout", "/identity/idp-versions", "/identity/claims/dry-run",
  31. "/identity/directory/delta", "/identity/sessions", "/identity/emergency/requests", "/identity/audit"):
  32. assert endpoint in api
  33. assert "OidcTransport" in api
  34. assert "client_secret" in (ROOT / "app/core/system/oidc.py").read_text()
  35. assert "safe_detail" in api
  36. assert "access_token" not in api.split("response = redirect", 1)[1].split("return response", 1)[0]
  37. repository = (ROOT / "app/core/system/identity_repository.py").read_text()
  38. assert "WHERE refresh_hash=:refresh_hash AND status='active'" in repository
  39. assert "RETURNING family_uid::text" in repository
  40. assert "provider_uid=CAST(:provider AS uuid) AND enterprise_subject=:subject" in repository
  41. assert "provider_disabled" in api
  42. for audit_event in ("idp_dry_run", "idp_connectivity", "claims_dry_run", "identity_refresh",
  43. "identity_logout", "identity_session_revoke", "identity_session_risk_terminate",
  44. "identity_session_revoke_all", "emergency_request"):
  45. assert f'"{audit_event}"' in api
  46. assert "pg_advisory_xact_lock" in repository
  47. assert "SELECT id FROM public.users WHERE id=CAST(:user AS uuid) FOR UPDATE" in repository
  48. assert "put_organization_node" in repository and "apply_directory_event_atomic" in repository
  49. assert 'visible = ("uid", "user_uid", "identity_source", "status", "created_at", "last_seen_at", "expires_at")' in api
  50. assert '"family_uid"' not in api.split("def identity_sessions():", 1)[1].split("def identity_session_revoke", 1)[0]
  51. auth = (ROOT / "app/core/system/auth.py").read_text()
  52. assert 'if active_provider and not claims.get("sid")' in auth
  53. assert "enterprise identity state unavailable; local login denied" in auth
  54. assert "provider_configuration_changed" in api
  55. assert "commit=False" in api and "insert_exchange_code" in api
  56. def test_frontend_has_sso_callback_secure_storage_single_refresh_and_management_surface():
  57. auth = (ROOT / "frontend/src/utils/auth.js").read_text()
  58. request = (ROOT / "frontend/src/utils/request.js").read_text()
  59. routes = (ROOT / "frontend/src/router/defaultRoute.js").read_text()
  60. menu = (ROOT / "frontend/src/router/routes.js").read_text()
  61. callback = (ROOT / "frontend/src/views/login/callback.vue").read_text()
  62. management = (ROOT / "frontend/src/views/systemManage/enterpriseIdentity/index.vue").read_text()
  63. user_store = (ROOT / "frontend/src/store/modules/user.js").read_text()
  64. assert "sessionStorage.getItem('access_token')" in auth
  65. assert "localStorage.removeItem('access_token')" in auth
  66. assert "delete profile.token" in user_store and "delete userInfo.token" in user_store
  67. assert "let refreshPromise = null" in request
  68. assert "__identityRetry" in request
  69. assert "'/login/callback'" in routes and "noLogin: true" in routes
  70. assert "history.replaceState" in callback and "exchangeIdentityCode" in callback
  71. assert "/systemManage/enterprise-identity" in menu and "identity:manage" in menu
  72. assert "待企业 IdP UAT" in management
  73. for operation in ("createIdpVersion", "runIdpAction", "claimsDryRun", "syncDirectoryDelta",
  74. "requestEmergencyAccess", "runEmergencyAction"):
  75. assert operation in management
  76. def test_phase3_identity_docs_and_status_are_fail_closed():
  77. delivery = (ROOT / "docs/phase3/P3_WP02_ENTERPRISE_IDENTITY.md").read_text()
  78. evidence = (ROOT / "docs/validation/P3_WP02_ENTERPRISE_IDENTITY_EVIDENCE.md").read_text()
  79. required = (ROOT / "docs/phase3/P3_WP00_REQUIREMENTS.json").read_text()
  80. status = "ENGINEERING_COMPLETE_ENTERPRISE_IDP_UAT_BLOCKED"
  81. assert status in delivery and status in evidence and status in required
  82. assert "TBD_EXTERNAL" in delivery and "TBD_EXTERNAL" in evidence