ethyca-fides 2.68.1b1__py2.py3-none-any.whl → 2.68.1b2__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 (116) hide show
  1. {ethyca_fides-2.68.1b1.dist-info → ethyca_fides-2.68.1b2.dist-info}/METADATA +1 -1
  2. {ethyca_fides-2.68.1b1.dist-info → ethyca_fides-2.68.1b2.dist-info}/RECORD +113 -105
  3. fides/_version.py +3 -3
  4. fides/api/alembic/migrations/versions/3baf42d251a6_add_generic_taxonomy_models.py +239 -0
  5. fides/api/api/v1/endpoints/generic_overrides.py +64 -167
  6. fides/api/db/base.py +6 -0
  7. fides/api/models/taxonomy.py +275 -0
  8. fides/api/service/deps.py +5 -0
  9. fides/api/service/privacy_request/request_service.py +6 -1
  10. fides/api/task/manual/manual_task_graph_task.py +11 -0
  11. fides/api/util/connection_type.py +68 -33
  12. fides/data/sample_project/docker-compose.yml +3 -3
  13. fides/service/taxonomy/__init__.py +0 -0
  14. fides/service/taxonomy/handlers/__init__.py +11 -0
  15. fides/service/taxonomy/handlers/base.py +42 -0
  16. fides/service/taxonomy/handlers/legacy_handler.py +95 -0
  17. fides/service/taxonomy/taxonomy_service.py +261 -0
  18. fides/service/taxonomy/utils.py +160 -0
  19. fides/ui-build/static/admin/404.html +1 -1
  20. fides/ui-build/static/admin/_next/static/chunks/pages/{_app-65723cd4b8fc36ac.js → _app-2c10f6b217b7978b.js} +1 -1
  21. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center-58827eb86516931f.js +1 -0
  22. fides/ui-build/static/admin/_next/static/chunks/pages/integrations/{[id]-766e57bcf38b5b1e.js → [id]-4e286a1e501a0c73.js} +1 -1
  23. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests-709bcb0bc6a5382d.js +1 -0
  24. fides/ui-build/static/admin/_next/static/css/a72179b1754aadd3.css +1 -0
  25. fides/ui-build/static/admin/_next/static/{tzF4yti8NslASlGnxnZ8m → qvk5eMANVfwYkdURE7fgG}/_buildManifest.js +1 -1
  26. fides/ui-build/static/admin/add-systems/manual.html +1 -1
  27. fides/ui-build/static/admin/add-systems/multiple.html +1 -1
  28. fides/ui-build/static/admin/add-systems.html +1 -1
  29. fides/ui-build/static/admin/consent/configure/add-vendors.html +1 -1
  30. fides/ui-build/static/admin/consent/configure.html +1 -1
  31. fides/ui-build/static/admin/consent/privacy-experience/[id].html +1 -1
  32. fides/ui-build/static/admin/consent/privacy-experience/new.html +1 -1
  33. fides/ui-build/static/admin/consent/privacy-experience.html +1 -1
  34. fides/ui-build/static/admin/consent/privacy-notices/[id].html +1 -1
  35. fides/ui-build/static/admin/consent/privacy-notices/new.html +1 -1
  36. fides/ui-build/static/admin/consent/privacy-notices.html +1 -1
  37. fides/ui-build/static/admin/consent/properties.html +1 -1
  38. fides/ui-build/static/admin/consent/reporting.html +1 -1
  39. fides/ui-build/static/admin/consent.html +1 -1
  40. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn]/[resourceUrn].html +1 -1
  41. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn].html +1 -1
  42. fides/ui-build/static/admin/data-catalog/[systemId]/projects.html +1 -1
  43. fides/ui-build/static/admin/data-catalog/[systemId]/resources/[resourceUrn].html +1 -1
  44. fides/ui-build/static/admin/data-catalog/[systemId]/resources.html +1 -1
  45. fides/ui-build/static/admin/data-catalog.html +1 -1
  46. fides/ui-build/static/admin/data-discovery/action-center/[monitorId]/[systemId].html +1 -1
  47. fides/ui-build/static/admin/data-discovery/action-center/[monitorId].html +1 -1
  48. fides/ui-build/static/admin/data-discovery/action-center.html +1 -1
  49. fides/ui-build/static/admin/data-discovery/activity.html +1 -1
  50. fides/ui-build/static/admin/data-discovery/detection/[resourceUrn].html +1 -1
  51. fides/ui-build/static/admin/data-discovery/detection.html +1 -1
  52. fides/ui-build/static/admin/data-discovery/discovery/[resourceUrn].html +1 -1
  53. fides/ui-build/static/admin/data-discovery/discovery.html +1 -1
  54. fides/ui-build/static/admin/datamap.html +1 -1
  55. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName]/[...subfieldNames].html +1 -1
  56. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName].html +1 -1
  57. fides/ui-build/static/admin/dataset/[datasetId].html +1 -1
  58. fides/ui-build/static/admin/dataset/new.html +1 -1
  59. fides/ui-build/static/admin/dataset.html +1 -1
  60. fides/ui-build/static/admin/datastore-connection/[id].html +1 -1
  61. fides/ui-build/static/admin/datastore-connection/new.html +1 -1
  62. fides/ui-build/static/admin/datastore-connection.html +1 -1
  63. fides/ui-build/static/admin/index.html +1 -1
  64. fides/ui-build/static/admin/integrations/[id].html +1 -1
  65. fides/ui-build/static/admin/integrations.html +1 -1
  66. fides/ui-build/static/admin/lib/fides-preview.js +1 -1
  67. fides/ui-build/static/admin/lib/fides-tcf.js +2 -2
  68. fides/ui-build/static/admin/lib/fides.js +1 -1
  69. fides/ui-build/static/admin/login/[provider].html +1 -1
  70. fides/ui-build/static/admin/login.html +1 -1
  71. fides/ui-build/static/admin/messaging/[id].html +1 -1
  72. fides/ui-build/static/admin/messaging/add-template.html +1 -1
  73. fides/ui-build/static/admin/messaging.html +1 -1
  74. fides/ui-build/static/admin/poc/ant-components.html +1 -1
  75. fides/ui-build/static/admin/poc/form-experiments/AntForm.html +1 -1
  76. fides/ui-build/static/admin/poc/form-experiments/FormikAntFormItem.html +1 -1
  77. fides/ui-build/static/admin/poc/form-experiments/FormikControlled.html +1 -1
  78. fides/ui-build/static/admin/poc/form-experiments/FormikField.html +1 -1
  79. fides/ui-build/static/admin/poc/form-experiments/FormikSpreadField.html +1 -1
  80. fides/ui-build/static/admin/poc/forms.html +1 -1
  81. fides/ui-build/static/admin/poc/table-migration.html +1 -1
  82. fides/ui-build/static/admin/privacy-requests/[id].html +1 -1
  83. fides/ui-build/static/admin/privacy-requests/configure/messaging.html +1 -1
  84. fides/ui-build/static/admin/privacy-requests/configure/storage.html +1 -1
  85. fides/ui-build/static/admin/privacy-requests/configure.html +1 -1
  86. fides/ui-build/static/admin/privacy-requests.html +1 -1
  87. fides/ui-build/static/admin/properties/[id].html +1 -1
  88. fides/ui-build/static/admin/properties/add-property.html +1 -1
  89. fides/ui-build/static/admin/properties.html +1 -1
  90. fides/ui-build/static/admin/reporting/datamap.html +1 -1
  91. fides/ui-build/static/admin/settings/about/alpha.html +1 -1
  92. fides/ui-build/static/admin/settings/about.html +1 -1
  93. fides/ui-build/static/admin/settings/consent/[configuration_id]/[purpose_id].html +1 -1
  94. fides/ui-build/static/admin/settings/consent.html +1 -1
  95. fides/ui-build/static/admin/settings/custom-fields.html +1 -1
  96. fides/ui-build/static/admin/settings/domain-records.html +1 -1
  97. fides/ui-build/static/admin/settings/domains.html +1 -1
  98. fides/ui-build/static/admin/settings/email-templates.html +1 -1
  99. fides/ui-build/static/admin/settings/locations.html +1 -1
  100. fides/ui-build/static/admin/settings/organization.html +1 -1
  101. fides/ui-build/static/admin/settings/regulations.html +1 -1
  102. fides/ui-build/static/admin/systems/configure/[id]/test-datasets.html +1 -1
  103. fides/ui-build/static/admin/systems/configure/[id].html +1 -1
  104. fides/ui-build/static/admin/systems.html +1 -1
  105. fides/ui-build/static/admin/taxonomy.html +1 -1
  106. fides/ui-build/static/admin/user-management/new.html +1 -1
  107. fides/ui-build/static/admin/user-management/profile/[id].html +1 -1
  108. fides/ui-build/static/admin/user-management.html +1 -1
  109. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center-53a763e49ce34a74.js +0 -1
  110. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests-f43a988542813110.js +0 -1
  111. fides/ui-build/static/admin/_next/static/css/e1628f15dd5f019b.css +0 -1
  112. {ethyca_fides-2.68.1b1.dist-info → ethyca_fides-2.68.1b2.dist-info}/WHEEL +0 -0
  113. {ethyca_fides-2.68.1b1.dist-info → ethyca_fides-2.68.1b2.dist-info}/entry_points.txt +0 -0
  114. {ethyca_fides-2.68.1b1.dist-info → ethyca_fides-2.68.1b2.dist-info}/licenses/LICENSE +0 -0
  115. {ethyca_fides-2.68.1b1.dist-info → ethyca_fides-2.68.1b2.dist-info}/top_level.txt +0 -0
  116. /fides/ui-build/static/admin/_next/static/{tzF4yti8NslASlGnxnZ8m → qvk5eMANVfwYkdURE7fgG}/_ssgManifest.js +0 -0
