ethyca-fides 2.70.5rc0__py2.py3-none-any.whl → 2.71.0rc1__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ethyca-fides might be problematic. Click here for more details.
- {ethyca_fides-2.70.5rc0.dist-info → ethyca_fides-2.71.0rc1.dist-info}/METADATA +5 -3
- {ethyca_fides-2.70.5rc0.dist-info → ethyca_fides-2.71.0rc1.dist-info}/RECORD +236 -214
- fides/_version.py +3 -3
- fides/api/alembic/migrations/versions/0eef0016cf06_adding_oauth2_config_for_.py +69 -0
- fides/api/alembic/migrations/versions/9e0dcbf67b9f_add_digest_config.py +84 -0
- fides/api/alembic/migrations/versions/a8e0c016afd_add_classification_benchmark_table.py +126 -0
- fides/api/alembic/migrations/versions/cd8649be3a2b_add_classifications_and_user_assigned_.py +74 -0
- fides/api/alembic/migrations/versions/e2cda8d6abc3_add_dismissed_in_activity_view_to_.py +29 -0
- fides/api/api/v1/endpoints/connection_endpoints.py +153 -2
- fides/api/api/v1/endpoints/privacy_request_endpoints.py +18 -2
- fides/api/db/base.py +2 -0
- fides/api/db/crud.py +30 -2
- fides/api/db/database.py +1 -1
- fides/api/db/safe_crud.py +377 -0
- fides/api/migrations/post_upgrade_index_creation.py +10 -0
- fides/api/models/conditional_dependency/__init__.py +0 -0
- fides/api/models/conditional_dependency/conditional_dependency_base.py +82 -0
- fides/api/models/connection_oauth_credentials.py +57 -0
- fides/api/models/connectionconfig.py +7 -0
- fides/api/models/detection_discovery/__init__.py +2 -0
- fides/api/models/detection_discovery/classification_benchmark.py +140 -0
- fides/api/models/detection_discovery/core.py +11 -0
- fides/api/models/detection_discovery/monitor_task.py +2 -1
- fides/api/models/digest/__init__.py +10 -0
- fides/api/models/digest/conditional_dependencies.py +9 -0
- fides/api/models/digest/digest_config.py +76 -0
- fides/api/models/manual_task/conditional_dependency.py +21 -77
- fides/api/models/policy.py +24 -0
- fides/api/models/privacy_notice.py +1 -1
- fides/api/models/privacy_request/privacy_request.py +68 -0
- fides/api/models/privacy_request/webhook.py +3 -1
- fides/api/oauth/roles.py +2 -0
- fides/api/schemas/connection_configuration/connection_oauth_config.py +42 -0
- fides/api/schemas/privacy_request.py +5 -2
- fides/api/schemas/user.py +2 -2
- fides/api/service/connectors/http_connector.py +48 -12
- fides/api/service/privacy_request/dsr_package/dsr_report_builder.py +2 -2
- fides/api/service/storage/streaming/smart_open_streaming_storage.py +36 -28
- fides/api/service/storage/util.py +11 -102
- fides/api/task/conditional_dependencies/sql_schemas.py +301 -0
- fides/api/task/conditional_dependencies/sql_translator.py +757 -0
- fides/api/task/manual/manual_task_utils.py +5 -6
- fides/common/api/v1/urn_registry.py +1 -0
- fides/ui-build/static/admin/404.html +1 -1
- fides/ui-build/static/admin/_next/static/chunks/155-047c3806cc41295e.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/2962-e92d525bf570a9a3.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/3550-83cb70e80cbe41ba.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{3700-dc3f05d21e2a5ff6.js → 3700-08e0703b1ef770da.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{504-afd3588f1908ac33.js → 504-88caa30c03374e9b.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/5643-de406832755d9515.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/6419-9b3a86af57c86791.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/7218-e2983b96b95e33b4.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{7476-0b6e114658b15eaa.js → 7476-d055aa931da47ac0.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/7725-f2a7be705b75dcc3.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/{_app-a4e3c999afb28ee7.js → _app-a77584f9ad3334af.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/{manual-939253f8daf349b2.js → manual-75e99306393938e8.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/[projectUrn]/{[resourceUrn]-293c1f2d9aefb447.js → [resourceUrn]-2fa4b3a58f75f81d.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/resources/{[resourceUrn]-18501152fa1e4f40.js → [resourceUrn]-5b31e3d7727b917a.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/[systemId]-2e1e2b7808d3b21f.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/{[monitorId]-e8ec4080d9a3e22f.js → [monitorId]-0d512528b498d75c.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/discovery/{[resourceUrn]-1901c26cdde820da.js → [resourceUrn]-5525cf287d4ab493.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{discovery-0cea22af5929c81f.js → discovery-ed4723e1b67d890e.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{datamap-619f27c745188adb.js → datamap-15616bea02397ef4.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/{[id]-cc149157d290a94d.js → [id]-816e02b6cbe4a684.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/{new-4c9fb068a5561658.js → new-b6838162200141b3.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations/{[id]-8b13bb5f7bee61c6.js → [id]-01e025f878ba806c.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{integrations-6f109ef64304ef59.js → integrations-14120a529d7dac27.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/{[id]-8566e3b7b2a632fa.js → [id]-7dac2302f573f5ee.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{privacy-requests-2c82cf73d20416f2.js → privacy-requests-7af00f72cf694077.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/reporting/{datamap-5b423687e227ad4d.js → datamap-f7753e9effae3816.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/custom-fields/[id]-a653c05606c53063.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/custom-fields/new-90848e4a15057af1.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/custom-fields-5edfec10a945ca43.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/messaging-providers/{[key]-b0d93bf478bf63ee.js → [key]-77239269acc2d31a.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/messaging-providers/{new-084f9756b9987285.js → new-8bf1821722b082e9.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/{[id]-1895c6a13b543436.js → [id]-547c6ef0ad52b85d.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{systems-d266cc062b56beb2.js → systems-7238e025208078ec.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/taxonomy-a8cfa7de4948b374.js +1 -0
- fides/ui-build/static/admin/_next/static/css/64fac6fb884435c2.css +1 -0
- fides/ui-build/static/admin/_next/static/jPG1zRWiCiEu5jt-DKkZA/_buildManifest.js +1 -0
- fides/ui-build/static/admin/add-systems/manual.html +1 -1
- fides/ui-build/static/admin/add-systems/multiple.html +1 -1
- fides/ui-build/static/admin/add-systems.html +1 -1
- fides/ui-build/static/admin/consent/configure/add-vendors.html +1 -1
- fides/ui-build/static/admin/consent/configure.html +1 -1
- fides/ui-build/static/admin/consent/privacy-experience/[id].html +1 -1
- fides/ui-build/static/admin/consent/privacy-experience/new.html +1 -1
- fides/ui-build/static/admin/consent/privacy-experience.html +1 -1
- fides/ui-build/static/admin/consent/privacy-notices/[id].html +1 -1
- fides/ui-build/static/admin/consent/privacy-notices/new.html +1 -1
- fides/ui-build/static/admin/consent/privacy-notices.html +1 -1
- fides/ui-build/static/admin/consent/properties.html +1 -1
- fides/ui-build/static/admin/consent/reporting.html +1 -1
- fides/ui-build/static/admin/consent.html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn]/[resourceUrn].html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn].html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/projects.html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/resources/[resourceUrn].html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/resources.html +1 -1
- fides/ui-build/static/admin/data-catalog.html +1 -1
- fides/ui-build/static/admin/data-discovery/action-center/[monitorId]/[systemId].html +1 -1
- fides/ui-build/static/admin/data-discovery/action-center/[monitorId].html +1 -1
- fides/ui-build/static/admin/data-discovery/action-center.html +1 -1
- fides/ui-build/static/admin/data-discovery/activity.html +1 -1
- fides/ui-build/static/admin/data-discovery/detection/[resourceUrn].html +1 -1
- fides/ui-build/static/admin/data-discovery/detection.html +1 -1
- fides/ui-build/static/admin/data-discovery/discovery/[resourceUrn].html +1 -1
- fides/ui-build/static/admin/data-discovery/discovery.html +1 -1
- fides/ui-build/static/admin/datamap.html +1 -1
- fides/ui-build/static/admin/dataset/[datasetId]/[collectionName]/[...subfieldNames].html +1 -1
- fides/ui-build/static/admin/dataset/[datasetId]/[collectionName].html +1 -1
- fides/ui-build/static/admin/dataset/[datasetId].html +1 -1
- fides/ui-build/static/admin/dataset/new.html +1 -1
- fides/ui-build/static/admin/dataset.html +1 -1
- fides/ui-build/static/admin/datastore-connection/[id].html +1 -1
- fides/ui-build/static/admin/datastore-connection/new.html +1 -1
- fides/ui-build/static/admin/datastore-connection.html +1 -1
- fides/ui-build/static/admin/index.html +1 -1
- fides/ui-build/static/admin/integrations/[id].html +1 -1
- fides/ui-build/static/admin/integrations.html +1 -1
- fides/ui-build/static/admin/login/[provider].html +1 -1
- fides/ui-build/static/admin/login.html +1 -1
- fides/ui-build/static/admin/messaging/[id].html +1 -1
- fides/ui-build/static/admin/messaging/add-template.html +1 -1
- fides/ui-build/static/admin/messaging.html +1 -1
- fides/ui-build/static/admin/poc/ant-components.html +1 -1
- fides/ui-build/static/admin/poc/form-experiments/AntForm.html +1 -1
- fides/ui-build/static/admin/poc/form-experiments/FormikAntFormItem.html +1 -1
- fides/ui-build/static/admin/poc/form-experiments/FormikControlled.html +1 -1
- fides/ui-build/static/admin/poc/form-experiments/FormikField.html +1 -1
- fides/ui-build/static/admin/poc/form-experiments/FormikSpreadField.html +1 -1
- fides/ui-build/static/admin/poc/forms.html +1 -1
- fides/ui-build/static/admin/poc/table-migration.html +1 -1
- fides/ui-build/static/admin/privacy-requests/[id].html +1 -1
- fides/ui-build/static/admin/privacy-requests/configure/storage.html +1 -1
- fides/ui-build/static/admin/privacy-requests/configure.html +1 -1
- fides/ui-build/static/admin/privacy-requests.html +1 -1
- fides/ui-build/static/admin/properties/[id].html +1 -1
- fides/ui-build/static/admin/properties/add-property.html +1 -1
- fides/ui-build/static/admin/properties.html +1 -1
- fides/ui-build/static/admin/reporting/datamap.html +1 -1
- fides/ui-build/static/admin/settings/about/alpha.html +1 -1
- fides/ui-build/static/admin/settings/about.html +1 -1
- fides/ui-build/static/admin/settings/consent/[configuration_id]/[purpose_id].html +1 -1
- fides/ui-build/static/admin/settings/consent.html +1 -1
- fides/ui-build/static/admin/settings/custom-fields/[id].html +1 -0
- fides/ui-build/static/admin/settings/custom-fields/new.html +1 -0
- fides/ui-build/static/admin/settings/custom-fields.html +1 -1
- fides/ui-build/static/admin/settings/domain-records.html +1 -1
- fides/ui-build/static/admin/settings/domains.html +1 -1
- fides/ui-build/static/admin/settings/email-templates.html +1 -1
- fides/ui-build/static/admin/settings/locations.html +1 -1
- fides/ui-build/static/admin/settings/messaging-providers/[key].html +1 -1
- fides/ui-build/static/admin/settings/messaging-providers/new.html +1 -1
- fides/ui-build/static/admin/settings/messaging-providers.html +1 -1
- fides/ui-build/static/admin/settings/organization.html +1 -1
- fides/ui-build/static/admin/settings/privacy-requests.html +1 -1
- fides/ui-build/static/admin/settings/regulations.html +1 -1
- fides/ui-build/static/admin/systems/configure/[id]/test-datasets.html +1 -1
- fides/ui-build/static/admin/systems/configure/[id].html +1 -1
- fides/ui-build/static/admin/systems.html +1 -1
- fides/ui-build/static/admin/taxonomy.html +1 -1
- fides/ui-build/static/admin/user-management/new.html +1 -1
- fides/ui-build/static/admin/user-management/profile/[id].html +1 -1
- fides/ui-build/static/admin/user-management.html +1 -1
- fides/ui-build/static/admin/_next/static/PZe9jkBWNOGDEEZ4Y_Rvq/_buildManifest.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/155-88303b05c6e115a5.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/3550-d04125c828d591a1.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/6419-d0c00d661b01f8fa.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/7725-dd6736855807936a.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/9951-651d521d9a02b6df.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/[systemId]-b0e3f1886de28d66.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/custom-fields-26ce8fc493993765.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/taxonomy-07848c232d960f6a.js +0 -1
- fides/ui-build/static/admin/_next/static/css/1866822702989bb3.css +0 -1
- {ethyca_fides-2.70.5rc0.dist-info → ethyca_fides-2.71.0rc1.dist-info}/WHEEL +0 -0
- {ethyca_fides-2.70.5rc0.dist-info → ethyca_fides-2.71.0rc1.dist-info}/entry_points.txt +0 -0
- {ethyca_fides-2.70.5rc0.dist-info → ethyca_fides-2.71.0rc1.dist-info}/licenses/LICENSE +0 -0
- {ethyca_fides-2.70.5rc0.dist-info → ethyca_fides-2.71.0rc1.dist-info}/top_level.txt +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{1115-7888473b3dc28cda.js → 1115-90baef2a89f361ad.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{1817-ee9e29a6b8c4af50.js → 1817-ca6473f31a67a804.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{2040-ab6212a3074f34f9.js → 2040-fdecc41a18e40bdc.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{3585-451504ea5ed7a1ea.js → 3585-efd5d41f08e180c4.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{3696-c25dc8d1b0e1aee1.js → 3696-90c8b336bbc46782.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{3872-3514b712afd683c0.js → 3872-04d3afbfa41a7782.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{3923-a551756b413367ea.js → 3923-98bea73b618292aa.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{4608-b22905c65f61db15.js → 4608-a9941d0c236ebca1.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{502-a40d39e615f7b664.js → 502-d3ecae97b67befbd.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{5185-33f50cf9ae17b42e.js → 5185-51eaa78e3ed6bfb7.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{5574-b8c4cba5a6938c00.js → 5574-c31ea831371610d5.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{5783-8de76df87af55e98.js → 5783-d119cb132abd8a91.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{6084-2cd165179c428a6f.js → 6084-d0943ee628bf4388.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{6416-264aadc6b336ee0a.js → 6416-0ccadfefcdad00cc.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{6882-447f15e87b8c48a5.js → 6882-10296485ec326e6b.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{6954-599f2de2c902e9b2.js → 6954-4b24e1731c1cc3b3.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{7158-835ba42fd881d8dd.js → 7158-04745cc8d684b2e7.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{7630-e7ea13be69c118a1.js → 7630-d0d3a0fe3f95e971.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{796-e07ac2c543f569e3.js → 796-02086581996a0548.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{8002-25fd174aec9b077b.js → 8002-ed832921ad190832.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{9226-2f960b7ca530642a.js → 9226-4a7027057f55ca2a.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{9826-c02be5882205bbbc.js → 9826-ccedc28e978ca9e1.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/{multiple-83ed7da0bb90b0a5.js → multiple-2ca59996860a33c5.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{add-systems-f90aa48500c1cbde.js → add-systems-58920afe2b67f952.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/configure/{add-vendors-46b88bda3d7b15c5.js → add-vendors-7a258b7ecd6da4b8.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{configure-c0db068d1863222f.js → configure-fb5017ff5fa54fcc.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{privacy-experience-92a337ee96845af2.js → privacy-experience-92182be6603c2842.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{[id]-1fd2cda8707314f6.js → [id]-9c23fbe813c997d0.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{new-2987e397445713c5.js → new-0e5e38bbcfe59fd2.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{privacy-notices-59a8aff5935482ec.js → privacy-notices-ab54b19609bff325.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{consent-e5a33654a2dfaf35.js → consent-d2bf72508c3cad55.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/{[projectUrn]-6a4b0d49dcbf17a8.js → [projectUrn]-4a1af12d2d7cd660.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/{projects-be7b385073f22414.js → projects-99573a1ee3ef8f4c.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/{resources-af80fdca3bbdc82f.js → resources-6e429b7511028d60.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{data-catalog-351caadeef03876a.js → data-catalog-56fd0f3e465e52b6.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{action-center-1554afcb8b9da2ab.js → action-center-040813022f0890c9.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/detection/{[resourceUrn]-0497f3ffdb632516.js → [resourceUrn]-844a8de0d1b506e2.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{detection-a780390da99f3e43.js → detection-11b07cf2d91b17ef.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/[collectionName]/{[...subfieldNames]-ef71f387fbbab425.js → [...subfieldNames]-d4031e438c363fff.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/{[collectionName]-d790553662caf5c3.js → [collectionName]-9463af37079762d0.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/{[datasetId]-cb63db8594fe8dc1.js → [datasetId]-60a4a9eb4aab4c11.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/{new-78e052c8f95110c4.js → new-ea198c4a7869f402.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{dataset-d6d7ee8bd8858a8a.js → dataset-0e3a6ac4797ffbbb.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{datastore-connection-a832084ce294f8af.js → datastore-connection-223c2d1ded51bfb1.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{index-269b8f81546dad66.js → index-b74d1e8608ae5b5d.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/messaging/{[id]-d8c5c03fb2f31d65.js → [id]-e8d2140787045acd.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/messaging/{add-template-0e0c06e3c8aabe02.js → add-template-e3f93462a08251bf.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{messaging-3f15804cf9625f01.js → messaging-b5f7d6afdecd013d.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/poc/{table-migration-e551fccfcaae76e7.js → table-migration-29fb7b39f8962650.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/{storage-1b38b656807ed0cd.js → storage-648d775d0fce49dc.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/{configure-b00532a0ad4f6927.js → configure-8f577df28ebca869.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/properties/{[id]-322a01e2bceab3fb.js → [id]-57a75c7e9659271a.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/properties/{add-property-777ed2d73812043d.js → add-property-8964c2300206bc89.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{consent-55e36839d219a503.js → consent-e5d781b28f8e29c8.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{domain-records-7ddf9d992fe714a6.js → domain-records-744f669431b84f71.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{domains-7c78ae51f0dd7102.js → domains-a3275554ffe8e640.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{email-templates-05ffbda19ab894b9.js → email-templates-604790638c656fbd.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{locations-77fb85bdd0be42b5.js → locations-be2a885150adc133.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{messaging-providers-6c51ffd46bb598e7.js → messaging-providers-8d92be437793c96f.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{organization-44456bfe54ac4ad5.js → organization-3c86162afe9759df.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{privacy-requests-fbe7e8030d837aed.js → privacy-requests-8cbdfd08e0fa88fb.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{regulations-2866ac99faa5a542.js → regulations-4fe3b90747d885e5.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/[id]/{test-datasets-a86bafe1b4e1205f.js → test-datasets-2deb6becece69d46.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/user-management/{new-6304dad2c5fab694.js → new-629c88e90699369b.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/user-management/profile/{[id]-ff4711db191099cd.js → [id]-98f737e735eaa0f0.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{user-management-82fcf1151e2fe2ba.js → user-management-562624e5461083ec.js} +0 -0
- /fides/ui-build/static/admin/_next/static/{PZe9jkBWNOGDEEZ4Y_Rvq → jPG1zRWiCiEu5jt-DKkZA}/_ssgManifest.js +0 -0
|
@@ -3,6 +3,9 @@ from typing import Any, Dict, List, Optional
|
|
|
3
3
|
|
|
4
4
|
import requests
|
|
5
5
|
from loguru import logger
|
|
6
|
+
from oauthlib.oauth2 import BackendApplicationClient
|
|
7
|
+
from requests.auth import HTTPBasicAuth
|
|
8
|
+
from requests_oauthlib import OAuth2Session
|
|
6
9
|
from starlette.status import HTTP_500_INTERNAL_SERVER_ERROR
|
|
7
10
|
|
|
8
11
|
from fides.api.common_exceptions import ClientUnsuccessfulException
|
|
@@ -41,25 +44,58 @@ class HTTPSConnector(BaseConnector[None]):
|
|
|
41
44
|
) -> Optional[Dict[str, Any]]:
|
|
42
45
|
"""Calls a client-defined endpoint and returns the data that it responds with"""
|
|
43
46
|
config = HttpsSchema(**self.configuration.secrets or {})
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
|
|
48
|
+
def _request_with_oauth() -> requests.Response:
|
|
49
|
+
"""Helper function to make a request with OAuth2 authentication"""
|
|
50
|
+
client_id = oauth_config.client_id
|
|
51
|
+
client_secret = oauth_config.client_secret
|
|
52
|
+
scopes = oauth_config.scope.split(" ") or []
|
|
53
|
+
auth = HTTPBasicAuth(client_id, client_secret)
|
|
54
|
+
client = BackendApplicationClient(client_id=client_id)
|
|
55
|
+
session_client = OAuth2Session(client=client, scope=scopes)
|
|
56
|
+
try:
|
|
57
|
+
# Fetch the access token from the token URL
|
|
58
|
+
session_client.fetch_token(token_url=oauth_config.token_url, auth=auth)
|
|
59
|
+
except Exception as e:
|
|
60
|
+
logger.error(f"Error fetching OAuth2 token: {e}")
|
|
61
|
+
raise ClientUnsuccessfulException(
|
|
62
|
+
status_code=HTTP_500_INTERNAL_SERVER_ERROR
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
return session_client.post(
|
|
66
|
+
url=config.url, headers=additional_headers, json=request_body
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def _request_without_oauth() -> requests.Response:
|
|
70
|
+
"""Helper function to make a request without OAuth2 authentication"""
|
|
71
|
+
headers = self.build_authorization_header()
|
|
72
|
+
headers.update(additional_headers)
|
|
73
|
+
return requests.post(url=config.url, headers=headers, json=request_body)
|
|
74
|
+
|
|
75
|
+
oauth_config = self.configuration.oauth_config
|
|
46
76
|
|
|
47
77
|
try:
|
|
48
|
-
response =
|
|
78
|
+
response = (
|
|
79
|
+
_request_with_oauth()
|
|
80
|
+
if oauth_config is not None
|
|
81
|
+
else _request_without_oauth()
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
if not response_expected:
|
|
85
|
+
return {}
|
|
86
|
+
|
|
87
|
+
if not response.ok:
|
|
88
|
+
logger.error("Invalid response received from webhook.")
|
|
89
|
+
raise ClientUnsuccessfulException(status_code=response.status_code)
|
|
90
|
+
|
|
91
|
+
return json.loads(response.text)
|
|
92
|
+
|
|
49
93
|
except requests.ConnectionError:
|
|
50
|
-
logger.
|
|
94
|
+
logger.error("HTTPS client received a connection error.")
|
|
51
95
|
raise ClientUnsuccessfulException(
|
|
52
96
|
status_code=HTTP_500_INTERNAL_SERVER_ERROR
|
|
53
97
|
)
|
|
54
98
|
|
|
55
|
-
if not response_expected:
|
|
56
|
-
return {}
|
|
57
|
-
|
|
58
|
-
if not response.ok:
|
|
59
|
-
logger.error("Invalid response received from webhook.")
|
|
60
|
-
raise ClientUnsuccessfulException(status_code=response.status_code)
|
|
61
|
-
return json.loads(response.text)
|
|
62
|
-
|
|
63
99
|
def test_connection(self) -> Optional[ConnectionTestStatus]:
|
|
64
100
|
"""
|
|
65
101
|
Override to skip connection test
|
|
@@ -26,7 +26,7 @@ from fides.api.service.storage.util import (
|
|
|
26
26
|
is_attachment_field,
|
|
27
27
|
process_attachment_naming,
|
|
28
28
|
process_attachments_contextually,
|
|
29
|
-
|
|
29
|
+
resolve_path_from_context,
|
|
30
30
|
)
|
|
31
31
|
from fides.api.util.storage_util import StorageJSONEncoder, format_size
|
|
32
32
|
from fides.config import CONFIG
|
|
@@ -226,7 +226,7 @@ class DSRReportBuilder:
|
|
|
226
226
|
file_size = format_attachment_size(attachment.get("file_size"))
|
|
227
227
|
|
|
228
228
|
# Determine the actual directory for this attachment based on its context
|
|
229
|
-
actual_directory =
|
|
229
|
+
actual_directory = resolve_path_from_context(attachment, directory)
|
|
230
230
|
|
|
231
231
|
# Generate attachment URL using shared utility with actual storage path
|
|
232
232
|
download_url = attachment.get("download_url")
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
import csv
|
|
5
5
|
import json
|
|
6
6
|
import time
|
|
7
|
+
from copy import deepcopy
|
|
7
8
|
from datetime import datetime
|
|
8
9
|
from io import BytesIO, StringIO
|
|
9
10
|
from itertools import chain
|
|
@@ -36,13 +37,11 @@ from fides.api.service.storage.streaming.schemas import (
|
|
|
36
37
|
)
|
|
37
38
|
from fides.api.service.storage.streaming.smart_open_client import SmartOpenStorageClient
|
|
38
39
|
from fides.api.service.storage.util import (
|
|
39
|
-
convert_processed_attachments_to_attachment_processing_info,
|
|
40
40
|
determine_dataset_name_from_path,
|
|
41
|
-
extract_storage_key_from_attachment,
|
|
42
41
|
get_unique_filename,
|
|
43
42
|
process_attachments_contextually,
|
|
44
43
|
resolve_attachment_storage_path,
|
|
45
|
-
|
|
44
|
+
resolve_path_from_context,
|
|
46
45
|
)
|
|
47
46
|
|
|
48
47
|
DEFAULT_ATTACHMENT_NAME = "attachment"
|
|
@@ -169,7 +168,9 @@ class SmartOpenStreamingStorage:
|
|
|
169
168
|
"""
|
|
170
169
|
try:
|
|
171
170
|
# Extract storage key using shared utility
|
|
172
|
-
storage_key =
|
|
171
|
+
storage_key = (
|
|
172
|
+
attachment.get("download_url") or attachment.get("file_name") or ""
|
|
173
|
+
)
|
|
173
174
|
if not storage_key:
|
|
174
175
|
return None
|
|
175
176
|
|
|
@@ -182,7 +183,7 @@ class SmartOpenStreamingStorage:
|
|
|
182
183
|
)
|
|
183
184
|
|
|
184
185
|
# Resolve base path using shared utility
|
|
185
|
-
base_path =
|
|
186
|
+
base_path = resolve_path_from_context(attachment)
|
|
186
187
|
|
|
187
188
|
# Create AttachmentProcessingInfo
|
|
188
189
|
processing_info = AttachmentProcessingInfo(
|
|
@@ -281,7 +282,11 @@ class SmartOpenStreamingStorage:
|
|
|
281
282
|
) from e
|
|
282
283
|
|
|
283
284
|
def _collect_and_validate_attachments(
|
|
284
|
-
self,
|
|
285
|
+
self,
|
|
286
|
+
data: dict,
|
|
287
|
+
used_filenames_data: set[str],
|
|
288
|
+
used_filenames_attachments: set[str],
|
|
289
|
+
processed_attachments: dict[tuple[str, str], str],
|
|
285
290
|
) -> list[AttachmentProcessingInfo]:
|
|
286
291
|
"""Collect and validate attachments using the same contextual approach as DSR report builder.
|
|
287
292
|
|
|
@@ -294,26 +299,30 @@ class SmartOpenStreamingStorage:
|
|
|
294
299
|
Returns:
|
|
295
300
|
List of validated AttachmentProcessingInfo objects
|
|
296
301
|
"""
|
|
297
|
-
|
|
298
|
-
used_filenames_data: set[str] = set()
|
|
299
|
-
used_filenames_attachments: set[str] = set()
|
|
300
|
-
processed_attachments: dict[tuple[str, str], str] = {}
|
|
302
|
+
validated_attachments = []
|
|
301
303
|
|
|
302
304
|
# Use the shared contextual processing function
|
|
303
305
|
# Note: This method should only be used when DSR report builder is not available
|
|
304
306
|
# For HTML format, use _collect_and_validate_attachments_from_dsr_builder instead
|
|
305
307
|
processed_attachments_list = process_attachments_contextually(
|
|
306
|
-
data,
|
|
307
|
-
used_filenames_data,
|
|
308
|
-
used_filenames_attachments,
|
|
309
|
-
processed_attachments,
|
|
310
|
-
enable_streaming=True,
|
|
308
|
+
data=data,
|
|
309
|
+
used_filenames_data=used_filenames_data,
|
|
310
|
+
used_filenames_attachments=used_filenames_attachments,
|
|
311
|
+
processed_attachments=processed_attachments,
|
|
312
|
+
enable_streaming=True,
|
|
311
313
|
)
|
|
312
314
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
315
|
+
for processed_attachment in processed_attachments_list:
|
|
316
|
+
# Add context information to the attachment data
|
|
317
|
+
attachment_with_context = deepcopy(processed_attachment["attachment"])
|
|
318
|
+
attachment_with_context["_context"] = processed_attachment["context"]
|
|
319
|
+
|
|
320
|
+
# Validate and convert to AttachmentProcessingInfo
|
|
321
|
+
validated = self._validate_attachment(attachment_with_context)
|
|
322
|
+
if validated:
|
|
323
|
+
validated_attachments.append(validated)
|
|
324
|
+
|
|
325
|
+
return validated_attachments
|
|
317
326
|
|
|
318
327
|
def _collect_and_validate_attachments_from_dsr_builder(
|
|
319
328
|
self, data: dict, dsr_builder: "DSRReportBuilder"
|
|
@@ -342,17 +351,11 @@ class SmartOpenStreamingStorage:
|
|
|
342
351
|
else:
|
|
343
352
|
used_filenames_data.update(filenames)
|
|
344
353
|
|
|
345
|
-
|
|
354
|
+
return self._collect_and_validate_attachments(
|
|
346
355
|
data,
|
|
347
356
|
used_filenames_data,
|
|
348
357
|
used_filenames_attachments,
|
|
349
358
|
dsr_builder.processed_attachments,
|
|
350
|
-
enable_streaming=True, # Always use streaming mode for storage
|
|
351
|
-
)
|
|
352
|
-
|
|
353
|
-
# Convert to AttachmentProcessingInfo objects using shared utility
|
|
354
|
-
return convert_processed_attachments_to_attachment_processing_info(
|
|
355
|
-
processed_attachments_list, self._validate_attachment
|
|
356
359
|
)
|
|
357
360
|
|
|
358
361
|
@retry_cloud_storage_operation(
|
|
@@ -374,7 +377,7 @@ class SmartOpenStreamingStorage:
|
|
|
374
377
|
"""Upload data to cloud storage using smart-open streaming for memory efficiency.
|
|
375
378
|
|
|
376
379
|
This function leverages smart-open's streaming capabilities while maintaining
|
|
377
|
-
our DSR-specific business logic for package
|
|
380
|
+
our DSR-specific business logic for package and attachment processing.
|
|
378
381
|
All data is streamed directly from source to destination without local storage.
|
|
379
382
|
|
|
380
383
|
Args:
|
|
@@ -627,7 +630,12 @@ class SmartOpenStreamingStorage:
|
|
|
627
630
|
resp_format: Response format (csv, json)
|
|
628
631
|
"""
|
|
629
632
|
# Collect and validate all attachments using shared contextual processing
|
|
630
|
-
all_attachments = self._collect_and_validate_attachments(
|
|
633
|
+
all_attachments = self._collect_and_validate_attachments(
|
|
634
|
+
data=data,
|
|
635
|
+
used_filenames_data=set(),
|
|
636
|
+
used_filenames_attachments=set(),
|
|
637
|
+
processed_attachments={},
|
|
638
|
+
)
|
|
631
639
|
|
|
632
640
|
if not all_attachments:
|
|
633
641
|
# No attachments, just upload the data
|
|
@@ -544,31 +544,9 @@ def _process_attachment_list(
|
|
|
544
544
|
return processed_attachments_list
|
|
545
545
|
|
|
546
546
|
|
|
547
|
-
def
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
This function provides a consistent way to extract storage keys from
|
|
552
|
-
attachment dictionaries across different components.
|
|
553
|
-
|
|
554
|
-
Args:
|
|
555
|
-
attachment: The attachment dictionary
|
|
556
|
-
|
|
557
|
-
Returns:
|
|
558
|
-
The storage key (URL or filename) for the attachment
|
|
559
|
-
"""
|
|
560
|
-
if original_url := attachment.get("original_download_url"):
|
|
561
|
-
return original_url
|
|
562
|
-
|
|
563
|
-
if download_url := attachment.get("download_url"):
|
|
564
|
-
return download_url
|
|
565
|
-
|
|
566
|
-
file_name = attachment.get("file_name")
|
|
567
|
-
return file_name if file_name is not None else ""
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
def resolve_base_path_from_context(
|
|
571
|
-
attachment: dict[str, Any], default_base_path: str = "attachments"
|
|
547
|
+
def resolve_path_from_context(
|
|
548
|
+
attachment: dict[str, Any],
|
|
549
|
+
default_path: str = "attachments",
|
|
572
550
|
) -> str:
|
|
573
551
|
"""
|
|
574
552
|
Resolve the base path for an attachment based on its context.
|
|
@@ -578,93 +556,24 @@ def resolve_base_path_from_context(
|
|
|
578
556
|
|
|
579
557
|
Args:
|
|
580
558
|
attachment: The attachment dictionary
|
|
581
|
-
|
|
559
|
+
default_path: Default path if no context is found
|
|
582
560
|
|
|
583
561
|
Returns:
|
|
584
|
-
The resolved
|
|
562
|
+
The resolved path for the attachment
|
|
585
563
|
"""
|
|
586
564
|
if not attachment.get("_context"):
|
|
587
|
-
return
|
|
565
|
+
return default_path
|
|
588
566
|
|
|
589
567
|
context = attachment["_context"]
|
|
590
568
|
context_type = context.get("type")
|
|
591
569
|
|
|
592
|
-
if context_type
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
return f"data/{
|
|
570
|
+
if context_type in ["direct", "nested"]:
|
|
571
|
+
dataset = context.get("dataset", "")
|
|
572
|
+
collection = context.get("collection", "")
|
|
573
|
+
return f"data/{dataset}/{collection}/attachments"
|
|
596
574
|
if context_type == "top_level":
|
|
597
575
|
return "attachments"
|
|
598
|
-
# Handle old context format
|
|
599
576
|
if context.get("key") and context.get("item_id"):
|
|
600
577
|
return f"{context['key']}/{context['item_id']}/attachments"
|
|
601
|
-
# Fallback for unknown context types
|
|
602
|
-
return "unknown/unknown/attachments"
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
def resolve_directory_from_context(
|
|
606
|
-
attachment: dict[str, Any], default_directory: str = "attachments"
|
|
607
|
-
) -> str:
|
|
608
|
-
"""
|
|
609
|
-
Resolve the directory path for an attachment based on its context.
|
|
610
|
-
|
|
611
|
-
This function provides consistent directory resolution logic for DSR report builder.
|
|
612
|
-
|
|
613
|
-
Args:
|
|
614
|
-
attachment: The attachment dictionary
|
|
615
|
-
default_directory: Default directory if no context is found
|
|
616
|
-
|
|
617
|
-
Returns:
|
|
618
|
-
The resolved directory path for the attachment
|
|
619
|
-
"""
|
|
620
|
-
if not attachment.get("_context"):
|
|
621
|
-
return default_directory
|
|
622
|
-
|
|
623
|
-
context = attachment["_context"]
|
|
624
|
-
context_type = context.get("type")
|
|
625
|
-
|
|
626
|
-
if context_type == "direct":
|
|
627
|
-
return f"data/{context['dataset']}/{context['collection']}"
|
|
628
|
-
if context_type == "nested":
|
|
629
|
-
return f"data/{context['dataset']}/{context['collection']}"
|
|
630
|
-
if context_type == "top_level":
|
|
631
|
-
return "attachments"
|
|
632
|
-
if context.get("key") and context.get("item_id"):
|
|
633
|
-
return f"{context['key']}/{context['item_id']}"
|
|
634
|
-
|
|
635
|
-
return default_directory
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
def convert_processed_attachments_to_attachment_processing_info(
|
|
639
|
-
processed_attachments_list: list[dict[str, Any]], validate_attachment_func: Callable
|
|
640
|
-
) -> list[Any]:
|
|
641
|
-
"""
|
|
642
|
-
Convert processed attachments list to AttachmentProcessingInfo objects.
|
|
643
|
-
|
|
644
|
-
This is a shared utility function to avoid duplication between different
|
|
645
|
-
attachment collection methods.
|
|
646
|
-
|
|
647
|
-
Args:
|
|
648
|
-
processed_attachments_list: List of processed attachment dictionaries
|
|
649
|
-
validate_attachment_func: Function to validate individual attachments
|
|
650
|
-
Signature: validate_attachment_func(attachment_with_context) -> AttachmentProcessingInfo | None
|
|
651
|
-
|
|
652
|
-
Returns:
|
|
653
|
-
List of validated AttachmentProcessingInfo objects
|
|
654
|
-
"""
|
|
655
|
-
validated_attachments = []
|
|
656
|
-
|
|
657
|
-
for processed_attachment in processed_attachments_list:
|
|
658
|
-
attachment_data = processed_attachment["attachment"]
|
|
659
|
-
|
|
660
|
-
# Add context information to the attachment data
|
|
661
|
-
attachment_with_context = attachment_data.copy()
|
|
662
|
-
attachment_with_context["_context"] = processed_attachment["context"]
|
|
663
|
-
|
|
664
|
-
# Validate and convert to AttachmentProcessingInfo
|
|
665
|
-
if validate_attachment_func is not None:
|
|
666
|
-
validated = validate_attachment_func(attachment_with_context)
|
|
667
|
-
if validated:
|
|
668
|
-
validated_attachments.append(validated)
|
|
669
578
|
|
|
670
|
-
return
|
|
579
|
+
return default_path
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import types
|
|
2
|
+
import uuid
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
from sqlalchemy import Column, any_, bindparam
|
|
7
|
+
|
|
8
|
+
from fides.api.task.conditional_dependencies.schemas import Operator
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SQLTranslationError(Exception):
|
|
12
|
+
"""Error raised when SQL translation fails"""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _escape_like_pattern(val: Any) -> str:
|
|
16
|
+
"""
|
|
17
|
+
Escape LIKE wildcards in user input to prevent pattern injection attacks.
|
|
18
|
+
|
|
19
|
+
This prevents users from injecting wildcards (% and _) that could:
|
|
20
|
+
- Match unintended records through pattern injection
|
|
21
|
+
- Cause performance issues with expensive wildcard operations
|
|
22
|
+
- Enable information disclosure through pattern probing
|
|
23
|
+
|
|
24
|
+
Note: This function escapes ALL % and _ characters as a security measure.
|
|
25
|
+
While this may seem aggressive for normal field names, it's necessary because:
|
|
26
|
+
1. We can't distinguish between intentional and malicious wildcards
|
|
27
|
+
2. Field values shouldn't typically contain SQL wildcards
|
|
28
|
+
3. Better to be overly cautious with user input
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
val: User input value to escape
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Escaped string safe for use in LIKE patterns
|
|
35
|
+
|
|
36
|
+
Examples:
|
|
37
|
+
_escape_like_pattern("admin%") -> "admin\\%"
|
|
38
|
+
_escape_like_pattern("test_user") -> "test\\_user" # Escapes _ for security
|
|
39
|
+
_escape_like_pattern("normal") -> "normal"
|
|
40
|
+
"""
|
|
41
|
+
if val is None:
|
|
42
|
+
return ""
|
|
43
|
+
|
|
44
|
+
val_str = str(val)
|
|
45
|
+
# Escape backslashes first to prevent double-escaping
|
|
46
|
+
# Then escape LIKE wildcards (% and _)
|
|
47
|
+
return val_str.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _validate_and_escape_json_path_component(component: str) -> str:
|
|
51
|
+
"""
|
|
52
|
+
Validate and escape JSON path components to prevent injection attacks.
|
|
53
|
+
|
|
54
|
+
This prevents users from injecting malicious content in JSON path components that could:
|
|
55
|
+
- Break out of single quotes in PostgreSQL JSON operators
|
|
56
|
+
- Inject arbitrary SQL through malformed JSON paths
|
|
57
|
+
- Cause syntax errors or unexpected behavior
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
component: JSON path component to validate and escape
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Validated and escaped component safe for use in PostgreSQL JSON operators
|
|
64
|
+
|
|
65
|
+
Raises:
|
|
66
|
+
SQLTranslationError: If the component contains invalid characters
|
|
67
|
+
|
|
68
|
+
Examples:
|
|
69
|
+
_validate_and_escape_json_path_component("field") -> "field"
|
|
70
|
+
_validate_and_escape_json_path_component("field'name") -> "field''name"
|
|
71
|
+
_validate_and_escape_json_path_component("") -> raises SQLTranslationError
|
|
72
|
+
"""
|
|
73
|
+
if not component or not isinstance(component, str):
|
|
74
|
+
raise SQLTranslationError(
|
|
75
|
+
f"Invalid JSON path component: '{component}'. Components must be non-empty strings."
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Check for potentially dangerous characters
|
|
79
|
+
if len(component) > 100: # Reasonable limit for JSON field names
|
|
80
|
+
raise SQLTranslationError(
|
|
81
|
+
f"JSON path component too long: '{component[:50]}...'. Maximum length is 100 characters."
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Escape single quotes for PostgreSQL JSON operators (double them)
|
|
85
|
+
escaped_component = component.replace("'", "''")
|
|
86
|
+
|
|
87
|
+
return escaped_component
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _handle_list_contains(col: Column, val: Any) -> Column:
|
|
91
|
+
"""
|
|
92
|
+
Handle list_contains operator for different scenarios:
|
|
93
|
+
- If val is a list: check if column value is IN the list
|
|
94
|
+
- If val is a single value: check if column contains the value (for arrays/JSON) or use LIKE (for strings)
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
col: SQLAlchemy column
|
|
98
|
+
val: Value to compare against
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
SQLAlchemy expression for the comparison
|
|
102
|
+
"""
|
|
103
|
+
if isinstance(val, list):
|
|
104
|
+
# If value is a list, check if column value is IN the list
|
|
105
|
+
return col.in_(val)
|
|
106
|
+
|
|
107
|
+
# For single values, we need to handle different column types
|
|
108
|
+
# Check if this is a PostgreSQL array column
|
|
109
|
+
if hasattr(col.type, "item_type") or str(col.type).startswith("ARRAY"):
|
|
110
|
+
# This is a PostgreSQL array - use the ANY operator with proper parameter binding
|
|
111
|
+
# This is safer and prevents SQL injection
|
|
112
|
+
# Generate unique parameter name to avoid conflicts when multiple list operations exist in same query
|
|
113
|
+
unique_param_name = f"array_val_{uuid.uuid4().hex[:8]}"
|
|
114
|
+
param = bindparam(unique_param_name, val)
|
|
115
|
+
return param == any_(col)
|
|
116
|
+
|
|
117
|
+
# Try JSON containment for JSONB/JSON columns
|
|
118
|
+
try:
|
|
119
|
+
return col.contains(val)
|
|
120
|
+
except Exception:
|
|
121
|
+
# Fallback to LIKE for string columns with escaped wildcards
|
|
122
|
+
escaped_val = _escape_like_pattern(val)
|
|
123
|
+
return col.like(f"%{escaped_val}%", escape="\\")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
OPERATOR_MAP = types.MappingProxyType(
|
|
127
|
+
{
|
|
128
|
+
Operator.eq: lambda col, val: col == val,
|
|
129
|
+
Operator.neq: lambda col, val: col != val,
|
|
130
|
+
Operator.lt: lambda col, val: col < val,
|
|
131
|
+
Operator.lte: lambda col, val: col <= val,
|
|
132
|
+
Operator.gt: lambda col, val: col > val,
|
|
133
|
+
Operator.gte: lambda col, val: col >= val,
|
|
134
|
+
Operator.contains: lambda col, val: col.like(
|
|
135
|
+
f"%{_escape_like_pattern(val)}%", escape="\\"
|
|
136
|
+
),
|
|
137
|
+
Operator.starts_with: lambda col, val: col.like(
|
|
138
|
+
f"{_escape_like_pattern(val)}%", escape="\\"
|
|
139
|
+
),
|
|
140
|
+
Operator.ends_with: lambda col, val: col.like(
|
|
141
|
+
f"%{_escape_like_pattern(val)}", escape="\\"
|
|
142
|
+
),
|
|
143
|
+
Operator.exists: lambda col, val: col.isnot(None),
|
|
144
|
+
Operator.not_exists: lambda col, val: col.is_(None),
|
|
145
|
+
Operator.list_contains: _handle_list_contains,
|
|
146
|
+
Operator.not_in_list: lambda col, val: ~_handle_list_contains(col, val),
|
|
147
|
+
}
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class FieldAddress(BaseModel):
|
|
152
|
+
"""Parsed field address with table and column information"""
|
|
153
|
+
|
|
154
|
+
table_name: str = Field(description="Table name extracted from field address")
|
|
155
|
+
column_name: str = Field(description="Base column name without JSON path")
|
|
156
|
+
json_path: Optional[list[str]] = Field(
|
|
157
|
+
default=None, description="JSON path components if this is a JSON field"
|
|
158
|
+
)
|
|
159
|
+
full_address: str = Field(description="Original field address string")
|
|
160
|
+
|
|
161
|
+
def __hash__(self) -> int:
|
|
162
|
+
"""Make FieldAddress hashable for use in sets"""
|
|
163
|
+
return hash(
|
|
164
|
+
(
|
|
165
|
+
self.table_name,
|
|
166
|
+
self.column_name,
|
|
167
|
+
tuple(self.json_path) if self.json_path else None,
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def __eq__(self, other: Any) -> bool:
|
|
172
|
+
"""Enable equality comparison for FieldAddress objects"""
|
|
173
|
+
if not isinstance(other, FieldAddress):
|
|
174
|
+
return False
|
|
175
|
+
return (
|
|
176
|
+
self.table_name == other.table_name
|
|
177
|
+
and self.column_name == other.column_name
|
|
178
|
+
and self.json_path == other.json_path
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
def to_sql_column(self, enable_json_operators: bool = True) -> str:
|
|
182
|
+
"""
|
|
183
|
+
Convert field address to PostgreSQL column reference
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
enable_json_operators: Whether to use PostgreSQL JSON operators for JSON paths
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
SQL column reference string
|
|
190
|
+
"""
|
|
191
|
+
is_json_path = self.json_path is not None and len(self.json_path) > 0
|
|
192
|
+
if not is_json_path or not enable_json_operators:
|
|
193
|
+
return self.column_name
|
|
194
|
+
|
|
195
|
+
if self.json_path is None:
|
|
196
|
+
# This should never happen
|
|
197
|
+
raise SQLTranslationError(
|
|
198
|
+
"Field address internal error."
|
|
199
|
+
) # pragma: no cover
|
|
200
|
+
|
|
201
|
+
# Build PostgreSQL JSON path: column->'path'->'path'->>'final_path'
|
|
202
|
+
# Validate and escape all components to prevent injection
|
|
203
|
+
if self.json_path and len(self.json_path) == 1:
|
|
204
|
+
# Simple case: column->>'field'
|
|
205
|
+
escaped_component = _validate_and_escape_json_path_component(
|
|
206
|
+
self.json_path[0]
|
|
207
|
+
)
|
|
208
|
+
return f"{self.column_name}->>'{escaped_component}'"
|
|
209
|
+
|
|
210
|
+
# Complex case: column->'field'->'field'->>'final_field'
|
|
211
|
+
path_parts = []
|
|
212
|
+
# All but last use -> operator
|
|
213
|
+
for part in self.json_path[:-1]:
|
|
214
|
+
escaped_part = _validate_and_escape_json_path_component(part)
|
|
215
|
+
path_parts.append(f"->'{escaped_part}'")
|
|
216
|
+
# Last part uses ->> operator (returns text)
|
|
217
|
+
escaped_final = _validate_and_escape_json_path_component(self.json_path[-1])
|
|
218
|
+
path_parts.append(f"->>'{escaped_final}'")
|
|
219
|
+
|
|
220
|
+
return f"{self.column_name}{''.join(path_parts)}"
|
|
221
|
+
|
|
222
|
+
@classmethod
|
|
223
|
+
def _parse_parts_to_components(
|
|
224
|
+
cls, parts: list[str]
|
|
225
|
+
) -> tuple[str, str, Optional[list[str]]]:
|
|
226
|
+
"""
|
|
227
|
+
Parse a list of parts into table_name, column_name, and json_path components.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
parts: List of address parts (e.g., ["table", "column", "path1", "path2"])
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
Tuple of (table_name, column_name, json_path)
|
|
234
|
+
"""
|
|
235
|
+
if len(parts) < 2:
|
|
236
|
+
return "", parts[0] if parts else "", None
|
|
237
|
+
|
|
238
|
+
table_name = parts[0]
|
|
239
|
+
column_name = parts[1]
|
|
240
|
+
json_path = parts[2:] if len(parts) > 2 else None
|
|
241
|
+
|
|
242
|
+
return table_name, column_name, json_path
|
|
243
|
+
|
|
244
|
+
@classmethod
|
|
245
|
+
def parse(cls, field_address: str) -> "FieldAddress":
|
|
246
|
+
"""
|
|
247
|
+
Parse a field address into components
|
|
248
|
+
|
|
249
|
+
Supports formats:
|
|
250
|
+
- "table.column" -> table="table", column="column"
|
|
251
|
+
- "table.json_column.path.subpath" -> table="table", column="json_column", json_path=["path", "subpath"]
|
|
252
|
+
- "table:column" -> table="table", column="column" (alternative format)
|
|
253
|
+
- "column" -> table="", column="column" (requires default table)
|
|
254
|
+
"""
|
|
255
|
+
if ":" in field_address:
|
|
256
|
+
# Format: table:column or dataset:collection:field
|
|
257
|
+
# Handle colon-separated format first, then check for dots in the remaining parts
|
|
258
|
+
colon_parts = field_address.split(":")
|
|
259
|
+
if len(colon_parts) >= 2:
|
|
260
|
+
table_name = colon_parts[0]
|
|
261
|
+
remaining = ":".join(colon_parts[1:]) # Join remaining parts
|
|
262
|
+
|
|
263
|
+
# Check if the remaining part has dots (JSON path)
|
|
264
|
+
if "." in remaining:
|
|
265
|
+
# Mixed format: table:column.path1.path2
|
|
266
|
+
dot_parts = remaining.split(".")
|
|
267
|
+
parts = [table_name] + dot_parts
|
|
268
|
+
else:
|
|
269
|
+
# Pure colon format: table:column or table:column:path1:path2
|
|
270
|
+
parts = colon_parts
|
|
271
|
+
|
|
272
|
+
table_name, column_name, json_path = cls._parse_parts_to_components(
|
|
273
|
+
parts
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
return cls(
|
|
277
|
+
table_name=table_name,
|
|
278
|
+
column_name=column_name,
|
|
279
|
+
json_path=json_path,
|
|
280
|
+
full_address=field_address,
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
if "." in field_address:
|
|
284
|
+
# Format: table.column or table.json_column.path.subpath
|
|
285
|
+
parts = field_address.split(".")
|
|
286
|
+
table_name, column_name, json_path = cls._parse_parts_to_components(parts)
|
|
287
|
+
|
|
288
|
+
return cls(
|
|
289
|
+
table_name=table_name,
|
|
290
|
+
column_name=column_name,
|
|
291
|
+
json_path=json_path,
|
|
292
|
+
full_address=field_address,
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
# Simple column name without table
|
|
296
|
+
return cls(
|
|
297
|
+
table_name="", # Will need default table
|
|
298
|
+
column_name=field_address,
|
|
299
|
+
json_path=None,
|
|
300
|
+
full_address=field_address,
|
|
301
|
+
)
|