ethyca-fides 2.68.1b3__py2.py3-none-any.whl → 2.69.0__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 (271) hide show
  1. {ethyca_fides-2.68.1b3.dist-info → ethyca_fides-2.69.0.dist-info}/METADATA +1 -1
  2. {ethyca_fides-2.68.1b3.dist-info → ethyca_fides-2.69.0.dist-info}/RECORD +249 -243
  3. fides/_version.py +3 -3
  4. fides/api/alembic/migrations/versions/b1a2c3d4e5f6_add_location_to_privacy_request.py +26 -0
  5. fides/api/api/v1/api.py +2 -0
  6. fides/api/api/v1/endpoints/dsr_package_link.py +169 -0
  7. fides/api/api/v1/endpoints/oauth_endpoints.py +1 -0
  8. fides/api/api/v1/endpoints/privacy_request_endpoints.py +32 -1
  9. fides/api/api/v1/endpoints/user_endpoints.py +4 -0
  10. fides/api/models/privacy_request/privacy_request.py +1 -0
  11. fides/api/models/privacy_request/webhook.py +33 -1
  12. fides/api/oauth/utils.py +118 -50
  13. fides/api/schemas/application_config.py +7 -0
  14. fides/api/schemas/connection_configuration/connection_type_system_map.py +6 -0
  15. fides/api/schemas/enums/__init__.py +0 -0
  16. fides/api/schemas/enums/connection_category.py +20 -0
  17. fides/api/schemas/enums/integration_feature.py +26 -0
  18. fides/api/schemas/external_https.py +9 -0
  19. fides/api/schemas/privacy_request.py +16 -0
  20. fides/api/schemas/saas/connector_template.py +5 -0
  21. fides/api/schemas/saas/display_info.py +19 -0
  22. fides/api/schemas/saas/saas_config.py +2 -0
  23. fides/api/schemas/storage/storage.py +2 -0
  24. fides/api/service/connectors/saas/connector_registry_service.py +7 -0
  25. fides/api/service/privacy_request/dsr_package/dsr_report_builder.py +253 -71
  26. fides/api/service/privacy_request/dsr_package/templates/attachments_index.html +4 -2
  27. fides/api/service/privacy_request/dsr_package/templates/collection_index.html +3 -1
  28. fides/api/service/privacy_request/dsr_package/templates/dataset_index.html +1 -1
  29. fides/api/service/privacy_request/request_runner_service.py +52 -6
  30. fides/api/service/privacy_request/request_service.py +18 -1
  31. fides/api/service/storage/storage_uploader_service.py +1 -21
  32. fides/api/service/storage/streaming/dsr_storage.py +1 -5
  33. fides/api/service/storage/streaming/s3/s3_storage_client.py +78 -40
  34. fides/api/service/storage/streaming/s3/streaming_s3.py +9 -21
  35. fides/api/service/storage/streaming/smart_open_client.py +8 -7
  36. fides/api/service/storage/streaming/smart_open_streaming_storage.py +109 -196
  37. fides/api/service/storage/streaming/storage_client_factory.py +7 -3
  38. fides/api/service/storage/util.py +579 -0
  39. fides/api/task/manual/manual_task_graph_task.py +11 -9
  40. fides/api/util/connection_type.py +3 -0
  41. fides/api/util/saas_util.py +12 -1
  42. fides/api/util/text.py +51 -0
  43. fides/common/api/v1/urn_registry.py +3 -0
  44. fides/service/privacy_request/privacy_request_service.py +83 -0
  45. fides/ui-build/static/admin/404.html +1 -1
  46. fides/ui-build/static/admin/_next/static/Pc_eOxj5LbY3XOShbrjSX/_buildManifest.js +1 -0
  47. fides/ui-build/static/admin/_next/static/chunks/1099-79646e64f26d62fa.js +1 -0
  48. fides/ui-build/static/admin/_next/static/chunks/{1345-ab756811e19ff4fc.js → 1345-5e1c5b66e25c566e.js} +1 -1
  49. fides/ui-build/static/admin/_next/static/chunks/{1817-fd21f1f5ef0faffa.js → 1817-3d9e110e007853f0.js} +1 -1
  50. fides/ui-build/static/admin/_next/static/chunks/{1975.16126463309143e3.js → 1975.78e719130cfe3fd6.js} +1 -1
  51. fides/ui-build/static/admin/_next/static/chunks/{2921-0e5cc63a82e31830.js → 2921-52328140bc420d0f.js} +1 -1
  52. fides/ui-build/static/admin/_next/static/chunks/{3620-6cceae71bae5b531.js → 3620-31ebb43dba84cbbd.js} +1 -1
  53. fides/ui-build/static/admin/_next/static/chunks/3729-a1ca1608efc11ac4.js +1 -0
  54. fides/ui-build/static/admin/_next/static/chunks/3847-230bf61b053bc706.js +1 -0
  55. fides/ui-build/static/admin/_next/static/chunks/{3855-64541570e2f838fb.js → 3855-ef5194cdb228beb6.js} +1 -1
  56. fides/ui-build/static/admin/_next/static/chunks/{3872-7a18d18a5e287e4e.js → 3872-a91143aa35fa8ef8.js} +1 -1
  57. fides/ui-build/static/admin/_next/static/chunks/{3923-5c87b3d7f1626678.js → 3923-bb2417b8dcade7a4.js} +1 -1
  58. fides/ui-build/static/admin/_next/static/chunks/{401-3902e3e98790d401.js → 401-4af0a912e249d30f.js} +1 -1
  59. fides/ui-build/static/admin/_next/static/chunks/{4121-64ef70ef906bbdd0.js → 4121-c8d5d717e31899e1.js} +1 -1
  60. fides/ui-build/static/admin/_next/static/chunks/4164-355644b916ae0094.js +1 -0
  61. fides/ui-build/static/admin/_next/static/chunks/{4608-70521532195124de.js → 4608-23bbd4c3c4a59f42.js} +1 -1
  62. fides/ui-build/static/admin/_next/static/chunks/4786-0827aae7aceadd22.js +1 -0
  63. fides/ui-build/static/admin/_next/static/chunks/4808-78ca630f2d2503cd.js +1 -0
  64. fides/ui-build/static/admin/_next/static/chunks/{4844-351f99b6644b654e.js → 4844-46324c3d848b8b6a.js} +1 -1
  65. fides/ui-build/static/admin/_next/static/chunks/{5258-c6f96dc740eb5fb1.js → 5258-b0de22a8521686ab.js} +1 -1
  66. fides/ui-build/static/admin/_next/static/chunks/{5487-338800277d36b8d7.js → 5487-8c635883dcaa9c2a.js} +1 -1
  67. fides/ui-build/static/admin/_next/static/chunks/549-38ea1d91ee2addaa.js +1 -0
  68. fides/ui-build/static/admin/_next/static/chunks/{6084-da63f20d9416a982.js → 6084-0096d7de64ef8015.js} +1 -1
  69. fides/ui-build/static/admin/_next/static/chunks/{6853-1d947b75eb07188c.js → 6853-b17673391117c976.js} +1 -1
  70. fides/ui-build/static/admin/_next/static/chunks/{6954-24f9a4f27d67b732.js → 6954-9d46e2276c461c26.js} +1 -1
  71. fides/ui-build/static/admin/_next/static/chunks/{7476-a0dd03bfccf60d0c.js → 7476-d1b0af9ade392e5b.js} +1 -1
  72. fides/ui-build/static/admin/_next/static/chunks/{7630-9fbe06cfb98266fe.js → 7630-da0a7ce4e3a0d62c.js} +1 -1
  73. fides/ui-build/static/admin/_next/static/chunks/{787-3dd31844cf7fec55.js → 787-3499983fa346b380.js} +1 -1
  74. fides/ui-build/static/admin/_next/static/chunks/{79-dcd20e8b09501c17.js → 79-f197fc4db8d530e5.js} +1 -1
  75. fides/ui-build/static/admin/_next/static/chunks/{796-8773e04b64ce2260.js → 796-db1e30119ea973c7.js} +1 -1
  76. fides/ui-build/static/admin/_next/static/chunks/8002-971e29181f72edd1.js +1 -0
  77. fides/ui-build/static/admin/_next/static/chunks/{9046-57eab238570b8bf4.js → 9046-712156d461165f56.js} +1 -1
  78. fides/ui-build/static/admin/_next/static/chunks/{9676.bf0a8a6ff6dfd2af.js → 9676.9fd9552ef744c717.js} +1 -1
  79. fides/ui-build/static/admin/_next/static/chunks/{9826-756c958aecab59a2.js → 9826-b0b3d3cfb13bfbc1.js} +1 -1
  80. fides/ui-build/static/admin/_next/static/chunks/{9951-cdf73904a3adb27b.js → 9951-a88367a129b724ba.js} +1 -1
  81. fides/ui-build/static/admin/_next/static/chunks/pages/{404-dd625a559ada46ca.js → 404-471a6b18e712f050.js} +1 -1
  82. fides/ui-build/static/admin/_next/static/chunks/pages/{_app-b6b09b2878b77b21.js → _app-ef8e1c986bc5b795.js} +12 -12
  83. fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/{manual-92cf5e313be1f9e2.js → manual-9dc7e70ab5b05723.js} +1 -1
  84. fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/{multiple-d6c525ee731a2993.js → multiple-4b79a1652297ed9a.js} +1 -1
  85. fides/ui-build/static/admin/_next/static/chunks/pages/{add-systems-5664a3ea796e5ffb.js → add-systems-1632a59203fe8eab.js} +1 -1
  86. fides/ui-build/static/admin/_next/static/chunks/pages/consent/configure/{add-vendors-78f13de90111fd80.js → add-vendors-1ca9df7ca91bd101.js} +1 -1
  87. fides/ui-build/static/admin/_next/static/chunks/pages/consent/{configure-0fc678f3d6d2fcec.js → configure-07bdbc9ae4137db4.js} +1 -1
  88. fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience/{[id]-126db59dc25ca326.js → [id]-f80cf2d3966816fd.js} +1 -1
  89. fides/ui-build/static/admin/_next/static/chunks/pages/consent/{privacy-experience-289605267d6cce7e.js → privacy-experience-2795cd4115a77c94.js} +1 -1
  90. fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{[id]-e9fd9b28ac9705af.js → [id]-e02921dc82dccbb1.js} +1 -1
  91. fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{new-28c003b6043bd16c.js → new-98f9e4ba3610628a.js} +1 -1
  92. fides/ui-build/static/admin/_next/static/chunks/pages/consent/{privacy-notices-c643eff04525298e.js → privacy-notices-17ed82777810d1c6.js} +1 -1
  93. fides/ui-build/static/admin/_next/static/chunks/pages/consent/{properties-3ef5d01779a26455.js → properties-226efa1dcd41437f.js} +1 -1
  94. fides/ui-build/static/admin/_next/static/chunks/pages/consent/reporting-28b192e2c074b0f3.js +1 -0
  95. fides/ui-build/static/admin/_next/static/chunks/pages/{consent-8d4be9e7ec7d2a35.js → consent-09610b10923d9268.js} +1 -1
  96. fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/[projectUrn]/{[resourceUrn]-f27ec4578c674181.js → [resourceUrn]-da1a48336daff6f8.js} +1 -1
  97. fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/{[projectUrn]-27b6c255bd9e73b6.js → [projectUrn]-d8e776f1e64e4ba8.js} +1 -1
  98. fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/{projects-0f66dac32040519c.js → projects-75b9629b0d9cdf96.js} +1 -1
  99. fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/resources/{[resourceUrn]-3b938562df81c4b0.js → [resourceUrn]-470da05db63767cd.js} +1 -1
  100. fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog-6984c033b8fe3a13.js +1 -0
  101. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/[systemId]-2f0a33ef9ba1f1da.js +1 -0
  102. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]-e9d4f25b20ff6781.js +1 -0
  103. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center-9c428d3ef0985915.js +1 -0
  104. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{activity-21c141279e66237a.js → activity-b6ae7adb8ef0b525.js} +1 -1
  105. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/detection/{[resourceUrn]-3bc6a207693fd175.js → [resourceUrn]-c3a97e6721ca0abe.js} +1 -1
  106. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{detection-da16e73df395ad1d.js → detection-a0a7de552ef71f5b.js} +1 -1
  107. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/discovery/{[resourceUrn]-04b242632a114405.js → [resourceUrn]-109754fec0755339.js} +1 -1
  108. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{discovery-900fe50183a40d72.js → discovery-88654783b06b3b21.js} +1 -1
  109. fides/ui-build/static/admin/_next/static/chunks/pages/{datamap-4f1f7c3a9531a8f4.js → datamap-89136e6800dc9369.js} +1 -1
  110. fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/[collectionName]/{[...subfieldNames]-343294dcb10d9532.js → [...subfieldNames]-8f58192dcb54883d.js} +1 -1
  111. fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/{[collectionName]-1c097a0809fa5b6f.js → [collectionName]-dcb4ab380a77aa1e.js} +1 -1
  112. fides/ui-build/static/admin/_next/static/chunks/pages/dataset/{[datasetId]-b47fa2498b534719.js → [datasetId]-6f16d43071fb9c11.js} +1 -1
  113. fides/ui-build/static/admin/_next/static/chunks/pages/dataset/{new-a31f881cab25704a.js → new-97f06e21580f1f6a.js} +1 -1
  114. fides/ui-build/static/admin/_next/static/chunks/pages/{dataset-858c59c9e67e318d.js → dataset-674bb3940f088ecc.js} +1 -1
  115. fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/{[id]-16c28d272225afb6.js → [id]-6f77d8647fca71e0.js} +1 -1
  116. fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/new-821dd1269834cfa2.js +1 -0
  117. fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection-23e4caf79faa8106.js +1 -0
  118. fides/ui-build/static/admin/_next/static/chunks/pages/{index-fec557d99211f577.js → index-23eb64eed81dcb69.js} +1 -1
  119. fides/ui-build/static/admin/_next/static/chunks/pages/integrations/[id]-3a4cd3fe9094fba3.js +1 -0
  120. fides/ui-build/static/admin/_next/static/chunks/pages/integrations-57e618d7b16ac69a.js +1 -0
  121. fides/ui-build/static/admin/_next/static/chunks/pages/messaging/{[id]-4a08ca7762a19700.js → [id]-c9a323eb6a929476.js} +1 -1
  122. fides/ui-build/static/admin/_next/static/chunks/pages/messaging/{add-template-343a965dcdb3d11e.js → add-template-b9bb09e46921a590.js} +1 -1
  123. fides/ui-build/static/admin/_next/static/chunks/pages/{messaging-3ade4c54b1c8a11e.js → messaging-82c631a12b5a008c.js} +1 -1
  124. fides/ui-build/static/admin/_next/static/chunks/pages/poc/{ant-components-9103bfb854f71410.js → ant-components-bc0e2adf6e0d3ac7.js} +1 -1
  125. fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/{AntForm-3b97029bd4d3c3ea.js → AntForm-86ffcc1ad3fa912e.js} +1 -1
  126. fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/{FormikAntFormItem-9d9beb8f0d8a278c.js → FormikAntFormItem-ec04f595465bdf69.js} +1 -1
  127. fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/{FormikControlled-84a4d8fc60f839ed.js → FormikControlled-41d309754ff0c1de.js} +1 -1
  128. fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/{FormikField-1fccf542ab2e33bf.js → FormikField-cab1f78cec7808f9.js} +1 -1
  129. fides/ui-build/static/admin/_next/static/chunks/pages/poc/{forms-aa75263ae1ba67bb.js → forms-eb6058221403b156.js} +1 -1
  130. fides/ui-build/static/admin/_next/static/chunks/pages/poc/{table-migration-db334a1cbb102255.js → table-migration-38360083348c3d6c.js} +1 -1
  131. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/{[id]-11c1e4545c8f528c.js → [id]-0d0bb9eb004a3336.js} +1 -1
  132. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/{messaging-192a986f61c23268.js → messaging-f9320a58f489f5b7.js} +1 -1
  133. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/{storage-9216ac993d71387e.js → storage-d0cfa8aeddd43a40.js} +1 -1
  134. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/{configure-e55ec84d5380401d.js → configure-72ca94ec5ed85733.js} +1 -1
  135. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests-5a5edc8a4aa7c30a.js +1 -0
  136. fides/ui-build/static/admin/_next/static/chunks/pages/properties/{[id]-a74b51b704b80cb2.js → [id]-5ec775c4904fdbfe.js} +1 -1
  137. fides/ui-build/static/admin/_next/static/chunks/pages/properties/{add-property-8d23f0c55ff6510a.js → add-property-a6812c0916f2949e.js} +1 -1
  138. fides/ui-build/static/admin/_next/static/chunks/pages/{properties-77acceac4f99e7af.js → properties-da734840e4f9d04b.js} +1 -1
  139. fides/ui-build/static/admin/_next/static/chunks/pages/reporting/datamap-6903f42a0412bfa6.js +1 -0
  140. fides/ui-build/static/admin/_next/static/chunks/pages/settings/about/{alpha-6aad3f563ed03b3f.js → alpha-3e72e9f91991c119.js} +1 -1
  141. fides/ui-build/static/admin/_next/static/chunks/pages/settings/{about-c1b8f3606d160bb1.js → about-6aab092f4871cecb.js} +1 -1
  142. fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent/[configuration_id]/{[purpose_id]-d9f7f78810d58d08.js → [purpose_id]-9495e2eb506606c7.js} +1 -1
  143. fides/ui-build/static/admin/_next/static/chunks/pages/settings/{consent-ee2c7dde99b1dafb.js → consent-be47008304106395.js} +1 -1
  144. fides/ui-build/static/admin/_next/static/chunks/pages/settings/custom-fields-ae1b57589da7b175.js +1 -0
  145. fides/ui-build/static/admin/_next/static/chunks/pages/settings/{domain-records-31c270d228e00581.js → domain-records-23a6d7a921150188.js} +1 -1
  146. fides/ui-build/static/admin/_next/static/chunks/pages/settings/{domains-996b3f250dd3ea1f.js → domains-2a9e8859ab4d9de6.js} +1 -1
  147. fides/ui-build/static/admin/_next/static/chunks/pages/settings/{email-templates-ee94981326ddcbf4.js → email-templates-4f9f0fdf9925ae90.js} +1 -1
  148. fides/ui-build/static/admin/_next/static/chunks/pages/settings/{locations-0b831c58966782b8.js → locations-46f7af35cee4a8bb.js} +1 -1
  149. fides/ui-build/static/admin/_next/static/chunks/pages/settings/{organization-94271ba4a224a353.js → organization-a596a96cb8d0aa8e.js} +1 -1
  150. fides/ui-build/static/admin/_next/static/chunks/pages/settings/{regulations-41b8136e50320fd3.js → regulations-6ed5fc2410e00857.js} +1 -1
  151. fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/[id]/{test-datasets-52b45569cbc82e60.js → test-datasets-86811e3cda277e77.js} +1 -1
  152. fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/{[id]-36d74e93e54aabaf.js → [id]-5a43f108d8047d5b.js} +1 -1
  153. fides/ui-build/static/admin/_next/static/chunks/pages/{systems-24dfc8e2279ced2e.js → systems-045a841e22e85ea8.js} +1 -1
  154. fides/ui-build/static/admin/_next/static/chunks/pages/{taxonomy-d9675cf5e6083b27.js → taxonomy-1b3f2d4bcb0e164d.js} +1 -1
  155. fides/ui-build/static/admin/_next/static/chunks/pages/user-management/profile/{[id]-866826d7959df487.js → [id]-05d61c80a556b2d5.js} +1 -1
  156. fides/ui-build/static/admin/_next/static/chunks/pages/{user-management-e63b61a8f99ccd57.js → user-management-2cab41659f1ee7da.js} +1 -1
  157. fides/ui-build/static/admin/_next/static/chunks/{webpack-6d0a487039bcf30c.js → webpack-678e89d68dbcd94f.js} +1 -1
  158. fides/ui-build/static/admin/_next/static/css/650df9c348000a26.css +1 -0
  159. fides/ui-build/static/admin/_next/static/css/{dbcf63488933a4d5.css → 98fab0b3e6aa43ed.css} +1 -1
  160. fides/ui-build/static/admin/add-systems/manual.html +1 -1
  161. fides/ui-build/static/admin/add-systems/multiple.html +1 -1
  162. fides/ui-build/static/admin/add-systems.html +1 -1
  163. fides/ui-build/static/admin/consent/configure/add-vendors.html +1 -1
  164. fides/ui-build/static/admin/consent/configure.html +1 -1
  165. fides/ui-build/static/admin/consent/privacy-experience/[id].html +1 -1
  166. fides/ui-build/static/admin/consent/privacy-experience/new.html +1 -1
  167. fides/ui-build/static/admin/consent/privacy-experience.html +1 -1
  168. fides/ui-build/static/admin/consent/privacy-notices/[id].html +1 -1
  169. fides/ui-build/static/admin/consent/privacy-notices/new.html +1 -1
  170. fides/ui-build/static/admin/consent/privacy-notices.html +1 -1
  171. fides/ui-build/static/admin/consent/properties.html +1 -1
  172. fides/ui-build/static/admin/consent/reporting.html +1 -1
  173. fides/ui-build/static/admin/consent.html +1 -1
  174. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn]/[resourceUrn].html +1 -1
  175. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn].html +1 -1
  176. fides/ui-build/static/admin/data-catalog/[systemId]/projects.html +1 -1
  177. fides/ui-build/static/admin/data-catalog/[systemId]/resources/[resourceUrn].html +1 -1
  178. fides/ui-build/static/admin/data-catalog/[systemId]/resources.html +1 -1
  179. fides/ui-build/static/admin/data-catalog.html +1 -1
  180. fides/ui-build/static/admin/data-discovery/action-center/[monitorId]/[systemId].html +1 -1
  181. fides/ui-build/static/admin/data-discovery/action-center/[monitorId].html +1 -1
  182. fides/ui-build/static/admin/data-discovery/action-center.html +1 -1
  183. fides/ui-build/static/admin/data-discovery/activity.html +1 -1
  184. fides/ui-build/static/admin/data-discovery/detection/[resourceUrn].html +1 -1
  185. fides/ui-build/static/admin/data-discovery/detection.html +1 -1
  186. fides/ui-build/static/admin/data-discovery/discovery/[resourceUrn].html +1 -1
  187. fides/ui-build/static/admin/data-discovery/discovery.html +1 -1
  188. fides/ui-build/static/admin/datamap.html +1 -1
  189. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName]/[...subfieldNames].html +1 -1
  190. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName].html +1 -1
  191. fides/ui-build/static/admin/dataset/[datasetId].html +1 -1
  192. fides/ui-build/static/admin/dataset/new.html +1 -1
  193. fides/ui-build/static/admin/dataset.html +1 -1
  194. fides/ui-build/static/admin/datastore-connection/[id].html +1 -1
  195. fides/ui-build/static/admin/datastore-connection/new.html +1 -1
  196. fides/ui-build/static/admin/datastore-connection.html +1 -1
  197. fides/ui-build/static/admin/index.html +1 -1
  198. fides/ui-build/static/admin/integrations/[id].html +1 -1
  199. fides/ui-build/static/admin/integrations.html +1 -1
  200. fides/ui-build/static/admin/lib/fides-ext-gpp.js +1 -1
  201. fides/ui-build/static/admin/lib/fides-headless.js +1 -1
  202. fides/ui-build/static/admin/lib/fides-preview.js +1 -1
  203. fides/ui-build/static/admin/lib/fides-tcf.js +2 -2
  204. fides/ui-build/static/admin/lib/fides.js +2 -2
  205. fides/ui-build/static/admin/login/[provider].html +1 -1
  206. fides/ui-build/static/admin/login.html +1 -1
  207. fides/ui-build/static/admin/messaging/[id].html +1 -1
  208. fides/ui-build/static/admin/messaging/add-template.html +1 -1
  209. fides/ui-build/static/admin/messaging.html +1 -1
  210. fides/ui-build/static/admin/poc/ant-components.html +1 -1
  211. fides/ui-build/static/admin/poc/form-experiments/AntForm.html +1 -1
  212. fides/ui-build/static/admin/poc/form-experiments/FormikAntFormItem.html +1 -1
  213. fides/ui-build/static/admin/poc/form-experiments/FormikControlled.html +1 -1
  214. fides/ui-build/static/admin/poc/form-experiments/FormikField.html +1 -1
  215. fides/ui-build/static/admin/poc/form-experiments/FormikSpreadField.html +1 -1
  216. fides/ui-build/static/admin/poc/forms.html +1 -1
  217. fides/ui-build/static/admin/poc/table-migration.html +1 -1
  218. fides/ui-build/static/admin/privacy-requests/[id].html +1 -1
  219. fides/ui-build/static/admin/privacy-requests/configure/messaging.html +1 -1
  220. fides/ui-build/static/admin/privacy-requests/configure/storage.html +1 -1
  221. fides/ui-build/static/admin/privacy-requests/configure.html +1 -1
  222. fides/ui-build/static/admin/privacy-requests.html +1 -1
  223. fides/ui-build/static/admin/properties/[id].html +1 -1
  224. fides/ui-build/static/admin/properties/add-property.html +1 -1
  225. fides/ui-build/static/admin/properties.html +1 -1
  226. fides/ui-build/static/admin/reporting/datamap.html +1 -1
  227. fides/ui-build/static/admin/settings/about/alpha.html +1 -1
  228. fides/ui-build/static/admin/settings/about.html +1 -1
  229. fides/ui-build/static/admin/settings/consent/[configuration_id]/[purpose_id].html +1 -1
  230. fides/ui-build/static/admin/settings/consent.html +1 -1
  231. fides/ui-build/static/admin/settings/custom-fields.html +1 -1
  232. fides/ui-build/static/admin/settings/domain-records.html +1 -1
  233. fides/ui-build/static/admin/settings/domains.html +1 -1
  234. fides/ui-build/static/admin/settings/email-templates.html +1 -1
  235. fides/ui-build/static/admin/settings/locations.html +1 -1
  236. fides/ui-build/static/admin/settings/organization.html +1 -1
  237. fides/ui-build/static/admin/settings/regulations.html +1 -1
  238. fides/ui-build/static/admin/systems/configure/[id]/test-datasets.html +1 -1
  239. fides/ui-build/static/admin/systems/configure/[id].html +1 -1
  240. fides/ui-build/static/admin/systems.html +1 -1
  241. fides/ui-build/static/admin/taxonomy.html +1 -1
  242. fides/ui-build/static/admin/user-management/new.html +1 -1
  243. fides/ui-build/static/admin/user-management/profile/[id].html +1 -1
  244. fides/ui-build/static/admin/user-management.html +1 -1
  245. fides/ui-build/static/admin/_next/static/_BLI2ArqQzY5XnXbrcxa2/_buildManifest.js +0 -1
  246. fides/ui-build/static/admin/_next/static/chunks/1099-7b2085a3931da9e4.js +0 -1
  247. fides/ui-build/static/admin/_next/static/chunks/1138-0d846ffef62c580f.js +0 -1
  248. fides/ui-build/static/admin/_next/static/chunks/3729-7d2d52400f1f7413.js +0 -1
  249. fides/ui-build/static/admin/_next/static/chunks/4786-53ef1662f2d0d98c.js +0 -1
  250. fides/ui-build/static/admin/_next/static/chunks/4808-8713433c84a62efe.js +0 -1
  251. fides/ui-build/static/admin/_next/static/chunks/549-e6453a3526023e85.js +0 -1
  252. fides/ui-build/static/admin/_next/static/chunks/602-80d113e801d7407d.js +0 -1
  253. fides/ui-build/static/admin/_next/static/chunks/8002-dcd02da6e5649a1c.js +0 -1
  254. fides/ui-build/static/admin/_next/static/chunks/pages/consent/reporting-baa4a2f8f08ac224.js +0 -1
  255. fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog-ebf5e7fa4e2ffb49.js +0 -1
  256. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/[systemId]-b27c660039d951c9.js +0 -1
  257. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]-8ce5d24af470888e.js +0 -1
  258. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center-8e35e33928abbcdc.js +0 -1
  259. fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/new-68f502d8b0b5792c.js +0 -1
  260. fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection-1eb9acb17b133fd1.js +0 -1
  261. fides/ui-build/static/admin/_next/static/chunks/pages/integrations/[id]-e613543818d6cbd2.js +0 -1
  262. fides/ui-build/static/admin/_next/static/chunks/pages/integrations-8069f7c33695fd45.js +0 -1
  263. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests-48f447b31c786b80.js +0 -1
  264. fides/ui-build/static/admin/_next/static/chunks/pages/reporting/datamap-e60d398e255f4e00.js +0 -1
  265. fides/ui-build/static/admin/_next/static/chunks/pages/settings/custom-fields-a4dad8ca9de2d07b.js +0 -1
  266. fides/ui-build/static/admin/_next/static/css/92441453b27e9c34.css +0 -1
  267. {ethyca_fides-2.68.1b3.dist-info → ethyca_fides-2.69.0.dist-info}/WHEEL +0 -0
  268. {ethyca_fides-2.68.1b3.dist-info → ethyca_fides-2.69.0.dist-info}/entry_points.txt +0 -0
  269. {ethyca_fides-2.68.1b3.dist-info → ethyca_fides-2.69.0.dist-info}/licenses/LICENSE +0 -0
  270. {ethyca_fides-2.68.1b3.dist-info → ethyca_fides-2.69.0.dist-info}/top_level.txt +0 -0
  271. /fides/ui-build/static/admin/_next/static/{_BLI2ArqQzY5XnXbrcxa2 → Pc_eOxj5LbY3XOShbrjSX}/_ssgManifest.js +0 -0
