ethyca-fides 2.69.0rc9__py2.py3-none-any.whl → 2.69.1__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ethyca-fides might be problematic. Click here for more details.

Files changed (206) hide show
  1. {ethyca_fides-2.69.0rc9.dist-info → ethyca_fides-2.69.1.dist-info}/METADATA +2 -2
  2. {ethyca_fides-2.69.0rc9.dist-info → ethyca_fides-2.69.1.dist-info}/RECORD +204 -195
  3. fides/_version.py +3 -3
  4. fides/api/alembic/migrations/versions/78dbe23d8204_adding_privacy_request_redaction_patterns.py +52 -0
  5. fides/api/api/v1/api.py +2 -0
  6. fides/api/api/v1/endpoints/dsr_package_link.py +2 -2
  7. fides/api/api/v1/endpoints/oauth_endpoints.py +20 -6
  8. fides/api/api/v1/endpoints/privacy_request_redaction_patterns_endpoints.py +95 -0
  9. fides/api/api/v1/endpoints/user_endpoints.py +28 -1
  10. fides/api/app_setup.py +16 -2
  11. fides/api/db/base.py +3 -0
  12. fides/api/main.py +22 -0
  13. fides/api/models/client.py +1 -0
  14. fides/api/models/privacy_request_redaction_pattern.py +64 -0
  15. fides/api/oauth/utils.py +117 -6
  16. fides/api/schemas/privacy_request_redaction_patterns.py +55 -0
  17. fides/api/service/privacy_request/dsr_package/dsr_data_preprocessor.py +231 -0
  18. fides/api/service/privacy_request/dsr_package/dsr_report_builder.py +286 -120
  19. fides/api/service/privacy_request/dsr_package/templates/attachments_index.html +4 -2
  20. fides/api/service/privacy_request/dsr_package/templates/collection_index.html +3 -1
  21. fides/api/service/privacy_request/dsr_package/templates/dataset_index.html +1 -1
  22. fides/api/service/privacy_request/dsr_package/utils.py +268 -0
  23. fides/api/service/privacy_request/request_runner_service.py +8 -2
  24. fides/api/service/storage/streaming/smart_open_streaming_storage.py +107 -170
  25. fides/api/service/storage/util.py +579 -0
  26. fides/api/task/manual/manual_task_graph_task.py +11 -9
  27. fides/api/tasks/storage.py +2 -2
  28. fides/api/util/endpoint_utils.py +0 -13
  29. fides/api/util/rate_limit.py +194 -0
  30. fides/common/api/scope_registry.py +8 -0
  31. fides/common/api/v1/urn_registry.py +3 -0
  32. fides/config/redis_settings.py +27 -3
  33. fides/config/security_settings.py +31 -9
  34. fides/ui-build/static/admin/404.html +1 -1
  35. fides/ui-build/static/admin/_next/static/1TigfgzjzHeoVqRLNIMYa/_buildManifest.js +1 -0
  36. fides/ui-build/static/admin/_next/static/chunks/4831-fd99c0b3784de128.js +1 -0
  37. fides/ui-build/static/admin/_next/static/chunks/pages/{_app-ef8e1c986bc5b795.js → _app-fcdad91f6f66292b.js} +1 -1
  38. fides/ui-build/static/admin/_next/static/chunks/pages/settings/privacy-requests-2ecc073f41628f62.js +1 -0
  39. fides/ui-build/static/admin/_next/static/chunks/pages/user-management/{new-de8cb3739ab99c09.js → new-92f52c43f522a350.js} +1 -1
  40. fides/ui-build/static/admin/_next/static/chunks/pages/user-management/profile/{[id]-05d61c80a556b2d5.js → [id]-64452dfae2c5e614.js} +1 -1
  41. fides/ui-build/static/admin/add-systems/manual.html +1 -1
  42. fides/ui-build/static/admin/add-systems/multiple.html +1 -1
  43. fides/ui-build/static/admin/add-systems.html +1 -1
  44. fides/ui-build/static/admin/consent/configure/add-vendors.html +1 -1
  45. fides/ui-build/static/admin/consent/configure.html +1 -1
  46. fides/ui-build/static/admin/consent/privacy-experience/[id].html +1 -1
  47. fides/ui-build/static/admin/consent/privacy-experience/new.html +1 -1
  48. fides/ui-build/static/admin/consent/privacy-experience.html +1 -1
  49. fides/ui-build/static/admin/consent/privacy-notices/[id].html +1 -1
  50. fides/ui-build/static/admin/consent/privacy-notices/new.html +1 -1
  51. fides/ui-build/static/admin/consent/privacy-notices.html +1 -1
  52. fides/ui-build/static/admin/consent/properties.html +1 -1
  53. fides/ui-build/static/admin/consent/reporting.html +1 -1
  54. fides/ui-build/static/admin/consent.html +1 -1
  55. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn]/[resourceUrn].html +1 -1
  56. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn].html +1 -1
  57. fides/ui-build/static/admin/data-catalog/[systemId]/projects.html +1 -1
  58. fides/ui-build/static/admin/data-catalog/[systemId]/resources/[resourceUrn].html +1 -1
  59. fides/ui-build/static/admin/data-catalog/[systemId]/resources.html +1 -1
  60. fides/ui-build/static/admin/data-catalog.html +1 -1
  61. fides/ui-build/static/admin/data-discovery/action-center/[monitorId]/[systemId].html +1 -1
  62. fides/ui-build/static/admin/data-discovery/action-center/[monitorId].html +1 -1
  63. fides/ui-build/static/admin/data-discovery/action-center.html +1 -1
  64. fides/ui-build/static/admin/data-discovery/activity.html +1 -1
  65. fides/ui-build/static/admin/data-discovery/detection/[resourceUrn].html +1 -1
  66. fides/ui-build/static/admin/data-discovery/detection.html +1 -1
  67. fides/ui-build/static/admin/data-discovery/discovery/[resourceUrn].html +1 -1
  68. fides/ui-build/static/admin/data-discovery/discovery.html +1 -1
  69. fides/ui-build/static/admin/datamap.html +1 -1
  70. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName]/[...subfieldNames].html +1 -1
  71. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName].html +1 -1
  72. fides/ui-build/static/admin/dataset/[datasetId].html +1 -1
  73. fides/ui-build/static/admin/dataset/new.html +1 -1
  74. fides/ui-build/static/admin/dataset.html +1 -1
  75. fides/ui-build/static/admin/datastore-connection/[id].html +1 -1
  76. fides/ui-build/static/admin/datastore-connection/new.html +1 -1
  77. fides/ui-build/static/admin/datastore-connection.html +1 -1
  78. fides/ui-build/static/admin/index.html +1 -1
  79. fides/ui-build/static/admin/integrations/[id].html +1 -1
  80. fides/ui-build/static/admin/integrations.html +1 -1
  81. fides/ui-build/static/admin/login/[provider].html +1 -1
  82. fides/ui-build/static/admin/login.html +1 -1
  83. fides/ui-build/static/admin/messaging/[id].html +1 -1
  84. fides/ui-build/static/admin/messaging/add-template.html +1 -1
  85. fides/ui-build/static/admin/messaging.html +1 -1
  86. fides/ui-build/static/admin/poc/ant-components.html +1 -1
  87. fides/ui-build/static/admin/poc/form-experiments/AntForm.html +1 -1
  88. fides/ui-build/static/admin/poc/form-experiments/FormikAntFormItem.html +1 -1
  89. fides/ui-build/static/admin/poc/form-experiments/FormikControlled.html +1 -1
  90. fides/ui-build/static/admin/poc/form-experiments/FormikField.html +1 -1
  91. fides/ui-build/static/admin/poc/form-experiments/FormikSpreadField.html +1 -1
  92. fides/ui-build/static/admin/poc/forms.html +1 -1
  93. fides/ui-build/static/admin/poc/table-migration.html +1 -1
  94. fides/ui-build/static/admin/privacy-requests/[id].html +1 -1
  95. fides/ui-build/static/admin/privacy-requests/configure/messaging.html +1 -1
  96. fides/ui-build/static/admin/privacy-requests/configure/storage.html +1 -1
  97. fides/ui-build/static/admin/privacy-requests/configure.html +1 -1
  98. fides/ui-build/static/admin/privacy-requests.html +1 -1
  99. fides/ui-build/static/admin/properties/[id].html +1 -1
  100. fides/ui-build/static/admin/properties/add-property.html +1 -1
  101. fides/ui-build/static/admin/properties.html +1 -1
  102. fides/ui-build/static/admin/reporting/datamap.html +1 -1
  103. fides/ui-build/static/admin/settings/about/alpha.html +1 -1
  104. fides/ui-build/static/admin/settings/about.html +1 -1
  105. fides/ui-build/static/admin/settings/consent/[configuration_id]/[purpose_id].html +1 -1
  106. fides/ui-build/static/admin/settings/consent.html +1 -1
  107. fides/ui-build/static/admin/settings/custom-fields.html +1 -1
  108. fides/ui-build/static/admin/settings/domain-records.html +1 -1
  109. fides/ui-build/static/admin/settings/domains.html +1 -1
  110. fides/ui-build/static/admin/settings/email-templates.html +1 -1
  111. fides/ui-build/static/admin/settings/locations.html +1 -1
  112. fides/ui-build/static/admin/settings/organization.html +1 -1
  113. fides/ui-build/static/admin/settings/privacy-requests.html +1 -0
  114. fides/ui-build/static/admin/settings/regulations.html +1 -1
  115. fides/ui-build/static/admin/systems/configure/[id]/test-datasets.html +1 -1
  116. fides/ui-build/static/admin/systems/configure/[id].html +1 -1
  117. fides/ui-build/static/admin/systems.html +1 -1
  118. fides/ui-build/static/admin/taxonomy.html +1 -1
  119. fides/ui-build/static/admin/user-management/new.html +1 -1
  120. fides/ui-build/static/admin/user-management/profile/[id].html +1 -1
  121. fides/ui-build/static/admin/user-management.html +1 -1
  122. fides/ui-build/static/admin/_next/static/XiHm-6CdVChTC5rbN9GtT/_buildManifest.js +0 -1
  123. fides/ui-build/static/admin/_next/static/chunks/4121-c8d5d717e31899e1.js +0 -1
  124. {ethyca_fides-2.69.0rc9.dist-info → ethyca_fides-2.69.1.dist-info}/WHEEL +0 -0
  125. {ethyca_fides-2.69.0rc9.dist-info → ethyca_fides-2.69.1.dist-info}/entry_points.txt +0 -0
  126. {ethyca_fides-2.69.0rc9.dist-info → ethyca_fides-2.69.1.dist-info}/licenses/LICENSE +0 -0
  127. {ethyca_fides-2.69.0rc9.dist-info → ethyca_fides-2.69.1.dist-info}/top_level.txt +0 -0
  128. /fides/ui-build/static/admin/_next/static/{XiHm-6CdVChTC5rbN9GtT → 1TigfgzjzHeoVqRLNIMYa}/_ssgManifest.js +0 -0
  129. /fides/ui-build/static/admin/_next/static/chunks/{1817-3d9e110e007853f0.js → 1817-0ca16d288fad916d.js} +0 -0
  130. /fides/ui-build/static/admin/_next/static/chunks/{3620-31ebb43dba84cbbd.js → 3620-602eb74dc896d556.js} +0 -0
  131. /fides/ui-build/static/admin/_next/static/chunks/{3729-a1ca1608efc11ac4.js → 3729-c17ac8031a4c4fd1.js} +0 -0
  132. /fides/ui-build/static/admin/_next/static/chunks/{3872-a91143aa35fa8ef8.js → 3872-f78dec02f0d959ae.js} +0 -0
  133. /fides/ui-build/static/admin/_next/static/chunks/{4608-23bbd4c3c4a59f42.js → 4608-be8cba73f5d7c326.js} +0 -0
  134. /fides/ui-build/static/admin/_next/static/chunks/{4786-0827aae7aceadd22.js → 4786-61154adf88e448e1.js} +0 -0
  135. /fides/ui-build/static/admin/_next/static/chunks/{4808-78ca630f2d2503cd.js → 4808-dd4157aa72648068.js} +0 -0
  136. /fides/ui-build/static/admin/_next/static/chunks/{5487-8c635883dcaa9c2a.js → 5487-02d00bad7c6830e0.js} +0 -0
  137. /fides/ui-build/static/admin/_next/static/chunks/{6084-0096d7de64ef8015.js → 6084-c153669d5567e242.js} +0 -0
  138. /fides/ui-build/static/admin/_next/static/chunks/{6954-9d46e2276c461c26.js → 6954-5296188c19d7d0ac.js} +0 -0
  139. /fides/ui-build/static/admin/_next/static/chunks/{7476-d1b0af9ade392e5b.js → 7476-45c5088baa8b66af.js} +0 -0
  140. /fides/ui-build/static/admin/_next/static/chunks/{7630-da0a7ce4e3a0d62c.js → 7630-7ed6c6117775dffe.js} +0 -0
  141. /fides/ui-build/static/admin/_next/static/chunks/{787-3499983fa346b380.js → 787-a8c7eab617e2fceb.js} +0 -0
  142. /fides/ui-build/static/admin/_next/static/chunks/{79-f197fc4db8d530e5.js → 79-65674011d455af4d.js} +0 -0
  143. /fides/ui-build/static/admin/_next/static/chunks/{796-db1e30119ea973c7.js → 796-9e1ca1a4030707c5.js} +0 -0
  144. /fides/ui-build/static/admin/_next/static/chunks/{8002-971e29181f72edd1.js → 8002-24af20d679efc04e.js} +0 -0
  145. /fides/ui-build/static/admin/_next/static/chunks/{9826-b0b3d3cfb13bfbc1.js → 9826-dbae8dee941a7fac.js} +0 -0
  146. /fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/{manual-9dc7e70ab5b05723.js → manual-ace203dfacacbdc4.js} +0 -0
  147. /fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/{multiple-4b79a1652297ed9a.js → multiple-920fb469e0dda1d2.js} +0 -0
  148. /fides/ui-build/static/admin/_next/static/chunks/pages/{add-systems-1632a59203fe8eab.js → add-systems-bd0d82078e67cac3.js} +0 -0
  149. /fides/ui-build/static/admin/_next/static/chunks/pages/consent/configure/{add-vendors-1ca9df7ca91bd101.js → add-vendors-406170eaae4329c6.js} +0 -0
  150. /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{configure-07bdbc9ae4137db4.js → configure-7207ab23bdb36ce8.js} +0 -0
  151. /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{privacy-experience-2795cd4115a77c94.js → privacy-experience-9dda4de5ec580279.js} +0 -0
  152. /fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{[id]-e02921dc82dccbb1.js → [id]-b378576cba255609.js} +0 -0
  153. /fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{new-98f9e4ba3610628a.js → new-2ca1de7b88094ab0.js} +0 -0
  154. /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{privacy-notices-17ed82777810d1c6.js → privacy-notices-0d4844d0b808e6e4.js} +0 -0
  155. /fides/ui-build/static/admin/_next/static/chunks/pages/{consent-09610b10923d9268.js → consent-3e8bdefe714254ec.js} +0 -0
  156. /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/[projectUrn]/{[resourceUrn]-da1a48336daff6f8.js → [resourceUrn]-2c29ff7a01198f30.js} +0 -0
  157. /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/{[projectUrn]-d8e776f1e64e4ba8.js → [projectUrn]-04cfe2cfba7b7cd8.js} +0 -0
  158. /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/{projects-75b9629b0d9cdf96.js → projects-5f2d7b24804f861f.js} +0 -0
  159. /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/resources/{[resourceUrn]-470da05db63767cd.js → [resourceUrn]-8eb581024bc0172f.js} +0 -0
  160. /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/{resources-6c3714ee97a718c1.js → resources-de704de849960f01.js} +0 -0
  161. /fides/ui-build/static/admin/_next/static/chunks/pages/{data-catalog-6984c033b8fe3a13.js → data-catalog-30108b00ac769fc3.js} +0 -0
  162. /fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/{[systemId]-2f0a33ef9ba1f1da.js → [systemId]-e1ba213fb666b3f4.js} +0 -0
  163. /fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/{[monitorId]-e9d4f25b20ff6781.js → [monitorId]-6d133580045abdda.js} +0 -0
  164. /fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{action-center-9c428d3ef0985915.js → action-center-9a81d42a474e1e48.js} +0 -0
  165. /fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/detection/{[resourceUrn]-c3a97e6721ca0abe.js → [resourceUrn]-8f736b078e9842da.js} +0 -0
  166. /fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{detection-a0a7de552ef71f5b.js → detection-eb814e3c22807871.js} +0 -0
  167. /fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/discovery/{[resourceUrn]-109754fec0755339.js → [resourceUrn]-6875b7783fcfda2f.js} +0 -0
  168. /fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{discovery-88654783b06b3b21.js → discovery-172dbd7740e212ca.js} +0 -0
  169. /fides/ui-build/static/admin/_next/static/chunks/pages/{datamap-89136e6800dc9369.js → datamap-c7390e046b2e2b7f.js} +0 -0
  170. /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/[collectionName]/{[...subfieldNames]-8f58192dcb54883d.js → [...subfieldNames]-dfd71c1e9c458b89.js} +0 -0
  171. /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/{[collectionName]-dcb4ab380a77aa1e.js → [collectionName]-7cdc42ec5493b83d.js} +0 -0
  172. /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/{[datasetId]-6f16d43071fb9c11.js → [datasetId]-e12b11ba15bc3fc1.js} +0 -0
  173. /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/{new-97f06e21580f1f6a.js → new-e32fccc4ca520d2b.js} +0 -0
  174. /fides/ui-build/static/admin/_next/static/chunks/pages/{dataset-674bb3940f088ecc.js → dataset-7c59a6abf6ba6207.js} +0 -0
  175. /fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/{[id]-6f77d8647fca71e0.js → [id]-927b7e476c4b47d0.js} +0 -0
  176. /fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/{new-821dd1269834cfa2.js → new-cbe100d50df34285.js} +0 -0
  177. /fides/ui-build/static/admin/_next/static/chunks/pages/{datastore-connection-23e4caf79faa8106.js → datastore-connection-cce20440b177050b.js} +0 -0
  178. /fides/ui-build/static/admin/_next/static/chunks/pages/{index-23eb64eed81dcb69.js → index-6cd8708106331b8d.js} +0 -0
  179. /fides/ui-build/static/admin/_next/static/chunks/pages/integrations/{[id]-3a4cd3fe9094fba3.js → [id]-4c3c413a2668df53.js} +0 -0
  180. /fides/ui-build/static/admin/_next/static/chunks/pages/{integrations-57e618d7b16ac69a.js → integrations-95402b5001c07ef2.js} +0 -0
  181. /fides/ui-build/static/admin/_next/static/chunks/pages/messaging/{[id]-c9a323eb6a929476.js → [id]-3c6dc2f6e6bae960.js} +0 -0
  182. /fides/ui-build/static/admin/_next/static/chunks/pages/messaging/{add-template-b9bb09e46921a590.js → add-template-4a6d4023a7791be8.js} +0 -0
  183. /fides/ui-build/static/admin/_next/static/chunks/pages/{messaging-82c631a12b5a008c.js → messaging-76b204c9b98d656f.js} +0 -0
  184. /fides/ui-build/static/admin/_next/static/chunks/pages/poc/{table-migration-38360083348c3d6c.js → table-migration-48500551fd6a7602.js} +0 -0
  185. /fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/{[id]-0d0bb9eb004a3336.js → [id]-0f25a76dd18c5e20.js} +0 -0
  186. /fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/{messaging-f9320a58f489f5b7.js → messaging-ad6ad3e5bd72765d.js} +0 -0
  187. /fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/{storage-d0cfa8aeddd43a40.js → storage-6032d82f0fc2893d.js} +0 -0
  188. /fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/{configure-72ca94ec5ed85733.js → configure-d83e5bd52a638234.js} +0 -0
  189. /fides/ui-build/static/admin/_next/static/chunks/pages/{privacy-requests-5a5edc8a4aa7c30a.js → privacy-requests-baf31c3e4b081046.js} +0 -0
  190. /fides/ui-build/static/admin/_next/static/chunks/pages/properties/{[id]-5ec775c4904fdbfe.js → [id]-e784c05d056b2371.js} +0 -0
  191. /fides/ui-build/static/admin/_next/static/chunks/pages/properties/{add-property-a6812c0916f2949e.js → add-property-0a7a2db148a7561a.js} +0 -0
  192. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/about/{alpha-3e72e9f91991c119.js → alpha-a82f3df840d5c1b5.js} +0 -0
  193. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{about-6aab092f4871cecb.js → about-d06fb16487705b9d.js} +0 -0
  194. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{consent-be47008304106395.js → consent-93a978443bf299db.js} +0 -0
  195. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{custom-fields-ae1b57589da7b175.js → custom-fields-9ecb803099082bf4.js} +0 -0
  196. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{domain-records-23a6d7a921150188.js → domain-records-16fdd91a81074dd1.js} +0 -0
  197. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{domains-2a9e8859ab4d9de6.js → domains-4cdd6001e7cb9aee.js} +0 -0
  198. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{email-templates-4f9f0fdf9925ae90.js → email-templates-1914de830ce5cfc4.js} +0 -0
  199. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{locations-46f7af35cee4a8bb.js → locations-2e635dcd11b78224.js} +0 -0
  200. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{organization-a596a96cb8d0aa8e.js → organization-f547f1f33c12faf3.js} +0 -0
  201. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{regulations-6ed5fc2410e00857.js → regulations-7c02e469d8c5bd74.js} +0 -0
  202. /fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/[id]/{test-datasets-86811e3cda277e77.js → test-datasets-20b1193ed76c56b0.js} +0 -0
  203. /fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/{[id]-5a43f108d8047d5b.js → [id]-6e15332935f6b538.js} +0 -0
  204. /fides/ui-build/static/admin/_next/static/chunks/pages/{systems-045a841e22e85ea8.js → systems-fbc8761ef4d55516.js} +0 -0
  205. /fides/ui-build/static/admin/_next/static/chunks/pages/{taxonomy-1b3f2d4bcb0e164d.js → taxonomy-4d7827fc9c46b6b8.js} +0 -0
  206. /fides/ui-build/static/admin/_next/static/chunks/pages/{user-management-2cab41659f1ee7da.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 TYPE_CHECKING, Any, Optional
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
- from fides.api.schemas.policy import ActionType
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 DsrReportBuilder:
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
- Args:
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 = _map_privacy_request(privacy_request)
78
- self.dsr_data = 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 used filenames across all attachments
81
- self.used_filenames: set[str] = set()
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
- def _get_unique_filename(self, filename: str) -> str:
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
- file_name = attachment.get("file_name")
198
- if not file_name:
199
- logger.warning("Skipping attachment with no file name")
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
- file_size = attachment.get("file_size")
208
- if isinstance(file_size, (int, float)):
209
- file_size = format_size(float(file_size))
210
- else:
211
- file_size = "Unknown"
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
- # Get a unique filename to prevent duplicates
214
- unique_filename = self._get_unique_filename(file_name)
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
- # Add to processed attachments
217
- processed_attachments.append(
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, rows: list[dict[str, Any]], dataset_name: str, collection_name: str
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 index, collection_item in enumerate(rows, 1):
238
- # Create a copy of the item data to avoid modifying the original
239
- item_data = collection_item.copy()
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
- # Add item content to the list
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": index,
257
- "heading": f"{collection_name} (item #{index})",
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 _get_datasets_from_dsr_data(self) -> dict[str, Any]:
431
+ def _add_comprehensive_attachments_index(self) -> None:
298
432
  """
299
- Returns the datasets from the DSR data.
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
- # pre-process data to split the dataset:collection keys
302
- datasets: dict[str, Any] = defaultdict(lambda: defaultdict(list))
303
- for key, rows in self.dsr_data.items():
304
-
305
- # we handle attachments separately
306
- if key == "attachments":
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
- parts = key.split(":", 1)
310
- if len(parts) > 1:
311
- dataset_name, collection_name = parts
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
- for row in rows:
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
- datasets[dataset_name][collection_name].extend(rows)
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
- return datasets
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._get_datasets_from_dsr_data()
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 Additional Attachments last if it exists
364
- if "attachments" in self.dsr_data:
365
- self._add_attachments(self.dsr_data["attachments"])
366
- self.main_links["Additional Attachments"] = "attachments/index.html"
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
- <p class="expiration-notice">Note: All download links will expire in 7 days.</p>
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
- <p class="expiration-notice">Note: All download links will expire in 7 days.</p>
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>
@@ -27,4 +27,4 @@
27
27
  </div>
28
28
  </div>
29
29
  </body>
30
- </html>
30
+ </html>