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.
- {ethyca_fides-2.68.1b1.dist-info → ethyca_fides-2.68.1b2.dist-info}/METADATA +1 -1
- {ethyca_fides-2.68.1b1.dist-info → ethyca_fides-2.68.1b2.dist-info}/RECORD +113 -105
- fides/_version.py +3 -3
- fides/api/alembic/migrations/versions/3baf42d251a6_add_generic_taxonomy_models.py +239 -0
- fides/api/api/v1/endpoints/generic_overrides.py +64 -167
- fides/api/db/base.py +6 -0
- fides/api/models/taxonomy.py +275 -0
- fides/api/service/deps.py +5 -0
- fides/api/service/privacy_request/request_service.py +6 -1
- fides/api/task/manual/manual_task_graph_task.py +11 -0
- fides/api/util/connection_type.py +68 -33
- fides/data/sample_project/docker-compose.yml +3 -3
- fides/service/taxonomy/__init__.py +0 -0
- fides/service/taxonomy/handlers/__init__.py +11 -0
- fides/service/taxonomy/handlers/base.py +42 -0
- fides/service/taxonomy/handlers/legacy_handler.py +95 -0
- fides/service/taxonomy/taxonomy_service.py +261 -0
- fides/service/taxonomy/utils.py +160 -0
- fides/ui-build/static/admin/404.html +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{_app-65723cd4b8fc36ac.js → _app-2c10f6b217b7978b.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center-58827eb86516931f.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations/{[id]-766e57bcf38b5b1e.js → [id]-4e286a1e501a0c73.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests-709bcb0bc6a5382d.js +1 -0
- fides/ui-build/static/admin/_next/static/css/a72179b1754aadd3.css +1 -0
- fides/ui-build/static/admin/_next/static/{tzF4yti8NslASlGnxnZ8m → qvk5eMANVfwYkdURE7fgG}/_buildManifest.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/lib/fides-tcf.js +2 -2
- fides/ui-build/static/admin/lib/fides.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/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center-53a763e49ce34a74.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests-f43a988542813110.js +0 -1
- fides/ui-build/static/admin/_next/static/css/e1628f15dd5f019b.css +0 -1
- {ethyca_fides-2.68.1b1.dist-info → ethyca_fides-2.68.1b2.dist-info}/WHEEL +0 -0
- {ethyca_fides-2.68.1b1.dist-info → ethyca_fides-2.68.1b2.dist-info}/entry_points.txt +0 -0
- {ethyca_fides-2.68.1b1.dist-info → ethyca_fides-2.68.1b2.dist-info}/licenses/LICENSE +0 -0
- {ethyca_fides-2.68.1b1.dist-info → ethyca_fides-2.68.1b2.dist-info}/top_level.txt +0 -0
- /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
|
-
[
|
|
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
|
|
193
|
-
search
|
|
194
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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,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
|