mage-ai 0.9.32__py3-none-any.whl → 0.9.34__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of mage-ai might be problematic. Click here for more details.

Files changed (365) hide show
  1. mage_ai/api/constants.py +37 -2
  2. mage_ai/api/errors.py +9 -5
  3. mage_ai/api/middleware.py +2 -2
  4. mage_ai/api/mixins/__init__.py +0 -0
  5. mage_ai/api/mixins/result_set.py +65 -0
  6. mage_ai/api/operations/base.py +41 -11
  7. mage_ai/api/operations/constants.py +7 -6
  8. mage_ai/api/parsers/BaseParser.py +6 -4
  9. mage_ai/api/parsers/PipelineScheduleParser.py +36 -2
  10. mage_ai/api/policies/BackfillPolicy.py +1 -0
  11. mage_ai/api/policies/BasePolicy.py +229 -40
  12. mage_ai/api/policies/PermissionPolicy.py +17 -0
  13. mage_ai/api/policies/PipelineSchedulePolicy.py +18 -2
  14. mage_ai/api/policies/RolePermissionPolicy.py +2 -1
  15. mage_ai/api/policies/RolePolicy.py +3 -0
  16. mage_ai/api/policies/SessionPolicy.py +36 -1
  17. mage_ai/api/policies/StatusPolicy.py +46 -3
  18. mage_ai/api/policies/UserPolicy.py +25 -2
  19. mage_ai/api/policies/mixins/__init__.py +0 -0
  20. mage_ai/api/policies/mixins/user_permissions.py +241 -0
  21. mage_ai/api/presenters/PermissionPresenter.py +35 -8
  22. mage_ai/api/presenters/RolePresenter.py +29 -12
  23. mage_ai/api/presenters/StatusPresenter.py +11 -0
  24. mage_ai/api/presenters/UserPresenter.py +21 -24
  25. mage_ai/api/resources/BaseResource.py +24 -9
  26. mage_ai/api/resources/BlockResource.py +3 -2
  27. mage_ai/api/resources/DatabaseResource.py +2 -2
  28. mage_ai/api/resources/PermissionResource.py +65 -3
  29. mage_ai/api/resources/Resource.py +8 -1
  30. mage_ai/api/resources/RolePermissionResource.py +23 -0
  31. mage_ai/api/resources/RoleResource.py +75 -4
  32. mage_ai/api/resources/StatusResource.py +34 -0
  33. mage_ai/api/resources/UserResource.py +35 -4
  34. mage_ai/api/result_set.py +8 -1
  35. mage_ai/authentication/permissions/constants.py +78 -0
  36. mage_ai/data_integrations/destinations/constants.py +1 -0
  37. mage_ai/data_integrations/utils/config.py +9 -0
  38. mage_ai/data_integrations/utils/scheduler.py +16 -7
  39. mage_ai/data_preparation/models/block/__init__.py +56 -16
  40. mage_ai/data_preparation/models/block/data_integration/mixins.py +92 -1
  41. mage_ai/data_preparation/models/block/data_integration/utils.py +5 -5
  42. mage_ai/data_preparation/models/block/integration/__init__.py +12 -7
  43. mage_ai/data_preparation/models/block/sql/__init__.py +5 -2
  44. mage_ai/data_preparation/models/block/sql/utils/shared.py +15 -1
  45. mage_ai/data_preparation/models/pipelines/interactions.py +15 -0
  46. mage_ai/data_preparation/shared/secrets.py +7 -17
  47. mage_ai/data_preparation/templates/data_exporters/streaming/google_cloud_pubsub.yaml +5 -0
  48. mage_ai/data_preparation/templates/repo/metadata.yaml +5 -0
  49. mage_ai/io/druid.py +9 -5
  50. mage_ai/io/mssql.py +52 -30
  51. mage_ai/io/postgres.py +1 -0
  52. mage_ai/io/sql.py +15 -1
  53. mage_ai/orchestration/db/models/oauth.py +181 -17
  54. mage_ai/orchestration/db/models/schedules.py +11 -0
  55. mage_ai/presenters/interactions/constants.py +1 -0
  56. mage_ai/presenters/pages/loaders/pipeline_schedules.py +9 -3
  57. mage_ai/server/api/base.py +16 -6
  58. mage_ai/server/api/v1.py +2 -1
  59. mage_ai/server/constants.py +1 -1
  60. mage_ai/server/frontend_dist/404.html +2 -2
  61. mage_ai/server/frontend_dist/_next/static/PBVuphyo_muEAj347ZP_b/_buildManifest.js +1 -0
  62. mage_ai/server/frontend_dist/_next/static/chunks/1124-d8fc76201b83b376.js +1 -0
  63. mage_ai/server/frontend_dist/_next/static/chunks/{1749-9ec0f4709f5a9284.js → 1749-a6bdce4ee8a09bce.js} +1 -1
  64. mage_ai/server/frontend_dist/_next/static/chunks/1769-613e23e361eb5bce.js +1 -0
  65. mage_ai/server/frontend_dist/_next/static/chunks/1821-953efd0da290d25f.js +1 -0
  66. mage_ai/server/frontend_dist/_next/static/chunks/2327-1a797c758f8b064a.js +1 -0
  67. mage_ai/server/frontend_dist/_next/static/chunks/2677-a85c5a72bb695304.js +1 -0
  68. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/3419-6c8ec8db8c398c12.js → frontend_dist/_next/static/chunks/3419-0df6c5ef72f2e672.js} +1 -1
  69. mage_ai/server/frontend_dist/_next/static/chunks/3684-e1a713b7c16f0151.js +1 -0
  70. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/3859-3501cdba0a33f9f2.js → frontend_dist/_next/static/chunks/3859-ba594d21a1260cd2.js} +1 -1
  71. mage_ai/server/frontend_dist/_next/static/chunks/4636-84f545d1d238df13.js +1 -0
  72. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/4839-963da65cea58054f.js → frontend_dist/_next/static/chunks/4839-e5fe343a369734bc.js} +1 -1
  73. mage_ai/server/frontend_dist/_next/static/chunks/5457-97433bc45b42a88a.js +1 -0
  74. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/5499-63667ad5a785dba5.js → frontend_dist/_next/static/chunks/5499-b74459f6be5f9229.js} +1 -1
  75. mage_ai/server/frontend_dist/_next/static/chunks/6043-6ea109833b88eb1d.js +1 -0
  76. mage_ai/server/frontend_dist/_next/static/chunks/6333-ca4cd6a73a597a40.js +1 -0
  77. mage_ai/server/frontend_dist/_next/static/chunks/7022-80d082a1d7fd1234.js +1 -0
  78. mage_ai/server/frontend_dist/_next/static/chunks/7361-25f211ef377e5958.js +1 -0
  79. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/8013-1d6b1f7c13264cb4.js → frontend_dist/_next/static/chunks/8013-e67c71ddea072a20.js} +1 -1
  80. mage_ai/server/frontend_dist/_next/static/chunks/8146-941c5155c3bfcc35.js +1 -0
  81. mage_ai/server/frontend_dist/_next/static/chunks/9161-837b653aa849a76f.js +1 -0
  82. mage_ai/server/frontend_dist/_next/static/chunks/9264-5730e4e059db40a8.js +1 -0
  83. mage_ai/server/frontend_dist/_next/static/chunks/pages/_app-752d991f239a128f.js +1 -0
  84. mage_ai/server/frontend_dist/_next/static/chunks/pages/{block-layout-a624972d126a3dbd.js → block-layout-a27a28d2a615e364.js} +1 -1
  85. mage_ai/server/frontend_dist/_next/static/chunks/pages/files-98e27a4d7bd0da47.js +1 -0
  86. mage_ai/server/frontend_dist/_next/static/chunks/pages/global-data-products/{[...slug]-a95787fe1695f493.js → [...slug]-9eb5dad57da13efd.js} +1 -1
  87. mage_ai/server/frontend_dist/_next/static/chunks/pages/{global-data-products-0d55711df91a78d0.js → global-data-products-26909dec66f00231.js} +1 -1
  88. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/settings-b9eea6abc676ca81.js +1 -0
  89. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users/[user]-11c601c6ef07fe86.js +1 -0
  90. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users/new-ae6083077c9c1c41.js +1 -0
  91. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users-bfce0ee677d57206.js +1 -0
  92. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage-476c921d62f565fc.js +1 -0
  93. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/overview-9bc34ef66d84330a.js → frontend_dist/_next/static/chunks/pages/overview-2ec6b17e45a52be8.js} +1 -1
  94. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipeline-runs-605918f3a5c1aac4.js +1 -0
  95. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-84ec3ab0770bcd68.js → frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-ee65a62ed166bd85.js} +1 -1
  96. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills-2402004a19a6ad53.js +1 -0
  97. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/dashboard-9a5b4768a640cd68.js → frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/dashboard-8781db69f19759a1.js} +1 -1
  98. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-d7fd4857579e2b00.js +1 -0
  99. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/logs-bd4bd009146bab36.js → frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/logs-234007c99efdccf6.js} +1 -1
  100. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/monitors/{block-runs-8405a83adeaa8ff5.js → block-runs-da7510d4b277e47b.js} +1 -1
  101. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/monitors/{block-runtime-98aa0840a00cc661.js → block-runtime-eae853ff34b09481.js} +1 -1
  102. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/{monitors-0fb48e1cc51f78b2.js → monitors-a057b17847b82468.js} +1 -1
  103. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/runs/{[run]-845c1f010d5ec380.js → [run]-99d11c86f8b15369.js} +1 -1
  104. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/runs-9255f85760e1f57a.js → frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/runs-3f0980d8810a540b.js} +1 -1
  105. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/{settings-187ac359020704e1.js → settings-9edf75d03460aaeb.js} +1 -1
  106. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/syncs-c52d0c49439ec620.js → frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/syncs-e7692e54979f037d.js} +1 -1
  107. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers/[...slug]-7b3b57523b226a0e.js → frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers/[...slug]-cb6a3bcaf4fa1a81.js} +1 -1
  108. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers-5eff96c149584e87.js +1 -0
  109. mage_ai/server/frontend_dist/_next/static/chunks/pages/{pipelines-efc080913a247e99.js → pipelines-270b912e1ac189b5.js} +1 -1
  110. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/account/profile-2058d022972cdea4.js +1 -0
  111. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/permissions/[...slug]-1a95628ea8d0d846.js +1 -0
  112. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/permissions-cb1cdf5f8e5bf9c5.js +1 -0
  113. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/preferences-58978256db4efbda.js +1 -0
  114. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/roles/[...slug]-9ddd7eb842d5a911.js +1 -0
  115. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/roles-1694c5eb1acbcf30.js +1 -0
  116. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/sync-data-6850e854fbedbb61.js +1 -0
  117. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/users/[...slug]-5061c073e1c0de07.js +1 -0
  118. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/users-f551c5665bfd3494.js +1 -0
  119. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/sign-in-6fe3657070f98aca.js → frontend_dist/_next/static/chunks/pages/sign-in-e779dbab123e626e.js} +1 -1
  120. mage_ai/server/frontend_dist/_next/static/chunks/pages/templates/[...slug]-935113d252ada806.js +1 -0
  121. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/templates-25b6d24cfa81c306.js → frontend_dist/_next/static/chunks/pages/templates-7079d637e396f2a8.js} +1 -1
  122. mage_ai/server/frontend_dist/_next/static/chunks/pages/terminal-67fdb4a3be93aa14.js +1 -0
  123. mage_ai/server/frontend_dist/_next/static/chunks/pages/triggers-9c0374c7c783b34a.js +1 -0
  124. mage_ai/server/frontend_dist/_next/static/chunks/pages/version-control-ea3a0d48e5822b42.js +1 -0
  125. mage_ai/server/frontend_dist/block-layout.html +2 -2
  126. mage_ai/server/frontend_dist/files.html +2 -2
  127. mage_ai/server/frontend_dist/global-data-products/[...slug].html +2 -2
  128. mage_ai/server/frontend_dist/global-data-products.html +2 -2
  129. mage_ai/server/frontend_dist/index.html +2 -2
  130. mage_ai/server/frontend_dist/manage/settings.html +2 -2
  131. mage_ai/server/frontend_dist/manage/users/[user].html +2 -2
  132. mage_ai/server/frontend_dist/manage/users/new.html +2 -2
  133. mage_ai/server/frontend_dist/manage/users.html +2 -2
  134. mage_ai/server/frontend_dist/manage.html +2 -2
  135. mage_ai/server/frontend_dist/overview.html +2 -2
  136. mage_ai/server/frontend_dist/pipeline-runs.html +2 -2
  137. mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills/[...slug].html +2 -2
  138. mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills.html +2 -2
  139. mage_ai/server/frontend_dist/pipelines/[pipeline]/dashboard.html +2 -2
  140. mage_ai/server/frontend_dist/pipelines/[pipeline]/edit.html +2 -2
  141. mage_ai/server/frontend_dist/pipelines/[pipeline]/logs.html +2 -2
  142. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runs.html +2 -2
  143. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runtime.html +2 -2
  144. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors.html +2 -2
  145. mage_ai/server/frontend_dist/pipelines/[pipeline]/runs/[run].html +2 -2
  146. mage_ai/server/frontend_dist/pipelines/[pipeline]/runs.html +2 -2
  147. mage_ai/server/frontend_dist/pipelines/[pipeline]/settings.html +2 -2
  148. mage_ai/server/frontend_dist/pipelines/[pipeline]/syncs.html +2 -2
  149. mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers/[...slug].html +2 -2
  150. mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers.html +2 -2
  151. mage_ai/server/frontend_dist/pipelines/[pipeline].html +2 -2
  152. mage_ai/server/frontend_dist/pipelines.html +2 -2
  153. mage_ai/server/frontend_dist/settings/account/profile.html +2 -2
  154. mage_ai/server/frontend_dist/settings/workspace/permissions/[...slug].html +24 -0
  155. mage_ai/server/frontend_dist/settings/workspace/permissions.html +24 -0
  156. mage_ai/server/frontend_dist/settings/workspace/preferences.html +2 -2
  157. mage_ai/server/frontend_dist/settings/workspace/roles/[...slug].html +24 -0
  158. mage_ai/server/frontend_dist/settings/workspace/roles.html +24 -0
  159. mage_ai/server/frontend_dist/settings/workspace/sync-data.html +2 -2
  160. mage_ai/server/frontend_dist/settings/workspace/users/[...slug].html +24 -0
  161. mage_ai/server/frontend_dist/settings/workspace/users.html +2 -2
  162. mage_ai/server/frontend_dist/settings.html +2 -2
  163. mage_ai/server/frontend_dist/sign-in.html +5 -5
  164. mage_ai/server/frontend_dist/templates/[...slug].html +2 -2
  165. mage_ai/server/frontend_dist/templates.html +2 -2
  166. mage_ai/server/frontend_dist/terminal.html +2 -2
  167. mage_ai/server/frontend_dist/test.html +2 -2
  168. mage_ai/server/frontend_dist/triggers.html +2 -2
  169. mage_ai/server/frontend_dist/version-control.html +2 -2
  170. mage_ai/server/frontend_dist_base_path_template/404.html +2 -2
  171. mage_ai/server/frontend_dist_base_path_template/_next/static/L-IKw5_bRZUs-wyjnpN_j/_buildManifest.js +1 -0
  172. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/1124-d8fc76201b83b376.js +1 -0
  173. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{1749-9ec0f4709f5a9284.js → 1749-a6bdce4ee8a09bce.js} +1 -1
  174. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/1769-613e23e361eb5bce.js +1 -0
  175. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/1821-953efd0da290d25f.js +1 -0
  176. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/2327-1a797c758f8b064a.js +1 -0
  177. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/2677-a85c5a72bb695304.js +1 -0
  178. mage_ai/server/{frontend_dist/_next/static/chunks/3419-6c8ec8db8c398c12.js → frontend_dist_base_path_template/_next/static/chunks/3419-0df6c5ef72f2e672.js} +1 -1
  179. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/3684-e1a713b7c16f0151.js +1 -0
  180. mage_ai/server/{frontend_dist/_next/static/chunks/3859-3501cdba0a33f9f2.js → frontend_dist_base_path_template/_next/static/chunks/3859-ba594d21a1260cd2.js} +1 -1
  181. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/4636-84f545d1d238df13.js +1 -0
  182. mage_ai/server/{frontend_dist/_next/static/chunks/4839-963da65cea58054f.js → frontend_dist_base_path_template/_next/static/chunks/4839-e5fe343a369734bc.js} +1 -1
  183. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/5457-97433bc45b42a88a.js +1 -0
  184. mage_ai/server/{frontend_dist/_next/static/chunks/5499-63667ad5a785dba5.js → frontend_dist_base_path_template/_next/static/chunks/5499-b74459f6be5f9229.js} +1 -1
  185. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/6043-6ea109833b88eb1d.js +1 -0
  186. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/6333-ca4cd6a73a597a40.js +1 -0
  187. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/7022-80d082a1d7fd1234.js +1 -0
  188. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/7361-25f211ef377e5958.js +1 -0
  189. mage_ai/server/{frontend_dist/_next/static/chunks/8013-1d6b1f7c13264cb4.js → frontend_dist_base_path_template/_next/static/chunks/8013-e67c71ddea072a20.js} +1 -1
  190. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/8146-941c5155c3bfcc35.js +1 -0
  191. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/9161-837b653aa849a76f.js +1 -0
  192. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/9264-5730e4e059db40a8.js +1 -0
  193. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/_app-752d991f239a128f.js +1 -0
  194. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/{block-layout-a624972d126a3dbd.js → block-layout-a27a28d2a615e364.js} +1 -1
  195. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/files-98e27a4d7bd0da47.js +1 -0
  196. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/global-data-products/{[...slug]-a95787fe1695f493.js → [...slug]-9eb5dad57da13efd.js} +1 -1
  197. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/{global-data-products-0d55711df91a78d0.js → global-data-products-26909dec66f00231.js} +1 -1
  198. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage/settings-b9eea6abc676ca81.js +1 -0
  199. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage/users/[user]-11c601c6ef07fe86.js +1 -0
  200. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage/users/new-ae6083077c9c1c41.js +1 -0
  201. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage/users-bfce0ee677d57206.js +1 -0
  202. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage-476c921d62f565fc.js +1 -0
  203. mage_ai/server/{frontend_dist/_next/static/chunks/pages/overview-9bc34ef66d84330a.js → frontend_dist_base_path_template/_next/static/chunks/pages/overview-2ec6b17e45a52be8.js} +1 -1
  204. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipeline-runs-605918f3a5c1aac4.js +1 -0
  205. mage_ai/server/{frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-84ec3ab0770bcd68.js → frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-ee65a62ed166bd85.js} +1 -1
  206. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills-2402004a19a6ad53.js +1 -0
  207. mage_ai/server/{frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/dashboard-9a5b4768a640cd68.js → frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/dashboard-8781db69f19759a1.js} +1 -1
  208. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/edit-d7fd4857579e2b00.js +1 -0
  209. mage_ai/server/{frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/logs-bd4bd009146bab36.js → frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/logs-234007c99efdccf6.js} +1 -1
  210. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/monitors/{block-runs-8405a83adeaa8ff5.js → block-runs-da7510d4b277e47b.js} +1 -1
  211. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/monitors/{block-runtime-98aa0840a00cc661.js → block-runtime-eae853ff34b09481.js} +1 -1
  212. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/{monitors-0fb48e1cc51f78b2.js → monitors-a057b17847b82468.js} +1 -1
  213. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/runs/{[run]-845c1f010d5ec380.js → [run]-99d11c86f8b15369.js} +1 -1
  214. mage_ai/server/{frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/runs-9255f85760e1f57a.js → frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/runs-3f0980d8810a540b.js} +1 -1
  215. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/{settings-187ac359020704e1.js → settings-9edf75d03460aaeb.js} +1 -1
  216. mage_ai/server/{frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/syncs-c52d0c49439ec620.js → frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/syncs-e7692e54979f037d.js} +1 -1
  217. mage_ai/server/{frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers/[...slug]-7b3b57523b226a0e.js → frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers/[...slug]-cb6a3bcaf4fa1a81.js} +1 -1
  218. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers-5eff96c149584e87.js +1 -0
  219. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/{pipelines-efc080913a247e99.js → pipelines-270b912e1ac189b5.js} +1 -1
  220. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/account/profile-2058d022972cdea4.js +1 -0
  221. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/permissions/[...slug]-1a95628ea8d0d846.js +1 -0
  222. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/permissions-cb1cdf5f8e5bf9c5.js +1 -0
  223. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/preferences-58978256db4efbda.js +1 -0
  224. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/roles/[...slug]-9ddd7eb842d5a911.js +1 -0
  225. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/roles-1694c5eb1acbcf30.js +1 -0
  226. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/sync-data-6850e854fbedbb61.js +1 -0
  227. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/users/[...slug]-5061c073e1c0de07.js +1 -0
  228. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/users-f551c5665bfd3494.js +1 -0
  229. mage_ai/server/{frontend_dist/_next/static/chunks/pages/sign-in-6fe3657070f98aca.js → frontend_dist_base_path_template/_next/static/chunks/pages/sign-in-e779dbab123e626e.js} +1 -1
  230. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/templates/[...slug]-935113d252ada806.js +1 -0
  231. mage_ai/server/{frontend_dist/_next/static/chunks/pages/templates-25b6d24cfa81c306.js → frontend_dist_base_path_template/_next/static/chunks/pages/templates-7079d637e396f2a8.js} +1 -1
  232. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/terminal-67fdb4a3be93aa14.js +1 -0
  233. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/triggers-9c0374c7c783b34a.js +1 -0
  234. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/version-control-ea3a0d48e5822b42.js +1 -0
  235. mage_ai/server/frontend_dist_base_path_template/block-layout.html +2 -2
  236. mage_ai/server/frontend_dist_base_path_template/files.html +5 -5
  237. mage_ai/server/frontend_dist_base_path_template/global-data-products/[...slug].html +5 -5
  238. mage_ai/server/frontend_dist_base_path_template/global-data-products.html +5 -5
  239. mage_ai/server/frontend_dist_base_path_template/index.html +2 -2
  240. mage_ai/server/frontend_dist_base_path_template/manage/settings.html +5 -5
  241. mage_ai/server/frontend_dist_base_path_template/manage/users/[user].html +5 -5
  242. mage_ai/server/frontend_dist_base_path_template/manage/users/new.html +5 -5
  243. mage_ai/server/frontend_dist_base_path_template/manage/users.html +5 -5
  244. mage_ai/server/frontend_dist_base_path_template/manage.html +5 -5
  245. mage_ai/server/frontend_dist_base_path_template/overview.html +5 -5
  246. mage_ai/server/frontend_dist_base_path_template/pipeline-runs.html +5 -5
  247. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/backfills/[...slug].html +5 -5
  248. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/backfills.html +5 -5
  249. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/dashboard.html +5 -5
  250. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/edit.html +2 -2
  251. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/logs.html +5 -5
  252. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors/block-runs.html +5 -5
  253. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors/block-runtime.html +5 -5
  254. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors.html +5 -5
  255. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/runs/[run].html +5 -5
  256. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/runs.html +5 -5
  257. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/settings.html +5 -5
  258. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/syncs.html +5 -5
  259. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/triggers/[...slug].html +5 -5
  260. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/triggers.html +5 -5
  261. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline].html +2 -2
  262. mage_ai/server/frontend_dist_base_path_template/pipelines.html +5 -5
  263. mage_ai/server/frontend_dist_base_path_template/settings/account/profile.html +5 -5
  264. mage_ai/server/frontend_dist_base_path_template/settings/workspace/permissions/[...slug].html +24 -0
  265. mage_ai/server/frontend_dist_base_path_template/settings/workspace/permissions.html +24 -0
  266. mage_ai/server/frontend_dist_base_path_template/settings/workspace/preferences.html +5 -5
  267. mage_ai/server/frontend_dist_base_path_template/settings/workspace/roles/[...slug].html +24 -0
  268. mage_ai/server/frontend_dist_base_path_template/settings/workspace/roles.html +24 -0
  269. mage_ai/server/frontend_dist_base_path_template/settings/workspace/sync-data.html +5 -5
  270. mage_ai/server/frontend_dist_base_path_template/settings/workspace/users/[...slug].html +24 -0
  271. mage_ai/server/frontend_dist_base_path_template/settings/workspace/users.html +5 -5
  272. mage_ai/server/frontend_dist_base_path_template/settings.html +2 -2
  273. mage_ai/server/frontend_dist_base_path_template/sign-in.html +13 -13
  274. mage_ai/server/frontend_dist_base_path_template/templates/[...slug].html +5 -5
  275. mage_ai/server/frontend_dist_base_path_template/templates.html +5 -5
  276. mage_ai/server/frontend_dist_base_path_template/terminal.html +5 -5
  277. mage_ai/server/frontend_dist_base_path_template/test.html +4 -4
  278. mage_ai/server/frontend_dist_base_path_template/triggers.html +5 -5
  279. mage_ai/server/frontend_dist_base_path_template/version-control.html +5 -5
  280. mage_ai/server/server.py +37 -2
  281. mage_ai/services/aws/__init__.py +19 -8
  282. mage_ai/services/spark/config.py +5 -1
  283. mage_ai/services/spark/spark.py +5 -4
  284. mage_ai/settings/__init__.py +2 -0
  285. mage_ai/streaming/constants.py +1 -0
  286. mage_ai/streaming/sinks/google_cloud_pubsub.py +66 -0
  287. mage_ai/streaming/sinks/sink_factory.py +6 -0
  288. mage_ai/tests/api/operations/test_operations.py +8 -2
  289. mage_ai/tests/api/operations/test_pipeline_schedules.py +3 -0
  290. mage_ai/tests/data_preparation/models/block/sql/utils/test_shared.py +130 -5
  291. mage_ai/tests/factory.py +2 -0
  292. mage_ai/tests/orchestration/test_pipeline_scheduler.py +20 -3
  293. mage_ai/tests/streaming/sinks/test_google_cloud_pubsub.py +28 -0
  294. mage_ai/usage_statistics/logger.py +4 -1
  295. {mage_ai-0.9.32.dist-info → mage_ai-0.9.34.dist-info}/METADATA +1 -1
  296. {mage_ai-0.9.32.dist-info → mage_ai-0.9.34.dist-info}/RECORD +302 -268
  297. mage_ai/api/policies/utils.py +0 -14
  298. mage_ai/server/frontend_dist/_next/static/cbN7L4Ms6UUPoANozEh9w/_buildManifest.js +0 -1
  299. mage_ai/server/frontend_dist/_next/static/chunks/1769-e2efd4df8d09481f.js +0 -1
  300. mage_ai/server/frontend_dist/_next/static/chunks/2327-2d8a1555605cf4af.js +0 -1
  301. mage_ai/server/frontend_dist/_next/static/chunks/2369-cb9cd97052e18d27.js +0 -1
  302. mage_ai/server/frontend_dist/_next/static/chunks/2677-aea54e655c4a727f.js +0 -1
  303. mage_ai/server/frontend_dist/_next/static/chunks/5457-994e1044953f1425.js +0 -1
  304. mage_ai/server/frontend_dist/_next/static/chunks/5597-c034402ee26af3b4.js +0 -1
  305. mage_ai/server/frontend_dist/_next/static/chunks/6333-d1f8db4e7d9656a5.js +0 -1
  306. mage_ai/server/frontend_dist/_next/static/chunks/6648-9bd31397f1d1b551.js +0 -1
  307. mage_ai/server/frontend_dist/_next/static/chunks/9161-0101571a3635a938.js +0 -1
  308. mage_ai/server/frontend_dist/_next/static/chunks/9264-7c4fcfed1200046a.js +0 -1
  309. mage_ai/server/frontend_dist/_next/static/chunks/9696-3e8ab7786f0e3a0e.js +0 -1
  310. mage_ai/server/frontend_dist/_next/static/chunks/pages/_app-976b488b8aea327f.js +0 -1
  311. mage_ai/server/frontend_dist/_next/static/chunks/pages/files-1a6c6654bfc953ac.js +0 -1
  312. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/settings-49004d918b04c866.js +0 -1
  313. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users/[user]-97177740fbb6eb9f.js +0 -1
  314. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users/new-5b6411c9eb8c4ba4.js +0 -1
  315. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users-0a7a746cc0998608.js +0 -1
  316. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage-606012a580245001.js +0 -1
  317. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipeline-runs-0801de3c9a2976ea.js +0 -1
  318. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills-0a5865d4cf012127.js +0 -1
  319. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-87b1a0601c4236cd.js +0 -1
  320. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers-77ffaa2d721a0a4e.js +0 -1
  321. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/account/profile-4e72d40881ac0605.js +0 -1
  322. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/preferences-8da8f8e2fdb1c99f.js +0 -1
  323. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/sync-data-be029347e99fcea2.js +0 -1
  324. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/users-d4b07df78b3d6037.js +0 -1
  325. mage_ai/server/frontend_dist/_next/static/chunks/pages/templates/[...slug]-87c492e9edc6064e.js +0 -1
  326. mage_ai/server/frontend_dist/_next/static/chunks/pages/terminal-2ab9f3d0c50cce81.js +0 -1
  327. mage_ai/server/frontend_dist/_next/static/chunks/pages/triggers-0ba40243a030ddf4.js +0 -1
  328. mage_ai/server/frontend_dist/_next/static/chunks/pages/version-control-28a3a43d65074394.js +0 -1
  329. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/1769-e2efd4df8d09481f.js +0 -1
  330. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/2327-2d8a1555605cf4af.js +0 -1
  331. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/2369-cb9cd97052e18d27.js +0 -1
  332. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/2677-aea54e655c4a727f.js +0 -1
  333. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/5457-994e1044953f1425.js +0 -1
  334. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/5597-c034402ee26af3b4.js +0 -1
  335. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/6333-d1f8db4e7d9656a5.js +0 -1
  336. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/6648-9bd31397f1d1b551.js +0 -1
  337. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/9161-0101571a3635a938.js +0 -1
  338. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/9264-7c4fcfed1200046a.js +0 -1
  339. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/9696-3e8ab7786f0e3a0e.js +0 -1
  340. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/_app-976b488b8aea327f.js +0 -1
  341. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/files-1a6c6654bfc953ac.js +0 -1
  342. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage/settings-49004d918b04c866.js +0 -1
  343. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage/users/[user]-97177740fbb6eb9f.js +0 -1
  344. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage/users/new-5b6411c9eb8c4ba4.js +0 -1
  345. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage/users-0a7a746cc0998608.js +0 -1
  346. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage-606012a580245001.js +0 -1
  347. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipeline-runs-0801de3c9a2976ea.js +0 -1
  348. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills-0a5865d4cf012127.js +0 -1
  349. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/edit-87b1a0601c4236cd.js +0 -1
  350. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers-77ffaa2d721a0a4e.js +0 -1
  351. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/account/profile-4e72d40881ac0605.js +0 -1
  352. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/preferences-8da8f8e2fdb1c99f.js +0 -1
  353. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/sync-data-be029347e99fcea2.js +0 -1
  354. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/users-d4b07df78b3d6037.js +0 -1
  355. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/templates/[...slug]-87c492e9edc6064e.js +0 -1
  356. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/terminal-2ab9f3d0c50cce81.js +0 -1
  357. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/triggers-0ba40243a030ddf4.js +0 -1
  358. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/version-control-28a3a43d65074394.js +0 -1
  359. mage_ai/server/frontend_dist_base_path_template/_next/static/o_Yc57jh8Te2SI-VoipGQ/_buildManifest.js +0 -1
  360. /mage_ai/server/frontend_dist/_next/static/{cbN7L4Ms6UUPoANozEh9w → PBVuphyo_muEAj347ZP_b}/_ssgManifest.js +0 -0
  361. /mage_ai/server/frontend_dist_base_path_template/_next/static/{o_Yc57jh8Te2SI-VoipGQ → L-IKw5_bRZUs-wyjnpN_j}/_ssgManifest.js +0 -0
  362. {mage_ai-0.9.32.dist-info → mage_ai-0.9.34.dist-info}/LICENSE +0 -0
  363. {mage_ai-0.9.32.dist-info → mage_ai-0.9.34.dist-info}/WHEEL +0 -0
  364. {mage_ai-0.9.32.dist-info → mage_ai-0.9.34.dist-info}/entry_points.txt +0 -0
  365. {mage_ai-0.9.32.dist-info → mage_ai-0.9.34.dist-info}/top_level.txt +0 -0
