dmart 1.4.40.post8__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.
Files changed (489) hide show
  1. dmart/__init__.py +7 -0
  2. dmart/alembic/README +1 -0
  3. dmart/alembic/__init__.py +0 -0
  4. dmart/alembic/env.py +91 -0
  5. dmart/alembic/notes.txt +11 -0
  6. dmart/alembic/script.py.mako +28 -0
  7. dmart/alembic/scripts/__init__.py +0 -0
  8. dmart/alembic/scripts/calculate_checksums.py +77 -0
  9. dmart/alembic/scripts/migration_f7a4949eed19.py +28 -0
  10. dmart/alembic/versions/0f3d2b1a7c21_add_authz_materialized_views.py +87 -0
  11. dmart/alembic/versions/10d2041b94d4_last_checksum_history.py +62 -0
  12. dmart/alembic/versions/1cf4e1ee3cb8_ext_permission_with_filter_fields_values.py +33 -0
  13. dmart/alembic/versions/26bfe19b49d4_rm_failedloginattempts.py +42 -0
  14. dmart/alembic/versions/3c8bca2219cc_add_otp_table.py +38 -0
  15. dmart/alembic/versions/6675fd9dfe42_remove_unique_from_sessions_table.py +36 -0
  16. dmart/alembic/versions/71bc1df82e6a_adding_user_last_login_at.py +43 -0
  17. dmart/alembic/versions/74288ccbd3b5_initial.py +264 -0
  18. dmart/alembic/versions/7520a89a8467_rm_activesession_table.py +39 -0
  19. dmart/alembic/versions/848b623755a4_make_created_nd_updated_at_required.py +138 -0
  20. dmart/alembic/versions/8640dcbebf85_add_notes_to_users.py +32 -0
  21. dmart/alembic/versions/91c94250232a_adding_fk_on_owner_shortname.py +104 -0
  22. dmart/alembic/versions/98ecd6f56f9a_ext_meta_with_owner_group_shortname.py +66 -0
  23. dmart/alembic/versions/9aae9138c4ef_indexing_created_at_updated_at.py +80 -0
  24. dmart/alembic/versions/__init__.py +0 -0
  25. dmart/alembic/versions/b53f916b3f6d_json_to_jsonb.py +492 -0
  26. dmart/alembic/versions/eb5f1ec65156_adding_user_locked_to_device.py +36 -0
  27. dmart/alembic/versions/f7a4949eed19_adding_query_policies_to_meta.py +60 -0
  28. dmart/alembic.ini +117 -0
  29. dmart/api/__init__.py +0 -0
  30. dmart/api/info/__init__.py +0 -0
  31. dmart/api/info/router.py +109 -0
  32. dmart/api/managed/__init__.py +0 -0
  33. dmart/api/managed/router.py +1541 -0
  34. dmart/api/managed/utils.py +1879 -0
  35. dmart/api/public/__init__.py +0 -0
  36. dmart/api/public/router.py +758 -0
  37. dmart/api/qr/__init__.py +0 -0
  38. dmart/api/qr/router.py +108 -0
  39. dmart/api/user/__init__.py +0 -0
  40. dmart/api/user/model/__init__.py +0 -0
  41. dmart/api/user/model/errors.py +14 -0
  42. dmart/api/user/model/requests.py +165 -0
  43. dmart/api/user/model/responses.py +11 -0
  44. dmart/api/user/router.py +1413 -0
  45. dmart/api/user/service.py +270 -0
  46. dmart/bundler.py +52 -0
  47. dmart/cli.py +1133 -0
  48. dmart/config/__init__.py +0 -0
  49. dmart/config/channels.json +11 -0
  50. dmart/config/notification.json +17 -0
  51. dmart/config.env.sample +27 -0
  52. dmart/config.ini.sample +7 -0
  53. dmart/conftest.py +13 -0
  54. dmart/curl.sh +196 -0
  55. dmart/cxb/__init__.py +0 -0
  56. dmart/cxb/assets/@codemirror-Rn7_6DkE.js +10 -0
  57. dmart/cxb/assets/@edraj-CS4NwVbD.js +1 -0
  58. dmart/cxb/assets/@floating-ui-BwwcF-xh.js +1 -0
  59. dmart/cxb/assets/@formatjs-yKEsAtjs.js +1 -0
  60. dmart/cxb/assets/@fortawesome-DRW1UCdr.js +9 -0
  61. dmart/cxb/assets/@jsonquerylang-laKNoFFq.js +12 -0
  62. dmart/cxb/assets/@lezer-za4Q-8Ew.js +1 -0
  63. dmart/cxb/assets/@marijn-DXwl3gUT.js +1 -0
  64. dmart/cxb/assets/@popperjs-l0sNRNKZ.js +1 -0
  65. dmart/cxb/assets/@replit--ERk53eB.js +1 -0
  66. dmart/cxb/assets/@roxi-CGMFK4i8.js +6 -0
  67. dmart/cxb/assets/@typewriter-cCzskkIv.js +17 -0
  68. dmart/cxb/assets/@zerodevx-BlBZjKxu.js +1 -0
  69. dmart/cxb/assets/@zerodevx-CVEpe6WZ.css +1 -0
  70. dmart/cxb/assets/BreadCrumbLite-DAhOx38v.js +1 -0
  71. dmart/cxb/assets/EntryRenderer-CCqV8Rkg.js +32 -0
  72. dmart/cxb/assets/EntryRenderer-DXytdFp9.css +1 -0
  73. dmart/cxb/assets/ListView-BQelo7vZ.js +16 -0
  74. dmart/cxb/assets/ListView-U8of-_c-.css +1 -0
  75. dmart/cxb/assets/Prism--hMplq-p.js +3 -0
  76. dmart/cxb/assets/Prism-Uh6uStUw.css +1 -0
  77. dmart/cxb/assets/Table2Cols-BsbwicQm.js +1 -0
  78. dmart/cxb/assets/_..-BvT6vdHa.css +1 -0
  79. dmart/cxb/assets/_...404_-fuLH_rX9.js +2 -0
  80. dmart/cxb/assets/_...fallback_-Ba_NLmAE.js +1 -0
  81. dmart/cxb/assets/_module-3HrtKAWo.js +3 -0
  82. dmart/cxb/assets/_module-DFKFq0AM.js +4 -0
  83. dmart/cxb/assets/_module-Dgq0ZVtz.js +1 -0
  84. dmart/cxb/assets/ajv-Cpj98o6Y.js +1 -0
  85. dmart/cxb/assets/axios-CG2WSiiR.js +6 -0
  86. dmart/cxb/assets/clsx-B-dksMZM.js +1 -0
  87. dmart/cxb/assets/codemirror-wrapped-line-indent-DPhKvljI.js +1 -0
  88. dmart/cxb/assets/compare-C3AjiGFR.js +1 -0
  89. dmart/cxb/assets/compute-scroll-into-view-Bl8rNFhg.js +1 -0
  90. dmart/cxb/assets/consolite-DlCuI0F9.js +1 -0
  91. dmart/cxb/assets/crelt-C8TCjufn.js +1 -0
  92. dmart/cxb/assets/date-fns-l0sNRNKZ.js +1 -0
  93. dmart/cxb/assets/deepmerge-rn4rBaHU.js +1 -0
  94. dmart/cxb/assets/dmart_services-AL6-IdDE.js +1 -0
  95. dmart/cxb/assets/downloadFile-D08i0YDh.js +1 -0
  96. dmart/cxb/assets/easy-signal-BiPFIK3O.js +1 -0
  97. dmart/cxb/assets/esm-env-rsSWfq8L.js +1 -0
  98. dmart/cxb/assets/export-OF_rTiXu.js +1 -0
  99. dmart/cxb/assets/fast-deep-equal-l0sNRNKZ.js +1 -0
  100. dmart/cxb/assets/fast-diff-C-IidNf4.js +1 -0
  101. dmart/cxb/assets/fast-uri-l0sNRNKZ.js +1 -0
  102. dmart/cxb/assets/flowbite-svelte-BLvjb-sa.js +1 -0
  103. dmart/cxb/assets/flowbite-svelte-CD54FDqW.css +1 -0
  104. dmart/cxb/assets/flowbite-svelte-icons-BI8GVhw_.js +1 -0
  105. dmart/cxb/assets/github-slugger-CQ4oX9Ud.js +1 -0
  106. dmart/cxb/assets/global-igKv-1g9.js +1 -0
  107. dmart/cxb/assets/hookar-BMRD9G9H.js +1 -0
  108. dmart/cxb/assets/immutable-json-patch-DtRO2E_S.js +1 -0
  109. dmart/cxb/assets/import-1vE3gBat.js +1 -0
  110. dmart/cxb/assets/index-B-eTh-ZX.js +1 -0
  111. dmart/cxb/assets/index-BSsK-X71.js +1 -0
  112. dmart/cxb/assets/index-BVyxzKtH.js +1 -0
  113. dmart/cxb/assets/index-BdeNM69f.js +1 -0
  114. dmart/cxb/assets/index-CC-A1ipE.js +1 -0
  115. dmart/cxb/assets/index-CQohGiYB.js +1 -0
  116. dmart/cxb/assets/index-ChjnkpdZ.js +4 -0
  117. dmart/cxb/assets/index-DLP7csA4.js +1 -0
  118. dmart/cxb/assets/index-DTfhnhwd.js +1 -0
  119. dmart/cxb/assets/index-DdXRK7n9.js +2 -0
  120. dmart/cxb/assets/index-DtiCmB4o.js +1 -0
  121. dmart/cxb/assets/index-NBrXBlLA.css +2 -0
  122. dmart/cxb/assets/index-X1uNehO7.js +1 -0
  123. dmart/cxb/assets/index-nrQW6Nrr.js +1 -0
  124. dmart/cxb/assets/info-B986lRiM.js +1 -0
  125. dmart/cxb/assets/intl-messageformat-Dc5UU-HB.js +3 -0
  126. dmart/cxb/assets/jmespath-l0sNRNKZ.js +1 -0
  127. dmart/cxb/assets/json-schema-traverse-l0sNRNKZ.js +1 -0
  128. dmart/cxb/assets/json-source-map-DRgZidqy.js +5 -0
  129. dmart/cxb/assets/jsonpath-plus-l0sNRNKZ.js +1 -0
  130. dmart/cxb/assets/jsonrepair-B30Dx381.js +8 -0
  131. dmart/cxb/assets/lodash-es-DZVAA2ox.js +1 -0
  132. dmart/cxb/assets/marked-DKjyhwJX.js +56 -0
  133. dmart/cxb/assets/marked-gfm-heading-id-U5zO829x.js +2 -0
  134. dmart/cxb/assets/marked-mangle-CDMeiHC6.js +1 -0
  135. dmart/cxb/assets/memoize-one-BdPwpGay.js +1 -0
  136. dmart/cxb/assets/natural-compare-lite-Bg2Xcf-o.js +7 -0
  137. dmart/cxb/assets/pagination-svelte-D5CyoiE_.js +13 -0
  138. dmart/cxb/assets/pagination-svelte-v10nAbbM.css +1 -0
  139. dmart/cxb/assets/plantuml-encoder-C47mzt9T.js +1 -0
  140. dmart/cxb/assets/prismjs-DTUiLGJu.js +9 -0
  141. dmart/cxb/assets/profile-BUf-tKMe.js +1 -0
  142. dmart/cxb/assets/query-CNmXTsgf.js +1 -0
  143. dmart/cxb/assets/queryHelpers-C9iBWwqe.js +1 -0
  144. dmart/cxb/assets/scroll-into-view-if-needed-KR58zyjF.js +1 -0
  145. dmart/cxb/assets/spaces-0oyGvpii.js +1 -0
  146. dmart/cxb/assets/style-mod-Bs6eFhZE.js +3 -0
  147. dmart/cxb/assets/svelte-B2XmcTi_.js +4 -0
  148. dmart/cxb/assets/svelte-awesome-COLlx0DN.css +1 -0
  149. dmart/cxb/assets/svelte-awesome-DhnMA6Q_.js +1 -0
  150. dmart/cxb/assets/svelte-datatables-net-CY7LBj6I.js +1 -0
  151. dmart/cxb/assets/svelte-floating-ui-BlS3sOAQ.js +1 -0
  152. dmart/cxb/assets/svelte-i18n-CT2KkQaN.js +3 -0
  153. dmart/cxb/assets/svelte-jsoneditor-BzfX6Usi.css +1 -0
  154. dmart/cxb/assets/svelte-jsoneditor-CUGSvWId.js +25 -0
  155. dmart/cxb/assets/svelte-select-CegQKzqH.css +1 -0
  156. dmart/cxb/assets/svelte-select-CjHAt_85.js +6 -0
  157. dmart/cxb/assets/tailwind-merge-CJvxXMcu.js +1 -0
  158. dmart/cxb/assets/tailwind-variants-Cj20BoQ3.js +1 -0
  159. dmart/cxb/assets/toast-B9WDyfyI.js +1 -0
  160. dmart/cxb/assets/tslib-pJfR_DrR.js +1 -0
  161. dmart/cxb/assets/typewriter-editor-DkTVIJdm.js +25 -0
  162. dmart/cxb/assets/user-DeK_NB5v.js +1 -0
  163. dmart/cxb/assets/vanilla-picker-l5rcX3cq.js +8 -0
  164. dmart/cxb/assets/w3c-keyname-Vcq4gwWv.js +1 -0
  165. dmart/cxb/config.json +11 -0
  166. dmart/cxb/config.sample.json +11 -0
  167. dmart/cxb/favicon.ico +0 -0
  168. dmart/cxb/favicon.png +0 -0
  169. dmart/cxb/index.html +28 -0
  170. dmart/data_adapters/__init__.py +0 -0
  171. dmart/data_adapters/adapter.py +16 -0
  172. dmart/data_adapters/base_data_adapter.py +467 -0
  173. dmart/data_adapters/file/__init__.py +0 -0
  174. dmart/data_adapters/file/adapter.py +2043 -0
  175. dmart/data_adapters/file/adapter_helpers.py +1013 -0
  176. dmart/data_adapters/file/archive.py +150 -0
  177. dmart/data_adapters/file/create_index.py +331 -0
  178. dmart/data_adapters/file/create_users_folders.py +52 -0
  179. dmart/data_adapters/file/custom_validations.py +68 -0
  180. dmart/data_adapters/file/drop_index.py +40 -0
  181. dmart/data_adapters/file/health_check.py +560 -0
  182. dmart/data_adapters/file/redis_services.py +1110 -0
  183. dmart/data_adapters/helpers.py +27 -0
  184. dmart/data_adapters/sql/__init__.py +0 -0
  185. dmart/data_adapters/sql/adapter.py +3218 -0
  186. dmart/data_adapters/sql/adapter_helpers.py +491 -0
  187. dmart/data_adapters/sql/create_tables.py +451 -0
  188. dmart/data_adapters/sql/create_users_folders.py +53 -0
  189. dmart/data_adapters/sql/db_to_json_migration.py +485 -0
  190. dmart/data_adapters/sql/health_check_sql.py +232 -0
  191. dmart/data_adapters/sql/json_to_db_migration.py +454 -0
  192. dmart/data_adapters/sql/update_query_policies.py +101 -0
  193. dmart/data_generator.py +81 -0
  194. dmart/dmart.py +761 -0
  195. dmart/get_settings.py +7 -0
  196. dmart/hypercorn_config.toml +3 -0
  197. dmart/info.json +1 -0
  198. dmart/languages/__init__.py +0 -0
  199. dmart/languages/arabic.json +15 -0
  200. dmart/languages/english.json +16 -0
  201. dmart/languages/kurdish.json +14 -0
  202. dmart/languages/loader.py +12 -0
  203. dmart/login_creds.sh +7 -0
  204. dmart/login_creds.sh.sample +7 -0
  205. dmart/main.py +563 -0
  206. dmart/manifest.sh +12 -0
  207. dmart/migrate.py +24 -0
  208. dmart/models/__init__.py +0 -0
  209. dmart/models/api.py +203 -0
  210. dmart/models/core.py +597 -0
  211. dmart/models/enums.py +255 -0
  212. dmart/password_gen.py +8 -0
  213. dmart/plugins/__init__.py +0 -0
  214. dmart/plugins/action_log/__init__.py +0 -0
  215. dmart/plugins/action_log/config.json +13 -0
  216. dmart/plugins/action_log/plugin.py +121 -0
  217. dmart/plugins/admin_notification_sender/__init__.py +0 -0
  218. dmart/plugins/admin_notification_sender/config.json +13 -0
  219. dmart/plugins/admin_notification_sender/plugin.py +124 -0
  220. dmart/plugins/ldap_manager/__init__.py +0 -0
  221. dmart/plugins/ldap_manager/config.json +12 -0
  222. dmart/plugins/ldap_manager/dmart.schema +146 -0
  223. dmart/plugins/ldap_manager/plugin.py +100 -0
  224. dmart/plugins/ldap_manager/slapd.conf +53 -0
  225. dmart/plugins/local_notification/__init__.py +0 -0
  226. dmart/plugins/local_notification/config.json +13 -0
  227. dmart/plugins/local_notification/plugin.py +123 -0
  228. dmart/plugins/realtime_updates_notifier/__init__.py +0 -0
  229. dmart/plugins/realtime_updates_notifier/config.json +12 -0
  230. dmart/plugins/realtime_updates_notifier/plugin.py +58 -0
  231. dmart/plugins/redis_db_update/__init__.py +0 -0
  232. dmart/plugins/redis_db_update/config.json +13 -0
  233. dmart/plugins/redis_db_update/plugin.py +188 -0
  234. dmart/plugins/resource_folders_creation/__init__.py +0 -0
  235. dmart/plugins/resource_folders_creation/config.json +12 -0
  236. dmart/plugins/resource_folders_creation/plugin.py +81 -0
  237. dmart/plugins/system_notification_sender/__init__.py +0 -0
  238. dmart/plugins/system_notification_sender/config.json +13 -0
  239. dmart/plugins/system_notification_sender/plugin.py +188 -0
  240. dmart/plugins/update_access_controls/__init__.py +0 -0
  241. dmart/plugins/update_access_controls/config.json +12 -0
  242. dmart/plugins/update_access_controls/plugin.py +9 -0
  243. dmart/publish.sh +57 -0
  244. dmart/pylint.sh +16 -0
  245. dmart/pyrightconfig.json +7 -0
  246. dmart/redis_connections.sh +13 -0
  247. dmart/reload.sh +56 -0
  248. dmart/run.sh +3 -0
  249. dmart/run_notification_campaign.py +85 -0
  250. dmart/sample/spaces/applications/.dm/meta.space.json +30 -0
  251. dmart/sample/spaces/applications/api/.dm/meta.folder.json +1 -0
  252. dmart/sample/spaces/applications/api/.dm/query_all_applications/meta.content.json +1 -0
  253. dmart/sample/spaces/applications/api/.dm/test_by_saad/attachments.media/meta.warframe.json +1 -0
  254. dmart/sample/spaces/applications/api/.dm/test_by_saad/attachments.media/warframe.png +0 -0
  255. dmart/sample/spaces/applications/api/.dm/test_by_saad/meta.content.json +1 -0
  256. dmart/sample/spaces/applications/api/.dm/user_profile/meta.content.json +1 -0
  257. dmart/sample/spaces/applications/api/applications/.dm/create_log/meta.content.json +1 -0
  258. dmart/sample/spaces/applications/api/applications/.dm/create_public_logs/meta.content.json +1 -0
  259. dmart/sample/spaces/applications/api/applications/.dm/meta.folder.json +1 -0
  260. dmart/sample/spaces/applications/api/applications/.dm/query_all_translated_data/meta.content.json +1 -0
  261. dmart/sample/spaces/applications/api/applications/.dm/query_logs/meta.content.json +1 -0
  262. dmart/sample/spaces/applications/api/applications/.dm/query_translated_enums/meta.content.json +1 -0
  263. dmart/sample/spaces/applications/api/applications/.dm/query_translated_others/meta.content.json +1 -0
  264. dmart/sample/spaces/applications/api/applications/.dm/query_translated_resolution/meta.content.json +1 -0
  265. dmart/sample/spaces/applications/api/applications/create_log.json +1 -0
  266. dmart/sample/spaces/applications/api/applications/create_public_logs.json +1 -0
  267. dmart/sample/spaces/applications/api/applications/query_all_translated_data.json +1 -0
  268. dmart/sample/spaces/applications/api/applications/query_logs.json +1 -0
  269. dmart/sample/spaces/applications/api/applications/query_translated_enums.json +1 -0
  270. dmart/sample/spaces/applications/api/applications/query_translated_others.json +1 -0
  271. dmart/sample/spaces/applications/api/applications/query_translated_resolution.json +1 -0
  272. dmart/sample/spaces/applications/api/applications.json +1 -0
  273. dmart/sample/spaces/applications/api/management/.dm/create_subaccount/meta.content.json +1 -0
  274. dmart/sample/spaces/applications/api/management/.dm/meta.folder.json +1 -0
  275. dmart/sample/spaces/applications/api/management/.dm/update_password/meta.content.json +1 -0
  276. dmart/sample/spaces/applications/api/management/create_subaccount.json +53 -0
  277. dmart/sample/spaces/applications/api/management/update_password.json +1 -0
  278. dmart/sample/spaces/applications/api/management.json +1 -0
  279. dmart/sample/spaces/applications/api/query_all_applications.json +15 -0
  280. dmart/sample/spaces/applications/api/test_by_saad.json +1 -0
  281. dmart/sample/spaces/applications/api/user/.dm/meta.folder.json +1 -0
  282. dmart/sample/spaces/applications/api/user/.dm/test_by_saad/meta.content.json +1 -0
  283. dmart/sample/spaces/applications/api/user/.dm/user_profile/meta.content.json +1 -0
  284. dmart/sample/spaces/applications/api/user/test_by_saad.json +1 -0
  285. dmart/sample/spaces/applications/api/user/user_profile.json +1 -0
  286. dmart/sample/spaces/applications/api/user_profile.json +1 -0
  287. dmart/sample/spaces/applications/api.json +1 -0
  288. dmart/sample/spaces/applications/collections/.dm/meta.folder.json +19 -0
  289. dmart/sample/spaces/applications/collections.json +1 -0
  290. dmart/sample/spaces/applications/configurations/.dm/meta.folder.json +1 -0
  291. dmart/sample/spaces/applications/configurations/time_out.json +1 -0
  292. dmart/sample/spaces/applications/configurations.json +19 -0
  293. dmart/sample/spaces/applications/errors.json +1 -0
  294. dmart/sample/spaces/applications/logs/.dm/meta.folder.json +1 -0
  295. dmart/sample/spaces/applications/logs.json +1 -0
  296. dmart/sample/spaces/applications/queries/.dm/meta.folder.json +1 -0
  297. dmart/sample/spaces/applications/queries/.dm/order/meta.content.json +1 -0
  298. dmart/sample/spaces/applications/queries/order.json +1 -0
  299. dmart/sample/spaces/applications/queries.json +1 -0
  300. dmart/sample/spaces/applications/schema/.dm/api/meta.schema.json +1 -0
  301. dmart/sample/spaces/applications/schema/.dm/configuration/meta.schema.json +1 -0
  302. dmart/sample/spaces/applications/schema/.dm/error/meta.schema.json +1 -0
  303. dmart/sample/spaces/applications/schema/.dm/log/meta.schema.json +1 -0
  304. dmart/sample/spaces/applications/schema/.dm/meta.folder.json +1 -0
  305. dmart/sample/spaces/applications/schema/.dm/query/meta.schema.json +16 -0
  306. dmart/sample/spaces/applications/schema/.dm/translation/meta.schema.json +1 -0
  307. dmart/sample/spaces/applications/schema/api.json +28 -0
  308. dmart/sample/spaces/applications/schema/configuration.json +1 -0
  309. dmart/sample/spaces/applications/schema/error.json +43 -0
  310. dmart/sample/spaces/applications/schema/log.json +1 -0
  311. dmart/sample/spaces/applications/schema/query.json +118 -0
  312. dmart/sample/spaces/applications/schema/translation.json +26 -0
  313. dmart/sample/spaces/applications/schema.json +1 -0
  314. dmart/sample/spaces/applications/translations/.dm/meta.folder.json +1 -0
  315. dmart/sample/spaces/applications/translations.json +1 -0
  316. dmart/sample/spaces/archive/.dm/meta.space.json +27 -0
  317. dmart/sample/spaces/custom_plugins/dummy/__pycache__/plugin.cpython-314.pyc +0 -0
  318. dmart/sample/spaces/custom_plugins/dummy/config.json +28 -0
  319. dmart/sample/spaces/custom_plugins/dummy/plugin.py +6 -0
  320. dmart/sample/spaces/custom_plugins/missed_entry/config.json +12 -0
  321. dmart/sample/spaces/custom_plugins/missed_entry/plugin.py +119 -0
  322. dmart/sample/spaces/custom_plugins/own_changed_notification/__pycache__/plugin.cpython-314.pyc +0 -0
  323. dmart/sample/spaces/custom_plugins/own_changed_notification/config.json +12 -0
  324. dmart/sample/spaces/custom_plugins/own_changed_notification/plugin.py +65 -0
  325. dmart/sample/spaces/custom_plugins/reports_stats/config.json +14 -0
  326. dmart/sample/spaces/custom_plugins/reports_stats/plugin.py +82 -0
  327. dmart/sample/spaces/custom_plugins/system_notification_sender/config.json +22 -0
  328. dmart/sample/spaces/custom_plugins/system_notification_sender/notification.py +268 -0
  329. dmart/sample/spaces/custom_plugins/system_notification_sender/plugin.py +98 -0
  330. dmart/sample/spaces/management/.dm/events.jsonl +32 -0
  331. dmart/sample/spaces/management/.dm/meta.space.json +48 -0
  332. dmart/sample/spaces/management/.dm/notifications/attachments.view.json/admin.json +36 -0
  333. dmart/sample/spaces/management/.dm/notifications/attachments.view.json/meta.admin.json +1 -0
  334. dmart/sample/spaces/management/.dm/notifications/attachments.view.json/meta.system.json +1 -0
  335. dmart/sample/spaces/management/.dm/notifications/attachments.view.json/system.json +32 -0
  336. dmart/sample/spaces/management/collections/.dm/meta.folder.json +1 -0
  337. dmart/sample/spaces/management/collections.json +1 -0
  338. dmart/sample/spaces/management/groups/.dm/meta.folder.json +1 -0
  339. dmart/sample/spaces/management/groups.json +1 -0
  340. dmart/sample/spaces/management/health_check/.dm/meta.folder.json +1 -0
  341. dmart/sample/spaces/management/health_check.json +1 -0
  342. dmart/sample/spaces/management/notifications/.dm/meta.folder.json +1 -0
  343. dmart/sample/spaces/management/notifications/admin/.dm/meta.folder.json +9 -0
  344. dmart/sample/spaces/management/notifications/system/.dm/meta.folder.json +9 -0
  345. dmart/sample/spaces/management/notifications.json +1 -0
  346. dmart/sample/spaces/management/permissions/.dm/access_applications/meta.permission.json +31 -0
  347. dmart/sample/spaces/management/permissions/.dm/access_applications_world/meta.permission.json +31 -0
  348. dmart/sample/spaces/management/permissions/.dm/access_messages/meta.permission.json +23 -0
  349. dmart/sample/spaces/management/permissions/.dm/access_personal/meta.permission.json +40 -0
  350. dmart/sample/spaces/management/permissions/.dm/access_protected/meta.permission.json +33 -0
  351. dmart/sample/spaces/management/permissions/.dm/access_public/meta.permission.json +24 -0
  352. dmart/sample/spaces/management/permissions/.dm/browse_all_folders/meta.permission.json +23 -0
  353. dmart/sample/spaces/management/permissions/.dm/create_log/meta.permission.json +24 -0
  354. dmart/sample/spaces/management/permissions/.dm/interviewer/meta.permission.json +1 -0
  355. dmart/sample/spaces/management/permissions/.dm/manage_applications/meta.permission.json +1 -0
  356. dmart/sample/spaces/management/permissions/.dm/manage_debug/meta.permission.json +25 -0
  357. dmart/sample/spaces/management/permissions/.dm/manage_spaces/meta.permission.json +24 -0
  358. dmart/sample/spaces/management/permissions/.dm/meta.folder.json +1 -0
  359. dmart/sample/spaces/management/permissions/.dm/rules_management_default/meta.permission.json +32 -0
  360. dmart/sample/spaces/management/permissions/.dm/super_manager/meta.permission.json +52 -0
  361. dmart/sample/spaces/management/permissions/.dm/view_activity_log/meta.permission.json +26 -0
  362. dmart/sample/spaces/management/permissions/.dm/view_collections/meta.permission.json +29 -0
  363. dmart/sample/spaces/management/permissions/.dm/view_logs/meta.permission.json +30 -0
  364. dmart/sample/spaces/management/permissions/.dm/view_roles/meta.permission.json +29 -0
  365. dmart/sample/spaces/management/permissions/.dm/view_users/meta.permission.json +25 -0
  366. dmart/sample/spaces/management/permissions/.dm/view_world/meta.permission.json +31 -0
  367. dmart/sample/spaces/management/permissions/.dm/world/meta.permission.json +35 -0
  368. dmart/sample/spaces/management/permissions.json +1 -0
  369. dmart/sample/spaces/management/requests.json +1 -0
  370. dmart/sample/spaces/management/roles/.dm/dummy/meta.role.json +12 -0
  371. dmart/sample/spaces/management/roles/.dm/logged_in/meta.role.json +18 -0
  372. dmart/sample/spaces/management/roles/.dm/manager/meta.role.json +13 -0
  373. dmart/sample/spaces/management/roles/.dm/meta.folder.json +1 -0
  374. dmart/sample/spaces/management/roles/.dm/moderator/meta.role.json +13 -0
  375. dmart/sample/spaces/management/roles/.dm/super_admin/meta.role.json +14 -0
  376. dmart/sample/spaces/management/roles/.dm/test_role/meta.role.json +13 -0
  377. dmart/sample/spaces/management/roles/.dm/world/meta.role.json +15 -0
  378. dmart/sample/spaces/management/roles.json +1 -0
  379. dmart/sample/spaces/management/schema/.dm/admin_notification_request/attachments.media/meta.ui_schema.json +10 -0
  380. dmart/sample/spaces/management/schema/.dm/admin_notification_request/attachments.media/ui_schema.json +32 -0
  381. dmart/sample/spaces/management/schema/.dm/admin_notification_request/meta.schema.json +1 -0
  382. dmart/sample/spaces/management/schema/.dm/api/meta.schema.json +1 -0
  383. dmart/sample/spaces/management/schema/.dm/folder_rendering/meta.schema.json +1 -0
  384. dmart/sample/spaces/management/schema/.dm/health_check/meta.schema.json +17 -0
  385. dmart/sample/spaces/management/schema/.dm/meta.folder.json +1 -0
  386. dmart/sample/spaces/management/schema/.dm/meta_schema/meta.schema.json +1 -0
  387. dmart/sample/spaces/management/schema/.dm/metafile/meta.schema.json +14 -0
  388. dmart/sample/spaces/management/schema/.dm/notification/meta.schema.json +1 -0
  389. dmart/sample/spaces/management/schema/.dm/system_notification_request/attachments.media/meta.ui_schema.json +10 -0
  390. dmart/sample/spaces/management/schema/.dm/system_notification_request/attachments.media/ui_schema.json +32 -0
  391. dmart/sample/spaces/management/schema/.dm/system_notification_request/meta.schema.json +1 -0
  392. dmart/sample/spaces/management/schema/.dm/view/meta.schema.json +1 -0
  393. dmart/sample/spaces/management/schema/.dm/workflow/meta.schema.json +1 -0
  394. dmart/sample/spaces/management/schema/admin_notification_request.json +89 -0
  395. dmart/sample/spaces/management/schema/api.json +1 -0
  396. dmart/sample/spaces/management/schema/folder_rendering.json +238 -0
  397. dmart/sample/spaces/management/schema/health_check.json +8 -0
  398. dmart/sample/spaces/management/schema/meta_schema.json +74 -0
  399. dmart/sample/spaces/management/schema/metafile.json +153 -0
  400. dmart/sample/spaces/management/schema/notification.json +28 -0
  401. dmart/sample/spaces/management/schema/system_notification_request.json +57 -0
  402. dmart/sample/spaces/management/schema/view.json +23 -0
  403. dmart/sample/spaces/management/schema/workflow.json +87 -0
  404. dmart/sample/spaces/management/schema.json +1 -0
  405. dmart/sample/spaces/management/users/.dm/alibaba/meta.user.json +23 -0
  406. dmart/sample/spaces/management/users/.dm/anonymous/meta.user.json +18 -0
  407. dmart/sample/spaces/management/users/.dm/dmart/meta.user.json +26 -0
  408. dmart/sample/spaces/management/users/.dm/meta.folder.json +14 -0
  409. dmart/sample/spaces/management/workflows/.dm/channel/meta.content.json +1 -0
  410. dmart/sample/spaces/management/workflows/.dm/meta.folder.json +1 -0
  411. dmart/sample/spaces/management/workflows/channel.json +148 -0
  412. dmart/sample/spaces/management/workflows.json +1 -0
  413. dmart/sample/spaces/maqola/.dm/meta.space.json +33 -0
  414. dmart/sample/spaces/personal/.dm/meta.space.json +24 -0
  415. dmart/sample/spaces/personal/people/.dm/meta.folder.json +1 -0
  416. dmart/sample/spaces/personal/people/dmart/.dm/meta.folder.json +1 -0
  417. dmart/sample/spaces/personal/people/dmart/messages/.dm/0b5f7e7f/meta.content.json +1 -0
  418. dmart/sample/spaces/personal/people/dmart/messages/.dm/meta.folder.json +1 -0
  419. dmart/sample/spaces/personal/people/dmart/messages/.dm/mytest/meta.content.json +1 -0
  420. dmart/sample/spaces/personal/people/dmart/messages/0b5f7e7f.json +1 -0
  421. dmart/sample/spaces/personal/people/dmart/messages/mytest.json +1 -0
  422. dmart/sample/spaces/personal/people/dmart/notifications/.dm/meta.folder.json +1 -0
  423. dmart/sample/spaces/personal/people/dmart/private/.dm/inner/meta.content.json +1 -0
  424. dmart/sample/spaces/personal/people/dmart/private/.dm/meta.folder.json +1 -0
  425. dmart/sample/spaces/personal/people/dmart/private/inner.json +1 -0
  426. dmart/sample/spaces/personal/people/dmart/protected/.dm/avatar/meta.content.json +1 -0
  427. dmart/sample/spaces/personal/people/dmart/protected/.dm/meta.folder.json +1 -0
  428. dmart/sample/spaces/personal/people/dmart/protected/avatar.png +0 -0
  429. dmart/sample/spaces/personal/people/dmart/public/.dm/meta.folder.json +1 -0
  430. dmart/sample/test/.gitignore +2 -0
  431. dmart/sample/test/createcontent.json +9 -0
  432. dmart/sample/test/createmedia.json +9 -0
  433. dmart/sample/test/createmedia_entry.json +6 -0
  434. dmart/sample/test/createschema.json +8 -0
  435. dmart/sample/test/createschemawork.json +11 -0
  436. dmart/sample/test/createticket.json +13 -0
  437. dmart/sample/test/data.json +4 -0
  438. dmart/sample/test/deletecontent.json +12 -0
  439. dmart/sample/test/logo.jpeg +0 -0
  440. dmart/sample/test/my.jpg +0 -0
  441. dmart/sample/test/myticket.json +23 -0
  442. dmart/sample/test/resources.csv +12 -0
  443. dmart/sample/test/schema.json +16 -0
  444. dmart/sample/test/temp.json +1 -0
  445. dmart/sample/test/test.dmart +45 -0
  446. dmart/sample/test/ticket_schema.json +23 -0
  447. dmart/sample/test/ticket_workflow.json +85 -0
  448. dmart/sample/test/ticketbody.json +4 -0
  449. dmart/sample/test/ticketcontent.json +14 -0
  450. dmart/sample/test/updatecontent.json +20 -0
  451. dmart/sample/test/workflow_schema.json +68 -0
  452. dmart/scheduled_notification_handler.py +121 -0
  453. dmart/schema_migration.py +208 -0
  454. dmart/schema_modulate.py +192 -0
  455. dmart/set_admin_passwd.py +75 -0
  456. dmart/sync.py +202 -0
  457. dmart/test_utils.py +34 -0
  458. dmart/utils/__init__.py +0 -0
  459. dmart/utils/access_control.py +306 -0
  460. dmart/utils/async_request.py +8 -0
  461. dmart/utils/exporter.py +309 -0
  462. dmart/utils/firebase_notifier.py +57 -0
  463. dmart/utils/generate_email.py +37 -0
  464. dmart/utils/helpers.py +352 -0
  465. dmart/utils/hypercorn_config.py +12 -0
  466. dmart/utils/internal_error_code.py +60 -0
  467. dmart/utils/jwt.py +124 -0
  468. dmart/utils/logger.py +167 -0
  469. dmart/utils/middleware.py +99 -0
  470. dmart/utils/notification.py +75 -0
  471. dmart/utils/password_hashing.py +16 -0
  472. dmart/utils/plugin_manager.py +202 -0
  473. dmart/utils/query_policies_helper.py +128 -0
  474. dmart/utils/regex.py +44 -0
  475. dmart/utils/repository.py +529 -0
  476. dmart/utils/router_helper.py +19 -0
  477. dmart/utils/settings.py +212 -0
  478. dmart/utils/sms_notifier.py +21 -0
  479. dmart/utils/social_sso.py +67 -0
  480. dmart/utils/templates/activation.html.j2 +26 -0
  481. dmart/utils/templates/reminder.html.j2 +17 -0
  482. dmart/utils/ticket_sys_utils.py +203 -0
  483. dmart/utils/web_notifier.py +29 -0
  484. dmart/websocket.py +231 -0
  485. dmart-1.4.40.post8.dist-info/METADATA +75 -0
  486. dmart-1.4.40.post8.dist-info/RECORD +489 -0
  487. dmart-1.4.40.post8.dist-info/WHEEL +5 -0
  488. dmart-1.4.40.post8.dist-info/entry_points.txt +2 -0
  489. dmart-1.4.40.post8.dist-info/top_level.txt +1 -0
