ethyca-fides 2.70.4b0__py2.py3-none-any.whl → 2.70.5b0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ethyca-fides might be problematic. Click here for more details.

Files changed (102) hide show
  1. {ethyca_fides-2.70.4b0.dist-info → ethyca_fides-2.70.5b0.dist-info}/METADATA +1 -1
  2. {ethyca_fides-2.70.4b0.dist-info → ethyca_fides-2.70.5b0.dist-info}/RECORD +102 -96
  3. fides/_version.py +3 -3
  4. fides/api/alembic/migrations/versions/9e0dcbf67b9f_add_digest_config.py +84 -0
  5. fides/api/db/base.py +1 -0
  6. fides/api/models/digest/__init__.py +10 -0
  7. fides/api/models/digest/conditional_dependencies.py +9 -0
  8. fides/api/models/digest/digest_config.py +76 -0
  9. fides/api/models/privacy_notice.py +25 -5
  10. fides/api/task/conditional_dependencies/sql_schemas.py +301 -0
  11. fides/api/task/conditional_dependencies/sql_translator.py +757 -0
  12. fides/ui-build/static/admin/404.html +1 -1
  13. fides/ui-build/static/admin/_next/static/chunks/pages/{_app-de41f80e35acbde0.js → _app-a9d053921092de16.js} +1 -1
  14. fides/ui-build/static/admin/add-systems/manual.html +1 -1
  15. fides/ui-build/static/admin/add-systems/multiple.html +1 -1
  16. fides/ui-build/static/admin/add-systems.html +1 -1
  17. fides/ui-build/static/admin/consent/configure/add-vendors.html +1 -1
  18. fides/ui-build/static/admin/consent/configure.html +1 -1
  19. fides/ui-build/static/admin/consent/privacy-experience/[id].html +1 -1
  20. fides/ui-build/static/admin/consent/privacy-experience/new.html +1 -1
  21. fides/ui-build/static/admin/consent/privacy-experience.html +1 -1
  22. fides/ui-build/static/admin/consent/privacy-notices/[id].html +1 -1
  23. fides/ui-build/static/admin/consent/privacy-notices/new.html +1 -1
  24. fides/ui-build/static/admin/consent/privacy-notices.html +1 -1
  25. fides/ui-build/static/admin/consent/properties.html +1 -1
  26. fides/ui-build/static/admin/consent/reporting.html +1 -1
  27. fides/ui-build/static/admin/consent.html +1 -1
  28. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn]/[resourceUrn].html +1 -1
  29. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn].html +1 -1
  30. fides/ui-build/static/admin/data-catalog/[systemId]/projects.html +1 -1
  31. fides/ui-build/static/admin/data-catalog/[systemId]/resources/[resourceUrn].html +1 -1
  32. fides/ui-build/static/admin/data-catalog/[systemId]/resources.html +1 -1
  33. fides/ui-build/static/admin/data-catalog.html +1 -1
  34. fides/ui-build/static/admin/data-discovery/action-center/[monitorId]/[systemId].html +1 -1
  35. fides/ui-build/static/admin/data-discovery/action-center/[monitorId].html +1 -1
  36. fides/ui-build/static/admin/data-discovery/action-center.html +1 -1
  37. fides/ui-build/static/admin/data-discovery/activity.html +1 -1
  38. fides/ui-build/static/admin/data-discovery/detection/[resourceUrn].html +1 -1
  39. fides/ui-build/static/admin/data-discovery/detection.html +1 -1
  40. fides/ui-build/static/admin/data-discovery/discovery/[resourceUrn].html +1 -1
  41. fides/ui-build/static/admin/data-discovery/discovery.html +1 -1
  42. fides/ui-build/static/admin/datamap.html +1 -1
  43. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName]/[...subfieldNames].html +1 -1
  44. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName].html +1 -1
  45. fides/ui-build/static/admin/dataset/[datasetId].html +1 -1
  46. fides/ui-build/static/admin/dataset/new.html +1 -1
  47. fides/ui-build/static/admin/dataset.html +1 -1
  48. fides/ui-build/static/admin/datastore-connection/[id].html +1 -1
  49. fides/ui-build/static/admin/datastore-connection/new.html +1 -1
  50. fides/ui-build/static/admin/datastore-connection.html +1 -1
  51. fides/ui-build/static/admin/index.html +1 -1
  52. fides/ui-build/static/admin/integrations/[id].html +1 -1
  53. fides/ui-build/static/admin/integrations.html +1 -1
  54. fides/ui-build/static/admin/login/[provider].html +1 -1
  55. fides/ui-build/static/admin/login.html +1 -1
  56. fides/ui-build/static/admin/messaging/[id].html +1 -1
  57. fides/ui-build/static/admin/messaging/add-template.html +1 -1
  58. fides/ui-build/static/admin/messaging.html +1 -1
  59. fides/ui-build/static/admin/poc/ant-components.html +1 -1
  60. fides/ui-build/static/admin/poc/form-experiments/AntForm.html +1 -1
  61. fides/ui-build/static/admin/poc/form-experiments/FormikAntFormItem.html +1 -1
  62. fides/ui-build/static/admin/poc/form-experiments/FormikControlled.html +1 -1
  63. fides/ui-build/static/admin/poc/form-experiments/FormikField.html +1 -1
  64. fides/ui-build/static/admin/poc/form-experiments/FormikSpreadField.html +1 -1
  65. fides/ui-build/static/admin/poc/forms.html +1 -1
  66. fides/ui-build/static/admin/poc/table-migration.html +1 -1
  67. fides/ui-build/static/admin/privacy-requests/[id].html +1 -1
  68. fides/ui-build/static/admin/privacy-requests/configure/storage.html +1 -1
  69. fides/ui-build/static/admin/privacy-requests/configure.html +1 -1
  70. fides/ui-build/static/admin/privacy-requests.html +1 -1
  71. fides/ui-build/static/admin/properties/[id].html +1 -1
  72. fides/ui-build/static/admin/properties/add-property.html +1 -1
  73. fides/ui-build/static/admin/properties.html +1 -1
  74. fides/ui-build/static/admin/reporting/datamap.html +1 -1
  75. fides/ui-build/static/admin/settings/about/alpha.html +1 -1
  76. fides/ui-build/static/admin/settings/about.html +1 -1
  77. fides/ui-build/static/admin/settings/consent/[configuration_id]/[purpose_id].html +1 -1
  78. fides/ui-build/static/admin/settings/consent.html +1 -1
  79. fides/ui-build/static/admin/settings/custom-fields.html +1 -1
  80. fides/ui-build/static/admin/settings/domain-records.html +1 -1
  81. fides/ui-build/static/admin/settings/domains.html +1 -1
  82. fides/ui-build/static/admin/settings/email-templates.html +1 -1
  83. fides/ui-build/static/admin/settings/locations.html +1 -1
  84. fides/ui-build/static/admin/settings/messaging-providers/[key].html +1 -1
  85. fides/ui-build/static/admin/settings/messaging-providers/new.html +1 -1
  86. fides/ui-build/static/admin/settings/messaging-providers.html +1 -1
  87. fides/ui-build/static/admin/settings/organization.html +1 -1
  88. fides/ui-build/static/admin/settings/privacy-requests.html +1 -1
  89. fides/ui-build/static/admin/settings/regulations.html +1 -1
  90. fides/ui-build/static/admin/systems/configure/[id]/test-datasets.html +1 -1
  91. fides/ui-build/static/admin/systems/configure/[id].html +1 -1
  92. fides/ui-build/static/admin/systems.html +1 -1
  93. fides/ui-build/static/admin/taxonomy.html +1 -1
  94. fides/ui-build/static/admin/user-management/new.html +1 -1
  95. fides/ui-build/static/admin/user-management/profile/[id].html +1 -1
  96. fides/ui-build/static/admin/user-management.html +1 -1
  97. {ethyca_fides-2.70.4b0.dist-info → ethyca_fides-2.70.5b0.dist-info}/WHEEL +0 -0
  98. {ethyca_fides-2.70.4b0.dist-info → ethyca_fides-2.70.5b0.dist-info}/entry_points.txt +0 -0
  99. {ethyca_fides-2.70.4b0.dist-info → ethyca_fides-2.70.5b0.dist-info}/licenses/LICENSE +0 -0
  100. {ethyca_fides-2.70.4b0.dist-info → ethyca_fides-2.70.5b0.dist-info}/top_level.txt +0 -0
  101. /fides/ui-build/static/admin/_next/static/{gAyR6aUQCeUTA_oMVYpBp → uFcubrjoFrTUGQFZXt15O}/_buildManifest.js +0 -0
  102. /fides/ui-build/static/admin/_next/static/{gAyR6aUQCeUTA_oMVYpBp → uFcubrjoFrTUGQFZXt15O}/_ssgManifest.js +0 -0