@@ -1,15 +1,18 @@
1
1
  import importlib
2
2
  import inspect
3
3
  from collections.abc import Iterable
4
- from typing import List, Tuple, Union
4
+ from typing import Callable, List, Tuple, Union
5
5
 
6
6
  import inflection
7
7
 
8
8
  from mage_ai import settings
9
9
  from mage_ai.api.constants import AttributeOperationType, AttributeType
10
10
  from mage_ai.api.errors import ApiError
11
+ from mage_ai.api.mixins.result_set import ResultSetMixIn
11
12
  from mage_ai.api.oauth_scope import OauthScope
12
13
  from mage_ai.api.operations.constants import OperationType
14
+ from mage_ai.api.policies.mixins.user_permissions import UserPermissionMixIn
15
+ from mage_ai.api.result_set import ResultSet
13
16
  from mage_ai.api.utils import (
14
17
  has_at_least_admin_role,
15
18
  has_at_least_editor_role,
@@ -21,15 +24,23 @@ from mage_ai.api.utils import (
21
24
  from mage_ai.data_preparation.repo_manager import get_project_uuid
22
25
  from mage_ai.orchestration.constants import Entity
23
26
  from mage_ai.services.tracking.metrics import increment
24
- from mage_ai.settings import DISABLE_NOTEBOOK_EDIT_ACCESS, REQUIRE_USER_AUTHENTICATION
27
+ from mage_ai.settings import (
28
+ DISABLE_NOTEBOOK_EDIT_ACCESS,
29
+ REQUIRE_USER_AUTHENTICATION,
30
+ REQUIRE_USER_PERMISSIONS,
31
+ )
25
32
  from mage_ai.shared.hash import extract
26
33
 
27
34
 
28
- class BasePolicy():
35
+ class BasePolicy(UserPermissionMixIn, ResultSetMixIn):
29
36
  action_rules = {}
30
37
  query_rules = {}
31
38
  read_rules = {}
32
39
  write_rules = {}
40
+ override_permission_action_rules = {}
41
+ override_permission_query_rules = {}
42
+ override_permission_read_rules = {}
43
+ override_permission_write_rules = {}
33
44
 
34
45
  def __init__(self, resource, current_user, **kwargs):
35
46
  self.current_user = current_user
@@ -37,10 +48,15 @@ class BasePolicy():
37
48
  self.resource = resource
38
49
  self.parent_model_attr = None
39
50
  self.parent_resource_attr = None
40
- if isinstance(resource, Iterable):
41
- self.resources = resource
51
+ self.result_set_attr = None
52
+
53
+ if resource:
54
+ if isinstance(resource, Iterable):
55
+ self.resources = resource
56
+ else:
57
+ self.resources = [resource]
42
58
  else:
43
- self.resources = [resource]
59
+ self.result_set_attr = ResultSet([])
44
60
 
45
61
  @property
46
62
  def entity(self) -> Tuple[Union[Entity, None], Union[str, None]]:
@@ -48,60 +64,142 @@ class BasePolicy():
48
64
 
49
65
  @classmethod
50
66
  def action_rule(self, action):
67
+ if REQUIRE_USER_PERMISSIONS:
68
+ return self.action_rule_with_permissions(action)
69
+
51
70
  if not self.action_rules.get(self.__name__):
52
71
  self.action_rules[self.__name__] = {}
53
72
  return self.action_rules[self.__name__].get(action)
54
73
 
74
+ @classmethod
75
+ def override_permission_action_rule(self, action):
76
+ if not self.override_permission_action_rules.get(self.__name__):
77
+ self.override_permission_action_rules[self.__name__] = {}
78
+ return self.override_permission_action_rules[self.__name__].get(action)
79
+
55
80
  @classmethod
56
81
  def query_rule(self, query):
82
+ if REQUIRE_USER_PERMISSIONS:
83
+ return self.attribute_rule_with_permissions(AttributeOperationType.QUERY, query)
84
+
57
85
  if not self.query_rules.get(self.__name__):
58
86
  self.query_rules[self.__name__] = {}
59
87
  return self.query_rules[self.__name__].get(query)
60
88
 
89
+ @classmethod
90
+ def override_permission_query_rule(self, query):
91
+ if not self.override_permission_query_rules.get(self.__name__):
92
+ self.override_permission_query_rules[self.__name__] = {}
93
+ return self.override_permission_query_rules[self.__name__].get(query)
94
+
61
95
  @classmethod
62
96
  def read_rule(self, read):
97
+ if REQUIRE_USER_PERMISSIONS:
98
+ return self.attribute_rule_with_permissions(AttributeOperationType.READ, read)
99
+
63
100
  if not self.read_rules.get(self.__name__):
64
101
  self.read_rules[self.__name__] = {}
65
102
  return self.read_rules[self.__name__].get(read)
66
103
 
104
+ @classmethod
105
+ def override_permission_read_rule(self, read):
106
+ if not self.override_permission_read_rules.get(self.__name__):
107
+ self.override_permission_read_rules[self.__name__] = {}
108
+ return self.override_permission_read_rules[self.__name__].get(read)
109
+
67
110
  @classmethod
68
111
  def write_rule(self, write):
112
+ if REQUIRE_USER_PERMISSIONS:
113
+ return self.attribute_rule_with_permissions(AttributeOperationType.WRITE, write)
114
+
69
115
  if not self.write_rules.get(self.__name__):
70
116
  self.write_rules[self.__name__] = {}
71
117
  return self.write_rules[self.__name__].get(write)
72
118
 
119
+ @classmethod
120
+ def override_permission_write_rule(self, write):
121
+ if not self.override_permission_write_rules.get(self.__name__):
122
+ self.override_permission_write_rules[self.__name__] = {}
123
+ return self.override_permission_write_rules[self.__name__].get(write)
124
+
73
125
  @classmethod
74
126
  def allow_actions(self, array, **kwargs):
75
127
  if not self.action_rules.get(self.__name__):
76
128
  self.action_rules[self.__name__] = {}
77
- for key in array:
129
+ if not self.override_permission_action_rules.get(self.__name__):
130
+ self.override_permission_action_rules[self.__name__] = {}
131
+
132
+ array_use = array or [OperationType.ALL]
133
+ for key in array_use:
78
134
  if not self.action_rules[self.__name__].get(key):
79
135
  self.action_rules[self.__name__][key] = {}
80
136
  for scope in kwargs.get('scopes', []):
81
137
  self.action_rules[self.__name__][key][scope] = extract(kwargs, [
82
138
  'condition'])
139
+ if not self.override_permission_action_rules[self.__name__].get(key):
140
+ self.override_permission_action_rules[self.__name__][key] = {}
141
+ for scope in kwargs.get('scopes', []):
142
+ self.override_permission_action_rules[self.__name__][key][scope] = extract(
143
+ kwargs,
144
+ [
145
+ 'override_permission_condition',
146
+ ],
147
+ )
83
148
 
84
149
  @classmethod
85
150
  def allow_query(self, array: List = None, **kwargs):
86
151
  if not self.query_rules.get(self.__name__):
87
152
  self.query_rules[self.__name__] = {}
153
+ if not self.override_permission_query_rules.get(self.__name__):
154
+ self.override_permission_query_rules[self.__name__] = {}
88
155
 
89
156
  array_use = array or [AttributeType.ALL]
90
157
  for key in array_use:
91
158
  if not self.query_rules[self.__name__].get(key):
92
159
  self.query_rules[self.__name__][key] = {}
160
+ if not self.override_permission_query_rules[self.__name__].get(key):
161
+ self.override_permission_query_rules[self.__name__][key] = {}
162
+
163
+ actions = kwargs.get('on_action', [OperationType.ALL])
164
+ actions = actions if isinstance(actions, list) else [actions]
165
+
93
166
  for scope in kwargs.get('scopes', []):
94
- self.query_rules[self.__name__][key][scope] = extract(kwargs, ['condition'])
167
+ if not self.query_rules[self.__name__][key].get(scope):
168
+ self.query_rules[self.__name__][key][scope] = {}
169
+ for action in actions:
170
+ self.query_rules[self.__name__][key][scope][action] = extract(
171
+ kwargs,
172
+ [
173
+ 'condition',
174
+ ],
175
+ )
176
+
177
+ if not self.override_permission_query_rules[self.__name__][key].get(scope):
178
+ self.override_permission_query_rules[self.__name__][key][scope] = {}
179
+ for action in actions:
180
+ self.override_permission_query_rules[self.__name__][key][scope][action] = \
181
+ extract(
182
+ kwargs,
183
+ [
184
+ 'override_permission_condition',
185
+ ])
95
186
 
96
187
  @classmethod
97
188
  def allow_read(self, array, **kwargs):
98
189
  if not self.read_rules.get(self.__name__):
99
190
  self.read_rules[self.__name__] = {}
191
+ if not self.override_permission_read_rules.get(self.__name__):
192
+ self.override_permission_read_rules[self.__name__] = {}
193
+
100
194
  for key in array:
101
195
  if not self.read_rules[self.__name__].get(key):
102
196
  self.read_rules[self.__name__][key] = {}
197
+ if not self.override_permission_read_rules[self.__name__].get(key):
198
+ self.override_permission_read_rules[self.__name__][key] = {}
199
+
103
200
  actions = kwargs.get('on_action', [OperationType.ALL])
104
201
  actions = actions if isinstance(actions, list) else [actions]
202
+
105
203
  for scope in kwargs.get('scopes', []):
106
204
  if not self.read_rules[self.__name__][key].get(scope):
107
205
  self.read_rules[self.__name__][key][scope] = {}
@@ -109,15 +207,32 @@ class BasePolicy():
109
207
  self.read_rules[self.__name__][key][scope][action] = extract(kwargs, [
110
208
  'condition'])
111
209
 
210
+ if not self.override_permission_read_rules[self.__name__][key].get(scope):
211
+ self.override_permission_read_rules[self.__name__][key][scope] = {}
212
+ for action in actions:
213
+ self.override_permission_read_rules[self.__name__][key][scope][action] = \
214
+ extract(
215
+ kwargs,
216
+ [
217
+ 'override_permission_condition',
218
+ ])
219
+
112
220
  @classmethod
113
221
  def allow_write(self, array, **kwargs):
114
222
  if not self.write_rules.get(self.__name__):
115
223
  self.write_rules[self.__name__] = {}
224
+ if not self.override_permission_write_rules.get(self.__name__):
225
+ self.override_permission_write_rules[self.__name__] = {}
226
+
116
227
  for key in array:
117
228
  if not self.write_rules[self.__name__].get(key):
118
229
  self.write_rules[self.__name__][key] = {}
230
+ if not self.override_permission_write_rules[self.__name__].get(key):
231
+ self.override_permission_write_rules[self.__name__][key] = {}
232
+
119
233
  actions = kwargs.get('on_action', [OperationType.ALL])
120
234
  actions = actions if isinstance(actions, list) else [actions]
235
+
121
236
  for scope in kwargs.get('scopes', []):
122
237
  if not self.write_rules[self.__name__][key].get(scope):
123
238
  self.write_rules[self.__name__][key][scope] = {}
@@ -125,10 +240,24 @@ class BasePolicy():
125
240
  self.write_rules[self.__name__][key][scope][action] = extract(kwargs, [
126
241
  'condition'])
127
242
 
243
+ if not self.override_permission_write_rules[self.__name__][key].get(scope):
244
+ self.override_permission_write_rules[self.__name__][key][scope] = {}
245
+ for action in actions:
246
+ self.override_permission_write_rules[self.__name__][key][scope][action] = \
247
+ extract(
248
+ kwargs,
249
+ [
250
+ 'override_permission_condition',
251
+ ])
252
+
128
253
  @classmethod
129
254
  def resource_name(self):
130
255
  return inflection.pluralize(self.resource_name_singular())
131
256
 
257
+ @classmethod
258
+ def model_name(self) -> str:
259
+ return self.__name__.replace('Policy', '')
260
+
132
261
  @classmethod
133
262
  def resource_name_singular(self):
134
263
  return inflection.underscore(
@@ -178,6 +307,9 @@ class BasePolicy():
178
307
  )
179
308
 
180
309
  async def authorize_action(self, action):
310
+ if self.is_owner():
311
+ return True
312
+
181
313
  config = self.__class__.action_rule(action)
182
314
  if config:
183
315
  await self.__validate_scopes(action, config.keys())
@@ -187,6 +319,9 @@ class BasePolicy():
187
319
  action,
188
320
  config[self.current_scope()]['condition'],
189
321
  operation=action,
322
+ override_permission_condition=(self.__class__.override_permission_action_rule(
323
+ action,
324
+ ) or {}).get(self.current_scope(), {}).get('override_permission_condition'),
190
325
  )
191
326
  else:
192
327
  error = ApiError.UNAUTHORIZED_ACCESS
@@ -207,9 +342,11 @@ class BasePolicy():
207
342
  if 'read' == read_or_write:
208
343
  attribute_operation = AttributeOperationType.READ
209
344
  orig_config = self.__class__.read_rule(attrb)
345
+ orig_config_override = self.__class__.override_permission_read_rule(attrb)
210
346
  else:
211
- orig_config = self.__class__.write_rule(attrb)
212
347
  attribute_operation = AttributeOperationType.WRITE
348
+ orig_config = self.__class__.write_rule(attrb)
349
+ orig_config_override = self.__class__.override_permission_write_rule(attrb)
213
350
 
214
351
  config = None
215
352
  if orig_config:
@@ -220,6 +357,15 @@ class BasePolicy():
220
357
  if config is None:
221
358
  config = config_scope.get(OperationType.ALL)
222
359
 
360
+ config_override = None
361
+ if orig_config_override:
362
+ await self.__validate_scopes(attrb, orig_config_override.keys())
363
+ config_override_scope = orig_config_override.get(self.current_scope(), {})
364
+ config_override = config_override_scope.get(api_operation_action)
365
+
366
+ if config_override is None:
367
+ config_override = config_override_scope.get(OperationType.ALL)
368
+
223
369
  if config is None:
224
370
  error = ApiError.UNAUTHORIZED_ACCESS
225
371
  error.update({
@@ -231,13 +377,16 @@ class BasePolicy():
231
377
  ),
232
378
  })
