ethyca-fides 2.69.0rc10__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 (200) hide show
  1. {ethyca_fides-2.69.0rc10.dist-info → ethyca_fides-2.69.1.dist-info}/METADATA +2 -2
  2. {ethyca_fides-2.69.0rc10.dist-info → ethyca_fides-2.69.1.dist-info}/RECORD +198 -189
  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 +31 -47
  19. fides/api/service/privacy_request/dsr_package/utils.py +268 -0
  20. fides/api/service/storage/streaming/smart_open_streaming_storage.py +3 -3
  21. fides/api/tasks/storage.py +2 -2
  22. fides/api/util/endpoint_utils.py +0 -13
  23. fides/api/util/rate_limit.py +194 -0
  24. fides/common/api/scope_registry.py +8 -0
  25. fides/common/api/v1/urn_registry.py +3 -0
  26. fides/config/redis_settings.py +27 -3
  27. fides/config/security_settings.py +31 -9
  28. fides/ui-build/static/admin/404.html +1 -1
  29. fides/ui-build/static/admin/_next/static/1TigfgzjzHeoVqRLNIMYa/_buildManifest.js +1 -0
  30. fides/ui-build/static/admin/_next/static/chunks/4831-fd99c0b3784de128.js +1 -0
  31. fides/ui-build/static/admin/_next/static/chunks/pages/{_app-ef8e1c986bc5b795.js → _app-fcdad91f6f66292b.js} +1 -1
  32. fides/ui-build/static/admin/_next/static/chunks/pages/settings/privacy-requests-2ecc073f41628f62.js +1 -0
  33. fides/ui-build/static/admin/_next/static/chunks/pages/user-management/{new-de8cb3739ab99c09.js → new-92f52c43f522a350.js} +1 -1
  34. fides/ui-build/static/admin/_next/static/chunks/pages/user-management/profile/{[id]-05d61c80a556b2d5.js → [id]-64452dfae2c5e614.js} +1 -1
  35. fides/ui-build/static/admin/add-systems/manual.html +1 -1
  36. fides/ui-build/static/admin/add-systems/multiple.html +1 -1
  37. fides/ui-build/static/admin/add-systems.html +1 -1
  38. fides/ui-build/static/admin/consent/configure/add-vendors.html +1 -1
  39. fides/ui-build/static/admin/consent/configure.html +1 -1
  40. fides/ui-build/static/admin/consent/privacy-experience/[id].html +1 -1
  41. fides/ui-build/static/admin/consent/privacy-experience/new.html +1 -1
  42. fides/ui-build/static/admin/consent/privacy-experience.html +1 -1
  43. fides/ui-build/static/admin/consent/privacy-notices/[id].html +1 -1
  44. fides/ui-build/static/admin/consent/privacy-notices/new.html +1 -1
  45. fides/ui-build/static/admin/consent/privacy-notices.html +1 -1
  46. fides/ui-build/static/admin/consent/properties.html +1 -1
  47. fides/ui-build/static/admin/consent/reporting.html +1 -1
  48. fides/ui-build/static/admin/consent.html +1 -1
  49. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn]/[resourceUrn].html +1 -1
  50. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn].html +1 -1
  51. fides/ui-build/static/admin/data-catalog/[systemId]/projects.html +1 -1
  52. fides/ui-build/static/admin/data-catalog/[systemId]/resources/[resourceUrn].html +1 -1
  53. fides/ui-build/static/admin/data-catalog/[systemId]/resources.html +1 -1
  54. fides/ui-build/static/admin/data-catalog.html +1 -1
  55. fides/ui-build/static/admin/data-discovery/action-center/[monitorId]/[systemId].html +1 -1
  56. fides/ui-build/static/admin/data-discovery/action-center/[monitorId].html +1 -1
  57. fides/ui-build/static/admin/data-discovery/action-center.html +1 -1
  58. fides/ui-build/static/admin/data-discovery/activity.html +1 -1
  59. fides/ui-build/static/admin/data-discovery/detection/[resourceUrn].html +1 -1
  60. fides/ui-build/static/admin/data-discovery/detection.html +1 -1
  61. fides/ui-build/static/admin/data-discovery/discovery/[resourceUrn].html +1 -1
  62. fides/ui-build/static/admin/data-discovery/discovery.html +1 -1
  63. fides/ui-build/static/admin/datamap.html +1 -1
  64. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName]/[...subfieldNames].html +1 -1
  65. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName].html +1 -1
  66. fides/ui-build/static/admin/dataset/[datasetId].html +1 -1
  67. fides/ui-build/static/admin/dataset/new.html +1 -1
  68. fides/ui-build/static/admin/dataset.html +1 -1
  69. fides/ui-build/static/admin/datastore-connection/[id].html +1 -1
  70. fides/ui-build/static/admin/datastore-connection/new.html +1 -1
  71. fides/ui-build/static/admin/datastore-connection.html +1 -1
  72. fides/ui-build/static/admin/index.html +1 -1
  73. fides/ui-build/static/admin/integrations/[id].html +1 -1
  74. fides/ui-build/static/admin/integrations.html +1 -1
  75. fides/ui-build/static/admin/login/[provider].html +1 -1
  76. fides/ui-build/static/admin/login.html +1 -1
  77. fides/ui-build/static/admin/messaging/[id].html +1 -1
  78. fides/ui-build/static/admin/messaging/add-template.html +1 -1
  79. fides/ui-build/static/admin/messaging.html +1 -1
  80. fides/ui-build/static/admin/poc/ant-components.html +1 -1
  81. fides/ui-build/static/admin/poc/form-experiments/AntForm.html +1 -1
  82. fides/ui-build/static/admin/poc/form-experiments/FormikAntFormItem.html +1 -1
  83. fides/ui-build/static/admin/poc/form-experiments/FormikControlled.html +1 -1
  84. fides/ui-build/static/admin/poc/form-experiments/FormikField.html +1 -1
  85. fides/ui-build/static/admin/poc/form-experiments/FormikSpreadField.html +1 -1
  86. fides/ui-build/static/admin/poc/forms.html +1 -1
  87. fides/ui-build/static/admin/poc/table-migration.html +1 -1
  88. fides/ui-build/static/admin/privacy-requests/[id].html +1 -1
  89. fides/ui-build/static/admin/privacy-requests/configure/messaging.html +1 -1
  90. fides/ui-build/static/admin/privacy-requests/configure/storage.html +1 -1
  91. fides/ui-build/static/admin/privacy-requests/configure.html +1 -1
  92. fides/ui-build/static/admin/privacy-requests.html +1 -1
  93. fides/ui-build/static/admin/properties/[id].html +1 -1
  94. fides/ui-build/static/admin/properties/add-property.html +1 -1
  95. fides/ui-build/static/admin/properties.html +1 -1
  96. fides/ui-build/static/admin/reporting/datamap.html +1 -1
  97. fides/ui-build/static/admin/settings/about/alpha.html +1 -1
  98. fides/ui-build/static/admin/settings/about.html +1 -1
  99. fides/ui-build/static/admin/settings/consent/[configuration_id]/[purpose_id].html +1 -1
  100. fides/ui-build/static/admin/settings/consent.html +1 -1
  101. fides/ui-build/static/admin/settings/custom-fields.html +1 -1
  102. fides/ui-build/static/admin/settings/domain-records.html +1 -1
  103. fides/ui-build/static/admin/settings/domains.html +1 -1
  104. fides/ui-build/static/admin/settings/email-templates.html +1 -1
  105. fides/ui-build/static/admin/settings/locations.html +1 -1
  106. fides/ui-build/static/admin/settings/organization.html +1 -1
  107. fides/ui-build/static/admin/settings/privacy-requests.html +1 -0
  108. fides/ui-build/static/admin/settings/regulations.html +1 -1
  109. fides/ui-build/static/admin/systems/configure/[id]/test-datasets.html +1 -1
  110. fides/ui-build/static/admin/systems/configure/[id].html +1 -1
  111. fides/ui-build/static/admin/systems.html +1 -1
  112. fides/ui-build/static/admin/taxonomy.html +1 -1
  113. fides/ui-build/static/admin/user-management/new.html +1 -1
  114. fides/ui-build/static/admin/user-management/profile/[id].html +1 -1
  115. fides/ui-build/static/admin/user-management.html +1 -1
  116. fides/ui-build/static/admin/_next/static/8qfO1Ol3G3QbcXpHAnPlU/_buildManifest.js +0 -1
  117. fides/ui-build/static/admin/_next/static/chunks/4121-c8d5d717e31899e1.js +0 -1
  118. {ethyca_fides-2.69.0rc10.dist-info → ethyca_fides-2.69.1.dist-info}/WHEEL +0 -0
  119. {ethyca_fides-2.69.0rc10.dist-info → ethyca_fides-2.69.1.dist-info}/entry_points.txt +0 -0
  120. {ethyca_fides-2.69.0rc10.dist-info → ethyca_fides-2.69.1.dist-info}/licenses/LICENSE +0 -0
  121. {ethyca_fides-2.69.0rc10.dist-info → ethyca_fides-2.69.1.dist-info}/top_level.txt +0 -0
  122. /fides/ui-build/static/admin/_next/static/{8qfO1Ol3G3QbcXpHAnPlU → 1TigfgzjzHeoVqRLNIMYa}/_ssgManifest.js +0 -0
  123. /fides/ui-build/static/admin/_next/static/chunks/{1817-3d9e110e007853f0.js → 1817-0ca16d288fad916d.js} +0 -0
  124. /fides/ui-build/static/admin/_next/static/chunks/{3620-31ebb43dba84cbbd.js → 3620-602eb74dc896d556.js} +0 -0
  125. /fides/ui-build/static/admin/_next/static/chunks/{3729-a1ca1608efc11ac4.js → 3729-c17ac8031a4c4fd1.js} +0 -0
  126. /fides/ui-build/static/admin/_next/static/chunks/{3872-a91143aa35fa8ef8.js → 3872-f78dec02f0d959ae.js} +0 -0
  127. /fides/ui-build/static/admin/_next/static/chunks/{4608-23bbd4c3c4a59f42.js → 4608-be8cba73f5d7c326.js} +0 -0
  128. /fides/ui-build/static/admin/_next/static/chunks/{4786-0827aae7aceadd22.js → 4786-61154adf88e448e1.js} +0 -0
  129. /fides/ui-build/static/admin/_next/static/chunks/{4808-78ca630f2d2503cd.js → 4808-dd4157aa72648068.js} +0 -0
  130. /fides/ui-build/static/admin/_next/static/chunks/{5487-8c635883dcaa9c2a.js → 5487-02d00bad7c6830e0.js} +0 -0
  131. /fides/ui-build/static/admin/_next/static/chunks/{6084-0096d7de64ef8015.js → 6084-c153669d5567e242.js} +0 -0
  132. /fides/ui-build/static/admin/_next/static/chunks/{6954-9d46e2276c461c26.js → 6954-5296188c19d7d0ac.js} +0 -0
  133. /fides/ui-build/static/admin/_next/static/chunks/{7476-d1b0af9ade392e5b.js → 7476-45c5088baa8b66af.js} +0 -0
  134. /fides/ui-build/static/admin/_next/static/chunks/{7630-da0a7ce4e3a0d62c.js → 7630-7ed6c6117775dffe.js} +0 -0
  135. /fides/ui-build/static/admin/_next/static/chunks/{787-3499983fa346b380.js → 787-a8c7eab617e2fceb.js} +0 -0
  136. /fides/ui-build/static/admin/_next/static/chunks/{79-f197fc4db8d530e5.js → 79-65674011d455af4d.js} +0 -0
  137. /fides/ui-build/static/admin/_next/static/chunks/{796-db1e30119ea973c7.js → 796-9e1ca1a4030707c5.js} +0 -0
  138. /fides/ui-build/static/admin/_next/static/chunks/{8002-971e29181f72edd1.js → 8002-24af20d679efc04e.js} +0 -0
  139. /fides/ui-build/static/admin/_next/static/chunks/{9826-b0b3d3cfb13bfbc1.js → 9826-dbae8dee941a7fac.js} +0 -0
  140. /fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/{manual-9dc7e70ab5b05723.js → manual-ace203dfacacbdc4.js} +0 -0
  141. /fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/{multiple-4b79a1652297ed9a.js → multiple-920fb469e0dda1d2.js} +0 -0
  142. /fides/ui-build/static/admin/_next/static/chunks/pages/{add-systems-1632a59203fe8eab.js → add-systems-bd0d82078e67cac3.js} +0 -0
  143. /fides/ui-build/static/admin/_next/static/chunks/pages/consent/configure/{add-vendors-1ca9df7ca91bd101.js → add-vendors-406170eaae4329c6.js} +0 -0
  144. /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{configure-07bdbc9ae4137db4.js → configure-7207ab23bdb36ce8.js} +0 -0
  145. /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{privacy-experience-2795cd4115a77c94.js → privacy-experience-9dda4de5ec580279.js} +0 -0
  146. /fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{[id]-e02921dc82dccbb1.js → [id]-b378576cba255609.js} +0 -0
  147. /fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{new-98f9e4ba3610628a.js → new-2ca1de7b88094ab0.js} +0 -0
  148. /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{privacy-notices-17ed82777810d1c6.js → privacy-notices-0d4844d0b808e6e4.js} +0 -0
  149. /fides/ui-build/static/admin/_next/static/chunks/pages/{consent-09610b10923d9268.js → consent-3e8bdefe714254ec.js} +0 -0
  150. /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/[projectUrn]/{[resourceUrn]-da1a48336daff6f8.js → [resourceUrn]-2c29ff7a01198f30.js} +0 -0
  151. /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/{[projectUrn]-d8e776f1e64e4ba8.js → [projectUrn]-04cfe2cfba7b7cd8.js} +0 -0
  152. /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/{projects-75b9629b0d9cdf96.js → projects-5f2d7b24804f861f.js} +0 -0
  153. /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/resources/{[resourceUrn]-470da05db63767cd.js → [resourceUrn]-8eb581024bc0172f.js} +0 -0
  154. /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/{resources-6c3714ee97a718c1.js → resources-de704de849960f01.js} +0 -0
  155. /fides/ui-build/static/admin/_next/static/chunks/pages/{data-catalog-6984c033b8fe3a13.js → data-catalog-30108b00ac769fc3.js} +0 -0
  156. /fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/{[systemId]-2f0a33ef9ba1f1da.js → [systemId]-e1ba213fb666b3f4.js} +0 -0
  157. /fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/{[monitorId]-e9d4f25b20ff6781.js → [monitorId]-6d133580045abdda.js} +0 -0
  158. /fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{action-center-9c428d3ef0985915.js → action-center-9a81d42a474e1e48.js} +0 -0
  159. /fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/detection/{[resourceUrn]-c3a97e6721ca0abe.js → [resourceUrn]-8f736b078e9842da.js} +0 -0
  160. /fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{detection-a0a7de552ef71f5b.js → detection-eb814e3c22807871.js} +0 -0
  161. /fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/discovery/{[resourceUrn]-109754fec0755339.js → [resourceUrn]-6875b7783fcfda2f.js} +0 -0
  162. /fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{discovery-88654783b06b3b21.js → discovery-172dbd7740e212ca.js} +0 -0
  163. /fides/ui-build/static/admin/_next/static/chunks/pages/{datamap-89136e6800dc9369.js → datamap-c7390e046b2e2b7f.js} +0 -0
  164. /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/[collectionName]/{[...subfieldNames]-8f58192dcb54883d.js → [...subfieldNames]-dfd71c1e9c458b89.js} +0 -0
  165. /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/{[collectionName]-dcb4ab380a77aa1e.js → [collectionName]-7cdc42ec5493b83d.js} +0 -0
  166. /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/{[datasetId]-6f16d43071fb9c11.js → [datasetId]-e12b11ba15bc3fc1.js} +0 -0
  167. /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/{new-97f06e21580f1f6a.js → new-e32fccc4ca520d2b.js} +0 -0
  168. /fides/ui-build/static/admin/_next/static/chunks/pages/{dataset-674bb3940f088ecc.js → dataset-7c59a6abf6ba6207.js} +0 -0
  169. /fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/{[id]-6f77d8647fca71e0.js → [id]-927b7e476c4b47d0.js} +0 -0
  170. /fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/{new-821dd1269834cfa2.js → new-cbe100d50df34285.js} +0 -0
  171. /fides/ui-build/static/admin/_next/static/chunks/pages/{datastore-connection-23e4caf79faa8106.js → datastore-connection-cce20440b177050b.js} +0 -0
  172. /fides/ui-build/static/admin/_next/static/chunks/pages/{index-23eb64eed81dcb69.js → index-6cd8708106331b8d.js} +0 -0
  173. /fides/ui-build/static/admin/_next/static/chunks/pages/integrations/{[id]-3a4cd3fe9094fba3.js → [id]-4c3c413a2668df53.js} +0 -0
  174. /fides/ui-build/static/admin/_next/static/chunks/pages/{integrations-57e618d7b16ac69a.js → integrations-95402b5001c07ef2.js} +0 -0
  175. /fides/ui-build/static/admin/_next/static/chunks/pages/messaging/{[id]-c9a323eb6a929476.js → [id]-3c6dc2f6e6bae960.js} +0 -0
  176. /fides/ui-build/static/admin/_next/static/chunks/pages/messaging/{add-template-b9bb09e46921a590.js → add-template-4a6d4023a7791be8.js} +0 -0
  177. /fides/ui-build/static/admin/_next/static/chunks/pages/{messaging-82c631a12b5a008c.js → messaging-76b204c9b98d656f.js} +0 -0
  178. /fides/ui-build/static/admin/_next/static/chunks/pages/poc/{table-migration-38360083348c3d6c.js → table-migration-48500551fd6a7602.js} +0 -0
  179. /fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/{[id]-0d0bb9eb004a3336.js → [id]-0f25a76dd18c5e20.js} +0 -0
  180. /fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/{messaging-f9320a58f489f5b7.js → messaging-ad6ad3e5bd72765d.js} +0 -0
  181. /fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/{storage-d0cfa8aeddd43a40.js → storage-6032d82f0fc2893d.js} +0 -0
  182. /fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/{configure-72ca94ec5ed85733.js → configure-d83e5bd52a638234.js} +0 -0
  183. /fides/ui-build/static/admin/_next/static/chunks/pages/{privacy-requests-5a5edc8a4aa7c30a.js → privacy-requests-baf31c3e4b081046.js} +0 -0
  184. /fides/ui-build/static/admin/_next/static/chunks/pages/properties/{[id]-5ec775c4904fdbfe.js → [id]-e784c05d056b2371.js} +0 -0
  185. /fides/ui-build/static/admin/_next/static/chunks/pages/properties/{add-property-a6812c0916f2949e.js → add-property-0a7a2db148a7561a.js} +0 -0
  186. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/about/{alpha-3e72e9f91991c119.js → alpha-a82f3df840d5c1b5.js} +0 -0
  187. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{about-6aab092f4871cecb.js → about-d06fb16487705b9d.js} +0 -0
  188. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{consent-be47008304106395.js → consent-93a978443bf299db.js} +0 -0
  189. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{custom-fields-ae1b57589da7b175.js → custom-fields-9ecb803099082bf4.js} +0 -0
  190. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{domain-records-23a6d7a921150188.js → domain-records-16fdd91a81074dd1.js} +0 -0
  191. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{domains-2a9e8859ab4d9de6.js → domains-4cdd6001e7cb9aee.js} +0 -0
  192. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{email-templates-4f9f0fdf9925ae90.js → email-templates-1914de830ce5cfc4.js} +0 -0
  193. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{locations-46f7af35cee4a8bb.js → locations-2e635dcd11b78224.js} +0 -0
  194. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{organization-a596a96cb8d0aa8e.js → organization-f547f1f33c12faf3.js} +0 -0
  195. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{regulations-6ed5fc2410e00857.js → regulations-7c02e469d8c5bd74.js} +0 -0
  196. /fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/[id]/{test-datasets-86811e3cda277e77.js → test-datasets-20b1193ed76c56b0.js} +0 -0
  197. /fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/{[id]-5a43f108d8047d5b.js → [id]-6e15332935f6b538.js} +0 -0
  198. /fides/ui-build/static/admin/_next/static/chunks/pages/{systems-045a841e22e85ea8.js → systems-fbc8761ef4d55516.js} +0 -0
  199. /fides/ui-build/static/admin/_next/static/chunks/pages/{taxonomy-1b3f2d4bcb0e164d.js → taxonomy-4d7827fc9c46b6b8.js} +0 -0
  200. /fides/ui-build/static/admin/_next/static/chunks/pages/{user-management-2cab41659f1ee7da.js → user-management-9cec020f89544426.js} +0 -0
