ethyca-fides 2.66.2b0__py2.py3-none-any.whl → 2.66.2b2__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 (165) hide show
  1. {ethyca_fides-2.66.2b0.dist-info → ethyca_fides-2.66.2b2.dist-info}/METADATA +1 -1
  2. {ethyca_fides-2.66.2b0.dist-info → ethyca_fides-2.66.2b2.dist-info}/RECORD +156 -154
  3. fides/_version.py +3 -3
  4. fides/api/alembic/migrations/versions/a7065df4dcf1_add_finalized_fields_to_privacy_request.py +65 -0
  5. fides/api/api/v1/endpoints/privacy_request_endpoints.py +44 -1
  6. fides/api/graph/traversal.py +1 -1
  7. fides/api/models/privacy_request/execution_log.py +1 -0
  8. fides/api/models/privacy_request/privacy_request.py +6 -0
  9. fides/api/schemas/policy.py +1 -0
  10. fides/api/schemas/privacy_request.py +5 -0
  11. fides/api/service/privacy_request/request_runner_service.py +116 -53
  12. fides/api/task/create_request_tasks.py +1 -1
  13. fides/api/task/execute_request_tasks.py +1 -1
  14. fides/api/task/filter_results.py +1 -1
  15. fides/api/task/manual/manual_task_address.py +46 -0
  16. fides/api/task/manual/manual_task_graph_task.py +118 -127
  17. fides/api/task/manual/manual_task_utils.py +52 -105
  18. fides/common/api/v1/urn_registry.py +1 -1
  19. fides/config/execution_settings.py +4 -0
  20. fides/ui-build/static/admin/404.html +1 -1
  21. fides/ui-build/static/admin/_next/static/{QGhYEMWDATmdofDzIl0_2 → EACyrT3Bb5qN9POVQHTCB}/_buildManifest.js +1 -1
  22. fides/ui-build/static/admin/_next/static/chunks/{203-d9fe5384a6e94799.js → 203-4e777c324a01dbec.js} +1 -1
  23. fides/ui-build/static/admin/_next/static/chunks/5309-1172322bf91b5d57.js +1 -0
  24. fides/ui-build/static/admin/_next/static/chunks/{6780-4b687168dd8daa84.js → 6780-a00c87739acc004d.js} +1 -1
  25. fides/ui-build/static/admin/_next/static/chunks/9046-c8233981762585b4.js +1 -0
  26. fides/ui-build/static/admin/_next/static/chunks/pages/{_app-65e47e67bee20654.js → _app-f2c3d287bac00395.js} +1 -1
  27. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]-7d3115059503b904.js +1 -0
  28. fides/ui-build/static/admin/_next/static/chunks/pages/integrations/[id]-f9c0eac932188593.js +1 -0
  29. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/[id]-c73497fc333c324d.js +1 -0
  30. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/messaging-7498d1d5974a78b0.js +1 -0
  31. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/storage-2d3a2d967767a131.js +1 -0
  32. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests-6a9068df48bdee05.js +1 -0
  33. fides/ui-build/static/admin/_next/static/css/79e296c724c1568c.css +1 -0
  34. fides/ui-build/static/admin/_next/static/css/{94965f224bc991e9.css → 8bc1833f1fa53ff0.css} +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/lib/fides-preview.js +1 -1
  76. fides/ui-build/static/admin/lib/fides-tcf.js +1 -1
  77. fides/ui-build/static/admin/lib/fides.js +1 -1
  78. fides/ui-build/static/admin/login/[provider].html +1 -1
  79. fides/ui-build/static/admin/login.html +1 -1
  80. fides/ui-build/static/admin/messaging/[id].html +1 -1
  81. fides/ui-build/static/admin/messaging/add-template.html +1 -1
  82. fides/ui-build/static/admin/messaging.html +1 -1
  83. fides/ui-build/static/admin/poc/ant-components.html +1 -1
  84. fides/ui-build/static/admin/poc/form-experiments/AntForm.html +1 -1
  85. fides/ui-build/static/admin/poc/form-experiments/FormikAntFormItem.html +1 -1
  86. fides/ui-build/static/admin/poc/form-experiments/FormikControlled.html +1 -1
  87. fides/ui-build/static/admin/poc/form-experiments/FormikField.html +1 -1
  88. fides/ui-build/static/admin/poc/form-experiments/FormikSpreadField.html +1 -1
  89. fides/ui-build/static/admin/poc/forms.html +1 -1
  90. fides/ui-build/static/admin/poc/table-migration.html +1 -1
  91. fides/ui-build/static/admin/privacy-requests/[id].html +1 -1
  92. fides/ui-build/static/admin/privacy-requests/configure/messaging.html +1 -1
  93. fides/ui-build/static/admin/privacy-requests/configure/storage.html +1 -1
  94. fides/ui-build/static/admin/privacy-requests/configure.html +1 -1
  95. fides/ui-build/static/admin/privacy-requests.html +1 -1
  96. fides/ui-build/static/admin/properties/[id].html +1 -1
  97. fides/ui-build/static/admin/properties/add-property.html +1 -1
  98. fides/ui-build/static/admin/properties.html +1 -1
  99. fides/ui-build/static/admin/reporting/datamap.html +1 -1
  100. fides/ui-build/static/admin/settings/about/alpha.html +1 -1
  101. fides/ui-build/static/admin/settings/about.html +1 -1
  102. fides/ui-build/static/admin/settings/consent/[configuration_id]/[purpose_id].html +1 -1
  103. fides/ui-build/static/admin/settings/consent.html +1 -1
  104. fides/ui-build/static/admin/settings/custom-fields.html +1 -1
  105. fides/ui-build/static/admin/settings/domain-records.html +1 -1
  106. fides/ui-build/static/admin/settings/domains.html +1 -1
  107. fides/ui-build/static/admin/settings/email-templates.html +1 -1
  108. fides/ui-build/static/admin/settings/locations.html +1 -1
  109. fides/ui-build/static/admin/settings/organization.html +1 -1
  110. fides/ui-build/static/admin/settings/regulations.html +1 -1
  111. fides/ui-build/static/admin/systems/configure/[id]/test-datasets.html +1 -1
  112. fides/ui-build/static/admin/systems/configure/[id].html +1 -1
  113. fides/ui-build/static/admin/systems.html +1 -1
  114. fides/ui-build/static/admin/taxonomy.html +1 -1
  115. fides/ui-build/static/admin/user-management/new.html +1 -1
  116. fides/ui-build/static/admin/user-management/profile/[id].html +1 -1
  117. fides/ui-build/static/admin/user-management.html +1 -1
  118. fides/ui-build/static/admin/_next/static/chunks/5309-67bdf9001531e972.js +0 -1
  119. fides/ui-build/static/admin/_next/static/chunks/9046-54877976a0529de2.js +0 -1
  120. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]-7e63ac744c45f6da.js +0 -1
  121. fides/ui-build/static/admin/_next/static/chunks/pages/integrations/[id]-8d83a5518c00fcfc.js +0 -1
  122. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/[id]-f18b2b83c4592f03.js +0 -1
  123. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/messaging-f9be923340cd99cf.js +0 -1
  124. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/storage-eb1ccc8a5dbf0fe5.js +0 -1
  125. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests-1daa805a3099c6d4.js +0 -1
  126. fides/ui-build/static/admin/_next/static/css/b81194f2c3930152.css +0 -1
  127. {ethyca_fides-2.66.2b0.dist-info → ethyca_fides-2.66.2b2.dist-info}/WHEEL +0 -0
  128. {ethyca_fides-2.66.2b0.dist-info → ethyca_fides-2.66.2b2.dist-info}/entry_points.txt +0 -0
  129. {ethyca_fides-2.66.2b0.dist-info → ethyca_fides-2.66.2b2.dist-info}/licenses/LICENSE +0 -0
  130. {ethyca_fides-2.66.2b0.dist-info → ethyca_fides-2.66.2b2.dist-info}/top_level.txt +0 -0
  131. /fides/ui-build/static/admin/_next/static/{QGhYEMWDATmdofDzIl0_2 → EACyrT3Bb5qN9POVQHTCB}/_ssgManifest.js +0 -0
  132. /fides/ui-build/static/admin/_next/static/chunks/{3450-f7bb8d46fbe4e78d.js → 3450-1cc2bb07ed142203.js} +0 -0
  133. /fides/ui-build/static/admin/_next/static/chunks/{3872-08c855f3edb230c4.js → 3872-84b7e380b88b4454.js} +0 -0
  134. /fides/ui-build/static/admin/_next/static/chunks/{5574-a7d8a753d273b11a.js → 5574-9312f97b637d9ee2.js} +0 -0
  135. /fides/ui-build/static/admin/_next/static/chunks/{6084-a872a8bc704c980f.js → 6084-5d7598b7bcb548cf.js} +0 -0
  136. /fides/ui-build/static/admin/_next/static/chunks/{6662-4a7805f74af51f1d.js → 6662-efb2cf74641647f2.js} +0 -0
  137. /fides/ui-build/static/admin/_next/static/chunks/{6882-85c6277d9531a83a.js → 6882-586b84aeb02d5830.js} +0 -0
  138. /fides/ui-build/static/admin/_next/static/chunks/{7476-cfe41e915f0c4f40.js → 7476-d206c11823c91088.js} +0 -0
  139. /fides/ui-build/static/admin/_next/static/chunks/{7630-7a8039aa37893129.js → 7630-b1c93688013ef013.js} +0 -0
  140. /fides/ui-build/static/admin/_next/static/chunks/{787-d05b25c73f49b6af.js → 787-cbe2d0bfb513d90a.js} +0 -0
  141. /fides/ui-build/static/admin/_next/static/chunks/{79-1952e8aab93b7209.js → 79-3db1941d274f40c7.js} +0 -0
  142. /fides/ui-build/static/admin/_next/static/chunks/{796-d2876f4d09a6d81f.js → 796-98d4bd68909fbe1e.js} +0 -0
  143. /fides/ui-build/static/admin/_next/static/chunks/{9226-7a799c930b73f217.js → 9226-72ad691ca57b83ef.js} +0 -0
  144. /fides/ui-build/static/admin/_next/static/chunks/{9826-b7d5467e3a3225c8.js → 9826-3c578665c6d3b21d.js} +0 -0
  145. /fides/ui-build/static/admin/_next/static/chunks/pages/{add-systems-f28841affa791975.js → add-systems-0943633a8e422695.js} +0 -0
  146. /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{configure-50d21e23b607688b.js → configure-0e1ca0f4c8e7f4da.js} +0 -0
  147. /fides/ui-build/static/admin/_next/static/chunks/pages/{consent-509691da6b06f834.js → consent-e17c56eec8d91371.js} +0 -0
  148. /fides/ui-build/static/admin/_next/static/chunks/pages/{data-catalog-2f43cfefc869b85f.js → data-catalog-8a7f9285da66b965.js} +0 -0
  149. /fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{action-center-409694b8441bd8fb.js → action-center-85e140788e251272.js} +0 -0
  150. /fides/ui-build/static/admin/_next/static/chunks/pages/{datamap-e707dc714452498e.js → datamap-d2b275d83089820d.js} +0 -0
  151. /fides/ui-build/static/admin/_next/static/chunks/pages/{datastore-connection-6865aa958f3da342.js → datastore-connection-0f29b47402292070.js} +0 -0
  152. /fides/ui-build/static/admin/_next/static/chunks/pages/{index-f127ebaac4689d10.js → index-12ac3e317fc86f21.js} +0 -0
  153. /fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/{configure-295bfe6880b209f2.js → configure-e551a860ec727802.js} +0 -0
  154. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/about/{alpha-8152e5828469cf91.js → alpha-8f98a4895e74725e.js} +0 -0
  155. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{about-9839e544924ac1f2.js → about-8155a35a62fdb5ae.js} +0 -0
  156. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{consent-f7a0f8367129cd70.js → consent-a989532a12c40dcf.js} +0 -0
  157. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{custom-fields-10df8d6fdc8bc149.js → custom-fields-45bea76ff7eda3cb.js} +0 -0
  158. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{domain-records-4056a3323cdc7042.js → domain-records-51333dbd21cb37c8.js} +0 -0
  159. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{domains-5a3691559262e874.js → domains-bde86e5f6c09da5a.js} +0 -0
  160. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{email-templates-e1d3c3d0ab878812.js → email-templates-4f9a5cc8bea7725b.js} +0 -0
  161. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{organization-26df33de21fb11b3.js → organization-55a10e01dffc8039.js} +0 -0
  162. /fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/[id]/{test-datasets-170c9c55f65ffb0f.js → test-datasets-f91f22cf96566ed4.js} +0 -0
  163. /fides/ui-build/static/admin/_next/static/chunks/pages/{systems-2112c006765c66b6.js → systems-648a0ff4920579ce.js} +0 -0
  164. /fides/ui-build/static/admin/_next/static/chunks/pages/{taxonomy-a53e89319582ce58.js → taxonomy-0b9d1a24188f65a9.js} +0 -0
  165. /fides/ui-build/static/admin/_next/static/chunks/{webpack-ff4c22c9f0840531.js → webpack-63a0c45b150a1037.js} +0 -0
