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
fides/api/oauth/utils.py CHANGED
@@ -4,16 +4,16 @@ import json
4
4
  from datetime import datetime
5
5
  from functools import update_wrapper
6
6
  from types import FunctionType
7
- from typing import Any, Callable, Dict, List, Optional, Tuple
7
+ from typing import Any, Callable, Dict, List, Optional, Tuple, cast
8
8
 
9
- from fastapi import Depends, HTTPException, Security
9
+ from fastapi import Depends, HTTPException, Request, Security
10
10
  from fastapi.security import SecurityScopes
11
11
  from jose import exceptions, jwe
12
12
  from jose.constants import ALGORITHMS
13
13
  from loguru import logger
14
14
  from pydantic import ValidationError
15
15
  from sqlalchemy.orm import Session
16
- from starlette.status import HTTP_404_NOT_FOUND
16
+ from starlette.status import HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND
17
17
 
18
18
  from fides.api.api.deps import get_db
19
19
  from fides.api.common_exceptions import AuthenticationError, AuthorizationError
@@ -30,7 +30,7 @@ from fides.api.models.fides_user_permissions import FidesUserPermissions
30
30
  from fides.api.models.policy import PolicyPreWebhook
31
31
  from fides.api.models.pre_approval_webhook import PreApprovalWebhook
32
32
  from fides.api.models.privacy_request import RequestTask
33
- from fides.api.oauth.roles import get_scopes_from_roles
33
+ from fides.api.oauth.roles import ROLES_TO_SCOPES_MAPPING, get_scopes_from_roles
34
34
  from fides.api.request_context import set_user_id
35
35
  from fides.api.schemas.external_https import (
36
36
  DownloadTokenJWE,
@@ -80,6 +80,28 @@ def is_callback_token_expired(issued_at: Optional[datetime]) -> bool:
80
80
  ).total_seconds() / 60.0 > CONFIG.execution.privacy_request_delay_timeout
81
81
 
82
82
 
83
+ def is_token_invalidated(issued_at: datetime, client: ClientDetail) -> bool:
84
+ """
85
+ Return True if the token should be considered invalid due to security events
86
+ (e.g., user password reset) that occurred after the token was issued.
87
+
88
+ Any errors accessing related objects are logged and treated as non-invalidating.
89
+ """
90
+ try:
91
+ if (
92
+ client.user is not None
93
+ and client.user.password_reset_at is not None
94
+ and issued_at < client.user.password_reset_at
95
+ ):
96
+ return True
97
+ return False
98
+ except Exception as exc:
99
+ logger.exception(
100
+ "Unable to evaluate password reset timestamp for client user: {}", exc
101
+ )
102
+ return False
103
+
104
+
83
105
  def _get_webhook_jwe_or_error(
84
106
  security_scopes: SecurityScopes, authorization: str = Security(oauth2_scheme)
85
107
  ) -> WebhookJWE:
@@ -225,7 +247,7 @@ async def get_current_user(
225
247
  created_at=datetime.utcnow(),
226
248
  )
227
249
 
228
- return client.user # type: ignore[attr-defined]
250
+ return cast(FidesUser, client.user)
229
251
 
230
252
 