233
379
  raise ApiError(error)
380
+
234
381
  cond = config.get('condition')
382
+
235
383
  if cond:
236
384
  await self.__validate_condition(
237
385
  attrb,
238
386
  cond,
239
387
  attribute_operation=attribute_operation,
240
388
  operation=api_operation_action,
389
+ override_permission_condition=config_override.get('override_permission_condition'),
241
390
  **kwargs,
242
391
  )
243
392
 
@@ -248,36 +397,63 @@ class BasePolicy():
248
397
  for attrb in attrbs:
249
398
  await self.authorize_attribute(read_or_write, attrb, **kwargs)
250
399
 
251
- async def authorize_query(self, query):
252
- if not query:
253
- return
400
+ async def authorize_query(self, query, **kwargs):
401
+ if self.is_owner() or not query:
402
+ return True
403
+
404
+ api_operation_action = self.options.get(
405
+ 'api_operation_action',
406
+ kwargs.get('api_operation_action', OperationType.ALL),
407
+ )
254
408
 
255
409
  for key, value in query.items():
256
- if key != settings.QUERY_API_KEY:
257
- api_operation_action = self.options.get(
258
- 'api_operation_action',
259
- OperationType.ALL,
260
- )
410
+ if key == settings.QUERY_API_KEY:
411
+ continue
261
412
 