fides/_version.py CHANGED
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2025-07-25T16:16:16-0700",
11
+ "date": "2025-07-29T12:07:43-0300",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "3805a2cc72d6f998672d84b64fd0f2c78a64f81d",
15
- "version": "2.66.2b0"
14
+ "full-revisionid": "4ba0450a533ee5c9b65ed8ae70df97cfb829ef8d",
15
+ "version": "2.66.2b2"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -0,0 +1,65 @@
1
+ """add finalized fields to privacy request
2
+
3
+ Revision ID: a7065df4dcf1
4
+ Revises: 7e9a2b52f498
5
+ Create Date: 2025-07-01 14:07:36.779437
6
+
7
+ """
8
+
9
+ import sqlalchemy as sa
10
+ from alembic import op
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = "a7065df4dcf1"
14
+ down_revision = "7e9a2b52f498"
15
+ branch_labels = None
16
+ depends_on = None
17
+
18
+
19
+ def upgrade():
20
+ # ### commands auto generated by Alembic - please adjust! ###
21
+ op.add_column(
22
+ "privacyrequest",
23
+ sa.Column("finalized_at", sa.DateTime(timezone=True), nullable=True),
24
+ )
25
+ op.add_column(
26
+ "privacyrequest", sa.Column("finalized_by", sa.String(), nullable=True)
27
+ )
28
+ op.create_foreign_key(
29
+ "privacyrequest_fidesuser_id_fkey",
30
+ "privacyrequest",
31
+ "fidesuser",
32
+ ["finalized_by"],
33
+ ["id"],
34
+ ondelete="SET NULL",
35
+ )
36
+ op.execute(
37
+ "alter type privacyrequeststatus add value 'requires_manual_finalization'"
38
+ )
39
+ # ### end Alembic commands ###
40
+
41
+
42
+ def downgrade():
43
+ # ### commands auto generated by Alembic - please adjust! ###
44
+ op.drop_constraint(
45
+ "privacyrequest_fidesuser_id_fkey", "privacyrequest", type_="foreignkey"
46
+ )
47
+ op.drop_column("privacyrequest", "finalized_by")
48
+ op.drop_column("privacyrequest", "finalized_at")
49
+
50
+ op.execute(
51
+ "delete from privacyrequest where status in ('requires_manual_finalization')"
52
+ )
53
+
54
+ op.execute("alter type privacyrequeststatus rename to privacyrequeststatus_old")
55
+ op.execute(
56
+ "create type privacyrequeststatus as enum('identity_unverified', 'requires_input', 'pending', 'in_processing', 'complete', 'pending', 'error', 'paused', 'approved', 'denied', 'canceled', 'awaiting_email_send')"
57
+ )
58
+ op.execute(
59
+ (
60
+ "alter table privacyrequest alter column status type privacyrequeststatus using "
61
+ "status::text::privacyrequeststatus"
62
+ )
63
+ )
64
+ op.execute("drop type privacyrequeststatus_old")
65
+ # ### end Alembic commands ###
@@ -4,7 +4,7 @@ import csv
4
4
  import io