@@ -0,0 +1,275 @@
1
+ # pylint: disable=protected-access
2
+ from __future__ import annotations
3
+
4
+ from enum import Enum
5
+ from typing import Any, Dict, List, Optional, Type
6
+
7
+ from sqlalchemy import (
8
+ BOOLEAN,
9
+ Column,
10
+ ForeignKey,
11
+ ForeignKeyConstraint,
12
+ String,
13
+ Text,
14
+ UniqueConstraint,
15
+ )
16
+ from sqlalchemy.ext.associationproxy import association_proxy
17
+ from sqlalchemy.ext.declarative import declared_attr
18
+ from sqlalchemy.orm import RelationshipProperty, Session, relationship
19
+
20
+ from fides.api.common_exceptions import ValidationError
21
+ from fides.api.db.base_class import Base
22
+ from fides.api.models.sql_models import FidesBase # type: ignore[attr-defined]
23
+
24
+ LEGACY_TAXONOMIES = {"data_categories", "data_uses", "data_subjects"}
25
+
26
+
27
+ class TargetType(str, Enum):
28
+ """Enumeration of target types that taxonomies can apply to."""
29
+
30
+ SYSTEM = "system"
31
+ PRIVACY_DECLARATION = "privacy_declaration"
32
+ TAXONOMY = "taxonomy" # For taxonomy-to-taxonomy relationships
33
+
34
+
35
+ class Taxonomy(Base, FidesBase):
36
+ """The SQL model for taxonomy resources.
37
+
38
+ This is a generic taxonomy model that can be used to create any taxonomy.
39
+ For now we seed the database with the legacy taxonomies (data_category, data use, data subject)
40
+ so that these legacy taxonomy types can be used for allowed usage relationships.
41
+ """
42
+
43
+ # Overriding the id definition from Base so we don't treat this as the primary key
44
+ id = Column(
45
+ String(255),
46
+ nullable=False,
47
+ index=False,
48
+ unique=True,
49
+ default=FidesBase.generate_uuid,
50
+ )
51
+
52
+ # The fides_key is inherited from FidesBase and acts as the primary key
53
+
54
+ # This is private to encourage the use of applies_to (see comment below)
55
+ _allowed_usages: RelationshipProperty[List[TaxonomyAllowedUsage]] = relationship(
56
+ "TaxonomyAllowedUsage",
57
+ back_populates="source_taxonomy",
58
+ cascade="all, delete-orphan",
59
+ lazy="selectin",
60
+ )
61
+
62
+ # Association proxy to simplify access to target_type values
63
+ # This allows getting a list of strings while the actual storage
64
+ # is handled through the TaxonomyAllowedUsage model
65
+ # Updates should be done through the create/update methods
66
+ applies_to: List[str] = association_proxy(
67
+ "_allowed_usages",
68
+ "target_type",
69
+ # Allow setting via strings, the relationship backref will set FK
70
+ creator=lambda target_type: TaxonomyAllowedUsage(target_type=target_type),
71
+ )
72
+
73
+ @classmethod
74
+ def create(
75
+ cls: Type["Taxonomy"],
76
+ db: Session,
77
+ *,
78
+ data: Dict[str, Any],
79
+ check_name: bool = True,
80
+ ) -> "Taxonomy":
81
+ """Create a new Taxonomy with proper handling of applies_to."""
82
+ # Disallow creating taxonomies that represent legacy types
83
+ fides_key = data.get("fides_key")
84
+ if fides_key in LEGACY_TAXONOMIES:
85
+ raise ValidationError(
86
+ f"Cannot create taxonomy '{fides_key}'. This is a taxonomy managed by the system."
87
+ )
88
+ applies_to = data.pop("applies_to", [])
89
+
90
+ # Create the taxonomy
91
+ taxonomy: Taxonomy = super().create(db=db, data=data, check_name=check_name)
92
+
93
+ # Reconcile allowed usages if applies_to was provided
94
+ if applies_to:
95
+ taxonomy._reconcile_allowed_usages(db, applies_to)
96
+
97
+ return cls.persist_obj(db, taxonomy)
98
+
99
+ def update(self, db: Session, *, data: Dict[str, Any]) -> "Taxonomy":
100
+ """Update a Taxonomy with proper handling of applies_to."""
101
+ applies_to = data.pop("applies_to", None)
102
+
103
+ # Update the base fields
104
+ super().update(db=db, data=data)
105
+
106
+ # If applies_to was provided, reconcile allowed usages
107
+ if applies_to is not None:
108
+ self._reconcile_allowed_usages(db, applies_to)
109
+
110
+ return self
111
+
112
+ def save(self, db: Session) -> "Taxonomy":
113
+ """Override save to reconcile any direct `applies_to` edits before persisting.
114
+
115
+ This allows callers to mutate `applies_to` via the association proxy and then call save.
116
+ """
117
+ # Ensure no duplicate target types and reconcile relationship objects to the current values
118
+ self._reconcile_allowed_usages(db, list(self.applies_to))
119
+ return super().save(db) # type: ignore[return-value]
120
+
121
+ def _reconcile_allowed_usages(self, db: Session, applies_to: List[str]) -> None:
122
+ """Ensure `_allowed_usages` matches the provided `applies_to` list.
123
+
124
+ - Deletes usages not in the provided list
125
+ - Creates usages missing from the relationship
126
+ - Deduplicates by target_type
127
+ """
128
+ existing_usages = {usage.target_type: usage for usage in self._allowed_usages}
129
+ desired_types = set(applies_to)
130
+
131
+ # Delete usages that should no longer exist
132
+ for target_type in set(existing_usages.keys()) - desired_types:
133
+ # Remove from relationship; delete-orphan cascade will handle DB delete
134
+ self._allowed_usages.remove(existing_usages[target_type])
135
+
136
+ # Add missing usages
137
+ for target_type in desired_types - set(existing_usages.keys()):
138
+ self._allowed_usages.append(TaxonomyAllowedUsage(target_type=target_type))
139
+
140
+ # Deduplicate any accidental duplicates in-memory
141
+ seen: set[str] = set()
142
+ for usage in list(self._allowed_usages):
143
+ if usage.target_type in seen:
144
+ # Remove duplicate from relationship; delete-orphan will handle DB
145
+ self._allowed_usages.remove(usage)
146
+ else:
147
+ seen.add(usage.target_type)
148
+
149
+
150
+ class TaxonomyAllowedUsage(Base):
151
+ """
152
+ The SQL model for taxonomy allowed usage.
153
+ Defines what types of targets a taxonomy can be applied to.
154
+
155
+ target_type can be either:
156
+ - A generic type: "system", "privacy_declaration", "taxonomy"
157
+ - A taxonomy key: "data_categories", "data_uses", etc.
158
+ """
159
+
160
+ @declared_attr
161
+ def __tablename__(self) -> str:
162
+ return "taxonomy_allowed_usage"
163
+
164
+ id = Column(
165
+ String(255),
166
+ nullable=False,
167
+ index=False,
168
+ unique=True,
169
+ default=FidesBase.generate_uuid,
170
+ )
171
+
172
+ source_taxonomy: RelationshipProperty[Taxonomy] = relationship(
173
+ "Taxonomy",
174
+ back_populates="_allowed_usages",
175
+ )
176
+
177
+ source_taxonomy_key: Column[str] = Column(
178
+ String,
179
+ ForeignKey("taxonomy.fides_key", ondelete="CASCADE"),
180
+ primary_key=True,
181
+ )
182
+ target_type: Column[str] = Column(
183
+ String, primary_key=True
184
+ ) # Can be "system", "dataset", OR a taxonomy key like "data_categories"
185
+
186
+
187
+ class TaxonomyElement(Base, FidesBase):
188
+ """
189
+ The SQL model for taxonomy elements.
190
+
191
+ This is a generic taxonomy element model that can be used to create any taxonomy element.
192
+
193
+ As of now the legacy taxonomy elements still exist in their own tables (ctl_data_categories, ctl_data_uses, ctl_data_subjects),
194
+ but we can migrate them to this model in the future if needed.
195
+ """
196
+
197
+ @declared_attr
198
+ def __tablename__(self) -> str:
199
+ return "taxonomy_element"
200
+
201
+ id = Column(
202
+ String(255),
203
+ nullable=False,
204
+ index=False,
205
+ unique=True,
206
+ default=FidesBase.generate_uuid,
207
+ )
208
+
209
+ # Which taxonomy this element belongs to
210
+ taxonomy_type = Column(
211
+ String,
212
+ ForeignKey("taxonomy.fides_key", ondelete="CASCADE"),
213
+ nullable=False,
214
+ index=True,
215
+ )
216
+
217
+ parent_key = Column(
218
+ Text, ForeignKey("taxonomy_element.fides_key", ondelete="RESTRICT"), index=True
219
+ )
220
+ active = Column(BOOLEAN, default=True, nullable=False, index=True)
221
+
222
+ children: RelationshipProperty[List[TaxonomyElement]] = relationship(
223
+ "TaxonomyElement",
224
+ back_populates="parent",
225
+ cascade="save-update, merge, refresh-expire", # intentionally do not cascade deletes
226
+ passive_deletes="all",
227
+ )
228
+
229
+ parent: RelationshipProperty[Optional[TaxonomyElement]] = relationship(
230
+ "TaxonomyElement",
231
+ back_populates="children",
232
+ remote_side="TaxonomyElement.fides_key",
233
+ )
234
+
235
+
236
+ class TaxonomyUsage(Base):
237
+ """
238
+ The SQL model for taxonomy usage.
239
+ Tracks the application of taxonomy elements to other taxonomy elements.
240
+
241
+ Example: Applying a "high" tag (from sensitivity taxonomy) to "user.contact.email" (from data_categories taxonomy).
242
+ """
243
+
244
+ @declared_attr
245
+ def __tablename__(self) -> str:
246
+ return "taxonomy_usage"
247
+
248
+ # The taxonomy element being applied (e.g., risk)
249
+ source_element_key = Column(String, nullable=False, index=True)
250
+
251
+ # The taxonomy element it's being applied to (e.g., a data category)
252
+ target_element_key = Column(String, nullable=False, index=True)
253
+
254
+ # Denormalized taxonomy types for validation and performance
255
+ source_taxonomy = Column(String, nullable=False, index=True)
256
+ target_taxonomy = Column(String, nullable=False, index=True)
257
+
258
+ __table_args__ = (
259
+ # Validate that this type of usage is allowed
260
+ ForeignKeyConstraint(
261
+ ["source_taxonomy", "target_taxonomy"],
262
+ [
263
+ "taxonomy_allowed_usage.source_taxonomy_key",
264
+ "taxonomy_allowed_usage.target_type",
265
+ ],
266
+ ondelete="RESTRICT",
267
+ name="fk_taxonomy_usage_allowed",
268
+ ),
269
+ # Prevent duplicate applications
270
+ UniqueConstraint(
271
+ "source_element_key",
272
+ "target_element_key",
273
+ name="uq_taxonomy_usage",
274
+ ),
275
+ )
fides/api/service/deps.py CHANGED
@@ -8,6 +8,7 @@ from fides.service.dataset.dataset_config_service import DatasetConfigService
8
8
  from fides.service.dataset.dataset_service import DatasetService