@@ -0,0 +1,301 @@
1
+ import types
2
+ import uuid
3
+ from typing import Any, Optional
4
+
5
+ from pydantic import BaseModel, Field
6
+ from sqlalchemy import Column, any_, bindparam
7
+
8
+ from fides.api.task.conditional_dependencies.schemas import Operator
9
+
10
+
11
+ class SQLTranslationError(Exception):
12
+ """Error raised when SQL translation fails"""
13
+
14
+
15
+ def _escape_like_pattern(val: Any) -> str:
16
+ """
17
+ Escape LIKE wildcards in user input to prevent pattern injection attacks.
18
+
19
+ This prevents users from injecting wildcards (% and _) that could:
20
+ - Match unintended records through pattern injection
21
+ - Cause performance issues with expensive wildcard operations
22
+ - Enable information disclosure through pattern probing
23
+
24
+ Note: This function escapes ALL % and _ characters as a security measure.
25
+ While this may seem aggressive for normal field names, it's necessary because:
26
+ 1. We can't distinguish between intentional and malicious wildcards
27
+ 2. Field values shouldn't typically contain SQL wildcards
28
+ 3. Better to be overly cautious with user input
29
+
30
+ Args:
31
+ val: User input value to escape
32
+
33
+ Returns:
34
+ Escaped string safe for use in LIKE patterns
35
+
36
+ Examples:
37
+ _escape_like_pattern("admin%") -> "admin\\%"
38
+ _escape_like_pattern("test_user") -> "test\\_user" # Escapes _ for security
39
+ _escape_like_pattern("normal") -> "normal"
40
+ """
41
+ if val is None:
42
+ return ""
43
+
44
+ val_str = str(val)
45
+ # Escape backslashes first to prevent double-escaping
46
+ # Then escape LIKE wildcards (% and _)
47
+ return val_str.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
48
+
49
+
50
+ def _validate_and_escape_json_path_component(component: str) -> str:
51
+ """
52
+ Validate and escape JSON path components to prevent injection attacks.
53
+
54
+ This prevents users from injecting malicious content in JSON path components that could:
55
+ - Break out of single quotes in PostgreSQL JSON operators
56
+ - Inject arbitrary SQL through malformed JSON paths
57
+ - Cause syntax errors or unexpected behavior
58
+
59
+ Args:
60
+ component: JSON path component to validate and escape
61
+
62
+ Returns:
63
+ Validated and escaped component safe for use in PostgreSQL JSON operators
64
+
65
+ Raises:
66
+ SQLTranslationError: If the component contains invalid characters
67
+
68
+ Examples:
69
+ _validate_and_escape_json_path_component("field") -> "field"
70
+ _validate_and_escape_json_path_component("field'name") -> "field''name"
71
+ _validate_and_escape_json_path_component("") -> raises SQLTranslationError
72
+ """
73
+ if not component or not isinstance(component, str):
74
+ raise SQLTranslationError(
75
+ f"Invalid JSON path component: '{component}'. Components must be non-empty strings."
76
+ )
77
+
78
+ # Check for potentially dangerous characters
79
+ if len(component) > 100: # Reasonable limit for JSON field names
80
+ raise SQLTranslationError(
81
+ f"JSON path component too long: '{component[:50]}...'. Maximum length is 100 characters."
82
+ )
83
+
84
+ # Escape single quotes for PostgreSQL JSON operators (double them)
85
+ escaped_component = component.replace("'", "''")
86
+
87
+ return escaped_component
88
+
89
+
90
+ def _handle_list_contains(col: Column, val: Any) -> Column:
91
+ """
92
+ Handle list_contains operator for different scenarios:
93
+ - If val is a list: check if column value is IN the list
94
+ - If val is a single value: check if column contains the value (for arrays/JSON) or use LIKE (for strings)
95
+
96
+ Args:
97
+ col: SQLAlchemy column
98
+ val: Value to compare against
99
+
100
+ Returns:
101
+ SQLAlchemy expression for the comparison
102
+ """
103
+ if isinstance(val, list):
104
+ # If value is a list, check if column value is IN the list
105
+ return col.in_(val)
106
+
107
+ # For single values, we need to handle different column types
108
+ # Check if this is a PostgreSQL array column
109
+ if hasattr(col.type, "item_type") or str(col.type).startswith("ARRAY"):
110
+ # This is a PostgreSQL array - use the ANY operator with proper parameter binding
111
+ # This is safer and prevents SQL injection
112
+ # Generate unique parameter name to avoid conflicts when multiple list operations exist in same query
113
+ unique_param_name = f"array_val_{uuid.uuid4().hex[:8]}"
114
+ param = bindparam(unique_param_name, val)
115
+ return param == any_(col)
116
+
117
+ # Try JSON containment for JSONB/JSON columns
118
+ try:
119
+ return col.contains(val)
120
+ except Exception:
121
+ # Fallback to LIKE for string columns with escaped wildcards
122
+ escaped_val = _escape_like_pattern(val)
123
+ return col.like(f"%{escaped_val}%", escape="\\")
124
+
125
+
126
+ OPERATOR_MAP = types.MappingProxyType(
127
+ {
128
+ Operator.eq: lambda col, val: col == val,
129
+ Operator.neq: lambda col, val: col != val,
130
+ Operator.lt: lambda col, val: col < val,
131
+ Operator.lte: lambda col, val: col <= val,
132
+ Operator.gt: lambda col, val: col > val,
133
+ Operator.gte: lambda col, val: col >= val,
134
+ Operator.contains: lambda col, val: col.like(
135
+ f"%{_escape_like_pattern(val)}%", escape="\\"
136
+ ),
137
+ Operator.starts_with: lambda col, val: col.like(
138
+ f"{_escape_like_pattern(val)}%", escape="\\"
139
+ ),
140
+ Operator.ends_with: lambda col, val: col.like(
141
+ f"%{_escape_like_pattern(val)}", escape="\\"
142
+ ),
143
+ Operator.exists: lambda col, val: col.isnot(None),
144
+ Operator.not_exists: lambda col, val: col.is_(None),
145
+ Operator.list_contains: _handle_list_contains,
146
+ Operator.not_in_list: lambda col, val: ~_handle_list_contains(col, val),
147
+ }
148
+ )
149
+
150
+
151
+ class FieldAddress(BaseModel):
152
+ """Parsed field address with table and column information"""
153
+
154
+ table_name: str = Field(description="Table name extracted from field address")
155
+ column_name: str = Field(description="Base column name without JSON path")
156
+ json_path: Optional[list[str]] = Field(
157
+ default=None, description="JSON path components if this is a JSON field"
158
+ )
159
+ full_address: str = Field(description="Original field address string")
160
+
161
+ def __hash__(self) -> int:
162
+ """Make FieldAddress hashable for use in sets"""
163
+ return hash(
164
+ (
165
+ self.table_name,
166
+ self.column_name,
167
+ tuple(self.json_path) if self.json_path else None,
168
+ )
169
+ )
170
+
171
+ def __eq__(self, other: Any) -> bool:
172
+ """Enable equality comparison for FieldAddress objects"""
173
+ if not isinstance(other, FieldAddress):
174
+ return False
175
+ return (
176
+ self.table_name == other.table_name
177
+ and self.column_name == other.column_name
178
+ and self.json_path == other.json_path
179
+ )
180
+
181
+ def to_sql_column(self, enable_json_operators: bool = True) -> str:
182
+ """
183
+ Convert field address to PostgreSQL column reference
184
+
185
+ Args:
186
+ enable_json_operators: Whether to use PostgreSQL JSON operators for JSON paths
187
+
188
+ Returns:
189
+ SQL column reference string
190
+ """
191
+ is_json_path = self.json_path is not None and len(self.json_path) > 0
192
+ if not is_json_path or not enable_json_operators:
193
+ return self.column_name
194
+
195
+ if self.json_path is None:
196
+ # This should never happen
197
+ raise SQLTranslationError(
198
+ "Field address internal error."
199
+ ) # pragma: no cover
200
+
201
+ # Build PostgreSQL JSON path: column->'path'->'path'->>'final_path'
202
+ # Validate and escape all components to prevent injection
203
+ if self.json_path and len(self.json_path) == 1:
204
+ # Simple case: column->>'field'
205
+ escaped_component = _validate_and_escape_json_path_component(
206
+ self.json_path[0]
207
+ )
208
+ return f"{self.column_name}->>'{escaped_component}'"
209
+
210
+ # Complex case: column->'field'->'field'->>'final_field'
211
+ path_parts = []
212
+ # All but last use -> operator
213
+ for part in self.json_path[:-1]:
214
+ escaped_part = _validate_and_escape_json_path_component(part)
215
+ path_parts.append(f"->'{escaped_part}'")
216
+ # Last part uses ->> operator (returns text)
217
+ escaped_final = _validate_and_escape_json_path_component(self.json_path[-1])
218
+ path_parts.append(f"->>'{escaped_final}'")
219
+
220
+ return f"{self.column_name}{''.join(path_parts)}"
221
+
222
+ @classmethod
223
+ def _parse_parts_to_components(
224
+ cls, parts: list[str]
225
+ ) -> tuple[str, str, Optional[list[str]]]:
226
+ """
227
+ Parse a list of parts into table_name, column_name, and json_path components.
228
+
229
+ Args:
230
+ parts: List of address parts (e.g., ["table", "column", "path1", "path2"])
231
+
232
+ Returns:
233
+ Tuple of (table_name, column_name, json_path)
234
+ """
235
+ if len(parts) < 2:
236
+ return "", parts[0] if parts else "", None
237
+
238
+ table_name = parts[0]
239
+ column_name = parts[1]
240
+ json_path = parts[2:] if len(parts) > 2 else None
241
+
242
+ return table_name, column_name, json_path
243
+
244
+ @classmethod
245
+ def parse(cls, field_address: str) -> "FieldAddress":
246
+ """
247
+ Parse a field address into components
248
+
249
+ Supports formats:
250
+ - "table.column" -> table="table", column="column"
251
+ - "table.json_column.path.subpath" -> table="table", column="json_column", json_path=["path", "subpath"]
252
+ - "table:column" -> table="table", column="column" (alternative format)
253
+ - "column" -> table="", column="column" (requires default table)
254
+ """
255
+ if ":" in field_address:
256
+ # Format: table:column or dataset:collection:field
257
+ # Handle colon-separated format first, then check for dots in the remaining parts
258
+ colon_parts = field_address.split(":")
259
+ if len(colon_parts) >= 2:
260
+ table_name = colon_parts[0]
261
+ remaining = ":".join(colon_parts[1:]) # Join remaining parts
262
+
263
+ # Check if the remaining part has dots (JSON path)
264
+ if "." in remaining:
265
+ # Mixed format: table:column.path1.path2
266
+ dot_parts = remaining.split(".")
267
+ parts = [table_name] + dot_parts
268
+ else:
269
+ # Pure colon format: table:column or table:column:path1:path2
270
+ parts = colon_parts
271
+
272
+ table_name, column_name, json_path = cls._parse_parts_to_components(
273
+ parts
274
+ )
275
+
276
+ return cls(
277
+ table_name=table_name,
278
+ column_name=column_name,
279
+ json_path=json_path,
280
+ full_address=field_address,
281
+ )
282
+
283
+ if "." in field_address:
284
+ # Format: table.column or table.json_column.path.subpath
285
+ parts = field_address.split(".")
286
+ table_name, column_name, json_path = cls._parse_parts_to_components(parts)
287
+
288
+ return cls(
289
+ table_name=table_name,
290
+ column_name=column_name,
291
+ json_path=json_path,
292
+ full_address=field_address,
293
+ )
294
+
295
+ # Simple column name without table
296
+ return cls(
297
+ table_name="", # Will need default table
298
+ column_name=field_address,
299
+ json_path=None,
300
+ full_address=field_address,
301
+ )