ethyca-fides 2.67.2b3__py2.py3-none-any.whl → 2.67.2rc0__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.67.2b3.dist-info → ethyca_fides-2.67.2rc0.dist-info}/METADATA +2 -2
- {ethyca_fides-2.67.2b3.dist-info → ethyca_fides-2.67.2rc0.dist-info}/RECORD +112 -114
- fides/_version.py +3 -3
- fides/api/db/crud.py +41 -24
- fides/api/graph/traversal.py +1 -1
- fides/api/main.py +1 -2
- fides/api/models/detection_discovery/core.py +20 -33
- fides/api/models/manual_task/manual_task.py +6 -3
- fides/api/models/privacy_request/privacy_request.py +0 -78
- fides/api/service/privacy_request/dsr_package/dsr_report_builder.py +19 -3
- fides/api/task/conditional_dependencies/evaluator.py +19 -12
- fides/api/task/conditional_dependencies/schemas.py +0 -51
- fides/api/task/create_request_tasks.py +3 -5
- fides/api/task/filter_results.py +1 -1
- fides/api/task/manual/manual_task_address.py +2 -4
- fides/api/task/manual/manual_task_graph_task.py +83 -87
- fides/api/task/manual/manual_task_utils.py +201 -84
- fides/api/tasks/storage.py +0 -3
- fides/api/util/aws_util.py +13 -1
- fides/api/util/storage_util.py +0 -19
- fides/config/__init__.py +0 -4
- fides/config/config_proxy.py +0 -7
- fides/config/utils.py +0 -1
- fides/ui-build/static/admin/404.html +1 -1
- fides/ui-build/static/admin/_next/static/{4v0_8k2Uo2QWG7nSdQ6A3 → Y7C7V1jdXK7rWXDIdtD13}/_buildManifest.js +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations/{[id]-330475705adbd36f.js → [id]-e0a755c69081fffa.js} +1 -1
- 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/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/task/conditional_dependencies/operators.py +0 -150
- fides/config/privacy_center_settings.py +0 -17
- {ethyca_fides-2.67.2b3.dist-info → ethyca_fides-2.67.2rc0.dist-info}/WHEEL +0 -0
- {ethyca_fides-2.67.2b3.dist-info → ethyca_fides-2.67.2rc0.dist-info}/entry_points.txt +0 -0
- {ethyca_fides-2.67.2b3.dist-info → ethyca_fides-2.67.2rc0.dist-info}/licenses/LICENSE +0 -0
- {ethyca_fides-2.67.2b3.dist-info → ethyca_fides-2.67.2rc0.dist-info}/top_level.txt +0 -0
- /fides/ui-build/static/admin/_next/static/{4v0_8k2Uo2QWG7nSdQ6A3 → Y7C7V1jdXK7rWXDIdtD13}/_ssgManifest.js +0 -0
|
@@ -380,51 +380,38 @@ class StagedResourceAncestor(Base):
|
|
|
380
380
|
)
|
|
381
381
|
|
|
382
382
|
@classmethod
|
|
383
|
-
def
|
|
383
|
+
def create_staged_resource_ancestor_links(
|
|
384
384
|
cls,
|
|
385
385
|
db: Session,
|
|
386
|
-
|
|
387
|
-
|
|
386
|
+
resource_urn: str,
|
|
387
|
+
ancestor_urns: Set[str],
|
|
388
388
|
) -> None:
|
|
389
389
|
"""
|
|
390
|
-
Bulk inserts
|
|
391
|
-
based on the provided
|
|
390
|
+
Bulk inserts entries in the StagedResourceAncestor table
|
|
391
|
+
based on the provided resource URN and the set of its ancestor URNs.
|
|
392
392
|
|
|
393
393
|
We execute the bulk INSERT with the provided (synchronous) db session,
|
|
394
394
|
but the transaction is _not_ committed, so the caller must commit the transaction
|
|
395
395
|
to persist the changes.
|
|
396
|
-
|
|
397
|
-
Uses batching to handle large datasets without hitting PostgreSQL parameter limits.
|
|
398
|
-
|
|
399
|
-
Args:
|
|
400
|
-
db: Database session
|
|
401
|
-
ancestor_links: Dict mapping descendant URNs to sets of ancestor URNs
|
|
402
396
|
"""
|
|
403
|
-
|
|
404
|
-
"""
|
|
405
|
-
INSERT INTO stagedresourceancestor (id, ancestor_urn, descendant_urn)
|
|
406
|
-
VALUES ('srl_' || gen_random_uuid(), :ancestor_urn, :descendant_urn)
|
|
407
|
-
ON CONFLICT (ancestor_urn, descendant_urn) DO NOTHING;
|
|
408
|
-
"""
|
|
409
|
-
)
|
|
397
|
+
links_to_insert = []
|
|
410
398
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
for ancestor_urn in ancestor_urns:
|
|
416
|
-
current_batch.append(
|
|
417
|
-
{"ancestor_urn": ancestor_urn, "descendant_urn": descendant_urn}
|
|
418
|
-
)
|
|
399
|
+
for ancestor_urn in ancestor_urns:
|
|
400
|
+
links_to_insert.append(
|
|
401
|
+
{"ancestor_urn": ancestor_urn, "descendant_urn": resource_urn}
|
|
402
|
+
)
|
|
419
403
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
404
|
+
if links_to_insert:
|
|
405
|
+
# Using raw SQL for ON CONFLICT with parameters for safety
|
|
406
|
+
stmt_text = text(
|
|
407
|
+
"""
|
|
408
|
+
INSERT INTO stagedresourceancestor (id, ancestor_urn, descendant_urn)
|
|
409
|
+
VALUES ('srl_' || gen_random_uuid(), :ancestor_urn, :descendant_urn)
|
|
410
|
+
ON CONFLICT (ancestor_urn, descendant_urn) DO NOTHING;
|
|
411
|
+
"""
|
|
412
|
+
)
|
|
424
413
|
|
|
425
|
-
|
|
426
|
-
if current_batch:
|
|
427
|
-
db.execute(stmt_text, current_batch)
|
|
414
|
+
db.execute(stmt_text, links_to_insert)
|
|
428
415
|
|
|
429
416
|
|
|
430
417
|
class StagedResource(Base):
|
|
@@ -383,13 +383,15 @@ class ManualTaskInstance(Base):
|
|
|
383
383
|
|
|
384
384
|
@property
|
|
385
385
|
def incomplete_fields(self) -> list["ManualTaskConfigField"]:
|
|
386
|
-
"""Get all fields that
|
|
386
|
+
"""Get all fields that haven't been completed yet.
|
|
387
|
+
A field is considered incomplete if:
|
|
388
|
+
1. It's required and has no submission
|
|
387
389
|
Returns:
|
|
388
390
|
list[ManualTaskConfigField]: List of incomplete fields
|
|
389
391
|
"""
|
|
390
392
|
return [
|
|
391
393
|
field
|
|
392
|
-
for field in self.
|
|
394
|
+
for field in self.required_fields
|
|
393
395
|
if not self.get_submission_for_field(field.id)
|
|
394
396
|
]
|
|
395
397
|
|
|
@@ -399,7 +401,8 @@ class ManualTaskInstance(Base):
|
|
|
399
401
|
return [
|
|
400
402
|
field
|
|
401
403
|
for field in self.config.field_definitions
|
|
402
|
-
if
|
|
404
|
+
if field.field_metadata.get("required", False)
|
|
405
|
+
and self.get_submission_for_field(field.id)
|
|
403
406
|
]
|
|
404
407
|
|
|
405
408
|
def get_submission_for_field(
|
|
@@ -47,16 +47,8 @@ from fides.api.models.attachment import (
|
|
|
47
47
|
from fides.api.models.audit_log import AuditLog
|
|
48
48
|
from fides.api.models.client import ClientDetail
|
|
49
49
|
from fides.api.models.comment import Comment, CommentReference, CommentReferenceType
|
|
50
|
-
from fides.api.models.connectionconfig import ConnectionConfig
|
|
51
50
|
from fides.api.models.fides_user import FidesUser
|
|
52
51
|
from fides.api.models.field_types import EncryptedLargeDataDescriptor
|
|
53
|
-
from fides.api.models.manual_task import (
|
|
54
|
-
ManualTask,
|
|
55
|
-
ManualTaskConfig,
|
|
56
|
-
ManualTaskConfigurationType,
|
|
57
|
-
ManualTaskEntityType,
|
|
58
|
-
ManualTaskInstance,
|
|
59
|
-
)
|
|
60
52
|
from fides.api.models.manual_webhook import AccessManualWebhook
|
|
61
53
|
from fides.api.models.masking_secret import MaskingSecret
|
|
62
54
|
from fides.api.models.policy import (
|
|
@@ -208,14 +200,6 @@ class PrivacyRequest(
|
|
|
208
200
|
viewonly=True,
|
|
209
201
|
uselist=True,
|
|
210
202
|
)
|
|
211
|
-
manual_task_instances = relationship(
|
|
212
|
-
"ManualTaskInstance",
|
|
213
|
-
lazy="select",
|
|
214
|
-
passive_deletes="all",
|
|
215
|
-
primaryjoin="and_(ManualTaskInstance.entity_id==foreign(PrivacyRequest.id), "
|
|
216
|
-
"ManualTaskInstance.entity_type=='privacy_request')",
|
|
217
|
-
uselist=True,
|
|
218
|
-
)
|
|
219
203
|
property_id = Column(String, nullable=True)
|
|
220
204
|
|
|
221
205
|
cancel_reason = Column(String(200))
|
|
@@ -1191,68 +1175,6 @@ class PrivacyRequest(
|
|
|
1191
1175
|
db, manual_webhook_id, "erasure_manual_webhook"
|
|
1192
1176
|
)
|
|
1193
1177
|
|
|
1194
|
-
def create_manual_task_instances(
|
|
1195
|
-
self, db: Session, connection_configs_with_manual_tasks: list[ConnectionConfig]
|
|
1196
|
-
) -> list[ManualTaskInstance]:
|
|
1197
|
-
"""Create ManualTaskInstance entries for all active manual tasks relevant to a privacy request."""
|
|
1198
|
-
# Early return if no relevant policy rules
|
|
1199
|
-
policy_rules = {
|
|
1200
|
-
ActionType.access: bool(
|
|
1201
|
-
self.policy.get_rules_for_action(action_type=ActionType.access)
|
|
1202
|
-
),
|
|
1203
|
-
ActionType.erasure: bool(
|
|
1204
|
-
self.policy.get_rules_for_action(action_type=ActionType.erasure)
|
|
1205
|
-
),
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
if not any(policy_rules.values()):
|
|
1209
|
-
return []
|
|
1210
|
-
|
|
1211
|
-
# Build configuration types using list comprehension
|
|
1212
|
-
config_types = [
|
|
1213
|
-
(
|
|
1214
|
-
ManualTaskConfigurationType.access_privacy_request
|
|
1215
|
-
if action_type == ActionType.access
|
|
1216
|
-
else ManualTaskConfigurationType.erasure_privacy_request
|
|
1217
|
-
)
|
|
1218
|
-
for action_type, has_rules in policy_rules.items()
|
|
1219
|
-
if has_rules
|
|
1220
|
-
]
|
|
1221
|
-
|
|
1222
|
-
# Get all relevant manual tasks and configs in one query
|
|
1223
|
-
connection_config_ids = [cc.id for cc in connection_configs_with_manual_tasks]
|
|
1224
|
-
manual_tasks_with_configs = (
|
|
1225
|
-
db.query(ManualTask, ManualTaskConfig)
|
|
1226
|
-
.join(ManualTaskConfig, ManualTask.id == ManualTaskConfig.task_id)
|
|
1227
|
-
.filter(
|
|
1228
|
-
ManualTask.parent_entity_id.in_(connection_config_ids),
|
|
1229
|
-
ManualTask.parent_entity_type == "connection_config",
|
|
1230
|
-
ManualTaskConfig.is_current.is_(True),
|
|
1231
|
-
ManualTaskConfig.config_type.in_(config_types),
|
|
1232
|
-
)
|
|
1233
|
-
.all()
|
|
1234
|
-
)
|
|
1235
|
-
|
|
1236
|
-
# Get existing config IDs to avoid duplicates
|
|
1237
|
-
existing_config_ids = {
|
|
1238
|
-
instance.config_id for instance in self.manual_task_instances
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
|
-
# Create instances using list comprehension and filter out existing ones
|
|
1242
|
-
return [
|
|
1243
|
-
ManualTaskInstance.create(
|
|
1244
|
-
db=db,
|
|
1245
|
-
data={
|
|
1246
|
-
"entity_id": self.id,
|
|
1247
|
-
"entity_type": ManualTaskEntityType.privacy_request,
|
|
1248
|
-
"task_id": manual_task.id,
|
|
1249
|
-
"config_id": config.id,
|
|
1250
|
-
},
|
|
1251
|
-
)
|
|
1252
|
-
for manual_task, config in manual_tasks_with_configs
|
|
1253
|
-
if config.id not in existing_config_ids
|
|
1254
|
-
]
|
|
1255
|
-
|
|
1256
1178
|
def get_existing_request_task(
|
|
1257
1179
|
self,
|
|
1258
1180
|
db: Session,
|
|
@@ -13,7 +13,7 @@ from loguru import logger
|
|
|
13
13
|
|
|
14
14
|
from fides.api.models.privacy_request import PrivacyRequest
|
|
15
15
|
from fides.api.schemas.policy import ActionType
|
|
16
|
-
from fides.api.util.storage_util import StorageJSONEncoder
|
|
16
|
+
from fides.api.util.storage_util import StorageJSONEncoder
|
|
17
17
|
|
|
18
18
|
DSR_DIRECTORY = Path(__file__).parent.resolve()
|
|
19
19
|
|
|
@@ -204,7 +204,7 @@ class DsrReportBuilder:
|
|
|
204
204
|
|
|
205
205
|
file_size = attachment.get("file_size")
|
|
206
206
|
if isinstance(file_size, (int, float)):
|
|
207
|
-
file_size =
|
|
207
|
+
file_size = self._format_size(float(file_size))
|
|
208
208
|
else:
|
|
209
209
|
file_size = "Unknown"
|
|
210
210
|
|
|
@@ -321,6 +321,22 @@ class DsrReportBuilder:
|
|
|
321
321
|
|
|
322
322
|
return datasets
|
|
323
323
|
|
|
324
|
+
def _format_size(self, size_bytes: float) -> str:
|
|
325
|
+
"""
|
|
326
|
+
Format size in bytes to human readable format.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
size_bytes: Size in bytes
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
Formatted string with appropriate unit (B, KB, MB, GB)
|
|
333
|
+
"""
|
|
334
|
+
for unit in ["B", "KB", "MB", "GB"]:
|
|
335
|
+
if size_bytes < 1024.0:
|
|
336
|
+
return f"{size_bytes:.1f} {unit}"
|
|
337
|
+
size_bytes /= 1024.0
|
|
338
|
+
return f"{size_bytes:.1f} TB"
|
|
339
|
+
|
|
324
340
|
def generate(self) -> BytesIO:
|
|
325
341
|
"""
|
|
326
342
|
Processes the request and DSR data to build zip file containing the DSR report.
|
|
@@ -379,7 +395,7 @@ class DsrReportBuilder:
|
|
|
379
395
|
|
|
380
396
|
# Calculate time taken and file size
|
|
381
397
|
time_taken = time_module.time() - start_time
|
|
382
|
-
file_size =
|
|
398
|
+
file_size = self._format_size(float(len(self.baos.getvalue())))
|
|
383
399
|
|
|
384
400
|
logger.bind(time_to_generate=time_taken, dsr_package_size=file_size).info(
|
|
385
401
|
"DSR report generation complete."
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import operator as py_operator
|
|
1
2
|
from typing import Any, Union
|
|
2
3
|
|
|
3
4
|
from loguru import logger
|
|
4
5
|
from sqlalchemy.orm import Session
|
|
5
6
|
|
|
6
7
|
from fides.api.graph.config import FieldPath
|
|
7
|
-
from fides.api.task.conditional_dependencies.operators import operator_methods
|
|
8
8
|
from fides.api.task.conditional_dependencies.schemas import (
|
|
9
9
|
Condition,
|
|
10
10
|
ConditionGroup,
|
|
@@ -13,9 +13,18 @@ from fides.api.task.conditional_dependencies.schemas import (
|
|
|
13
13
|
Operator,
|
|
14
14
|
)
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
operator_methods = {
|
|
17
|
+
Operator.exists: lambda a, _: a is not None,
|
|
18
|
+
Operator.not_exists: lambda a, _: a is None,
|
|
19
|
+
Operator.eq: py_operator.eq,
|
|
20
|
+
Operator.neq: py_operator.ne,
|
|
21
|
+
Operator.lt: lambda a, b: a < b if a is not None else False,
|
|
22
|
+
Operator.lte: lambda a, b: a <= b if a is not None else False,
|
|
23
|
+
Operator.gt: lambda a, b: a > b if a is not None else False,
|
|
24
|
+
Operator.gte: lambda a, b: a >= b if a is not None else False,
|
|
25
|
+
Operator.list_contains: lambda a, b: b in a if isinstance(a, list) else False,
|
|
26
|
+
Operator.not_in_list: lambda a, b: a not in b if isinstance(b, list) else True,
|
|
27
|
+
}
|
|
19
28
|
|
|
20
29
|
|
|
21
30
|
class ConditionEvaluator:
|
|
@@ -35,9 +44,9 @@ class ConditionEvaluator:
|
|
|
35
44
|
self, condition: ConditionLeaf, data: Union[dict, Any]
|
|
36
45
|
) -> bool:
|
|
37
46
|
"""Evaluate a leaf condition against input data"""
|
|
38
|
-
|
|
47
|
+
actual_value = self._get_nested_value(data, condition.field_address.split("."))
|
|
39
48
|
# Apply operator and return result
|
|
40
|
-
return self._apply_operator(
|
|
49
|
+
return self._apply_operator(actual_value, condition.operator, condition.value)
|
|
41
50
|
|
|
42
51
|
def _evaluate_group_condition(
|
|
43
52
|
self, group: ConditionGroup, data: Union[dict, Any]
|
|
@@ -87,7 +96,7 @@ class ConditionEvaluator:
|
|
|
87
96
|
return current if current != {} else None
|
|
88
97
|
|
|
89
98
|
def _apply_operator(
|
|
90
|
-
self,
|
|
99
|
+
self, actual_value: Any, operator: Operator, expected_value: Any
|
|
91
100
|
) -> bool:
|
|
92
101
|
"""Apply operator to actual and expected values"""
|
|
93
102
|
|
|
@@ -95,8 +104,6 @@ class ConditionEvaluator:
|
|
|
95
104
|
operator_method = operator_methods.get(operator)
|
|
96
105
|
if operator_method is None:
|
|
97
106
|
logger.warning(f"Unknown operator: {operator}")
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
except (TypeError, ValueError) as e:
|
|
102
|
-
raise ConditionEvaluationError(f"Error evaluating condition: {e}") from e
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
return operator_method(actual_value, expected_value)
|
|
@@ -5,67 +5,16 @@ from pydantic import BaseModel, Field, model_validator
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class Operator(str, Enum):
|
|
8
|
-
# Basic comparison operators
|
|
9
|
-
# Column value equals user input (e.g., user.role eq "admin")
|
|
10
8
|
eq = "eq"
|
|
11
|
-
# Column value not equal to user input (e.g., user.status neq "inactive")
|
|
12
9
|
neq = "neq"
|
|
13
|
-
|
|
14
|
-
# Numeric comparison operators
|
|
15
|
-
# Column value less than user input (e.g., user.age lt 18)
|
|
16
10
|
lt = "lt"
|
|
17
|
-
# Column value less than or equal to user input (e.g., user.score lte 100)
|
|
18
11
|
lte = "lte"
|
|
19
|
-
# Column value greater than user input (e.g., user.balance gt 1000)
|
|
20
12
|
gt = "gt"
|
|
21
|
-
# Column value greater than or equal to user input (e.g., user.rating gte 4.0)
|
|
22
13
|
gte = "gte"
|
|
23
|
-
|
|
24
|
-
# Existence operators
|
|
25
|
-
# Field exists and is not None (e.g., user.email exists)
|
|
26
14
|
exists = "exists"
|
|
27
|
-
# Field does not exist or is None (e.g., user.middle_name not_exists)
|
|
28
15
|
not_exists = "not_exists"
|
|
29
|
-
|
|
30
|
-
# List membership operators (work with both single values and lists)
|
|
31
|
-
#
|
|
32
|
-
# Column value is in user's list OR user's value is in column's list
|
|
33
16
|
list_contains = "list_contains"
|
|
34
|
-
# Examples: user.role list_contains ["admin", "moderator"] (role in list)
|
|
35
|
-
# user.permissions list_contains "write" (value in permissions)
|
|
36
|
-
|
|
37
|
-
# Column value is NOT in user's list OR user's value is NOT in column's list
|
|
38
17
|
not_in_list = "not_in_list"
|
|
39
|
-
# Examples: user.role not_in_list ["banned", "suspended"] (role not blocked)
|
|
40
|
-
# user.permissions not_in_list "delete" (value not in permissions)
|
|
41
|
-
|
|
42
|
-
# List-to-list comparison operators (both values must be lists)
|
|
43
|
-
# Lists have at least one common element
|
|
44
|
-
list_intersects = "list_intersects"
|
|
45
|
-
# Example: user.roles list_intersects ["admin", "moderator"] (any common role)
|
|
46
|
-
|
|
47
|
-
# Column list is completely contained within user's list
|
|
48
|
-
list_subset = "list_subset"
|
|
49
|
-
# Example: user.permissions list_subset ["read", "write", "delete", "manage"]
|
|
50
|
-
# (all user permissions are allowed)
|
|
51
|
-
|
|
52
|
-
# Column list completely contains user's list
|
|
53
|
-
list_superset = "list_superset"
|
|
54
|
-
# Example: user.tags list_superset ["premium", "verified"]
|
|
55
|
-
# (user has all required tags plus extras)
|
|
56
|
-
|
|
57
|
-
# Lists have no common elements
|
|
58
|
-
list_disjoint = "list_disjoint"
|
|
59
|
-
# Example: user.roles list_disjoint ["banned", "suspended"]
|
|
60
|
-
# (user has no restricted roles)
|
|
61
|
-
|
|
62
|
-
# String operators
|
|
63
|
-
# String starts with user input (e.g., user.email starts_with "admin@")
|
|
64
|
-
starts_with = "starts_with"
|
|
65
|
-
# String ends with user input (e.g., user.domain ends_with ".com")
|
|
66
|
-
ends_with = "ends_with"
|
|
67
|
-
# String contains user input (e.g., user.description contains "verified")
|
|
68
|
-
contains = "contains"
|
|
69
18
|
|
|
70
19
|
|
|
71
20
|
class GroupOperator(str, Enum):
|
|
@@ -35,7 +35,7 @@ from fides.api.task.deprecated_graph_task import format_data_use_map_for_caching
|
|
|
35
35
|
from fides.api.task.execute_request_tasks import log_task_queued, queue_request_task
|
|
36
36
|
from fides.api.task.manual.manual_task_address import ManualTaskAddress
|
|
37
37
|
from fides.api.task.manual.manual_task_utils import (
|
|
38
|
-
|
|
38
|
+
create_manual_task_instances_for_privacy_request,
|
|
39
39
|
)
|
|
40
40
|
from fides.api.util.logger_context_utils import log_context
|
|
41
41
|
|
|
@@ -92,7 +92,7 @@ def build_access_networkx_digraph(
|
|
|
92
92
|
manual_nodes = [
|
|
93
93
|
addr
|
|
94
94
|
for addr in traversal_nodes.keys()
|
|
95
|
-
if ManualTaskAddress.
|
|
95
|
+
if addr.collection == ManualTaskAddress.MANUAL_DATA_COLLECTION
|
|
96
96
|
]
|
|
97
97
|
for manual_node in manual_nodes:
|
|
98
98
|
networkx_graph.add_edge(ROOT_COLLECTION_ADDRESS, manual_node)
|
|
@@ -472,9 +472,7 @@ def run_access_request(
|
|
|
472
472
|
)
|
|
473
473
|
|
|
474
474
|
# Snapshot manual task field instances for this privacy request
|
|
475
|
-
privacy_request
|
|
476
|
-
session, get_connection_configs_with_manual_tasks(session)
|
|
477
|
-
)
|
|
475
|
+
create_manual_task_instances_for_privacy_request(session, privacy_request)
|
|
478
476
|
|
|
479
477
|
# Save Access Request Tasks to the database
|
|
480
478
|
ready_tasks = persist_new_access_request_tasks(
|
fides/api/task/filter_results.py
CHANGED
|
@@ -39,7 +39,7 @@ def filter_data_categories(
|
|
|
39
39
|
continue
|
|
40
40
|
|
|
41
41
|
# Skip manual task data - it doesn't need filtering since it's controlled by field definitions
|
|
42
|
-
if ManualTaskAddress.
|
|
42
|
+
if f":{ManualTaskAddress.MANUAL_DATA_COLLECTION}" in node_address:
|
|
43
43
|
filtered_access_results[node_address].extend(results)
|
|
44
44
|
continue
|
|
45
45
|
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from typing import Union
|
|
2
|
-
|
|
3
1
|
from fides.api.graph.config import CollectionAddress
|
|
4
2
|
|
|
5
3
|
|
|
@@ -22,7 +20,7 @@ class ManualTaskAddress:
|
|
|
22
20
|
return collection_name == ManualTaskAddress.MANUAL_DATA_COLLECTION
|
|
23
21
|
|
|
24
22
|
@staticmethod
|
|
25
|
-
def is_manual_task_address(address:
|
|
23
|
+
def is_manual_task_address(address: CollectionAddress) -> bool:
|
|
26
24
|
"""Check if address represents manual task data"""
|
|
27
25
|
if isinstance(address, str):
|
|
28
26
|
# Handle string format "connection_key:collection_name"
|
|
@@ -35,7 +33,7 @@ class ManualTaskAddress:
|
|
|
35
33
|
return ManualTaskAddress._is_manual_data_collection(address.collection)
|
|
36
34
|
|
|
37
35
|
@staticmethod
|
|
38
|
-
def get_connection_key(address:
|
|
36
|
+
def get_connection_key(address: CollectionAddress) -> str:
|
|
39
37
|
"""Extract connection config key from manual task address"""
|
|
40
38
|
if not ManualTaskAddress.is_manual_task_address(address):
|
|
41
39
|
raise ValueError(f"Not a manual task address: {address}")
|