9
9
  from fides.service.messaging.messaging_service import MessagingService
10
10
  from fides.service.privacy_request.privacy_request_service import PrivacyRequestService
11
+ from fides.service.taxonomy.taxonomy_service import TaxonomyService
11
12
  from fides.service.user.user_service import UserService
12
13
 
13
14
 
@@ -41,3 +42,7 @@ def get_user_service(
41
42
  config_proxy: ConfigProxy = Depends(get_config_proxy),
42
43
  ) -> UserService:
43
44
  return UserService(db, config, config_proxy)
45
+
46
+
47
+ def get_taxonomy_service(db: Session = Depends(get_db)) -> TaxonomyService:
48
+ return TaxonomyService(db)
@@ -191,7 +191,11 @@ def poll_for_exited_privacy_request_tasks(self: DatabaseTask) -> Set[str]:
191
191
  db.query(PrivacyRequest)
192
192
  .filter(
193
193
  PrivacyRequest.status.in_(
194
- [PrivacyRequestStatus.in_processing, PrivacyRequestStatus.approved]
194
+ [
195
+ PrivacyRequestStatus.in_processing,
196
+ PrivacyRequestStatus.approved,
197
+ PrivacyRequestStatus.requires_input,
198
+ ]
195
199
  )
196
200
  )