262
- error_message = f'Query parameter {key} of value {value} ' \
263
- f'is not permitted on {api_operation_action} operation.'
264
-
265
- config = self.__class__.query_rule(key) or \
266
- self.__class__.query_rule(AttributeType.ALL)
267
-
268
- if not config:
269
- error = ApiError.UNAUTHORIZED_ACCESS
270
- error.update({
271
- 'message': error_message,
272
- })
273
- raise ApiError(error)
274
- elif config.get(self.current_scope(), {}).get('condition'):
275
- await self.__validate_condition(
276
- key,
277
- config[self.current_scope()]['condition'],
278
- message=error_message,
279
- operation=api_operation_action,
280
- )
413
+ error_message = f'Query parameter {key} of value {value} ' \
414
+ f'is not permitted on {api_operation_action} operation ' \
415
+ f'for {self.__class__.resource_name()}.'
416
+
417
+ orig_config = self.__class__.query_rule(key) or \
418
+ self.__class__.query_rule(AttributeType.ALL)
419
+
420
+ config = None
421
+ if orig_config:
422
+ await self.__validate_scopes(key, orig_config.keys())
423
+ config_scope = orig_config.get(self.current_scope(), {})
424
+ config = config_scope.get(api_operation_action)
425
+
426
+ if config is None:
427
+ config = config_scope.get(OperationType.ALL)
428
+
429
+ orig_config_override = self.__class__.override_permission_query_rule(key) or \
430
+ self.__class__.override_permission_query_rule(AttributeType.ALL)
431
+
432
+ config_override = None
433
+ if orig_config_override:
434
+ await self.__validate_scopes(key, orig_config_override.keys())
435
+ config_override_scope = orig_config_override.get(self.current_scope(), {})
436
+ config_override = config_override_scope.get(api_operation_action)
437
+
438
+ if config_override is None:
439
+ config_override = config_override_scope.get(OperationType.ALL)
440
+
441
+ if config is None:
442
+ error = ApiError.UNAUTHORIZED_ACCESS
443
+ error.update({
444
+ 'message': error_message,
445
+ })
446
+ raise ApiError(error)
447
+
448
+ cond = config.get('condition')
449
+
450
+ await self.__validate_condition(
451
+ key,
452
+ cond,
453
+ message=error_message,
454
+ operation=api_operation_action,
455
+ override_permission_condition=config_override.get('override_permission_condition'),
456
+ )
281
457
 
