ethyca-fides 2.71.1b1__py2.py3-none-any.whl → 2.71.1rc1__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.
- {ethyca_fides-2.71.1b1.dist-info → ethyca_fides-2.71.1rc1.dist-info}/METADATA +2 -2
- {ethyca_fides-2.71.1b1.dist-info → ethyca_fides-2.71.1rc1.dist-info}/RECORD +170 -159
- fides/_version.py +3 -3
- fides/api/alembic/migrations/versions/4bfbeff34611_add_polling_status.py +68 -0
- fides/api/alembic/migrations/versions/7db29f9cd77b_create_new_sub_request_table.py +95 -0
- fides/api/alembic/migrations/versions/b97e92b038d2_add_digest_execution_model.py +117 -0
- fides/api/api/v1/endpoints/generic_overrides.py +3 -9
- fides/api/common_exceptions.py +4 -0
- fides/api/main.py +2 -2
- fides/api/models/attachment.py +1 -0
- fides/api/models/digest/__init__.py +2 -0
- fides/api/models/digest/digest_config.py +10 -1
- fides/api/models/digest/digest_execution.py +132 -0
- fides/api/models/event_audit.py +8 -0
- fides/api/models/privacy_notice.py +0 -8
- fides/api/models/privacy_request/request_task.py +98 -1
- fides/api/models/worker_task.py +8 -0
- fides/api/schemas/saas/async_polling_configuration.py +81 -0
- fides/api/schemas/saas/saas_config.py +10 -3
- fides/api/schemas/saas/strategy_configuration.py +0 -12
- fides/api/service/async_dsr/handlers/__init__.py +0 -0
- fides/api/service/async_dsr/handlers/polling_attachment_handler.py +155 -0
- fides/api/service/async_dsr/handlers/polling_request_handler.py +88 -0
- fides/api/service/async_dsr/handlers/polling_response_handler.py +261 -0
- fides/api/service/async_dsr/handlers/polling_sub_request_handler.py +123 -0
- fides/api/service/async_dsr/strategies/__init__.py +0 -0
- fides/api/service/async_dsr/strategies/async_dsr_strategy.py +52 -0
- fides/api/service/async_dsr/strategies/async_dsr_strategy_callback.py +199 -0
- fides/api/service/async_dsr/strategies/async_dsr_strategy_factory.py +72 -0
- fides/api/service/async_dsr/strategies/async_dsr_strategy_polling.py +678 -0
- fides/api/service/async_dsr/utils.py +130 -0
- fides/api/service/connectors/fides/fides_client.py +63 -1
- fides/api/service/connectors/query_configs/saas_query_config.py +4 -5
- fides/api/service/connectors/saas_connector.py +77 -69
- fides/api/service/privacy_request/attachment_handling.py +9 -2
- fides/api/service/privacy_request/request_runner_service.py +9 -83
- fides/api/service/privacy_request/request_service.py +47 -74
- fides/api/service/saas_request/saas_request_override_factory.py +66 -1
- fides/api/task/execute_request_tasks.py +5 -2
- fides/api/task/filter_results.py +35 -2
- fides/api/task/graph_task.py +34 -2
- fides/config/execution_settings.py +7 -3
- fides/service/dataset/dataset_service.py +0 -39
- fides/service/privacy_request/privacy_request_service.py +48 -103
- fides/ui-build/static/admin/404.html +1 -1
- fides/ui-build/static/admin/_next/static/{_IxwgneyQjdSaZFEF3Tqu → AfNel282iPq07N-lE1Vzx}/_buildManifest.js +1 -1
- fides/ui-build/static/admin/_next/static/chunks/155-c1ae010c664e2245.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{3585-f728d32fda6f1ac1.js → 3585-efd5d41f08e180c4.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/3855-12ee1dfbbe47fd28.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/409-c1256ecda1b15db6.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/4558-de5ced790b3380dc.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/4608-a9941d0c236ebca1.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{4718-3a412bdb90add82f.js → 4718-6585c97c26647e65.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/502-d3ecae97b67befbd.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{7045-14e955890f1147e4.js → 7045-f15044a4d4525946.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{_app-c1c2f757b1f3da12.js → _app-a7c02dd2ff07f9e1.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/add-systems-58920afe2b67f952.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/reporting-ae4909cad9b67822.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/[systemId]-29c1fb777bd464e0.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/activity-2635ef588bf06145.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/datamap-15616bea02397ef4.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/new-ea198c4a7869f402.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations-f682b1def859931e.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/[id]-febf156d2977f3ac.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/storage-648d775d0fce49dc.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/custom-fields-5edfec10a945ca43.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/{privacy-requests-97221067330c0c27.js → privacy-requests-8cbdfd08e0fa88fb.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/systems-0f1d833282f09684.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/taxonomy-a8cfa7de4948b374.js +1 -0
- fides/ui-build/static/admin/_next/static/css/{295d729ea1b11885.css → 64fac6fb884435c2.css} +1 -1
- fides/ui-build/static/admin/_next/static/css/f38242c11f7fea64.css +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-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/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/[id].html +1 -1
- fides/ui-build/static/admin/settings/custom-fields/new.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/messaging-providers/[key].html +1 -1
- fides/ui-build/static/admin/settings/messaging-providers/new.html +1 -1
- fides/ui-build/static/admin/settings/messaging-providers.html +1 -1
- fides/ui-build/static/admin/settings/organization.html +1 -1
- fides/ui-build/static/admin/settings/privacy-requests.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/async_dsr/async_dsr_service.py +0 -195
- fides/api/service/async_dsr/async_dsr_strategy.py +0 -5
- fides/api/service/async_dsr/async_dsr_strategy_callback.py +0 -16
- fides/api/service/async_dsr/async_dsr_strategy_factory.py +0 -63
- fides/api/service/async_dsr/async_dsr_strategy_polling.py +0 -94
- fides/ui-build/static/admin/_next/static/chunks/155-b4337d0826d5addc.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/3855-ed226b8a8050bd40.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/409-5c3d31163028339f.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/4558-8305aee48def1dcd.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/4608-0c6ef78e30a51f84.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/502-0d9f4ac29ef34a1c.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/add-systems-19214babd1f219e3.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/reporting-c1a3caf3c286bf5d.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/[systemId]-5b57f9132426fe52.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/activity-a28cc0e23bbe4fc8.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datamap-7d22222608ec3aac.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/new-d514cd4ec62e3b03.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations-331544e9b85c4ac2.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/[id]-7dac2302f573f5ee.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/storage-479890582973deaf.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/custom-fields-2fcd95c41e578d57.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/systems-6c91bdea40875227.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/taxonomy-3059aba38adefa56.js +0 -1
- fides/ui-build/static/admin/_next/static/css/073713cd1eddda79.css +0 -1
- {ethyca_fides-2.71.1b1.dist-info → ethyca_fides-2.71.1rc1.dist-info}/WHEEL +0 -0
- {ethyca_fides-2.71.1b1.dist-info → ethyca_fides-2.71.1rc1.dist-info}/entry_points.txt +0 -0
- {ethyca_fides-2.71.1b1.dist-info → ethyca_fides-2.71.1rc1.dist-info}/licenses/LICENSE +0 -0
- {ethyca_fides-2.71.1b1.dist-info → ethyca_fides-2.71.1rc1.dist-info}/top_level.txt +0 -0
- /fides/ui-build/static/admin/_next/static/{_IxwgneyQjdSaZFEF3Tqu → AfNel282iPq07N-lE1Vzx}/_ssgManifest.js +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{6882-dbe0a25dcf1a8ee0.js → 6882-10296485ec326e6b.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{7079-50571e9f3269d74d.js → 7079-bbc7b856802a4834.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{9046-b6616ba7b59d947e.js → 9046-2a332fe338535c84.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{data-catalog-b7326c51d88cc2cc.js → data-catalog-56fd0f3e465e52b6.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/[collectionName]/{[...subfieldNames]-0abd30eada811b5b.js → [...subfieldNames]-d4031e438c363fff.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/{[collectionName]-007965429368d9a3.js → [collectionName]-9463af37079762d0.js} +0 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pure utility functions for async DSR operations.
|
|
3
|
+
|
|
4
|
+
This module contains utility functions with no business logic dependencies.
|
|
5
|
+
These are helper functions that can be used across the async DSR system.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from enum import Enum
|
|
9
|
+
|
|
10
|
+
# Type checking imports
|
|
11
|
+
from typing import TYPE_CHECKING, List, cast
|
|
12
|
+
|
|
13
|
+
from loguru import logger
|
|
14
|
+
from sqlalchemy.orm import Session
|
|
15
|
+
|
|
16
|
+
from fides.api.common_exceptions import PrivacyRequestError
|
|
17
|
+
from fides.api.models.connectionconfig import ConnectionConfig
|
|
18
|
+
from fides.api.models.datasetconfig import DatasetConfig
|
|
19
|
+
from fides.api.models.privacy_request.request_task import AsyncTaskType, RequestTask
|
|
20
|
+
from fides.api.schemas.saas.saas_config import SaaSRequest
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from fides.api.service.connectors.query_configs.saas_query_config import (
|
|
24
|
+
SaaSQueryConfig,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def has_async_requests(query_config: "SaaSQueryConfig") -> bool:
|
|
29
|
+
"""Check if any read/update/delete requests have async configuration"""
|
|
30
|
+
all_requests: List[SaaSRequest] = cast(
|
|
31
|
+
List[SaaSRequest], query_config.get_read_requests_by_identity()
|
|
32
|
+
)
|
|
33
|
+
masking_request = query_config.get_masking_request()
|
|
34
|
+
if masking_request:
|
|
35
|
+
all_requests.append(masking_request)
|
|
36
|
+
return any(request.async_config is not None for request in all_requests)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def is_polling_continuation(request_task: RequestTask) -> bool:
|
|
40
|
+
"""Check if this is a polling continuation (not initial request)"""
|
|
41
|
+
async_type_check = request_task.async_type == AsyncTaskType.polling
|
|
42
|
+
sub_requests_count = len(request_task.sub_requests)
|
|
43
|
+
|
|
44
|
+
logger.warning(
|
|
45
|
+
f"is_polling_continuation for task {request_task.id}: "
|
|
46
|
+
f"async_type={request_task.async_type} (polling={async_type_check}), "
|
|
47
|
+
f"sub_requests_count={sub_requests_count}"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
return async_type_check and sub_requests_count > 0
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def is_callback_completion(request_task: RequestTask) -> bool:
|
|
54
|
+
"""Check if this is a completed callback request"""
|
|
55
|
+
return bool(request_task.callback_succeeded)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def is_async_request(
|
|
59
|
+
request_task: RequestTask, query_config: "SaaSQueryConfig"
|
|
60
|
+
) -> bool:
|
|
61
|
+
"""
|
|
62
|
+
Check if this is an async request (callback, continuation, or initial async).
|
|
63
|
+
|
|
64
|
+
This is the main entry point for async detection.
|
|
65
|
+
"""
|
|
66
|
+
return (
|
|
67
|
+
is_callback_completion(request_task)
|
|
68
|
+
or is_polling_continuation(request_task)
|
|
69
|
+
or has_async_requests(query_config)
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class AsyncPhase(Enum):
|
|
74
|
+
"""Enum representing different phases of async DSR processing."""
|
|
75
|
+
|
|
76
|
+
callback_completion = "callback_completion"
|
|
77
|
+
polling_continuation = "polling_continuation"
|
|
78
|
+
initial_async = "initial_async"
|
|
79
|
+
sync = "sync"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def get_async_phase(
|
|
83
|
+
request_task: RequestTask, query_config: "SaaSQueryConfig"
|
|
84
|
+
) -> AsyncPhase:
|
|
85
|
+
"""
|
|
86
|
+
Classify the phase of async request for routing purposes.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
AsyncPhase enum value representing the current phase:
|
|
90
|
+
- callback_completion: Callback has completed
|
|
91
|
+
- polling_continuation: Continuing an existing polling process
|
|
92
|
+
- initial_async: Initial async request setup
|
|
93
|
+
- sync: Not an async request
|
|
94
|
+
"""
|
|
95
|
+
if is_callback_completion(request_task):
|
|
96
|
+
return AsyncPhase.callback_completion
|
|
97
|
+
if is_polling_continuation(request_task):
|
|
98
|
+
return AsyncPhase.polling_continuation
|
|
99
|
+
if has_async_requests(query_config):
|
|
100
|
+
return AsyncPhase.initial_async
|
|
101
|
+
return AsyncPhase.sync
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def get_connection_config_from_task(
|
|
105
|
+
db: Session, request_task: RequestTask
|
|
106
|
+
) -> ConnectionConfig:
|
|
107
|
+
"""
|
|
108
|
+
Get ConnectionConfig from a RequestTask.
|
|
109
|
+
|
|
110
|
+
This utility function retrieves the connection configuration
|
|
111
|
+
associated with a request task by looking up the dataset configuration.
|
|
112
|
+
"""
|
|
113
|
+
dataset_config = DatasetConfig.filter(
|
|
114
|
+
db=db,
|
|
115
|
+
conditions=(DatasetConfig.fides_key == request_task.dataset_name),
|
|
116
|
+
).first()
|
|
117
|
+
if not dataset_config:
|
|
118
|
+
raise PrivacyRequestError(
|
|
119
|
+
f"DatasetConfig with fides_key {request_task.dataset_name} not found."
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
connection_config = ConnectionConfig.get(
|
|
123
|
+
db=db, object_id=dataset_config.connection_config_id
|
|
124
|
+
)
|
|
125
|
+
if not connection_config:
|
|
126
|
+
raise PrivacyRequestError(
|
|
127
|
+
f"ConnectionConfig with id {dataset_config.connection_config_id} not found."
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
return connection_config
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from asyncio import sleep
|
|
4
|
+
from datetime import datetime
|
|
3
5
|
from typing import Any, Dict, List, Optional
|
|
4
6
|
|
|
5
7
|
import httpx
|
|
6
8
|
from httpx import AsyncClient, Client, HTTPStatusError, Request, RequestError, Timeout
|
|
7
9
|
from loguru import logger
|
|
8
10
|
|
|
11
|
+
from fides.api.common_exceptions import PrivacyRequestNotFound
|
|
9
12
|
from fides.api.schemas.privacy_request import (
|
|
10
13
|
PrivacyRequestCreate,
|
|
11
14
|
PrivacyRequestResponse,
|
|
@@ -13,11 +16,11 @@ from fides.api.schemas.privacy_request import (
|
|
|
13
16
|
)
|
|
14
17
|
from fides.api.schemas.redis_cache import Identity
|
|
15
18
|
from fides.api.schemas.user import UserLogin
|
|
16
|
-
from fides.api.service.privacy_request.request_service import poll_server_for_completion
|
|
17
19
|
from fides.api.util.collection_util import Row
|
|
18
20
|
from fides.api.util.errors import FidesError
|
|
19
21
|
from fides.api.util.wrappers import sync
|
|
20
22
|
from fides.common.api.v1 import urn_registry as urls
|
|
23
|
+
from fides.common.api.v1.urn_registry import PRIVACY_REQUESTS
|
|
21
24
|
|
|
22
25
|
COMPLETION_STATUSES = [
|
|
23
26
|
PrivacyRequestStatus.complete,
|
|
@@ -27,6 +30,65 @@ COMPLETION_STATUSES = [
|
|
|
27
30
|
]
|
|
28
31
|
|
|
29
32
|
|
|
33
|
+
def get_async_client() -> AsyncClient:
|
|
34
|
+
"""Return an async client used to make API requests"""
|
|
35
|
+
return AsyncClient()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def poll_server_for_completion(
|
|
39
|
+
privacy_request_id: str,
|
|
40
|
+
server_url: str,
|
|
41
|
+
token: str,
|
|
42
|
+
*,
|
|
43
|
+
poll_interval_seconds: int = 30,
|
|
44
|
+
timeout_seconds: int = 1800, # 30 minutes
|
|
45
|
+
client: AsyncClient | None = None,
|
|
46
|
+
) -> PrivacyRequestResponse:
|
|
47
|
+
"""Poll a server for privacy request completion.
|
|
48
|
+
|
|
49
|
+
Requests will report complete with if they have a status of canceled, complete,
|
|
50
|
+
denied, or error. By default the polling will time out if not completed in 30
|
|
51
|
+
minutes, time can be overridden by setting the timeout_seconds.
|
|
52
|
+
"""
|
|
53
|
+
url = f"{server_url}{urls.V1_URL_PREFIX}{PRIVACY_REQUESTS}?request_id={privacy_request_id}"
|
|
54
|
+
start_time = datetime.now()
|
|
55
|
+
elapsed_time = 0.0
|
|
56
|
+
while elapsed_time < timeout_seconds:
|
|
57
|
+
if client:
|
|
58
|
+
response = await client.get(
|
|
59
|
+
url, headers={"Authorization": f"Bearer {token}"}
|
|
60
|
+
)
|
|
61
|
+
else:
|
|
62
|
+
async_client = get_async_client()
|
|
63
|
+
response = await async_client.get(
|
|
64
|
+
url, headers={"Authorization": f"Bearer {token}"}
|
|
65
|
+
)
|
|
66
|
+
response.raise_for_status()
|
|
67
|
+
|
|
68
|
+
# Privacy requests are returned paginated. Since this is searching for a specific
|
|
69
|
+
# privacy request there should only be one value present in items.
|
|
70
|
+
items = response.json()["items"]
|
|
71
|
+
if not items:
|
|
72
|
+
raise PrivacyRequestNotFound(
|
|
73
|
+
f"No privacy request found with id '{privacy_request_id}'"
|
|
74
|
+
)
|
|
75
|
+
status = PrivacyRequestResponse(**items[0])
|
|
76
|
+
if status.status and status.status in (
|
|
77
|
+
PrivacyRequestStatus.complete,
|
|
78
|
+
PrivacyRequestStatus.canceled,
|
|
79
|
+
PrivacyRequestStatus.error,
|
|
80
|
+
PrivacyRequestStatus.denied,
|
|
81
|
+
):
|
|
82
|
+
return status
|
|
83
|
+
|
|
84
|
+
await sleep(poll_interval_seconds)
|
|
85
|
+
time_delta = datetime.now() - start_time
|
|
86
|
+
elapsed_time = time_delta.seconds
|
|
87
|
+
raise TimeoutError(
|
|
88
|
+
f"Timeout of {timeout_seconds} seconds has been exceeded while waiting for privacy request {privacy_request_id}"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
30
92
|
class FidesClient:
|
|
31
93
|
"""
|
|
32
94
|
A helper client to broker communications between Fides servers.
|
|
@@ -10,7 +10,6 @@ from uuid import uuid4
|
|
|
10
10
|
import pydash
|
|
11
11
|
from fideslang.models import FidesDatasetReference
|
|
12
12
|
from loguru import logger
|
|
13
|
-
from sqlalchemy.orm import Session
|
|
14
13
|
|
|
15
14
|
from fides.api.common_exceptions import FidesopsException
|
|
16
15
|
from fides.api.graph.execution import ExecutionNode
|
|
@@ -140,7 +139,7 @@ class SaaSQueryConfig(QueryConfig[SaaSRequestParams]):
|
|
|
140
139
|
)
|
|
141
140
|
return request
|
|
142
141
|
|
|
143
|
-
def get_masking_request(self
|
|
142
|
+
def get_masking_request(self) -> Optional[SaaSRequest]:
|
|
144
143
|
"""
|
|
145
144
|
Returns a tuple of the preferred action and SaaSRequest to use for masking.
|
|
146
145
|
An update request is preferred, but we can use a gdpr delete endpoint or
|
|
@@ -353,7 +352,8 @@ class SaaSQueryConfig(QueryConfig[SaaSRequestParams]):
|
|
|
353
352
|
]
|
|
354
353
|
|
|
355
354
|
param_values[UUID] = str(uuid4())
|
|
356
|
-
|
|
355
|
+
# Use full ISO-8601 timestamp including time component (UTC, seconds precision)
|
|
356
|
+
param_values[ISO_8601_DATETIME] = datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
357
357
|
param_values[FIELD_LIST] = ",".join(
|
|
358
358
|
[
|
|
359
359
|
field.name
|
|
@@ -384,8 +384,7 @@ class SaaSQueryConfig(QueryConfig[SaaSRequestParams]):
|
|
|
384
384
|
The fields in the row are masked according to the policy and added to the request body
|
|
385
385
|
if specified by the body field of the masking request.
|
|
386
386
|
"""
|
|
387
|
-
|
|
388
|
-
current_request: SaaSRequest = self.get_masking_request(session) # type: ignore
|
|
387
|
+
current_request: SaaSRequest = self.get_masking_request() # type: ignore
|
|
389
388
|
param_values: Dict[str, Any] = self.generate_update_param_values(
|
|
390
389
|
row, policy, request, current_request
|
|
391
390
|
)
|
|
@@ -10,18 +10,16 @@ from requests import Response
|
|
|
10
10
|
from sqlalchemy.orm import Session
|
|
11
11
|
from starlette.status import HTTP_204_NO_CONTENT
|
|
12
12
|
|
|
13
|
+
from fides.api.api.deps import get_autoclose_db_session as get_db
|
|
13
14
|
from fides.api.common_exceptions import (
|
|
14
|
-
AwaitingAsyncTask,
|
|
15
15
|
FidesopsException,
|
|
16
16
|
PostProcessingException,
|
|
17
|
-
PrivacyRequestError,
|
|
18
17
|
SkippingConsentPropagation,
|
|
19
18
|
)
|
|
20
19
|
from fides.api.graph.execution import ExecutionNode
|
|
21
20
|
from fides.api.models.connectionconfig import ConnectionConfig, ConnectionTestStatus
|
|
22
21
|
from fides.api.models.policy import Policy
|
|
23
22
|
from fides.api.models.privacy_request import PrivacyRequest, RequestTask
|
|
24
|
-
from fides.api.models.privacy_request.request_task import AsyncTaskType
|
|
25
23
|
from fides.api.schemas.consentable_item import (
|
|
26
24
|
ConsentableItem,
|
|
27
25
|
build_consent_item_hierarchy,
|
|
@@ -39,6 +37,10 @@ from fides.api.schemas.saas.shared_schemas import (
|
|
|
39
37
|
ConsentPropagationStatus,
|
|
40
38
|
SaaSRequestParams,
|
|
41
39
|
)
|
|
40
|
+
from fides.api.service.async_dsr.strategies.async_dsr_strategy import AsyncDSRStrategy
|
|
41
|
+
from fides.api.service.async_dsr.strategies.async_dsr_strategy_factory import (
|
|
42
|
+
get_strategy,
|
|
43
|
+
)
|
|
42
44
|
from fides.api.service.connectors.base_connector import BaseConnector
|
|
43
45
|
from fides.api.service.connectors.query_configs.saas_query_config import SaaSQueryConfig
|
|
44
46
|
from fides.api.service.connectors.saas.authenticated_client import AuthenticatedClient
|
|
@@ -216,19 +218,29 @@ class SaaSConnector(BaseConnector[AuthenticatedClient], Contextualizable):
|
|
|
216
218
|
request_task: RequestTask,
|
|
217
219
|
input_data: Dict[str, List[Any]],
|
|
218
220
|
) -> List[Row]:
|
|
219
|
-
"""
|
|
221
|
+
"""
|
|
222
|
+
Retrieve data from SaaS APIs.
|
|
223
|
+
|
|
224
|
+
Handles sync requests directly and delegates async requests to external handlers.
|
|
225
|
+
"""
|
|
220
226
|
|
|
221
227
|
# pylint: disable=too-many-branches
|
|
222
228
|
self.set_privacy_request_state(privacy_request, node, request_task)
|
|
223
|
-
|
|
224
|
-
# If this is True, we assume we've received results from a third party
|
|
225
|
-
# asynchronously and we can proceed to the next node.
|
|
226
|
-
logger.info(
|
|
227
|
-
"Access callback succeeded for request task '{}'", request_task.id
|
|
228
|
-
)
|
|
229
|
-
return request_task.get_access_data()
|
|
229
|
+
|
|
230
230
|
query_config: SaaSQueryConfig = self.query_config(node)
|
|
231
231
|
|
|
232
|
+
# Delegate async requests
|
|
233
|
+
with get_db() as db:
|
|
234
|
+
if async_dsr_strategy := _get_async_dsr_strategy(
|
|
235
|
+
db, request_task, query_config, ActionType.access
|
|
236
|
+
):
|
|
237
|
+
return async_dsr_strategy.async_retrieve_data(
|
|
238
|
+
client=self.create_client(),
|
|
239
|
+
request_task_id=request_task.id,
|
|
240
|
+
query_config=query_config,
|
|
241
|
+
input_data=input_data,
|
|
242
|
+
)
|
|
243
|
+
|
|
232
244
|
# generate initial set of requests if read request is defined, otherwise raise an exception
|
|
233
245
|
# An endpoint can be defined with multiple 'read' requests if the data for a single
|
|
234
246
|
# collection can be accessed in multiple ways for example:
|
|
@@ -265,30 +277,8 @@ class SaaSConnector(BaseConnector[AuthenticatedClient], Contextualizable):
|
|
|
265
277
|
input_data[CUSTOM_PRIVACY_REQUEST_FIELDS] = [custom_privacy_request_fields]
|
|
266
278
|
|
|
267
279
|
rows: List[Row] = []
|
|
268
|
-
awaiting_async_processing: bool = False
|
|
269
|
-
|
|
270
280
|
for read_request in read_requests:
|
|
271
281
|
self.set_saas_request_state(read_request)
|
|
272
|
-
if (
|
|
273
|
-
read_request.async_config is not None
|
|
274
|
-
and request_task.id # Only supported in DSR 3.0
|
|
275
|
-
):
|
|
276
|
-
# Asynchronous read request detected. We will exit below and put the
|
|
277
|
-
# Request Task in an "awaiting_processing" status.
|
|
278
|
-
awaiting_async_processing = True
|
|
279
|
-
|
|
280
|
-
# Validate async strategy with proper enum value checking
|
|
281
|
-
strategy_value = read_request.async_config.strategy
|
|
282
|
-
valid_strategies = [task_type.value for task_type in AsyncTaskType]
|
|
283
|
-
|
|
284
|
-
if strategy_value not in valid_strategies:
|
|
285
|
-
raise PrivacyRequestError(
|
|
286
|
-
f"Invalid async type '{strategy_value}' for request task {request_task.id}. "
|
|
287
|
-
f"Valid types are: {valid_strategies}"
|
|
288
|
-
)
|
|
289
|
-
|
|
290
|
-
request_task.async_type = AsyncTaskType(strategy_value)
|
|
291
|
-
|
|
292
282
|
# check all the values specified by param_values are provided in input_data
|
|
293
283
|
if self._missing_dataset_reference_values(
|
|
294
284
|
input_data, read_request.param_values
|
|
@@ -334,7 +324,7 @@ class SaaSConnector(BaseConnector[AuthenticatedClient], Contextualizable):
|
|
|
334
324
|
# This allows us to build an output object even if we didn't generate and execute
|
|
335
325
|
# any HTTP requests. This is useful if we just want to select specific input_data
|
|
336
326
|
# values to provide as row data to the mask_data function
|
|
337
|
-
elif read_request.output:
|
|
327
|
+
elif not read_request.path and read_request.output:
|
|
338
328
|
rows.extend(
|
|
339
329
|
self._apply_output_template(
|
|
340
330
|
query_config.generate_param_value_maps(
|
|
@@ -345,17 +335,6 @@ class SaaSConnector(BaseConnector[AuthenticatedClient], Contextualizable):
|
|
|
345
335
|
)
|
|
346
336
|
|
|
347
337
|
self.unset_connector_state()
|
|
348
|
-
if awaiting_async_processing:
|
|
349
|
-
# If a read request was marked to expect async results, original response data here is ignored.
|
|
350
|
-
# We'll instead use the data received in the callback URL later.
|
|
351
|
-
# However for polling async request we want to save the request data for ids that we will use on the pollings status
|
|
352
|
-
if request_task.async_type == AsyncTaskType.polling:
|
|
353
|
-
# Saving the request task access data to use it on the polling status request
|
|
354
|
-
# TODO: Consider if we want to clean up the rows. Currently this is the concern of the GraphTask.
|
|
355
|
-
request_task.access_data = rows
|
|
356
|
-
# Raising an AwaitingAsyncTask to put this task in an awaiting_processing state
|
|
357
|
-
raise AwaitingAsyncTask()
|
|
358
|
-
|
|
359
338
|
return rows
|
|
360
339
|
|
|
361
340
|
def _apply_output_template(
|
|
@@ -536,22 +515,28 @@ class SaaSConnector(BaseConnector[AuthenticatedClient], Contextualizable):
|
|
|
536
515
|
rows: List[Row],
|
|
537
516
|
input_data: Optional[Dict[str, List[Any]]] = None,
|
|
538
517
|
) -> int:
|
|
539
|
-
"""
|
|
540
|
-
|
|
541
|
-
if request_task.callback_succeeded:
|
|
542
|
-
# If this is True, we assume the data was masked
|
|
543
|
-
# asynchronously and we can proceed to the next node.
|
|
544
|
-
logger.info(
|
|
545
|
-
"Masking callback succeeded for request task '{}'", request_task.id
|
|
546
|
-
)
|
|
547
|
-
# If we've received the callback for this node, return rows_masked directly
|
|
548
|
-
return request_task.rows_masked or 0
|
|
518
|
+
"""
|
|
519
|
+
Execute masking requests.
|
|
549
520
|
|
|
521
|
+
Handles synchronous requests directly and delegates async requests to external handlers.
|
|
522
|
+
"""
|
|
550
523
|
self.set_privacy_request_state(privacy_request, node, request_task)
|
|
524
|
+
|
|
551
525
|
query_config = self.query_config(node)
|
|
552
526
|
|
|
553
|
-
|
|
554
|
-
|
|
527
|
+
# Delegate async requests
|
|
528
|
+
with get_db() as db:
|
|
529
|
+
if async_dsr_strategy := _get_async_dsr_strategy(
|
|
530
|
+
db, request_task, query_config, ActionType.erasure
|
|
531
|
+
):
|
|
532
|
+
return async_dsr_strategy.async_mask_data(
|
|
533
|
+
client=self.create_client(),
|
|
534
|
+
request_task_id=request_task.id,
|
|
535
|
+
query_config=query_config,
|
|
536
|
+
rows=rows,
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
masking_request = query_config.get_masking_request()
|
|
555
540
|
rows_updated = 0
|
|
556
541
|
|
|
557
542
|
if not masking_request:
|
|
@@ -648,18 +633,6 @@ class SaaSConnector(BaseConnector[AuthenticatedClient], Contextualizable):
|
|
|
648
633
|
)
|
|
649
634
|
|
|
650
635
|
self.unset_connector_state()
|
|
651
|
-
|
|
652
|
-
awaiting_async_callback: bool = bool(
|
|
653
|
-
masking_request.async_config
|
|
654
|
-
and masking_request.async_config.strategy == AsyncTaskType.callback.value
|
|
655
|
-
) and bool(
|
|
656
|
-
request_task.id
|
|
657
|
-
) # Only supported in DSR 3.0
|
|
658
|
-
if awaiting_async_callback:
|
|
659
|
-
# Asynchronous masking request detected in saas config.
|
|
660
|
-
# If the masking request was marked to expect async results, original responses are ignored
|
|
661
|
-
# and we raise an AwaitingAsyncTask to put this task in an awaiting_processing state.
|
|
662
|
-
raise AwaitingAsyncTask()
|
|
663
636
|
return rows_updated
|
|
664
637
|
|
|
665
638
|
@staticmethod
|
|
@@ -1112,3 +1085,38 @@ class SaaSConnector(BaseConnector[AuthenticatedClient], Contextualizable):
|
|
|
1112
1085
|
return []
|
|
1113
1086
|
|
|
1114
1087
|
return consent_requests.opt_in if opt_in else consent_requests.opt_out # type: ignore
|
|
1088
|
+
|
|
1089
|
+
|
|
1090
|
+
def _get_async_dsr_strategy(
|
|
1091
|
+
session: Session,
|
|
1092
|
+
request_task: RequestTask,
|
|
1093
|
+
query_config: SaaSQueryConfig,
|
|
1094
|
+
action_type: ActionType,
|
|
1095
|
+
) -> Optional[AsyncDSRStrategy]:
|
|
1096
|
+
"""
|
|
1097
|
+
Returns the async DSR strategy if any of the read or masking requests have an async_config.
|
|
1098
|
+
"""
|
|
1099
|
+
|
|
1100
|
+
# Async processing is only supported for DSR 3.0.
|
|
1101
|
+
# A request task with an ID of None is an indicator that the request is not DSR 3.0.
|
|
1102
|
+
if request_task.id is None:
|
|
1103
|
+
return None
|
|
1104
|
+
|
|
1105
|
+
if action_type == ActionType.access:
|
|
1106
|
+
read_requests = query_config.get_read_requests_by_identity()
|
|
1107
|
+
for request in read_requests:
|
|
1108
|
+
if request.async_config is not None:
|
|
1109
|
+
return get_strategy(
|
|
1110
|
+
request.async_config.strategy,
|
|
1111
|
+
session,
|
|
1112
|
+
request.async_config.configuration,
|
|
1113
|
+
)
|
|
1114
|
+
elif action_type == ActionType.erasure:
|
|
1115
|
+
masking_request = query_config.get_masking_request()
|
|
1116
|
+
if masking_request is not None and masking_request.async_config is not None:
|
|
1117
|
+
return get_strategy(
|
|
1118
|
+
masking_request.async_config.strategy,
|
|
1119
|
+
session,
|
|
1120
|
+
masking_request.async_config.configuration,
|
|
1121
|
+
)
|
|
1122
|
+
return None
|
|
@@ -5,7 +5,7 @@ from typing import Any, Dict, Iterator, List, Optional, Tuple
|
|
|
5
5
|
from loguru import logger
|
|
6
6
|
|
|
7
7
|
from fides.api.models.attachment import Attachment, AttachmentType
|
|
8
|
-
from fides.api.schemas.storage.storage import StorageDetails
|
|
8
|
+
from fides.api.schemas.storage.storage import StorageDetails, StorageType
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
@dataclass
|
|
@@ -84,12 +84,19 @@ def get_attachments_content(
|
|
|
84
84
|
continue
|
|
85
85
|
|
|
86
86
|
processed_count += 1
|
|
87
|
+
|
|
88
|
+
# Handle bucket_name differently for different storage types
|
|
89
|
+
bucket_name = None
|
|
90
|
+
if attachment.config.type in [StorageType.s3, StorageType.gcs]:
|
|
91
|
+
bucket_name = attachment.config.details[StorageDetails.BUCKET.value]
|
|
92
|
+
# For local storage, bucket_name is not needed
|
|
93
|
+
|
|
87
94
|
yield AttachmentData(
|
|
88
95
|
file_name=attachment.file_name,
|
|
89
96
|
file_size=size,
|
|
90
97
|
download_url=str(url) if url else None,
|
|
91
98
|
content_type=attachment.content_type,
|
|
92
|
-
bucket_name=
|
|
99
|
+
bucket_name=bucket_name or "", # Empty string for local storage
|
|
93
100
|
file_key=attachment.file_key,
|
|
94
101
|
storage_key=attachment.storage_key,
|
|
95
102
|
)
|
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
import time
|
|
3
3
|
from copy import deepcopy
|
|
4
4
|
from datetime import datetime, timedelta
|
|
5
|
-
from typing import Any, Optional
|
|
5
|
+
from typing import Any, Optional
|
|
6
6
|
|
|
7
|
-
import requests
|
|
8
7
|
from loguru import logger
|
|
9
8
|
from pydantic import ValidationError as PydanticValidationError
|
|
10
9
|
from sqlalchemy.orm import Query, Session
|
|
@@ -22,7 +21,6 @@ from fides.api.common_exceptions import (
|
|
|
22
21
|
ValidationError,
|
|
23
22
|
)
|
|
24
23
|
from fides.api.db.session import get_db_session
|
|
25
|
-
from fides.api.graph.config import CollectionAddress
|
|
26
24
|
from fides.api.graph.graph import DatasetGraph
|
|
27
25
|
from fides.api.models.attachment import Attachment, AttachmentReferenceType
|
|
28
26
|
from fides.api.models.audit_log import AuditLog, AuditLogAction
|
|
@@ -53,7 +51,7 @@ from fides.api.schemas.policy import ActionType, CurrentStep
|
|
|
53
51
|
from fides.api.schemas.privacy_request import PrivacyRequestStatus
|
|
54
52
|
from fides.api.schemas.redis_cache import Identity
|
|
55
53
|
from fides.api.schemas.storage.storage import StorageType
|
|
56
|
-
from fides.api.service.connectors import
|
|
54
|
+
from fides.api.service.connectors import get_connector
|
|
57
55
|
from fides.api.service.connectors.consent_email_connector import (
|
|
58
56
|
CONSENT_EMAIL_CONNECTOR_TYPES,
|
|
59
57
|
)
|
|
@@ -85,11 +83,7 @@ from fides.api.util.collection_util import Row
|
|
|
85
83
|
from fides.api.util.logger import Pii, _log_exception, _log_warning
|
|
86
84
|
from fides.api.util.logger_context_utils import LoggerContextKeys, log_context
|
|
87
85
|
from fides.api.util.memory_watchdog import memory_limiter
|
|
88
|
-
from fides.common.api.v1.urn_registry import
|
|
89
|
-
PRIVACY_CENTER_DSR_PACKAGE,
|
|
90
|
-
PRIVACY_REQUEST_TRANSFER_TO_PARENT,
|
|
91
|
-
V1_URL_PREFIX,
|
|
92
|
-
)
|
|
86
|
+
from fides.common.api.v1.urn_registry import PRIVACY_CENTER_DSR_PACKAGE
|
|
93
87
|
from fides.config import CONFIG
|
|
94
88
|
from fides.config.config_proxy import ConfigProxy
|
|
95
89
|
|
|
@@ -307,8 +301,11 @@ def upload_and_save_access_results( # pylint: disable=R0912
|
|
|
307
301
|
) -> list[str]:
|
|
308
302
|
"""Process the data uploads after the access portion of the privacy request has completed"""
|
|
309
303
|
download_urls: list[str] = []
|
|
310
|
-
# Remove manual webhook attachments from the list of attachments
|
|
311
|
-
# This is done because
|
|
304
|
+
# Remove manual webhook attachments and request task attachments from the list of attachments
|
|
305
|
+
# This is done because:
|
|
306
|
+
# - manual webhook attachments are already included in the manual_data
|
|
307
|
+
# - manual task submission attachments are already included in the manual_data
|
|
308
|
+
# - request task attachments (from async polling) are already embedded in the dataset results
|
|
312
309
|
loaded_attachments = [
|
|
313
310
|
attachment
|
|
314
311
|
for attachment in privacy_request.attachments
|
|
@@ -317,6 +314,7 @@ def upload_and_save_access_results( # pylint: disable=R0912
|
|
|
317
314
|
in [
|
|
318
315
|
AttachmentReferenceType.access_manual_webhook,
|
|
319
316
|
AttachmentReferenceType.manual_task_submission,
|
|
317
|
+
AttachmentReferenceType.request_task,
|
|
320
318
|
]
|
|
321
319
|
for ref in attachment.references
|
|
322
320
|
)
|
|
@@ -922,78 +920,6 @@ def mark_paused_privacy_request_as_expired(privacy_request_id: str) -> None:
|
|
|
922
920
|
db.close()
|
|
923
921
|
|
|
924
922
|
|
|
925
|
-
def _retrieve_child_results( # pylint: disable=R0911
|
|
926
|
-
fides_connector: Tuple[str, ConnectionConfig],
|
|
927
|
-
rule_key: str,
|
|
928
|
-
access_result: dict[str, list[Row]],
|
|
929
|
-
) -> Optional[list[dict[str, Optional[list[Row]]]]]:
|
|
930
|
-
"""Get child access request results to add to upload."""
|
|
931
|
-
try:
|
|
932
|
-
connector = FidesConnector(fides_connector[1])
|
|
933
|
-
except Exception as e:
|
|
934
|
-
logger.error(
|
|
935
|
-
"Error create client for child server {}: {}", fides_connector[0], e
|
|
936
|
-
)
|
|
937
|
-
return None
|
|
938
|
-
|
|
939
|
-
results = []
|
|
940
|
-
|
|
941
|
-
for key, rows in access_result.items():
|
|
942
|
-
address = CollectionAddress.from_string(key)
|
|
943
|
-
privacy_request_id = None
|
|
944
|
-
if address.dataset == fides_connector[0]:
|
|
945
|
-
if not rows:
|
|
946
|
-
logger.info("No rows found for result entry {}", key)
|
|
947
|
-
continue
|
|
948
|
-
privacy_request_id = rows[0]["id"]
|
|
949
|
-
|
|
950
|
-
if not privacy_request_id:
|
|
951
|
-
logger.error(
|
|
952
|
-
"No privacy request found for connector key {}", fides_connector[0]
|
|
953
|
-
)
|
|
954
|
-
continue
|
|
955
|
-
|
|
956
|
-
try:
|
|
957
|
-
client = connector.create_client()
|
|
958
|
-
except requests.exceptions.HTTPError as e:
|
|
959
|
-
logger.error(
|
|
960
|
-
"Error logger into to child server for privacy request {}: {}",
|
|
961
|
-
privacy_request_id,
|
|
962
|
-
e,
|
|
963
|
-
)
|
|
964
|
-
continue
|
|
965
|
-
|
|
966
|
-
try:
|
|
967
|
-
request = client.authenticated_request(
|
|
968
|
-
method="get",
|
|
969
|
-
path=f"{V1_URL_PREFIX}{PRIVACY_REQUEST_TRANSFER_TO_PARENT.format(privacy_request_id=privacy_request_id, rule_key=rule_key)}",
|
|
970
|
-
headers={"Authorization": f"Bearer {client.token}"},
|
|
971
|
-
)
|
|
972
|
-
response = client.session.send(request)
|
|
973
|
-
except requests.exceptions.HTTPError as e:
|
|
974
|
-
logger.error(
|
|
975
|
-
"Error retrieving data from child server for privacy request {}: {}",
|
|
976
|
-
privacy_request_id,
|
|
977
|
-
e,
|
|
978
|
-
)
|
|
979
|
-
continue
|
|
980
|
-
|
|
981
|
-
if response.status_code != 200:
|
|
982
|
-
logger.error(
|
|
983
|
-
"Error retrieving data from child server for privacy request {}: {}",
|
|
984
|
-
privacy_request_id,
|
|
985
|
-
response.json(),
|
|
986
|
-
)
|
|
987
|
-
continue
|
|
988
|
-
|
|
989
|
-
results.append(response.json())
|
|
990
|
-
|
|
991
|
-
if not results:
|
|
992
|
-
return None
|
|
993
|
-
|
|
994
|
-
return results
|
|
995
|
-
|
|
996
|
-
|
|
997
923
|
def get_consent_email_connection_configs(db: Session) -> Query:
|
|
998
924
|
"""Return enabled consent email connection configs."""
|
|
999
925
|
return db.query(ConnectionConfig).filter(
|