fides/_version.py CHANGED
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2025-08-25T16:34:15-0600",
11
+ "date": "2025-09-03T13:11:38-0400",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "05d56cc8111caa00faea2dd8433a36fd4d5b7a8a",
15
- "version": "2.68.1b3"
14
+ "full-revisionid": "23d1e78103b4b6527ab8d28f26e0d4f052653ee7",
15
+ "version": "2.69.0"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -0,0 +1,26 @@
1
+ """add location to privacy request
2
+
3
+ Revision ID: b1a2c3d4e5f6
4
+ Revises: 2f3c1a2d6b10
5
+ Create Date: 2025-08-06 18:15:00.000000
6
+
7
+ """
8
+
9
+ import sqlalchemy as sa
10
+ from alembic import op
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = "b1a2c3d4e5f6"
14
+ down_revision = "90502bcda282"
15
+ branch_labels = None
16
+ depends_on = None
17
+
18
+
19
+ def upgrade():
20
+ # Add location column to privacyrequest table
21
+ op.add_column("privacyrequest", sa.Column("location", sa.String(), nullable=True))
22
+
23
+
24
+ def downgrade():
25
+ # Remove location column from privacyrequest table
26
+ op.drop_column("privacyrequest", "location")
fides/api/api/v1/api.py CHANGED
@@ -5,6 +5,7 @@ from fides.api.api.v1.endpoints import (
5
5
  consent_request_endpoints,
6
6
  dataset_config_endpoints,
7
7
  drp_endpoints,
8
+ dsr_package_link,
8
9
  encryption_endpoints,
9
10
  identity_verification_endpoints,
10
11
  manual_webhook_endpoints,
@@ -32,6 +33,7 @@ api_router.include_router(connection_endpoints.router)
32
33
  api_router.include_router(consent_request_endpoints.router)
33
34
  api_router.include_router(dataset_config_endpoints.router)
34
35
  api_router.include_router(drp_endpoints.router)
36
+ api_router.include_router(dsr_package_link.router)
35
37
  api_router.include_router(encryption_endpoints.router)
36
38
  api_router.include_router(masking_endpoints.router)
37
39
  api_router.include_router(oauth_endpoints.router)
@@ -0,0 +1,169 @@
1
+ import uuid
2
+
3
+ from fastapi import Depends, HTTPException, Request, Response
4
+ from fastapi.responses import RedirectResponse
5
+ from sqlalchemy.orm import Session
6
+ from starlette.status import (
7
+ HTTP_302_FOUND,
8
+ HTTP_400_BAD_REQUEST,
9
+ HTTP_401_UNAUTHORIZED,
10
+ HTTP_403_FORBIDDEN,
11
+ HTTP_404_NOT_FOUND,
12
+ HTTP_422_UNPROCESSABLE_ENTITY,
13
+ )
14
+
15
+ from fides.api.api.deps import get_db
16
+ from fides.api.common_exceptions import AuthenticationError, AuthorizationError
17
+ from fides.api.models.privacy_request import PrivacyRequest
18
+ from fides.api.models.storage import get_active_default_storage_config
19
+ from fides.api.oauth.utils import validate_download_token
20
+ from fides.api.schemas.privacy_request import PrivacyRequestStatus
21
+ from fides.api.schemas.storage.storage import StorageType
22
+ from fides.api.service.storage.streaming.s3 import S3StorageClient
23
+ from fides.api.util.api_router import APIRouter
24
+ from fides.api.util.endpoint_utils import fides_limiter
25
+ from fides.common.api.v1.urn_registry import PRIVACY_CENTER_DSR_PACKAGE, V1_URL_PREFIX
26
+ from fides.config import CONFIG
27
+
28
+ router = APIRouter(tags=["Privacy Center"], prefix=V1_URL_PREFIX)
29
+
30
+
31
+ def get_privacy_request_or_error(
32
+ privacy_request_id: str, db: Session
33
+ ) -> PrivacyRequest:
34
+ """Load the privacy request or throw a 404"""
35
+ # Note: UUID format validation is now done earlier in the endpoint
36
+ privacy_request = PrivacyRequest.get(db, object_id=privacy_request_id)
37
+
38
+ if not privacy_request:
39
+ raise HTTPException(
40
+ status_code=HTTP_404_NOT_FOUND,
41
+ detail=f"No privacy request found with id '{privacy_request_id}'.",
42
+ )
43
+
44
+ if privacy_request.deleted_at is not None:
45
+ raise HTTPException(
46
+ status_code=HTTP_422_UNPROCESSABLE_ENTITY,
47
+ detail=f"Privacy request with id {privacy_request_id} has been deleted.",
48
+ )
49
+
50
+ return privacy_request
51
+
52
+
53
+ def raise_error(status_code: int, detail: str) -> None:
54
+ """Raise an HTTPException with the given status code and detail"""
55
+ raise HTTPException(
56
+ status_code=status_code,
57
+ detail=detail,
58
+ )
59
+
60
+
61
+ @router.get(
62
+ PRIVACY_CENTER_DSR_PACKAGE,
63
+ status_code=HTTP_302_FOUND,
64
+ )
65
+ @fides_limiter.limit(CONFIG.security.public_request_rate_limit)
66
+ def get_access_results_urls(
67
+ privacy_request_id: str,
68
+ token: str,
69
+ db: Session = Depends(get_db),
70
+ *,
71
+ request: Request, # required for rate limiting
72
+ response: Response, # required for rate limiting
73
+ ) -> RedirectResponse:
74
+ """
75
+ Public endpoint for retrieving access results URLs for a privacy request.
76
+ This endpoint generates fresh presigned URLs and redirects to the first available result.
77
+ This endpoint is designed to be accessible via email links sent to end users.
78
+ No authentication is required, but a valid download token is required for security.
79
+ Rate limiting is applied for additional security.
80
+
81
+ privacy_request_id parameter is required in the URL path.
82
+ token parameter is required as a query parameter for security.
83
+ """
84
+ # --------------Security checks--------------
85
+ # First validate the privacy request ID format to prevent SSRF attacks
86
+ # This is a simple string check that doesn't require database access
87
+ if not privacy_request_id.startswith("pri_"):
88
+ raise_error(
89
+ HTTP_400_BAD_REQUEST,
90
+ f"Invalid privacy request ID format: '{privacy_request_id}'. Must start with 'pri_' followed by a valid UUID v4.",
91
+ )
92
+
93
+ # Extract the UUID part after the prefix
94
+ uuid_part = privacy_request_id[4:] # Remove "pri_" prefix
95
+
96
+ try:
97
+ uuid.UUID(uuid_part, version=4)
98
+ except ValueError:
99
+ raise_error(
100
+ HTTP_400_BAD_REQUEST,
101
+ f"Invalid privacy request ID format: '{privacy_request_id}'. Must start with 'pri_' followed by a valid UUID v4.",
102
+ )
103
+
104
+ try:
105
+ # Validate the download token before proceeding
106
+ validate_download_token(token, privacy_request_id)
107
+ except AuthenticationError as e:
108
+ raise_error(HTTP_401_UNAUTHORIZED, str(e.detail))
109
+ except AuthorizationError as e:
110
+ raise_error(HTTP_403_FORBIDDEN, str(e.detail))
111
+
112
+ # --------------Data checks--------------
113
+ storage_config = get_active_default_storage_config(db)
114
+ privacy_request = get_privacy_request_or_error(privacy_request_id, db)
115
+
116
+ if not storage_config:
117
+ raise_error(
118
+ HTTP_400_BAD_REQUEST, "No active default storage configuration found."
119
+ )
120
+
121
+ if privacy_request.status != PrivacyRequestStatus.complete:
122
+ raise_error(
123
+ HTTP_400_BAD_REQUEST,
124
+ f"Access results for privacy request '{privacy_request.id}' are not available because the request is not complete.",
125
+ )
126
+
127
+ if (
128
+ not privacy_request.access_result_urls
129
+ or not privacy_request.access_result_urls.get("access_result_urls")
130
+ ):
131
+ raise_error(
132
+ HTTP_404_NOT_FOUND,
133
+ f"No access results found for privacy request '{privacy_request.id}'.",
134
+ )
135
+
136
+ # --------------Processing--------------
137
+ file_name = f"{privacy_request.id}.zip"
138
+
139
+ # At this point, storage_config is guaranteed to exist due to earlier validation
140
+ # and we've already checked that it's not None above
141
+ assert storage_config is not None
142
+
143
+ if storage_config.type != StorageType.s3:
144
+ # Handle all other storage types (transcend, ethyca, local, etc.)
145
+ raise_error(
146
+ HTTP_400_BAD_REQUEST,
147
+ f"Storage type '{storage_config.type}' is not supported for download redirects. "
148
+ "Only S3 storage is supported for this endpoint.",
149
+ )
150
+
151
+ # Get bucket name from storage config
152
+ bucket_name = storage_config.details.get("bucket")
153
+ if not bucket_name:
154
+ raise_error(HTTP_400_BAD_REQUEST, "S3 bucket name not found in storage config.")
155
+
156
+ try:
157
+ # Use S3StorageClient for cleaner presigned URL generation
158
+ s3_storage_client = S3StorageClient(
159
+ storage_config.details.get("auth_method"), storage_config.secrets
160
+ )
161
+ result_url = s3_storage_client.generate_presigned_url(
162
+ bucket=bucket_name,
163
+ key=file_name,
164
+ )
165
+ except Exception as e:
166
+ raise_error(HTTP_400_BAD_REQUEST, f"Failed to generate presigned URL: {str(e)}")
167
+
168
+ # Convert the URL to string for RedirectResponse
169
+ return RedirectResponse(url=str(result_url), status_code=HTTP_302_FOUND)
@@ -67,6 +67,7 @@ router = APIRouter(tags=["OAuth"], prefix=V1_URL_PREFIX)
67
67
  )