@@ -0,0 +1,268 @@
1
+ from typing import Any, List, Optional
2
+
3
+ from fideslang.models import Dataset, DatasetField
4
+ from loguru import logger
5
+ from sqlalchemy import text
6
+ from sqlalchemy.orm import Session
7
+
8
+ from fides.api.models.datasetconfig import DatasetConfig
9
+ from fides.api.models.privacy_request.privacy_request import PrivacyRequest
10
+ from fides.api.schemas.policy import ActionType
11
+
12
+
13
+ # TODO: keeping this for a bit to help with development and testing
14
+ def get_redaction_entities_map(db: Session) -> set[str]:
15
+ """
16
+ Create a set of hierarchical entity keys that should be redacted based on fides_meta.redact: name.
17
+
18
+ This utility function reads all enabled dataset configurations from the database
19
+ and builds a set of hierarchical entity keys (dataset_name, dataset_name.collection_name,
20
+ dataset_name.collection_name.field_name) that have fides_meta.redact set to "name".
21
+
22
+ Supports deeply nested field structures with unlimited nesting depth.
23
+
24
+ Args:
25
+ db: Database session
26
+
27
+ Returns:
28
+ Set of hierarchical entity keys that should be redacted
29
+ """
30
+ redaction_entities = set()
31
+
32
+ try:
33
+ dataset_configs = DatasetConfig.all(db=db)
34
+
35
+ for dataset_config in dataset_configs:
36
+ ctl_dataset = dataset_config.ctl_dataset
37
+ if not ctl_dataset:
38
+ continue
39
+
40
+ dataset = Dataset.model_validate(dataset_config.ctl_dataset)
41
+ # Intentionally using the fides_key instead of name since it's always provided
42
+ dataset_name = dataset.fides_key
43
+
44
+ # Check dataset level
45
+ if dataset.fides_meta and dataset.fides_meta.redact == "name":
46
+ redaction_entities.add(dataset_name)
47
+
48
+ # Check collection level
49
+ for collection_dict in dataset.collections:
50
+ # Collections are stored as dictionaries in the database
51
+ collection_name = collection_dict.name
52
+ if not collection_name:
53
+ continue
54
+
55
+ collection_path = f"{dataset_name}.{collection_name}"
56
+ collection_fides_meta = collection_dict.fides_meta
57
+
58
+ if collection_fides_meta and collection_fides_meta.redact == "name":
59
+ redaction_entities.add(collection_path)
60
+
61
+ # Check field level (with recursive nested field support)
62
+ _traverse_fields_for_redaction(
63
+ collection_dict.fields, collection_path, redaction_entities
64
+ )
65
+
66
+ except Exception as exc:
67
+ # Log error but don't fail, just return empty set
68
+ logger.warning(f"Error extracting redaction configurations: {exc}")
69
+
70
+ return redaction_entities
71
+
72
+
73
+ def get_redaction_entities_map_db(db: Session) -> set[str]:
74
+ """
75
+ Create a set of hierarchical entity keys that should be redacted based on fides_meta.redact: name.
76
+
77
+ This function uses a hybrid approach:
78
+ 1. First identifies datasets that contain ANY redaction metadata at any level
79
+ 2. Then processes only those datasets with redaction metadata
80
+
81
+
82
+ Args:
83
+ db: Database session
84
+
85
+ Returns:
86
+ Set of hierarchical entity keys that should be redacted
87
+ """
88
+ redaction_entities: set[str] = set()
89
+
90
+ try:
91
+ # Step 1: Pre-filter to find datasets with ANY redaction metadata
92
+ # Simple existence check - no paths needed, just check if redaction exists anywhere
93
+ pre_filter_query = """
94
+ SELECT DISTINCT dc.ctl_dataset_id
95
+ FROM datasetconfig dc
96
+ JOIN ctl_datasets ds ON dc.ctl_dataset_id = ds.id
97
+ WHERE
98
+ -- Dataset-level redaction
99
+ ds.fides_meta->>'redact' = 'name'
100
+ OR
101
+ -- Collection-level redaction
102
+ EXISTS (
103
+ SELECT 1 FROM jsonb_array_elements(ds.collections::jsonb) AS collection
104
+ WHERE collection->'fides_meta'->>'redact' = 'name'
105
+ LIMIT 1
106
+ )
107
+ OR
108
+ -- Field-level redaction using jsonb_path_query
109
+ EXISTS (
110
+ SELECT 1
111
+ FROM jsonb_path_query(ds.collections::jsonb, '$.**.fides_meta') AS fides_meta
112
+ WHERE fides_meta->>'redact' = 'name'
113
+ LIMIT 1
114
+ )
115
+ """
116
+
117
+ candidate_datasets = db.execute(pre_filter_query).fetchall()
118
+
119
+ if not candidate_datasets:
120
+ logger.debug("No datasets found with redaction metadata")
121
+ return redaction_entities
122
+
123
+ logger.debug(
124
+ f"Pre-filtered to {len(candidate_datasets)} datasets with redaction metadata"
125
+ )
126
+
127
+ # Step 2: Process only the candidate datasets with targeted queries
128
+ # Convert to a format we can use in SQL ANY clause
129
+ dataset_ids = [row[0] for row in candidate_datasets]
130
+
131
+ # Query for dataset-level redactions (only on candidate datasets)
132
+ dataset_query = text(
133
+ """
134
+ SELECT ds.fides_key as entity_path
135
+ FROM datasetconfig dc
136
+ JOIN ctl_datasets ds ON dc.ctl_dataset_id = ds.id
137
+ WHERE ds.id = ANY(:dataset_ids)
138
+ AND ds.fides_meta->>'redact' = 'name'
139
+ """
140
+ )
141
+
142
+ dataset_results = db.execute(
143
+ dataset_query, {"dataset_ids": dataset_ids}
144
+ ).fetchall()
145
+ for row in dataset_results:
146
+ redaction_entities.add(row[0])
147
+
148
+ # Query for collection-level redactions (only on candidate datasets)
149
+ collection_query = text(
150
+ """
151
+ SELECT ds.fides_key || '.' || (collection->>'name') as entity_path
152
+ FROM datasetconfig dc
153
+ JOIN ctl_datasets ds ON dc.ctl_dataset_id = ds.id
154
+ CROSS JOIN LATERAL jsonb_array_elements(ds.collections::jsonb) AS collection
155
+ WHERE ds.id = ANY(:dataset_ids)
156
+ AND collection->'fides_meta'->>'redact' = 'name'
157
+ AND collection->>'name' IS NOT NULL
158
+ """
159
+ )
160
+
161
+ collection_results = db.execute(
162
+ collection_query, {"dataset_ids": dataset_ids}
163
+ ).fetchall()
164
+ for row in collection_results:
165
+ redaction_entities.add(row[0])
166
+
167
+ # Query for field-level redactions (including nested fields)
168
+ # This uses a recursive CTE to handle arbitrary nesting levels
169
+ field_query = text(
170
+ """
171
+ WITH RECURSIVE field_hierarchy AS (
172
+ -- Base case: top-level fields in collections (only candidate datasets)
173
+ SELECT
174
+ ds.fides_key || '.' ||
175
+ (collection->>'name') || '.' ||
176
+ (field->>'name') as entity_path,
177
+ field->'fields' as nested_fields,
178
+ field->'fides_meta'->>'redact' as redact_value
179
+ FROM datasetconfig dc
180
+ JOIN ctl_datasets ds ON dc.ctl_dataset_id = ds.id
181
+ CROSS JOIN LATERAL jsonb_array_elements(ds.collections::jsonb) AS collection
182
+ CROSS JOIN LATERAL jsonb_array_elements(collection->'fields') AS field
183
+ WHERE ds.id = ANY(:dataset_ids)
184
+ AND collection->>'name' IS NOT NULL
185
+ AND field->>'name' IS NOT NULL
186
+
187
+ UNION ALL
188
+
189
+ -- Recursive case: nested fields
190
+ SELECT
191
+ fh.entity_path || '.' || (nested_field->>'name') as entity_path,
192
+ nested_field->'fields' as nested_fields,
193
+ nested_field->'fides_meta'->>'redact' as redact_value
194
+ FROM field_hierarchy fh
195
+ CROSS JOIN LATERAL jsonb_array_elements(fh.nested_fields) AS nested_field
196
+ WHERE jsonb_typeof(fh.nested_fields) = 'array'
197
+ AND nested_field->>'name' IS NOT NULL
198
+ )
199
+ SELECT DISTINCT entity_path
200
+ FROM field_hierarchy
201
+ WHERE redact_value = 'name'
202
+ """
203
+ )
204
+
205
+ field_results = db.execute(field_query, {"dataset_ids": dataset_ids}).fetchall()
206
+ for row in field_results:
207
+ redaction_entities.add(row[0])
208
+
209
+ logger.debug(f"Found {len(redaction_entities)} entities requiring redaction")
210
+
211
+ except Exception as exc:
212
+ # Log error but don't fail, just return empty set
213
+ logger.warning(
214
+ f"Error extracting redaction configurations from database: {exc}"
215
+ )
216
+
217
+ return redaction_entities
218
+
219
+
220
+ def map_privacy_request(privacy_request: PrivacyRequest) -> dict[str, Any]:
221
+ """Creates a map with a subset of values from the privacy request"""
222
+ request_data: dict[str, Any] = {}
223
+ request_data["id"] = privacy_request.id
224
+
225
+ action_type: Optional[ActionType] = privacy_request.policy.get_action_type()
226
+ if action_type:
227
+ request_data["type"] = action_type.value
228
+
229
+ request_data["identity"] = {
230
+ key: value
231
+ for key, value in privacy_request.get_persisted_identity()
232
+ .labeled_dict(include_default_labels=True)
233
+ .items()
234
+ if value["value"] is not None
235
+ }
236
+
237
+ if privacy_request.requested_at:
238
+ request_data["requested_at"] = privacy_request.requested_at.strftime(
239
+ "%m/%d/%Y %H:%M %Z"
240
+ )
241
+ return request_data
242
+
243
+
244
+ def _traverse_fields_for_redaction(
245
+ fields: List[DatasetField], current_path: str, redaction_entities: set[str]
246
+ ) -> None:
247
+ """
248
+ Recursively traverse nested fields to find redaction entities.
249
+
250
+ Args:
251
+ fields: List of field dictionaries to traverse
252
+ current_path: Current hierarchical path (e.g., "dataset.collection")
253
+ redaction_entities: Set to add redacted field paths to
254
+ """
255
+ for field in fields:
256
+ field_name = field.name
257
+ if not field_name:
258
+ continue
259
+
260
+ field_path = f"{current_path}.{field_name}"
261
+ field_fides_meta = field.fides_meta
262
+
263
+ if field_fides_meta and field_fides_meta.redact == "name":
264
+ redaction_entities.add(field_path)
265
+
266
+ # Recursively check nested fields
267
+ if field.fields:
268
+ _traverse_fields_for_redaction(field.fields, field_path, redaction_entities)
@@ -17,7 +17,7 @@ from fides.api.common_exceptions import StorageUploadError
17
17
  from fides.api.models.privacy_request import PrivacyRequest