dmart/utils/logger.py ADDED
@@ -0,0 +1,167 @@
1
+ import re
2
+ import json
3
+ import logging
4
+ import os
5
+
6
+ from utils.settings import settings
7
+ import socket
8
+
9
+
10
+ class JSONEncoder(json.JSONEncoder):
11
+ def default(self, o):
12
+ return str(o)
13
+
14
+
15
+ COMBINED_SENSITIVE_PATTERN = re.compile(
16
+ r'(?i)(?:'
17
+ r'(your\s+otp\s+code\s+is\s+)\d{4,8}|'
18
+ r'(otp\s+for\s+\d+\s+is\s+)\d{4,8}|'
19
+ r'(zip\s+pw\s+for\s+\d+\s+is\s+)[a-f0-9]{6,}|'
20
+ r'(your\s+password\s+for\s+the\s+export\s+zip\s+is\s+)[a-f0-9]{6,}|'
21
+ r'(invitation=)[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+|'
22
+ r'("pin"\s*:\s*)"[^"]*"|'
23
+ r'("authorization"\s*:\s*)"[^"]*"|'
24
+ r'("auth_token"\s*:\s*)"[^"]*"|'
25
+ r'("firebase_token"\s*:\s*)"[^"]*"|'
26
+ r'("evd-device-id"\s*:\s*)"[^"]*"|'
27
+ r'("access_token"\s*:\s*)"[^"]*"|'
28
+ r'("cookie"\s*:\s*)"[^"]*"|'
29
+ r'("set-cookie"\s*:\s*)"[^"]*"'
30
+ r')'
31
+ )
32
+
33
+
34
+ SENSITIVE_KEYWORDS = ("authorization", "token", "password", "otp", "pin", "cookie", "auth", "firebase_token",
35
+ "evd-device-id")
36
+
37
+
38
+ def mask_replacement(match):
39
+ """Replace matched groups with a general mask."""
40
+ groups = match.groups()
41
+ for i, group in enumerate(groups):
42
+ if group is not None:
43
+ # For JSON format: "key": "value" → "key": "******"
44
+ if '"' in group:
45
+ return group + '"' + ('*' * 6) + '"'
46
+ # For plain format: key: value → key: ******
47
+ else:
48
+ return group + ('*' * 6)
49
+ return match.group(0)
50
+
51
+
52
+ def mask_sensitive_data_string(log_string: str) -> str:
53
+ """Mask sensitive data only if keywords present."""
54
+ if not isinstance(log_string, str):
55
+ return log_string
56
+ # Quick keyword check first (very fast)
57
+ if not any(keyword in log_string.lower() for keyword in SENSITIVE_KEYWORDS):
58
+ return log_string
59
+ # Only apply regex if keywords found
60
+ return COMBINED_SENSITIVE_PATTERN.sub(mask_replacement, log_string)
61
+
62
+
63
+ class CustomFormatter(logging.Formatter):
64
+ """
65
+ Emits one JSON line with this exact key order:
66
+ correlation_id, time, level, message, props, thread, process
67
+ """
68
+ def __init__(self) -> None:
69
+ log_dir = os.path.dirname(settings.log_file)
70
+ if not os.path.exists(log_dir):
71
+ os.mkdir(log_dir)
72
+ super().__init__()
73
+
74
+ def format(self, record):
75
+ correlation_id = getattr(record, "correlation_id", "")
76
+ if correlation_id == "ROOT" and getattr(record, "props", None):
77
+ correlation_id = getattr(record, "props", {})\
78
+ .get("response", {}).get("headers", {}).get("x-correlation-id", "")
79
+
80
+ props = getattr(record, "props", {})
81
+
82
+ # Extract hostname
83
+ hostname = socket.gethostname()
84
+
85
+ data = {
86
+ "hostname": hostname,
87
+ "correlation_id": correlation_id,
88
+ "time": self.formatTime(record),
89
+ "level": record.levelname,
90
+ "message": record.getMessage(),
91
+ "props": props,
92
+ "thread": record.threadName,
93
+ "process": record.process,
94
+ }
95
+ try:
96
+ log_string = json.dumps(data, cls=JSONEncoder)
97
+ masked_log = mask_sensitive_data_string(log_string)
98
+ return masked_log
99
+ except Exception as e:
100
+ return json.dumps({"error": str(e), "message": record.getMessage()})
101
+
102
+
103
+ logging_schema : dict = {
104
+ "version": 1,
105
+ "disable_existing_loggers": False,
106
+ "filters": {
107
+ "correlation_id": {
108
+ "()": "asgi_correlation_id.CorrelationIdFilter",
109
+ "uuid_length": 32,
110
+ "default_value": "ROOT",
111
+ },
112
+ },
113
+ "formatters": {
114
+ "json": {"()": "utils.logger.CustomFormatter"},
115
+ },
116
+ "handlers": {
117
+ "console": {
118
+ "class": "logging.StreamHandler",
119
+ "filters": ["correlation_id"],
120
+ "level": "INFO",
121
+ "formatter": "json",
122
+ "stream": "ext://sys.stdout", # Default is stderr
123
+ },
124
+ "file": {
125
+ "class": "concurrent_log_handler.ConcurrentRotatingFileHandler",
126
+ "filters": ["correlation_id"],
127
+ "filename": settings.log_file,
128
+ "backupCount": 5,
129
+ "maxBytes": 0x10000000,
130
+ "use_gzip": False,
131
+ "formatter": "json",
132
+ },
133
+ },
134
+ "root": {
135
+ "handlers": settings.log_handlers,
136
+ "level": "INFO",
137
+ },
138
+ "loggers": {
139
+ "fastapi": {
140
+ "handlers": settings.log_handlers,
141
+ "level": logging.INFO,
142
+ "propagate": False,
143
+ },
144
+ "hypercorn": {
145
+ "handlers": settings.log_handlers,
146
+ "level": logging.INFO,
147
+ "propagate": False,
148
+ },
149
+ "hypercorn.error": {
150
+ "handlers": settings.log_handlers,
151
+ "level": logging.INFO,
152
+ "propagate": False,
153
+ },
154
+ "hypercorn.access": {
155
+ "handlers": settings.log_handlers,
156
+ "level": logging.INFO,
157
+ "propagate": False,
158
+ },
159
+ },
160
+ }
161
+
162
+
163
+ def changeLogFile(log_file: str | None = None) -> None:
164
+ global logging_schema
165
+ if (log_file and "handlers" in logging_schema and "file" in logging_schema["handlers"]
166
+ and "filename" in logging_schema["handlers"]["file"]):
167
+ logging_schema["handlers"]["file"]["filename"] = log_file
@@ -0,0 +1,99 @@
1
+ from contextvars import ContextVar
2
+ from typing import Any
3
+ from starlette.types import ASGIApp, Receive, Scope, Send
4
+ from starlette.requests import Request
5
+ from utils.internal_error_code import InternalErrorCode
6
+ from utils.settings import settings
7
+ import models.api as api
8
+ from fastapi import status
9
+
10
+ REQUEST_DATA_CTX_KEY = "request_data"
11
+
12
+ _request_data_ctx_var: ContextVar[dict] = ContextVar(REQUEST_DATA_CTX_KEY, default={})
13
+
14
+ def get_request_data() -> dict:
15
+ return _request_data_ctx_var.get()
16
+
17
+ class CustomRequestMiddleware:
18
+ def __init__(
19
+ self,
20
+ app: ASGIApp,
21
+ ) -> None:
22
+ self.app = app
23
+
24
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
25
+ if scope["type"] not in ["http", "websocket"]:
26
+ try:
27
+ await self.app(scope, receive, send)
28
+ except Exception as _:
29
+ return
30
+
31
+
32
+ request = Request(scope, receive)
33
+ request_headers = {}
34
+ for k,v in request.headers.items():
35
+ if k in ['cookie', 'authorization']:
36
+ continue
37
+ request_headers[k] = v
38
+
39
+ request_data = _request_data_ctx_var.set({
40
+ "request_headers": request_headers,
41
+ })
42
+
43
+ await self.app(scope, receive, send)
44
+
45
+ _request_data_ctx_var.reset(request_data)
46
+
47
+
48
+ class ChannelMiddleware:
49
+ def __init__(
50
+ self,
51
+ app: ASGIApp,
52
+ ) -> None:
53
+ self.app = app
54
+
55
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
56
+ if scope["type"] not in ["http", "websocket"] or not settings.enable_channel_auth:
57
+ await self.app(scope, receive, send)
58
+ return
59
+
60
+ request = Request(scope, receive)
61
+ channel_key = request.headers.get("x-channel-key")
62
+ if not channel_key:
63
+ for channel in settings.channels:
64
+ for pattern in channel["allowed_api_patterns"]:
65
+ if pattern.search(request.scope['path']):
66
+ raise api.Exception(
67
+ status_code=status.HTTP_403_FORBIDDEN,
68
+ error=api.Error(
69
+ type="channel_auth", code=InternalErrorCode.NOT_ALLOWED, message="Requested method or path is forbidden"
70
+ ),
71
+ )
72
+ await self.app(scope, receive, send)
73
+ return
74
+
75
+ request_channel: dict[str, Any] | None = None
76
+ for channel in settings.channels:
77
+ if channel_key in channel.get("keys", []):
78
+ request_channel = channel
79
+ break
80
+
81
+ if not request_channel:
82
+ raise api.Exception(
83
+ status_code=status.HTTP_403_FORBIDDEN,
84
+ error=api.Error(
85
+ type="channel_auth", code=InternalErrorCode.NOT_ALLOWED, message="Requested method or path is forbidden [2]"
86
+ ),
87
+ )
88
+
89
+ for pattern in request_channel["allowed_api_patterns"]:
90
+ if pattern.search(request.scope['path']):
91
+ await self.app(scope, receive, send)
92
+ return
93
+
94
+ raise api.Exception(
95
+ status_code=status.HTTP_403_FORBIDDEN,
96
+ error=api.Error(
97
+ type="channel_auth", code=InternalErrorCode.NOT_ALLOWED, message="Requested method or path is forbidden [3]"
98
+ ),
99
+ )
@@ -0,0 +1,75 @@
1
+ from abc import ABC, abstractmethod
2
+ from importlib.util import find_spec, module_from_spec
3
+ import json
4
+ import sys
5
+ from typing import Any
6
+ from pathlib import Path
7
+
8
+ from models.core import NotificationData
9
+ from utils.settings import settings
10
+ from models.core import User
11
+ from data_adapters.adapter import data_adapter as db
12
+ from fastapi.logger import logger
13
+
14
+
15
+ class Notifier(ABC):
16
+
17
+ @abstractmethod
18
+ async def send(self, data: NotificationData) -> bool:
19
+ pass
20
+
21
+ async def _load_user(self, shortname: str) -> Any:
22
+ if not hasattr(self, "user"):
23
+
24
+ self.user = await db.load(
25
+ space_name=settings.management_space,
26
+ subpath=settings.users_subpath,
27
+ shortname=shortname,
28
+ class_type=User,
29
+ user_shortname="__system__",
30
+ )
31
+ return self.user
32
+
33
+
34
+ class NotificationManager:
35
+
36
+ notifiers: dict[str, Notifier] = {}
37
+
38
+ def __init__(self) -> None:
39
+ # Load the notifiers depending on config/notification.json file
40
+ if not self.notifiers:
41
+ config_path = Path(__file__).resolve().parent.parent / "config/notification.json"
42
+ if config_path.exists():
43
+ with open(config_path) as conf_file:
44
+ conf_data = json.load(conf_file)
45
+
46
+ for platform, data in conf_data.items():
47
+ if not data["active"]:
48
+ continue
49
+
50
+ module_name = data["module"]
51
+ module_specs = find_spec(module_name)
52
+ if not module_specs or not module_specs.loader:
53
+ continue
54
+ module = module_from_spec(module_specs)
55
+ sys.modules[module_name] = module
56
+ module_specs.loader.exec_module(module)
57
+ self.notifiers[platform] = getattr(module, data["class"])()
58
+
59
+ async def send(self, platform, data: NotificationData) -> bool:
60
+ if platform not in self.notifiers:
61
+ return False
62
+ try:
63
+ await self.notifiers[platform].send(data)
64
+ return True
65
+ except Exception as e:
66
+ logger.warning(
67
+ "Notification",
68
+ extra={
69
+ "props": {
70
+ "title": f"FAIL at {self.notifiers[platform]}.send",
71
+ "message": str(e),
72
+ }
73
+ },
74
+ )
75
+ return False
@@ -0,0 +1,16 @@
1
+ from argon2 import PasswordHasher
2
+
3
+ ph = PasswordHasher(
4
+ memory_cost=102400,
5
+ time_cost=1,
6
+ parallelism=8
7
+ )
8
+
9
+ def verify_password(plain_password: str, hashed_password: str):
10
+ try:
11
+ return ph.verify(hashed_password, plain_password)
12
+ except Exception:
13
+ return False
14
+
15
+ def hash_password(password: str):
16
+ return ph.hash(password)
@@ -0,0 +1,202 @@
1
+ import asyncio
2
+ import os
3
+ import sys
4
+ from importlib.util import find_spec, module_from_spec
5
+ from inspect import iscoroutine
6
+ from pathlib import Path
7
+ import aiofiles
8
+ from fastapi import Depends, FastAPI
9
+ from fastapi.logger import logger
10
+ from models.core import (
11
+ ActionType,
12
+ PluginWrapper,
13
+ PluginBase,
14
+ Event,
15
+ EventFilter,
16
+ EventListenTime,
17
+ )
18
+ from models.enums import ResourceType, PluginType
19
+ from utils.settings import settings
20
+
21
+ CUSTOM_PLUGINS_PATH = settings.spaces_folder / "custom_plugins"
22
+
23
+ # Allow python to search for modules inside the custom plugins
24
+ # by including the path to the parent folder of the custom plugins to sys.path
25
+ if CUSTOM_PLUGINS_PATH.parent.exists():
26
+ sys.path.append(str(CUSTOM_PLUGINS_PATH.parent.resolve()))
27
+
28
+ class PluginManager:
29
+
30
+ plugins_wrappers: dict[
31
+ ActionType, list[PluginWrapper]
32
+ ] = {} # {action_type: list_of_plugins_wrappers]}
33
+
34
+ async def load_plugins(self, app: FastAPI, capture_body):
35
+ # Load core plugins
36
+ current = Path(__file__).resolve().parent
37
+ parent = current.parent
38
+ path = parent / "plugins"
39
+ if path.is_dir():
40
+ await self.load_path_plugins(path, app, capture_body)
41
+
42
+ # Load custom plugins
43
+ path = CUSTOM_PLUGINS_PATH
44
+ if path.is_dir():
45
+ await self.load_path_plugins(path, app, capture_body)
46
+ self.sort_plugins()
47
+
48
+
49
+ async def load_path_plugins(self, path: Path, app: FastAPI, capture_body):
50
+
51
+ plugins_iterator = os.scandir(path)
52
+ for plugin_path in plugins_iterator:
53
+ config_file_path = Path(f"{plugin_path.path}/config.json")
54
+ plugin_file_path = Path(f"{plugin_path.path}/plugin.py")
55
+ if(
56
+ not config_file_path.is_file() or
57
+ not plugin_file_path.is_file()
58
+ ):
59
+ continue
60
+
61
+ # Load plugin config file
62
+ async with aiofiles.open(config_file_path, "r") as config_file:
63
+ plugin_wrapper: PluginWrapper = PluginWrapper.model_validate_json(
64
+ await config_file.read()
65
+ )
66
+ plugin_wrapper.shortname = plugin_path.name
67
+ if not plugin_wrapper.is_active:
68
+ continue
69
+ # Load the plugin module
70
+ module_name = f"{path.parts[-1]}.{plugin_wrapper.shortname}.plugin"
71
+ spec = find_spec(module_name)
72
+ if not spec:
73
+ continue
74
+ module = module_from_spec(spec)
75
+ sys.modules[module_name] = module
76
+ if not spec.loader:
77
+ continue
78
+ spec.loader.exec_module(module)
79
+ try:
80
+ # Register the API plugin routes
81
+ if plugin_wrapper.type == PluginType.api:
82
+ app.include_router(
83
+ module.router,
84
+ prefix=f"/{plugin_wrapper.shortname}",
85
+ tags=[plugin_wrapper.shortname],
86
+ dependencies=[Depends(capture_body)],
87
+ )
88
+
89
+ # Add the Hook Plugin to the loaded plugins
90
+ elif plugin_wrapper.type == PluginType.hook:
91
+ plugin_wrapper.object = getattr(module, "Plugin")()
92
+
93
+ self.store_plugin_in_its_action_dict(plugin_wrapper)
94
+ except Exception as e:
95
+ logger.error(
96
+ f"PLUGIN_ERROR, PLUGIN API {plugin_wrapper.shortname} Failed to load, error: {e.args}"
97
+ )
98
+
99
+ plugins_iterator.close()
100
+
101
+ def store_plugin_in_its_action_dict(self, plugin_wrapper: PluginWrapper):
102
+ if plugin_wrapper.filters:
103
+ for action in plugin_wrapper.filters.actions:
104
+ self.plugins_wrappers.setdefault(action, []).append(plugin_wrapper)
105
+
106
+ def sort_plugins(self):
107
+ """Sort plugins based on plugin_wrapper.ordinal"""
108
+
109
+ for action_type, plugins in self.plugins_wrappers.items():
110
+ self.plugins_wrappers[action_type] = sorted(
111
+ plugins, key=lambda x: x.ordinal
112
+ )
113
+
114
+ def matched_filters(self, plugin_filters: EventFilter, event: Event):
115
+ formats_of_subpath = [event.subpath]
116
+ if event.subpath and event.subpath[0] == "/":
117
+ formats_of_subpath.append(event.subpath[1:])
118
+ else:
119
+ formats_of_subpath.append(f"/{event.subpath}")
120
+
121
+ if "__ALL__" not in plugin_filters.subpaths and not any(
122
+ subpath in plugin_filters.subpaths for subpath in formats_of_subpath
123
+ ):
124
+ return False
125
+
126
+ if event.resource_type == ResourceType.content and (
127
+ "__ALL__" not in plugin_filters.schema_shortnames
128
+ and event.schema_shortname not in plugin_filters.schema_shortnames
129
+ ):
130
+ return False
131
+
132
+ if (
133
+ plugin_filters.resource_types
134
+ and "__ALL__" not in plugin_filters.resource_types
135
+ and event.resource_type not in plugin_filters.resource_types
136
+ ):
137
+ return False
138
+
139
+ return True
140
+
141
+ async def before_action(self, event: Event):
142
+ if event.action_type not in self.plugins_wrappers:
143
+ return
144
+
145
+ from data_adapters.adapter import data_adapter as db
146
+ space = await db.fetch_space(event.space_name)
147
+ if space is None:
148
+ return
149
+ space_plugins = space.active_plugins
150
+
151
+ loop = asyncio.get_event_loop()
152
+ for plugin_model in self.plugins_wrappers[event.action_type]:
153
+ if (
154
+ plugin_model.shortname in space_plugins
155
+ and plugin_model.listen_time == EventListenTime.before
156
+ and plugin_model.filters
157
+ and self.matched_filters(plugin_model.filters, event)
158
+ ):
159
+ try:
160
+ object = plugin_model.object
161
+ if isinstance(object, PluginBase):
162
+ plugin_execution = object.hook(event)
163
+ if iscoroutine(plugin_execution):
164
+ loop.create_task(plugin_execution)
165
+ except Exception as e:
166
+ # print(f"Plugin:{plugin_model}:{str(e)}")
167
+ logger.error(f"Plugin:{plugin_model}:{str(e)}")
168
+
169
+ async def after_action(self, event: Event):
170
+ if event.action_type not in self.plugins_wrappers:
171
+ return
172
+
173
+ from data_adapters.adapter import data_adapter as db
174
+ space = await db.fetch_space(event.space_name)
175
+ if space is None:
176
+ return
177
+ space_plugins = space.active_plugins
178
+
179
+ loop = asyncio.get_event_loop()
180
+ _plugin_model = None
181
+ try:
182
+ for plugin_model in self.plugins_wrappers[event.action_type]:
183
+ _plugin_model = plugin_model
184
+ if (
185
+ plugin_model.shortname in space_plugins
186
+ and plugin_model.listen_time == EventListenTime.after
187
+ and plugin_model.filters
188
+ and self.matched_filters(plugin_model.filters, event)
189
+ ):
190
+ try:
191
+ object = plugin_model.object
192
+ if isinstance(object, PluginBase):
193
+ plugin_execution = object.hook(event)
194
+ if iscoroutine(plugin_execution):
195
+ loop.create_task(plugin_execution)
196
+ except Exception as e:
197
+ logger.error(f"Plugin:{plugin_model}:{str(e)}")
198
+ except Exception as e:
199
+ logger.error(f"Plugin:{_plugin_model}:{str(e)}")
200
+
201
+
202
+ plugin_manager = PluginManager()
@@ -0,0 +1,128 @@
1
+ from models.enums import ResourceType, ConditionType
2
+ from utils.settings import settings
3
+
4
+ def generate_query_policies(
5
+ space_name: str,
6
+ subpath: str,
7
+ resource_type: str,
8
+ is_active: bool,
9
+ owner_shortname: str,
10
+ owner_group_shortname: str | None,
11
+ entry_shortname: str | None = None,
12
+ ) -> list:
13
+ subpath_parts = ["/"]
14
+ subpath_parts += subpath.strip("/").split("/")
15
+
16
+ if resource_type == ResourceType.folder and entry_shortname:
17
+ subpath_parts.append(entry_shortname)
18
+
19
+ query_policies: list = []
20
+ full_subpath = ""
21
+ for subpath_part in subpath_parts:
22
+ full_subpath += subpath_part
23
+ query_policies.append(
24
+ f"{space_name}:{full_subpath.strip('/')}:{resource_type}:{str(is_active).lower()}:{owner_shortname}"
25
+ )
26
+ if owner_group_shortname is None:
27
+ query_policies.append(
28
+ f"{space_name}:{full_subpath.strip('/')}:{resource_type}:{str(is_active).lower()}"
29
+ )
30
+ else:
31
+ query_policies.append(
32
+ f"{space_name}:{full_subpath.strip('/')}:{resource_type}:{str(is_active).lower()}:{owner_group_shortname}"
33
+ )
34
+
35
+ full_subpath_parts = full_subpath.split("/")
36
+ if len(full_subpath_parts) > 1:
37
+ subpath_with_magic_keyword = (
38
+ "/".join(full_subpath_parts[:1]) + "/" + settings.all_subpaths_mw
39
+ )
40
+ if len(full_subpath_parts) > 2:
41
+ subpath_with_magic_keyword += "/" + "/".join(full_subpath_parts[2:])
42
+ query_policies.append(
43
+ f"{space_name}:{subpath_with_magic_keyword.strip('/')}:{resource_type}:{str(is_active).lower()}"
44
+ )
45
+
46
+ if full_subpath == "/":
47
+ full_subpath = ""
48
+ else:
49
+ full_subpath += "/"
50
+
51
+ return query_policies
52
+
53
+
54
+ def matches_subpath(perm_key: str, space_name: str, query_subpath: str) -> bool:
55
+ if not perm_key.startswith(f"{space_name}:"):
56
+ return False
57
+
58
+ parts = perm_key.split(":", 2)
59
+ if len(parts) < 3:
60
+ return False
61
+
62
+ perm_subpath = parts[1].lstrip("/")
63
+ return (
64
+ query_subpath == perm_subpath
65
+ or query_subpath.startswith(perm_subpath + "/")
66
+ )
67
+
68
+
69
+ async def get_user_query_policies(
70
+ db,
71
+ user_shortname: str,
72
+ space_name: str,
73
+ subpath: str,
74
+ is_space: bool = False,
75
+ ) -> list:
76
+ """
77
+ Generate list of query policies based on user's permissions
78
+ ex: [
79
+ "products:offers:content:true:admin_shortname", # IF conditions = {"is_active", "own"}
80
+ "products:offers:content:true:*", # IF conditions = {"is_active"}
81
+ "products:offers:content:false:admin_shortname|products:offers:content:true:admin_shortname",
82
+ # ^^^ IF conditions = {"own"}
83
+ "products:offers:content:*", # IF conditions = {}
84
+ ]
85
+ """
86
+ user_permissions = await db.get_user_permissions(user_shortname)
87
+ user_groups = (await db.load_user_meta(user_shortname)).groups or []
88
+ user_groups.append(user_shortname)
89
+
90
+ query_subpath = subpath.lstrip("/")
91
+
92
+ filtered_permissions = {
93
+ perm_key: permission
94
+ for perm_key, permission in user_permissions.items()
95
+ if 'query' in permission.get('allowed_actions', [])
96
+ and (
97
+ is_space
98
+ or perm_key.startswith(settings.all_spaces_mw)
99
+ or perm_key.startswith(f'{space_name}:__all_subpaths__')
100
+ or matches_subpath(perm_key, space_name, query_subpath)
101
+ )
102
+ }
103
+ sql_query_policies = []
104
+ for perm_key, permission in filtered_permissions.items():
105
+ perm_key = perm_key.replace(settings.all_spaces_mw, space_name)
106
+ perm_key = perm_key.replace(settings.all_subpaths_mw, subpath.strip("/"))
107
+ perm_key = perm_key.strip("/")
108
+ if (
109
+ ConditionType.is_active in permission["conditions"]
110
+ and ConditionType.own in permission["conditions"]
111
+ ):
112
+ for user_group in user_groups:
113
+ sql_query_policies.append(f"{perm_key}:true:{user_group}")
114
+ elif ConditionType.is_active in permission["conditions"]:
115
+ sql_query_policies.append(f"{perm_key}:true:*")
116
+ elif ConditionType.own in permission["conditions"]:
117
+ for user_group in user_groups:
118
+ if settings.active_data_db == 'file':
119
+ sql_query_policies.append(
120
+ f"{perm_key}:true:{user_shortname}|{perm_key}:false:{user_group}"
121
+ )
122
+ else:
123
+ sql_query_policies.append(f"{perm_key}:true:{user_shortname}")
124
+ sql_query_policies.append(f"{perm_key}:false:{user_shortname}")
125
+
126
+ else:
127
+ sql_query_policies.append(f"{perm_key}:*")
128
+ return sql_query_policies