ethyca-fides 2.69.1b1__py2.py3-none-any.whl → 2.69.1rc0__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.
- {ethyca_fides-2.69.1b1.dist-info → ethyca_fides-2.69.1rc0.dist-info}/METADATA +2 -2
- {ethyca_fides-2.69.1b1.dist-info → ethyca_fides-2.69.1rc0.dist-info}/RECORD +241 -232
- fides/_version.py +3 -3
- fides/api/alembic/migrations/versions/78dbe23d8204_adding_privacy_request_redaction_patterns.py +52 -0
- fides/api/api/v1/api.py +2 -0
- fides/api/api/v1/endpoints/dsr_package_link.py +5 -3
- fides/api/api/v1/endpoints/oauth_endpoints.py +19 -7
- fides/api/api/v1/endpoints/privacy_request_redaction_patterns_endpoints.py +95 -0
- fides/api/api/v1/endpoints/user_endpoints.py +27 -4
- fides/api/app_setup.py +16 -2
- fides/api/db/base.py +3 -0
- fides/api/main.py +22 -0
- fides/api/models/client.py +1 -0
- fides/api/models/privacy_request_redaction_pattern.py +64 -0
- fides/api/oauth/utils.py +126 -12
- fides/api/schemas/privacy_request_redaction_patterns.py +55 -0
- fides/api/service/privacy_request/dsr_package/dsr_data_preprocessor.py +231 -0
- fides/api/service/privacy_request/dsr_package/dsr_report_builder.py +286 -120
- fides/api/service/privacy_request/dsr_package/templates/attachments_index.html +4 -2
- fides/api/service/privacy_request/dsr_package/templates/collection_index.html +3 -1
- fides/api/service/privacy_request/dsr_package/templates/dataset_index.html +1 -1
- fides/api/service/privacy_request/dsr_package/utils.py +268 -0
- fides/api/service/privacy_request/request_runner_service.py +8 -2
- fides/api/service/privacy_request/request_service.py +18 -1
- fides/api/service/storage/storage_uploader_service.py +1 -21
- fides/api/service/storage/streaming/dsr_storage.py +1 -5
- fides/api/service/storage/streaming/s3/s3_storage_client.py +78 -40
- fides/api/service/storage/streaming/s3/streaming_s3.py +9 -21
- fides/api/service/storage/streaming/smart_open_client.py +8 -7
- fides/api/service/storage/streaming/smart_open_streaming_storage.py +110 -197
- fides/api/service/storage/streaming/storage_client_factory.py +7 -3
- fides/api/service/storage/util.py +579 -0
- fides/api/task/manual/manual_task_graph_task.py +11 -9
- fides/api/tasks/storage.py +2 -2
- fides/api/util/endpoint_utils.py +0 -13
- fides/api/util/rate_limit.py +194 -0
- fides/common/api/scope_registry.py +8 -0
- fides/common/api/v1/urn_registry.py +3 -0
- fides/config/redis_settings.py +27 -3
- fides/config/security_settings.py +21 -6
- fides/ui-build/static/admin/404.html +1 -1
- fides/ui-build/static/admin/_next/static/OmXHlY9MvjoZH9jDkAytl/_buildManifest.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{1345-04e37a66c0d40dc1.js → 1345-5e1c5b66e25c566e.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{3729-f5f2976904dce90d.js → 3729-c17ac8031a4c4fd1.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{3847-2c0126e6eb54c526.js → 3847-230bf61b053bc706.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{3855-9dd54ded74f4036b.js → 3855-ef5194cdb228beb6.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/4164-355644b916ae0094.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{4608-d101417a3abb4ad6.js → 4608-be8cba73f5d7c326.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{4786-7aafb744445445b2.js → 4786-61154adf88e448e1.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{4808-357ca7ea7bbd24c6.js → 4808-dd4157aa72648068.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/4831-fd99c0b3784de128.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{4844-707b20a6c4b127cc.js → 4844-46324c3d848b8b6a.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{5258-1a8b9f66b97761fc.js → 5258-b0de22a8521686ab.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{9046-058a4d8f0b5e08f9.js → 9046-712156d461165f56.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{9676.b7d5d1d90b9da224.js → 9676.9fd9552ef744c717.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{9951-b954027a046ce553.js → 9951-a88367a129b724ba.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{_app-7612a768b9a6304b.js → _app-fcdad91f6f66292b.js} +2 -2
- fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/{manual-20253dd047fb9736.js → manual-ace203dfacacbdc4.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{add-systems-169099ff7b305cf5.js → add-systems-bd0d82078e67cac3.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/reporting-28b192e2c074b0f3.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/[projectUrn]/{[resourceUrn]-adc500a03e239857.js → [resourceUrn]-2c29ff7a01198f30.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/resources/{[resourceUrn]-c8b3d090e4ba60d3.js → [resourceUrn]-8eb581024bc0172f.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/{[systemId]-cfb0b1200bc1a2c9.js → [systemId]-e1ba213fb666b3f4.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/{[monitorId]-fed8b879c13c2bf3.js → [monitorId]-6d133580045abdda.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{activity-58a110542d6bcd0f.js → activity-b6ae7adb8ef0b525.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/detection/{[resourceUrn]-22eec362dfbb1d2a.js → [resourceUrn]-8f736b078e9842da.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{detection-4decce5ef996e563.js → detection-eb814e3c22807871.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/discovery/{[resourceUrn]-01acdd1ad492fd89.js → [resourceUrn]-6875b7783fcfda2f.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{discovery-85fdbf4cde60d910.js → discovery-172dbd7740e212ca.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{datamap-60e27b673c68bd27.js → datamap-c7390e046b2e2b7f.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/{new-2c7b1213b6604d30.js → new-e32fccc4ca520d2b.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/{[id]-7ed3f05700dc397b.js → [id]-927b7e476c4b47d0.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/{new-95f6d64f84fc6bf3.js → new-cbe100d50df34285.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations/{[id]-02ff0dd9d8f1d719.js → [id]-4c3c413a2668df53.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{integrations-0f12d5b658c98c9f.js → integrations-95402b5001c07ef2.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/{[id]-b379873a5771e55b.js → [id]-0f25a76dd18c5e20.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/{messaging-a9bb257906dcd7e0.js → messaging-ad6ad3e5bd72765d.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/{storage-de0036c74b78e9b7.js → storage-6032d82f0fc2893d.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{privacy-requests-91f578139548652c.js → privacy-requests-baf31c3e4b081046.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/reporting/{datamap-83019a6753e14857.js → datamap-6903f42a0412bfa6.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/{custom-fields-74f0fe9656f4a1f6.js → custom-fields-9ecb803099082bf4.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/privacy-requests-2ecc073f41628f62.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/{[id]-e91da49c0681a300.js → [id]-6e15332935f6b538.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{taxonomy-78c3a5200d362cff.js → taxonomy-4d7827fc9c46b6b8.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/user-management/{new-de8cb3739ab99c09.js → new-92f52c43f522a350.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/user-management/profile/{[id]-4f9001870e2b3aff.js → [id]-64452dfae2c5e614.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{webpack-b5eb3e1da37616d2.js → webpack-678e89d68dbcd94f.js} +1 -1
- 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/messaging.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.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/organization.html +1 -1
- fides/ui-build/static/admin/settings/privacy-requests.html +1 -0
- 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/LqjE2O1xWlQKqW38Sy4m3/_buildManifest.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/4121-bd6f467aacd80f91.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/768-7eac4b30d7477b0a.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/reporting-60e3394c887f3d5e.js +0 -1
- {ethyca_fides-2.69.1b1.dist-info → ethyca_fides-2.69.1rc0.dist-info}/WHEEL +0 -0
- {ethyca_fides-2.69.1b1.dist-info → ethyca_fides-2.69.1rc0.dist-info}/entry_points.txt +0 -0
- {ethyca_fides-2.69.1b1.dist-info → ethyca_fides-2.69.1rc0.dist-info}/licenses/LICENSE +0 -0
- {ethyca_fides-2.69.1b1.dist-info → ethyca_fides-2.69.1rc0.dist-info}/top_level.txt +0 -0
- /fides/ui-build/static/admin/_next/static/{LqjE2O1xWlQKqW38Sy4m3 → OmXHlY9MvjoZH9jDkAytl}/_ssgManifest.js +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{1099-c34f76b4da5f3d15.js → 1099-79646e64f26d62fa.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{1817-e6934e258111a961.js → 1817-0ca16d288fad916d.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{2921-0696287bb8de1f0b.js → 2921-52328140bc420d0f.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{3620-8c0ee3d0b19c342d.js → 3620-602eb74dc896d556.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{3872-72ea3eb040366277.js → 3872-f78dec02f0d959ae.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{3923-c4f2b03706ddbe39.js → 3923-bb2417b8dcade7a4.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{401-959a15ed18a8abdf.js → 401-4af0a912e249d30f.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{5487-fd9724519f31caff.js → 5487-02d00bad7c6830e0.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{549-d3bef0990071230c.js → 549-38ea1d91ee2addaa.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{6084-487d27d017c45e05.js → 6084-c153669d5567e242.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{6853-f7ab74e30abbdeaf.js → 6853-b17673391117c976.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{6954-b0a7b8ac6db238f8.js → 6954-5296188c19d7d0ac.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{7476-9a57db910472b48e.js → 7476-45c5088baa8b66af.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{7630-46807321449479c7.js → 7630-7ed6c6117775dffe.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{787-79e8e558bd80ece6.js → 787-a8c7eab617e2fceb.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{79-3e5047415bee9391.js → 79-65674011d455af4d.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{796-fb2af44165f37ecc.js → 796-9e1ca1a4030707c5.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{8002-c1f66179adabece8.js → 8002-24af20d679efc04e.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{9826-0d9a7f61c08ed88a.js → 9826-dbae8dee941a7fac.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{404-9c9efb820bb6b432.js → 404-471a6b18e712f050.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/{multiple-caadc3c0e86a7bfe.js → multiple-920fb469e0dda1d2.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/configure/{add-vendors-acefb9e08c6c4082.js → add-vendors-406170eaae4329c6.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{configure-558fbb1667473abb.js → configure-7207ab23bdb36ce8.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience/{[id]-b1ff1c9683841815.js → [id]-f80cf2d3966816fd.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{privacy-experience-674906d2d9a0a3a6.js → privacy-experience-9dda4de5ec580279.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{[id]-5b7aa7971f070513.js → [id]-b378576cba255609.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{new-285958a12a66575e.js → new-2ca1de7b88094ab0.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{privacy-notices-dc56bbdb6dd7a670.js → privacy-notices-0d4844d0b808e6e4.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{properties-0843757f00eeaaec.js → properties-226efa1dcd41437f.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{consent-5cba58ebecb4511f.js → consent-3e8bdefe714254ec.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/{[projectUrn]-0f90cac9f190130c.js → [projectUrn]-04cfe2cfba7b7cd8.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/{projects-f9117645c941342c.js → projects-5f2d7b24804f861f.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/{resources-6c3714ee97a718c1.js → resources-de704de849960f01.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{data-catalog-c566640eecc6f3fe.js → data-catalog-30108b00ac769fc3.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{action-center-991659e916ad60b1.js → action-center-9a81d42a474e1e48.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/[collectionName]/{[...subfieldNames]-3f4ba87513e030a2.js → [...subfieldNames]-dfd71c1e9c458b89.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/{[collectionName]-a6cd31103deba465.js → [collectionName]-7cdc42ec5493b83d.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/{[datasetId]-491d2c8dd559d0cb.js → [datasetId]-e12b11ba15bc3fc1.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{dataset-c8bcd568d3b0ee7f.js → dataset-7c59a6abf6ba6207.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{datastore-connection-9a33a412a5f36960.js → datastore-connection-cce20440b177050b.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{index-1299410f671fac23.js → index-6cd8708106331b8d.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/messaging/{[id]-ba232c4b17576ccb.js → [id]-3c6dc2f6e6bae960.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/messaging/{add-template-b97883026fbc5b1e.js → add-template-4a6d4023a7791be8.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{messaging-38189c1058aa4acf.js → messaging-76b204c9b98d656f.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/poc/{ant-components-3119bdb3811409bf.js → ant-components-bc0e2adf6e0d3ac7.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/{AntForm-8f9d0434dc3eb8cf.js → AntForm-86ffcc1ad3fa912e.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/{FormikAntFormItem-a14e51c7f22f3395.js → FormikAntFormItem-ec04f595465bdf69.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/{FormikControlled-a40645b57822684d.js → FormikControlled-41d309754ff0c1de.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/{FormikField-c55df6527b700701.js → FormikField-cab1f78cec7808f9.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/poc/{forms-292d761616b162a0.js → forms-eb6058221403b156.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/poc/{table-migration-135bcf384b81820a.js → table-migration-48500551fd6a7602.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/{configure-70efccbe0732786b.js → configure-d83e5bd52a638234.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/properties/{[id]-63c6f0193634add5.js → [id]-e784c05d056b2371.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/properties/{add-property-e76567f0374d5912.js → add-property-0a7a2db148a7561a.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{properties-2050b7dac0413c11.js → properties-da734840e4f9d04b.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/about/{alpha-913f8eab62460f31.js → alpha-a82f3df840d5c1b5.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{about-2ed1ee6017c0656a.js → about-d06fb16487705b9d.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent/[configuration_id]/{[purpose_id]-bffd6292e7e0302a.js → [purpose_id]-9495e2eb506606c7.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{consent-e54b4a7d80b81800.js → consent-93a978443bf299db.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{domain-records-a649ca07ce51b0fe.js → domain-records-16fdd91a81074dd1.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{domains-eb675ba600cea2a8.js → domains-4cdd6001e7cb9aee.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{email-templates-4db2f42f90867890.js → email-templates-1914de830ce5cfc4.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{locations-89b404989b4ea21c.js → locations-2e635dcd11b78224.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{organization-eb1ecff37fd85c72.js → organization-f547f1f33c12faf3.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{regulations-07d883b5aaec58f2.js → regulations-7c02e469d8c5bd74.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/[id]/{test-datasets-61ecb7546830c345.js → test-datasets-20b1193ed76c56b0.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{systems-44d714017dd87e62.js → systems-fbc8761ef4d55516.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{user-management-583a7f0c1bead240.js → user-management-9cec020f89544426.js} +0 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
from typing import Any, List, Optional
|
|
2
|
+
|
|
3
|
+
from fideslang.models import Dataset, DatasetField
|
|
4
|
+
from loguru import logger
|
|
5
|
+
from sqlalchemy import text
|
|
6
|
+
from sqlalchemy.orm import Session
|
|
7
|
+
|
|
8
|
+
from fides.api.models.datasetconfig import DatasetConfig
|
|
9
|
+
from fides.api.models.privacy_request.privacy_request import PrivacyRequest
|
|
10
|
+
from fides.api.schemas.policy import ActionType
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# TODO: keeping this for a bit to help with development and testing
|
|
14
|
+
def get_redaction_entities_map(db: Session) -> set[str]:
|
|
15
|
+
"""
|
|
16
|
+
Create a set of hierarchical entity keys that should be redacted based on fides_meta.redact: name.
|
|
17
|
+
|
|
18
|
+
This utility function reads all enabled dataset configurations from the database
|
|
19
|
+
and builds a set of hierarchical entity keys (dataset_name, dataset_name.collection_name,
|
|
20
|
+
dataset_name.collection_name.field_name) that have fides_meta.redact set to "name".
|
|
21
|
+
|
|
22
|
+
Supports deeply nested field structures with unlimited nesting depth.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
db: Database session
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Set of hierarchical entity keys that should be redacted
|
|
29
|
+
"""
|
|
30
|
+
redaction_entities = set()
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
dataset_configs = DatasetConfig.all(db=db)
|
|
34
|
+
|
|
35
|
+
for dataset_config in dataset_configs:
|
|
36
|
+
ctl_dataset = dataset_config.ctl_dataset
|
|
37
|
+
if not ctl_dataset:
|
|
38
|
+
continue
|
|
39
|
+
|
|
40
|
+
dataset = Dataset.model_validate(dataset_config.ctl_dataset)
|
|
41
|
+
# Intentionally using the fides_key instead of name since it's always provided
|
|
42
|
+
dataset_name = dataset.fides_key
|
|
43
|
+
|
|
44
|
+
# Check dataset level
|
|
45
|
+
if dataset.fides_meta and dataset.fides_meta.redact == "name":
|
|
46
|
+
redaction_entities.add(dataset_name)
|
|
47
|
+
|
|
48
|
+
# Check collection level
|
|
49
|
+
for collection_dict in dataset.collections:
|
|
50
|
+
# Collections are stored as dictionaries in the database
|
|
51
|
+
collection_name = collection_dict.name
|
|
52
|
+
if not collection_name:
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
collection_path = f"{dataset_name}.{collection_name}"
|
|
56
|
+
collection_fides_meta = collection_dict.fides_meta
|
|
57
|
+
|
|
58
|
+
if collection_fides_meta and collection_fides_meta.redact == "name":
|
|
59
|
+
redaction_entities.add(collection_path)
|
|
60
|
+
|
|
61
|
+
# Check field level (with recursive nested field support)
|
|
62
|
+
_traverse_fields_for_redaction(
|
|
63
|
+
collection_dict.fields, collection_path, redaction_entities
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
except Exception as exc:
|
|
67
|
+
# Log error but don't fail, just return empty set
|
|
68
|
+
logger.warning(f"Error extracting redaction configurations: {exc}")
|
|
69
|
+
|
|
70
|
+
return redaction_entities
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_redaction_entities_map_db(db: Session) -> set[str]:
|
|
74
|
+
"""
|
|
75
|
+
Create a set of hierarchical entity keys that should be redacted based on fides_meta.redact: name.
|
|
76
|
+
|
|
77
|
+
This function uses a hybrid approach:
|
|
78
|
+
1. First identifies datasets that contain ANY redaction metadata at any level
|
|
79
|
+
2. Then processes only those datasets with redaction metadata
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
db: Database session
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Set of hierarchical entity keys that should be redacted
|
|
87
|
+
"""
|
|
88
|
+
redaction_entities: set[str] = set()
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
# Step 1: Pre-filter to find datasets with ANY redaction metadata
|
|
92
|
+
# Simple existence check - no paths needed, just check if redaction exists anywhere
|
|
93
|
+
pre_filter_query = """
|
|
94
|
+
SELECT DISTINCT dc.ctl_dataset_id
|
|
95
|
+
FROM datasetconfig dc
|
|
96
|
+
JOIN ctl_datasets ds ON dc.ctl_dataset_id = ds.id
|
|
97
|
+
WHERE
|
|
98
|
+
-- Dataset-level redaction
|
|
99
|
+
ds.fides_meta->>'redact' = 'name'
|
|
100
|
+
OR
|
|
101
|
+
-- Collection-level redaction
|
|
102
|
+
EXISTS (
|
|
103
|
+
SELECT 1 FROM jsonb_array_elements(ds.collections::jsonb) AS collection
|
|
104
|
+
WHERE collection->'fides_meta'->>'redact' = 'name'
|
|
105
|
+
LIMIT 1
|
|
106
|
+
)
|
|
107
|
+
OR
|
|
108
|
+
-- Field-level redaction using jsonb_path_query
|
|
109
|
+
EXISTS (
|
|
110
|
+
SELECT 1
|
|
111
|
+
FROM jsonb_path_query(ds.collections::jsonb, '$.**.fides_meta') AS fides_meta
|
|
112
|
+
WHERE fides_meta->>'redact' = 'name'
|
|
113
|
+
LIMIT 1
|
|
114
|
+
)
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
candidate_datasets = db.execute(pre_filter_query).fetchall()
|
|
118
|
+
|
|
119
|
+
if not candidate_datasets:
|
|
120
|
+
logger.debug("No datasets found with redaction metadata")
|
|
121
|
+
return redaction_entities
|
|
122
|
+
|
|
123
|
+
logger.debug(
|
|
124
|
+
f"Pre-filtered to {len(candidate_datasets)} datasets with redaction metadata"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Step 2: Process only the candidate datasets with targeted queries
|
|
128
|
+
# Convert to a format we can use in SQL ANY clause
|
|
129
|
+
dataset_ids = [row[0] for row in candidate_datasets]
|
|
130
|
+
|
|
131
|
+
# Query for dataset-level redactions (only on candidate datasets)
|
|
132
|
+
dataset_query = text(
|
|
133
|
+
"""
|
|
134
|
+
SELECT ds.fides_key as entity_path
|
|
135
|
+
FROM datasetconfig dc
|
|
136
|
+
JOIN ctl_datasets ds ON dc.ctl_dataset_id = ds.id
|
|
137
|
+
WHERE ds.id = ANY(:dataset_ids)
|
|
138
|
+
AND ds.fides_meta->>'redact' = 'name'
|
|
139
|
+
"""
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
dataset_results = db.execute(
|
|
143
|
+
dataset_query, {"dataset_ids": dataset_ids}
|
|
144
|
+
).fetchall()
|
|
145
|
+
for row in dataset_results:
|
|
146
|
+
redaction_entities.add(row[0])
|
|
147
|
+
|
|
148
|
+
# Query for collection-level redactions (only on candidate datasets)
|
|
149
|
+
collection_query = text(
|
|
150
|
+
"""
|
|
151
|
+
SELECT ds.fides_key || '.' || (collection->>'name') as entity_path
|
|
152
|
+
FROM datasetconfig dc
|
|
153
|
+
JOIN ctl_datasets ds ON dc.ctl_dataset_id = ds.id
|
|
154
|
+
CROSS JOIN LATERAL jsonb_array_elements(ds.collections::jsonb) AS collection
|
|
155
|
+
WHERE ds.id = ANY(:dataset_ids)
|
|
156
|
+
AND collection->'fides_meta'->>'redact' = 'name'
|
|
157
|
+
AND collection->>'name' IS NOT NULL
|
|
158
|
+
"""
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
collection_results = db.execute(
|
|
162
|
+
collection_query, {"dataset_ids": dataset_ids}
|
|
163
|
+
).fetchall()
|
|
164
|
+
for row in collection_results:
|
|
165
|
+
redaction_entities.add(row[0])
|
|
166
|
+
|
|
167
|
+
# Query for field-level redactions (including nested fields)
|
|
168
|
+
# This uses a recursive CTE to handle arbitrary nesting levels
|
|
169
|
+
field_query = text(
|
|
170
|
+
"""
|
|
171
|
+
WITH RECURSIVE field_hierarchy AS (
|
|
172
|
+
-- Base case: top-level fields in collections (only candidate datasets)
|
|
173
|
+
SELECT
|
|
174
|
+
ds.fides_key || '.' ||
|
|
175
|
+
(collection->>'name') || '.' ||
|
|
176
|
+
(field->>'name') as entity_path,
|
|
177
|
+
field->'fields' as nested_fields,
|
|
178
|
+
field->'fides_meta'->>'redact' as redact_value
|
|
179
|
+
FROM datasetconfig dc
|
|
180
|
+
JOIN ctl_datasets ds ON dc.ctl_dataset_id = ds.id
|
|
181
|
+
CROSS JOIN LATERAL jsonb_array_elements(ds.collections::jsonb) AS collection
|
|
182
|
+
CROSS JOIN LATERAL jsonb_array_elements(collection->'fields') AS field
|
|
183
|
+
WHERE ds.id = ANY(:dataset_ids)
|
|
184
|
+
AND collection->>'name' IS NOT NULL
|
|
185
|
+
AND field->>'name' IS NOT NULL
|
|
186
|
+
|
|
187
|
+
UNION ALL
|
|
188
|
+
|
|
189
|
+
-- Recursive case: nested fields
|
|
190
|
+
SELECT
|
|
191
|
+
fh.entity_path || '.' || (nested_field->>'name') as entity_path,
|
|
192
|
+
nested_field->'fields' as nested_fields,
|
|
193
|
+
nested_field->'fides_meta'->>'redact' as redact_value
|
|
194
|
+
FROM field_hierarchy fh
|
|
195
|
+
CROSS JOIN LATERAL jsonb_array_elements(fh.nested_fields) AS nested_field
|
|
196
|
+
WHERE jsonb_typeof(fh.nested_fields) = 'array'
|
|
197
|
+
AND nested_field->>'name' IS NOT NULL
|
|
198
|
+
)
|
|
199
|
+
SELECT DISTINCT entity_path
|
|
200
|
+
FROM field_hierarchy
|
|
201
|
+
WHERE redact_value = 'name'
|
|
202
|
+
"""
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
field_results = db.execute(field_query, {"dataset_ids": dataset_ids}).fetchall()
|
|
206
|
+
for row in field_results:
|
|
207
|
+
redaction_entities.add(row[0])
|
|
208
|
+
|
|
209
|
+
logger.debug(f"Found {len(redaction_entities)} entities requiring redaction")
|
|
210
|
+
|
|
211
|
+
except Exception as exc:
|
|
212
|
+
# Log error but don't fail, just return empty set
|
|
213
|
+
logger.warning(
|
|
214
|
+
f"Error extracting redaction configurations from database: {exc}"
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
return redaction_entities
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def map_privacy_request(privacy_request: PrivacyRequest) -> dict[str, Any]:
|
|
221
|
+
"""Creates a map with a subset of values from the privacy request"""
|
|
222
|
+
request_data: dict[str, Any] = {}
|
|
223
|
+
request_data["id"] = privacy_request.id
|
|
224
|
+
|
|
225
|
+
action_type: Optional[ActionType] = privacy_request.policy.get_action_type()
|
|
226
|
+
if action_type:
|
|
227
|
+
request_data["type"] = action_type.value
|
|
228
|
+
|
|
229
|
+
request_data["identity"] = {
|
|
230
|
+
key: value
|
|
231
|
+
for key, value in privacy_request.get_persisted_identity()
|
|
232
|
+
.labeled_dict(include_default_labels=True)
|
|
233
|
+
.items()
|
|
234
|
+
if value["value"] is not None
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if privacy_request.requested_at:
|
|
238
|
+
request_data["requested_at"] = privacy_request.requested_at.strftime(
|
|
239
|
+
"%m/%d/%Y %H:%M %Z"
|
|
240
|
+
)
|
|
241
|
+
return request_data
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _traverse_fields_for_redaction(
|
|
245
|
+
fields: List[DatasetField], current_path: str, redaction_entities: set[str]
|
|
246
|
+
) -> None:
|
|
247
|
+
"""
|
|
248
|
+
Recursively traverse nested fields to find redaction entities.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
fields: List of field dictionaries to traverse
|
|
252
|
+
current_path: Current hierarchical path (e.g., "dataset.collection")
|
|
253
|
+
redaction_entities: Set to add redacted field paths to
|
|
254
|
+
"""
|
|
255
|
+
for field in fields:
|
|
256
|
+
field_name = field.name
|
|
257
|
+
if not field_name:
|
|
258
|
+
continue
|
|
259
|
+
|
|
260
|
+
field_path = f"{current_path}.{field_name}"
|
|
261
|
+
field_fides_meta = field.fides_meta
|
|
262
|
+
|
|
263
|
+
if field_fides_meta and field_fides_meta.redact == "name":
|
|
264
|
+
redaction_entities.add(field_path)
|
|
265
|
+
|
|
266
|
+
# Recursively check nested fields
|
|
267
|
+
if field.fields:
|
|
268
|
+
_traverse_fields_for_redaction(field.fields, field_path, redaction_entities)
|
|
@@ -312,8 +312,14 @@ def upload_and_save_access_results( # pylint: disable=R0912
|
|
|
312
312
|
loaded_attachments = [
|
|
313
313
|
attachment
|
|
314
314
|
for attachment in privacy_request.attachments
|
|
315
|
-
if
|
|
316
|
-
|
|
315
|
+
if not any(
|
|
316
|
+
ref.reference_type
|
|
317
|
+
in [
|
|
318
|
+
AttachmentReferenceType.access_manual_webhook,
|
|
319
|
+
AttachmentReferenceType.manual_task_submission,
|
|
320
|
+
]
|
|
321
|
+
for ref in attachment.references
|
|
322
|
+
)
|
|
317
323
|
]
|
|
318
324
|
attachments = get_attachments_content(loaded_attachments)
|
|
319
325
|
# Process attachments once for both upload and storage
|
|
@@ -540,6 +540,7 @@ def _get_request_task_ids_in_progress(
|
|
|
540
540
|
|
|
541
541
|
|
|
542
542
|
# pylint: disable=too-many-branches
|
|
543
|
+
# pylint: disable=too-many-statements
|
|
543
544
|
@celery_app.task(base=DatabaseTask, bind=True)
|
|
544
545
|
def requeue_interrupted_tasks(self: DatabaseTask) -> None:
|
|
545
546
|
"""
|
|
@@ -660,8 +661,24 @@ def requeue_interrupted_tasks(self: DatabaseTask) -> None:
|
|
|
660
661
|
break
|
|
661
662
|
|
|
662
663
|
# If the task ID is not cached, we can't check if it's running
|
|
663
|
-
# This means the subtask is stuck -
|
|
664
|
+
# This means the subtask is stuck - but we need to handle this differently
|
|
665
|
+
# based on the privacy request status
|
|
664
666
|
if not subtask_id:
|
|
667
|
+
if (
|
|
668
|
+
privacy_request.status
|
|
669
|
+
== PrivacyRequestStatus.requires_input
|
|
670
|
+
):
|
|
671
|
+
# For requires_input status, don't automatically error the request
|
|
672
|
+
# as it's intentionally waiting for user input
|
|
673
|
+
logger.warning(
|
|
674
|
+
f"No task ID found for request task {request_task_id} "
|
|
675
|
+
f"(privacy request {privacy_request.id}) in requires_input status - "
|
|
676
|
+
f"keeping request in current status as it may be waiting for manual input"
|
|
677
|
+
)
|
|
678
|
+
should_requeue = False
|
|
679
|
+
break
|
|
680
|
+
|
|
681
|
+
# For other statuses, cancel the entire privacy request
|
|
665
682
|
_cancel_interrupted_tasks_and_error_privacy_request(
|
|
666
683
|
db,
|
|
667
684
|
privacy_request,
|
|
@@ -51,13 +51,7 @@ def upload(
|
|
|
51
51
|
config.secrets is not None,
|
|
52
52
|
)
|
|
53
53
|
|
|
54
|
-
if config.secrets:
|
|
55
|
-
logger.debug("Storage config secrets type: {}", type(config.secrets))
|
|
56
|
-
if isinstance(config.secrets, dict):
|
|
57
|
-
logger.debug("Storage config secrets keys: {}", list(config.secrets.keys()))
|
|
58
|
-
else:
|
|
59
|
-
logger.debug("Storage config secrets is not a dict: {}", config.secrets)
|
|
60
|
-
else:
|
|
54
|
+
if not config.secrets:
|
|
61
55
|
logger.warning("Storage config has no secrets!")
|
|
62
56
|
|
|
63
57
|
uploader: Any = _get_uploader_from_config_type(config.type) # type: ignore
|
|
@@ -130,19 +124,6 @@ def _s3_uploader(
|
|
|
130
124
|
config.secrets is not None,
|
|
131
125
|
)
|
|
132
126
|
|
|
133
|
-
if config.secrets:
|
|
134
|
-
logger.debug(
|
|
135
|
-
"Config secrets keys: {}",
|
|
136
|
-
(
|
|
137
|
-
list(config.secrets.keys())
|
|
138
|
-
if isinstance(config.secrets, dict)
|
|
139
|
-
else "Not a dict"
|
|
140
|
-
),
|
|
141
|
-
)
|
|
142
|
-
logger.debug("Config secrets type: {}", type(config.secrets))
|
|
143
|
-
else:
|
|
144
|
-
logger.warning("Config secrets is None or empty!")
|
|
145
|
-
|
|
146
127
|
enable_streaming = config.details.get(StorageDetails.ENABLE_STREAMING.value, False)
|
|
147
128
|
file_key: str = _construct_file_key(privacy_request.id, config, enable_streaming)
|
|
148
129
|
|
|
@@ -154,7 +135,6 @@ def _s3_uploader(
|
|
|
154
135
|
file_key = f"{privacy_request.id}.zip"
|
|
155
136
|
# Use streaming upload for better memory efficiency
|
|
156
137
|
logger.debug("Using streaming S3 upload for {}", file_key)
|
|
157
|
-
logger.debug("Calling upload_to_s3_streaming with secrets: {}", config.secrets)
|
|
158
138
|
return upload_to_s3_streaming(
|
|
159
139
|
config.secrets, # type: ignore
|
|
160
140
|
data,
|
|
@@ -17,7 +17,6 @@ def stream_dsr_buffer_to_storage(
|
|
|
17
17
|
bucket_name: str,
|
|
18
18
|
file_key: str,
|
|
19
19
|
dsr_buffer: BytesIO,
|
|
20
|
-
content_type: str = "application/zip",
|
|
21
20
|
) -> None:
|
|
22
21
|
"""Stream DSR buffer to storage using smart-open streaming.
|
|
23
22
|
|
|
@@ -29,15 +28,12 @@ def stream_dsr_buffer_to_storage(
|
|
|
29
28
|
bucket_name: Storage bucket name
|
|
30
29
|
file_key: File key in storage
|
|
31
30
|
dsr_buffer: Pre-generated DSR report buffer (BytesIO)
|
|
32
|
-
content_type: MIME type for the uploaded file (defaults to application/zip)
|
|
33
31
|
"""
|
|
34
32
|
# Get the content from the buffer
|
|
35
33
|
content = dsr_buffer.getvalue()
|
|
36
34
|
try:
|
|
37
35
|
# Use smart-open's streaming upload for efficient memory usage
|
|
38
|
-
with storage_client.stream_upload(
|
|
39
|
-
bucket_name, file_key, content_type=content_type
|
|
40
|
-
) as upload_stream:
|
|
36
|
+
with storage_client.stream_upload(bucket_name, file_key) as upload_stream:
|
|
41
37
|
upload_stream.write(content)
|
|
42
38
|
|
|
43
39
|
except Exception as e:
|
|
@@ -7,7 +7,7 @@ from typing import Any, Optional
|
|
|
7
7
|
from fideslang.validation import AnyHttpUrlString
|
|
8
8
|
from loguru import logger
|
|
9
9
|
|
|
10
|
-
from fides.api.schemas.storage.storage import AWSAuthMethod
|
|
10
|
+
from fides.api.schemas.storage.storage import AWSAuthMethod
|
|
11
11
|
from fides.api.service.storage.s3 import create_presigned_url_for_s3
|
|
12
12
|
from fides.api.service.storage.streaming.base_storage_client import BaseStorageClient
|
|
13
13
|
from fides.api.util.aws_util import get_s3_client
|
|
@@ -20,17 +20,19 @@ class S3StorageClient(BaseStorageClient):
|
|
|
20
20
|
generation for the smart-open storage client.
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
|
-
def __init__(self, storage_secrets: dict[
|
|
23
|
+
def __init__(self, auth_method: str, storage_secrets: dict[str, Any]):
|
|
24
24
|
"""Initialize the storage client with secrets.
|
|
25
25
|
|
|
26
26
|
Args:
|
|
27
|
-
storage_secrets: Provider-specific storage credentials and configuration using
|
|
27
|
+
storage_secrets: Provider-specific storage credentials and configuration using string keys
|
|
28
|
+
(e.g., "aws_access_key_id", "region_name") from format_secrets()
|
|
28
29
|
"""
|
|
29
30
|
super().__init__(storage_secrets)
|
|
30
|
-
self.storage_secrets: dict[
|
|
31
|
+
self.storage_secrets: dict[str, Any] = storage_secrets
|
|
32
|
+
self.auth_method = auth_method
|
|
31
33
|
|
|
32
34
|
def build_uri(self, bucket: str, key: str) -> str:
|
|
33
|
-
"""Build
|
|
35
|
+
"""Build S3 URI for the given bucket and key.
|
|
34
36
|
|
|
35
37
|
Args:
|
|
36
38
|
bucket: S3 bucket name
|
|
@@ -45,26 +47,75 @@ class S3StorageClient(BaseStorageClient):
|
|
|
45
47
|
|
|
46
48
|
def get_transport_params(self) -> dict[str, Any]:
|
|
47
49
|
"""Get S3-specific transport parameters for smart-open.
|
|
50
|
+
Type annotation: get_s3_client returns a boto3 S3 client object, not a Session
|
|
51
|
+
This is what smart-open expects for the "client" transport parameter
|
|
48
52
|
|
|
49
53
|
Returns:
|
|
50
54
|
Dictionary of S3 transport parameters for smart-open
|
|
51
55
|
"""
|
|
52
|
-
params = {}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
56
|
+
params: dict[str, Any] = {}
|
|
57
|
+
|
|
58
|
+
# Create S3 client for smart-open
|
|
59
|
+
try:
|
|
60
|
+
# Determine auth method based on available credentials
|
|
61
|
+
if self.auth_method == AWSAuthMethod.AUTOMATIC.value:
|
|
62
|
+
|
|
63
|
+
# For automatic authentication, check if region is available
|
|
64
|
+
if not self.storage_secrets.get("region_name", None):
|
|
65
|
+
logger.warning(
|
|
66
|
+
"No region specified in storage secrets for automatic authentication"
|
|
67
|
+
"This may cause credential issues - consider setting a default region"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Extract assume_role_arn if present
|
|
71
|
+
assume_role_arn = None
|
|
72
|
+
if (
|
|
73
|
+
"assume_role_arn" in self.storage_secrets
|
|
74
|
+
and self.storage_secrets["assume_role_arn"]
|
|
75
|
+
):
|
|
76
|
+
assume_role_arn = self.storage_secrets["assume_role_arn"]
|
|
77
|
+
logger.debug(f"Using assume role ARN: {assume_role_arn}")
|
|
78
|
+
|
|
79
|
+
# Create S3 client using existing utility
|
|
80
|
+
# get_s3_client returns a boto3 S3 client, not a Session
|
|
81
|
+
s3_client: Any = None
|
|
82
|
+
try:
|
|
83
|
+
s3_client = get_s3_client(
|
|
84
|
+
self.auth_method, self.storage_secrets, assume_role_arn # type: ignore
|
|
85
|
+
)
|
|
86
|
+
logger.debug("Successfully created S3 client")
|
|
87
|
+
except Exception as e:
|
|
88
|
+
# For automatic authentication, try to provide more helpful error messages
|
|
89
|
+
if self.auth_method == AWSAuthMethod.AUTOMATIC.value:
|
|
90
|
+
logger.error(
|
|
91
|
+
f"Failed to create S3 client with automatic authentication: {e}. "
|
|
92
|
+
"This usually means AWS credentials are not available in the environment"
|
|
93
|
+
"Please ensure AWS credentials are configured via environment variables, IAM roles, or AWS profiles"
|
|
94
|
+
)
|
|
95
|
+
raise ValueError(
|
|
96
|
+
f"Automatic AWS authentication failed: {e}. Please check your AWS credential configuration."
|
|
97
|
+
)
|
|
98
|
+
raise
|
|
99
|
+
|
|
100
|
+
params["client"] = s3_client
|
|
101
|
+
|
|
102
|
+
except Exception as e:
|
|
103
|
+
logger.error(f"Failed to create S3 client for smart-open: {e}")
|
|
104
|
+
raise
|
|
105
|
+
|
|
106
|
+
# Include credentials at top level for compatibility
|
|
107
|
+
# Note: When using an S3 client, these credential parameters are not needed
|
|
108
|
+
# and will be ignored by smart-open, causing warnings
|
|
109
|
+
# Only include them if no S3 client is provided (fallback scenario)
|
|
110
|
+
if not params.get("client"):
|
|
111
|
+
for key, transport_key in [
|
|
112
|
+
("aws_access_key_id", "access_key"),
|
|
113
|
+
("aws_secret_access_key", "secret_key"),
|
|
114
|
+
("region_name", "region"),
|
|
115
|
+
("assume_role_arn", "assume_role_arn"),
|
|
116
|
+
]:
|
|
117
|
+
if key in self.storage_secrets and self.storage_secrets[key]:
|
|
118
|
+
params[transport_key] = self.storage_secrets[key]
|
|
68
119
|
|
|
69
120
|
return params
|
|
70
121
|
|
|
@@ -85,28 +136,15 @@ class S3StorageClient(BaseStorageClient):
|
|
|
85
136
|
Exception: If presigned URL generation fails
|
|
86
137
|
"""
|
|
87
138
|
try:
|
|
88
|
-
# Storage secrets are already in the right format for get_s3_client
|
|
89
|
-
# get_s3_client expects dict[StorageSecrets, Any] with enum keys
|
|
90
|
-
s3_secrets = self.storage_secrets
|
|
91
|
-
|
|
92
|
-
# Determine auth method based on available credentials
|
|
93
|
-
# If AWS credentials are present, use SECRET_KEYS, otherwise use AUTOMATIC
|
|
94
|
-
if (
|
|
95
|
-
StorageSecrets.AWS_ACCESS_KEY_ID in self.storage_secrets
|
|
96
|
-
and StorageSecrets.AWS_SECRET_ACCESS_KEY in self.storage_secrets
|
|
97
|
-
and self.storage_secrets[StorageSecrets.AWS_ACCESS_KEY_ID]
|
|
98
|
-
and self.storage_secrets[StorageSecrets.AWS_SECRET_ACCESS_KEY]
|
|
99
|
-
):
|
|
100
|
-
auth_method = AWSAuthMethod.SECRET_KEYS.value
|
|
101
|
-
else:
|
|
102
|
-
auth_method = AWSAuthMethod.AUTOMATIC.value
|
|
103
|
-
|
|
104
139
|
# Extract assume_role_arn if present for role assumption
|
|
105
140
|
assume_role_arn = None
|
|
106
|
-
if
|
|
107
|
-
assume_role_arn = self.storage_secrets[
|
|
141
|
+
if "assume_role_arn" in self.storage_secrets:
|
|
142
|
+
assume_role_arn = self.storage_secrets["assume_role_arn"]
|
|
108
143
|
|
|
109
|
-
|
|
144
|
+
# get_s3_client returns a boto3 S3 client, not a Session
|
|
145
|
+
s3_client: Any = get_s3_client(
|
|
146
|
+
self.auth_method, self.storage_secrets, assume_role_arn # type: ignore
|
|
147
|
+
)
|
|
110
148
|
return create_presigned_url_for_s3(s3_client, bucket, key, ttl_seconds)
|
|
111
149
|
except Exception as e:
|
|
112
150
|
logger.error(f"Failed to generate S3 presigned URL: {e}")
|
|
@@ -54,7 +54,8 @@ def upload_to_s3_streaming(
|
|
|
54
54
|
return response
|
|
55
55
|
|
|
56
56
|
try:
|
|
57
|
-
|
|
57
|
+
logger.debug("Creating SmartOpenStorageClient with formatted secrets")
|
|
58
|
+
storage_client = SmartOpenStorageClient("s3", auth_method, formatted_secrets)
|
|
58
59
|
|
|
59
60
|
# Create upload config for the streaming interface
|
|
60
61
|
upload_config = StorageUploadConfig(
|
|
@@ -92,6 +93,10 @@ def _process_storage_secrets_input(
|
|
|
92
93
|
"""Process input and convert to string-keyed dictionary."""
|
|
93
94
|
final_secrets: dict[str, Any] = {}
|
|
94
95
|
|
|
96
|
+
logger.debug(
|
|
97
|
+
f"Processing storage secrets input of type: {type(storage_secrets).__name__}"
|
|
98
|
+
)
|
|
99
|
+
|
|
95
100
|
if isinstance(storage_secrets, StorageSecretsS3):
|
|
96
101
|
# Convert StorageSecretsS3 model directly to string keys
|
|
97
102
|
for key, value in storage_secrets.model_dump().items():
|
|
@@ -133,6 +138,7 @@ def _validate_aws_credentials(final_secrets: dict[str, Any]) -> None:
|
|
|
133
138
|
else:
|
|
134
139
|
# AUTOMATIC authentication - check if region is provided
|
|
135
140
|
has_region = "region_name" in final_secrets and final_secrets["region_name"]
|
|
141
|
+
|
|
136
142
|
if not has_region:
|
|
137
143
|
raise ValueError(
|
|
138
144
|
"Missing required region_name for AUTOMATIC authentication. "
|
|
@@ -140,13 +146,6 @@ def _validate_aws_credentials(final_secrets: dict[str, Any]) -> None:
|
|
|
140
146
|
)
|
|
141
147
|
|
|
142
148
|
|
|
143
|
-
def _set_default_region(final_secrets: dict[str, Any]) -> None:
|
|
144
|
-
"""Set default region if missing."""
|
|
145
|
-
if "region_name" not in final_secrets or not final_secrets["region_name"]:
|
|
146
|
-
logger.debug("Setting default region to 'us-east-1'")
|
|
147
|
-
final_secrets["region_name"] = "us-east-1"
|
|
148
|
-
|
|
149
|
-
|
|
150
149
|
def format_secrets(
|
|
151
150
|
storage_secrets: Union[StorageSecretsS3, dict[StorageSecrets, Any]] # type: ignore[misc]
|
|
152
151
|
) -> dict[str, Any]:
|
|
@@ -156,8 +155,8 @@ def format_secrets(
|
|
|
156
155
|
This function handles multiple credential formats and processes them through several stages:
|
|
157
156
|
1. Input processing: Accepts either StorageSecretsS3 models (from API) or raw dicts (from database)
|
|
158
157
|
2. Key normalization: Converts all keys to string format for consistency
|
|
159
|
-
3.
|
|
160
|
-
4.
|
|
158
|
+
3. Default setting: Sets default region if missing (before validation for better automatic auth support)
|
|
159
|
+
4. Validation: Ensures required AWS credentials are present based on auth method
|
|
161
160
|
5. Return: Returns string-keyed dict ready for S3StorageClient
|
|
162
161
|
|
|
163
162
|
Input formats:
|
|
@@ -179,18 +178,7 @@ def format_secrets(
|
|
|
179
178
|
Raises:
|
|
180
179
|
ValueError: If required AWS credentials are missing for the chosen auth method
|
|
181
180
|
"""
|
|
182
|
-
logger.debug("format_secrets called with type: {}", type(storage_secrets).__name__)
|
|
183
|
-
|
|
184
|
-
# Stage 1: Process input and create final format directly
|
|
185
181
|
final_secrets = _process_storage_secrets_input(storage_secrets)
|
|
186
|
-
|
|
187
|
-
# Stage 2: Validate required credentials BEFORE setting defaults
|
|
188
182
|
_validate_aws_credentials(final_secrets)
|
|
189
183
|
|
|
190
|
-
# Stage 3: Set default region if missing (after validation)
|
|
191
|
-
_set_default_region(final_secrets)
|
|
192
|
-
|
|
193
|
-
logger.debug(
|
|
194
|
-
"format_secrets completed successfully with {} keys", len(final_secrets)
|
|
195
|
-
)
|
|
196
184
|
return final_secrets
|
|
@@ -29,7 +29,12 @@ class SmartOpenStorageClient:
|
|
|
29
29
|
|
|
30
30
|
min_part_size: int = MIN_PART_SIZE
|
|
31
31
|
|
|
32
|
-
def __init__(
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
storage_type: str,
|
|
35
|
+
auth_method: Optional[str],
|
|
36
|
+
storage_secrets: Any,
|
|
37
|
+
):
|
|
33
38
|
"""Initialize the smart-open storage client.
|
|
34
39
|
|
|
35
40
|
Args:
|
|
@@ -38,9 +43,10 @@ class SmartOpenStorageClient:
|
|
|
38
43
|
Will be passed to the specific storage client implementation.
|
|
39
44
|
"""
|
|
40
45
|
self.storage_type = StorageClientFactory._normalize_storage_type(storage_type)
|
|
46
|
+
self.auth_method = auth_method
|
|
41
47
|
self.storage_secrets = storage_secrets
|
|
42
48
|
self._provider_client = StorageClientFactory.create_client(
|
|
43
|
-
storage_type, storage_secrets
|
|
49
|
+
storage_type, auth_method, storage_secrets
|
|
44
50
|
)
|
|
45
51
|
|
|
46
52
|
def _build_uri(self, bucket: str, key: str) -> str:
|
|
@@ -216,7 +222,6 @@ class SmartOpenStorageClient:
|
|
|
216
222
|
self,
|
|
217
223
|
bucket: str,
|
|
218
224
|
key: str,
|
|
219
|
-
content_type: Optional[str] = None,
|
|
220
225
|
) -> Any:
|
|
221
226
|
"""Get a writable stream for uploading data.
|
|
222
227
|
|
|
@@ -225,7 +230,6 @@ class SmartOpenStorageClient:
|
|
|
225
230
|
Args:
|
|
226
231
|
bucket: Storage bucket/container name
|
|
227
232
|
key: Object key/path
|
|
228
|
-
content_type: MIME type of the object
|
|
229
233
|
|
|
230
234
|
Returns:
|
|
231
235
|
Writable file-like object
|
|
@@ -233,9 +237,6 @@ class SmartOpenStorageClient:
|
|
|
233
237
|
uri = self._build_uri(bucket, key)
|
|
234
238
|
transport_params = self._get_transport_params()
|
|
235
239
|
|
|
236
|
-
if content_type:
|
|
237
|
-
transport_params["content_type"] = content_type
|
|
238
|
-
|
|
239
240
|
return smart_open.open(uri, "wb", transport_params=transport_params)
|
|
240
241
|
|
|
241
242
|
@retry_cloud_storage_operation(
|