231
253
  def verify_callback_oauth_policy_pre_webhook(
@@ -370,8 +392,10 @@ def extract_token_and_load_client(
370
392
  logger.debug("Auth token expired.")
371
393
  raise AuthorizationError(detail="Not Authorized for this action")
372
394
 
395
+ issued_at_dt = datetime.fromisoformat(issued_at)
396
+
373
397
  if is_token_expired(
374
- datetime.fromisoformat(issued_at),
398
+ issued_at_dt,
375
399
  token_duration_override or CONFIG.security.oauth_access_token_expire_minutes,
376
400
  ):
377
401
  raise AuthorizationError(detail="Not Authorized for this action")
@@ -394,6 +418,12 @@ def extract_token_and_load_client(
394
418
  logger.debug("Auth token belongs to an invalid client_id.")
395
419
  raise AuthorizationError(detail="Not Authorized for this action")
396
420
 
421
+ # Invalidate tokens issued prior to the user's most recent password reset.
422
+ # This ensures any existing sessions are expired immediately after a password change.
423
+ if is_token_invalidated(issued_at_dt, client):
424
+ logger.debug("Auth token issued before latest password reset.")
425
+ raise AuthorizationError(detail="Not Authorized for this action")
426
+
397
427
  # Populate request-scoped context with the authenticated user identifier.
398
428
  # Prefer the linked user_id; fall back to the client id when this is the
399
429
  # special root client (which has no associated FidesUser row).
@@ -476,6 +506,87 @@ def has_scope_subset(user_scopes: List[str], endpoint_scopes: SecurityScopes) ->
476
506
  return set(endpoint_scopes.scopes).issubset(user_scopes)
477
507
 
478
508
 
509
+ def get_client_effective_scopes(client: "ClientDetail") -> List[str]:
510
+ """
511
+ Get all scopes available to a client, including both direct scopes and role-derived scopes.
512
+
513
+ Args:
514
+ client: The ClientDetail instance
515
+
516
+ Returns:
517
+ List of scope strings that the client has access to
518
+ """
519
+ effective_scopes = set()
520
+
521
+ # Add direct scopes
522
+ if client.scopes:
523
+ effective_scopes.update(client.scopes)
524
+
525
+ # Add role-derived scopes
526
+ if client.roles:
527
+ for role in client.roles:
528
+ role_scopes = ROLES_TO_SCOPES_MAPPING.get(role, [])
529
+ effective_scopes.update(role_scopes)
530
+
531
+ # Add user permission scopes if client is associated with a user
532
+ # Note: client.user is available via SQLAlchemy backref from FidesUser.client relationship
533
+ user = getattr(client, "user", None) # Use getattr to avoid mypy attr-defined error
534
+ if user and hasattr(user, "permissions") and user.permissions:
535
+ effective_scopes.update(user.permissions.total_scopes)
536
+
537
+ return sorted(list(effective_scopes))
538
+
539
+
540
+ def verify_client_can_assign_scopes(
541
+ request: "Request",
542
+ requesting_client: "ClientDetail",
543
+ scopes: List[str],
544
+ db: "Session",
545
+ ) -> None:
546
+ """
547
+ Verify that a requesting client has permission to assign the given scopes.
548
+
549
+ Raises HTTPException if the client lacks permission.
550
+ Root client is exempt from this check.
551
+
552
+ Args:
553
+ request: FastAPI request object containing Authorization header
554
+ requesting_client: The client making the request
555
+ scopes: List of scopes to be assigned
556
+ db: Database session
557
+
558
+ Raises:
559
+ HTTPException: If the client lacks permission to assign the scopes
560
+ """
561
+ # Root client can assign any scope
562
+ if requesting_client.id == CONFIG.security.oauth_root_client_id:
563
+ return
564
+
565
+ # Get the actual token scopes (not the client's database scopes)
566
+ authorization = request.headers.get("Authorization", "").replace("Bearer ", "")
567
+ token_data, _ = extract_token_and_load_client(authorization, db)
568
+
569
+ # Get token's effective scopes
570
+ token_scopes = token_data.get("scopes", [])
571
+
572
+ # Check if user has the scopes via roles as well
573
+ has_scope_via_role = has_permissions(
574
+ token_data=token_data,
575
+ client=requesting_client,
576
+ endpoint_scopes=SecurityScopes(scopes),
577
+ )
578
+
579
+ # If they don't have all scopes via direct assignment or roles, check individual scopes
580
+ if not has_scope_via_role:
581
+ unauthorized_scopes = set(scopes) - set(token_scopes)
582
+
583
+ if unauthorized_scopes:
584
+ raise HTTPException(
585
+ status_code=HTTP_403_FORBIDDEN,
586
+ detail=f"Cannot assign scopes that you do not have. Missing scopes: {sorted(unauthorized_scopes)}",
587
+ )
588
+
589
+
479
590
  def create_temporary_user_for_login_flow(config: FidesConfig) -> FidesUser:
480
591
  """
481
592
  Create a temporary FidesUser in-memory with an attached in-memory ClientDetail
@@ -0,0 +1,55 @@
1
+ import re
2
+ from typing import List
3
+
4
+ from pydantic import BaseModel, Field, field_validator
5
+
6
+
7
+ class PrivacyRequestRedactionPatternsRequest(BaseModel):
8
+ """Request schema for updating privacy request redaction patterns."""
9
+
10
+ patterns: List[str] = Field(
11
+ description="List of regex patterns used to redact dataset, collection, and field names in privacy request package reports",
12
+ max_length=100, # Limit number of patterns
13
+ )
14
+
15
+ @field_validator("patterns")
16
+ @classmethod
17
+ def validate_patterns(cls, patterns: List[str]) -> List[str]:
18
+ """Validate regex patterns with ReDoS protection via Pydantic's default rust-regex engine."""
19
+ if not patterns:
20
+ return patterns
21
+
22
+ validated_patterns = []
23
+ for i, pattern in enumerate(patterns):
24
+ if not isinstance(pattern, str):
25
+ raise ValueError(f"Pattern at index {i} must be a string")
26
+
27
+ pattern = pattern.strip()
28
+ if not pattern:
29
+ raise ValueError(f"Pattern at index {i} cannot be empty")
30
+
31
+ # Reasonable length limit for regex patterns
32
+ if len(pattern) > 500:
33
+ raise ValueError(
34
+ f"Pattern at index {i} is too long (max 500 characters)"
35
+ )
36
+
37
+ # Pydantic's rust-regex engine provides ReDoS protection
38
+ try:
39
+ re.compile(pattern, re.IGNORECASE)
40
+ except re.error as e:
41
+ raise ValueError(f"Invalid regex pattern at index {i}: {e}")
42
+
43
+ validated_patterns.append(pattern)
44
+
45
+ return validated_patterns
46
+
47
+
48
+ class PrivacyRequestRedactionPatternsResponse(BaseModel):
49
+ """Response schema for privacy request redaction patterns."""
50
+
51
+ patterns: List[str] = Field(
52
+ description="List of regex patterns used to redact dataset, collection, and field names in privacy request package reports"
53
+ )
54
+
55
+ model_config = {"from_attributes": True}
@@ -0,0 +1,231 @@
1
+ import re
2
+ from typing import Any, Dict, List, Literal, Optional, Set, Tuple
3
+
4
+ from loguru import logger
5
+ from sqlalchemy.orm import Session
6
+
7
+ from fides.api.models.privacy_request_redaction_pattern import (
8
+ PrivacyRequestRedactionPattern,
9
+ )
10
+ from fides.api.service.privacy_request.dsr_package.utils import (
11
+ get_redaction_entities_map_db,
12
+ )
13
+
14
+
15
+ class DSRDataPreprocessor:
16
+ """
17
+ Processes DSR data to apply name redaction before report generation.
18
+ """
19
+
20
+ def __init__(self, db: Session):
21
+ self.db = db
22
+ self.redaction_patterns: List[str] = (
23
+ PrivacyRequestRedactionPattern.get_patterns(db) or []
24
+ )
25
+ self.entities_to_redact: Set[str] = get_redaction_entities_map_db(db)
26
+
27
+ def process_dsr_data(self, dsr_data: dict[str, Any]) -> dict[str, Any]:
28
+ """Process the DSR data to apply all redaction upfront."""
29
+ if not self.redaction_patterns and not self.entities_to_redact:
30
+ return dsr_data
31
+
32
+ # First pass: collect and map dataset names
33
+ dataset_mapping = self._create_dataset_mapping(dsr_data)
34
+
35
+ # Second pass: process data with redaction
36
+ processed_data = {}
37
+ collection_indices: Dict[str, Dict[str, int]] = (
38
+ {}
39
+ ) # Track collection indices within each dataset
40
+
41
+ for key, rows in dsr_data.items():
42
+ # The "attachment" key is used to pass in the privacy request's attachments into `dsr_data`.
43
+ # We don't need to redact these.
44
+ if key == "attachments":
45
+ processed_data[key] = rows
46
+ continue
47
+
48
+ dataset_name, collection_name = self._parse_key(key, rows)
49
+
50
+ # Get redacted dataset name
51
+ redacted_dataset = dataset_mapping.get(dataset_name, dataset_name)
52
+
53
+ # Get redacted collection name (index per dataset)
54
+ if dataset_name not in collection_indices:
55
+ collection_indices[dataset_name] = {}
56
+
57
+ if collection_name not in collection_indices[dataset_name]:
58
+ collection_indices[dataset_name][collection_name] = (
59
+ len(collection_indices[dataset_name]) + 1
60
+ )
61
+
62
+ collection_index = collection_indices[dataset_name][collection_name]
63
+ redacted_collection = self._redact_name(
64
+ "collection", collection_name, collection_index, dataset_name
65
+ )
66
+
67
+ # Process rows
68
+ new_key = f"{redacted_dataset}:{redacted_collection}"
69
+ processed_data[new_key] = [
70
+ self._process_row(row, dataset_name, collection_name) for row in rows
71
+ ]
72
+
73
+ return processed_data
74
+
75
+ def _create_dataset_mapping(self, dsr_data: dict[str, Any]) -> Dict[str, str]:
76
+ """Create dataset name mapping with ordered numbering for redacted datasets."""
77
+
78
+ # Extract unique dataset names in order of appearance
79
+ dataset_names = []
80
+ for key, rows in dsr_data.items():
81
+ if key not in ["attachments", "dataset"]:
82
+ dataset_name, _ = self._parse_key(key, rows)
83
+ dataset_names.append(dataset_name)
84
+
85
+ unique_datasets = list(dict.fromkeys(dataset_names))
86
+
87
+ # Create mapping using position-based numbering for redacted datasets
88
+ mapping = {}
89
+
90
+ # Handle regular datasets
91
+ for index, name in enumerate(unique_datasets, 1):
92
+ hierarchical_key = self._build_hierarchical_key("dataset", name)
93
+ if self._should_redact(name, hierarchical_key):
94
+ mapping[name] = f"dataset_{index}"
95
+ else:
96
+ mapping[name] = name # Keep original name
97
+
98
+ # Special "dataset" and "attachment" cases comes last
99
+ # These keys are reserved for additional data and do not need to be redacted
100
+ if "dataset" in dsr_data.keys():
101
+ mapping["dataset"] = "dataset"
102
+
103
+ if "attachments" in dsr_data.keys():
104
+ mapping["attachments"] = "attachments"
105
+
106
+ return mapping
107
+
108
+ def _parse_key(self, key: str, rows: List[dict]) -> Tuple[str, str]:
109
+ """Parse a key into dataset and collection names."""
110
+ if ":" in key:
111
+ parts = key.split(":", 1)
112
+ return parts[0], parts[1]
113
+
114
+ # Fallback logic
115
+ for row in rows:
116
+ if "system_name" in row:
117
+ return row["system_name"], key
118
+ return "manual", key
119
+
120
+ def _should_redact(self, name: str, hierarchical_key: str) -> bool:
121
+ """Check if a name should be redacted."""
122
+ if hierarchical_key in self.entities_to_redact:
123
+ return True
124
+
125
+ for pattern in self.redaction_patterns:
126
+ try:
127
+ if re.search(pattern, name, re.IGNORECASE):
128
+ return True
129
+ except re.error:
130
+ logger.warning(f"Invalid regex pattern: {pattern}")
131
+
132
+ return False
133
+
134
+ def _redact_name(
135
+ self,
136
+ name_type: Literal["dataset", "collection", "field"],
137
+ name: str,
138
+ index: int,
139
+ dataset_name: Optional[str] = None,
140
+ collection_name: Optional[str] = None,
141
+ ) -> str:
142
+ """Apply redaction to a name based on patterns and configurations."""
143
+ hierarchical_key = self._build_hierarchical_key(
144
+ name_type, name, dataset_name, collection_name
145
+ )
146
+
147
+ if self._should_redact(name, hierarchical_key):
148
+ return f"{name_type}_{index}"
149
+
150
+ return name
151
+
152
+ def _build_hierarchical_key(
153
+ self,
154
+ name_type: Literal["dataset", "collection", "field"],
155
+ name: str,
156
+ dataset_name: Optional[str] = None,
157
+ collection_name: Optional[str] = None,
158
+ ) -> str:
159
+ """Build hierarchical key for entity lookup."""
160
+ if name_type == "dataset":
161
+ return name
162
+ if name_type == "collection" and dataset_name:
163
+ return f"{dataset_name}.{name}"
164
+ if name_type == "field" and dataset_name and collection_name:
165
+ return f"{dataset_name}.{collection_name}.{name}"
166
+ return name
167
+
168
+ def _process_row(
169
+ self, row: dict[str, Any], dataset_name: str, collection_name: str
170
+ ) -> dict[str, Any]:
171
+ """Process a single row with field redaction."""
172
+ processed = {}
173
+
174
+ # Use enumerate for positional indexing (matches original)
175
+ for field_index, (field_name, value) in enumerate(row.items(), start=1):
176
+ redacted_field = self._redact_name(
177
+ "field", field_name, field_index, dataset_name, collection_name
178
+ )
179
+
180
+ # Process nested values
181
+ base_path = f"{dataset_name}.{collection_name}.{field_name}"
182
+ processed[redacted_field] = self._process_nested_value(value, base_path)
183
+
184
+ return processed
185
+
186
+ def _process_nested_value(
187
+ self,
188
+ value: Any,
189
+ current_path: str,
190
+ field_index_counter: Optional[Dict[str, Dict[str, int]]] = None,
191
+ ) -> Any:
192
+ """Recursively process nested values matching original logic."""
193
+ if field_index_counter is None:
194
+ field_index_counter = {}
195
+
196
+ if isinstance(value, dict):
197
+ processed = {}
198
+ level_key = f"{current_path}_fields"
199
+
200
+ if level_key not in field_index_counter:
201
+ field_index_counter[level_key] = {}
202
+
203
+ for field_name, field_value in value.items():
204
+ full_path = f"{current_path}.{field_name}"
205
+
206
+ # Get or create index for this field at this level
207
+ if field_name not in field_index_counter[level_key]:
208
+ field_index_counter[level_key][field_name] = (
209
+ len(field_index_counter[level_key]) + 1
210
+ )
211
+
212
+ if self._should_redact(field_name, full_path):
213
+ redacted_name = (
214
+ f"field_{field_index_counter[level_key][field_name]}"
215
+ )
216
+ else:
217
+ redacted_name = field_name
218
+
219
+ processed[redacted_name] = self._process_nested_value(
220
+ field_value, full_path, field_index_counter
221
+ )
222
+
223
+ return processed
224
+
225
+ if isinstance(value, list):
226
+ return [
227
+ self._process_nested_value(item, current_path, field_index_counter)
228
+ for item in value
229
+ ]
230
+
231
+ return value
@@ -5,13 +5,18 @@ import time as time_module
5
5
  import zipfile
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
+ from sqlalchemy.orm import object_session
13
14
 
14
- from fides.api.schemas.policy import ActionType
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
15
20
  from fides.api.service.storage.util import (
16
21
  _get_datasets_from_dsr_data,
17
22
  create_attachment_info_dict,
@@ -31,12 +36,9 @@ TEXT_COLOR = "#4A5568"
31
36
  HEADER_COLOR = "#FAFAFA"
32
37
  BORDER_COLOR = "#E2E8F0"
33
38
 
34
- if TYPE_CHECKING:
35
- from fides.api.models.privacy_request import PrivacyRequest # pragma: no cover
36
-
37
39
 
38
40
  # pylint: disable=too-many-instance-attributes
39
- class DsrReportBuilder:
41
+ class DSRReportBuilder:
40
42
  """
41
43
  Manages populating HTML templates from the given data and adding the generated
42
44
  pages to a zip file in a way that the pages can be navigated between.
@@ -48,7 +50,7 @@ class DsrReportBuilder:
48
50
  - data/dataset_name/collection_name/item_index.html: the detail page for the item
49
51
  - attachments/index.html: the index page for the attachments
50
52
 
51
- Args:
53
+ Args:
52
54
  privacy_request: the privacy request object
53
55
  dsr_data: the DSR data
54
56
  """
@@ -86,9 +88,18 @@ class DsrReportBuilder:
86
88
  }
87
89
  self.main_links: dict[str, Any] = {} # used to track the generated pages
88
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
+
89
100
  # report data to populate the templates
90
- self.request_data = _map_privacy_request(privacy_request)
91
- self.dsr_data = dsr_data
101
+ self.request_data = map_privacy_request(privacy_request)
102
+ self.dsr_data = processed_dsr_data
92
103
  self.enable_streaming = enable_streaming
93
104
 
94
105
  # Track used filenames per dataset to prevent conflicts within the same dataset
@@ -150,10 +161,6 @@ class DsrReportBuilder:
150
161
  """
151
162
  Generates a page for each collection in the dataset and an index page for the dataset.
152
163
  Tracks the generated links to build a root level index after each collection has been processed.
153
-
154
- Args:
155
- dataset_name: the name of the dataset to add
156
- collections: the collections to add to the dataset
157
164
  """
158
165
  # track links to collection indexes
159
166
  collection_links = {}
@@ -325,19 +332,17 @@ class DsrReportBuilder:
325
332
  return corrected_attachment_info
326
333
 
327
334
  def _add_collection(
328
- 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,
329
339
  ) -> None:
330
340
  """
331
341
  Adds a collection to the zip file.
332
-
333
- Args:
334
- rows: the rows to add to the collection
335
- dataset_name: the name of the dataset to add the collection to
336
- collection_name: the name of the collection to add
337
342
  """
338
343
  items_content = []
339
344
 
340
- for index, collection_item in enumerate(rows, 1):
345
+ for item_index, collection_item in enumerate(rows, 1):
341
346
  # Create a deep copy of the item data to avoid modifying the original DSR data
342
347
  # This ensures the comprehensive attachments index can access unmodified attachments
343
348
  item_data = copy.deepcopy(collection_item)
@@ -377,11 +382,13 @@ class DsrReportBuilder:
377
382
  # Replace the field value with processed attachment links
378
383
  item_data[field_name] = attachment_links
379
384
 
380
- # Add item content to the list
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})"
381
388
  items_content.append(
382
389
  {
383
- "index": index,
384
- "heading": f"{collection_name} (item #{index})",
390
+ "index": item_index,
391
+ "heading": item_heading,
385
392
  "data": item_data,
386
393
  }
387
394
  )
@@ -518,6 +525,7 @@ class DsrReportBuilder:
518
525
  # Add Additional Data if it exists
519
526
  if "dataset" in datasets:
520
527
  self._add_dataset("dataset", datasets["dataset"])
528
+ # Use a more friendly name for the link but keep the dataset name for the path
521
529
  self.main_links["Additional Data"] = "data/dataset/index.html"
522
530
 
523
531
  # Add comprehensive attachments index that includes ALL attachments
@@ -569,27 +577,3 @@ class DsrReportBuilder:
569
577
  "DSR report generation complete."
570
578
  )
571
579
  return self.baos
572
-
573
-
574
- def _map_privacy_request(privacy_request: "PrivacyRequest") -> dict[str, Any]:
575
- """Creates a map with a subset of values from the privacy request"""
576
- request_data: dict[str, Any] = {}
577
- request_data["id"] = privacy_request.id
578
-
579
- action_type: Optional[ActionType] = privacy_request.policy.get_action_type()
580
- if action_type:
581
- request_data["type"] = action_type.value
582
-
583
- request_data["identity"] = {
584
- key: value
585
- for key, value in privacy_request.get_persisted_identity()
586
- .labeled_dict(include_default_labels=True)
587
- .items()
588
- if value["value"] is not None
589
- }
590
-
591
- if privacy_request.requested_at:
592
- request_data["requested_at"] = privacy_request.requested_at.strftime(
593
- "%m/%d/%Y %H:%M %Z"
594
- )
595
- return request_data