18
18
  from fides.api.schemas.storage.storage import ResponseFormat
19
19
  from fides.api.service.privacy_request.dsr_package.dsr_report_builder import (
20
- DsrReportBuilder,
20
+ DSRReportBuilder,
21
21
  )
22
22
  from fides.api.service.storage.streaming.dsr_storage import (
23
23
  create_dsr_report_files_generator,
@@ -346,7 +346,7 @@ class SmartOpenStreamingStorage:
346
346
  )
347
347
 
348
348
  def _collect_and_validate_attachments_from_dsr_builder(
349
- self, data: dict, dsr_builder: "DsrReportBuilder"
349
+ self, data: dict, dsr_builder: "DSRReportBuilder"
350
350
  ) -> list[AttachmentProcessingInfo]:
351
351
  """Collect and validate attachments using the DSR report builder's processed attachments.
352
352
 
@@ -544,7 +544,7 @@ class SmartOpenStreamingStorage:
544
544
  """
545
545
  # Generate the DSR report first
546
546
  try:
547
- dsr_builder = DsrReportBuilder(
547
+ dsr_builder = DSRReportBuilder(
548
548
  privacy_request=privacy_request,
549
549
  dsr_data=data,
550
550
  enable_streaming=True,
@@ -12,7 +12,7 @@ from loguru import logger
12
12
  from fides.api.common_exceptions import StorageUploadError
13
13
  from fides.api.schemas.storage.storage import ResponseFormat, StorageSecrets
14
14
  from fides.api.service.privacy_request.dsr_package.dsr_report_builder import (
15
- DsrReportBuilder,
15
+ DSRReportBuilder,
16
16
  )
17
17
  from fides.api.service.storage.gcs import get_gcs_blob
18
18
  from fides.api.service.storage.s3 import (
@@ -47,7 +47,7 @@ def write_to_in_memory_buffer(
47
47
  logger.debug("Writing data to in-memory buffer")
48
48
  try:
49
49
  if resp_format == ResponseFormat.html.value:
50
- return DsrReportBuilder(
50
+ return DSRReportBuilder(
51
51
  privacy_request=privacy_request,
52
52
  dsr_data=data,
53
53
  ).generate()
@@ -5,8 +5,6 @@ from typing import Any, Callable, Dict, List, Optional, Tuple
5
5
 
6
6
  from fastapi import HTTPException
7
7
  from fideslang import FidesModelType
8
- from slowapi import Limiter
9
- from slowapi.util import get_remote_address # type: ignore
10
8
  from sqlalchemy.ext.asyncio import AsyncSession
11
9
  from starlette.status import HTTP_400_BAD_REQUEST
12
10
 
@@ -23,7 +21,6 @@ from fides.common.api.scope_registry import (
23
21
  ORGANIZATION,
24
22
  SYSTEM,
25
23
  )
26
- from fides.config import CONFIG
27
24
 
28
25
  from fides.api.models.sql_models import ( # type: ignore[attr-defined] # isort: skip
29
26
  ModelWithDefaultField,
@@ -44,16 +41,6 @@ CLI_SCOPE_PREFIX_MAPPING: Dict[str, str] = {
44
41
  "system": SYSTEM,
45
42
  }
46
43
 
47
- # Used for rate limiting with Slow API
48
- # Decorate individual routes to deviate from the default rate limits
49
- fides_limiter = Limiter(
50
- default_limits=[CONFIG.security.request_rate_limit],
51
- headers_enabled=True,
52
- key_prefix=CONFIG.security.rate_limit_prefix,
53
- key_func=get_remote_address,
54
- retry_after="http-date",
55
- )
56
-
57
44
 
58
45
  async def forbid_if_editing_is_default(
59
46
  sql_model: Base,
@@ -0,0 +1,194 @@
1
+ from __future__ import annotations
2
+
3
+ from ipaddress import ip_address
4
+ from typing import Optional
5
+
6
+ from fastapi import Request
7
+ from fastapi.responses import JSONResponse
8
+ from loguru import logger
9
+ from slowapi import Limiter
10
+ from slowapi.util import get_remote_address # type: ignore
11
+ from starlette.middleware.base import BaseHTTPMiddleware
12
+
13
+ from fides.config import CONFIG
14
+
15
+
16
+ class InvalidClientIPError(Exception):
17
+ def __init__(self, detail: str, header_value: str, header_name: str):
18
+ self.detail = detail
19
+ self.header_value = header_value
20
+ self.header_name = header_name
21
+ super().__init__(detail)
22
+
23
+
24
+ def validate_client_ip(ip: Optional[str]) -> bool:
25
+ """
26
+ Returns true if the provided ip is valid and not from a reserved range.
27
+ Returns false otherwise.
28
+ """
29
+ if not ip:
30
+ return False
31
+ try:
32
+ ip_obj = ip_address(ip)
33
+ if (
34
+ ip_obj.is_loopback
35
+ or ip_obj.is_link_local
36
+ or ip_obj.is_reserved
37
+ or ip_obj.is_multicast
38
+ or ip_obj.is_private
39
+ ):
40
+ return False
41
+ return True
42
+ except ValueError:
43
+ return False
44
+
45
+
46
+ def _extract_hostname_from_ip(ip: str) -> Optional[str]:
47
+ """
48
+ Extract hostname/IP address from header value, stripping port if present.
49
+
50
+ Simple string-based approach following the reference implementation pattern.
51
+ Does not validate whether the result is a valid IP address.
52
+
53
+ Examples:
54
+ # IPv4 cases
55
+ _extract_hostname_from_ip("192.168.1.1") -> "192.168.1.1"
56
+ _extract_hostname_from_ip("192.168.1.1:8080") -> "192.168.1.1"
57
+
58
+ # IPv6 cases
59
+ _extract_hostname_from_ip("2001:db8::1") -> "2001:db8::1"
60
+ _extract_hostname_from_ip("[2001:db8::1]:8080") -> "2001:db8::1"
61
+
62
+ # Edge cases (alidation will later reject)
63
+ _extract_hostname_from_ip("192.168.1.1, 192.168.1.2") -> "192.168.1.1, 192.168.1.2"
64
+ _extract_hostname_from_ip("not-an-ip:8080") -> "not-an-ip"
65
+
66
+ # Error
67
+ _extract_hostname_from_ip("") -> raises ValueError
68
+
69
+ Raises:
70
+ ValueError: If no hostname can be extracted from the input
71
+ """
72
+
73
+ clean_ip = ip.strip()
74
+
75
+ if not clean_ip:
76
+ raise ValueError("Could not parse IP from header value")
77
+
78
+ # Handle IPv6 with port: [IPv6]:port
79
+ if "]:" in clean_ip:
80
+ return clean_ip.split("]:")[0].replace("[", "").strip()
81
+
82
+ # Handle IPv4 with port: IPv4:port
83
+ if ":" in clean_ip and "::" not in clean_ip:
84
+ return clean_ip.split(":")[0].strip()
85
+
86
+ # Return as-is (IPv6 without port, IPv4 without port, or other values)
87
+ return clean_ip
88
+
89
+
90
+ def _resolve_client_ip_from_header(request: Request, strict: bool) -> str:
91
+ """Shared resolver for client IP from the configured header.
92
+
93
+ - When strict=True: raise InvalidClientIPError on invalid/malformed header values.
94
+ - When strict=False: never raise; fall back to the connection source IP.
95
+ """
96
+ header_name = CONFIG.security.rate_limit_client_ip_header
97
+ if not header_name:
98
+ # This line should never be reached when rate limiting is enabled
99
+ logger.warning(
100
+ "Rate limit client IP header not configured. Falling back to source IP.",
101
+ header_name,
102
+ )
103
+ return get_remote_address(request)
104
+
105
+ ip_address_from_header = request.headers.get(header_name)
106
+ if not ip_address_from_header:
107
+ logger.debug(
108
+ "Rate limit header '{}' not found. Falling back to source IP.",
109
+ header_name,
110
+ )
111
+ return get_remote_address(request)
112
+
113
+ # Extract and validate IP
114
+ try:
115
+ extracted_ip = _extract_hostname_from_ip(ip_address_from_header)
116
+ if extracted_ip and validate_client_ip(extracted_ip):
117
+ return extracted_ip
118
+ raise ValueError("IP failed validation")
119
+ except ValueError:
120
+ if strict:
121
+ logger.error(
122
+ "Invalid IP '{}' in header '{}'. Rejecting request.",
123
+ ip_address_from_header,
124
+ header_name,
125
+ )
126
+ raise InvalidClientIPError(
127
+ detail="Invalid IP address format",
128
+ header_value=ip_address_from_header,
129
+ header_name=header_name,
130
+ )
131
+ # Non-strict path: fall back silently to source IP
132
+ return get_remote_address(request)
133
+
134
+
135
+ def get_client_ip_from_header(request: Request) -> str:
136
+ """
137
+ Extracts the client IP from the configured CDN header.
138
+
139
+ If the header is not configured or is missing, it falls back to the
140
+ source IP on the request.
141
+
142
+ Raises InvalidClientIPError if header contains invalid IP format.
143
+ """
144
+ return _resolve_client_ip_from_header(request, strict=True)
145
+
146
+
147
+ def safe_rate_limit_key(request: Request) -> str:
148
+ """
149
+ Safe key function for SlowAPI limiter.
150
+
151
+ Must never raise. If the configured header is missing or malformed,
152
+ fall back to the connection source IP for rate limiting purposes.
153
+ """
154
+ return _resolve_client_ip_from_header(request, strict=False)
155
+
156
+
157
+ class RateLimitIPValidationMiddleware(BaseHTTPMiddleware):
158
+ """
159
+ Pre-validate the configured client IP header when rate limiting is enabled.
160
+
161
+ If the header is present but invalid, short-circuit the request with 422.
162
+ This keeps SlowAPI's middleware path free of exceptions from the key function.
163
+ """
164
+
165
+ async def dispatch(self, request: Request, call_next): # type: ignore
166
+ if is_rate_limit_enabled:
167
+ try:
168
+ # Triggers parsing/validation; raises on invalid header
169
+ get_client_ip_from_header(request)
170
+ except InvalidClientIPError:
171
+ return JSONResponse(
172
+ status_code=422, content={"detail": "Invalid client IP header"}
173
+ )
174
+ return await call_next(request)
175
+
176
+
177
+ # Used for rate limiting with Slow API
178
+ # Decorate individual routes to deviate from the default rate limits
179
+ is_rate_limit_enabled = (
180
+ CONFIG.security.rate_limit_client_ip_header is not None
181
+ and CONFIG.security.rate_limit_client_ip_header != ""
182
+ )
183
+ fides_limiter = Limiter(
184
+ storage_uri=CONFIG.redis.connection_url_unencoded,
185
+ application_limits=[
186
+ CONFIG.security.request_rate_limit
187
+ ], # Creates ONE shared bucket for all endpoints
188
+ headers_enabled=True,
189
+ key_prefix=CONFIG.security.rate_limit_prefix,
190
+ key_func=safe_rate_limit_key,
191
+ retry_after="http-date",
192
+ in_memory_fallback_enabled=False, # Fall back to no rate limiting if Redis unavailable
193
+ enabled=is_rate_limit_enabled,
194
+ )
@@ -30,6 +30,7 @@ DATA_SUBJECT = "data_subject"
30
30
  DATA_USE = "data_use"
31
31
  DATASET = "dataset"
32
32
  DELETE = "delete"
33
+ PRIVACY_REQUEST_REDACTION_PATTERNS = "privacy-request-redaction-patterns"
33
34
  ENCRYPTION = "encryption"
34
35
  MESSAGING_TEMPLATE = "messaging-template"
35
36
  EVALUATION = "evaluation"
@@ -134,6 +135,11 @@ DATA_USE_UPDATE = f"{DATA_USE}:{UPDATE}"
134
135
  DATA_USE_DELETE = f"{DATA_USE}:{DELETE}"
135
136
 
136
137
  DATASET_CREATE_OR_UPDATE = f"{DATASET}:{CREATE_OR_UPDATE}"
138
+
139
+ PRIVACY_REQUEST_REDACTION_PATTERNS_READ = f"{PRIVACY_REQUEST_REDACTION_PATTERNS}:{READ}"
140
+ PRIVACY_REQUEST_REDACTION_PATTERNS_UPDATE = (
141
+ f"{PRIVACY_REQUEST_REDACTION_PATTERNS}:{UPDATE}"
142
+ )
137
143
  DATASET_DELETE = f"{DATASET}:{DELETE}"
138
144
  DATASET_READ = f"{DATASET}:{READ}"
139
145
  DATASET_TEST = f"{DATASET}:{TEST}"
@@ -288,6 +294,8 @@ SCOPE_DOCS = {
288
294
  DATA_USE_UPDATE: "Update data uses",
289
295
  DATASET_CREATE_OR_UPDATE: "Create or modify datasets",
290
296
  DATASET_DELETE: "Delete datasets",
297
+ PRIVACY_REQUEST_REDACTION_PATTERNS_READ: "View privacy request redaction patterns",
298
+ PRIVACY_REQUEST_REDACTION_PATTERNS_UPDATE: "Update privacy request redaction patterns",
291
299
  DATASET_READ: "View datasets",
292
300
  DATASET_TEST: "Run a standalone privacy request test for a dataset",
293
301
  ENCRYPTION_EXEC: "Encrypt data",
@@ -116,6 +116,9 @@ PRIVACY_REQUEST_TRANSFER_TO_PARENT = (
116
116
  "/privacy-request/transfer/{privacy_request_id}/{rule_key}"
117
117
  )
118
118
 
119
+ # Privacy Request Redaction Patterns URLs
120
+ PRIVACY_REQUEST_REDACTION_PATTERNS = "/privacy-request/redaction-patterns"
121
+
119
122
  # Privacy Request pre-approve URLs
120
123
  PRIVACY_REQUEST_PRE_APPROVE = "/privacy-request/{privacy_request_id}/pre-approve"
121
124
  PRIVACY_REQUEST_PRE_APPROVE_ELIGIBLE = PRIVACY_REQUEST_PRE_APPROVE + "/eligible"
@@ -181,8 +181,24 @@ class RedisSettings(FidesSettings):
181
181
  description="A full connection URL to the read-only Redis cache. If not specified, this URL is automatically assembled from the read_only_host, read_only_port, read_only_password and read_only_db_index specified above.",
182
182
  exclude=True,
183
183
  )
184
+ connection_url_unencoded: Optional[str] = Field(
185
+ default=None,
186
+ description="A full connection URL to the Redis cache with the password unencoded. If not specified, this URL is automatically assembled from the host, port, password and db_index specified above.",
187
+ exclude=True,
188
+ )
189
+ read_only_connection_url_unencoded: Optional[str] = Field(
190
+ default=None,
191
+ description="A full connection URL to the read-only Redis cache with the password unencoded. If not specified, this URL is automatically assembled from the read_only_host, read_only_port, read_only_password and read_only_db_index specified above.",
192
+ exclude=True,
193
+ )
184
194
 
185
- @field_validator("connection_url", "read_only_connection_url", mode="before")
195
+ @field_validator(
196
+ "connection_url",
197
+ "read_only_connection_url",
198
+ "connection_url_unencoded",
199
+ "read_only_connection_url_unencoded",
200
+ mode="before",
201
+ )
186
202
  @classmethod
187
203
  def assemble_connection_url(
188
204
  cls,
@@ -195,7 +211,14 @@ class RedisSettings(FidesSettings):
195
211
  return v
196
212
 
197
213
  # Determine which set of settings to use based on field name
198
- is_read_only = info.field_name == "read_only_connection_url"
214
+ is_read_only = info.field_name in (
215
+ "read_only_connection_url",
216
+ "read_only_connection_url_unencoded",
217
+ )
218
+ is_unencoded = info.field_name in (
219
+ "connection_url_unencoded",
220
+ "read_only_connection_url_unencoded",
221
+ )
199
222
 
200
223
  # Extract settings - fallbacks already resolved by field validators for read-only fields
201
224
  user = (
@@ -244,7 +267,8 @@ class RedisSettings(FidesSettings):
244
267
  # redis://<user>:<password>@<host>
245
268
  auth_prefix = ""
246
269
  if password or user:
247
- auth_prefix = f"{quote_plus(user)}:{quote_plus(password)}@"
270
+ encoded_password = password if is_unencoded else quote_plus(password)
271
+ auth_prefix = f"{quote_plus(user)}:{encoded_password}@"
248
272
 
249
273
  # For host, we don't have a fallback - read replica should be a different host
250
274
  host = (