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
|
@@ -1,18 +1,34 @@
|
|
|
1
|
+
import copy
|
|
1
2
|
import json
|
|
2
3
|
import os
|
|
3
4
|
import time as time_module
|
|
4
5
|
import zipfile
|
|
5
|
-
from collections import defaultdict
|
|
6
6
|
from io import BytesIO
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import
|
|
8
|
+
from typing import Any, Optional
|
|
9
9
|
|
|
10
10
|
import jinja2
|
|
11
11
|
from jinja2 import Environment, FileSystemLoader
|
|
12
12
|
from loguru import logger
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
from sqlalchemy.orm import object_session
|
|
14
|
+
|
|
15
|
+
from fides.api.models.privacy_request import PrivacyRequest
|
|
16
|
+
from fides.api.service.privacy_request.dsr_package.dsr_data_preprocessor import (
|
|
17
|
+
DSRDataPreprocessor,
|
|
18
|
+
)
|
|
19
|
+
from fides.api.service.privacy_request.dsr_package.utils import map_privacy_request
|
|
20
|
+
from fides.api.service.storage.util import (
|
|
21
|
+
_get_datasets_from_dsr_data,
|
|
22
|
+
create_attachment_info_dict,
|
|
23
|
+
format_attachment_size,
|
|
24
|
+
generate_attachment_url_from_storage_path,
|
|
25
|
+
is_attachment_field,
|
|
26
|
+
process_attachment_naming,
|
|
27
|
+
process_attachments_contextually,
|
|
28
|
+
resolve_directory_from_context,
|
|
29
|
+
)
|
|
15
30
|
from fides.api.util.storage_util import StorageJSONEncoder, format_size
|
|
31
|
+
from fides.config import CONFIG
|
|
16
32
|
|
|
17
33
|
DSR_DIRECTORY = Path(__file__).parent.resolve()
|
|
18
34
|
|
|
@@ -20,12 +36,9 @@ TEXT_COLOR = "#4A5568"
|
|
|
20
36
|
HEADER_COLOR = "#FAFAFA"
|
|
21
37
|
BORDER_COLOR = "#E2E8F0"
|
|
22
38
|
|
|
23
|
-
if TYPE_CHECKING:
|
|
24
|
-
from fides.api.models.privacy_request import PrivacyRequest # pragma: no cover
|
|
25
|
-
|
|
26
39
|
|
|
27
40
|
# pylint: disable=too-many-instance-attributes
|
|
28
|
-
class
|
|
41
|
+
class DSRReportBuilder:
|
|
29
42
|
"""
|
|
30
43
|
Manages populating HTML templates from the given data and adding the generated
|
|
31
44
|
pages to a zip file in a way that the pages can be navigated between.
|
|
@@ -37,7 +50,7 @@ class DsrReportBuilder:
|
|
|
37
50
|
- data/dataset_name/collection_name/item_index.html: the detail page for the item
|
|
38
51
|
- attachments/index.html: the index page for the attachments
|
|
39
52
|
|
|
40
|
-
|
|
53
|
+
Args:
|
|
41
54
|
privacy_request: the privacy request object
|
|
42
55
|
dsr_data: the DSR data
|
|
43
56
|
"""
|
|
@@ -46,6 +59,7 @@ class DsrReportBuilder:
|
|
|
46
59
|
self,
|
|
47
60
|
privacy_request: "PrivacyRequest",
|
|
48
61
|
dsr_data: dict[str, Any],
|
|
62
|
+
enable_streaming: bool = False,
|
|
49
63
|
):
|
|
50
64
|
"""
|
|
51
65
|
Initializes the DSR report builder.
|
|
@@ -54,7 +68,6 @@ class DsrReportBuilder:
|
|
|
54
68
|
jinja2.filters.FILTERS["pretty_print"] = lambda value, indent=4: json.dumps(
|
|
55
69
|
value, indent=indent, cls=StorageJSONEncoder
|
|
56
70
|
)
|
|
57
|
-
|
|
58
71
|
# Initialize instance zip file variables
|
|
59
72
|
self.baos = BytesIO()
|
|
60
73
|
|
|
@@ -70,15 +83,38 @@ class DsrReportBuilder:
|
|
|
70
83
|
"text_color": TEXT_COLOR,
|
|
71
84
|
"header_color": HEADER_COLOR,
|
|
72
85
|
"border_color": BORDER_COLOR,
|
|
86
|
+
"download_link_ttl_days": self._get_download_link_ttl_days(),
|
|
87
|
+
"enable_streaming": enable_streaming,
|
|
73
88
|
}
|
|
74
89
|
self.main_links: dict[str, Any] = {} # used to track the generated pages
|
|
75
90
|
|
|
91
|
+
# Process the DSR data for redaction
|
|
92
|
+
db = object_session(privacy_request)
|
|
93
|
+
if db is not None:
|
|
94
|
+
processor = DSRDataPreprocessor(db)
|
|
95
|
+
processed_dsr_data = processor.process_dsr_data(dsr_data)
|
|
96
|
+
else:
|
|
97
|
+
# Fallback if no database session available
|
|
98
|
+
processed_dsr_data = dsr_data
|
|
99
|
+
|
|
76
100
|
# report data to populate the templates
|
|
77
|
-
self.request_data =
|
|
78
|
-
self.dsr_data =
|
|
101
|
+
self.request_data = map_privacy_request(privacy_request)
|
|
102
|
+
self.dsr_data = processed_dsr_data
|
|
103
|
+
self.enable_streaming = enable_streaming
|
|
104
|
+
|
|
105
|
+
# Track used filenames per dataset to prevent conflicts within the same dataset
|
|
106
|
+
# Maps dataset_name -> set of used filenames
|
|
107
|
+
self.used_filenames_per_dataset: dict[str, set[str]] = {}
|
|
79
108
|
|
|
80
|
-
# Track
|
|
81
|
-
|
|
109
|
+
# Track attachments by their unique identifier to prevent duplicate processing
|
|
110
|
+
# Maps (download_url, file_name) -> unique_filename
|
|
111
|
+
self.processed_attachments: dict[tuple[str, str], str] = {}
|
|
112
|
+
# Track which attachments were processed as dataset attachments (not top-level)
|
|
113
|
+
self.dataset_processed_attachments: set[tuple[str, str]] = set()
|
|
114
|
+
|
|
115
|
+
def _get_download_link_ttl_days(self) -> int:
|
|
116
|
+
"""Get the download link TTL in days from the security configuration."""
|
|
117
|
+
return int(CONFIG.security.subject_request_download_link_ttl_seconds / 86400)
|
|
82
118
|
|
|
83
119
|
def _populate_template(
|
|
84
120
|
self,
|
|
@@ -125,10 +161,6 @@ class DsrReportBuilder:
|
|
|
125
161
|
"""
|
|
126
162
|
Generates a page for each collection in the dataset and an index page for the dataset.
|
|
127
163
|
Tracks the generated links to build a root level index after each collection has been processed.
|
|
128
|
-
|
|
129
|
-
Args:
|
|
130
|
-
dataset_name: the name of the dataset to add
|
|
131
|
-
collections: the collections to add to the dataset
|
|
132
164
|
"""
|
|
133
165
|
# track links to collection indexes
|
|
134
166
|
collection_links = {}
|
|
@@ -148,34 +180,12 @@ class DsrReportBuilder:
|
|
|
148
180
|
),
|
|
149
181
|
)
|
|
150
182
|
|
|
151
|
-
|
|
152
|
-
"""
|
|
153
|
-
Generates a unique filename by appending a counter if the file already exists.
|
|
154
|
-
Now tracks filenames across all directories to ensure global uniqueness.
|
|
155
|
-
|
|
156
|
-
Args:
|
|
157
|
-
filename: The original filename
|
|
158
|
-
|
|
159
|
-
Returns:
|
|
160
|
-
A unique filename that won't conflict with existing files
|
|
161
|
-
"""
|
|
162
|
-
base_name, extension = os.path.splitext(filename)
|
|
163
|
-
counter = 1
|
|
164
|
-
unique_filename = filename
|
|
165
|
-
|
|
166
|
-
# Check if file exists in used_filenames set
|
|
167
|
-
while unique_filename in self.used_filenames:
|
|
168
|
-
unique_filename = f"{base_name}_{counter}{extension}"
|
|
169
|
-
counter += 1
|
|
170
|
-
|
|
171
|
-
# Add the new filename to the set
|
|
172
|
-
self.used_filenames.add(unique_filename)
|
|
173
|
-
return unique_filename
|
|
174
|
-
|
|
183
|
+
# pylint: disable=too-many-branches
|
|
175
184
|
def _write_attachment_content(
|
|
176
185
|
self,
|
|
177
186
|
attachments: list[dict[str, Any]],
|
|
178
187
|
directory: str,
|
|
188
|
+
dataset_name: str = "attachments",
|
|
179
189
|
) -> dict[str, dict[str, str]]:
|
|
180
190
|
"""
|
|
181
191
|
Processes attachments and returns a dictionary mapping filenames to their download URLs and sizes.
|
|
@@ -194,51 +204,150 @@ class DsrReportBuilder:
|
|
|
194
204
|
if not isinstance(attachment, dict):
|
|
195
205
|
continue
|
|
196
206
|
|
|
197
|
-
|
|
198
|
-
if not
|
|
199
|
-
|
|
207
|
+
# Get or create the used_filenames set for this dataset
|
|
208
|
+
if dataset_name not in self.used_filenames_per_dataset:
|
|
209
|
+
self.used_filenames_per_dataset[dataset_name] = set()
|
|
210
|
+
used_filenames = self.used_filenames_per_dataset[dataset_name]
|
|
211
|
+
|
|
212
|
+
# Process attachment naming using shared utility
|
|
213
|
+
result = process_attachment_naming(
|
|
214
|
+
attachment, used_filenames, self.processed_attachments, dataset_name
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
if result is None: # Skip if processing failed
|
|
200
218
|
continue
|
|
201
219
|
|
|
220
|
+
unique_filename, attachment_key = result
|
|
221
|
+
# Track that this attachment was processed as a dataset attachment
|
|
222
|
+
self.dataset_processed_attachments.add(attachment_key)
|
|
223
|
+
|
|
224
|
+
# Format file size using shared utility
|
|
225
|
+
file_size = format_attachment_size(attachment.get("file_size"))
|
|
226
|
+
|
|
227
|
+
# Determine the actual directory for this attachment based on its context
|
|
228
|
+
actual_directory = resolve_directory_from_context(attachment, directory)
|
|
229
|
+
|
|
230
|
+
# Generate attachment URL using shared utility with actual storage path
|
|
202
231
|
download_url = attachment.get("download_url")
|
|
203
232
|
if not download_url:
|
|
204
|
-
logger.warning("Skipping attachment with no download URL")
|
|
205
233
|
continue
|
|
206
234
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
235
|
+
attachment_url = generate_attachment_url_from_storage_path(
|
|
236
|
+
download_url,
|
|
237
|
+
unique_filename,
|
|
238
|
+
actual_directory, # This is the base_path where the file will be stored
|
|
239
|
+
actual_directory, # This is the HTML template directory
|
|
240
|
+
self.enable_streaming,
|
|
241
|
+
)
|
|
212
242
|
|
|
213
|
-
#
|
|
214
|
-
|
|
243
|
+
# Create attachment info dictionary using shared utility
|
|
244
|
+
file_name = attachment.get("file_name")
|
|
245
|
+
if not file_name:
|
|
246
|
+
continue
|
|
215
247
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
(unique_filename, {"url": download_url, "size": file_size})
|
|
248
|
+
attachment_info = create_attachment_info_dict(
|
|
249
|
+
attachment_url, file_size, file_name
|
|
219
250
|
)
|
|
220
251
|
|
|
252
|
+
processed_attachments.append((unique_filename, attachment_info))
|
|
253
|
+
|
|
221
254
|
# Convert list of tuples to dictionary
|
|
222
255
|
return dict(processed_attachments)
|
|
223
256
|
|
|
257
|
+
def _get_processed_attachments_list(
|
|
258
|
+
self, data: dict[str, Any]
|
|
259
|
+
) -> list[dict[str, Any]]:
|
|
260
|
+
"""Get all processed attachments using shared contextual logic.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
data: The DSR data dictionary
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
List of processed attachment dictionaries
|
|
267
|
+
"""
|
|
268
|
+
# Create temporary sets for compatibility with the shared function
|
|
269
|
+
used_filenames_data = set()
|
|
270
|
+
used_filenames_attachments = set()
|
|
271
|
+
|
|
272
|
+
# Populate the temporary sets from our per-dataset tracking
|
|
273
|
+
for dataset_name, filenames in self.used_filenames_per_dataset.items():
|
|
274
|
+
if dataset_name == "attachments":
|
|
275
|
+
used_filenames_attachments.update(filenames)
|
|
276
|
+
else:
|
|
277
|
+
used_filenames_data.update(filenames)
|
|
278
|
+
|
|
279
|
+
processed_attachments_list = process_attachments_contextually(
|
|
280
|
+
data,
|
|
281
|
+
used_filenames_data,
|
|
282
|
+
used_filenames_attachments,
|
|
283
|
+
self.processed_attachments,
|
|
284
|
+
enable_streaming=self.enable_streaming,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
# Trust the contextual processing completely - it already correctly determines
|
|
288
|
+
# context based on the attachment's location in the DSR data structure
|
|
289
|
+
filtered_list = processed_attachments_list
|
|
290
|
+
|
|
291
|
+
return filtered_list
|
|
292
|
+
|
|
293
|
+
def _generate_attachment_url_from_index(
|
|
294
|
+
self, context: dict[str, Any], unique_filename: str
|
|
295
|
+
) -> str:
|
|
296
|
+
"""Generate the correct URL from attachments/index.html to an attachment file.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
context: The attachment context information
|
|
300
|
+
unique_filename: The unique filename of the attachment
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
The relative URL from attachments/index.html to the attachment file
|
|
304
|
+
"""
|
|
305
|
+
if context.get("type") == "top_level":
|
|
306
|
+
# Top-level attachments are in the same directory as the index
|
|
307
|
+
return unique_filename
|
|
308
|
+
if context.get("type") in ["direct", "nested"]:
|
|
309
|
+
# Dataset attachments are in data/dataset/collection/attachments/
|
|
310
|
+
# From attachments/index.html, we need to go to ../data/dataset/collection/attachments/filename
|
|
311
|
+
dataset = context.get("dataset", "unknown")
|
|
312
|
+
collection = context.get("collection", "unknown")
|
|
313
|
+
return f"../data/{dataset}/{collection}/attachments/{unique_filename}"
|
|
314
|
+
# Fallback for other cases - return just the filename
|
|
315
|
+
return unique_filename
|
|
316
|
+
|
|
317
|
+
def _create_attachment_info_with_corrected_url(
|
|
318
|
+
self, attachment_info: dict[str, str], correct_url: str
|
|
319
|
+
) -> dict[str, str]:
|
|
320
|
+
"""Create attachment info with corrected URL.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
attachment_info: The original attachment info
|
|
324
|
+
correct_url: The corrected URL
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
New attachment info with corrected URL and safe_url
|
|
328
|
+
"""
|
|
329
|
+
corrected_attachment_info = attachment_info.copy()
|
|
330
|
+
corrected_attachment_info["url"] = correct_url
|
|
331
|
+
corrected_attachment_info["safe_url"] = correct_url
|
|
332
|
+
return corrected_attachment_info
|
|
333
|
+
|
|
224
334
|
def _add_collection(
|
|
225
|
-
self,
|
|
335
|
+
self,
|
|
336
|
+
rows: list[dict[str, Any]],
|
|
337
|
+
dataset_name: str,
|
|
338
|
+
collection_name: str,
|
|
226
339
|
) -> None:
|
|
227
340
|
"""
|
|
228
341
|
Adds a collection to the zip file.
|
|
229
|
-
|
|
230
|
-
Args:
|
|
231
|
-
rows: the rows to add to the collection
|
|
232
|
-
dataset_name: the name of the dataset to add the collection to
|
|
233
|
-
collection_name: the name of the collection to add
|
|
234
342
|
"""
|
|
235
343
|
items_content = []
|
|
236
344
|
|
|
237
|
-
for
|
|
238
|
-
# Create a copy of the item data to avoid modifying the original
|
|
239
|
-
|
|
345
|
+
for item_index, collection_item in enumerate(rows, 1):
|
|
346
|
+
# Create a deep copy of the item data to avoid modifying the original DSR data
|
|
347
|
+
# This ensures the comprehensive attachments index can access unmodified attachments
|
|
348
|
+
item_data = copy.deepcopy(collection_item)
|
|
240
349
|
|
|
241
|
-
# Process any attachments in the item
|
|
350
|
+
# Process any attachments in the item - First check for direct attachments key
|
|
242
351
|
if "attachments" in item_data and isinstance(
|
|
243
352
|
item_data["attachments"], list
|
|
244
353
|
):
|
|
@@ -246,15 +355,40 @@ class DsrReportBuilder:
|
|
|
246
355
|
attachment_links = self._write_attachment_content(
|
|
247
356
|
item_data["attachments"],
|
|
248
357
|
f"data/{dataset_name}/{collection_name}",
|
|
358
|
+
dataset_name,
|
|
249
359
|
)
|
|
250
360
|
# Add the attachment URLs to the item data
|
|
251
361
|
item_data["attachments"] = attachment_links
|
|
252
|
-
|
|
253
|
-
|
|
362
|
+
else:
|
|
363
|
+
# Check for nested attachment fields (ManualTask format)
|
|
364
|
+
attachment_fields_found = []
|
|
365
|
+
for field_name, field_value in item_data.items():
|
|
366
|
+
if isinstance(field_value, list) and field_value:
|
|
367
|
+
# Check if this field contains attachment-like data
|
|
368
|
+
first_item = field_value[0]
|
|
369
|
+
if isinstance(first_item, dict) and all(
|
|
370
|
+
key in first_item
|
|
371
|
+
for key in ["file_name", "download_url", "file_size"]
|
|
372
|
+
):
|
|
373
|
+
attachment_fields_found.append(field_name)
|
|
374
|
+
|
|
375
|
+
# Process attachments and get their URLs
|
|
376
|
+
attachment_links = self._write_attachment_content(
|
|
377
|
+
field_value,
|
|
378
|
+
f"data/{dataset_name}/{collection_name}",
|
|
379
|
+
dataset_name,
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
# Replace the field value with processed attachment links
|
|
383
|
+
item_data[field_name] = attachment_links
|
|
384
|
+
|
|
385
|
+
# Add item content to the list with item heading
|
|
386
|
+
# Field names are already redacted in the processed data
|
|
387
|
+
item_heading = f"{collection_name} (item #{item_index})"
|
|
254
388
|
items_content.append(
|
|
255
389
|
{
|
|
256
|
-
"index":
|
|
257
|
-
"heading":
|
|
390
|
+
"index": item_index,
|
|
391
|
+
"heading": item_heading,
|
|
258
392
|
"data": item_data,
|
|
259
393
|
}
|
|
260
394
|
)
|
|
@@ -294,34 +428,67 @@ class DsrReportBuilder:
|
|
|
294
428
|
),
|
|
295
429
|
)
|
|
296
430
|
|
|
297
|
-
def
|
|
431
|
+
def _add_comprehensive_attachments_index(self) -> None:
|
|
298
432
|
"""
|
|
299
|
-
|
|
433
|
+
Creates a comprehensive attachments index that includes ALL attachments
|
|
434
|
+
from all datasets and top-level attachments, with links pointing to their
|
|
435
|
+
actual storage locations.
|
|
300
436
|
"""
|
|
301
|
-
#
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
437
|
+
# Get all processed attachments using shared logic on original DSR data
|
|
438
|
+
processed_attachments_list = self._get_processed_attachments_list(self.dsr_data)
|
|
439
|
+
|
|
440
|
+
# Create a comprehensive attachment links dictionary with deduplication
|
|
441
|
+
all_attachment_links = {}
|
|
442
|
+
seen_attachment_keys = set()
|
|
443
|
+
|
|
444
|
+
for processed_attachment in processed_attachments_list:
|
|
445
|
+
unique_filename = processed_attachment["unique_filename"]
|
|
446
|
+
attachment_info = processed_attachment["attachment_info"]
|
|
447
|
+
context = processed_attachment["context"]
|
|
448
|
+
attachment = processed_attachment["attachment"]
|
|
449
|
+
|
|
450
|
+
# Create a unique key based on download_url to avoid duplicates
|
|
451
|
+
attachment_key = attachment.get("download_url")
|
|
452
|
+
if attachment_key in seen_attachment_keys:
|
|
307
453
|
continue
|
|
454
|
+
seen_attachment_keys.add(attachment_key)
|
|
308
455
|
|
|
309
|
-
|
|
310
|
-
if
|
|
311
|
-
|
|
456
|
+
# Generate the correct URL based on streaming settings
|
|
457
|
+
if self.enable_streaming:
|
|
458
|
+
# For streaming mode, use local attachment references
|
|
459
|
+
correct_url = self._generate_attachment_url_from_index(
|
|
460
|
+
context, unique_filename
|
|
461
|
+
)
|
|
462
|
+
else:
|
|
463
|
+
# For non-streaming mode, use original download URLs
|
|
464
|
+
correct_url = attachment.get("download_url", unique_filename)
|
|
465
|
+
|
|
466
|
+
# Create a descriptive key that includes the source location
|
|
467
|
+
if context.get("type") == "top_level":
|
|
468
|
+
key = f"Top-level: {unique_filename}"
|
|
469
|
+
elif context.get("type") in ["direct", "nested"]:
|
|
470
|
+
dataset = context.get("dataset", "unknown")
|
|
471
|
+
collection = context.get("collection", "unknown")
|
|
472
|
+
key = f"{dataset}/{collection}: {unique_filename}"
|
|
312
473
|
else:
|
|
313
|
-
|
|
314
|
-
if "system_name" in row:
|
|
315
|
-
dataset_name = row["system_name"]
|
|
316
|
-
collection_name = parts[0]
|
|
317
|
-
break
|
|
318
|
-
else:
|
|
319
|
-
dataset_name = "manual"
|
|
320
|
-
collection_name = parts[0]
|
|
474
|
+
key = unique_filename
|
|
321
475
|
|
|
322
|
-
|
|
476
|
+
# Create new attachment info with the correct URL
|
|
477
|
+
corrected_attachment_info = self._create_attachment_info_with_corrected_url(
|
|
478
|
+
attachment_info, correct_url
|
|
479
|
+
)
|
|
480
|
+
all_attachment_links[key] = corrected_attachment_info
|
|
323
481
|
|
|
324
|
-
|
|
482
|
+
# Generate comprehensive attachments index page
|
|
483
|
+
self._add_file(
|
|
484
|
+
"attachments/index.html",
|
|
485
|
+
self._populate_template(
|
|
486
|
+
"templates/attachments_index.html",
|
|
487
|
+
"All Attachments",
|
|
488
|
+
"All files attached to this privacy request",
|
|
489
|
+
all_attachment_links,
|
|
490
|
+
),
|
|
491
|
+
)
|
|
325
492
|
|
|
326
493
|
def generate(self) -> BytesIO:
|
|
327
494
|
"""
|
|
@@ -343,7 +510,7 @@ class DsrReportBuilder:
|
|
|
343
510
|
)
|
|
344
511
|
|
|
345
512
|
# pre-process data to split the dataset:collection keys
|
|
346
|
-
datasets: dict[str, Any] = self.
|
|
513
|
+
datasets: dict[str, Any] = _get_datasets_from_dsr_data(self.dsr_data)
|
|
347
514
|
|
|
348
515
|
# Sort datasets alphabetically, excluding special cases
|
|
349
516
|
regular_datasets = [
|
|
@@ -358,12 +525,35 @@ class DsrReportBuilder:
|
|
|
358
525
|
# Add Additional Data if it exists
|
|
359
526
|
if "dataset" in datasets:
|
|
360
527
|
self._add_dataset("dataset", datasets["dataset"])
|
|
528
|
+
# Use a more friendly name for the link but keep the dataset name for the path
|
|
361
529
|
self.main_links["Additional Data"] = "data/dataset/index.html"
|
|
362
530
|
|
|
363
|
-
# Add
|
|
364
|
-
if
|
|
365
|
-
|
|
366
|
-
self.
|
|
531
|
+
# Add comprehensive attachments index that includes ALL attachments
|
|
532
|
+
# Check if there are any attachments at all (top-level or in datasets)
|
|
533
|
+
has_top_level_attachments = (
|
|
534
|
+
"attachments" in self.dsr_data and self.dsr_data["attachments"]
|
|
535
|
+
)
|
|
536
|
+
has_dataset_attachments = any(
|
|
537
|
+
any(
|
|
538
|
+
"attachments" in item
|
|
539
|
+
or any(
|
|
540
|
+
is_attachment_field(field_value)
|
|
541
|
+
for field_value in item.values()
|
|
542
|
+
if isinstance(field_value, list)
|
|
543
|
+
)
|
|
544
|
+
for item in collection_items
|
|
545
|
+
if isinstance(item, dict)
|
|
546
|
+
)
|
|
547
|
+
for collection in datasets.values()
|
|
548
|
+
if isinstance(collection, dict)
|
|
549
|
+
for collection_items in collection.values()
|
|
550
|
+
if isinstance(collection_items, list)
|
|
551
|
+
)
|
|
552
|
+
has_attachments = has_top_level_attachments or has_dataset_attachments
|
|
553
|
+
|
|
554
|
+
if has_attachments:
|
|
555
|
+
self._add_comprehensive_attachments_index()
|
|
556
|
+
self.main_links["All Attachments"] = "attachments/index.html"
|
|
367
557
|
|
|
368
558
|
# create the main index once all the datasets have been added
|
|
369
559
|
self._add_file(
|
|
@@ -387,27 +577,3 @@ class DsrReportBuilder:
|
|
|
387
577
|
"DSR report generation complete."
|
|
388
578
|
)
|
|
389
579
|
return self.baos
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
def _map_privacy_request(privacy_request: "PrivacyRequest") -> dict[str, Any]:
|
|
393
|
-
"""Creates a map with a subset of values from the privacy request"""
|
|
394
|
-
request_data: dict[str, Any] = {}
|
|
395
|
-
request_data["id"] = privacy_request.id
|
|
396
|
-
|
|
397
|
-
action_type: Optional[ActionType] = privacy_request.policy.get_action_type()
|
|
398
|
-
if action_type:
|
|
399
|
-
request_data["type"] = action_type.value
|
|
400
|
-
|
|
401
|
-
request_data["identity"] = {
|
|
402
|
-
key: value
|
|
403
|
-
for key, value in privacy_request.get_persisted_identity()
|
|
404
|
-
.labeled_dict(include_default_labels=True)
|
|
405
|
-
.items()
|
|
406
|
-
if value["value"] is not None
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
if privacy_request.requested_at:
|
|
410
|
-
request_data["requested_at"] = privacy_request.requested_at.strftime(
|
|
411
|
-
"%m/%d/%Y %H:%M %Z"
|
|
412
|
-
)
|
|
413
|
-
return request_data
|
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
</a>
|
|
15
15
|
</div>
|
|
16
16
|
<h1>Attachments</h1>
|
|
17
|
-
|
|
17
|
+
{% if not enable_streaming %}
|
|
18
|
+
<p class="expiration-notice">Note: All download links will expire in {{ download_link_ttl_days }} days.</p>
|
|
19
|
+
{% endif %}
|
|
18
20
|
<div class="table table-hover">
|
|
19
21
|
<div class="table-row">
|
|
20
22
|
<div class="table-cell" style="text-align: left;">File Name</div>
|
|
@@ -22,7 +24,7 @@
|
|
|
22
24
|
</div>
|
|
23
25
|
{% for name, info in data.items() %}
|
|
24
26
|
<a href="{{ info.url }}" class="table-row" target="_blank">
|
|
25
|
-
<div class="table-cell" style="text-align: left;">{{ name }}</div>
|
|
27
|
+
<div class="table-cell" style="text-align: left;">{% if enable_streaming %}{{ name }}{% else %}{{ info.original_name }}{% endif %}</div>
|
|
26
28
|
<div class="table-cell" style="text-align: left;">{{ info.size }}</div>
|
|
27
29
|
</a>
|
|
28
30
|
{% endfor %}
|
|
@@ -35,7 +35,9 @@
|
|
|
35
35
|
{% endif %}
|
|
36
36
|
|
|
37
37
|
{% if _is_attachment_block %}
|
|
38
|
-
|
|
38
|
+
{% if not enable_streaming %}
|
|
39
|
+
<p class="expiration-notice">Note: All download links will expire in {{ download_link_ttl_days }} days.</p>
|
|
40
|
+
{% endif %}
|
|
39
41
|
<div class="table table-hover">
|
|
40
42
|
<div class="table-row">
|
|
41
43
|
<div class="table-cell" style="text-align: left;">File Name</div>
|