5
5
  import json
6
6
  from collections import defaultdict
7
- from datetime import datetime
7
+ from datetime import datetime, timezone
8
8
  from typing import (
9
9
  Annotated,
10
10
  Any,
@@ -146,6 +146,7 @@ from fides.common.api.v1.urn_registry import (
146
146
  PRIVACY_REQUEST_BULK_SOFT_DELETE,
147
147
  PRIVACY_REQUEST_DENY,
148
148
  PRIVACY_REQUEST_FILTERED_RESULTS,
149
+ PRIVACY_REQUEST_FINALIZE,
149
150
  PRIVACY_REQUEST_MANUAL_WEBHOOK_ACCESS_INPUT,
150
151
  PRIVACY_REQUEST_MANUAL_WEBHOOK_ERASURE_INPUT,
151
152
  PRIVACY_REQUEST_NOTIFICATIONS,
@@ -1862,6 +1863,48 @@ def resume_privacy_request_from_requires_input(
1862
1863
  return privacy_request # type: ignore[return-value]
1863
1864
 
1864
1865
 
1866
+ @router.post(
1867
+ PRIVACY_REQUEST_FINALIZE,
1868
+ status_code=HTTP_200_OK,
1869
+ response_model=PrivacyRequestResponse,
1870
+ dependencies=[Security(verify_oauth_client, scopes=[PRIVACY_REQUEST_REVIEW])],
1871
+ )
1872
+ def finalize_privacy_request(
1873
+ privacy_request_id: str,
1874
+ *,
1875
+ db: Session = Depends(deps.get_db),
1876
+ client: ClientDetail = Security(
1877
+ verify_oauth_client,
1878
+ scopes=[PRIVACY_REQUEST_REVIEW],
1879
+ ),
1880
+ ) -> PrivacyRequestResponse:
1881
+ """
1882
+ Finalizes a privacy request, moving it from the 'requires_finalization' state to 'complete'.
1883
+ This is done by re-queueing the request, which will then hit the finalization logic in the
1884
+ request runner service. This logic marks the privacy request as complete
1885
+ and sends out any configured messaging to the user.
1886
+ """
1887
+ privacy_request = get_privacy_request_or_error(db, privacy_request_id)
1888
+
1889
+ if privacy_request.status != PrivacyRequestStatus.requires_manual_finalization:
1890
+ raise HTTPException(
1891
+ status_code=HTTP_400_BAD_REQUEST,
1892
+ detail=f"Cannot manually finalize privacy request '{privacy_request_id}': status is {privacy_request.status}, not requires_manual_finalization.",
1893
+ )
1894
+
1895
+ # Set finalized_by and finalized_at here, so the request runner service knows not to
1896
+ # put the request back into the requires_finalization state.
1897
+ privacy_request.finalized_at = datetime.now(timezone.utc)
1898
+ privacy_request.finalized_by = client.user_id
1899
+ privacy_request.save(db=db)
1900
+
1901
+ queue_privacy_request(
1902
+ privacy_request_id=privacy_request_id,
1903
+ )
1904
+
1905
+ return privacy_request # type: ignore[return-value]
1906
+
1907
+
1865
1908
  @router.get(
1866
1909
  REQUEST_TASKS,
1867
1910
  dependencies=[Security(verify_oauth_client, scopes=[PRIVACY_REQUEST_READ])],
@@ -115,7 +115,7 @@ class BaseTraversal:
115
115
  )
116
116
 
117
117
  # Ensure manual_task collections execute right after ROOT
118
- from fides.api.task.manual.manual_task_utils import ManualTaskAddress
118
+ from fides.api.task.manual.manual_task_address import ManualTaskAddress
119
119
 
120
120
  for addr in self.traversal_node_dict.keys():
121
121
  if ManualTaskAddress.is_manual_task_address(addr):
@@ -24,6 +24,7 @@ EXECUTION_CHECKPOINTS = [
24
24
  CurrentStep.finalize_consent,
25
25
  CurrentStep.email_post_send,
26
26
  CurrentStep.post_webhooks,
27
+ CurrentStep.finalization,
27
28
  ]
28
29
 
29
30
  COMPLETED_EXECUTION_LOG_STATUSES = [
@@ -145,6 +145,12 @@ class PrivacyRequest(
145
145
  ForeignKey(FidesUser.id_field_path, ondelete="SET NULL"),
146
146
  nullable=True,
147
147
  )
148
+ finalized_at = Column(DateTime(timezone=True), nullable=True)
149
+ finalized_by = Column(
150
+ String,
151
+ ForeignKey(FidesUser.id_field_path, ondelete="SET NULL"),
152
+ nullable=True,
153
+ )
148
154
  submitted_by = Column(
149
155
  String,
150
156
  ForeignKey(FidesUser.id_field_path, ondelete="SET NULL"),
@@ -28,6 +28,7 @@ class CurrentStep(EnumType):
28
28
  finalize_consent = "finalize_consent"
29
29
  email_post_send = "email_post_send"
30
30
  post_webhooks = "post_webhooks"
31
+ finalization = "finalization"
31
32
 
32
33
 
33
34
  # action types we actively support in policies/requests
@@ -124,6 +124,8 @@ class PrivacyRequestResubmit(PrivacyRequestCreate):
124
124
  identity_verified_at: Optional[datetime] = None
125
125
  custom_privacy_request_fields_approved_at: Optional[datetime] = None
126
126
  custom_privacy_request_fields_approved_by: Optional[str] = None
127
+ finalized_at: Optional[datetime] = None
128
+ finalized_by: Optional[str] = None
127
129
 
128
130
 
129
131
  class ConsentRequestCreate(FidesSchema):
@@ -287,6 +289,7 @@ class PrivacyRequestStatus(str, EnumType):
287
289
  complete = "complete"
288
290
  paused = "paused"
289
291
  awaiting_email_send = "awaiting_email_send"
292
+ requires_manual_finalization = "requires_manual_finalization"
290
293
  canceled = "canceled"
291
294
  error = "error"
292
295
 
@@ -320,6 +323,8 @@ class PrivacyRequestResponse(FidesSchema):
320
323
  source: Optional[PrivacyRequestSource] = None
321
324
  deleted_at: Optional[datetime] = None
322
325
  deleted_by: Optional[str] = None
326
+ finalized_at: Optional[datetime] = None
327
+ finalized_by: Optional[str] = None
323
328
  model_config = ConfigDict(from_attributes=True, use_enum_values=True)
324
329
 
325
330
 
@@ -480,12 +480,21 @@ def run_privacy_request(
480
480
  connection_configs
481
481
  )
482
482
 
483
+ # If the privacy request requires manual finalization and has not yet been finalized, we exit here
484
+ if (
485
+ privacy_request.status
486
+ == PrivacyRequestStatus.requires_manual_finalization
487
+ and privacy_request.finalized_at is None
488
+ ):
489
+ return
490
+
483
491
  # Access CHECKPOINT
484
492
  if (
485
493
  policy.get_rules_for_action(action_type=ActionType.access)
486
494
  or policy.get_rules_for_action(action_type=ActionType.erasure)
487
495
  ) and can_run_checkpoint(
488
- request_checkpoint=CurrentStep.access, from_checkpoint=resume_step
496
+ request_checkpoint=CurrentStep.access,
497
+ from_checkpoint=resume_step,
489
498
  ):
490
499
  privacy_request.cache_failed_checkpoint_details(CurrentStep.access)
491
500
  access_runner(
@@ -530,7 +539,8 @@ def run_privacy_request(
530
539
  if policy.get_rules_for_action(
531
540
  action_type=ActionType.erasure
532
541
  ) and can_run_checkpoint(
533
- request_checkpoint=CurrentStep.erasure, from_checkpoint=resume_step
542
+ request_checkpoint=CurrentStep.erasure,
543
+ from_checkpoint=resume_step,
534
544
  ):
535
545
  privacy_request.cache_failed_checkpoint_details(CurrentStep.erasure)
536
546
  _verify_masking_secrets(policy, privacy_request, resume_step)
@@ -656,58 +666,111 @@ def run_privacy_request(
656
666
  if not proceed:
657
667
  return
658
668
 
659
- legacy_request_completion_enabled = ConfigProxy(
660
- session
661
- ).notifications.send_request_completion_notification
662
-
663
- action_type = (
664
- MessagingActionType.PRIVACY_REQUEST_COMPLETE_ACCESS
665
- if policy.get_rules_for_action(action_type=ActionType.access)
666
- else MessagingActionType.PRIVACY_REQUEST_COMPLETE_DELETION
667
- )
668
-
669
- if message_send_enabled(
670
- session,
671
- privacy_request.property_id,
672
- action_type,
673
- legacy_request_completion_enabled,
674
- ) and not policy.get_rules_for_action(action_type=ActionType.consent):
675
- try:
676
- if not access_result_urls:
677
- # For DSR 3.0, if the request had both access and erasure rules, this needs to be fetched
678
- # from the database because the Privacy Request would have exited
679
- # processing and lost access to the access_result_urls in memory
680
- access_result_urls = (
681
- privacy_request.access_result_urls or {}
682
- ).get("access_result_urls", [])
683
- initiate_privacy_request_completion_email(
684
- session,
685
- policy,
686
- access_result_urls,
687
- identity_data,
688
- privacy_request.property_id,
689
- )
690
- except (IdentityNotFoundException, MessageDispatchException) as e:
691
- privacy_request.error_processing(db=session)
692
- # If dev mode, log traceback
693
- _log_exception(e, CONFIG.dev_mode)
694
- return
695
-
696
- # Only mark as complete if not in error state
697
- if privacy_request.status != PrivacyRequestStatus.error:
698
- privacy_request.finished_processing_at = datetime.utcnow()
699
- AuditLog.create(
700
- db=session,
701
- data={
702
- "user_id": "system",
703
- "privacy_request_id": privacy_request.id,
704
- "action": AuditLogAction.finished,
705
- "message": "",
706
- },
669
+ # Request finalization CHECKPOINT
670
+ if can_run_checkpoint(
671
+ request_checkpoint=CurrentStep.finalization,
672
+ from_checkpoint=resume_step,
673
+ ):
674
+ privacy_request.cache_failed_checkpoint_details(
675
+ CurrentStep.finalization,
707
676
  )
708
- privacy_request.status = PrivacyRequestStatus.complete
709
- logger.info("Privacy request run completed.")
710
- privacy_request.save(db=session)
677
+ if privacy_request.status != PrivacyRequestStatus.error:
678
+ erasure_rules = policy.get_rules_for_action(
679
+ action_type=ActionType.erasure
680
+ )
681
+ if (
682
+ privacy_request.finalized_at is None
683
+ and erasure_rules
684
+ and CONFIG.execution.erasure_request_finalization_required
685
+ ):
686
+ logger.info(
687
+ "Marking privacy request '{}' as requires manual finalization.",
688
+ privacy_request.id,
689
+ )
690
+ privacy_request.status = (
691
+ PrivacyRequestStatus.requires_manual_finalization
692
+ )
693
+ privacy_request.save(db=session)
694
+ return
695
+
696
+ # Finally, mark the request as complete
697
+ if privacy_request.finalized_at:
698
+ logger.info(
699
+ "Marking privacy request '{}' as finalized.",
700
+ privacy_request.id,
701
+ )
702
+ privacy_request.add_success_execution_log(
703
+ session,
704
+ connection_key=None,
705
+ dataset_name="Request finalized",
706
+ collection_name=None,
707
+ message=f"Request finalized for privacy request: {privacy_request.id}",
708
+ action_type=privacy_request.policy.get_action_type(), # type: ignore
709
+ )
710
+
711
+ logger.info(
712
+ "Marking privacy request '{}' as complete.",
713
+ privacy_request.id,
714
+ )
715
+ AuditLog.create(
716
+ db=session,
717
+ data={
718
+ "user_id": "system",
719
+ "privacy_request_id": privacy_request.id,
720
+ "action": AuditLogAction.finished,
721
+ "message": "",
722
+ },
723
+ )
724
+ privacy_request.status = PrivacyRequestStatus.complete
725
+ privacy_request.finished_processing_at = datetime.utcnow()
726
+ privacy_request.save(db=session)
727
+
728
+ # Send a final email to the user confirming request completion
729
+ if privacy_request.status == PrivacyRequestStatus.complete:
730
+ legacy_request_completion_enabled = ConfigProxy(
731
+ session
732
+ ).notifications.send_request_completion_notification
733
+
734
+ action_type = (
735
+ MessagingActionType.PRIVACY_REQUEST_COMPLETE_ACCESS
736
+ if policy.get_rules_for_action(
737
+ action_type=ActionType.access
738
+ )
739
+ else MessagingActionType.PRIVACY_REQUEST_COMPLETE_DELETION
740
+ )
741
+
742
+ if message_send_enabled(
743
+ session,
744
+ privacy_request.property_id,
745
+ action_type,
746
+ legacy_request_completion_enabled,
747
+ ) and not policy.get_rules_for_action(
748
+ action_type=ActionType.consent
749
+ ):
750
+ if not access_result_urls:
751
+ # For DSR 3.0, if the request had both access and erasure rules, this needs to be fetched
752
+ # from the database because the Privacy Request would have exited
753
+ # processing and lost access to the access_result_urls in memory
754
+ access_result_urls = (
755
+ privacy_request.access_result_urls or {}
756
+ ).get("access_result_urls", [])
757
+
758
+ try:
759
+ initiate_privacy_request_completion_email(
760
+ session,
761
+ policy,
762
+ access_result_urls,
763
+ identity_data,
764
+ privacy_request.property_id,
765
+ )
766
+ except (
767
+ IdentityNotFoundException,
768
+ MessageDispatchException,
769
+ ) as e:
770
+ privacy_request.error_processing(db=session)
771
+ # If dev mode, log traceback
772
+ _log_exception(e, CONFIG.dev_mode)
773
+ return
711
774
 
712
775
 
713
776
  def initiate_privacy_request_completion_email(
@@ -33,8 +33,8 @@ from fides.api.models.worker_task import ExecutionLogStatus
33
33
  from fides.api.schemas.policy import ActionType
34
34
  from fides.api.task.deprecated_graph_task import format_data_use_map_for_caching
35
35
  from fides.api.task.execute_request_tasks import log_task_queued, queue_request_task
36
+ from fides.api.task.manual.manual_task_address import ManualTaskAddress
36
37
  from fides.api.task.manual.manual_task_utils import (
37
- ManualTaskAddress,
38
38
  create_manual_task_instances_for_privacy_request,
39
39
  )
40
40
  from fides.api.util.logger_context_utils import log_context
@@ -29,8 +29,8 @@ from fides.api.task.graph_task import (
29
29
  GraphTask,
30
30
  mark_current_and_downstream_nodes_as_failed,
31
31
  )
32
+ from fides.api.task.manual.manual_task_address import ManualTaskAddress
32
33
  from fides.api.task.manual.manual_task_graph_task import ManualTaskGraphTask
33
- from fides.api.task.manual.manual_task_utils import ManualTaskAddress
34
34
  from fides.api.task.task_resources import TaskResources
35
35
  from fides.api.tasks import DSR_QUEUE_NAME, DatabaseTask, celery_app
36
36
  from fides.api.util.cache import cache_task_tracking_key
@@ -6,7 +6,7 @@ from loguru import logger
6
6
 
7
7
  from fides.api.graph.config import CollectionAddress, FieldPath
8
8
  from fides.api.graph.graph import DatasetGraph
9
- from fides.api.task.manual.manual_task_utils import ManualTaskAddress
9
+ from fides.api.task.manual.manual_task_address import ManualTaskAddress
10
10
  from fides.api.util.collection_util import Row
11
11
 
12
12
 
@@ -0,0 +1,46 @@
1
+ from fides.api.graph.config import CollectionAddress
2
+
3
+
4
+ class ManualTaskAddress:
5
+ """Utility class for creating and parsing manual task addresses"""
6
+
7
+ MANUAL_DATA_COLLECTION = "manual_data"
8
+
9
+ @staticmethod
10
+ def create(connection_config_key: str) -> CollectionAddress:
11
+ """Create a CollectionAddress for manual data: {connection_key}:manual_data"""
12
+ return CollectionAddress(
13
+ dataset=connection_config_key,
14
+ collection=ManualTaskAddress.MANUAL_DATA_COLLECTION,
15
+ )
16
+
17
+ @staticmethod
18
+ def _is_manual_data_collection(collection_name: str) -> bool:
19
+ """Check if collection name represents manual task data"""
20
+ return collection_name == ManualTaskAddress.MANUAL_DATA_COLLECTION
21
+
22
+ @staticmethod
23
+ def is_manual_task_address(address: CollectionAddress) -> bool:
24
+ """Check if address represents manual task data"""
25
+ if isinstance(address, str):
26
+ # Handle string format "connection_key:collection_name"
27
+ _, _, collection_part = address.partition(":")
28
+ if not collection_part:
29
+ return False
30
+ return ManualTaskAddress._is_manual_data_collection(collection_part)
31
+
32
+ # Handle CollectionAddress object
33
+ return ManualTaskAddress._is_manual_data_collection(address.collection)
34
+
35
+ @staticmethod
36
+ def get_connection_key(address: CollectionAddress) -> str:
37
+ """Extract connection config key from manual task address"""
38
+ if not ManualTaskAddress.is_manual_task_address(address):
39
+ raise ValueError(f"Not a manual task address: {address}")
40
+
41
+ if isinstance(address, str):
42
+ # Handle string format "connection_key:collection_name"
43
+ return address.split(":")[0]
44
+
45
+ # Handle CollectionAddress object
46
+ return address.dataset