68
68
  async def acquire_access_token(
69
69
  request: Request,
70
+ response: Response,
70
71
  form_data: OAuth2ClientCredentialsRequestForm = Depends(),
71
72
  db: Session = Depends(get_db),
72
73
  ) -> AccessToken:
@@ -86,7 +86,7 @@ from fides.api.oauth.utils import (
86
86
  from fides.api.schemas.api import ResponseWithMessage
87
87
  from fides.api.schemas.dataset import CollectionAddressResponse, DryRunDatasetResponse
88
88
  from fides.api.schemas.external_https import PrivacyRequestResumeFormat
89
- from fides.api.schemas.policy import ActionType
89
+ from fides.api.schemas.policy import ActionType, CurrentStep
90
90
  from fides.api.schemas.privacy_request import (
91
91
  BulkPostPrivacyRequests,
92
92
  BulkReviewResponse,
@@ -124,6 +124,7 @@ from fides.api.util.endpoint_utils import validate_start_and_end_filters
124
124
  from fides.api.util.enums import ColumnSort
125
125
  from fides.api.util.fuzzy_search_utils import get_decrypted_identities_automaton
126
126
  from fides.api.util.storage_util import StorageJSONEncoder
127
+ from fides.api.util.text import normalize_location_code
127
128
  from fides.common.api.scope_registry import (
128
129
  PRIVACY_REQUEST_CALLBACK_RESUME,
129
130
  PRIVACY_REQUEST_CREATE,
@@ -403,6 +404,7 @@ def _filter_privacy_request_queryset(
403
404
  errored_lt: Optional[datetime] = None,
404
405
  errored_gt: Optional[datetime] = None,
405
406
  external_id: Optional[str] = None,
407
+ location: Optional[str] = None,
406
408
  action_type: Optional[ActionType] = None,
407
409
  include_consent_webhook_requests: Optional[bool] = False,
408
410
  include_deleted_requests: Optional[bool] = False,
@@ -539,6 +541,29 @@ def _filter_privacy_request_queryset(
539
541
  query = query.filter(PrivacyRequest.id.ilike(f"%{request_id}%"))
540
542
  if external_id:
541
543
  query = query.filter(PrivacyRequest.external_id.ilike(f"{external_id}%"))
544
+ if location:
545
+ # Support filtering by exact location match or country prefix
546
+ # e.g., "US" matches both "US" and "US-CA", "US-NY", etc.
547
+ # "US-CA" matches only "US-CA"
548
+ # Also normalize input to handle underscores and case insensitivity
549
+
550
+ try:
551
+ normalized_location = normalize_location_code(location)
552
+ except ValueError:
553
+ # If normalization fails, treat as no results to prevent errors
554
+ query = query.filter(False)
555
+ else:
556
+ if "-" in normalized_location:
557
+ # Exact match for subdivision codes
558
+ query = query.filter(PrivacyRequest.location == normalized_location)
559
+ else:
560
+ # Country code - match country or any subdivision of that country
561
+ query = query.filter(
562
+ or_(
563
+ PrivacyRequest.location == normalized_location,
564
+ PrivacyRequest.location.ilike(f"{normalized_location}-%"),
565
+ )
566
+ )
542
567
  if status:
543
568
  query = query.filter(PrivacyRequest.status.in_(status))
544
569
  if created_lt:
@@ -685,6 +710,7 @@ def _shared_privacy_request_search(
685
710
  errored_lt: Optional[datetime] = None,
686
711
  errored_gt: Optional[datetime] = None,
687
712
  external_id: Optional[str] = None,
713
+ location: Optional[str] = None,
688
714
  action_type: Optional[ActionType] = None,
689
715
  verbose: Optional[bool] = False,
690
716
  include_identities: Optional[bool] = False,
@@ -723,6 +749,7 @@ def _shared_privacy_request_search(
723
749
  errored_lt,
724
750
  errored_gt,
725
751
  external_id,
752
+ location,
726
753
  action_type,
727
754
  None,
728
755
  include_deleted_requests,
@@ -796,6 +823,7 @@ def get_request_status(
796
823
  errored_lt: Optional[datetime] = None,
797
824
  errored_gt: Optional[datetime] = None,
798
825
  external_id: Optional[str] = None,
826
+ location: Optional[str] = None,
799
827
  action_type: Optional[ActionType] = None,
800
828
  verbose: Optional[bool] = False,
801
829
  include_identities: Optional[bool] = False,
@@ -836,6 +864,7 @@ def get_request_status(
836
864
  errored_lt=errored_lt,
837
865
  errored_gt=errored_gt,
838
866
  external_id=external_id,
867
+ location=location,
839
868
  action_type=action_type,
840
869
  verbose=verbose,
841
870
  include_identities=include_identities,
@@ -890,6 +919,7 @@ def privacy_request_search(
890
919
  errored_lt=privacy_request_filter.errored_lt,
891
920
  errored_gt=privacy_request_filter.errored_gt,
892
921
  external_id=privacy_request_filter.external_id,
922
+ location=privacy_request_filter.location,
893
923
  action_type=privacy_request_filter.action_type,
894
924
  verbose=privacy_request_filter.verbose,
895
925
  include_identities=privacy_request_filter.include_identities,
@@ -1900,6 +1930,7 @@ def finalize_privacy_request(
1900
1930
 
1901
1931
  queue_privacy_request(
1902
1932
  privacy_request_id=privacy_request_id,
1933
+ from_step=CurrentStep.finalization.value,
1903
1934
  )
1904
1935
 
1905
1936
  return privacy_request # type: ignore[return-value]
@@ -250,6 +250,10 @@ def logout_oauth_client(
250
250
  if authorization is None:
251
251
  raise AuthenticationError(detail="Authentication Failure")
252
252
 
253
+ # Validate that the token looks like a valid JWE token (5 segments separated by dots)
254
+ if not authorization or authorization.count(".") != 4:
255
+ return None
256
+
253
257
  try:
254
258
  token_data = json.loads(
255
259
  extract_payload(authorization, CONFIG.security.app_encryption_key)
@@ -222,6 +222,7 @@ class PrivacyRequest(
222
222
  canceled_at = Column(DateTime(timezone=True), nullable=True)
223
223
  consent_preferences = Column(MutableList.as_mutable(JSONB), nullable=True)
224
224
  source = Column(EnumColumn(PrivacyRequestSource), nullable=True)
225
+ location = Column(String, nullable=True)
225
226
 
226
227
  # A PrivacyRequest can be soft deleted, so we store when it was deleted
227
228
  deleted_at = Column(DateTime(timezone=True), nullable=True)
@@ -13,12 +13,17 @@ from fides.api.models.policy import PolicyPreWebhook, WebhookDirection
13
13
  from fides.api.models.pre_approval_webhook import PreApprovalWebhook
14
14
  from fides.api.models.privacy_request.request_task import RequestTask
15
15
  from fides.api.oauth.jwt import generate_jwe
16
- from fides.api.schemas.external_https import RequestTaskJWE, WebhookJWE
16
+ from fides.api.schemas.external_https import (
17
+ DownloadTokenJWE,
18
+ RequestTaskJWE,
19
+ WebhookJWE,
20
+ )
17
21
  from fides.api.schemas.policy import ActionType
18
22
  from fides.api.schemas.privacy_request import PrivacyRequestStatus
19
23
  from fides.api.schemas.redis_cache import Identity
20
24
  from fides.common.api.scope_registry import (
21
25
  PRIVACY_REQUEST_CALLBACK_RESUME,
26
+ PRIVACY_REQUEST_READ_ACCESS_RESULTS,
22
27
  PRIVACY_REQUEST_REVIEW,
23
28
  )
24
29
  from fides.config import CONFIG
@@ -92,3 +97,30 @@ def generate_request_task_callback_jwe(request_task: RequestTask) -> str:
92
97
  json.dumps(jwe.model_dump(mode="json")),
93
98
  CONFIG.security.app_encryption_key,
94
99
  )
100
+
101
+
102
+ def generate_privacy_request_download_token(privacy_request_id: str) -> str:
103
+ """
104
+ Generate a JWE token for users to download their privacy request access package.
105
+ This is currently used for the DSR package link endpoint which provides a redirect
106
+ to a presigned URL for the access results.
107
+ This token expires based on the configured TTL for security.
108
+ """
109
+ from datetime import timedelta
110
+
111
+ now = datetime.now()
112
+ # Use the configured TTL from security settings
113
+ expiration = now + timedelta(
114
+ seconds=CONFIG.security.subject_request_download_link_ttl_seconds
115
+ )
116
+
117
+ jwe = DownloadTokenJWE(
118
+ privacy_request_id=privacy_request_id,
119
+ scopes=[PRIVACY_REQUEST_READ_ACCESS_RESULTS],
120
+ iat=now.isoformat(),
121
+ exp=expiration.isoformat(),
122
+ )
123
+ return generate_jwe(
124
+ json.dumps(jwe.model_dump(mode="json")),
125
+ CONFIG.security.app_encryption_key,
126
+ )
fides/api/oauth/utils.py CHANGED
@@ -32,7 +32,11 @@ from fides.api.models.pre_approval_webhook import PreApprovalWebhook
32
32
  from fides.api.models.privacy_request import RequestTask
33
33
  from fides.api.oauth.roles import get_scopes_from_roles
34
34
  from fides.api.request_context import set_user_id
35
- from fides.api.schemas.external_https import RequestTaskJWE, WebhookJWE
35
+ from fides.api.schemas.external_https import (
36
+ DownloadTokenJWE,
37
+ RequestTaskJWE,
38
+ WebhookJWE,
39
+ )
36
40
  from fides.api.schemas.oauth import OAuth2ClientCredentialsBearer
37
41
  from fides.common.api.v1.urn_registry import TOKEN, V1_URL_PREFIX
38
42
  from fides.config import CONFIG, FidesConfig
@@ -49,57 +53,25 @@ oauth2_scheme = OAuth2ClientCredentialsBearer(
49
53
 
50
54
  def extract_payload(jwe_string: str, encryption_key: str) -> str:
51
55
  """Given a jwe, extracts the payload and returns it in string form."""
52
- return jwe.decrypt(jwe_string, encryption_key)
56
+ try:
57
+ decrypted_payload = jwe.decrypt(jwe_string, encryption_key)
58
+ return decrypted_payload.decode("utf-8")
59
+ except exceptions.JWEError as e:
60
+ logger.debug("Failed to decrypt JWE: {}", e)
61
+ raise e
53
62
 
54
63
 
55
- def is_token_expired(issued_at: datetime | None, token_duration_min: int) -> bool:
56
- """Returns True if the datetime is earlier than token_duration_min ago."""
57
- if not issued_at:
64
+ def is_token_expired(
65
+ issued_at: Optional[datetime], token_duration_minutes: int
66
+ ) -> bool:
67
+ """Check if a token has expired based on its issued_at timestamp and duration."""
68
+ if issued_at is None:
58
69
  return True
59
-
60
- return (datetime.now() - issued_at).total_seconds() / 60.0 > token_duration_min
61
-
62
-
63
- def copy_func(source_function: Callable) -> Callable:
64
- """Based on http://stackoverflow.com/a/6528148/190597 (Glenn Maynard)"""
65
- target_function = FunctionType(
66
- source_function.__code__,
67
- source_function.__globals__,
68
- name=source_function.__name__,
69
- argdefs=source_function.__defaults__,
70
- closure=source_function.__closure__,
71
- )
72
- updated_target_function: Callable = update_wrapper(target_function, source_function)
73
- updated_target_function.__kwdefaults__ = source_function.__kwdefaults__
74
- return updated_target_function
70
+ return (datetime.now() - issued_at).total_seconds() / 60.0 > token_duration_minutes
75
71
 
76
72
 
77
- async def get_current_user(
78
- security_scopes: SecurityScopes,
79
- authorization: str = Security(oauth2_scheme),
80
- db: Session = Depends(get_db),
81
- ) -> FidesUser:
82
- """A wrapper around verify_oauth_client that returns that client's user if one exists."""
83
- client = await verify_oauth_client(
84
- security_scopes=security_scopes,
85
- authorization=authorization,
86
- db=db,
87
- )
88
-
89
- if client.id == CONFIG.security.oauth_root_client_id:
90
- return FidesUser(
91
- id=CONFIG.security.oauth_root_client_id,
92
- username=CONFIG.security.root_username,
93
- created_at=datetime.utcnow(),
94
- )
95
-
96
- return client.user # type: ignore[attr-defined]
97
-
98
-
99
- def is_callback_token_expired(issued_at: datetime | None) -> bool:
100
- """Returns True if the token is older than the expiration of the redis cache. We
101
- can't resume executing the privacy request if the identity data is gone.
102
- """
73
+ def is_callback_token_expired(issued_at: Optional[datetime]) -> bool:
74
+ """Check if a callback token has expired (24 hours)."""
103
75
  if not issued_at:
104
76
  return True
105
77
 
@@ -114,9 +86,13 @@ def _get_webhook_jwe_or_error(
114
86
  if authorization is None:
115
87
  raise AuthenticationError(detail="Authentication Failure")
116
88
 
117
- token_data = json.loads(
118
- extract_payload(authorization, CONFIG.security.app_encryption_key)
119
- )
89
+ try:
90
+ token_data = json.loads(
91
+ extract_payload(authorization, CONFIG.security.app_encryption_key)
92
+ )
93
+ except exceptions.JWEError:
94
+ raise AuthorizationError(detail="Not Authorized for this action")
95
+
120
96
  try:
121
97
  token = WebhookJWE(**token_data)
122
98
  except ValidationError:
@@ -160,6 +136,98 @@ def _get_request_task_jwe_or_error(
160
136
  return token
161
137
 
162
138
 
139
+ def validate_download_token(token: str, privacy_request_id: str) -> DownloadTokenJWE:
140
+ """
141
+ Validate a download token for accessing privacy request packages.
142
+
143
+ Args:
144
+ token: The JWE token to validate
145
+ privacy_request_id: The privacy request ID the token should grant access to
146
+
147
+ Returns:
148
+ The validated DownloadTokenJWE object
149
+
150
+ Raises:
151
+ AuthenticationError: If token is invalid or expired
152
+ AuthorizationError: If token doesn't grant access to the requested privacy request
153
+ """
154
+ if not token:
155
+ raise AuthenticationError(detail="Download token is required")
156
+
157
+ # Check if token looks like a JWE (should have 5 parts separated by dots)
158
+ if token.count(".") != 4:
159
+ raise AuthenticationError(detail="Invalid download token format")
160
+
161
+ try:
162
+ token_data = json.loads(
163
+ extract_payload(token, CONFIG.security.app_encryption_key)
164
+ )
165
+ except exceptions.JWEError:
166
+ raise AuthenticationError(detail="Invalid download token format")
167
+
168
+ try:
169
+ download_token = DownloadTokenJWE(**token_data)
170
+ except ValidationError:
171
+ raise AuthenticationError(detail="Invalid download token structure")
172
+
173
+ # Verify the token grants access to the requested privacy request
174
+ if download_token.privacy_request_id != privacy_request_id:
175
+ raise AuthorizationError(
176
+ detail="Download token does not grant access to this privacy request"
177
+ )
178
+
179
+ # Verify the token has the required scope
180
+ required_scope = "privacy-request-access-results:read"
181
+ if required_scope not in download_token.scopes:
182
+ raise AuthorizationError(detail="Download token lacks required permissions")
183
+
184
+ # Check if the token has expired
185
+ try:
186
+ expiration_time = datetime.fromisoformat(download_token.exp)
187
+ if datetime.now() > expiration_time:
188
+ raise AuthenticationError(detail="Download token has expired")
189
+ except (ValueError, TypeError):
190
+ raise AuthenticationError(detail="Invalid token expiration format")
191
+
192
+ return download_token
193
+
194
+
195
+ def copy_func(source_function: Callable) -> Callable:
196
+ """Based on http://stackoverflow.com/a/6528148/190597 (Glenn Maynard)"""
197
+ target_function = FunctionType(
198
+ source_function.__code__,
199
+ source_function.__globals__,
200
+ name=source_function.__name__,
201
+ argdefs=source_function.__defaults__,
202
+ closure=source_function.__closure__,
203
+ )
204
+ updated_target_function: Callable = update_wrapper(target_function, source_function)
205
+ updated_target_function.__kwdefaults__ = source_function.__kwdefaults__
206
+ return updated_target_function
207
+
208
+
209
+ async def get_current_user(
210
+ security_scopes: SecurityScopes,
211
+ authorization: str = Security(oauth2_scheme),
212
+ db: Session = Depends(get_db),
213
+ ) -> FidesUser:
214
+ """A wrapper around verify_oauth_client that returns that client's user if one exists."""
215
+ client = await verify_oauth_client(
216
+ security_scopes=security_scopes,
217
+ authorization=authorization,
218
+ db=db,
219
+ )
220
+
221
+ if client.id == CONFIG.security.oauth_root_client_id:
222
+ return FidesUser(
223
+ id=CONFIG.security.oauth_root_client_id,
224
+ username=CONFIG.security.root_username,
225
+ created_at=datetime.utcnow(),
226
+ )
227
+
228
+ return client.user # type: ignore[attr-defined]
229
+
230
+
163
231
  def verify_callback_oauth_policy_pre_webhook(
164
232
  security_scopes: SecurityScopes,
165
233
  authorization: str = Security(oauth2_scheme),
@@ -87,6 +87,12 @@ class AdminUIConfig(FidesSchema):
87
87
  model_config = ConfigDict(extra="forbid")
88
88
 
89
89
 
90
+ class PrivacyCenterConfig(FidesSchema):
91
+ url: SerializeAsAny[Optional[AnyHttpUrlStringRemovesSlash]] = None
92
+
93
+ model_config = ConfigDict(extra="forbid")
94
+
95
+
90
96
  class ConsentConfig(FidesSchema):
91
97
  override_vendor_purposes: Optional[bool]
92
98
  model_config = ConfigDict(extra="forbid")
@@ -119,6 +125,7 @@ class ApplicationConfig(FidesSchema):
119
125
  security: Optional[SecurityApplicationConfig] = None
120
126
  consent: Optional[ConsentConfig] = None
121
127
  admin_ui: Optional[AdminUIConfig] = None
128
+ privacy_center: Optional[PrivacyCenterConfig] = None
122
129
 
123
130
  @model_validator(mode="before")
124
131
  @classmethod
@@ -4,6 +4,8 @@ from pydantic import BaseModel, ConfigDict
4
4
 
5
5
  from fides.api.models.connectionconfig import ConnectionType
6
6
  from fides.api.schemas.connection_configuration.enums.system_type import SystemType
7
+ from fides.api.schemas.enums.connection_category import ConnectionCategory
8
+ from fides.api.schemas.enums.integration_feature import IntegrationFeature
7
9
  from fides.api.schemas.policy import ActionType
8
10
 
9
11
 
@@ -19,4 +21,8 @@ class ConnectionSystemTypeMap(BaseModel):
19
21
  authorization_required: Optional[bool] = False
20
22
  user_guide: Optional[str] = None
21
23
  supported_actions: List[ActionType]
24
+ # New fields for enhanced display information
25
+ category: Optional[ConnectionCategory] = None
26
+ tags: Optional[List[str]] = None
27
+ enabled_features: Optional[List[IntegrationFeature]] = None
22
28
  model_config = ConfigDict(use_enum_values=True, from_attributes=True)
File without changes
@@ -0,0 +1,20 @@
1
+ from enum import Enum
2
+
3
+
4
+ class ConnectionCategory(str, Enum):
5
+ """
6
+ Categories for connection types, matching frontend ConnectionCategory enum
7
+ """
8
+
9
+ DATA_CATALOG = "DATA_CATALOG"
10
+ DATA_WAREHOUSE = "DATA_WAREHOUSE"
11
+ DATABASE = "DATABASE"
12
+ IDENTITY_PROVIDER = "IDENTITY_PROVIDER"
13
+ WEBSITE = "WEBSITE"
14
+ CRM = "CRM"
15
+ MANUAL = "MANUAL"
16
+ MARKETING = "MARKETING"
17
+ ANALYTICS = "ANALYTICS"
18
+ ECOMMERCE = "ECOMMERCE"
19
+ COMMUNICATION = "COMMUNICATION"
20
+ CUSTOM = "CUSTOM" # Fallback for uncategorized/custom uploaded integrations