permissions.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  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. ROLE_PERMISSIONS = {
  73. "viewer": frozenset(
  74. {
  75. READ_GOVERNANCE,
  76. RULES_READ,
  77. RESPONSIBILITIES_READ,
  78. DOMAIN_TEMPLATES_READ,
  79. ACTIVE_METADATA_READ,
  80. DATA_OBSERVABILITY_READ,
  81. WORK_CENTER_READ,
  82. DATA_PRODUCTS_READ,
  83. AGENTS_READ,
  84. SECURITY_GOVERNANCE_READ,
  85. IDENTITY_READ,
  86. }
  87. ),
  88. "editor": frozenset(
  89. {
  90. READ_GOVERNANCE,
  91. EDIT_GOVERNANCE,
  92. APPROVE_REVIEW,
  93. OPERATE_ORDERS,
  94. RULES_READ,
  95. RULES_EDIT,
  96. RULES_EXECUTE,
  97. RESPONSIBILITIES_READ,
  98. INGESTION_RUN,
  99. DATA_ELEMENTS_EDIT,
  100. ONTOLOGIES_EDIT,
  101. DEVICE_ASSETS_EDIT,
  102. DEVICE_SEMANTICS_EDIT,
  103. DEVICE_ENTITIES_EDIT,
  104. DEVICE_QUALITY_EDIT,
  105. DEVICE_QUALITY_EXECUTE,
  106. QUALITY_ISSUES_EDIT,
  107. DEVICE_OBSERVABILITY_EDIT,
  108. DOMAIN_TEMPLATES_READ,
  109. DOMAIN_TEMPLATES_PREVIEW,
  110. ACTIVE_METADATA_READ,
  111. ACTIVE_METADATA_OPERATE,
  112. SEMANTIC_GOVERNANCE_EDIT,
  113. DATA_OBSERVABILITY_READ,
  114. DATA_OBSERVABILITY_OPERATE,
  115. WORK_CENTER_READ,
  116. WORK_CENTER_OPERATE,
  117. DATA_PRODUCTS_READ,
  118. DATA_PRODUCTS_OPERATE,
  119. AGENTS_READ,
  120. AGENTS_OPERATE,
  121. SECURITY_GOVERNANCE_READ,
  122. SECURITY_GOVERNANCE_OPERATE,
  123. IDENTITY_READ,
  124. IDENTITY_OPERATE,
  125. }
  126. ),
  127. "admin": frozenset(
  128. {
  129. READ_GOVERNANCE,
  130. EDIT_GOVERNANCE,
  131. APPROVE_REVIEW,
  132. MANAGE_USERS,
  133. ACTIVATE_WORKFLOW,
  134. OPERATE_ORDERS,
  135. DATASOURCE_POOL_MANAGE,
  136. RESPONSIBILITIES_READ,
  137. RESPONSIBILITIES_MANAGE,
  138. KNOWLEDGE_MANAGE,
  139. RULES_READ,
  140. RULES_EDIT,
  141. RULES_PUBLISH,
  142. DATAFLOW_RELEASE,
  143. DATAFLOW_DEPLOY,
  144. DATAFLOW_CANARY,
  145. DATAFLOW_ACTIVATE,
  146. DATAFLOW_ROLLBACK,
  147. RULES_EXECUTE,
  148. INGESTION_RUN,
  149. INGESTION_ADMIN,
  150. EVIDENCE_DOWNLOAD,
  151. DATA_ELEMENTS_EDIT,
  152. DATA_ELEMENTS_PUBLISH,
  153. ONTOLOGIES_EDIT,
  154. ONTOLOGIES_PUBLISH,
  155. DEVICE_ASSETS_EDIT,
  156. DEVICE_SEMANTICS_EDIT,
  157. DEVICE_SEMANTICS_REVIEW,
  158. DEVICE_ENTITIES_EDIT,
  159. DEVICE_ENTITIES_REVIEW,
  160. DEVICE_QUALITY_EDIT,
  161. DEVICE_QUALITY_EXECUTE,
  162. DEVICE_QUALITY_PUBLISH,
  163. QUALITY_ISSUES_EDIT,
  164. QUALITY_ISSUES_REVIEW,
  165. DEVICE_OBSERVABILITY_EDIT,
  166. GOVERNANCE_AUDIT_READ,
  167. GOVERNANCE_AUDIT_SEAL,
  168. DOMAIN_TEMPLATES_READ,
  169. DOMAIN_TEMPLATES_PREVIEW,
  170. DOMAIN_TEMPLATES_MANAGE,
  171. ACTIVE_METADATA_READ,
  172. ACTIVE_METADATA_OPERATE,
  173. ACTIVE_METADATA_MANAGE,
  174. SEMANTIC_GOVERNANCE_EDIT,
  175. SEMANTIC_GOVERNANCE_REVIEW,
  176. SEMANTIC_GOVERNANCE_PUBLISH,
  177. DATA_OBSERVABILITY_READ,
  178. DATA_OBSERVABILITY_OPERATE,
  179. DATA_OBSERVABILITY_MANAGE,
  180. WORK_CENTER_READ,
  181. WORK_CENTER_OPERATE,
  182. WORK_CENTER_MANAGE,
  183. DATA_PRODUCTS_READ,
  184. DATA_PRODUCTS_OPERATE,
  185. DATA_PRODUCTS_MANAGE,
  186. AGENTS_READ,
  187. AGENTS_OPERATE,
  188. AGENTS_MANAGE,
  189. SECURITY_GOVERNANCE_READ,
  190. SECURITY_GOVERNANCE_OPERATE,
  191. SECURITY_GOVERNANCE_MANAGE,
  192. IDENTITY_READ,
  193. IDENTITY_OPERATE,
  194. IDENTITY_MANAGE,
  195. }
  196. ),
  197. }
  198. PUBLIC = "public"
  199. def permission_for_request(path: str, method: str) -> tuple[str, ...]:
  200. """Classify every API request in one auditable, deny-by-default policy."""
  201. method = method.upper()
  202. public_methods = {
  203. ("/api/system/health", "GET"),
  204. ("/api/system/auth/login", "POST"),
  205. ("/api/system/identity/providers", "GET"),
  206. ("/api/system/identity/authorize", "POST"),
  207. ("/api/system/identity/callback", "GET"),
  208. ("/api/system/identity/exchange", "POST"),
  209. ("/api/system/identity/refresh", "POST"),
  210. }
  211. if (path, method) in public_methods:
  212. return (PUBLIC,)
  213. if path.startswith("/api/system/identity"):
  214. if path == "/api/system/identity/logout" and method == "POST":
  215. return (IDENTITY_READ,)
  216. if path in {"/api/system/identity/idp-versions", "/api/system/identity/audit"} and method == "GET":
  217. return (IDENTITY_MANAGE,)
  218. if path == "/api/system/identity/sessions" and method == "GET":
  219. return (IDENTITY_OPERATE,)
  220. if method == "GET":
  221. return (IDENTITY_READ,)
  222. if any(marker in path for marker in ("/idp-versions", "/emergency/")):
  223. return (IDENTITY_MANAGE,)
  224. return (IDENTITY_OPERATE,)
  225. if path.startswith("/api/system/responsibilities/"):
  226. if method == "GET":
  227. return (RESPONSIBILITIES_READ,)
  228. return (RESPONSIBILITIES_MANAGE,)
  229. if path.startswith("/api/system/work-center"):
  230. if method == "GET":
  231. return (WORK_CENTER_READ,)
  232. if any(
  233. segment in path
  234. for segment in ("/workflows", "/templates", "/timeouts", "/deliver")
  235. ):
  236. return (WORK_CENTER_MANAGE,)
  237. return (WORK_CENTER_OPERATE,)
  238. if path.startswith("/api/system/governance-audit"):
  239. if method == "GET":
  240. return (GOVERNANCE_AUDIT_READ,)
  241. return (GOVERNANCE_AUDIT_SEAL,)
  242. if path.startswith("/api/system/security-governance"):
  243. if method == "GET":
  244. return (SECURITY_GOVERNANCE_READ,)
  245. if any(
  246. marker in path
  247. for marker in (
  248. "/findings/",
  249. "/reconcile",
  250. "/vulnerabilities/",
  251. "/dispatch",
  252. )
  253. ):
  254. return (SECURITY_GOVERNANCE_MANAGE,)
  255. if any(
  256. path.startswith(prefix)
  257. for prefix in (
  258. "/api/system/security-governance/retention",
  259. "/api/system/security-governance/siem",
  260. "/api/system/security-governance/sboms",
  261. )
  262. ):
  263. return (SECURITY_GOVERNANCE_MANAGE,)
  264. return (SECURITY_GOVERNANCE_OPERATE,)
  265. if path.startswith("/api/dataservice/governance"):
  266. if method == "GET":
  267. return (DATA_PRODUCTS_READ,)
  268. if any(
  269. marker in path
  270. for marker in (
  271. "/reconcile",
  272. "/fulfill",
  273. )
  274. ) or path == "/api/dataservice/governance/products":
  275. return (DATA_PRODUCTS_MANAGE,)
  276. return (DATA_PRODUCTS_OPERATE,)
  277. if path.startswith("/api/knowledge/agents"):
  278. if method == "GET":
  279. return (AGENTS_READ,)
  280. if path == "/api/knowledge/agents" or path.endswith("/reconcile"):
  281. return (AGENTS_MANAGE,)
  282. return (AGENTS_OPERATE,)
  283. if path.startswith("/api/meta/domain-templates"):
  284. if method == "GET":
  285. return (DOMAIN_TEMPLATES_READ,)
  286. if path == "/api/meta/domain-templates/dry-run":
  287. return (DOMAIN_TEMPLATES_PREVIEW,)
  288. return (DOMAIN_TEMPLATES_MANAGE,)
  289. if path.startswith("/api/meta/active-metadata"):
  290. if method == "GET":
  291. return (ACTIVE_METADATA_READ,)
  292. if path == "/api/meta/active-metadata/plans":
  293. return (ACTIVE_METADATA_MANAGE,)
  294. return (ACTIVE_METADATA_OPERATE,)
  295. if path.startswith("/api/datafactory/observability"):
  296. if method == "GET":
  297. return (DATA_OBSERVABILITY_READ,)
  298. if path == "/api/datafactory/observability/slos":
  299. return (DATA_OBSERVABILITY_MANAGE,)
  300. return (DATA_OBSERVABILITY_OPERATE,)
  301. if path in {"/api/knowledge/search", "/api/knowledge/ask"}:
  302. return (READ_GOVERNANCE,)
  303. if path.startswith("/api/rules"):
  304. if path.startswith("/api/rules/deployments"):
  305. if method == "GET":
  306. return (RULES_READ,)
  307. if path.endswith("/deploy-disabled"):
  308. return (DATAFLOW_DEPLOY,)
  309. if path.endswith("/canary"):
  310. return (DATAFLOW_CANARY, RULES_EXECUTE)
  311. if path.endswith("/execute"):
  312. return (RULES_EXECUTE,)
  313. if path.endswith("/activate"):
  314. return (DATAFLOW_ACTIVATE,)
  315. if path.endswith("/rollback"):
  316. return (DATAFLOW_ROLLBACK,)
  317. if path.endswith("/reconcile-deploy"):
  318. return (DATAFLOW_DEPLOY,)
  319. if path.endswith("/reconcile-activate"):
  320. return (DATAFLOW_ACTIVATE,)
  321. if path.endswith("/reconcile-rollback"):
  322. return (DATAFLOW_ROLLBACK,)
  323. if path.endswith("/reconcile-execute"):
  324. return (RULES_EXECUTE,)
  325. if method == "POST":
  326. return (DATAFLOW_DEPLOY,)
  327. return (DATAFLOW_DEPLOY,)
  328. if method == "GET":
  329. return (RULES_READ,)
  330. if path.endswith("/publish"):
  331. return (RULES_PUBLISH,)
  332. if path.startswith("/api/rules/production-lines/") and path.endswith(
  333. "/release"
  334. ):
  335. return (DATAFLOW_RELEASE,)
  336. if path.endswith("/test") or path.endswith("/execute"):
  337. return (RULES_EXECUTE,)
  338. if method in {"POST", "PUT", "PATCH", "DELETE"}:
  339. return (RULES_EDIT,)
  340. return (RULES_PUBLISH,)
  341. if path.startswith("/api/knowledge/admin"):
  342. return (KNOWLEDGE_MANAGE,)
  343. if path.startswith("/api/system/users"):
  344. return (MANAGE_USERS,)
  345. if path.startswith("/api/development/v1/governance-metrics"):
  346. return (READ_GOVERNANCE,)
  347. if path.startswith("/api/development/v1/semantic"):
  348. if method == "GET":
  349. return (READ_GOVERNANCE,)
  350. if path.endswith("/review"):
  351. return (SEMANTIC_GOVERNANCE_REVIEW,)
  352. if path.endswith("/publish") or path.endswith("/rollback"):
  353. return (SEMANTIC_GOVERNANCE_PUBLISH,)
  354. return (SEMANTIC_GOVERNANCE_EDIT,)
  355. if path.startswith("/api/development/v1/device-assets"):
  356. if method == "GET":
  357. return (READ_GOVERNANCE,)
  358. return (DEVICE_ASSETS_EDIT,)
  359. if path.startswith("/api/development/v1/device-semantics"):
  360. if method == "GET":
  361. return (READ_GOVERNANCE,)
  362. if path.endswith("/review"):
  363. return (DEVICE_SEMANTICS_REVIEW,)
  364. return (DEVICE_SEMANTICS_EDIT,)
  365. if path.startswith("/api/development/v1/device-entities"):
  366. if method == "GET":
  367. return (READ_GOVERNANCE,)
  368. if path.endswith("/review") or path.endswith("/rollback"):
  369. return (DEVICE_ENTITIES_REVIEW,)
  370. return (DEVICE_ENTITIES_EDIT,)
  371. if path.startswith("/api/development/v1/device-observability"):
  372. if method == "GET":
  373. return (READ_GOVERNANCE,)
  374. return (DEVICE_OBSERVABILITY_EDIT,)
  375. if path.startswith("/api/development/v1/device-quality"):
  376. if method == "GET":
  377. return (READ_GOVERNANCE,)
  378. if path.startswith(
  379. "/api/development/v1/device-quality/issues"
  380. ):
  381. if path.endswith("/review"):
  382. return (QUALITY_ISSUES_REVIEW,)
  383. return (QUALITY_ISSUES_EDIT,)
  384. if path.endswith("/publish"):
  385. return (DEVICE_QUALITY_PUBLISH,)
  386. if path == "/api/development/v1/device-quality/runs":
  387. return (DEVICE_QUALITY_EXECUTE,)
  388. return (DEVICE_QUALITY_EDIT,)
  389. if path.startswith("/api/development/v1/ingestion-jobs"):
  390. if method == "GET":
  391. return (READ_GOVERNANCE,)
  392. if path.endswith("/retry"):
  393. return (INGESTION_ADMIN,)
  394. if path.endswith("/cancel"):
  395. return (INGESTION_RUN,)
  396. if method == "POST":
  397. return (INGESTION_RUN,)
  398. if path.startswith("/api/development/v1/data-elements"):
  399. if method == "GET":
  400. return (READ_GOVERNANCE,)
  401. return (DATA_ELEMENTS_EDIT,)
  402. if path.startswith("/api/development/v1/candidate-decisions"):
  403. return (DATA_ELEMENTS_EDIT,)
  404. if path.startswith("/api/development/v1/sources/files"):
  405. return (INGESTION_RUN,)
  406. if path.startswith("/api/development/v1/evidence"):
  407. return (READ_GOVERNANCE,)
  408. if path.startswith("/api/development/v1/ontologies"):
  409. if method == "GET":
  410. return (READ_GOVERNANCE,)
  411. if path.endswith("/publish") or path.endswith("/rollback"):
  412. return (ONTOLOGIES_PUBLISH,)
  413. return (ONTOLOGIES_EDIT,)
  414. if path.startswith("/api/bd/"):
  415. if path in {
  416. "/api/bd/list",
  417. "/api/bd/detail",
  418. "/api/bd/graphall",
  419. "/api/bd/search",
  420. }:
  421. return (READ_GOVERNANCE,)
  422. return (EDIT_GOVERNANCE,)
  423. if path.startswith("/api/system/workbench"):
  424. return (READ_GOVERNANCE,)
  425. if path == "/api/datasource/pools" or (
  426. path.startswith("/api/datasource/")
  427. and (path.endswith("/pool") or path.endswith("/pool/invalidate"))
  428. ):
  429. return (DATASOURCE_POOL_MANAGE,)
  430. if path == "/api/datasource/list":
  431. return (READ_GOVERNANCE,)
  432. if path.startswith("/api/system/") and not path.startswith("/api/system/auth/me"):
  433. return (MANAGE_USERS,)
  434. if path.startswith("/api/dataflow/") and any(
  435. marker in path.lower() for marker in ("activate", "publish", "execute")
  436. ):
  437. return (ACTIVATE_WORKFLOW,)
  438. if "order" in path.lower() and method != "GET":
  439. return (OPERATE_ORDERS,)
  440. if method == "GET":
  441. return (READ_GOVERNANCE,)
  442. if method in {"POST", "PUT", "PATCH", "DELETE"}:
  443. return (EDIT_GOVERNANCE,)
  444. return (MANAGE_USERS,)
  445. def permissions_for_roles(roles: Iterable[str]) -> frozenset[str]:
  446. result: set[str] = set()
  447. for role in roles:
  448. result.update(ROLE_PERMISSIONS.get(role, ()))
  449. return frozenset(result)
  450. def authenticate_request() -> dict | None:
  451. from app.core.system.auth import load_identity_from_token
  452. existing = getattr(g, "current_user", None)
  453. if existing:
  454. return existing
  455. header = request.headers.get("Authorization", "")
  456. if not header.startswith("Bearer "):
  457. return None
  458. token = header[7:].strip()
  459. if not token:
  460. return None
  461. return load_identity_from_token(token, secret=current_app.config["SECRET_KEY"])
  462. def configure_api_authorization(app) -> None:
  463. @app.before_request
  464. def enforce_api_policy():
  465. if not request.path.startswith("/api/") or request.method == "OPTIONS":
  466. return None
  467. required = permission_for_request(request.path, request.method)
  468. if required == (PUBLIC,):
  469. return None
  470. identity = authenticate_request()
  471. if identity is None:
  472. return jsonify(failed("未登录或登录已过期", code=401)), 401
  473. permissions = permissions_for_roles(identity["roles"])
  474. if not set(required).issubset(permissions):
  475. return jsonify(failed("权限不足", code=403)), 403
  476. identity["permissions"] = sorted(permissions)
  477. g.current_user = identity
  478. return None
  479. def require_permissions(*required: str):
  480. def decorator(view):
  481. @wraps(view)
  482. def wrapped(*args, **kwargs):
  483. identity = authenticate_request()
  484. if identity is None:
  485. return jsonify(failed("未登录或登录已过期", code=401)), 401
  486. permissions = permissions_for_roles(identity["roles"])
  487. if not set(required).issubset(permissions):
  488. return jsonify(failed("权限不足", code=403)), 403
  489. identity["permissions"] = sorted(permissions)
  490. g.current_user = identity
  491. return view(*args, **kwargs)
  492. wrapped.required_permissions = tuple(required)
  493. return wrapped
  494. return decorator