282
458
  def parent_model(self):
283
459
  if not self.parent_model_attr:
@@ -306,12 +482,19 @@ class BasePolicy():
306
482
  else:
307
483
  return OauthScope.CLIENT_PUBLIC
308
484
 
485
+ def result_set(self) -> ResultSet:
486
+ if self.resource:
487
+ return self.resource.result_set()
488
+
489
+ return self.result_set_attr
490
+
309
491
  async def __validate_condition(
310
492
  self,
311
493
  action,
312
494
  cond,
313
495
  attribute_operation: AttributeOperationType = None,
314
496
  operation: OperationType = None,
497
+ override_permission_condition: Callable = None,
315
498
  **kwargs,
316
499
  ):
317
500
  if not cond:
@@ -321,16 +504,22 @@ class BasePolicy():
321
504
  if validation and inspect.isawaitable(validation):
322
505
  validation = await validation
323
506
 
507
+ if not validation and override_permission_condition:
508
+ validation = override_permission_condition(self)
509
+ if validation and inspect.isawaitable(validation):
510
+ validation = await validation
511
+
324
512
  if not validation:
513
+ r_name = self.resource_name()
325
514
  error = ApiError.UNAUTHORIZED_ACCESS
326
- message = f'Unauthorized access for {action}'
515
+ message = f'Unauthorized access for {action} on {r_name}'
327
516
 
