ethyca-fides 2.63.1b4__py2.py3-none-any.whl → 2.63.2__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.
Files changed (117) hide show
  1. {ethyca_fides-2.63.1b4.dist-info → ethyca_fides-2.63.2.dist-info}/METADATA +1 -1
  2. {ethyca_fides-2.63.1b4.dist-info → ethyca_fides-2.63.2.dist-info}/RECORD +110 -113
  3. fides/_version.py +3 -3
  4. fides/api/models/attachment.py +23 -36
  5. fides/api/models/detection_discovery/monitor_task.py +1 -0
  6. fides/api/service/privacy_request/dsr_package/dsr_report_builder.py +46 -264
  7. fides/api/service/privacy_request/dsr_package/templates/collection_index.html +9 -34
  8. fides/api/service/privacy_request/dsr_package/templates/item.html +37 -0
  9. fides/api/service/privacy_request/dsr_package/templates/main.css +2 -45
  10. fides/api/service/privacy_request/dsr_package/templates/welcome.html +8 -12
  11. fides/api/service/privacy_request/request_runner_service.py +139 -258
  12. fides/api/service/storage/gcs.py +3 -15
  13. fides/api/service/storage/s3.py +14 -28
  14. fides/api/service/storage/util.py +7 -45
  15. fides/api/tasks/storage.py +91 -85
  16. fides/ui-build/static/admin/404.html +1 -1
  17. fides/ui-build/static/admin/_next/static/{X2nvWLg2_-vsCTkhSWpzw → IrQqz_6ngcumU4YlWY9nL}/_buildManifest.js +1 -1
  18. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/messaging-8cab04871908cfeb.js +1 -0
  19. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/storage-150d40428245ee0c.js +1 -0
  20. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests-20cdb2c8a03deae1.js +1 -0
  21. fides/ui-build/static/admin/add-systems/manual.html +1 -1
  22. fides/ui-build/static/admin/add-systems/multiple.html +1 -1
  23. fides/ui-build/static/admin/add-systems.html +1 -1
  24. fides/ui-build/static/admin/consent/configure/add-vendors.html +1 -1
  25. fides/ui-build/static/admin/consent/configure.html +1 -1
  26. fides/ui-build/static/admin/consent/privacy-experience/[id].html +1 -1
  27. fides/ui-build/static/admin/consent/privacy-experience/new.html +1 -1
  28. fides/ui-build/static/admin/consent/privacy-experience.html +1 -1
  29. fides/ui-build/static/admin/consent/privacy-notices/[id].html +1 -1
  30. fides/ui-build/static/admin/consent/privacy-notices/new.html +1 -1
  31. fides/ui-build/static/admin/consent/privacy-notices.html +1 -1
  32. fides/ui-build/static/admin/consent/properties.html +1 -1
  33. fides/ui-build/static/admin/consent/reporting.html +1 -1
  34. fides/ui-build/static/admin/consent.html +1 -1
  35. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn]/[resourceUrn].html +1 -1
  36. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn].html +1 -1
  37. fides/ui-build/static/admin/data-catalog/[systemId]/projects.html +1 -1
  38. fides/ui-build/static/admin/data-catalog/[systemId]/resources/[resourceUrn].html +1 -1
  39. fides/ui-build/static/admin/data-catalog/[systemId]/resources.html +1 -1
  40. fides/ui-build/static/admin/data-catalog.html +1 -1
  41. fides/ui-build/static/admin/data-discovery/action-center/[monitorId]/[systemId].html +1 -1
  42. fides/ui-build/static/admin/data-discovery/action-center/[monitorId].html +1 -1
  43. fides/ui-build/static/admin/data-discovery/action-center.html +1 -1
  44. fides/ui-build/static/admin/data-discovery/activity.html +1 -1
  45. fides/ui-build/static/admin/data-discovery/detection/[resourceUrn].html +1 -1
  46. fides/ui-build/static/admin/data-discovery/detection.html +1 -1
  47. fides/ui-build/static/admin/data-discovery/discovery/[resourceUrn].html +1 -1
  48. fides/ui-build/static/admin/data-discovery/discovery.html +1 -1
  49. fides/ui-build/static/admin/datamap.html +1 -1
  50. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName]/[...subfieldNames].html +1 -1
  51. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName].html +1 -1
  52. fides/ui-build/static/admin/dataset/[datasetId].html +1 -1
  53. fides/ui-build/static/admin/dataset/new.html +1 -1
  54. fides/ui-build/static/admin/dataset.html +1 -1
  55. fides/ui-build/static/admin/datastore-connection/[id].html +1 -1
  56. fides/ui-build/static/admin/datastore-connection/new.html +1 -1
  57. fides/ui-build/static/admin/datastore-connection.html +1 -1
  58. fides/ui-build/static/admin/index.html +1 -1
  59. fides/ui-build/static/admin/integrations/[id].html +1 -1
  60. fides/ui-build/static/admin/integrations.html +1 -1
  61. fides/ui-build/static/admin/lib/fides-ext-gpp.js +1 -1
  62. fides/ui-build/static/admin/lib/fides-headless.js +1 -1
  63. fides/ui-build/static/admin/lib/fides-preview.js +1 -1
  64. fides/ui-build/static/admin/lib/fides-tcf.js +2 -2
  65. fides/ui-build/static/admin/lib/fides.js +2 -2
  66. fides/ui-build/static/admin/login/[provider].html +1 -1
  67. fides/ui-build/static/admin/login.html +1 -1
  68. fides/ui-build/static/admin/messaging/[id].html +1 -1
  69. fides/ui-build/static/admin/messaging/add-template.html +1 -1
  70. fides/ui-build/static/admin/messaging.html +1 -1
  71. fides/ui-build/static/admin/poc/ant-components.html +1 -1
  72. fides/ui-build/static/admin/poc/form-experiments/AntForm.html +1 -1
  73. fides/ui-build/static/admin/poc/form-experiments/FormikAntFormItem.html +1 -1
  74. fides/ui-build/static/admin/poc/form-experiments/FormikControlled.html +1 -1
  75. fides/ui-build/static/admin/poc/form-experiments/FormikField.html +1 -1
  76. fides/ui-build/static/admin/poc/form-experiments/FormikSpreadField.html +1 -1
  77. fides/ui-build/static/admin/poc/forms.html +1 -1
  78. fides/ui-build/static/admin/poc/table-migration.html +1 -1
  79. fides/ui-build/static/admin/privacy-requests/[id].html +1 -1
  80. fides/ui-build/static/admin/privacy-requests/configure/messaging.html +1 -1
  81. fides/ui-build/static/admin/privacy-requests/configure/storage.html +1 -1
  82. fides/ui-build/static/admin/privacy-requests/configure.html +1 -1
  83. fides/ui-build/static/admin/privacy-requests.html +1 -1
  84. fides/ui-build/static/admin/properties/[id].html +1 -1
  85. fides/ui-build/static/admin/properties/add-property.html +1 -1
  86. fides/ui-build/static/admin/properties.html +1 -1
  87. fides/ui-build/static/admin/reporting/datamap.html +1 -1
  88. fides/ui-build/static/admin/settings/about/alpha.html +1 -1
  89. fides/ui-build/static/admin/settings/about.html +1 -1
  90. fides/ui-build/static/admin/settings/consent/[configuration_id]/[purpose_id].html +1 -1
  91. fides/ui-build/static/admin/settings/consent.html +1 -1
  92. fides/ui-build/static/admin/settings/custom-fields.html +1 -1
  93. fides/ui-build/static/admin/settings/domain-records.html +1 -1
  94. fides/ui-build/static/admin/settings/domains.html +1 -1
  95. fides/ui-build/static/admin/settings/email-templates.html +1 -1
  96. fides/ui-build/static/admin/settings/locations.html +1 -1
  97. fides/ui-build/static/admin/settings/organization.html +1 -1
  98. fides/ui-build/static/admin/settings/regulations.html +1 -1
  99. fides/ui-build/static/admin/systems/configure/[id]/test-datasets.html +1 -1
  100. fides/ui-build/static/admin/systems/configure/[id].html +1 -1
  101. fides/ui-build/static/admin/systems.html +1 -1
  102. fides/ui-build/static/admin/taxonomy.html +1 -1
  103. fides/ui-build/static/admin/user-management/new.html +1 -1
  104. fides/ui-build/static/admin/user-management/profile/[id].html +1 -1
  105. fides/ui-build/static/admin/user-management.html +1 -1
  106. fides/api/service/privacy_request/attachment_handling.py +0 -132
  107. fides/api/service/privacy_request/dsr_package/templates/attachments_index.html +0 -33
  108. fides/api/tasks/csv_utils.py +0 -170
  109. fides/api/tasks/encryption_utils.py +0 -42
  110. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/messaging-c583a61302f02add.js +0 -1
  111. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/storage-20d20a8d1736f7c4.js +0 -1
  112. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests-0e557d79e1e43c2b.js +0 -1
  113. {ethyca_fides-2.63.1b4.dist-info → ethyca_fides-2.63.2.dist-info}/WHEEL +0 -0
  114. {ethyca_fides-2.63.1b4.dist-info → ethyca_fides-2.63.2.dist-info}/entry_points.txt +0 -0
  115. {ethyca_fides-2.63.1b4.dist-info → ethyca_fides-2.63.2.dist-info}/licenses/LICENSE +0 -0
  116. {ethyca_fides-2.63.1b4.dist-info → ethyca_fides-2.63.2.dist-info}/top_level.txt +0 -0
  117. /fides/ui-build/static/admin/_next/static/{X2nvWLg2_-vsCTkhSWpzw → IrQqz_6ngcumU4YlWY9nL}/_ssgManifest.js +0 -0
