test_permission_matrix.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. def test_fixed_role_permission_matrix_is_monotonic():
  2. from app.core.system.permissions import ROLE_PERMISSIONS
  3. viewer = ROLE_PERMISSIONS["viewer"]
  4. editor = ROLE_PERMISSIONS["editor"]
  5. admin = ROLE_PERMISSIONS["admin"]
  6. assert viewer < editor < admin
  7. assert "governance:read" in viewer
  8. assert "governance:edit" in editor
  9. assert "users:manage" in admin
  10. assert "workflow:activate" in admin
  11. assert "ingestion:run" in editor
  12. assert "data-elements:edit" in editor
  13. assert "ontologies:edit" in editor
  14. assert "governance:responsibilities:read" in viewer
  15. assert "ingestion:admin" in admin
  16. assert "evidence:download" in admin
  17. assert "data-elements:publish" in admin
  18. assert "ontologies:publish" in admin
  19. assert "governance:responsibilities:manage" in admin
  20. assert "device-assets:edit" in editor
  21. assert "device-semantics:edit" in editor
  22. assert "device-semantics:review" not in editor
  23. assert "device-semantics:review" in admin
  24. assert "device-entities:edit" in editor
  25. assert "device-entities:review" not in editor
  26. assert "device-entities:review" in admin
  27. assert "device-quality:edit" in editor
  28. assert "device-quality:execute" in editor
  29. assert "device-quality:publish" not in editor
  30. assert "device-quality:publish" in admin
  31. assert "quality-issues:edit" in editor
  32. assert "quality-issues:review" not in editor
  33. assert "quality-issues:review" in admin
  34. assert "device-observability:edit" in editor
  35. assert "data-observability:read" in viewer
  36. assert "data-observability:operate" in editor
  37. assert "data-observability:manage" not in editor
  38. assert "data-observability:manage" in admin
  39. assert "governance-audit:read" not in editor
  40. assert "governance-audit:read" in admin
  41. assert "governance-audit:seal" in admin
  42. assert "edge-gateways:read" in viewer
  43. assert "edge-gateways:operate" in editor
  44. assert "edge-gateways:manage" not in editor
  45. assert "edge-gateways:manage" in admin
  46. def test_edge_gateway_machine_routes_are_public_but_human_routes_are_governed():
  47. from app.core.system.permissions import PUBLIC, permission_for_request
  48. gateway = "11111111-1111-1111-1111-111111111111"
  49. assert permission_for_request("/api/datasource/edge/register", "POST") == (PUBLIC,)
  50. for suffix in ("heartbeat", "tasks/pull", "events", "releases/ack", "reconcile", "tasks/task-1/outcome"):
  51. assert permission_for_request(f"/api/datasource/edge/gateways/{gateway}/{suffix}", "POST") == (PUBLIC,)
  52. assert permission_for_request("/api/datasource/edge/enrollments", "POST") == ("edge-gateways:manage",)
  53. assert permission_for_request("/api/datasource/edge/gateways", "GET") == ("edge-gateways:read",)
  54. assert permission_for_request("/api/datasource/edge/tasks", "POST") == ("edge-gateways:operate",)
  55. assert permission_for_request("/api/datasource/edge/tasks/task-1/cancel", "POST") == ("edge-gateways:operate",)
  56. assert permission_for_request(f"/api/datasource/edge/gateways/{gateway}/rotate", "POST") == ("edge-gateways:manage",)
  57. def test_data_development_paths_have_specific_write_policies():
  58. from app.core.system.permissions import permission_for_request
  59. assert permission_for_request(
  60. "/api/development/v1/ingestion-jobs", "POST"
  61. ) == ("ingestion:run",)
  62. assert permission_for_request(
  63. "/api/development/v1/ingestion-jobs/job-1/retry", "POST"
  64. ) == ("ingestion:admin",)
  65. assert permission_for_request(
  66. "/api/development/v1/ingestion-jobs/job-1/cancel", "POST"
  67. ) == ("ingestion:run",)
  68. assert permission_for_request(
  69. "/api/development/v1/ingestion-jobs/job-1/execute", "POST"
  70. ) == ("ingestion:run",)
  71. assert permission_for_request(
  72. "/api/development/v1/ingestion-jobs/job-1/catalog-snapshots", "GET"
  73. ) == ("governance:read",)
  74. assert permission_for_request(
  75. "/api/development/v1/ingestion-jobs/job-1/evidence", "GET"
  76. ) == ("governance:read",)
  77. assert permission_for_request(
  78. "/api/development/v1/data-elements", "POST"
  79. ) == ("data-elements:edit",)
  80. assert permission_for_request(
  81. "/api/development/v1/data-elements/element-1/transitions", "POST"
  82. ) == ("data-elements:edit",)
  83. assert permission_for_request(
  84. "/api/development/v1/candidate-decisions", "POST"
  85. ) == ("data-elements:edit",)
  86. assert permission_for_request(
  87. "/api/development/v1/device-assets", "GET"
  88. ) == ("governance:read",)
  89. assert permission_for_request(
  90. "/api/development/v1/device-assets/asset-1", "GET"
  91. ) == ("governance:read",)
  92. assert permission_for_request(
  93. "/api/development/v1/device-assets/import", "POST"
  94. ) == ("device-assets:edit",)
  95. assert permission_for_request(
  96. "/api/development/v1/device-semantics/profile", "GET"
  97. ) == ("governance:read",)
  98. assert permission_for_request(
  99. "/api/development/v1/device-semantics/bootstrap", "POST"
  100. ) == ("device-semantics:edit",)
  101. assert permission_for_request(
  102. "/api/development/v1/device-semantics/codes/code-1/submit",
  103. "POST",
  104. ) == ("device-semantics:edit",)
  105. assert permission_for_request(
  106. "/api/development/v1/device-semantics/codes/code-1/review",
  107. "POST",
  108. ) == ("device-semantics:review",)
  109. assert permission_for_request(
  110. "/api/development/v1/device-entities/candidates", "GET"
  111. ) == ("governance:read",)
  112. assert permission_for_request(
  113. "/api/development/v1/device-entities/candidates/generate", "POST"
  114. ) == ("device-entities:edit",)
  115. assert permission_for_request(
  116. "/api/development/v1/device-entities/candidates/candidate-1/review",
  117. "POST",
  118. ) == ("device-entities:review",)
  119. assert permission_for_request(
  120. "/api/development/v1/device-entities/merges/merge-1/rollback",
  121. "POST",
  122. ) == ("device-entities:review",)
  123. assert permission_for_request(
  124. "/api/development/v1/device-quality/profile", "GET"
  125. ) == ("governance:read",)
  126. assert permission_for_request(
  127. "/api/development/v1/device-quality/bootstrap", "POST"
  128. ) == ("device-quality:edit",)
  129. assert permission_for_request(
  130. "/api/development/v1/device-quality/profile/versions/version-1/publish",
  131. "POST",
  132. ) == ("device-quality:publish",)
  133. assert permission_for_request(
  134. "/api/development/v1/device-quality/runs", "POST"
  135. ) == ("device-quality:execute",)
  136. assert permission_for_request(
  137. "/api/development/v1/device-quality/issues", "GET"
  138. ) == ("governance:read",)
  139. assert permission_for_request(
  140. "/api/development/v1/device-quality/issues/import", "POST"
  141. ) == ("quality-issues:edit",)
  142. assert permission_for_request(
  143. "/api/development/v1/device-quality/issues/issue-1/review",
  144. "POST",
  145. ) == ("quality-issues:review",)
  146. assert permission_for_request(
  147. "/api/development/v1/device-observability/events",
  148. "GET",
  149. ) == ("governance:read",)
  150. assert permission_for_request(
  151. "/api/development/v1/device-observability/graph",
  152. "GET",
  153. ) == ("governance:read",)
  154. assert permission_for_request(
  155. "/api/development/v1/device-observability/root-cause",
  156. "GET",
  157. ) == ("governance:read",)
  158. assert permission_for_request(
  159. "/api/development/v1/device-observability/import",
  160. "POST",
  161. ) == ("device-observability:edit",)
  162. assert permission_for_request(
  163. "/api/development/v1/governance-metrics/summary",
  164. "GET",
  165. ) == ("governance:read",)
  166. assert permission_for_request(
  167. "/api/development/v1/governance-metrics/details",
  168. "GET",
  169. ) == ("governance:read",)
  170. assert permission_for_request(
  171. "/api/datafactory/observability/overview",
  172. "GET",
  173. ) == ("data-observability:read",)
  174. assert permission_for_request(
  175. "/api/datafactory/observability/collect",
  176. "POST",
  177. ) == ("data-observability:operate",)
  178. assert permission_for_request(
  179. "/api/datafactory/observability/slos",
  180. "POST",
  181. ) == ("data-observability:manage",)
  182. assert permission_for_request(
  183. "/api/system/governance-audit/events",
  184. "GET",
  185. ) == ("governance-audit:read",)
  186. assert permission_for_request(
  187. "/api/system/governance-audit/seals",
  188. "POST",
  189. ) == ("governance-audit:seal",)
  190. def test_business_domain_read_endpoints_are_available_to_viewers():
  191. from app.core.system.permissions import permission_for_request
  192. assert permission_for_request("/api/bd/list", "POST") == (
  193. "governance:read",
  194. )
  195. assert permission_for_request("/api/bd/detail", "POST") == (
  196. "governance:read",
  197. )
  198. assert permission_for_request("/api/bd/save", "POST") == (
  199. "governance:edit",
  200. )
  201. def test_permissions_are_derived_server_side_from_roles():
  202. from app.core.system.permissions import permissions_for_roles
  203. permissions = permissions_for_roles(["viewer", "editor"])
  204. assert "governance:read" in permissions
  205. assert "governance:edit" in permissions
  206. assert "users:manage" not in permissions
  207. def test_every_api_path_has_an_explicit_policy():
  208. from app import create_app
  209. from app.core.system.permissions import permission_for_request
  210. app = create_app()
  211. for rule in app.url_map.iter_rules():
  212. if not rule.rule.startswith("/api/"):
  213. continue
  214. for method in rule.methods - {"HEAD", "OPTIONS"}:
  215. assert permission_for_request(rule.rule, method) is not None, (
  216. method,
  217. rule.rule,
  218. )
  219. def test_knowledge_search_and_ask_posts_are_read_only():
  220. from app.core.system.permissions import READ_GOVERNANCE, permission_for_request
  221. assert permission_for_request("/api/knowledge/search", "POST") == (
  222. READ_GOVERNANCE,
  223. )
  224. assert permission_for_request("/api/knowledge/ask", "POST") == (READ_GOVERNANCE,)
  225. def test_knowledge_admin_routes_require_dedicated_manage_permission():
  226. from app.core.system.permissions import KNOWLEDGE_MANAGE, permission_for_request
  227. assert permission_for_request("/api/knowledge/admin/sync", "GET") == (
  228. KNOWLEDGE_MANAGE,
  229. )
  230. assert permission_for_request(
  231. "/api/knowledge/admin/change-sets/id/retry", "POST"
  232. ) == (KNOWLEDGE_MANAGE,)
  233. assert permission_for_request(
  234. "/api/knowledge/admin/device-sources", "GET"
  235. ) == (KNOWLEDGE_MANAGE,)
  236. assert permission_for_request(
  237. "/api/knowledge/admin/device-sources/id/scope", "PUT"
  238. ) == (KNOWLEDGE_MANAGE,)
  239. assert permission_for_request(
  240. "/api/knowledge/admin/query-audits", "GET"
  241. ) == (KNOWLEDGE_MANAGE,)
  242. def test_data_rule_routes_separate_read_authoring_and_release_permissions():
  243. from app.core.system.permissions import (
  244. DATAFLOW_RELEASE,
  245. RULES_EDIT,
  246. RULES_PUBLISH,
  247. RULES_READ,
  248. permission_for_request,
  249. )
  250. assert permission_for_request("/api/rules/capabilities", "GET") == (
  251. RULES_READ,
  252. )
  253. assert permission_for_request("/api/rules/interpret", "POST") == (
  254. RULES_EDIT,
  255. )
  256. assert permission_for_request("/api/rules/validate", "POST") == (
  257. RULES_EDIT,
  258. )
  259. assert permission_for_request(
  260. "/api/rules/versions/id/publish", "POST"
  261. ) == (RULES_PUBLISH,)
  262. assert permission_for_request(
  263. "/api/rules/production-lines/id/release", "POST"
  264. ) == (DATAFLOW_RELEASE,)
  265. def test_data_factory_actions_have_independent_permissions():
  266. from app.core.system.permissions import (
  267. DATAFLOW_ACTIVATE,
  268. DATAFLOW_CANARY,
  269. DATAFLOW_DEPLOY,
  270. DATAFLOW_ROLLBACK,
  271. RULES_EXECUTE,
  272. permission_for_request,
  273. )
  274. assert permission_for_request("/api/rules/deployments", "POST") == (
  275. DATAFLOW_DEPLOY,
  276. )
  277. assert permission_for_request(
  278. "/api/rules/deployments/id/deploy-disabled", "POST"
  279. ) == (DATAFLOW_DEPLOY,)
  280. assert permission_for_request(
  281. "/api/rules/deployments/id/canary", "POST"
  282. ) == (DATAFLOW_CANARY, RULES_EXECUTE)
  283. assert permission_for_request(
  284. "/api/rules/deployments/id/execute", "POST"
  285. ) == (RULES_EXECUTE,)
  286. assert permission_for_request(
  287. "/api/rules/deployments/id/activate", "POST"
  288. ) == (DATAFLOW_ACTIVATE,)
  289. assert permission_for_request(
  290. "/api/rules/deployments/id/rollback", "POST"
  291. ) == (DATAFLOW_ROLLBACK,)
  292. assert permission_for_request(
  293. "/api/rules/deployments/id/reconcile-deploy", "POST"
  294. ) == (DATAFLOW_DEPLOY,)
  295. assert permission_for_request(
  296. "/api/rules/deployments/id/reconcile-activate", "POST"
  297. ) == (DATAFLOW_ACTIVATE,)
  298. assert permission_for_request(
  299. "/api/rules/deployments/id/reconcile-rollback", "POST"
  300. ) == (DATAFLOW_ROLLBACK,)
  301. assert permission_for_request(
  302. "/api/rules/deployments/id/reconcile-execute", "POST"
  303. ) == (RULES_EXECUTE,)