328
517
  if attribute_operation:
329
- message = f'Unauthorized {attribute_operation} access for {action}'
518
+ message = f'Unauthorized {attribute_operation} access for {action} on {r_name}'
330
519
  if operation:
331
- message = f'{message} on {operation} operation'
520
+ message = f'{message} for {operation} operation'
332
521
  elif operation:
333
- message = f'Unauthorized access for {action} on {operation} operation'
522
+ message = f'Unauthorized operation {operation} on {r_name}'
334
523
 
335
524
  error.update({
336
525
  'message': kwargs.get(
@@ -26,6 +26,8 @@ PermissionPolicy.allow_actions([
26
26
 
27
27
 
28
28
  PermissionPolicy.allow_read(PermissionPresenter.default_attributes + [
29
+ 'entity_names',
30
+ 'entity_types',
29
31
  'query_attributes',
30
32
  'read_attributes',
31
33
  'write_attributes',
@@ -41,11 +43,16 @@ PermissionPolicy.allow_read(PermissionPresenter.default_attributes + [
41
43
 
42
44
  PermissionPolicy.allow_read(PermissionPresenter.default_attributes + [
43
45
  'role',
46
+ 'user',
44
47
  'user_id',
48
+ 'users',
45
49
  ], scopes=[
46
50
  OauthScope.CLIENT_PRIVATE,
47
51
  ], on_action=[
52
+ constants.CREATE,
53
+ constants.DELETE,
48
54
  constants.DETAIL,
55
+ constants.UPDATE,
49
56
  ], condition=lambda policy: policy.has_at_least_viewer_role())
50
57
 
51
58
 
@@ -56,9 +63,19 @@ PermissionPolicy.allow_write([
56
63
  'entity_name',
57
64
  'entity_type',
58
65
  'role_id',
66
+ 'role_ids',
59
67
  ], scopes=[
60
68
  OauthScope.CLIENT_PRIVATE,
61
69
  ], on_action=[
62
70
  constants.CREATE,
63
71
  constants.UPDATE,
64
72
  ], condition=lambda policy: policy.has_at_least_admin_role())
73
+
74
+
75
+ PermissionPolicy.allow_query([
76
+ 'only_entity_options',
77
+ ], scopes=[
78
+ OauthScope.CLIENT_PRIVATE,
79
+ ], on_action=[
80
+ constants.LIST,
81
+ ], condition=lambda policy: policy.has_at_least_viewer_role())
@@ -1,8 +1,10 @@
1
1
  from mage_ai.api.oauth_scope import OauthScope
2
2
  from mage_ai.api.operations import constants
3
3
  from mage_ai.api.policies.BasePolicy import BasePolicy
4
- from mage_ai.api.policies.utils import validate_pipeline_interactions_permissions
5
4
  from mage_ai.api.presenters.PipelineSchedulePresenter import PipelineSchedulePresenter
5
+ from mage_ai.data_preparation.models.pipelines.interactions import PipelineInteractions
6
+ from mage_ai.data_preparation.models.project import Project
7
+ from mage_ai.data_preparation.models.project.constants import FeatureUUID
6
8
  from mage_ai.data_preparation.repo_manager import get_project_uuid
7
9
  from mage_ai.orchestration.constants import Entity
8
10
 
@@ -40,7 +42,21 @@ async def authorize_operation_create(policy: PipelineSchedulePolicy) -> bool:
40
42
  if policy.has_at_least_editor_role():
41
43
  return True
42
44
 
43
- cond = await validate_pipeline_interactions_permissions(policy)
45
+ pipeline = policy.parent_model()
46
+ if not pipeline:
47
+ pipeline = policy.resource.pipeline
48
+
49
+ if not Project(pipeline.repo_config).is_feature_enabled(FeatureUUID.INTERACTIONS):
50
+ return False
51
+
52
+ pipeline_interaction = PipelineInteractions(pipeline)
53
+ cond = await pipeline_interaction.filter_for_permissions(policy.current_user)
54
+
55
+ if not policy.result_set().context.data:
56
+ policy.result_set().context.data = {}
57
+ if not policy.result_set().context.data.get('pipeline_interactions'):
58
+ policy.result_set().context.data['pipeline_interactions'] = {}
59
+ policy.result_set().context.data['pipeline_interactions'][pipeline.uuid] = pipeline_interaction
44
60
 
45
61
  return cond
46
62
 
@@ -26,8 +26,9 @@ RolePermissionPolicy.allow_read(RolePermissionPresenter.default_attributes + [
26
26
 
27
27
 
28
28
  RolePermissionPolicy.allow_write([
29
+ 'permission_id',
30
+ 'permission_ids',
29
31
  'role_id',
30
- 'user_id',
31
32
  ], scopes=[
32
33
  OauthScope.CLIENT_PRIVATE,
33
34
  ], on_action=[
@@ -44,6 +44,7 @@ RolePolicy.allow_read([
44
44
 
45
45
  RolePolicy.allow_read(RolePresenter.default_attributes + [
46
46
  'user_id',
47
+ 'users',
47
48
  ], scopes=[
48
49
  OauthScope.CLIENT_PRIVATE,
49
50
  ], on_action=[
@@ -55,6 +56,8 @@ RolePolicy.allow_read(RolePresenter.default_attributes + [
55
56
 
56
57
  RolePolicy.allow_write([
57
58
  'name'
59
+ 'permission_ids',
60
+ 'user_ids',
58
61
  ], scopes=[
59
62
  OauthScope.CLIENT_PRIVATE,
60
63
  ], on_action=[
@@ -1,10 +1,45 @@
1
+ from typing import Dict
2
+
3
+ from mage_ai.api.constants import AttributeOperationType
1
4
  from mage_ai.api.oauth_scope import OauthScope
2
5
  from mage_ai.api.operations import constants
6
+ from mage_ai.api.operations.constants import OperationType
3
7
  from mage_ai.api.policies.BasePolicy import BasePolicy
8
+ from mage_ai.api.policies.mixins.user_permissions import UserPermissionMixIn
4
9
  from mage_ai.orchestration.constants import Entity
10
+ from mage_ai.shared.hash import merge_dict
11
+
12
+
13
+ class SessionPolicy(BasePolicy, UserPermissionMixIn):
14
+ @classmethod
15
+ def action_rule_with_permissions(self, operation: OperationType) -> Dict:
16
+ return merge_dict(super().action_rule_with_permissions(operation), {
17
+ OauthScope.CLIENT_PUBLIC: dict(
18
+ condition=lambda _policy: OperationType.CREATE == operation,
19
+ ),
20
+ })
21
+
22
+ @classmethod
23
+ def attribute_rule_with_permissions(
24
+ self,
25
+ attribute_operation_type: AttributeOperationType,
26
+ resource_attribute: str,
27
+ ) -> Dict:
28
+ config = {}
29
+ if AttributeOperationType.READ == attribute_operation_type:
30
+ config = self.read_rules[self.__name__].get(resource_attribute)
31
+ else:
32
+ config = self.write_rules[self.__name__].get(resource_attribute)
5
33
 
34
+ return merge_dict(super().attribute_rule_with_permissions(
35
+ attribute_operation_type,
36
+ resource_attribute,
37
+ ), {
38
+ OauthScope.CLIENT_PUBLIC: {
39
+ OperationType.CREATE: config[OauthScope.CLIENT_PUBLIC][OperationType.CREATE],
40
+ },
41
+ })
6
42
 
7
- class SessionPolicy(BasePolicy):
8
43
  @property
9
44
  def entity(self):
10
45
  return Entity.ANY, None