@@ -1,7 +1,6 @@
1
1
  import time
2
- from copy import deepcopy
3
2
  from datetime import datetime, timedelta
4
- from typing import Any, Optional, Tuple
3
+ from typing import Any, Dict, List, Optional, Set, Tuple
5
4
 
6
5
  import requests
7
6
  from loguru import logger
@@ -22,7 +21,6 @@ from fides.api.common_exceptions import (
22
21
  from fides.api.db.session import get_db_session
23
22
  from fides.api.graph.config import CollectionAddress
24
23
  from fides.api.graph.graph import DatasetGraph
25
- from fides.api.models.attachment import Attachment, AttachmentReferenceType
26
24
  from fides.api.models.audit_log import AuditLog, AuditLogAction
27
25
  from fides.api.models.connectionconfig import AccessLevel, ConnectionConfig
28
26
  from fides.api.models.datasetconfig import DatasetConfig
@@ -31,7 +29,6 @@ from fides.api.models.policy import (
31
29
  Policy,
32
30
  PolicyPostWebhook,
33
31
  PolicyPreWebhook,
34
- Rule,
35
32
  WebhookTypes,
36
33
  )
37
34
  from fides.api.models.privacy_request import (
@@ -59,10 +56,6 @@ from fides.api.service.messaging.message_dispatch_service import (
59
56
  dispatch_message,
60
57
  message_send_enabled,
61
58
  )
62
- from fides.api.service.privacy_request.attachment_handling import (
63
- get_attachments_content,
64
- process_attachments_for_upload,
65
- )
66
59
  from fides.api.service.storage.storage_uploader_service import upload
67
60
  from fides.api.task.filter_results import filter_data_categories
68
61
  from fides.api.task.graph_runners import access_runner, consent_runner, erasure_runner
@@ -87,8 +80,7 @@ from fides.config.config_proxy import ConfigProxy
87
80
  class ManualWebhookResults(FidesSchema):
88
81
  """Represents manual webhook data retrieved from the cache and whether privacy request execution should continue"""
89
82
 
90
- manual_data_for_upload: dict[str, list[dict[str, Optional[Any]]]]
91
- manual_data_for_storage: dict[str, list[dict[str, Optional[Any]]]]
83
+ manual_data: Dict[str, List[Dict[str, Optional[Any]]]]
92
84
  proceed: bool
93
85
 
94
86
 
@@ -100,52 +92,17 @@ def get_manual_webhook_access_inputs(
100
92
 
101
93
  This data will be uploaded to the user as-is, without filtering.
102
94
  """
103
- manual_inputs_for_upload: dict[str, list[dict[str, Optional[Any]]]] = {}
104
- manual_inputs_for_storage: dict[str, list[dict[str, Optional[Any]]]] = {}
95
+ manual_inputs: Dict[str, List[Dict[str, Optional[Any]]]] = {}
105
96
 
106
97
  if not policy.get_rules_for_action(action_type=ActionType.access):
107
98
  # Don't fetch manual inputs unless this policy has an access rule
108
- return ManualWebhookResults(
109
- manual_data_for_upload=manual_inputs_for_upload,
110
- manual_data_for_storage=manual_inputs_for_storage,
111
- proceed=True,
112
- )
99
+ return ManualWebhookResults(manual_data=manual_inputs, proceed=True)
113
100
 
114
101
  try:
115
102
  for manual_webhook in AccessManualWebhook.get_enabled(db, ActionType.access):
116
- # Get the manual webhook input data
117
- webhook_data_for_storage = (
103
+ manual_inputs[manual_webhook.connection_config.key] = [
118
104
  privacy_request.get_manual_webhook_access_input_strict(manual_webhook)
119
- )
120
- # Add the system name to the webhook data for display purposes
121
- webhook_data_for_storage["system_name"] = (
122
- manual_webhook.connection_config.system.name
123
- )
124
- webhook_data_for_upload = deepcopy(webhook_data_for_storage)
125
-
126
- # Get any attachments for this webhook, load from db to ensure they have their configs
127
- webhook_attachments = privacy_request.get_access_manual_webhook_attachments(
128
- db, manual_webhook.id
129
- )
130
- if webhook_attachments:
131
- attachment_ids = [wa.id for wa in webhook_attachments]
132
- loaded_attachments = (
133
- db.query(Attachment).filter(Attachment.id.in_(attachment_ids)).all()
134
- )
135
- (
136
- webhook_data_for_upload["attachments"],
137
- webhook_data_for_storage["attachments"],
138
- ) = process_attachments_for_upload(
139
- get_attachments_content(loaded_attachments)
140
- )
141
-
142
- manual_inputs_for_upload[manual_webhook.connection_config.key] = [
143
- webhook_data_for_upload
144
- ]
145
- manual_inputs_for_storage[manual_webhook.connection_config.key] = [ # type: ignore[assignment]
146
- webhook_data_for_storage
147
105
  ]
148
-
149
106
  except (
150
107
  NoCachedManualWebhookEntry,
151
108
  ValidationError,
@@ -154,31 +111,19 @@ def get_manual_webhook_access_inputs(
154
111
  logger.info(exc)
155
112
  privacy_request.status = PrivacyRequestStatus.requires_input
156
113
  privacy_request.save(db)
157
- return ManualWebhookResults(
158
- manual_data_for_upload=manual_inputs_for_upload,
159
- manual_data_for_storage=manual_inputs_for_storage,
160
- proceed=False,
161
- )
114
+ return ManualWebhookResults(manual_data=manual_inputs, proceed=False)
162
115
 
163
- return ManualWebhookResults(
164
- manual_data_for_upload=manual_inputs_for_upload,
165
- manual_data_for_storage=manual_inputs_for_storage,
166
- proceed=True,
167
- )
116
+ return ManualWebhookResults(manual_data=manual_inputs, proceed=True)
168
117
 
169
118
 
170
119
  def get_manual_webhook_erasure_inputs(
171
120
  db: Session, privacy_request: PrivacyRequest, policy: Policy
172
121
  ) -> ManualWebhookResults:
173
- manual_inputs: dict[str, list[dict[str, Optional[Any]]]] = {}
122
+ manual_inputs: Dict[str, List[Dict[str, Optional[Any]]]] = {}
174
123
 
175
124
  if not policy.get_rules_for_action(action_type=ActionType.erasure):
176
125
  # Don't fetch manual inputs unless this policy has an access rule
177
- return ManualWebhookResults(
178
- manual_data_for_upload=manual_inputs,
179
- manual_data_for_storage=manual_inputs,
180
- proceed=True,
181
- )
126
+ return ManualWebhookResults(manual_data=manual_inputs, proceed=True)
182
127
  try:
183
128
  for manual_webhook in AccessManualWebhook().get_enabled(db, ActionType.erasure):
184
129
  manual_inputs[manual_webhook.connection_config.key] = [
@@ -192,93 +137,69 @@ def get_manual_webhook_erasure_inputs(
192
137
  logger.info(exc)
193
138
  privacy_request.status = PrivacyRequestStatus.requires_input
194
139
  privacy_request.save(db)
195
- return ManualWebhookResults(
196
- manual_data_for_upload=manual_inputs,
197
- manual_data_for_storage=manual_inputs,
198
- proceed=False,
199
- )
140
+ return ManualWebhookResults(manual_data=manual_inputs, proceed=False)
200
141
 
201
- return ManualWebhookResults(
202
- manual_data_for_upload=manual_inputs,
203
- manual_data_for_storage=manual_inputs,
204
- proceed=True,
205
- )
142
+ return ManualWebhookResults(manual_data=manual_inputs, proceed=True)
206
143
 
207
144
 
208
- @log_context(capture_args={"privacy_request_id": LoggerContextKeys.privacy_request_id})
209
- def upload_access_results(
210
- session: Session,
211
- policy: Policy,
212
- rule: Rule,
213
- results_to_upload: dict[str, list[dict[str, Optional[Any]]]],
214
- dataset_graph: DatasetGraph,
145
+ def run_webhooks_and_report_status(
146
+ db: Session,
215
147
  privacy_request: PrivacyRequest,
216
- upload_attachments: list[dict[str, Any]],
217
- ) -> list[str]:
218
- """Upload results for a single rule and return download URLs and modified results."""
219
- start_time = time.time()
220
- download_urls: list[str] = []
221
- storage_destination = rule.get_storage_destination(session)
222
-
223
- if upload_attachments:
224
- results_to_upload["attachments"] = upload_attachments
148
+ webhook_cls: WebhookTypes,
149
+ after_webhook_id: Optional[str] = None,
150
+ ) -> bool:
151
+ """
152
+ Runs a series of webhooks either pre- or post- privacy request execution, if any are configured.
153
+ Updates privacy request status if execution is paused/errored.
154
+ Returns True if execution should proceed.
155
+ """
156
+ webhooks = db.query(webhook_cls).filter_by(policy_id=privacy_request.policy.id) # type: ignore
225
157
 
226
- logger.info("Starting access request upload for rule {}", rule.key)
227
- try:
228
- download_url: Optional[str] = upload(
229
- db=session,
230
- privacy_request=privacy_request,
231
- data=results_to_upload,
232
- storage_key=storage_destination.key, # type: ignore
233
- data_category_field_mapping=dataset_graph.data_category_field_mapping,
234
- data_use_map=privacy_request.get_cached_data_use_map(),
235
- )
236
- if download_url:
237
- download_urls.append(download_url)
238
- logger.bind(
239
- time_taken=time.time() - start_time,
240
- ).info("Access package upload successful for privacy request.")
241
- privacy_request.add_success_execution_log(
242
- session,
243
- connection_key=None,
244
- dataset_name="Access package upload",
245
- collection_name=None,
246
- message="Access package upload successful for privacy request.",
247
- action_type=ActionType.access,
248
- )
249
- except common_exceptions.StorageUploadError as exc:
250
- logger.error(
251
- "Error uploading subject access data for rule {} on policy {}: {}",
252
- rule.key,
253
- policy.key,
254
- Pii(str(exc)),
255
- )
256
- privacy_request.add_error_execution_log(
257
- session,
258
- connection_key=None,
259
- dataset_name="Access package upload",
260
- collection_name=None,
261
- message="Access package upload failed for privacy request.",
262
- action_type=ActionType.access,
158
+ if after_webhook_id:
159
+ # Only run webhooks configured to run after this Pre-Execution webhook
160
+ pre_webhook = PolicyPreWebhook.get(db=db, object_id=after_webhook_id)
161
+ webhooks = webhooks.filter( # type: ignore[call-arg]
162
+ webhook_cls.order > pre_webhook.order, # type: ignore[union-attr]
263
163
  )
264
- privacy_request.status = PrivacyRequestStatus.error
265
164
 
266
- return download_urls
165
+ current_step = CurrentStep[f"{webhook_cls.prefix}_webhooks"]
267
166
 
167
+ for webhook in webhooks.order_by(webhook_cls.order): # type: ignore[union-attr]
168
+ try:
169
+ privacy_request.trigger_policy_webhook(
170
+ webhook=webhook,
171
+ policy_action=privacy_request.policy.get_action_type(),
172
+ )
173
+ except PrivacyRequestPaused:
174
+ logger.info(
175
+ "Pausing execution of privacy request {}. Halt instruction received from webhook {}.",
176
+ privacy_request.id,
177
+ webhook.key,
178
+ )
179
+ privacy_request.pause_processing(db)
180
+ initiate_paused_privacy_request_followup(privacy_request)
181
+ return False
182
+ except ClientUnsuccessfulException as exc:
183
+ logger.error(
184
+ "Privacy Request '{}' exited after response from webhook '{}': {}.",
185
+ privacy_request.id,
186
+ webhook.key,
187
+ Pii(str(exc.args[0])),
188
+ )
189
+ privacy_request.error_processing(db)
190
+ privacy_request.cache_failed_checkpoint_details(current_step)
191
+ return False
192
+ except PydanticValidationError:
193
+ logger.error(
194
+ "Privacy Request '{}' errored due to response validation error from webhook '{}'.",
195
+ privacy_request.id,
196
+ webhook.key,
197
+ )
198
+ privacy_request.error_processing(db)
199
+ privacy_request.cache_failed_checkpoint_details(current_step)
200
+ return False
268
201
 
269
- def save_access_results(
270
- session: Session,
271
- privacy_request: PrivacyRequest,
272
- download_urls: list[str],
273
- rule_filtered_results: dict[str, dict[str, list[dict[str, Optional[Any]]]]],
274
- ) -> None:
275
- """Save the results we uploaded to the user for later retrieval"""
276
- # Save the results we uploaded to the user for later retrieval
277
- privacy_request.save_filtered_access_results(session, rule_filtered_results)
278
- # Saving access request URL's on the privacy request in case DSR 3.0
279
- # exits processing before the email is sent
280
- privacy_request.access_result_urls = {"access_result_urls": download_urls}
281
- privacy_request.save(session)
202
+ return True
282
203
 
283
204
 
284
205
  @log_context(
@@ -286,40 +207,29 @@ def save_access_results(
286
207
  "privacy_request_id": LoggerContextKeys.privacy_request_id,
287
208
  }
288
209
  )
289
- def upload_and_save_access_results( # pylint: disable=R0912
210
+ def upload_access_results( # pylint: disable=R0912
290
211
  session: Session,
291
212
  policy: Policy,
292
- access_result: dict[str, list[Row]],
213
+ access_result: Dict[str, List[Row]],
293
214
  dataset_graph: DatasetGraph,
294
215
  privacy_request: PrivacyRequest,
295
- manual_data_access_results: ManualWebhookResults,
296
- fides_connector_datasets: set[str],
297
- ) -> list[str]:
216
+ manual_data: Dict[str, List[Dict[str, Optional[Any]]]],
217
+ fides_connector_datasets: Set[str],
218
+ ) -> List[str]:
298
219
  """Process the data uploads after the access portion of the privacy request has completed"""
299
- download_urls: list[str] = []
300
- # Remove manual webhook attachments from the list of attachments
301
- # This is done because the manual webhook attachments are already included in the manual_data
302
- loaded_attachments = [
303
- attachment
304
- for attachment in privacy_request.attachments
305
- if AttachmentReferenceType.access_manual_webhook
306
- not in [ref.reference_type for ref in attachment.references]
307
- ]
308
- attachments = get_attachments_content(loaded_attachments)
309
- # Process attachments once for both upload and storage
310
- upload_attachments, storage_attachments = process_attachments_for_upload(
311
- attachments
312
- )
313
-
220
+ start_time = time.time()
221
+ download_urls: List[str] = []
314
222
  if not access_result:
315
223
  logger.info("No results returned for access request")
316
224
 
317
- rule_filtered_results: dict[str, dict[str, list[dict[str, Optional[Any]]]]] = {}
225
+ rule_filtered_results: Dict[str, Dict[str, List[Row]]] = {}
318
226
  for rule in policy.get_rules_for_action( # pylint: disable=R1702
319
227
  action_type=ActionType.access
320
228
  ):
321
- target_categories: set[str] = {target.data_category for target in rule.targets} # type: ignore[attr-defined]
322
- filtered_results: dict[str, list[dict[str, Optional[Any]]]] = (
229
+ storage_destination = rule.get_storage_destination(session)
230
+
231
+ target_categories: Set[str] = {target.data_category for target in rule.targets} # type: ignore[attr-defined]
232
+ filtered_results: Dict[str, List[Dict[str, Optional[Any]]]] = (
323
233
  filter_data_categories(
324
234
  access_result,
325
235
  target_categories,
@@ -328,28 +238,59 @@ def upload_and_save_access_results( # pylint: disable=R0912
328
238
  fides_connector_datasets,
329
239
  )
330
240
  )
331
- # Create a copy of filtered results to modify for upload
332
- results_to_upload = deepcopy(filtered_results)
333
- results_to_upload.update(manual_data_access_results.manual_data_for_upload)
334
-
335
- rule_download_urls = upload_access_results(
336
- session,
337
- policy,
338
- rule,
339
- results_to_upload,
340
- dataset_graph,
341
- privacy_request,
342
- upload_attachments,
343
- )
344
- download_urls.extend(rule_download_urls)
345
241
 
346
- # Create results for storage
347
- filtered_results.update(manual_data_access_results.manual_data_for_storage)
348
- if storage_attachments:
349
- filtered_results["attachments"] = storage_attachments
242
+ filtered_results.update(
243
+ manual_data
244
+ ) # Add manual data directly to each upload packet
350
245
  rule_filtered_results[rule.key] = filtered_results
351
246
 
352
- save_access_results(session, privacy_request, download_urls, rule_filtered_results)
247
+ logger.info(
248
+ "Starting access request upload for rule {}",
249
+ rule.key,
250
+ )
251
+ try:
252
+ download_url: Optional[str] = upload(
253
+ db=session,
254
+ privacy_request=privacy_request,
255
+ data=filtered_results,
256
+ storage_key=storage_destination.key, # type: ignore
257
+ data_category_field_mapping=dataset_graph.data_category_field_mapping,
258
+ data_use_map=privacy_request.get_cached_data_use_map(),
259
+ )
260
+ if download_url:
261
+ download_urls.append(download_url)
262
+ privacy_request.add_success_execution_log(
263
+ session,
264
+ connection_key=None,
265
+ dataset_name="Access package upload",
266
+ collection_name=None,
267
+ message="Access package upload successful for privacy request.",
268
+ action_type=ActionType.access,
269
+ )
270
+ logger.bind(
271
+ time_taken=time.time() - start_time,
272
+ ).info("Access package upload successful for privacy request.")
273
+ except common_exceptions.StorageUploadError as exc:
274
+ logger.bind(
275
+ policy_key=policy.key,
276
+ rule_key=rule.key,
277
+ error=Pii(str(exc)),
278
+ ).error("Error uploading subject access data for rule.")
279
+ privacy_request.add_error_execution_log(
280
+ session,
281
+ connection_key=None,
282
+ dataset_name="Access package upload",
283
+ collection_name=None,
284
+ message="Access package upload failed for privacy request.",
285
+ action_type=ActionType.access,
286
+ )
287
+ privacy_request.status = PrivacyRequestStatus.error
288
+ # Save the results we uploaded to the user for later retrieval
289
+ privacy_request.save_filtered_access_results(session, rule_filtered_results)
290
+ # Saving access request URL's on the privacy request in case DSR 3.0
291
+ # exits processing before the email is sent
292
+ privacy_request.access_result_urls = {"access_result_urls": download_urls}
293
+ privacy_request.save(session)
353
294
  return download_urls
354
295
 
355
296
 
@@ -467,7 +408,7 @@ def run_privacy_request(
467
408
  for key, value in privacy_request.get_cached_identity_data().items()
468
409
  }
469
410
  connection_configs = ConnectionConfig.all(db=session)
470
- fides_connector_datasets: set[str] = filter_fides_connector_datasets(
411
+ fides_connector_datasets: Set[str] = filter_fides_connector_datasets(
471
412
  connection_configs
472
413
  )
473
414
 
@@ -490,8 +431,8 @@ def run_privacy_request(
490
431
  )
491
432
 
492
433
  # Upload Access Results CHECKPOINT
493
- access_result_urls: list[str] = []
494
- raw_access_results: dict = privacy_request.get_raw_access_results()
434
+ access_result_urls: List[str] = []
435
+ raw_access_results: Dict = privacy_request.get_raw_access_results()
495
436
  if (
496
437
  policy.get_rules_for_action(action_type=ActionType.access)
497
438
  or policy.get_rules_for_action(
@@ -507,13 +448,13 @@ def run_privacy_request(
507
448
  filtered_access_results = filter_by_enabled_actions(
508
449
  raw_access_results, connection_configs
509
450
  )
510
- access_result_urls = upload_and_save_access_results(
451
+ access_result_urls = upload_access_results(
511
452
  session,
512
453
  policy,
513
454
  filtered_access_results,
514
455
  dataset_graph,
515
456
  privacy_request,
516
- manual_webhook_access_results,
457
+ manual_webhook_access_results.manual_data,
517
458
  fides_connector_datasets,
518
459
  )
519
460
 
@@ -702,8 +643,8 @@ def run_privacy_request(
702
643
  def initiate_privacy_request_completion_email(
703
644
  session: Session,
704
645
  policy: Policy,
705
- access_result_urls: list[str],
706
- identity_data: dict[str, Any],
646
+ access_result_urls: List[str],
647
+ identity_data: Dict[str, Any],
707
648
  property_id: Optional[str],
708
649
  ) -> None:
709
650
  """
@@ -786,8 +727,8 @@ def mark_paused_privacy_request_as_expired(privacy_request_id: str) -> None:
786
727
  def _retrieve_child_results( # pylint: disable=R0911
787
728
  fides_connector: Tuple[str, ConnectionConfig],
788
729
  rule_key: str,
789
- access_result: dict[str, list[Row]],
790
- ) -> Optional[list[dict[str, Optional[list[Row]]]]]:
730
+ access_result: Dict[str, List[Row]],
731
+ ) -> Optional[List[Dict[str, Optional[List[Row]]]]]:
791
732
  """Get child access request results to add to upload."""
792
733
  try:
793
734
  connector = FidesConnector(fides_connector[1])
@@ -874,7 +815,7 @@ def get_erasure_email_connection_configs(db: Session) -> Query:
874
815
 
875
816
 
876
817
  def needs_batch_email_send(
877
- db: Session, user_identities: dict[str, Any], privacy_request: PrivacyRequest
818
+ db: Session, user_identities: Dict[str, Any], privacy_request: PrivacyRequest
878
819
  ) -> bool:
879
820
  """
880
821
  Delegates the "needs email" check to each configured email or
@@ -884,8 +825,8 @@ def needs_batch_email_send(
884
825
  If we don't need to send any emails, add skipped logs for any
885
826
  relevant erasure and consent email connectors.
886
827
  """
887
- can_skip_erasure_email: list[ConnectionConfig] = []
888
- can_skip_consent_email: list[ConnectionConfig] = []
828
+ can_skip_erasure_email: List[ConnectionConfig] = []
829
+ can_skip_consent_email: List[ConnectionConfig] = []
889
830
 
890
831
  needs_email_send: bool = False
891
832
 
@@ -916,8 +857,8 @@ def needs_batch_email_send(
916
857
  def _create_execution_logs_for_skipped_email_send(
917
858
  db: Session,
918
859
  privacy_request: PrivacyRequest,
919
- can_skip_erasure_email: list[ConnectionConfig],
920
- can_skip_consent_email: list[ConnectionConfig],
860
+ can_skip_erasure_email: List[ConnectionConfig],
861
+ can_skip_consent_email: List[ConnectionConfig],
921
862
  ) -> None:
922
863
  """Create skipped execution logs for relevant connectors
923
864
  if this privacy request does not need an email send at all. For consent requests,
@@ -933,63 +874,3 @@ def _create_execution_logs_for_skipped_email_send(
933
874
  for connection_config in can_skip_consent_email:
934
875
  connector = get_connector(connection_config)
935
876
  connector.add_skipped_log(db, privacy_request) # type: ignore[attr-defined]
936
-
937
-
938
- def run_webhooks_and_report_status(
939
- db: Session,
940
- privacy_request: PrivacyRequest,
941
- webhook_cls: WebhookTypes,
942
- after_webhook_id: Optional[str] = None,
943
- ) -> bool:
944
- """
945
- Runs a series of webhooks either pre- or post- privacy request execution, if any are configured.
946
- Updates privacy request status if execution is paused/errored.
947
- Returns True if execution should proceed.
948
- """
949
- webhooks = db.query(webhook_cls).filter_by(policy_id=privacy_request.policy.id) # type: ignore
950
-
951
- if after_webhook_id:
952
- # Only run webhooks configured to run after this Pre-Execution webhook
953
- pre_webhook = PolicyPreWebhook.get(db=db, object_id=after_webhook_id)
954
- webhooks = webhooks.filter( # type: ignore[call-arg]
955
- webhook_cls.order > pre_webhook.order, # type: ignore[union-attr]
956
- )
957
-
958
- current_step = CurrentStep[f"{webhook_cls.prefix}_webhooks"]
959
-
960
- for webhook in webhooks.order_by(webhook_cls.order): # type: ignore[union-attr]
961
- try:
962
- privacy_request.trigger_policy_webhook(
963
- webhook=webhook,
964
- policy_action=privacy_request.policy.get_action_type(),
965
- )
966
- except PrivacyRequestPaused:
967
- logger.info(
968
- "Pausing execution of privacy request {}. Halt instruction received from webhook {}.",
969
- privacy_request.id,
970
- webhook.key,
971
- )
972
- privacy_request.pause_processing(db)
973
- initiate_paused_privacy_request_followup(privacy_request)
974
- return False
975
- except ClientUnsuccessfulException as exc:
976
- logger.error(
977
- "Privacy Request '{}' exited after response from webhook '{}': {}.",
978
- privacy_request.id,
979
- webhook.key,
980
- Pii(str(exc.args[0])),
981
- )
982
- privacy_request.error_processing(db)
983
- privacy_request.cache_failed_checkpoint_details(current_step)
984
- return False
985
- except PydanticValidationError:
986
- logger.error(
987
- "Privacy Request '{}' errored due to response validation error from webhook '{}'.",
988
- privacy_request.id,
989
- webhook.key,
990
- )
991
- privacy_request.error_processing(db)
992
- privacy_request.cache_failed_checkpoint_details(current_step)
993
- return False
994
-
995
- return True
@@ -1,6 +1,6 @@
1
- from typing import Optional
1
+ from typing import Dict, Optional
2
2
 
3
- from google.cloud.storage import Blob, Client # type: ignore
3
+ from google.cloud.storage import Client # type: ignore
4
4
  from google.oauth2 import service_account
5
5
  from loguru import logger
6
6
 
@@ -10,7 +10,7 @@ from fides.api.schemas.storage.storage import GCSAuthMethod
10
10
 
11
11
  def get_gcs_client(
12
12
  auth_method: str,
13
- storage_secrets: Optional[dict],
13
+ storage_secrets: Optional[Dict],
14
14
  ) -> Client:
15
15
  """
16
16
  Abstraction to retrieve a GCS client using secrets.
@@ -36,15 +36,3 @@ def get_gcs_client(
36
36
  )
37
37
 
38
38
  return storage_client
39
-
40
-
41
- def get_gcs_blob(
42
- auth_method: str, storage_secrets: Optional[dict], bucket_name: str, file_key: str
43
- ) -> Blob:
44
- try:
45
- storage_client = get_gcs_client(auth_method, storage_secrets)
46
- bucket = storage_client.bucket(bucket_name)
47
- return bucket.blob(file_key)
48
- except Exception as e:
49
- logger.error(f"Error getting GCS blob: {str(e)}")
50
- raise e