197
201
  # Only look at Privacy Requests that haven't been deleted
@@ -546,6 +550,7 @@ def requeue_interrupted_tasks(self: DatabaseTask) -> None:
546
550
  [
547
551
  PrivacyRequestStatus.in_processing,
548
552
  PrivacyRequestStatus.approved,
553
+ PrivacyRequestStatus.requires_input,
549
554
  ]
550
555
  )
551
556
  )
@@ -63,6 +63,17 @@ class ManualTaskGraphTask(GraphTask):
63
63
  Calls _run_request with ACCESS configs.
64
64
  Returns data if submitted, raise AwaitingAsyncTaskCallback if not
65
65
  """
66
+ if self.resources.request.policy.get_action_type() == ActionType.erasure:
67
+ # We're in an erasure privacy request's access phase - complete access task immediately
68
+ # since access is just for data collection to support erasure, not for user data access
69
+ self.update_status(
70
+ "Access task completed immediately for erasure privacy request (data collection only)",
71
+ [],
72
+ ActionType.access,
73
+ ExecutionLogStatus.complete,
74
+ )
75
+ return []
76
+
66
77
  result = self._run_request(
67
78
  ManualTaskConfigurationType.access_privacy_request,
68
79
  ActionType.access,
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Any, Dict, Set
3
+ from typing import Any, Dict, Optional, Set
4
4
 
5
5
  from fides.api.common_exceptions import NoSuchConnectionTypeSecretSchemaError
6
6
  from fides.api.models.connectionconfig import ConnectionType
@@ -141,7 +141,9 @@ def get_connection_type_secret_schema(*, connection_type: str) -> dict[str, Any]
141
141
  Note that this does not return actual secrets, instead we return the *types* of
142
142
  secret fields needed to authenticate.
143
143
  """
