ethyca-fides 2.63.3rc0__py2.py3-none-any.whl → 2.64.1b0__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.63.3rc0.dist-info → ethyca_fides-2.64.1b0.dist-info}/METADATA +2 -2
- {ethyca_fides-2.63.3rc0.dist-info → ethyca_fides-2.64.1b0.dist-info}/RECORD +249 -242
- fides/_version.py +3 -3
- fides/api/alembic/migrations/versions/ba414a58ba90_create_manual_task_config_tables.py +121 -0
- fides/api/api/v1/endpoints/connection_endpoints.py +16 -3
- fides/api/api/v1/endpoints/oauth_endpoints.py +8 -2
- fides/api/db/base.py +4 -0
- fides/api/models/attachment.py +37 -23
- fides/api/models/manual_tasks/__init__.py +6 -0
- fides/api/models/manual_tasks/manual_task.py +11 -1
- fides/api/models/manual_tasks/manual_task_config.py +136 -0
- fides/api/models/manual_tasks/manual_task_log.py +13 -9
- fides/api/schemas/connection_configuration/connection_config.py +19 -4
- fides/api/schemas/manual_tasks/manual_task_config.py +311 -0
- fides/api/schemas/manual_tasks/manual_task_schemas.py +1 -1
- fides/api/schemas/manual_tasks/manual_task_status.py +1 -1
- fides/api/schemas/privacy_request.py +2 -0
- fides/api/service/connectors/query_configs/saas_query_config.py +15 -0
- fides/api/service/privacy_request/attachment_handling.py +132 -0
- fides/api/service/privacy_request/dsr_package/dsr_report_builder.py +264 -46
- fides/api/service/privacy_request/dsr_package/templates/attachments_index.html +33 -0
- fides/api/service/privacy_request/dsr_package/templates/collection_index.html +34 -9
- fides/api/service/privacy_request/dsr_package/templates/main.css +45 -2
- fides/api/service/privacy_request/dsr_package/templates/welcome.html +12 -8
- fides/api/service/privacy_request/request_runner_service.py +258 -139
- fides/api/service/storage/gcs.py +15 -3
- fides/api/service/storage/s3.py +28 -14
- fides/api/service/storage/util.py +45 -7
- fides/api/tasks/csv_utils.py +170 -0
- fides/api/tasks/encryption_utils.py +42 -0
- fides/api/tasks/storage.py +85 -91
- fides/api/util/cache.py +6 -3
- fides/api/util/saas_util.py +1 -0
- fides/config/redis_settings.py +105 -44
- fides/service/manual_tasks/manual_task_config_service.py +370 -0
- fides/service/manual_tasks/manual_task_service.py +231 -87
- fides/service/manual_tasks/utils.py +185 -0
- fides/ui-build/static/admin/404.html +1 -1
- fides/ui-build/static/admin/_next/static/chunks/1040-630c7f4284dc6f70.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/1100-d43cb04522a6505c.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/1316-6cc72a45ebf7ff81.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/1817-b4688ba5042ec687.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/2430-b480401d44c55416.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/2599-6c4d22e75028d8b6.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/2921-aabf41bf3d7573f9.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/3450-bdaeb35442d810b4.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/3505-a79256cd851dfab4.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/3513-8677ee280eaef0da.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/3855-0dec3e7d9e886550.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/3872-ffa16c2df7ef0ab6.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/3923-5d580fbb1dd6ae01.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/401-fceaae662cfca5a5.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/4060-71cd041e5a57ca5a.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/409-037cfc3f096150f0.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/4121-78a76e980acbd539.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/4230-1cdb7ea1be370ed3.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/431-f72599f01b98f07d.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/4481-f597a7cf03f8c9e1.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/5309-4511df9708d5a63c.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/5574-3cd33b3a6c937899.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/6277-3fb4c7fc790a6d20.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/6662-cb11881dcaabe5e2.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/6853-a4097260e402980e.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/6882-3cc73d407a088d7d.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/69-6889d6674c95e7b5.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/6954-13a25cef3a8fdd76.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/699-8ca44b0de9fa20f0.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/7553-a95939c32d54b5b7.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/79-5670e31eb65d0a53.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/796-9a6b13c838e25538.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/8433-adb1fcb29d82f1b7.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/9046-fdf53cc7e926a8c1.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{905-8ab919e7b274ed50.js → 905-742074a074be1055.js} +2 -2
- fides/ui-build/static/admin/_next/static/chunks/9226-ba6587e46ec7659a.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{9392.81edc11e3a175275.js → 9392.25024e070026343d.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/9676.e60a53f1f5890847.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/9767-e49b065d03ce4e80.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/9951-f9ab5cac7e2c05ab.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/404-ac2f0844e5c4b4cd.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/{_app-f70bffa5a7903c46.js → _app-8ec6e466f3373ac0.js} +10 -10
- fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/manual-0a5f2310ce6b1059.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/multiple-00cb904825aad7e3.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/add-systems-587e21d14e0a5196.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/configure/add-vendors-fa7305b88c1afd20.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/configure-f140ec9d8e8a0f7a.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience/[id]-0edb7c92518e7d21.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience-d8d926f0735a2546.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/[id]-5c949f2e3cef2398.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/new-130155cfb4a0bcc7.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices-1135ad8924d32c36.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/properties-776855e370414beb.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/reporting-21c23f75ff1135d9.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/consent-4d5ea70a77df1bb8.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/[projectUrn]/{[resourceUrn]-18e3faf7963962e4.js → [resourceUrn]-c623d6f1a61c8ea9.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/[projectUrn]-030ee304cbe5e530.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects-cfac259a30641e68.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/resources/{[resourceUrn]-7320524a47104798.js → [resourceUrn]-57bd5cdf784f059f.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog-7b5aec33da578745.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/[systemId]-c948f93c833d4358.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]-333f53caab751428.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center-27205f8457a1ecb0.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/activity-20a1043f6a06198f.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/detection/{[resourceUrn]-4c526db0c30c488a.js → [resourceUrn]-06edce289876dea1.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{detection-6f27dbb7c8edc69d.js → detection-faf326a6200637d0.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/discovery/{[resourceUrn]-562d2b8ae90dd1f0.js → [resourceUrn]-64acf269256ee74f.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{discovery-fe7f51502eda57c9.js → discovery-8c3e4be6d36da66d.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datamap-fb50de22f83edd4a.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/[collectionName]/[...subfieldNames]-41ab27c4195cfa93.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/[collectionName]-6e2caba24b3e78c2.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]-904d43e31157aa55.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/new-d4e31a1c4a58800e.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset-8f7d16bc5e9229c8.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/[id]-4a33dd0371dbaebc.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/new-e88509346b2d2851.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection-8f9b8890018e1ea5.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/{fides-js-docs-80b241bf6cddb72e.js → fides-js-docs-5d8fd1af75f19e2f.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/index-1adb6bcb71701ac2.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations/[id]-fdc2964fa7d1d8fd.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations-adc286ff254e7f41.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/messaging/[id]-9b4d1d61c7c97509.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/messaging/add-template-f8fd4795e260887c.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/messaging-aa744ae8b61e5ff2.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/ant-components-3407158757fb3627.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/AntForm-47e947c02ae90fd0.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/FormikAntFormItem-24f9a44512ce8827.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/FormikControlled-e567a69f8c37fb9a.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/FormikField-35a33f9c7def2220.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/forms-780e18dde8e38099.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/table-migration-7a17dffa515e5560.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/[id]-91dd0039cc2b70bc.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/messaging-b01cbbaac34dadbc.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/storage-a15410de7f1d0f84.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure-1a1aa83a3f88844c.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests-c4e1b69fb5bab61a.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/properties/[id]-19737d4f21aadbee.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/properties/add-property-9ccb295110feee20.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/properties-3a1037a2e036212a.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/reporting/datamap-4cdbbb8cd1d8698e.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/about/alpha-965cc21889b25e44.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/about-0e1c381d488a7ada.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent/[configuration_id]/[purpose_id]-d0e350d2a2667883.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent-7796fbf458f8f159.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/custom-fields-6f866bc799a5f67f.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/domain-records-0b44b2b224077dcd.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/domains-5d00e1155bd11178.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/email-templates-d2cdbe770683e9df.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/locations-811dadb489f23d7d.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/organization-24108f615ff5e1e7.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/regulations-54f142bc3e4c95ba.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/[id]/test-datasets-fef247a87baeb080.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/[id]-6eb886e7b7e6785b.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/systems-7b71274334c559a4.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/taxonomy-18534d2a79a3850b.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/user-management/profile/[id]-75d41fde668b9025.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/user-management-dd43755b687c09a7.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/webpack-0a61b5bd21a41fe6.js +1 -0
- fides/ui-build/static/admin/_next/static/css/c693338e3bc8dcc6.css +1 -0
- fides/ui-build/static/admin/_next/static/nRQ3pmK_d3F5PJE39rP2h/_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/images/connector-logos/salesforce.svg +41 -0
- 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/lib/fides-ext-gpp.js +1 -1
- fides/ui-build/static/admin/lib/fides-headless.js +1 -1
- fides/ui-build/static/admin/lib/fides-preview.js +1 -1
- fides/ui-build/static/admin/lib/fides-tcf.js +2 -2
- fides/ui-build/static/admin/lib/fides.js +2 -2
- 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/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/api/service/privacy_request/dsr_package/templates/item.html +0 -37
- fides/ui-build/static/admin/_next/static/chunks/1099-667e84655846e78c.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/1100-0b01f500b1eaf60d.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/1817-951f58ddd5d46081.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/1904-964f8eb328dc2715.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/2310-744354001d01f366.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/2921-49af55ad7c631c93.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/3119-e36ae5071c8f27a5.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/3505-113f9c95d34b7aae.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/3513-5c2d412a84a78971.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/3872-45cc725e241211f0.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/3923-b4f701ada3ef0ee0.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/401-7e800aed05537126.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/4060-90a52a9afc655bfc.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/4121-66b0e00d5e7ae817.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/4132-9b1731bfec6ee537.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/4481-7f6710c928bb0cb0.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/5258-2079138c8cc34f6c.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/5404-2694509cf5fa96da.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/5487-2cbd8d2169eb2a65.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/5683-37137111b3e769fb.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/6202-081545c7822d09af.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/6277-459e054b2702c60e.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/641-7e4eef3222cbda70.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/6853-688df0b88fe65fd5.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/69-00cba94689b9e740.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/6954-4e313b4599b8763d.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/7553-08bc3d9f66695111.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/79-fc548561beed136f.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/796-d6586308ed44334b.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/7980-736f9fd1e749ddd2.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/8032-74d94f7bd5b81c89.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/8433-51870336908fb253.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/8499-1fd392d0be9e8ee4.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/8702-d1c8296f9f6afc10.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/9033-fdd87182c15b91e2.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/9187-851756440f79cd75.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/9327-6a53461c9764b16f.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/9494-1ffb888b95289891.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/9676.61cf5f6d6a083175.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/9767-afd674014a1d1152.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/404-ec04f826f3dd79ea.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/manual-1bec086d567aacff.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/multiple-f68bf100e4e60e88.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/add-systems-effde5cfe49de55f.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/configure/add-vendors-ec95ed8cb0712828.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/configure-4cc195e15e0c3cc8.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience/[id]-be8972c79fb39705.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience-ccf96207de3b74ea.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/[id]-b9823cc008372cee.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/new-373f5772ab41bc93.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices-b9beeee1bde5ca79.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/properties-6ab3a5baafebd199.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/reporting-8562790ae07f17c9.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent-42d4d2d6e0253671.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/[projectUrn]-9d1df6d890671b0c.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects-7eb08af333baaa8e.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog-bb57eed84f59a932.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/[systemId]-47eb34aef3e31ac6.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]-76cefefc53e84c7f.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center-9d8e84a8b4bf568d.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/activity-f208fef5e72dafd6.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datamap-db45aa864e9dda8a.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/[collectionName]/[...subfieldNames]-d5cfdebc74654337.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/[collectionName]-618b89228f83435e.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]-12fc86f15a4c764e.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/new-c23d18a9b56c2e3c.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset-e3b2d8e3980ab093.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/[id]-6e27257eccf65f01.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/new-bbac1f624424282d.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection-84eb56f5a07bdf4f.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/index-773182b5e35d0045.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations/[id]-eb28c927da5bd653.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations-781808bca01f8048.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/messaging/[id]-b51bee8680d66b20.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/messaging/add-template-1de479533f733fbd.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/messaging-7dc295b3ed3d2224.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/ant-components-f64dac3392f5ded7.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/AntForm-eb020bfac4bee532.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/FormikAntFormItem-0bd62e28b539e114.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/FormikControlled-936877004113c2a6.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/FormikField-6416f01524482af9.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/forms-3f4e1313d1f2969b.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/table-migration-1db7a54437db7db0.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/[id]-64236fd0141414fd.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/messaging-8cab04871908cfeb.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/storage-150d40428245ee0c.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure-b6c876dceb16ad1b.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests-20cdb2c8a03deae1.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/properties/[id]-54bcc875592d1fb9.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/properties/add-property-644dc669b508a79a.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/properties-9a88220d03e7e02f.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/reporting/datamap-70dc1081df37ea69.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/about/alpha-3c71b4dbcb6fd6c9.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/about-7cb16e0000dd16c9.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent/[configuration_id]/[purpose_id]-a18d8f12ee8dcf5d.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent-dc220e116ad5c09e.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/custom-fields-55ddc5f25b86f28e.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/domain-records-5ca7decded228bc8.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/domains-a6fafa1be2834c40.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/email-templates-e633750d2b45ddd6.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/locations-3948686cbd372969.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/organization-04ece499c22d23f4.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/regulations-7752305084280cca.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/[id]/test-datasets-9e5f9066be6f218d.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/[id]-86de9e6d72098e45.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/systems-cac52e6c3abf6a15.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/taxonomy-60d5930b6855679b.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/user-management/profile/[id]-b0f4c34752d32c73.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/user-management-7dee83dd6d9292aa.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/webpack-8457bad7859c44f0.js +0 -1
- fides/ui-build/static/admin/_next/static/css/1fdf5c593349dbc6.css +0 -1
- fides/ui-build/static/admin/_next/static/f_8LkSZkhtDM14f8gbC0b/_buildManifest.js +0 -1
- {ethyca_fides-2.63.3rc0.dist-info → ethyca_fides-2.64.1b0.dist-info}/WHEEL +0 -0
- {ethyca_fides-2.63.3rc0.dist-info → ethyca_fides-2.64.1b0.dist-info}/entry_points.txt +0 -0
- {ethyca_fides-2.63.3rc0.dist-info → ethyca_fides-2.64.1b0.dist-info}/licenses/LICENSE +0 -0
- {ethyca_fides-2.63.3rc0.dist-info → ethyca_fides-2.64.1b0.dist-info}/top_level.txt +0 -0
- /fides/ui-build/static/admin/_next/static/{f_8LkSZkhtDM14f8gbC0b → nRQ3pmK_d3F5PJE39rP2h}/_ssgManifest.js +0 -0
fides/api/service/storage/s3.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
from
|
3
|
+
from io import BytesIO
|
4
|
+
from typing import IO, Any, Dict, Optional, Tuple, Union
|
4
5
|
|
5
6
|
from boto3.s3.transfer import TransferConfig
|
6
7
|
from botocore.exceptions import ClientError, ParamValidationError
|
@@ -34,7 +35,7 @@ def maybe_get_s3_client(
|
|
34
35
|
|
35
36
|
|
36
37
|
def create_presigned_url_for_s3(
|
37
|
-
s3_client: Any, bucket_name: str, file_key: str
|
38
|
+
s3_client: Any, bucket_name: str, file_key: str, ttl_seconds: Optional[int] = None
|
38
39
|
) -> AnyHttpUrlString:
|
39
40
|
"""
|
40
41
|
Generates a presigned URL to share an S3 object
|
@@ -45,10 +46,16 @@ def create_presigned_url_for_s3(
|
|
45
46
|
:return: Presigned URL as string.
|
46
47
|
"""
|
47
48
|
params = {"Bucket": bucket_name, "Key": file_key}
|
49
|
+
if ttl_seconds:
|
50
|
+
if ttl_seconds > 604800:
|
51
|
+
raise ValueError("TTL must be less than 7 days")
|
52
|
+
expires_in = ttl_seconds
|
53
|
+
else:
|
54
|
+
expires_in = CONFIG.security.subject_request_download_link_ttl_seconds
|
48
55
|
response = s3_client.generate_presigned_url(
|
49
56
|
"get_object",
|
50
57
|
Params=params,
|
51
|
-
ExpiresIn=
|
58
|
+
ExpiresIn=expires_in,
|
52
59
|
)
|
53
60
|
|
54
61
|
# The response contains the presigned URL
|
@@ -68,7 +75,7 @@ def generic_upload_to_s3( # pylint: disable=R0913
|
|
68
75
|
file_key: str,
|
69
76
|
auth_method: str,
|
70
77
|
document: IO[bytes],
|
71
|
-
size_threshold: int = LARGE_FILE_THRESHOLD, #
|
78
|
+
size_threshold: int = LARGE_FILE_THRESHOLD, # 25 MB threshold
|
72
79
|
) -> Tuple[int, AnyHttpUrlString]:
|
73
80
|
"""
|
74
81
|
Uploads file like objects to S3.
|
@@ -130,7 +137,8 @@ def generic_retrieve_from_s3(
|
|
130
137
|
file_key: str,
|
131
138
|
auth_method: str,
|
132
139
|
get_content: bool = False,
|
133
|
-
|
140
|
+
ttl_seconds: Optional[int] = None,
|
141
|
+
) -> Tuple[int, Union[str, IO[bytes]]]:
|
134
142
|
"""
|
135
143
|
Retrieves a file from S3 and returns its size and either a presigned URL or the actual content.
|
136
144
|
|
@@ -148,17 +156,23 @@ def generic_retrieve_from_s3(
|
|
148
156
|
s3_client = get_s3_client(auth_method, storage_secrets)
|
149
157
|
|
150
158
|
try:
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
159
|
+
# Get file size using head_object
|
160
|
+
size_response = s3_client.head_object(Bucket=bucket_name, Key=file_key)
|
161
|
+
# If the file is less than 25MB, we can get the content otherwise return the presigned URL
|
162
|
+
if get_content and size_response["ContentLength"] <= LARGE_FILE_THRESHOLD:
|
163
|
+
# Get the actual content using download_fileobj
|
164
|
+
file_obj = BytesIO()
|
165
|
+
s3_client.download_fileobj(
|
166
|
+
Bucket=bucket_name, Key=file_key, Fileobj=file_obj
|
167
|
+
)
|
168
|
+
file_obj.seek(0) # Reset file pointer to beginning
|
169
|
+
return int(size_response["ContentLength"]), file_obj
|
156
170
|
|
157
171
|
# Get presigned URL
|
158
|
-
presigned_url = create_presigned_url_for_s3(
|
159
|
-
|
160
|
-
|
161
|
-
return int(
|
172
|
+
presigned_url = create_presigned_url_for_s3(
|
173
|
+
s3_client, bucket_name, file_key, ttl_seconds
|
174
|
+
)
|
175
|
+
return int(size_response["ContentLength"]), str(presigned_url)
|
162
176
|
except ClientError as e:
|
163
177
|
logger.error(f"Error retrieving file from S3: {e}")
|
164
178
|
raise e
|
@@ -3,6 +3,10 @@ from enum import Enum as EnumType
|
|
3
3
|
|
4
4
|
from loguru import logger
|
5
5
|
|
6
|
+
# This is the max file size for downloading the content of an attachment.
|
7
|
+
# This is an industry standard used by companies like Google and Microsoft.
|
8
|
+
LARGE_FILE_THRESHOLD = 25 * 1024 * 1024 # 25 MB
|
9
|
+
|
6
10
|
|
7
11
|
class AllowedFileType(EnumType):
|
8
12
|
"""
|
@@ -24,15 +28,49 @@ class AllowedFileType(EnumType):
|
|
24
28
|
|
25
29
|
LOCAL_FIDES_UPLOAD_DIRECTORY = "fides_uploads"
|
26
30
|
|
27
|
-
# Default to 10MB if not specified in environment
|
28
|
-
LARGE_FILE_THRESHOLD = 10 * 1024 * 1024 # 10 MB threshold
|
29
|
-
|
30
31
|
|
31
32
|
def get_local_filename(file_key: str) -> str:
|
32
|
-
"""Verifies that the local storage directory exists and returns the local filepath
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
"""Verifies that the local storage directory exists and returns the local filepath.
|
34
|
+
|
35
|
+
This extra security checks are to prevent directory traversal attacks and "complete business and technical destruction".
|
36
|
+
Thanks Claude.
|
37
|
+
|
38
|
+
Args:
|
39
|
+
file_key: The key/path for the file
|
40
|
+
|
41
|
+
Returns:
|
42
|
+
The full local filepath
|
43
|
+
|
44
|
+
Raises:
|
45
|
+
ValueError: If the file_key is invalid or would result in a path outside the upload directory
|
46
|
+
"""
|
47
|
+
# Basic validation
|
48
|
+
if not file_key:
|
49
|
+
raise ValueError("File key cannot be empty")
|
50
|
+
|
51
|
+
# Security checks before normalization
|
52
|
+
if file_key.startswith("/"):
|
53
|
+
raise ValueError("Invalid file key: cannot start with '/'")
|
54
|
+
|
55
|
+
# Normalize the path to handle any path separators consistently
|
56
|
+
# First normalize using os.path.normpath to handle any redundant separators
|
57
|
+
normalized_key = os.path.normpath(file_key)
|
58
|
+
# Then convert all separators to forward slashes for consistency
|
59
|
+
normalized_key = normalized_key.replace("\\", "/")
|
60
|
+
|
61
|
+
# Additional security: ensure the final path is within the upload directory
|
62
|
+
final_path = os.path.join(LOCAL_FIDES_UPLOAD_DIRECTORY, normalized_key)
|
63
|
+
if not os.path.abspath(final_path).startswith(
|
64
|
+
os.path.abspath(LOCAL_FIDES_UPLOAD_DIRECTORY)
|
65
|
+
):
|
66
|
+
raise ValueError(
|
67
|
+
"Invalid file key: would result in path outside upload directory"
|
68
|
+
)
|
69
|
+
|
70
|
+
# Create all necessary directories
|
71
|
+
os.makedirs(os.path.dirname(final_path), exist_ok=True)
|
72
|
+
|
73
|
+
return final_path
|
36
74
|
|
37
75
|
|
38
76
|
def get_allowed_file_type_or_raise(file_key: str) -> str:
|
@@ -0,0 +1,170 @@
|
|
1
|
+
import zipfile
|
2
|
+
from io import BytesIO
|
3
|
+
from typing import Any, Optional
|
4
|
+
|
5
|
+
import pandas as pd
|
6
|
+
|
7
|
+
from fides.api.tasks.encryption_utils import encrypt_access_request_results
|
8
|
+
from fides.config import CONFIG
|
9
|
+
|
10
|
+
|
11
|
+
def create_csv_from_dataframe(df: pd.DataFrame) -> BytesIO:
|
12
|
+
"""Create a CSV file from a pandas DataFrame.
|
13
|
+
|
14
|
+
Args:
|
15
|
+
df: The DataFrame to convert to CSV
|
16
|
+
|
17
|
+
Returns:
|
18
|
+
BytesIO: A file-like object containing the CSV data
|
19
|
+
"""
|
20
|
+
buffer = BytesIO()
|
21
|
+
df.to_csv(buffer, index=False, encoding=CONFIG.security.encoding)
|
22
|
+
buffer.seek(0)
|
23
|
+
return buffer
|
24
|
+
|
25
|
+
|
26
|
+
def create_attachment_csv(attachments: list[dict[str, Any]]) -> Optional[BytesIO]:
|
27
|
+
"""Create a CSV file containing attachment metadata.
|
28
|
+
|
29
|
+
Args:
|
30
|
+
attachments: List of attachment dictionaries
|
31
|
+
privacy_request_id: The ID of the privacy request for encryption
|
32
|
+
|
33
|
+
Returns:
|
34
|
+
Optional[BytesIO]: A file-like object containing the CSV data, or None if no attachments
|
35
|
+
"""
|
36
|
+
if not attachments:
|
37
|
+
return None
|
38
|
+
|
39
|
+
# Filter out invalid attachments and create a list of valid ones
|
40
|
+
valid_attachments = []
|
41
|
+
for a in attachments:
|
42
|
+
if not isinstance(a, dict):
|
43
|
+
continue
|
44
|
+
|
45
|
+
# Check if the attachment has at least one of the required fields
|
46
|
+
if not any(
|
47
|
+
key in a
|
48
|
+
for key in ["file_name", "file_size", "content_type", "download_url"]
|
49
|
+
):
|
50
|
+
continue
|
51
|
+
|
52
|
+
valid_attachments.append(
|
53
|
+
{
|
54
|
+
"file_name": a.get("file_name", ""),
|
55
|
+
"file_size": a.get("file_size", 0),
|
56
|
+
"content_type": a.get("content_type", "application/octet-stream"),
|
57
|
+
"download_url": a.get("download_url", ""),
|
58
|
+
}
|
59
|
+
)
|
60
|
+
|
61
|
+
# Return None if there are no valid attachments
|
62
|
+
if not valid_attachments:
|
63
|
+
return None
|
64
|
+
|
65
|
+
df = pd.DataFrame(valid_attachments)
|
66
|
+
|
67
|
+
if df.empty:
|
68
|
+
return None
|
69
|
+
|
70
|
+
return create_csv_from_dataframe(df)
|
71
|
+
|
72
|
+
|
73
|
+
def _write_attachment_csv(
|
74
|
+
zip_file: zipfile.ZipFile,
|
75
|
+
key: str,
|
76
|
+
idx: int,
|
77
|
+
attachments: list[dict[str, Any]],
|
78
|
+
privacy_request_id: str,
|
79
|
+
) -> None:
|
80
|
+
"""Write attachment data to a CSV file in the zip archive.
|
81
|
+
|
82
|
+
Args:
|
83
|
+
zip_file: The zip file to write to
|
84
|
+
key: The key for the data
|
85
|
+
idx: The index of the item in the list
|
86
|
+
attachments: List of attachment dictionaries
|
87
|
+
privacy_request_id: The ID of the privacy request for encryption
|
88
|
+
"""
|
89
|
+
buffer = create_attachment_csv(attachments)
|
90
|
+
if buffer:
|
91
|
+
zip_file.writestr(
|
92
|
+
f"{key}/{idx + 1}/attachments.csv",
|
93
|
+
encrypt_access_request_results(buffer.getvalue(), privacy_request_id),
|
94
|
+
)
|
95
|
+
|
96
|
+
|
97
|
+
def _write_item_csv(
|
98
|
+
zip_file: zipfile.ZipFile,
|
99
|
+
key: str,
|
100
|
+
items: list[dict[str, Any]],
|
101
|
+
privacy_request_id: str,
|
102
|
+
) -> None:
|
103
|
+
"""Write item data to a CSV file in the zip archive.
|
104
|
+
|
105
|
+
Args:
|
106
|
+
zip_file: The zip file to write to
|
107
|
+
key: The key for the data
|
108
|
+
items: List of items to write
|
109
|
+
privacy_request_id: The ID of the privacy request for encryption
|
110
|
+
"""
|
111
|
+
if items:
|
112
|
+
df = pd.DataFrame(items)
|
113
|
+
buffer = create_csv_from_dataframe(df)
|
114
|
+
zip_file.writestr(
|
115
|
+
f"{key}.csv",
|
116
|
+
encrypt_access_request_results(buffer.getvalue(), privacy_request_id),
|
117
|
+
)
|
118
|
+
|
119
|
+
|
120
|
+
def _write_simple_csv(
|
121
|
+
zip_file: zipfile.ZipFile,
|
122
|
+
key: str,
|
123
|
+
value: Any,
|
124
|
+
privacy_request_id: str,
|
125
|
+
) -> None:
|
126
|
+
"""Write simple key-value data to a CSV file in the zip archive.
|
127
|
+
|
128
|
+
Args:
|
129
|
+
zip_file: The zip file to write to
|
130
|
+
key: The key for the data
|
131
|
+
value: The value to write
|
132
|
+
privacy_request_id: The ID of the privacy request for encryption
|
133
|
+
"""
|
134
|
+
df = pd.json_normalize({key: value})
|
135
|
+
buffer = create_csv_from_dataframe(df)
|
136
|
+
zip_file.writestr(
|
137
|
+
f"{key}.csv",
|
138
|
+
encrypt_access_request_results(buffer.getvalue(), privacy_request_id),
|
139
|
+
)
|
140
|
+
|
141
|
+
|
142
|
+
def write_csv_to_zip(
|
143
|
+
zip_file: zipfile.ZipFile, data: dict[str, Any], privacy_request_id: str
|
144
|
+
) -> None:
|
145
|
+
"""Write data to a zip file in CSV format.
|
146
|
+
|
147
|
+
Args:
|
148
|
+
zip_file: The zip file to write to
|
149
|
+
data: The data to convert to CSV
|
150
|
+
privacy_request_id: The ID of the privacy request for encryption
|
151
|
+
"""
|
152
|
+
for key, value in data.items():
|
153
|
+
if (
|
154
|
+
isinstance(value, list)
|
155
|
+
and value
|
156
|
+
and all(isinstance(item, dict) for item in value)
|
157
|
+
):
|
158
|
+
# Handle lists of dictionaries
|
159
|
+
items: list[dict[str, Any]] = []
|
160
|
+
for item in value:
|
161
|
+
# Extract attachments if they exist
|
162
|
+
attachments = item.pop("attachments", [])
|
163
|
+
if attachments:
|
164
|
+
_write_attachment_csv(
|
165
|
+
zip_file, key, len(items), attachments, privacy_request_id
|
166
|
+
)
|
167
|
+
items.append(item)
|
168
|
+
_write_item_csv(zip_file, key, items, privacy_request_id)
|
169
|
+
else:
|
170
|
+
_write_simple_csv(zip_file, key, value, privacy_request_id)
|
@@ -0,0 +1,42 @@
|
|
1
|
+
import secrets
|
2
|
+
from typing import Optional, Union
|
3
|
+
|
4
|
+
from fides.api.cryptography.cryptographic_util import bytes_to_b64_str
|
5
|
+
from fides.api.util.cache import get_cache, get_encryption_cache_key
|
6
|
+
from fides.api.util.encryption.aes_gcm_encryption_scheme import (
|
7
|
+
encrypt_to_bytes_verify_secrets_length,
|
8
|
+
)
|
9
|
+
from fides.config import CONFIG
|
10
|
+
|
11
|
+
|
12
|
+
def encrypt_access_request_results(data: Union[str, bytes], request_id: str) -> str:
|
13
|
+
"""Encrypt data with encryption key if provided, otherwise return unencrypted data.
|
14
|
+
|
15
|
+
Args:
|
16
|
+
data: The data to encrypt
|
17
|
+
request_id: The ID of the privacy request for encryption key lookup
|
18
|
+
|
19
|
+
Returns:
|
20
|
+
str: The encrypted data as a string
|
21
|
+
"""
|
22
|
+
cache = get_cache()
|
23
|
+
encryption_cache_key = get_encryption_cache_key(
|
24
|
+
privacy_request_id=request_id,
|
25
|
+
encryption_attr="key",
|
26
|
+
)
|
27
|
+
if isinstance(data, bytes):
|
28
|
+
data = data.decode(CONFIG.security.encoding)
|
29
|
+
|
30
|
+
encryption_key: Optional[str] = cache.get(encryption_cache_key)
|
31
|
+
if not encryption_key:
|
32
|
+
return data
|
33
|
+
|
34
|
+
bytes_encryption_key: bytes = encryption_key.encode(
|
35
|
+
encoding=CONFIG.security.encoding
|
36
|
+
)
|
37
|
+
nonce: bytes = secrets.token_bytes(CONFIG.security.aes_gcm_nonce_length)
|
38
|
+
# b64encode the entire nonce and the encrypted message together
|
39
|
+
return bytes_to_b64_str(
|
40
|
+
nonce
|
41
|
+
+ encrypt_to_bytes_verify_secrets_length(data, bytes_encryption_key, nonce)
|
42
|
+
)
|
fides/api/tasks/storage.py
CHANGED
@@ -1,23 +1,20 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import json
|
4
|
-
import secrets
|
5
4
|
import zipfile
|
6
5
|
from io import BytesIO
|
7
|
-
from typing import TYPE_CHECKING, Any,
|
6
|
+
from typing import TYPE_CHECKING, Any, Optional
|
8
7
|
|
9
|
-
import pandas as pd
|
10
8
|
from botocore.exceptions import ClientError, ParamValidationError
|
11
9
|
from fideslang.validation import AnyHttpUrlString
|
12
10
|
from loguru import logger
|
13
11
|
|
14
12
|
from fides.api.common_exceptions import StorageUploadError
|
15
|
-
from fides.api.cryptography.cryptographic_util import bytes_to_b64_str
|
16
13
|
from fides.api.schemas.storage.storage import ResponseFormat, StorageSecrets
|
17
14
|
from fides.api.service.privacy_request.dsr_package.dsr_report_builder import (
|
18
15
|
DsrReportBuilder,
|
19
16
|
)
|
20
|
-
from fides.api.service.storage.gcs import
|
17
|
+
from fides.api.service.storage.gcs import get_gcs_blob
|
21
18
|
from fides.api.service.storage.s3 import (
|
22
19
|
create_presigned_url_for_s3,
|
23
20
|
generic_upload_to_s3,
|
@@ -26,11 +23,9 @@ from fides.api.service.storage.util import (
|
|
26
23
|
LOCAL_FIDES_UPLOAD_DIRECTORY,
|
27
24
|
get_local_filename,
|
28
25
|
)
|
26
|
+
from fides.api.tasks.csv_utils import write_csv_to_zip
|
27
|
+
from fides.api.tasks.encryption_utils import encrypt_access_request_results
|
29
28
|
from fides.api.util.aws_util import get_s3_client
|
30
|
-
from fides.api.util.cache import get_cache, get_encryption_cache_key
|
31
|
-
from fides.api.util.encryption.aes_gcm_encryption_scheme import (
|
32
|
-
encrypt_to_bytes_verify_secrets_length,
|
33
|
-
)
|
34
29
|
from fides.api.util.storage_util import StorageJSONEncoder
|
35
30
|
from fides.config import CONFIG
|
36
31
|
|
@@ -38,33 +33,8 @@ if TYPE_CHECKING:
|
|
38
33
|
from fides.api.models.privacy_request import PrivacyRequest
|
39
34
|
|
40
35
|
|
41
|
-
def encrypt_access_request_results(data: Union[str, bytes], request_id: str) -> str:
|
42
|
-
"""Encrypt data with encryption key if provided, otherwise return unencrypted data"""
|
43
|
-
cache = get_cache()
|
44
|
-
encryption_cache_key = get_encryption_cache_key(
|
45
|
-
privacy_request_id=request_id,
|
46
|
-
encryption_attr="key",
|
47
|
-
)
|
48
|
-
if isinstance(data, bytes):
|
49
|
-
data = data.decode(CONFIG.security.encoding)
|
50
|
-
|
51
|
-
encryption_key: str | None = cache.get(encryption_cache_key)
|
52
|
-
if not encryption_key:
|
53
|
-
return data
|
54
|
-
|
55
|
-
bytes_encryption_key: bytes = encryption_key.encode(
|
56
|
-
encoding=CONFIG.security.encoding
|
57
|
-
)
|
58
|
-
nonce: bytes = secrets.token_bytes(CONFIG.security.aes_gcm_nonce_length)
|
59
|
-
# b64encode the entire nonce and the encrypted message together
|
60
|
-
return bytes_to_b64_str(
|
61
|
-
nonce
|
62
|
-
+ encrypt_to_bytes_verify_secrets_length(data, bytes_encryption_key, nonce)
|
63
|
-
)
|
64
|
-
|
65
|
-
|
66
36
|
def write_to_in_memory_buffer(
|
67
|
-
resp_format: str, data:
|
37
|
+
resp_format: str, data: dict[str, Any], privacy_request: PrivacyRequest
|
68
38
|
) -> BytesIO:
|
69
39
|
"""Write JSON/CSV data to in-memory file-like object to be passed to S3 or GCS. Encrypt data if encryption key/nonce
|
70
40
|
has been cached for the given privacy request id
|
@@ -73,46 +43,62 @@ def write_to_in_memory_buffer(
|
|
73
43
|
:param data: Dict
|
74
44
|
:param request_id: str, The privacy request id
|
75
45
|
"""
|
46
|
+
|
76
47
|
logger.debug("Writing data to in-memory buffer")
|
48
|
+
try:
|
49
|
+
if resp_format == ResponseFormat.html.value:
|
50
|
+
return DsrReportBuilder(
|
51
|
+
privacy_request=privacy_request,
|
52
|
+
dsr_data=data,
|
53
|
+
).generate()
|
54
|
+
|
55
|
+
if resp_format == ResponseFormat.json.value:
|
56
|
+
return convert_dict_to_encrypted_json(data, privacy_request.id)
|
57
|
+
|
58
|
+
if resp_format == ResponseFormat.csv.value:
|
59
|
+
zipped_csvs = BytesIO()
|
60
|
+
with zipfile.ZipFile(zipped_csvs, "w") as f:
|
61
|
+
write_csv_to_zip(f, data, privacy_request.id)
|
62
|
+
zipped_csvs.seek(0)
|
63
|
+
return zipped_csvs
|
64
|
+
except Exception as e:
|
65
|
+
logger.error(f"Error writing data to in-memory buffer: {str(e)}")
|
66
|
+
raise e
|
67
|
+
|
68
|
+
raise NotImplementedError(f"No handling for response format {resp_format}.")
|
69
|
+
|
70
|
+
|
71
|
+
def convert_dict_to_encrypted_json(
|
72
|
+
data: dict[str, Any], privacy_request_id: str
|
73
|
+
) -> BytesIO:
|
74
|
+
"""Convert data to JSON and encrypt it.
|
75
|
+
|
76
|
+
Args:
|
77
|
+
data: The data to convert and encrypt
|
78
|
+
privacy_request_id: The ID of the privacy request for encryption
|
77
79
|
|
78
|
-
|
80
|
+
Returns:
|
81
|
+
BytesIO: A file-like object containing the encrypted JSON data
|
82
|
+
|
83
|
+
Raises:
|
84
|
+
Exception: If JSON conversion fails
|
85
|
+
"""
|
86
|
+
try:
|
79
87
|
json_str = json.dumps(data, indent=2, default=StorageJSONEncoder().default)
|
80
88
|
return BytesIO(
|
81
|
-
encrypt_access_request_results(json_str,
|
89
|
+
encrypt_access_request_results(json_str, privacy_request_id).encode(
|
82
90
|
CONFIG.security.encoding
|
83
91
|
)
|
84
92
|
)
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
for key in data:
|
90
|
-
df = pd.json_normalize(data[key])
|
91
|
-
buffer = BytesIO()
|
92
|
-
df.to_csv(buffer, index=False, encoding=CONFIG.security.encoding)
|
93
|
-
buffer.seek(0)
|
94
|
-
f.writestr(
|
95
|
-
f"{key}.csv",
|
96
|
-
encrypt_access_request_results(
|
97
|
-
buffer.getvalue(), privacy_request.id
|
98
|
-
),
|
99
|
-
)
|
100
|
-
|
101
|
-
zipped_csvs.seek(0)
|
102
|
-
return zipped_csvs
|
103
|
-
|
104
|
-
if resp_format == ResponseFormat.html.value:
|
105
|
-
return DsrReportBuilder(
|
106
|
-
privacy_request=privacy_request,
|
107
|
-
dsr_data=data,
|
108
|
-
).generate()
|
109
|
-
|
110
|
-
raise NotImplementedError(f"No handling for response format {resp_format}.")
|
93
|
+
except Exception as e:
|
94
|
+
logger.error(f"Error converting data to JSON: {str(e)}")
|
95
|
+
logger.error(f"Data that failed to convert: {data}")
|
96
|
+
raise
|
111
97
|
|
112
98
|
|
113
99
|
def upload_to_s3( # pylint: disable=R0913
|
114
|
-
storage_secrets:
|
115
|
-
data:
|
100
|
+
storage_secrets: dict[StorageSecrets, Any],
|
101
|
+
data: dict,
|
116
102
|
bucket_name: str,
|
117
103
|
file_key: str,
|
118
104
|
resp_format: str,
|
@@ -140,18 +126,22 @@ def upload_to_s3( # pylint: disable=R0913
|
|
140
126
|
"storage", {}
|
141
127
|
).get("aws_s3_assume_role_arn"),
|
142
128
|
)
|
129
|
+
except (ClientError, ParamValidationError) as e:
|
130
|
+
logger.error(f"Error getting s3 client: {str(e)}")
|
131
|
+
raise StorageUploadError(f"Error getting s3 client: {str(e)}")
|
143
132
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
133
|
+
# handles file chunking
|
134
|
+
try:
|
135
|
+
s3_client.upload_fileobj(
|
136
|
+
Fileobj=write_to_in_memory_buffer(resp_format, data, privacy_request),
|
137
|
+
Bucket=bucket_name,
|
138
|
+
Key=file_key,
|
139
|
+
)
|
140
|
+
except ClientError as e:
|
141
|
+
logger.error("Encountered error while uploading s3 object: {}", e)
|
142
|
+
raise StorageUploadError(f"Error uploading to S3: {e}")
|
154
143
|
|
144
|
+
try:
|
155
145
|
presigned_url: AnyHttpUrlString = create_presigned_url_for_s3(
|
156
146
|
s3_client, bucket_name, file_key
|
157
147
|
)
|
@@ -162,13 +152,11 @@ def upload_to_s3( # pylint: disable=R0913
|
|
162
152
|
"Encountered error while uploading and generating link for s3 object: {}", e
|
163
153
|
)
|
164
154
|
raise StorageUploadError(f"Error uploading to S3: {e}")
|
165
|
-
except ParamValidationError as e:
|
166
|
-
raise StorageUploadError(f"The parameters you provided are incorrect: {e}")
|
167
155
|
|
168
156
|
|
169
157
|
def upload_to_gcs(
|
170
|
-
storage_secrets:
|
171
|
-
data:
|
158
|
+
storage_secrets: dict,
|
159
|
+
data: dict,
|
172
160
|
bucket_name: str,
|
173
161
|
file_key: str,
|
174
162
|
resp_format: str,
|
@@ -177,24 +165,30 @@ def upload_to_gcs(
|
|
177
165
|
) -> str:
|
178
166
|
"""Uploads access request data to a Google Cloud Storage bucket"""
|
179
167
|
logger.info("Starting Google Cloud Storage upload of {}", file_key)
|
168
|
+
content_type = {
|
169
|
+
ResponseFormat.json.value: "application/json",
|
170
|
+
ResponseFormat.csv.value: "application/zip",
|
171
|
+
ResponseFormat.html.value: "application/zip",
|
172
|
+
}
|
173
|
+
|
174
|
+
blob = get_gcs_blob(auth_method, storage_secrets, bucket_name, file_key)
|
175
|
+
in_memory_file = write_to_in_memory_buffer(resp_format, data, privacy_request)
|
180
176
|
|
181
177
|
try:
|
182
|
-
storage_client = get_gcs_client(auth_method, storage_secrets)
|
183
|
-
bucket = storage_client.bucket(bucket_name)
|
184
|
-
|
185
|
-
blob = bucket.blob(file_key)
|
186
|
-
in_memory_file = write_to_in_memory_buffer(resp_format, data, privacy_request)
|
187
|
-
content_type = {
|
188
|
-
ResponseFormat.json.value: "application/json",
|
189
|
-
ResponseFormat.csv.value: "application/zip",
|
190
|
-
ResponseFormat.html.value: "application/zip",
|
191
|
-
}
|
192
178
|
blob.upload_from_string(
|
193
179
|
in_memory_file.getvalue(), content_type=content_type[resp_format]
|
194
180
|
)
|
181
|
+
except Exception as e:
|
182
|
+
logger.error("Error uploading to GCS: {}", str(e))
|
183
|
+
logger.error(
|
184
|
+
"Encountered error while uploading and generating link for Google Cloud Storage object: {}",
|
185
|
+
e,
|
186
|
+
)
|
187
|
+
raise
|
195
188
|
|
196
|
-
|
189
|
+
logger.info("File {} uploaded to {}", file_key, blob.public_url)
|
197
190
|
|
191
|
+
try:
|
198
192
|
presigned_url = blob.generate_signed_url(
|
199
193
|
version="v4",
|
200
194
|
expiration=CONFIG.security.subject_request_download_link_ttl_seconds,
|
@@ -210,7 +204,7 @@ def upload_to_gcs(
|
|
210
204
|
|
211
205
|
|
212
206
|
def upload_to_local(
|
213
|
-
data:
|
207
|
+
data: dict,
|
214
208
|
file_key: str,
|
215
209
|
privacy_request: PrivacyRequest,
|
216
210
|
resp_format: str = ResponseFormat.json.value,
|
fides/api/util/cache.py
CHANGED
@@ -174,6 +174,7 @@ def _determine_redis_db_index(
|
|
174
174
|
- If *not* running under xdist, always use DB 1.
|
175
175
|
|
176
176
|
2. Non-test mode: return the value already present in `CONFIG.redis.db_index`
|
177
|
+
(or CONFIG.redis.read_only_db_index` if read_only is True)
|
177
178
|
"""
|
178
179
|
|
179
180
|
# 1. Test mode logic
|
@@ -261,18 +262,20 @@ def get_read_only_cache() -> FidesopsRedis:
|
|
261
262
|
ssl_ca_certs=CONFIG.redis.read_only_ssl_ca_certs,
|
262
263
|
ssl_cert_reqs=CONFIG.redis.read_only_ssl_cert_reqs,
|
263
264
|
)
|
264
|
-
logger.debug("New read-only Redis connection created.")
|
265
265
|
|
266
266
|
try:
|
267
|
+
# Test the connection by attempting to ping the Redis server
|
267
268
|
connected = _read_only_connection.ping()
|
268
|
-
logger.debug("Read-only Redis connection
|
269
|
-
except
|
269
|
+
logger.debug("Read-only Redis connection established successfully.")
|
270
|
+
except Exception as e:
|
271
|
+
logger.error(f"Failed to connect to read-only Redis: {e}")
|
270
272
|
connected = False
|
271
273
|
|
272
274
|
if not connected:
|
273
275
|
logger.error(
|
274
276
|
"Unable to establish read-only Redis connection. Returning writeable cache connection instead."
|
275
277
|
)
|
278
|
+
# If we can't connect to the read-only cache, fall back to the regular cache
|
276
279
|
return get_cache()
|
277
280
|
|
278
281
|
return _read_only_connection
|
fides/api/util/saas_util.py
CHANGED