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.
- dmart/__init__.py +7 -0
- dmart/alembic/README +1 -0
- dmart/alembic/__init__.py +0 -0
- dmart/alembic/env.py +91 -0
- dmart/alembic/notes.txt +11 -0
- dmart/alembic/script.py.mako +28 -0
- dmart/alembic/scripts/__init__.py +0 -0
- dmart/alembic/scripts/calculate_checksums.py +77 -0
- dmart/alembic/scripts/migration_f7a4949eed19.py +28 -0
- dmart/alembic/versions/0f3d2b1a7c21_add_authz_materialized_views.py +87 -0
- dmart/alembic/versions/10d2041b94d4_last_checksum_history.py +62 -0
- dmart/alembic/versions/1cf4e1ee3cb8_ext_permission_with_filter_fields_values.py +33 -0
- dmart/alembic/versions/26bfe19b49d4_rm_failedloginattempts.py +42 -0
- dmart/alembic/versions/3c8bca2219cc_add_otp_table.py +38 -0
- dmart/alembic/versions/6675fd9dfe42_remove_unique_from_sessions_table.py +36 -0
- dmart/alembic/versions/71bc1df82e6a_adding_user_last_login_at.py +43 -0
- dmart/alembic/versions/74288ccbd3b5_initial.py +264 -0
- dmart/alembic/versions/7520a89a8467_rm_activesession_table.py +39 -0
- dmart/alembic/versions/848b623755a4_make_created_nd_updated_at_required.py +138 -0
- dmart/alembic/versions/8640dcbebf85_add_notes_to_users.py +32 -0
- dmart/alembic/versions/91c94250232a_adding_fk_on_owner_shortname.py +104 -0
- dmart/alembic/versions/98ecd6f56f9a_ext_meta_with_owner_group_shortname.py +66 -0
- dmart/alembic/versions/9aae9138c4ef_indexing_created_at_updated_at.py +80 -0
- dmart/alembic/versions/__init__.py +0 -0
- dmart/alembic/versions/b53f916b3f6d_json_to_jsonb.py +492 -0
- dmart/alembic/versions/eb5f1ec65156_adding_user_locked_to_device.py +36 -0
- dmart/alembic/versions/f7a4949eed19_adding_query_policies_to_meta.py +60 -0
- dmart/alembic.ini +117 -0
- dmart/api/__init__.py +0 -0
- dmart/api/info/__init__.py +0 -0
- dmart/api/info/router.py +109 -0
- dmart/api/managed/__init__.py +0 -0
- dmart/api/managed/router.py +1541 -0
- dmart/api/managed/utils.py +1879 -0
- dmart/api/public/__init__.py +0 -0
- dmart/api/public/router.py +758 -0
- dmart/api/qr/__init__.py +0 -0
- dmart/api/qr/router.py +108 -0
- dmart/api/user/__init__.py +0 -0
- dmart/api/user/model/__init__.py +0 -0
- dmart/api/user/model/errors.py +14 -0
- dmart/api/user/model/requests.py +165 -0
- dmart/api/user/model/responses.py +11 -0
- dmart/api/user/router.py +1413 -0
- dmart/api/user/service.py +270 -0
- dmart/bundler.py +52 -0
- dmart/cli.py +1133 -0
- dmart/config/__init__.py +0 -0
- dmart/config/channels.json +11 -0
- dmart/config/notification.json +17 -0
- dmart/config.env.sample +27 -0
- dmart/config.ini.sample +7 -0
- dmart/conftest.py +13 -0
- dmart/curl.sh +196 -0
- dmart/cxb/__init__.py +0 -0
- dmart/cxb/assets/@codemirror-Rn7_6DkE.js +10 -0
- dmart/cxb/assets/@edraj-CS4NwVbD.js +1 -0
- dmart/cxb/assets/@floating-ui-BwwcF-xh.js +1 -0
- dmart/cxb/assets/@formatjs-yKEsAtjs.js +1 -0
- dmart/cxb/assets/@fortawesome-DRW1UCdr.js +9 -0
- dmart/cxb/assets/@jsonquerylang-laKNoFFq.js +12 -0
- dmart/cxb/assets/@lezer-za4Q-8Ew.js +1 -0
- dmart/cxb/assets/@marijn-DXwl3gUT.js +1 -0
- dmart/cxb/assets/@popperjs-l0sNRNKZ.js +1 -0
- dmart/cxb/assets/@replit--ERk53eB.js +1 -0
- dmart/cxb/assets/@roxi-CGMFK4i8.js +6 -0
- dmart/cxb/assets/@typewriter-cCzskkIv.js +17 -0
- dmart/cxb/assets/@zerodevx-BlBZjKxu.js +1 -0
- dmart/cxb/assets/@zerodevx-CVEpe6WZ.css +1 -0
- dmart/cxb/assets/BreadCrumbLite-DAhOx38v.js +1 -0
- dmart/cxb/assets/EntryRenderer-CCqV8Rkg.js +32 -0
- dmart/cxb/assets/EntryRenderer-DXytdFp9.css +1 -0
- dmart/cxb/assets/ListView-BQelo7vZ.js +16 -0
- dmart/cxb/assets/ListView-U8of-_c-.css +1 -0
- dmart/cxb/assets/Prism--hMplq-p.js +3 -0
- dmart/cxb/assets/Prism-Uh6uStUw.css +1 -0
- dmart/cxb/assets/Table2Cols-BsbwicQm.js +1 -0
- dmart/cxb/assets/_..-BvT6vdHa.css +1 -0
- dmart/cxb/assets/_...404_-fuLH_rX9.js +2 -0
- dmart/cxb/assets/_...fallback_-Ba_NLmAE.js +1 -0
- dmart/cxb/assets/_module-3HrtKAWo.js +3 -0
- dmart/cxb/assets/_module-DFKFq0AM.js +4 -0
- dmart/cxb/assets/_module-Dgq0ZVtz.js +1 -0
- dmart/cxb/assets/ajv-Cpj98o6Y.js +1 -0
- dmart/cxb/assets/axios-CG2WSiiR.js +6 -0
- dmart/cxb/assets/clsx-B-dksMZM.js +1 -0
- dmart/cxb/assets/codemirror-wrapped-line-indent-DPhKvljI.js +1 -0
- dmart/cxb/assets/compare-C3AjiGFR.js +1 -0
- dmart/cxb/assets/compute-scroll-into-view-Bl8rNFhg.js +1 -0
- dmart/cxb/assets/consolite-DlCuI0F9.js +1 -0
- dmart/cxb/assets/crelt-C8TCjufn.js +1 -0
- dmart/cxb/assets/date-fns-l0sNRNKZ.js +1 -0
- dmart/cxb/assets/deepmerge-rn4rBaHU.js +1 -0
- dmart/cxb/assets/dmart_services-AL6-IdDE.js +1 -0
- dmart/cxb/assets/downloadFile-D08i0YDh.js +1 -0
- dmart/cxb/assets/easy-signal-BiPFIK3O.js +1 -0
- dmart/cxb/assets/esm-env-rsSWfq8L.js +1 -0
- dmart/cxb/assets/export-OF_rTiXu.js +1 -0
- dmart/cxb/assets/fast-deep-equal-l0sNRNKZ.js +1 -0
- dmart/cxb/assets/fast-diff-C-IidNf4.js +1 -0
- dmart/cxb/assets/fast-uri-l0sNRNKZ.js +1 -0
- dmart/cxb/assets/flowbite-svelte-BLvjb-sa.js +1 -0
- dmart/cxb/assets/flowbite-svelte-CD54FDqW.css +1 -0
- dmart/cxb/assets/flowbite-svelte-icons-BI8GVhw_.js +1 -0
- dmart/cxb/assets/github-slugger-CQ4oX9Ud.js +1 -0
- dmart/cxb/assets/global-igKv-1g9.js +1 -0
- dmart/cxb/assets/hookar-BMRD9G9H.js +1 -0
- dmart/cxb/assets/immutable-json-patch-DtRO2E_S.js +1 -0
- dmart/cxb/assets/import-1vE3gBat.js +1 -0
- dmart/cxb/assets/index-B-eTh-ZX.js +1 -0
- dmart/cxb/assets/index-BSsK-X71.js +1 -0
- dmart/cxb/assets/index-BVyxzKtH.js +1 -0
- dmart/cxb/assets/index-BdeNM69f.js +1 -0
- dmart/cxb/assets/index-CC-A1ipE.js +1 -0
- dmart/cxb/assets/index-CQohGiYB.js +1 -0
- dmart/cxb/assets/index-ChjnkpdZ.js +4 -0
- dmart/cxb/assets/index-DLP7csA4.js +1 -0
- dmart/cxb/assets/index-DTfhnhwd.js +1 -0
- dmart/cxb/assets/index-DdXRK7n9.js +2 -0
- dmart/cxb/assets/index-DtiCmB4o.js +1 -0
- dmart/cxb/assets/index-NBrXBlLA.css +2 -0
- dmart/cxb/assets/index-X1uNehO7.js +1 -0
- dmart/cxb/assets/index-nrQW6Nrr.js +1 -0
- dmart/cxb/assets/info-B986lRiM.js +1 -0
- dmart/cxb/assets/intl-messageformat-Dc5UU-HB.js +3 -0
- dmart/cxb/assets/jmespath-l0sNRNKZ.js +1 -0
- dmart/cxb/assets/json-schema-traverse-l0sNRNKZ.js +1 -0
- dmart/cxb/assets/json-source-map-DRgZidqy.js +5 -0
- dmart/cxb/assets/jsonpath-plus-l0sNRNKZ.js +1 -0
- dmart/cxb/assets/jsonrepair-B30Dx381.js +8 -0
- dmart/cxb/assets/lodash-es-DZVAA2ox.js +1 -0
- dmart/cxb/assets/marked-DKjyhwJX.js +56 -0
- dmart/cxb/assets/marked-gfm-heading-id-U5zO829x.js +2 -0
- dmart/cxb/assets/marked-mangle-CDMeiHC6.js +1 -0
- dmart/cxb/assets/memoize-one-BdPwpGay.js +1 -0
- dmart/cxb/assets/natural-compare-lite-Bg2Xcf-o.js +7 -0
- dmart/cxb/assets/pagination-svelte-D5CyoiE_.js +13 -0
- dmart/cxb/assets/pagination-svelte-v10nAbbM.css +1 -0
- dmart/cxb/assets/plantuml-encoder-C47mzt9T.js +1 -0
- dmart/cxb/assets/prismjs-DTUiLGJu.js +9 -0
- dmart/cxb/assets/profile-BUf-tKMe.js +1 -0
- dmart/cxb/assets/query-CNmXTsgf.js +1 -0
- dmart/cxb/assets/queryHelpers-C9iBWwqe.js +1 -0
- dmart/cxb/assets/scroll-into-view-if-needed-KR58zyjF.js +1 -0
- dmart/cxb/assets/spaces-0oyGvpii.js +1 -0
- dmart/cxb/assets/style-mod-Bs6eFhZE.js +3 -0
- dmart/cxb/assets/svelte-B2XmcTi_.js +4 -0
- dmart/cxb/assets/svelte-awesome-COLlx0DN.css +1 -0
- dmart/cxb/assets/svelte-awesome-DhnMA6Q_.js +1 -0
- dmart/cxb/assets/svelte-datatables-net-CY7LBj6I.js +1 -0
- dmart/cxb/assets/svelte-floating-ui-BlS3sOAQ.js +1 -0
- dmart/cxb/assets/svelte-i18n-CT2KkQaN.js +3 -0
- dmart/cxb/assets/svelte-jsoneditor-BzfX6Usi.css +1 -0
- dmart/cxb/assets/svelte-jsoneditor-CUGSvWId.js +25 -0
- dmart/cxb/assets/svelte-select-CegQKzqH.css +1 -0
- dmart/cxb/assets/svelte-select-CjHAt_85.js +6 -0
- dmart/cxb/assets/tailwind-merge-CJvxXMcu.js +1 -0
- dmart/cxb/assets/tailwind-variants-Cj20BoQ3.js +1 -0
- dmart/cxb/assets/toast-B9WDyfyI.js +1 -0
- dmart/cxb/assets/tslib-pJfR_DrR.js +1 -0
- dmart/cxb/assets/typewriter-editor-DkTVIJdm.js +25 -0
- dmart/cxb/assets/user-DeK_NB5v.js +1 -0
- dmart/cxb/assets/vanilla-picker-l5rcX3cq.js +8 -0
- dmart/cxb/assets/w3c-keyname-Vcq4gwWv.js +1 -0
- dmart/cxb/config.json +11 -0
- dmart/cxb/config.sample.json +11 -0
- dmart/cxb/favicon.ico +0 -0
- dmart/cxb/favicon.png +0 -0
- dmart/cxb/index.html +28 -0
- dmart/data_adapters/__init__.py +0 -0
- dmart/data_adapters/adapter.py +16 -0
- dmart/data_adapters/base_data_adapter.py +467 -0
- dmart/data_adapters/file/__init__.py +0 -0
- dmart/data_adapters/file/adapter.py +2043 -0
- dmart/data_adapters/file/adapter_helpers.py +1013 -0
- dmart/data_adapters/file/archive.py +150 -0
- dmart/data_adapters/file/create_index.py +331 -0
- dmart/data_adapters/file/create_users_folders.py +52 -0
- dmart/data_adapters/file/custom_validations.py +68 -0
- dmart/data_adapters/file/drop_index.py +40 -0
- dmart/data_adapters/file/health_check.py +560 -0
- dmart/data_adapters/file/redis_services.py +1110 -0
- dmart/data_adapters/helpers.py +27 -0
- dmart/data_adapters/sql/__init__.py +0 -0
- dmart/data_adapters/sql/adapter.py +3218 -0
- dmart/data_adapters/sql/adapter_helpers.py +491 -0
- dmart/data_adapters/sql/create_tables.py +451 -0
- dmart/data_adapters/sql/create_users_folders.py +53 -0
- dmart/data_adapters/sql/db_to_json_migration.py +485 -0
- dmart/data_adapters/sql/health_check_sql.py +232 -0
- dmart/data_adapters/sql/json_to_db_migration.py +454 -0
- dmart/data_adapters/sql/update_query_policies.py +101 -0
- dmart/data_generator.py +81 -0
- dmart/dmart.py +761 -0
- dmart/get_settings.py +7 -0
- dmart/hypercorn_config.toml +3 -0
- dmart/info.json +1 -0
- dmart/languages/__init__.py +0 -0
- dmart/languages/arabic.json +15 -0
- dmart/languages/english.json +16 -0
- dmart/languages/kurdish.json +14 -0
- dmart/languages/loader.py +12 -0
- dmart/login_creds.sh +7 -0
- dmart/login_creds.sh.sample +7 -0
- dmart/main.py +563 -0
- dmart/manifest.sh +12 -0
- dmart/migrate.py +24 -0
- dmart/models/__init__.py +0 -0
- dmart/models/api.py +203 -0
- dmart/models/core.py +597 -0
- dmart/models/enums.py +255 -0
- dmart/password_gen.py +8 -0
- dmart/plugins/__init__.py +0 -0
- dmart/plugins/action_log/__init__.py +0 -0
- dmart/plugins/action_log/config.json +13 -0
- dmart/plugins/action_log/plugin.py +121 -0
- dmart/plugins/admin_notification_sender/__init__.py +0 -0
- dmart/plugins/admin_notification_sender/config.json +13 -0
- dmart/plugins/admin_notification_sender/plugin.py +124 -0
- dmart/plugins/ldap_manager/__init__.py +0 -0
- dmart/plugins/ldap_manager/config.json +12 -0
- dmart/plugins/ldap_manager/dmart.schema +146 -0
- dmart/plugins/ldap_manager/plugin.py +100 -0
- dmart/plugins/ldap_manager/slapd.conf +53 -0
- dmart/plugins/local_notification/__init__.py +0 -0
- dmart/plugins/local_notification/config.json +13 -0
- dmart/plugins/local_notification/plugin.py +123 -0
- dmart/plugins/realtime_updates_notifier/__init__.py +0 -0
- dmart/plugins/realtime_updates_notifier/config.json +12 -0
- dmart/plugins/realtime_updates_notifier/plugin.py +58 -0
- dmart/plugins/redis_db_update/__init__.py +0 -0
- dmart/plugins/redis_db_update/config.json +13 -0
- dmart/plugins/redis_db_update/plugin.py +188 -0
- dmart/plugins/resource_folders_creation/__init__.py +0 -0
- dmart/plugins/resource_folders_creation/config.json +12 -0
- dmart/plugins/resource_folders_creation/plugin.py +81 -0
- dmart/plugins/system_notification_sender/__init__.py +0 -0
- dmart/plugins/system_notification_sender/config.json +13 -0
- dmart/plugins/system_notification_sender/plugin.py +188 -0
- dmart/plugins/update_access_controls/__init__.py +0 -0
- dmart/plugins/update_access_controls/config.json +12 -0
- dmart/plugins/update_access_controls/plugin.py +9 -0
- dmart/publish.sh +57 -0
- dmart/pylint.sh +16 -0
- dmart/pyrightconfig.json +7 -0
- dmart/redis_connections.sh +13 -0
- dmart/reload.sh +56 -0
- dmart/run.sh +3 -0
- dmart/run_notification_campaign.py +85 -0
- dmart/sample/spaces/applications/.dm/meta.space.json +30 -0
- dmart/sample/spaces/applications/api/.dm/meta.folder.json +1 -0
- dmart/sample/spaces/applications/api/.dm/query_all_applications/meta.content.json +1 -0
- dmart/sample/spaces/applications/api/.dm/test_by_saad/attachments.media/meta.warframe.json +1 -0
- dmart/sample/spaces/applications/api/.dm/test_by_saad/attachments.media/warframe.png +0 -0
- dmart/sample/spaces/applications/api/.dm/test_by_saad/meta.content.json +1 -0
- dmart/sample/spaces/applications/api/.dm/user_profile/meta.content.json +1 -0
- dmart/sample/spaces/applications/api/applications/.dm/create_log/meta.content.json +1 -0
- dmart/sample/spaces/applications/api/applications/.dm/create_public_logs/meta.content.json +1 -0
- dmart/sample/spaces/applications/api/applications/.dm/meta.folder.json +1 -0
- dmart/sample/spaces/applications/api/applications/.dm/query_all_translated_data/meta.content.json +1 -0
- dmart/sample/spaces/applications/api/applications/.dm/query_logs/meta.content.json +1 -0
- dmart/sample/spaces/applications/api/applications/.dm/query_translated_enums/meta.content.json +1 -0
- dmart/sample/spaces/applications/api/applications/.dm/query_translated_others/meta.content.json +1 -0
- dmart/sample/spaces/applications/api/applications/.dm/query_translated_resolution/meta.content.json +1 -0
- dmart/sample/spaces/applications/api/applications/create_log.json +1 -0
- dmart/sample/spaces/applications/api/applications/create_public_logs.json +1 -0
- dmart/sample/spaces/applications/api/applications/query_all_translated_data.json +1 -0
- dmart/sample/spaces/applications/api/applications/query_logs.json +1 -0
- dmart/sample/spaces/applications/api/applications/query_translated_enums.json +1 -0
- dmart/sample/spaces/applications/api/applications/query_translated_others.json +1 -0
- dmart/sample/spaces/applications/api/applications/query_translated_resolution.json +1 -0
- dmart/sample/spaces/applications/api/applications.json +1 -0
- dmart/sample/spaces/applications/api/management/.dm/create_subaccount/meta.content.json +1 -0
- dmart/sample/spaces/applications/api/management/.dm/meta.folder.json +1 -0
- dmart/sample/spaces/applications/api/management/.dm/update_password/meta.content.json +1 -0
- dmart/sample/spaces/applications/api/management/create_subaccount.json +53 -0
- dmart/sample/spaces/applications/api/management/update_password.json +1 -0
- dmart/sample/spaces/applications/api/management.json +1 -0
- dmart/sample/spaces/applications/api/query_all_applications.json +15 -0
- dmart/sample/spaces/applications/api/test_by_saad.json +1 -0
- dmart/sample/spaces/applications/api/user/.dm/meta.folder.json +1 -0
- dmart/sample/spaces/applications/api/user/.dm/test_by_saad/meta.content.json +1 -0
- dmart/sample/spaces/applications/api/user/.dm/user_profile/meta.content.json +1 -0
- dmart/sample/spaces/applications/api/user/test_by_saad.json +1 -0
- dmart/sample/spaces/applications/api/user/user_profile.json +1 -0
- dmart/sample/spaces/applications/api/user_profile.json +1 -0
- dmart/sample/spaces/applications/api.json +1 -0
- dmart/sample/spaces/applications/collections/.dm/meta.folder.json +19 -0
- dmart/sample/spaces/applications/collections.json +1 -0
- dmart/sample/spaces/applications/configurations/.dm/meta.folder.json +1 -0
- dmart/sample/spaces/applications/configurations/time_out.json +1 -0
- dmart/sample/spaces/applications/configurations.json +19 -0
- dmart/sample/spaces/applications/errors.json +1 -0
- dmart/sample/spaces/applications/logs/.dm/meta.folder.json +1 -0
- dmart/sample/spaces/applications/logs.json +1 -0
- dmart/sample/spaces/applications/queries/.dm/meta.folder.json +1 -0
- dmart/sample/spaces/applications/queries/.dm/order/meta.content.json +1 -0
- dmart/sample/spaces/applications/queries/order.json +1 -0
- dmart/sample/spaces/applications/queries.json +1 -0
- dmart/sample/spaces/applications/schema/.dm/api/meta.schema.json +1 -0
- dmart/sample/spaces/applications/schema/.dm/configuration/meta.schema.json +1 -0
- dmart/sample/spaces/applications/schema/.dm/error/meta.schema.json +1 -0
- dmart/sample/spaces/applications/schema/.dm/log/meta.schema.json +1 -0
- dmart/sample/spaces/applications/schema/.dm/meta.folder.json +1 -0
- dmart/sample/spaces/applications/schema/.dm/query/meta.schema.json +16 -0
- dmart/sample/spaces/applications/schema/.dm/translation/meta.schema.json +1 -0
- dmart/sample/spaces/applications/schema/api.json +28 -0
- dmart/sample/spaces/applications/schema/configuration.json +1 -0
- dmart/sample/spaces/applications/schema/error.json +43 -0
- dmart/sample/spaces/applications/schema/log.json +1 -0
- dmart/sample/spaces/applications/schema/query.json +118 -0
- dmart/sample/spaces/applications/schema/translation.json +26 -0
- dmart/sample/spaces/applications/schema.json +1 -0
- dmart/sample/spaces/applications/translations/.dm/meta.folder.json +1 -0
- dmart/sample/spaces/applications/translations.json +1 -0
- dmart/sample/spaces/archive/.dm/meta.space.json +27 -0
- dmart/sample/spaces/custom_plugins/dummy/__pycache__/plugin.cpython-314.pyc +0 -0
- dmart/sample/spaces/custom_plugins/dummy/config.json +28 -0
- dmart/sample/spaces/custom_plugins/dummy/plugin.py +6 -0
- dmart/sample/spaces/custom_plugins/missed_entry/config.json +12 -0
- dmart/sample/spaces/custom_plugins/missed_entry/plugin.py +119 -0
- dmart/sample/spaces/custom_plugins/own_changed_notification/__pycache__/plugin.cpython-314.pyc +0 -0
- dmart/sample/spaces/custom_plugins/own_changed_notification/config.json +12 -0
- dmart/sample/spaces/custom_plugins/own_changed_notification/plugin.py +65 -0
- dmart/sample/spaces/custom_plugins/reports_stats/config.json +14 -0
- dmart/sample/spaces/custom_plugins/reports_stats/plugin.py +82 -0
- dmart/sample/spaces/custom_plugins/system_notification_sender/config.json +22 -0
- dmart/sample/spaces/custom_plugins/system_notification_sender/notification.py +268 -0
- dmart/sample/spaces/custom_plugins/system_notification_sender/plugin.py +98 -0
- dmart/sample/spaces/management/.dm/events.jsonl +32 -0
- dmart/sample/spaces/management/.dm/meta.space.json +48 -0
- dmart/sample/spaces/management/.dm/notifications/attachments.view.json/admin.json +36 -0
- dmart/sample/spaces/management/.dm/notifications/attachments.view.json/meta.admin.json +1 -0
- dmart/sample/spaces/management/.dm/notifications/attachments.view.json/meta.system.json +1 -0
- dmart/sample/spaces/management/.dm/notifications/attachments.view.json/system.json +32 -0
- dmart/sample/spaces/management/collections/.dm/meta.folder.json +1 -0
- dmart/sample/spaces/management/collections.json +1 -0
- dmart/sample/spaces/management/groups/.dm/meta.folder.json +1 -0
- dmart/sample/spaces/management/groups.json +1 -0
- dmart/sample/spaces/management/health_check/.dm/meta.folder.json +1 -0
- dmart/sample/spaces/management/health_check.json +1 -0
- dmart/sample/spaces/management/notifications/.dm/meta.folder.json +1 -0
- dmart/sample/spaces/management/notifications/admin/.dm/meta.folder.json +9 -0
- dmart/sample/spaces/management/notifications/system/.dm/meta.folder.json +9 -0
- dmart/sample/spaces/management/notifications.json +1 -0
- dmart/sample/spaces/management/permissions/.dm/access_applications/meta.permission.json +31 -0
- dmart/sample/spaces/management/permissions/.dm/access_applications_world/meta.permission.json +31 -0
- dmart/sample/spaces/management/permissions/.dm/access_messages/meta.permission.json +23 -0
- dmart/sample/spaces/management/permissions/.dm/access_personal/meta.permission.json +40 -0
- dmart/sample/spaces/management/permissions/.dm/access_protected/meta.permission.json +33 -0
- dmart/sample/spaces/management/permissions/.dm/access_public/meta.permission.json +24 -0
- dmart/sample/spaces/management/permissions/.dm/browse_all_folders/meta.permission.json +23 -0
- dmart/sample/spaces/management/permissions/.dm/create_log/meta.permission.json +24 -0
- dmart/sample/spaces/management/permissions/.dm/interviewer/meta.permission.json +1 -0
- dmart/sample/spaces/management/permissions/.dm/manage_applications/meta.permission.json +1 -0
- dmart/sample/spaces/management/permissions/.dm/manage_debug/meta.permission.json +25 -0
- dmart/sample/spaces/management/permissions/.dm/manage_spaces/meta.permission.json +24 -0
- dmart/sample/spaces/management/permissions/.dm/meta.folder.json +1 -0
- dmart/sample/spaces/management/permissions/.dm/rules_management_default/meta.permission.json +32 -0
- dmart/sample/spaces/management/permissions/.dm/super_manager/meta.permission.json +52 -0
- dmart/sample/spaces/management/permissions/.dm/view_activity_log/meta.permission.json +26 -0
- dmart/sample/spaces/management/permissions/.dm/view_collections/meta.permission.json +29 -0
- dmart/sample/spaces/management/permissions/.dm/view_logs/meta.permission.json +30 -0
- dmart/sample/spaces/management/permissions/.dm/view_roles/meta.permission.json +29 -0
- dmart/sample/spaces/management/permissions/.dm/view_users/meta.permission.json +25 -0
- dmart/sample/spaces/management/permissions/.dm/view_world/meta.permission.json +31 -0
- dmart/sample/spaces/management/permissions/.dm/world/meta.permission.json +35 -0
- dmart/sample/spaces/management/permissions.json +1 -0
- dmart/sample/spaces/management/requests.json +1 -0
- dmart/sample/spaces/management/roles/.dm/dummy/meta.role.json +12 -0
- dmart/sample/spaces/management/roles/.dm/logged_in/meta.role.json +18 -0
- dmart/sample/spaces/management/roles/.dm/manager/meta.role.json +13 -0
- dmart/sample/spaces/management/roles/.dm/meta.folder.json +1 -0
- dmart/sample/spaces/management/roles/.dm/moderator/meta.role.json +13 -0
- dmart/sample/spaces/management/roles/.dm/super_admin/meta.role.json +14 -0
- dmart/sample/spaces/management/roles/.dm/test_role/meta.role.json +13 -0
- dmart/sample/spaces/management/roles/.dm/world/meta.role.json +15 -0
- dmart/sample/spaces/management/roles.json +1 -0
- dmart/sample/spaces/management/schema/.dm/admin_notification_request/attachments.media/meta.ui_schema.json +10 -0
- dmart/sample/spaces/management/schema/.dm/admin_notification_request/attachments.media/ui_schema.json +32 -0
- dmart/sample/spaces/management/schema/.dm/admin_notification_request/meta.schema.json +1 -0
- dmart/sample/spaces/management/schema/.dm/api/meta.schema.json +1 -0
- dmart/sample/spaces/management/schema/.dm/folder_rendering/meta.schema.json +1 -0
- dmart/sample/spaces/management/schema/.dm/health_check/meta.schema.json +17 -0
- dmart/sample/spaces/management/schema/.dm/meta.folder.json +1 -0
- dmart/sample/spaces/management/schema/.dm/meta_schema/meta.schema.json +1 -0
- dmart/sample/spaces/management/schema/.dm/metafile/meta.schema.json +14 -0
- dmart/sample/spaces/management/schema/.dm/notification/meta.schema.json +1 -0
- dmart/sample/spaces/management/schema/.dm/system_notification_request/attachments.media/meta.ui_schema.json +10 -0
- dmart/sample/spaces/management/schema/.dm/system_notification_request/attachments.media/ui_schema.json +32 -0
- dmart/sample/spaces/management/schema/.dm/system_notification_request/meta.schema.json +1 -0
- dmart/sample/spaces/management/schema/.dm/view/meta.schema.json +1 -0
- dmart/sample/spaces/management/schema/.dm/workflow/meta.schema.json +1 -0
- dmart/sample/spaces/management/schema/admin_notification_request.json +89 -0
- dmart/sample/spaces/management/schema/api.json +1 -0
- dmart/sample/spaces/management/schema/folder_rendering.json +238 -0
- dmart/sample/spaces/management/schema/health_check.json +8 -0
- dmart/sample/spaces/management/schema/meta_schema.json +74 -0
- dmart/sample/spaces/management/schema/metafile.json +153 -0
- dmart/sample/spaces/management/schema/notification.json +28 -0
- dmart/sample/spaces/management/schema/system_notification_request.json +57 -0
- dmart/sample/spaces/management/schema/view.json +23 -0
- dmart/sample/spaces/management/schema/workflow.json +87 -0
- dmart/sample/spaces/management/schema.json +1 -0
- dmart/sample/spaces/management/users/.dm/alibaba/meta.user.json +23 -0
- dmart/sample/spaces/management/users/.dm/anonymous/meta.user.json +18 -0
- dmart/sample/spaces/management/users/.dm/dmart/meta.user.json +26 -0
- dmart/sample/spaces/management/users/.dm/meta.folder.json +14 -0
- dmart/sample/spaces/management/workflows/.dm/channel/meta.content.json +1 -0
- dmart/sample/spaces/management/workflows/.dm/meta.folder.json +1 -0
- dmart/sample/spaces/management/workflows/channel.json +148 -0
- dmart/sample/spaces/management/workflows.json +1 -0
- dmart/sample/spaces/maqola/.dm/meta.space.json +33 -0
- dmart/sample/spaces/personal/.dm/meta.space.json +24 -0
- dmart/sample/spaces/personal/people/.dm/meta.folder.json +1 -0
- dmart/sample/spaces/personal/people/dmart/.dm/meta.folder.json +1 -0
- dmart/sample/spaces/personal/people/dmart/messages/.dm/0b5f7e7f/meta.content.json +1 -0
- dmart/sample/spaces/personal/people/dmart/messages/.dm/meta.folder.json +1 -0
- dmart/sample/spaces/personal/people/dmart/messages/.dm/mytest/meta.content.json +1 -0
- dmart/sample/spaces/personal/people/dmart/messages/0b5f7e7f.json +1 -0
- dmart/sample/spaces/personal/people/dmart/messages/mytest.json +1 -0
- dmart/sample/spaces/personal/people/dmart/notifications/.dm/meta.folder.json +1 -0
- dmart/sample/spaces/personal/people/dmart/private/.dm/inner/meta.content.json +1 -0
- dmart/sample/spaces/personal/people/dmart/private/.dm/meta.folder.json +1 -0
- dmart/sample/spaces/personal/people/dmart/private/inner.json +1 -0
- dmart/sample/spaces/personal/people/dmart/protected/.dm/avatar/meta.content.json +1 -0
- dmart/sample/spaces/personal/people/dmart/protected/.dm/meta.folder.json +1 -0
- dmart/sample/spaces/personal/people/dmart/protected/avatar.png +0 -0
- dmart/sample/spaces/personal/people/dmart/public/.dm/meta.folder.json +1 -0
- dmart/sample/test/.gitignore +2 -0
- dmart/sample/test/createcontent.json +9 -0
- dmart/sample/test/createmedia.json +9 -0
- dmart/sample/test/createmedia_entry.json +6 -0
- dmart/sample/test/createschema.json +8 -0
- dmart/sample/test/createschemawork.json +11 -0
- dmart/sample/test/createticket.json +13 -0
- dmart/sample/test/data.json +4 -0
- dmart/sample/test/deletecontent.json +12 -0
- dmart/sample/test/logo.jpeg +0 -0
- dmart/sample/test/my.jpg +0 -0
- dmart/sample/test/myticket.json +23 -0
- dmart/sample/test/resources.csv +12 -0
- dmart/sample/test/schema.json +16 -0
- dmart/sample/test/temp.json +1 -0
- dmart/sample/test/test.dmart +45 -0
- dmart/sample/test/ticket_schema.json +23 -0
- dmart/sample/test/ticket_workflow.json +85 -0
- dmart/sample/test/ticketbody.json +4 -0
- dmart/sample/test/ticketcontent.json +14 -0
- dmart/sample/test/updatecontent.json +20 -0
- dmart/sample/test/workflow_schema.json +68 -0
- dmart/scheduled_notification_handler.py +121 -0
- dmart/schema_migration.py +208 -0
- dmart/schema_modulate.py +192 -0
- dmart/set_admin_passwd.py +75 -0
- dmart/sync.py +202 -0
- dmart/test_utils.py +34 -0
- dmart/utils/__init__.py +0 -0
- dmart/utils/access_control.py +306 -0
- dmart/utils/async_request.py +8 -0
- dmart/utils/exporter.py +309 -0
- dmart/utils/firebase_notifier.py +57 -0
- dmart/utils/generate_email.py +37 -0
- dmart/utils/helpers.py +352 -0
- dmart/utils/hypercorn_config.py +12 -0
- dmart/utils/internal_error_code.py +60 -0
- dmart/utils/jwt.py +124 -0
- dmart/utils/logger.py +167 -0
- dmart/utils/middleware.py +99 -0
- dmart/utils/notification.py +75 -0
- dmart/utils/password_hashing.py +16 -0
- dmart/utils/plugin_manager.py +202 -0
- dmart/utils/query_policies_helper.py +128 -0
- dmart/utils/regex.py +44 -0
- dmart/utils/repository.py +529 -0
- dmart/utils/router_helper.py +19 -0
- dmart/utils/settings.py +212 -0
- dmart/utils/sms_notifier.py +21 -0
- dmart/utils/social_sso.py +67 -0
- dmart/utils/templates/activation.html.j2 +26 -0
- dmart/utils/templates/reminder.html.j2 +17 -0
- dmart/utils/ticket_sys_utils.py +203 -0
- dmart/utils/web_notifier.py +29 -0
- dmart/websocket.py +231 -0
- dmart-1.4.40.post8.dist-info/METADATA +75 -0
- dmart-1.4.40.post8.dist-info/RECORD +489 -0
- dmart-1.4.40.post8.dist-info/WHEEL +5 -0
- dmart-1.4.40.post8.dist-info/entry_points.txt +2 -0
- 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
|