144
- connection_system_types: list[ConnectionSystemTypeMap] = get_connection_types()
144
+ connection_system_types: list[ConnectionSystemTypeMap] = get_connection_types(
145
+ include_test_connections=True
146
+ )
145
147
  if not any(item.identifier == connection_type for item in connection_system_types):
146
148
  raise NoSuchConnectionTypeSecretSchemaError(
147
149
  f"No connection type found with name '{connection_type}'."
@@ -189,15 +191,15 @@ def get_connection_type_secret_schema(*, connection_type: str) -> dict[str, Any]
189
191
  return schema
190
192
 
191
193
 
192
- def get_connection_types(
193
- search: str | None = None,
194
- system_type: SystemType | None = None,
194
+ def _is_match(elem: str, search: Optional[str] = None) -> bool:
195
+ """If a search query param was included, is it a substring of an available connector type?"""
196
+ return search.lower() in elem.lower() if search else True
197
+
198
+
199
+ def get_saas_connection_types(
195
200
  action_types: Set[ActionType] = SUPPORTED_ACTION_TYPES,
201
+ search: Optional[str] = None,
196
202
  ) -> list[ConnectionSystemTypeMap]:
197
- def is_match(elem: str) -> bool:
198
- """If a search query param was included, is it a substring of an available connector type?"""
199
- return search.lower() in elem.lower() if search else True
200
-
201
203
  def saas_request_type_filter(connection_type: str) -> bool:
202
204
  """
203
205
  If any of the request type filters are set to true,
@@ -216,6 +218,45 @@ def get_connection_types(
216
218
  action_type in template.supported_actions for action_type in action_types
217
219
  )
218
220
 
221
+ saas_connection_types: list[ConnectionSystemTypeMap] = []
222
+ saas_types: list[str] = sorted(
223
+ [
224
+ saas_type
225
+ for saas_type in ConnectorRegistry.connector_types()
226
+ if _is_match(saas_type, search) and saas_request_type_filter(saas_type)
227
+ ]
228
+ )
229
+
230
+ for item in saas_types:
231
+ connector_template = ConnectorRegistry.get_connector_template(item)
232
+ if connector_template is not None:
233
+ saas_connection_types.append(
234
+ ConnectionSystemTypeMap(
235
+ identifier=item,
236
+ type=SystemType.saas,
237
+ human_readable=connector_template.human_readable,
238
+ encoded_icon=connector_template.icon,
239
+ authorization_required=connector_template.authorization_required,
240
+ user_guide=connector_template.user_guide,
241
+ supported_actions=connector_template.supported_actions,
242
+ )
243
+ )
244
+
245
+ return saas_connection_types
246
+
247
+
248
+ # FIXME: this function needs a refactor
249
+ def get_connection_types(
250
+ search: str | None = None,
251
+ system_type: SystemType | None = None,
252
+ action_types: Set[ActionType] = SUPPORTED_ACTION_TYPES,
253
+ include_test_connections: bool = False,
254
+ ) -> list[ConnectionSystemTypeMap]:
255
+ """
256
+ Returns a list of ConnectionSystemTypeMap objects that match the given search and system type.
257
+
258
+ If include_test_connections is True, test connections like test_website will be included in the response.
259
+ """
219
260
  connection_system_types: list[ConnectionSystemTypeMap] = []
220
261
  if (system_type == SystemType.database or system_type is None) and (
221
262
  ActionType.access in action_types or ActionType.erasure in action_types
@@ -238,7 +279,7 @@ def get_connection_types(
238
279
  ConnectionType.sovrn,
239
280
  ConnectionType.test_website,
240
281
  ]
241
- and is_match(conn_type.value)
282
+ and _is_match(conn_type.value, search)
242
283
  ],
243
284
  key=lambda x: x.value,
244
285
  )
@@ -254,28 +295,10 @@ def get_connection_types(
254
295
  ]
255
296
  )
256
297
  if system_type == SystemType.saas or system_type is None:
257
- saas_types: list[str] = sorted(
258
- [
259
- saas_type
260
- for saas_type in ConnectorRegistry.connector_types()
261
- if is_match(saas_type) and saas_request_type_filter(saas_type)
262
- ]
298
+ saas_connection_types = get_saas_connection_types(
299
+ action_types=action_types, search=search
263
300
  )
264
-
265
- for item in saas_types:
266
- connector_template = ConnectorRegistry.get_connector_template(item)
267
- if connector_template is not None:
268
- connection_system_types.append(
269
- ConnectionSystemTypeMap(
270
- identifier=item,
271
- type=SystemType.saas,
272
- human_readable=connector_template.human_readable,
273
- encoded_icon=connector_template.icon,
274
- authorization_required=connector_template.authorization_required,
275
- user_guide=connector_template.user_guide,
276
- supported_actions=connector_template.supported_actions,
277
- )
278
- )
301
+ connection_system_types.extend(saas_connection_types)
279
302
 
280
303
  if (system_type == SystemType.manual or system_type is None) and (
281
304
  ActionType.access in action_types or ActionType.erasure in action_types
@@ -285,7 +308,7 @@ def get_connection_types(
285
308
  manual_type.value
286
309
  for manual_type in ConnectionType
287
310
  if manual_type == ConnectionType.manual_webhook
288
- and is_match(manual_type.value)
311
+ and _is_match(manual_type.value, search)
289
312
  ]
290
313
  )
291
314
  connection_system_types.extend(
@@ -307,7 +330,7 @@ def get_connection_types(
307
330
  for email_type in ConnectionType
308
331
  if email_type
309
332
  in ERASURE_EMAIL_CONNECTOR_TYPES + CONSENT_EMAIL_CONNECTOR_TYPES
310
- and is_match(email_type.value)
333
+ and _is_match(email_type.value, search)
311
334
  and ( # include consent or erasure connectors if requested, respectively
312
335
  (
313
336
  ActionType.consent in action_types
@@ -339,4 +362,16 @@ def get_connection_types(
339
362
  ]
340
363
  )
341
364
 
365
+ if include_test_connections and (
366
+ system_type == SystemType.website or system_type is None
367
+ ):
368
+ connection_system_types.append(
369
+ ConnectionSystemTypeMap(
370
+ identifier=ConnectionType.test_website.value,
371
+ type=SystemType.website,
372
+ human_readable=ConnectionType.test_website.human_readable,
373
+ supported_actions=[],
374
+ )
375
+ )
376
+
342
377
  return connection_system_types
@@ -87,7 +87,7 @@ services:
87
87
 
88
88
  fides-db:
89
89
  container_name: fides-db
90
- image: postgres:12
90
+ image: postgres:16
91
91
  healthcheck:
92
92
  test: ["CMD-SHELL", "pg_isready -U postgres"]
93
93
  interval: 15s
@@ -104,14 +104,14 @@ services:
104
104
 
105
105
  redis:
106
106
  container_name: fides-redis
107
- image: redis:6.2-alpine
107
+ image: redis:8.0-alpine
108
108
  command: redis-server --requirepass redispassword
109
109
  ports:
110
110
  - "7379:6379"
111
111
 
112
112
  postgres-test:
113
113
  container_name: fides-postgres-example-db
114
- image: postgres:12
114
+ image: postgres:16
115
115
  healthcheck:
116
116
  test: ["CMD-SHELL", "pg_isready -U postgres"]
117
117
  interval: 15s
File without changes
@@ -0,0 +1,11 @@
1
+ """
2
+ Taxonomy handlers package.
3
+ """
4
+
5
+ from .base import TaxonomyHandler
6
+ from .legacy_handler import LegacyTaxonomyHandler
7
+
8
+ __all__ = [
9
+ "TaxonomyHandler",
10
+ "LegacyTaxonomyHandler",
11
+ ]
@@ -0,0 +1,42 @@
1
+ """
2
+ Base taxonomy handler and core validation functions.
3
+ """
4
+
5
+ from abc import ABC, abstractmethod
6
+ from typing import Any, Dict, List, Optional, Type
7
+
8
+ from sqlalchemy.orm import Session
9
+
10
+ from fides.api.db.base_class import FidesBase
11
+
12
+
13
+ class TaxonomyHandler(ABC):
14
+ """Abstract handler for taxonomy CRUD operations."""
15
+
16
+ def __init__(self, db: Session, taxonomy_type: str):
17
+ self.db = db
18
+ self.taxonomy_type = taxonomy_type
19
+
20
+ @abstractmethod
21
+ def get_model(self) -> Type[FidesBase]:
22
+ pass
23
+
24
+ @abstractmethod
25
+ def get_elements(self, active_only: bool, parent_key: Optional[str]) -> List[Any]:
26
+ pass
27
+
28
+ @abstractmethod
29
+ def get_element(self, fides_key: str) -> Optional[Any]:
30
+ pass
31
+
32
+ @abstractmethod
33
+ def create_element(self, element_data: Dict) -> Any:
34
+ pass
35
+
36
+ @abstractmethod
37
+ def update_element(self, fides_key: str, element_data: Dict) -> Optional[Any]:
38
+ pass
39
+
40
+ @abstractmethod
41
+ def delete_element(self, fides_key: str) -> None:
42
+ pass
@@ -0,0 +1,95 @@
1
+ """
2
+ Legacy taxonomy handler for DataCategory, DataUse, and DataSubject models.
3
+ """
4
+
5
+ from typing import Dict, List, Optional, Type, Union
6
+
7
+ from sqlalchemy.orm import Query, Session
8
+
9
+ from fides.api.models.sql_models import ( # type:ignore[attr-defined]
10
+ DataCategory,
11
+ DataSubject,
12
+ DataUse,
13
+ )
14
+
15
+ from .base import TaxonomyHandler
16
+
17
+
18
+ class LegacyTaxonomyHandler(TaxonomyHandler):
19
+ """Handler for legacy taxonomy models (DataCategory, DataUse, DataSubject)."""
20
+
21
+ def __init__(self, db: Session, taxonomy_type: str) -> None:
22
+ super().__init__(db, taxonomy_type)
23
+ self.models = {
24
+ "data_categories": DataCategory,
25
+ "data_uses": DataUse,
26
+ "data_subjects": DataSubject,
27
+ }
28
+ if taxonomy_type not in self.models:
29
+ raise ValueError(
30
+ f"Taxonomy type '{taxonomy_type}' not supported. Supported types: {list(self.models.keys())}"
31
+ )
32
+
33
+ def get_elements(
34
+ self, active_only: bool, parent_key: Optional[str]
35
+ ) -> List[Union[DataCategory, DataUse, DataSubject]]:
36
+ model_class = self.get_model()
37
+ query = self._build_query(model_class, active_only, parent_key)
38
+ return query.all()
39
+
40
+ def get_element(
41
+ self, fides_key: str
42
+ ) -> Optional[Union[DataCategory, DataUse, DataSubject]]:
43
+ model_class = self.get_model()
44
+ element = (
45
+ self.db.query(model_class)
46
+ .filter(model_class.fides_key == fides_key)
47
+ .first()
48
+ )
49
+ return element
50
+
51
+ def create_element(
52
+ self, element_data: Dict
53
+ ) -> Union[DataCategory, DataUse, DataSubject]:
54
+ model_class = self.get_model()
55
+ element = model_class.create(self.db, data=element_data)
56
+ return element
57
+
58
+ def update_element(
59
+ self, fides_key: str, element_data: Dict
60
+ ) -> Optional[Union[DataCategory, DataUse, DataSubject]]:
61
+ model_class = self.get_model()
62
+ element = (
63
+ self.db.query(model_class)
64
+ .filter(model_class.fides_key == fides_key)
65
+ .first()
66
+ )
67
+
68
+ if not element:
69
+ return None
70
+
71
+ return element.update(db=self.db, data=element_data)
72
+
73
+ def delete_element(self, fides_key: str) -> None:
74
+ model_class = self.get_model()
75
+ element = (
76
+ self.db.query(model_class)
77
+ .filter(model_class.fides_key == fides_key)
78
+ .first()
79
+ )
80
+
81
+ if element:
82
+ self.db.delete(element)
83
+
84
+ def get_model(self) -> Type[Union[DataCategory, DataUse, DataSubject]]:
85
+ return self.models[self.taxonomy_type]
86
+
87
+ def _build_query(
88
+ self, model_class: Type, active_only: bool, parent_key: Optional[str]
89
+ ) -> Query:
90
+ query = self.db.query(model_class)
91
+ if active_only:
92
+ query = query.filter(model_class.active.is_(True))
93
+ if parent_key and hasattr(model_class, "parent_key"):
94
+ query = query.filter(model_class.parent_key == parent_key)
95
+ return query