permissions.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613
  1. from __future__ import annotations
  2. from collections.abc import Iterable
  3. from functools import wraps
  4. from flask import current_app, g, jsonify, request
  5. from app.models.result import failed
  6. READ_GOVERNANCE = "governance:read"
  7. EDIT_GOVERNANCE = "governance:edit"
  8. APPROVE_REVIEW = "review:approve"
  9. MANAGE_USERS = "users:manage"
  10. ACTIVATE_WORKFLOW = "workflow:activate"
  11. OPERATE_ORDERS = "orders:operate"
  12. DATASOURCE_POOL_MANAGE = "datasources:pools:manage"
  13. RESPONSIBILITIES_READ = "governance:responsibilities:read"
  14. RESPONSIBILITIES_MANAGE = "governance:responsibilities:manage"
  15. KNOWLEDGE_MANAGE = "knowledge:manage"
  16. RULES_READ = "rules:read"
  17. RULES_EDIT = "rules:edit"
  18. RULES_PUBLISH = "rules:publish"
  19. DATAFLOW_RELEASE = "dataflows:release"
  20. DATAFLOW_DEPLOY = "dataflows:deploy"
  21. DATAFLOW_CANARY = "dataflows:canary"
  22. DATAFLOW_ACTIVATE = "dataflows:activate"
  23. DATAFLOW_ROLLBACK = "dataflows:rollback"
  24. RULES_EXECUTE = "rules:execute"
  25. INGESTION_RUN = "ingestion:run"
  26. INGESTION_ADMIN = "ingestion:admin"
  27. EVIDENCE_DOWNLOAD = "evidence:download"
  28. DATA_ELEMENTS_EDIT = "data-elements:edit"
  29. DATA_ELEMENTS_PUBLISH = "data-elements:publish"
  30. ONTOLOGIES_EDIT = "ontologies:edit"
  31. ONTOLOGIES_PUBLISH = "ontologies:publish"
  32. DEVICE_ASSETS_EDIT = "device-assets:edit"
  33. DEVICE_SEMANTICS_EDIT = "device-semantics:edit"
  34. DEVICE_SEMANTICS_REVIEW = "device-semantics:review"
  35. DEVICE_ENTITIES_EDIT = "device-entities:edit"
  36. DEVICE_ENTITIES_REVIEW = "device-entities:review"
  37. DEVICE_QUALITY_EDIT = "device-quality:edit"
  38. DEVICE_QUALITY_EXECUTE = "device-quality:execute"
  39. DEVICE_QUALITY_PUBLISH = "device-quality:publish"
  40. QUALITY_ISSUES_EDIT = "quality-issues:edit"
  41. QUALITY_ISSUES_REVIEW = "quality-issues:review"
  42. DEVICE_OBSERVABILITY_EDIT = "device-observability:edit"
  43. GOVERNANCE_AUDIT_READ = "governance-audit:read"
  44. GOVERNANCE_AUDIT_SEAL = "governance-audit:seal"
  45. DOMAIN_TEMPLATES_READ = "domain-templates:read"
  46. DOMAIN_TEMPLATES_PREVIEW = "domain-templates:preview"
  47. DOMAIN_TEMPLATES_MANAGE = "domain-templates:manage"
  48. ACTIVE_METADATA_READ = "active-metadata:read"
  49. ACTIVE_METADATA_OPERATE = "active-metadata:operate"
  50. ACTIVE_METADATA_MANAGE = "active-metadata:manage"
  51. SEMANTIC_GOVERNANCE_EDIT = "semantic-governance:edit"
  52. SEMANTIC_GOVERNANCE_REVIEW = "semantic-governance:review"
  53. SEMANTIC_GOVERNANCE_PUBLISH = "semantic-governance:publish"
  54. DATA_OBSERVABILITY_READ = "data-observability:read"
  55. DATA_OBSERVABILITY_OPERATE = "data-observability:operate"
  56. DATA_OBSERVABILITY_MANAGE = "data-observability:manage"
  57. WORK_CENTER_READ = "governance:work-center:read"
  58. WORK_CENTER_OPERATE = "governance:work-center:operate"
  59. WORK_CENTER_MANAGE = "governance:work-center:manage"
  60. DATA_PRODUCTS_READ = "data-products:read"
  61. DATA_PRODUCTS_OPERATE = "data-products:operate"
  62. DATA_PRODUCTS_MANAGE = "data-products:manage"
  63. AGENTS_READ = "agents:read"
  64. AGENTS_OPERATE = "agents:operate"
  65. AGENTS_MANAGE = "agents:manage"
  66. SECURITY_GOVERNANCE_READ = "security-governance:read"
  67. SECURITY_GOVERNANCE_OPERATE = "security-governance:operate"
  68. SECURITY_GOVERNANCE_MANAGE = "security-governance:manage"
  69. IDENTITY_READ = "identity:read"
  70. IDENTITY_MANAGE = "identity:manage"
  71. IDENTITY_OPERATE = "identity:operate"
  72. CONNECTORS_READ = "connectors:read"
  73. CONNECTORS_OPERATE = "connectors:operate"
  74. CONNECTORS_MANAGE = "connectors:manage"
  75. EDGE_GATEWAYS_READ = "edge-gateways:read"
  76. EDGE_GATEWAYS_OPERATE = "edge-gateways:operate"
  77. EDGE_GATEWAYS_MANAGE = "edge-gateways:manage"
  78. BI_AI_CATALOG_READ = "bi-ai-catalog:read"
  79. BI_AI_CATALOG_MANAGE = "bi-ai-catalog:manage"
  80. METERING_READ = "metering:read"
  81. METERING_MANAGE = "metering:manage"
  82. ROLE_PERMISSIONS = {
  83. "viewer": frozenset(
  84. {
  85. READ_GOVERNANCE,
  86. RULES_READ,
  87. RESPONSIBILITIES_READ,
  88. DOMAIN_TEMPLATES_READ,
  89. ACTIVE_METADATA_READ,
  90. DATA_OBSERVABILITY_READ,
  91. WORK_CENTER_READ,
  92. DATA_PRODUCTS_READ,
  93. AGENTS_READ,
  94. SECURITY_GOVERNANCE_READ,
  95. IDENTITY_READ,
  96. CONNECTORS_READ,
  97. EDGE_GATEWAYS_READ,
  98. BI_AI_CATALOG_READ,
  99. METERING_READ,
  100. }
  101. ),
  102. "editor": frozenset(
  103. {
  104. READ_GOVERNANCE,
  105. EDIT_GOVERNANCE,
  106. APPROVE_REVIEW,
  107. OPERATE_ORDERS,
  108. RULES_READ,
  109. RULES_EDIT,
  110. RULES_EXECUTE,
  111. RESPONSIBILITIES_READ,
  112. INGESTION_RUN,
  113. DATA_ELEMENTS_EDIT,
  114. ONTOLOGIES_EDIT,
  115. DEVICE_ASSETS_EDIT,
  116. DEVICE_SEMANTICS_EDIT,
  117. DEVICE_ENTITIES_EDIT,
  118. DEVICE_QUALITY_EDIT,
  119. DEVICE_QUALITY_EXECUTE,
  120. QUALITY_ISSUES_EDIT,
  121. DEVICE_OBSERVABILITY_EDIT,
  122. DOMAIN_TEMPLATES_READ,
  123. DOMAIN_TEMPLATES_PREVIEW,
  124. ACTIVE_METADATA_READ,
  125. ACTIVE_METADATA_OPERATE,
  126. SEMANTIC_GOVERNANCE_EDIT,
  127. DATA_OBSERVABILITY_READ,
  128. DATA_OBSERVABILITY_OPERATE,
  129. WORK_CENTER_READ,
  130. WORK_CENTER_OPERATE,
  131. DATA_PRODUCTS_READ,
  132. DATA_PRODUCTS_OPERATE,
  133. AGENTS_READ,
  134. AGENTS_OPERATE,
  135. SECURITY_GOVERNANCE_READ,
  136. SECURITY_GOVERNANCE_OPERATE,
  137. IDENTITY_READ,
  138. IDENTITY_OPERATE,
  139. CONNECTORS_READ,
  140. CONNECTORS_OPERATE,
  141. EDGE_GATEWAYS_READ,
  142. EDGE_GATEWAYS_OPERATE,
  143. BI_AI_CATALOG_READ,
  144. METERING_READ,
  145. }
  146. ),
  147. "admin": frozenset(
  148. {
  149. READ_GOVERNANCE,
  150. EDIT_GOVERNANCE,
  151. APPROVE_REVIEW,
  152. MANAGE_USERS,
  153. ACTIVATE_WORKFLOW,
  154. OPERATE_ORDERS,
  155. DATASOURCE_POOL_MANAGE,
  156. RESPONSIBILITIES_READ,
  157. RESPONSIBILITIES_MANAGE,
  158. KNOWLEDGE_MANAGE,
  159. RULES_READ,
  160. RULES_EDIT,
  161. RULES_PUBLISH,
  162. DATAFLOW_RELEASE,
  163. DATAFLOW_DEPLOY,
  164. DATAFLOW_CANARY,
  165. DATAFLOW_ACTIVATE,
  166. DATAFLOW_ROLLBACK,
  167. RULES_EXECUTE,
  168. INGESTION_RUN,
  169. INGESTION_ADMIN,
  170. EVIDENCE_DOWNLOAD,
  171. DATA_ELEMENTS_EDIT,
  172. DATA_ELEMENTS_PUBLISH,
  173. ONTOLOGIES_EDIT,
  174. ONTOLOGIES_PUBLISH,
  175. DEVICE_ASSETS_EDIT,
  176. DEVICE_SEMANTICS_EDIT,
  177. DEVICE_SEMANTICS_REVIEW,
  178. DEVICE_ENTITIES_EDIT,
  179. DEVICE_ENTITIES_REVIEW,
  180. DEVICE_QUALITY_EDIT,
  181. DEVICE_QUALITY_EXECUTE,
  182. DEVICE_QUALITY_PUBLISH,
  183. QUALITY_ISSUES_EDIT,
  184. QUALITY_ISSUES_REVIEW,
  185. DEVICE_OBSERVABILITY_EDIT,
  186. GOVERNANCE_AUDIT_READ,
  187. GOVERNANCE_AUDIT_SEAL,
  188. DOMAIN_TEMPLATES_READ,
  189. DOMAIN_TEMPLATES_PREVIEW,
  190. DOMAIN_TEMPLATES_MANAGE,
  191. ACTIVE_METADATA_READ,
  192. ACTIVE_METADATA_OPERATE,
  193. ACTIVE_METADATA_MANAGE,
  194. SEMANTIC_GOVERNANCE_EDIT,
  195. SEMANTIC_GOVERNANCE_REVIEW,
  196. SEMANTIC_GOVERNANCE_PUBLISH,
  197. DATA_OBSERVABILITY_READ,
  198. DATA_OBSERVABILITY_OPERATE,
  199. DATA_OBSERVABILITY_MANAGE,
  200. WORK_CENTER_READ,
  201. WORK_CENTER_OPERATE,
  202. WORK_CENTER_MANAGE,
  203. DATA_PRODUCTS_READ,
  204. DATA_PRODUCTS_OPERATE,
  205. DATA_PRODUCTS_MANAGE,
  206. AGENTS_READ,
  207. AGENTS_OPERATE,
  208. AGENTS_MANAGE,
  209. SECURITY_GOVERNANCE_READ,
  210. SECURITY_GOVERNANCE_OPERATE,
  211. SECURITY_GOVERNANCE_MANAGE,
  212. IDENTITY_READ,
  213. IDENTITY_OPERATE,
  214. IDENTITY_MANAGE,
  215. CONNECTORS_READ,
  216. CONNECTORS_OPERATE,
  217. CONNECTORS_MANAGE,
  218. EDGE_GATEWAYS_READ,
  219. EDGE_GATEWAYS_OPERATE,
  220. EDGE_GATEWAYS_MANAGE,
  221. BI_AI_CATALOG_READ,
  222. BI_AI_CATALOG_MANAGE,
  223. METERING_READ,
  224. METERING_MANAGE,
  225. }
  226. ),
  227. }
  228. PUBLIC = "public"
  229. def permission_for_request(path: str, method: str) -> tuple[str, ...]:
  230. """Classify every API request in one auditable, deny-by-default policy."""
  231. method = method.upper()
  232. public_methods = {
  233. ("/api/system/health", "GET"),
  234. ("/api/system/auth/login", "POST"),
  235. ("/api/system/identity/providers", "GET"),
  236. ("/api/system/identity/authorize", "POST"),
  237. ("/api/system/identity/callback", "GET"),
  238. ("/api/system/identity/exchange", "POST"),
  239. ("/api/system/identity/refresh", "POST"),
  240. ("/api/datasource/connectors/machine/runs", "POST"),
  241. ("/api/datasource/edge/register", "POST"),
  242. }
  243. if (path, method) in public_methods:
  244. return (PUBLIC,)
  245. if (
  246. path.startswith("/api/datasource/connectors/machine/runs/")
  247. and path.rsplit("/", 1)[-1] in {"cancel", "resume"}
  248. and method == "POST"
  249. ):
  250. return (PUBLIC,)
  251. if path.startswith("/api/datasource/edge/gateways/") and method == "POST":
  252. suffix = path.rsplit("/", 1)[-1]
  253. if suffix in {"heartbeat", "pull", "events", "ack", "reconcile", "outcome"}:
  254. return (PUBLIC,)
  255. if path.startswith("/api/datasource/edge"):
  256. if path == "/api/datasource/edge/gateways" and method == "GET":
  257. return (EDGE_GATEWAYS_READ,)
  258. if path == "/api/datasource/edge/enrollments" and method == "POST":
  259. return (EDGE_GATEWAYS_MANAGE,)
  260. if path == "/api/datasource/edge/tasks" and method == "POST":
  261. return (EDGE_GATEWAYS_OPERATE,)
  262. if (
  263. path.startswith("/api/datasource/edge/tasks/")
  264. and path.endswith("/cancel")
  265. and method == "POST"
  266. ):
  267. return (EDGE_GATEWAYS_OPERATE,)
  268. if method == "GET":
  269. return (EDGE_GATEWAYS_READ,)
  270. return (EDGE_GATEWAYS_MANAGE,)
  271. if path.startswith("/api/system/identity"):
  272. if path == "/api/system/identity/logout" and method == "POST":
  273. return (IDENTITY_READ,)
  274. if (
  275. path in {"/api/system/identity/idp-versions", "/api/system/identity/audit"}
  276. and method == "GET"
  277. ):
  278. return (IDENTITY_MANAGE,)
  279. if path == "/api/system/identity/sessions" and method == "GET":
  280. return (IDENTITY_OPERATE,)
  281. if method == "GET":
  282. return (IDENTITY_READ,)
  283. if any(marker in path for marker in ("/idp-versions", "/emergency/")):
  284. return (IDENTITY_MANAGE,)
  285. return (IDENTITY_OPERATE,)
  286. if path.startswith("/api/system/tenant/"):
  287. return (IDENTITY_OPERATE,)
  288. if path.startswith("/api/system/bi-ai-catalog/"):
  289. return (
  290. (BI_AI_CATALOG_READ,)
  291. if method == "GET" or path.endswith("/search")
  292. else (BI_AI_CATALOG_MANAGE,)
  293. )
  294. if path.startswith("/api/system/metering/"):
  295. return (METERING_READ,) if method == "GET" else (METERING_MANAGE,)
  296. if path.startswith("/api/datasource/connectors"):
  297. if "/source-bindings" in path:
  298. return (CONNECTORS_MANAGE,)
  299. if method == "GET":
  300. return (CONNECTORS_READ,)
  301. if any(marker in path for marker in ("/principals", "/credentials/")):
  302. return (CONNECTORS_MANAGE,)
  303. return (CONNECTORS_OPERATE,)
  304. if path == "/api/datasource/graph":
  305. return (CONNECTORS_READ,)
  306. if path.startswith("/api/system/responsibilities/"):
  307. if method == "GET":
  308. return (RESPONSIBILITIES_READ,)
  309. return (RESPONSIBILITIES_MANAGE,)
  310. if path.startswith("/api/system/work-center"):
  311. if method == "GET":
  312. return (WORK_CENTER_READ,)
  313. if any(
  314. segment in path
  315. for segment in ("/workflows", "/templates", "/timeouts", "/deliver")
  316. ):
  317. return (WORK_CENTER_MANAGE,)
  318. return (WORK_CENTER_OPERATE,)
  319. if path.startswith("/api/system/governance-audit"):
  320. if method == "GET":
  321. return (GOVERNANCE_AUDIT_READ,)
  322. return (GOVERNANCE_AUDIT_SEAL,)
  323. if path.startswith("/api/system/security-governance"):
  324. if method == "GET":
  325. return (SECURITY_GOVERNANCE_READ,)
  326. if any(
  327. marker in path
  328. for marker in (
  329. "/findings/",
  330. "/reconcile",
  331. "/vulnerabilities/",
  332. "/dispatch",
  333. )
  334. ):
  335. return (SECURITY_GOVERNANCE_MANAGE,)
  336. if any(
  337. path.startswith(prefix)
  338. for prefix in (
  339. "/api/system/security-governance/retention",
  340. "/api/system/security-governance/siem",
  341. "/api/system/security-governance/sboms",
  342. )
  343. ):
  344. return (SECURITY_GOVERNANCE_MANAGE,)
  345. return (SECURITY_GOVERNANCE_OPERATE,)
  346. if path.startswith("/api/system/trusted-delivery"):
  347. if path.startswith("/api/system/trusted-delivery/controls/"):
  348. if method == "GET":
  349. return (SECURITY_GOVERNANCE_READ,)
  350. if any(
  351. marker in path
  352. for marker in (
  353. "/profiles/activate",
  354. "/profiles/rollback",
  355. "/capability-approvals",
  356. "/destruction-approvals",
  357. )
  358. ):
  359. return (SECURITY_GOVERNANCE_MANAGE,)
  360. return (SECURITY_GOVERNANCE_OPERATE,)
  361. if "/legal-holds" in path or "/reclaim/" in path:
  362. return (SECURITY_GOVERNANCE_MANAGE,)
  363. return (SECURITY_GOVERNANCE_OPERATE,)
  364. if path.startswith("/api/dataservice/governance"):
  365. if method == "GET":
  366. return (DATA_PRODUCTS_READ,)
  367. if (
  368. any(
  369. marker in path
  370. for marker in (
  371. "/reconcile",
  372. "/fulfill",
  373. )
  374. )
  375. or path == "/api/dataservice/governance/products"
  376. ):
  377. return (DATA_PRODUCTS_MANAGE,)
  378. return (DATA_PRODUCTS_OPERATE,)
  379. if path.startswith("/api/knowledge/agents"):
  380. if method == "GET":
  381. return (AGENTS_READ,)
  382. if path == "/api/knowledge/agents" or path.endswith("/reconcile"):
  383. return (AGENTS_MANAGE,)
  384. return (AGENTS_OPERATE,)
  385. if path.startswith("/api/meta/domain-templates"):
  386. if method == "GET":
  387. return (DOMAIN_TEMPLATES_READ,)
  388. if path == "/api/meta/domain-templates/dry-run":
  389. return (DOMAIN_TEMPLATES_PREVIEW,)
  390. return (DOMAIN_TEMPLATES_MANAGE,)
  391. if path.startswith("/api/meta/active-metadata"):
  392. if method == "GET":
  393. return (ACTIVE_METADATA_READ,)
  394. if path == "/api/meta/active-metadata/plans":
  395. return (ACTIVE_METADATA_MANAGE,)
  396. return (ACTIVE_METADATA_OPERATE,)
  397. if path.startswith("/api/datafactory/observability"):
  398. if method == "GET":
  399. return (DATA_OBSERVABILITY_READ,)
  400. if path == "/api/datafactory/observability/slos":
  401. return (DATA_OBSERVABILITY_MANAGE,)
  402. return (DATA_OBSERVABILITY_OPERATE,)
  403. if path in {"/api/knowledge/search", "/api/knowledge/ask"}:
  404. return (READ_GOVERNANCE,)
  405. if path.startswith("/api/rules"):
  406. if path.startswith("/api/rules/deployments"):
  407. if method == "GET":
  408. return (RULES_READ,)
  409. if path.endswith("/deploy-disabled"):
  410. return (DATAFLOW_DEPLOY,)
  411. if path.endswith("/canary"):
  412. return (DATAFLOW_CANARY, RULES_EXECUTE)
  413. if path.endswith("/execute"):
  414. return (RULES_EXECUTE,)
  415. if path.endswith("/activate"):
  416. return (DATAFLOW_ACTIVATE,)
  417. if path.endswith("/rollback"):
  418. return (DATAFLOW_ROLLBACK,)
  419. if path.endswith("/reconcile-deploy"):
  420. return (DATAFLOW_DEPLOY,)
  421. if path.endswith("/reconcile-activate"):
  422. return (DATAFLOW_ACTIVATE,)
  423. if path.endswith("/reconcile-rollback"):
  424. return (DATAFLOW_ROLLBACK,)
  425. if path.endswith("/reconcile-execute"):
  426. return (RULES_EXECUTE,)
  427. if method == "POST":
  428. return (DATAFLOW_DEPLOY,)
  429. return (DATAFLOW_DEPLOY,)
  430. if method == "GET":
  431. return (RULES_READ,)
  432. if path.endswith("/publish"):
  433. return (RULES_PUBLISH,)
  434. if path.startswith("/api/rules/production-lines/") and path.endswith(
  435. "/release"
  436. ):
  437. return (DATAFLOW_RELEASE,)
  438. if path.endswith("/test") or path.endswith("/execute"):
  439. return (RULES_EXECUTE,)
  440. if method in {"POST", "PUT", "PATCH", "DELETE"}:
  441. return (RULES_EDIT,)
  442. return (RULES_PUBLISH,)
  443. if path.startswith("/api/knowledge/admin"):
  444. return (KNOWLEDGE_MANAGE,)
  445. if path.startswith("/api/system/users"):
  446. return (MANAGE_USERS,)
  447. if path.startswith("/api/development/v1/governance-metrics"):
  448. return (READ_GOVERNANCE,)
  449. if path.startswith("/api/development/v1/semantic"):
  450. if method == "GET":
  451. return (READ_GOVERNANCE,)
  452. if path.endswith("/review"):
  453. return (SEMANTIC_GOVERNANCE_REVIEW,)
  454. if path.endswith("/publish") or path.endswith("/rollback"):
  455. return (SEMANTIC_GOVERNANCE_PUBLISH,)
  456. return (SEMANTIC_GOVERNANCE_EDIT,)
  457. if path.startswith("/api/development/v1/device-assets"):
  458. if method == "GET":
  459. return (READ_GOVERNANCE,)
  460. return (DEVICE_ASSETS_EDIT,)
  461. if path.startswith("/api/development/v1/device-semantics"):
  462. if method == "GET":
  463. return (READ_GOVERNANCE,)
  464. if path.endswith("/review"):
  465. return (DEVICE_SEMANTICS_REVIEW,)
  466. return (DEVICE_SEMANTICS_EDIT,)
  467. if path.startswith("/api/development/v1/device-entities"):
  468. if method == "GET":
  469. return (READ_GOVERNANCE,)
  470. if path.endswith("/review") or path.endswith("/rollback"):
  471. return (DEVICE_ENTITIES_REVIEW,)
  472. return (DEVICE_ENTITIES_EDIT,)
  473. if path.startswith("/api/development/v1/device-observability"):
  474. if method == "GET":
  475. return (READ_GOVERNANCE,)
  476. return (DEVICE_OBSERVABILITY_EDIT,)
  477. if path.startswith("/api/development/v1/device-quality"):
  478. if method == "GET":
  479. return (READ_GOVERNANCE,)
  480. if path.startswith("/api/development/v1/device-quality/issues"):
  481. if path.endswith("/review"):
  482. return (QUALITY_ISSUES_REVIEW,)
  483. return (QUALITY_ISSUES_EDIT,)
  484. if path.endswith("/publish"):
  485. return (DEVICE_QUALITY_PUBLISH,)
  486. if path == "/api/development/v1/device-quality/runs":
  487. return (DEVICE_QUALITY_EXECUTE,)
  488. return (DEVICE_QUALITY_EDIT,)
  489. if path.startswith("/api/development/v1/ingestion-jobs"):
  490. if method == "GET":
  491. return (READ_GOVERNANCE,)
  492. if path.endswith("/retry"):
  493. return (INGESTION_ADMIN,)
  494. if path.endswith("/cancel"):
  495. return (INGESTION_RUN,)
  496. if method == "POST":
  497. return (INGESTION_RUN,)
  498. if path.startswith("/api/development/v1/data-elements"):
  499. if method == "GET":
  500. return (READ_GOVERNANCE,)
  501. return (DATA_ELEMENTS_EDIT,)
  502. if path.startswith("/api/development/v1/candidate-decisions"):
  503. return (DATA_ELEMENTS_EDIT,)
  504. if path.startswith("/api/development/v1/sources/files"):
  505. return (INGESTION_RUN,)
  506. if path.startswith("/api/development/v1/evidence"):
  507. return (READ_GOVERNANCE,)
  508. if path.startswith("/api/development/v1/ontologies"):
  509. if method == "GET":
  510. return (READ_GOVERNANCE,)
  511. if path.endswith("/publish") or path.endswith("/rollback"):
  512. return (ONTOLOGIES_PUBLISH,)
  513. return (ONTOLOGIES_EDIT,)
  514. if path.startswith("/api/bd/"):
  515. if path in {
  516. "/api/bd/list",
  517. "/api/bd/detail",
  518. "/api/bd/graphall",
  519. "/api/bd/search",
  520. }:
  521. return (READ_GOVERNANCE,)
  522. return (EDIT_GOVERNANCE,)
  523. if path.startswith("/api/system/workbench"):
  524. return (READ_GOVERNANCE,)
  525. if path == "/api/datasource/pools" or (
  526. path.startswith("/api/datasource/")
  527. and (path.endswith("/pool") or path.endswith("/pool/invalidate"))
  528. ):
  529. return (DATASOURCE_POOL_MANAGE,)
  530. if path == "/api/datasource/list":
  531. return (READ_GOVERNANCE,)
  532. if path.startswith("/api/system/") and not path.startswith("/api/system/auth/me"):
  533. return (MANAGE_USERS,)
  534. if path.startswith("/api/dataflow/") and any(
  535. marker in path.lower() for marker in ("activate", "publish", "execute")
  536. ):
  537. return (ACTIVATE_WORKFLOW,)
  538. if "order" in path.lower() and method != "GET":
  539. return (OPERATE_ORDERS,)
  540. if method == "GET":
  541. return (READ_GOVERNANCE,)
  542. if method in {"POST", "PUT", "PATCH", "DELETE"}:
  543. return (EDIT_GOVERNANCE,)
  544. return (MANAGE_USERS,)
  545. def permissions_for_roles(roles: Iterable[str]) -> frozenset[str]:
  546. result: set[str] = set()
  547. for role in roles:
  548. result.update(ROLE_PERMISSIONS.get(role, ()))
  549. return frozenset(result)
  550. def authenticate_request() -> dict | None:
  551. from app.core.system.auth import load_identity_from_token
  552. existing = getattr(g, "current_user", None)
  553. if existing:
  554. return existing
  555. header = request.headers.get("Authorization", "")
  556. if not header.startswith("Bearer "):
  557. return None
  558. token = header[7:].strip()
  559. if not token:
  560. return None
  561. return load_identity_from_token(token, secret=current_app.config["SECRET_KEY"])
  562. def configure_api_authorization(app) -> None:
  563. @app.before_request
  564. def enforce_api_policy():
  565. if not request.path.startswith("/api/") or request.method == "OPTIONS":
  566. return None
  567. required = permission_for_request(request.path, request.method)
  568. if required == (PUBLIC,):
  569. return None
  570. identity = authenticate_request()
  571. if identity is None:
  572. return jsonify(failed("未登录或登录已过期", code=401)), 401
  573. permissions = permissions_for_roles(identity["roles"])
  574. if not set(required).issubset(permissions):
  575. return jsonify(failed("权限不足", code=403)), 403
  576. identity["permissions"] = sorted(permissions)
  577. g.current_user = identity
  578. return None
  579. def require_permissions(*required: str):
  580. def decorator(view):
  581. @wraps(view)
  582. def wrapped(*args, **kwargs):
  583. identity = authenticate_request()
  584. if identity is None:
  585. return jsonify(failed("未登录或登录已过期", code=401)), 401
  586. permissions = permissions_for_roles(identity["roles"])
  587. if not set(required).issubset(permissions):
  588. return jsonify(failed("权限不足", code=403)), 403
  589. identity["permissions"] = sorted(permissions)
  590. g.current_user = identity
  591. return view(*args, **kwargs)
  592. wrapped.required_permissions = tuple(required)
  593. return wrapped
  594. return decorator