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.
- {ethyca_fides-2.63.1b4.dist-info → ethyca_fides-2.63.2.dist-info}/METADATA +1 -1
- {ethyca_fides-2.63.1b4.dist-info → ethyca_fides-2.63.2.dist-info}/RECORD +110 -113
- fides/_version.py +3 -3
- fides/api/models/attachment.py +23 -36
- fides/api/models/detection_discovery/monitor_task.py +1 -0
- fides/api/service/privacy_request/dsr_package/dsr_report_builder.py +46 -264
- fides/api/service/privacy_request/dsr_package/templates/collection_index.html +9 -34
- fides/api/service/privacy_request/dsr_package/templates/item.html +37 -0
- fides/api/service/privacy_request/dsr_package/templates/main.css +2 -45
- fides/api/service/privacy_request/dsr_package/templates/welcome.html +8 -12
- fides/api/service/privacy_request/request_runner_service.py +139 -258
- fides/api/service/storage/gcs.py +3 -15
- fides/api/service/storage/s3.py +14 -28
- fides/api/service/storage/util.py +7 -45
- fides/api/tasks/storage.py +91 -85
- fides/ui-build/static/admin/404.html +1 -1
- fides/ui-build/static/admin/_next/static/{X2nvWLg2_-vsCTkhSWpzw → IrQqz_6ngcumU4YlWY9nL}/_buildManifest.js +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/messaging-8cab04871908cfeb.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/storage-150d40428245ee0c.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests-20cdb2c8a03deae1.js +1 -0
- fides/ui-build/static/admin/add-systems/manual.html +1 -1
- fides/ui-build/static/admin/add-systems/multiple.html +1 -1
- fides/ui-build/static/admin/add-systems.html +1 -1
- fides/ui-build/static/admin/consent/configure/add-vendors.html +1 -1
- fides/ui-build/static/admin/consent/configure.html +1 -1
- fides/ui-build/static/admin/consent/privacy-experience/[id].html +1 -1
- fides/ui-build/static/admin/consent/privacy-experience/new.html +1 -1
- fides/ui-build/static/admin/consent/privacy-experience.html +1 -1
- fides/ui-build/static/admin/consent/privacy-notices/[id].html +1 -1
- fides/ui-build/static/admin/consent/privacy-notices/new.html +1 -1
- fides/ui-build/static/admin/consent/privacy-notices.html +1 -1
- fides/ui-build/static/admin/consent/properties.html +1 -1
- fides/ui-build/static/admin/consent/reporting.html +1 -1
- fides/ui-build/static/admin/consent.html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn]/[resourceUrn].html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn].html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/projects.html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/resources/[resourceUrn].html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/resources.html +1 -1
- fides/ui-build/static/admin/data-catalog.html +1 -1
- fides/ui-build/static/admin/data-discovery/action-center/[monitorId]/[systemId].html +1 -1
- fides/ui-build/static/admin/data-discovery/action-center/[monitorId].html +1 -1
- fides/ui-build/static/admin/data-discovery/action-center.html +1 -1
- fides/ui-build/static/admin/data-discovery/activity.html +1 -1
- fides/ui-build/static/admin/data-discovery/detection/[resourceUrn].html +1 -1
- fides/ui-build/static/admin/data-discovery/detection.html +1 -1
- fides/ui-build/static/admin/data-discovery/discovery/[resourceUrn].html +1 -1
- fides/ui-build/static/admin/data-discovery/discovery.html +1 -1
- fides/ui-build/static/admin/datamap.html +1 -1
- fides/ui-build/static/admin/dataset/[datasetId]/[collectionName]/[...subfieldNames].html +1 -1
- fides/ui-build/static/admin/dataset/[datasetId]/[collectionName].html +1 -1
- fides/ui-build/static/admin/dataset/[datasetId].html +1 -1
- fides/ui-build/static/admin/dataset/new.html +1 -1
- fides/ui-build/static/admin/dataset.html +1 -1
- fides/ui-build/static/admin/datastore-connection/[id].html +1 -1
- fides/ui-build/static/admin/datastore-connection/new.html +1 -1
- fides/ui-build/static/admin/datastore-connection.html +1 -1
- fides/ui-build/static/admin/index.html +1 -1
- fides/ui-build/static/admin/integrations/[id].html +1 -1
- fides/ui-build/static/admin/integrations.html +1 -1
- fides/ui-build/static/admin/lib/fides-ext-gpp.js +1 -1
- fides/ui-build/static/admin/lib/fides-headless.js +1 -1
- fides/ui-build/static/admin/lib/fides-preview.js +1 -1
- fides/ui-build/static/admin/lib/fides-tcf.js +2 -2
- fides/ui-build/static/admin/lib/fides.js +2 -2
- fides/ui-build/static/admin/login/[provider].html +1 -1
- fides/ui-build/static/admin/login.html +1 -1
- fides/ui-build/static/admin/messaging/[id].html +1 -1
- fides/ui-build/static/admin/messaging/add-template.html +1 -1
- fides/ui-build/static/admin/messaging.html +1 -1
- fides/ui-build/static/admin/poc/ant-components.html +1 -1
- fides/ui-build/static/admin/poc/form-experiments/AntForm.html +1 -1
- fides/ui-build/static/admin/poc/form-experiments/FormikAntFormItem.html +1 -1
- fides/ui-build/static/admin/poc/form-experiments/FormikControlled.html +1 -1
- fides/ui-build/static/admin/poc/form-experiments/FormikField.html +1 -1
- fides/ui-build/static/admin/poc/form-experiments/FormikSpreadField.html +1 -1
- fides/ui-build/static/admin/poc/forms.html +1 -1
- fides/ui-build/static/admin/poc/table-migration.html +1 -1
- fides/ui-build/static/admin/privacy-requests/[id].html +1 -1
- fides/ui-build/static/admin/privacy-requests/configure/messaging.html +1 -1
- fides/ui-build/static/admin/privacy-requests/configure/storage.html +1 -1
- fides/ui-build/static/admin/privacy-requests/configure.html +1 -1
- fides/ui-build/static/admin/privacy-requests.html +1 -1
- fides/ui-build/static/admin/properties/[id].html +1 -1
- fides/ui-build/static/admin/properties/add-property.html +1 -1
- fides/ui-build/static/admin/properties.html +1 -1
- fides/ui-build/static/admin/reporting/datamap.html +1 -1
- fides/ui-build/static/admin/settings/about/alpha.html +1 -1
- fides/ui-build/static/admin/settings/about.html +1 -1
- fides/ui-build/static/admin/settings/consent/[configuration_id]/[purpose_id].html +1 -1
- fides/ui-build/static/admin/settings/consent.html +1 -1
- fides/ui-build/static/admin/settings/custom-fields.html +1 -1
- fides/ui-build/static/admin/settings/domain-records.html +1 -1
- fides/ui-build/static/admin/settings/domains.html +1 -1
- fides/ui-build/static/admin/settings/email-templates.html +1 -1
- fides/ui-build/static/admin/settings/locations.html +1 -1
- fides/ui-build/static/admin/settings/organization.html +1 -1
- fides/ui-build/static/admin/settings/regulations.html +1 -1
- fides/ui-build/static/admin/systems/configure/[id]/test-datasets.html +1 -1
- fides/ui-build/static/admin/systems/configure/[id].html +1 -1
- fides/ui-build/static/admin/systems.html +1 -1
- fides/ui-build/static/admin/taxonomy.html +1 -1
- fides/ui-build/static/admin/user-management/new.html +1 -1
- fides/ui-build/static/admin/user-management/profile/[id].html +1 -1
- fides/ui-build/static/admin/user-management.html +1 -1
- fides/api/service/privacy_request/attachment_handling.py +0 -132
- fides/api/service/privacy_request/dsr_package/templates/attachments_index.html +0 -33
- fides/api/tasks/csv_utils.py +0 -170
- fides/api/tasks/encryption_utils.py +0 -42
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/messaging-c583a61302f02add.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/storage-20d20a8d1736f7c4.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests-0e557d79e1e43c2b.js +0 -1
- {ethyca_fides-2.63.1b4.dist-info → ethyca_fides-2.63.2.dist-info}/WHEEL +0 -0
- {ethyca_fides-2.63.1b4.dist-info → ethyca_fides-2.63.2.dist-info}/entry_points.txt +0 -0
- {ethyca_fides-2.63.1b4.dist-info → ethyca_fides-2.63.2.dist-info}/licenses/LICENSE +0 -0
- {ethyca_fides-2.63.1b4.dist-info → ethyca_fides-2.63.2.dist-info}/top_level.txt +0 -0
- /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
|
-
|
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
|
-
|
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
|
-
|
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:
|
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
|
-
|
209
|
-
|
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
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
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
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
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
|
-
|
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
|
-
|
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
|
210
|
+
def upload_access_results( # pylint: disable=R0912
|
290
211
|
session: Session,
|
291
212
|
policy: Policy,
|
292
|
-
access_result:
|
213
|
+
access_result: Dict[str, List[Row]],
|
293
214
|
dataset_graph: DatasetGraph,
|
294
215
|
privacy_request: PrivacyRequest,
|
295
|
-
|
296
|
-
fides_connector_datasets:
|
297
|
-
) ->
|
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
|
-
|
300
|
-
|
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:
|
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
|
-
|
322
|
-
|
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
|
-
|
347
|
-
|
348
|
-
|
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
|
-
|
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:
|
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:
|
494
|
-
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 =
|
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:
|
706
|
-
identity_data:
|
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:
|
790
|
-
) -> Optional[
|
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:
|
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:
|
888
|
-
can_skip_consent_email:
|
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:
|
920
|
-
can_skip_consent_email:
|
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
|
fides/api/service/storage/gcs.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
from typing import Optional
|
1
|
+
from typing import Dict, Optional
|
2
2
|
|
3
|
-
from google.cloud.storage import
|
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[
|
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
|