ethyca-fides 2.68.1b0__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.1b0.dist-info → ethyca_fides-2.68.1b2.dist-info}/METADATA +1 -1
- {ethyca_fides-2.68.1b0.dist-info → ethyca_fides-2.68.1b2.dist-info}/RECORD +128 -118
- fides/_version.py +3 -3
- fides/api/alembic/migrations/versions/3baf42d251a6_add_generic_taxonomy_models.py +239 -0
- fides/api/api/deps.py +2 -0
- fides/api/api/v1/endpoints/generic_overrides.py +64 -167
- fides/api/db/base.py +6 -0
- fides/api/db/ctl_session.py +3 -0
- fides/api/db/session.py +2 -1
- fides/api/models/privacy_request/privacy_request.py +15 -0
- fides/api/models/taxonomy.py +275 -0
- fides/api/schemas/application_config.py +2 -1
- fides/api/schemas/privacy_center_config.py +15 -0
- fides/api/service/deps.py +5 -0
- fides/api/service/privacy_request/request_service.py +6 -1
- fides/api/task/conditional_dependencies/evaluator.py +192 -45
- fides/api/task/conditional_dependencies/logging_utils.py +196 -0
- fides/api/task/conditional_dependencies/operators.py +8 -2
- fides/api/task/conditional_dependencies/schemas.py +25 -1
- fides/api/task/graph_task.py +9 -2
- fides/api/task/manual/manual_task_conditional_evaluation.py +193 -0
- fides/api/task/manual/manual_task_graph_task.py +224 -119
- fides/api/task/manual/manual_task_utils.py +0 -4
- fides/api/tasks/__init__.py +1 -0
- fides/api/util/connection_type.py +68 -33
- fides/config/database_settings.py +10 -1
- 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/{JLiYN-Wiw1kNc_8IVythJ → 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.1b0.dist-info → ethyca_fides-2.68.1b2.dist-info}/WHEEL +0 -0
- {ethyca_fides-2.68.1b0.dist-info → ethyca_fides-2.68.1b2.dist-info}/entry_points.txt +0 -0
- {ethyca_fides-2.68.1b0.dist-info → ethyca_fides-2.68.1b2.dist-info}/licenses/LICENSE +0 -0
- {ethyca_fides-2.68.1b0.dist-info → ethyca_fides-2.68.1b2.dist-info}/top_level.txt +0 -0
- /fides/ui-build/static/admin/_next/static/{JLiYN-Wiw1kNc_8IVythJ → qvk5eMANVfwYkdURE7fgG}/_ssgManifest.js +0 -0
|
@@ -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
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Service layer for taxonomy management (data_categories, data_uses, data_subjects).
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, List, Literal, Optional, Union, overload
|
|
6
|
+
|
|
7
|
+
from sqlalchemy import or_
|
|
8
|
+
from sqlalchemy.orm import Session
|
|
9
|
+
|
|
10
|
+
from fides.api.models.sql_models import ( # type:ignore[attr-defined]
|
|
11
|
+
DataCategory,
|
|
12
|
+
DataSubject,
|
|
13
|
+
DataUse,
|
|
14
|
+
)
|
|
15
|
+
from fides.api.models.taxonomy import TaxonomyUsage
|
|
16
|
+
|
|
17
|
+
from .handlers import LegacyTaxonomyHandler, TaxonomyHandler
|
|
18
|
+
from .utils import (
|
|
19
|
+
activate_taxonomy_parents,
|
|
20
|
+
check_for_taxonomy_reactivation,
|
|
21
|
+
deactivate_taxonomy_node_and_descendants,
|
|
22
|
+
generate_taxonomy_fides_key,
|
|
23
|
+
handle_taxonomy_reactivation,
|
|
24
|
+
validate_default_taxonomy_restrictions,
|
|
25
|
+
validate_parent_key_exists,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TaxonomyService:
|
|
30
|
+
"""
|
|
31
|
+
Taxonomy service for managing Fides taxonomy elements.
|
|
32
|
+
Provides CRUD operations for data_categories, data_uses, and data_subjects.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
# Most of the main methods in this service have a type-specific overload to help with type hints
|
|
36
|
+
|
|
37
|
+
def __init__(self, db: Session):
|
|
38
|
+
self.db = db
|
|
39
|
+
|
|
40
|
+
@overload
|
|
41
|
+
def get_elements(
|
|
42
|
+
self,
|
|
43
|
+
taxonomy_type: Literal["data_categories"],
|
|
44
|
+
active_only: bool = True,
|
|
45
|
+
parent_key: Optional[str] = None,
|
|
46
|
+
) -> List[DataCategory]: ...
|
|
47
|
+
|
|
48
|
+
@overload
|
|
49
|
+
def get_elements(
|
|
50
|
+
self,
|
|
51
|
+
taxonomy_type: Literal["data_uses"],
|
|
52
|
+
active_only: bool = True,
|
|
53
|
+
parent_key: Optional[str] = None,
|
|
54
|
+
) -> List[DataUse]: ...
|
|
55
|
+
|
|
56
|
+
@overload
|
|
57
|
+
def get_elements(
|
|
58
|
+
self,
|
|
59
|
+
taxonomy_type: Literal["data_subjects"],
|
|
60
|
+
active_only: bool = True,
|
|
61
|
+
parent_key: Optional[str] = None,
|
|
62
|
+
) -> List[DataSubject]: ...
|
|
63
|
+
|
|
64
|
+
def get_elements(
|
|
65
|
+
self,
|
|
66
|
+
taxonomy_type: str,
|
|
67
|
+
active_only: bool = True,
|
|
68
|
+
parent_key: Optional[str] = None,
|
|
69
|
+
) -> List[Union[DataCategory, DataUse, DataSubject]]:
|
|
70
|
+
"""Get elements for a taxonomy type."""
|
|
71
|
+
return self._get_handler(taxonomy_type).get_elements(active_only, parent_key)
|
|
72
|
+
|
|
73
|
+
@overload
|
|
74
|
+
def get_element(
|
|
75
|
+
self, taxonomy_type: Literal["data_categories"], fides_key: str
|
|
76
|
+
) -> Optional[DataCategory]: ...
|
|
77
|
+
|
|
78
|
+
@overload
|
|
79
|
+
def get_element(
|
|
80
|
+
self, taxonomy_type: Literal["data_uses"], fides_key: str
|
|
81
|
+
) -> Optional[DataUse]: ...
|
|
82
|
+
|
|
83
|
+
@overload
|
|
84
|
+
def get_element(
|
|
85
|
+
self, taxonomy_type: Literal["data_subjects"], fides_key: str
|
|
86
|
+
) -> Optional[DataSubject]: ...
|
|
87
|
+
|
|
88
|
+
def get_element(self, taxonomy_type: str, fides_key: str) -> Optional[Any]:
|
|
89
|
+
"""Get a single element by fides_key."""
|
|
90
|
+
return self._get_handler(taxonomy_type).get_element(fides_key)
|
|
91
|
+
|
|
92
|
+
@overload
|
|
93
|
+
def create_element(
|
|
94
|
+
self, taxonomy_type: Literal["data_categories"], element_data: Dict
|
|
95
|
+
) -> DataCategory: ...
|
|
96
|
+
|
|
97
|
+
@overload
|
|
98
|
+
def create_element(
|
|
99
|
+
self, taxonomy_type: Literal["data_uses"], element_data: Dict
|
|
100
|
+
) -> DataUse: ...
|
|
101
|
+
|
|
102
|
+
@overload
|
|
103
|
+
def create_element(
|
|
104
|
+
self, taxonomy_type: Literal["data_subjects"], element_data: Dict
|
|
105
|
+
) -> DataSubject: ...
|
|
106
|
+
|
|
107
|
+
def create_element(
|
|
108
|
+
self, taxonomy_type: str, element_data: Dict
|
|
109
|
+
) -> Union[DataCategory, DataUse, DataSubject]:
|
|
110
|
+
"""Create a new taxonomy element."""
|
|
111
|
+
# Generate fides_key if not provided
|
|
112
|
+
processed_data = element_data.copy()
|
|
113
|
+
if not processed_data.get("fides_key") and processed_data.get("name"):
|
|
114
|
+
handler = self._get_handler(taxonomy_type)
|
|
115
|
+
processed_data["fides_key"] = generate_taxonomy_fides_key(
|
|
116
|
+
taxonomy_type,
|
|
117
|
+
processed_data["name"],
|
|
118
|
+
processed_data.get("parent_key"),
|
|
119
|
+
handler,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Centralized validation before delegation
|
|
123
|
+
self._validate_element_data(processed_data, taxonomy_type, action="create")
|
|
124
|
+
return self._get_handler(taxonomy_type).create_element(processed_data)
|
|
125
|
+
|
|
126
|
+
@overload
|
|
127
|
+
def update_element(
|
|
128
|
+
self,
|
|
129
|
+
taxonomy_type: Literal["data_categories"],
|
|
130
|
+
fides_key: str,
|
|
131
|
+
element_data: Dict,
|
|
132
|
+
) -> Optional[DataCategory]: ...
|
|
133
|
+
|
|
134
|
+
@overload
|
|
135
|
+
def update_element(
|
|
136
|
+
self, taxonomy_type: Literal["data_uses"], fides_key: str, element_data: Dict
|
|
137
|
+
) -> Optional[DataUse]: ...
|
|
138
|
+
|
|
139
|
+
@overload
|
|
140
|
+
def update_element(
|
|
141
|
+
self,
|
|
142
|
+
taxonomy_type: Literal["data_subjects"],
|
|
143
|
+
fides_key: str,
|
|
144
|
+
element_data: Dict,
|
|
145
|
+
) -> Optional[DataSubject]: ...
|
|
146
|
+
|
|
147
|
+
def update_element(
|
|
148
|
+
self, taxonomy_type: str, fides_key: str, element_data: Dict
|
|
149
|
+
) -> Optional[Any]:
|
|
150
|
+
"""Update an existing taxonomy element."""
|
|
151
|
+
# Get the existing element for validation
|
|
152
|
+
handler = self._get_handler(taxonomy_type)
|
|
153
|
+
existing_element = handler.get_element(fides_key)
|
|
154
|
+
|
|
155
|
+
self._validate_element_data(
|
|
156
|
+
element_data,
|
|
157
|
+
taxonomy_type,
|
|
158
|
+
existing_element=existing_element,
|
|
159
|
+
action="update",
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# Update the element via handler
|
|
163
|
+
updated_element = handler.update_element(fides_key, element_data)
|
|
164
|
+
|
|
165
|
+
# Handle hierarchical activation/deactivation logic at service level
|
|
166
|
+
if updated_element and "active" in element_data:
|
|
167
|
+
if element_data["active"]:
|
|
168
|
+
activate_taxonomy_parents(updated_element, self.db)
|
|
169
|
+
else:
|
|
170
|
+
# Cascade down - deactivate current node and children
|
|
171
|
+
deactivate_taxonomy_node_and_descendants(updated_element, self.db)
|
|
172
|
+
|
|
173
|
+
# Ensure hierarchical updates are visible across sessions
|
|
174
|
+
# The model's own update() commits, but parent/child mutations above
|
|
175
|
+
# only flush by design. Commit here so other sessions (e.g., API tests)
|
|
176
|
+
# can observe the propagated changes immediately.
|
|
177
|
+
self.db.commit()
|
|
178
|
+
# Refresh the updated element to return the latest state
|
|
179
|
+
self.db.refresh(updated_element)
|
|
180
|
+
|
|
181
|
+
return updated_element
|
|
182
|
+
|
|
183
|
+
def delete_element(self, taxonomy_type: str, fides_key: str) -> None:
|
|
184
|
+
"""Delete a taxonomy element."""
|
|
185
|
+
# First, remove any TaxonomyUsage rows that reference this element
|
|
186
|
+
# as either the source or the target element. There is no DB cascade
|
|
187
|
+
# for these relationships by design, so this cleanup is handled here.
|
|
188
|
+
self.db.query(TaxonomyUsage).filter(
|
|
189
|
+
or_(
|
|
190
|
+
TaxonomyUsage.source_element_key == fides_key,
|
|
191
|
+
TaxonomyUsage.target_element_key == fides_key,
|
|
192
|
+
)
|
|
193
|
+
).delete(synchronize_session=False)
|
|
194
|
+
|
|
195
|
+
# Then delete the element itself via the appropriate handler
|
|
196
|
+
self._get_handler(taxonomy_type).delete_element(fides_key)
|
|
197
|
+
self.db.commit()
|
|
198
|
+
|
|
199
|
+
@overload
|
|
200
|
+
def create_or_update_element(
|
|
201
|
+
self, taxonomy_type: Literal["data_categories"], element_data: Dict
|
|
202
|
+
) -> DataCategory: ...
|
|
203
|
+
|
|
204
|
+
@overload
|
|
205
|
+
def create_or_update_element(
|
|
206
|
+
self, taxonomy_type: Literal["data_uses"], element_data: Dict
|
|
207
|
+
) -> DataUse: ...
|
|
208
|
+
|
|
209
|
+
@overload
|
|
210
|
+
def create_or_update_element(
|
|
211
|
+
self, taxonomy_type: Literal["data_subjects"], element_data: Dict
|
|
212
|
+
) -> DataSubject: ...
|
|
213
|
+
|
|
214
|
+
def create_or_update_element(self, taxonomy_type: str, element_data: Dict) -> Any:
|
|
215
|
+
"""
|
|
216
|
+
Create or update a taxonomy element.
|
|
217
|
+
If the element is deactivated, it will be updated and re-activated, along with its parents.
|
|
218
|
+
This method provides compatibility with the existing generic_overrides endpoint pattern.
|
|
219
|
+
"""
|
|
220
|
+
# Check for reactivation case centrally
|
|
221
|
+
handler = self._get_handler(taxonomy_type)
|
|
222
|
+
reactivation_element = check_for_taxonomy_reactivation(
|
|
223
|
+
self.db, taxonomy_type, element_data, handler
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
if reactivation_element:
|
|
227
|
+
return handle_taxonomy_reactivation(
|
|
228
|
+
self.db, taxonomy_type, reactivation_element, element_data, handler
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
return self.create_element(taxonomy_type, element_data) # type: ignore[call-overload]
|
|
232
|
+
|
|
233
|
+
def _get_handler(self, taxonomy_type: str) -> TaxonomyHandler:
|
|
234
|
+
"""Get the handler for taxonomy operations."""
|
|
235
|
+
return LegacyTaxonomyHandler(self.db, taxonomy_type)
|
|
236
|
+
|
|
237
|
+
def _validate_element_data(
|
|
238
|
+
self,
|
|
239
|
+
element_data: Dict,
|
|
240
|
+
taxonomy_type: str,
|
|
241
|
+
existing_element: Optional[Union[DataCategory, DataUse, DataSubject]] = None,
|
|
242
|
+
action: str = "create",
|
|
243
|
+
) -> None:
|
|
244
|
+
"""
|
|
245
|
+
Centralized validation for taxonomy elements.
|
|
246
|
+
This runs before delegation to ensure consistent validation across all handlers.
|
|
247
|
+
"""
|
|
248
|
+
# Validate default taxonomy restrictions for taxonomy elements
|
|
249
|
+
if action == "create":
|
|
250
|
+
validate_default_taxonomy_restrictions(element_data, action="create")
|
|
251
|
+
elif action == "update" and existing_element:
|
|
252
|
+
validate_default_taxonomy_restrictions(
|
|
253
|
+
element_data, resource=existing_element, action="update"
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# Validate parent_key exists if provided
|
|
257
|
+
if "parent_key" in element_data and element_data["parent_key"]:
|
|
258
|
+
handler = self._get_handler(taxonomy_type)
|
|
259
|
+
validate_parent_key_exists(
|
|
260
|
+
taxonomy_type, element_data["parent_key"], handler
|
|
261
|
+
)
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
from typing import Any, Dict, Optional, Union
|
|
2
|
+
|
|
3
|
+
from sqlalchemy.orm import Session
|
|
4
|
+
|
|
5
|
+
from fides.api.common_exceptions import ValidationError
|
|
6
|
+
from fides.api.db.base_class import get_key_from_data
|
|
7
|
+
from fides.api.models.sql_models import ( # type:ignore[attr-defined]
|
|
8
|
+
DataCategory,
|
|
9
|
+
DataSubject,
|
|
10
|
+
DataUse,
|
|
11
|
+
)
|
|
12
|
+
from fides.api.util.errors import ForbiddenIsDefaultTaxonomyError
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Core validation functions for taxonomy operations
|
|
16
|
+
def activate_taxonomy_parents(
|
|
17
|
+
resource: Union[DataCategory, DataUse, DataSubject],
|
|
18
|
+
db: Session,
|
|
19
|
+
) -> None:
|
|
20
|
+
"""
|
|
21
|
+
Activates parents to match newly-active taxonomy node.
|
|
22
|
+
"""
|
|
23
|
+
parent = resource.parent
|
|
24
|
+
if parent:
|
|
25
|
+
parent.active = True
|
|
26
|
+
db.flush()
|
|
27
|
+
activate_taxonomy_parents(parent, db)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def deactivate_taxonomy_node_and_descendants(
|
|
31
|
+
resource: Union[DataCategory, DataUse, DataSubject],
|
|
32
|
+
db: Session,
|
|
33
|
+
) -> None:
|
|
34
|
+
"""
|
|
35
|
+
Recursively de-activates all descendants of a given taxonomy node.
|
|
36
|
+
"""
|
|
37
|
+
resource.active = False
|
|
38
|
+
db.flush()
|
|
39
|
+
children = resource.children
|
|
40
|
+
|
|
41
|
+
for child in children:
|
|
42
|
+
# Deactivate current child
|
|
43
|
+
child.active = False
|
|
44
|
+
db.flush()
|
|
45
|
+
# Recursively deactivate all descendants of this child
|
|
46
|
+
deactivate_taxonomy_node_and_descendants(child, db)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def validate_default_taxonomy_restrictions(
|
|
50
|
+
data: Dict,
|
|
51
|
+
resource: Optional[Union[DataCategory, DataUse, DataSubject]] = None,
|
|
52
|
+
action: str = "create",
|
|
53
|
+
) -> None:
|
|
54
|
+
"""
|
|
55
|
+
Validate restrictions on default taxonomy elements.
|
|
56
|
+
"""
|
|
57
|
+
# For creation, check if trying to create with is_default=True
|
|
58
|
+
if action == "create" and data.get("is_default"):
|
|
59
|
+
raise ForbiddenIsDefaultTaxonomyError(
|
|
60
|
+
"taxonomy", data.get("fides_key", "unknown"), action="create"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# For updates, check if trying to modify is_default field
|
|
64
|
+
if action == "update" and resource:
|
|
65
|
+
# Only check if is_default is explicitly present in the data and is being changed
|
|
66
|
+
if "is_default" in data and hasattr(resource, "is_default"):
|
|
67
|
+
if data["is_default"] != resource.is_default:
|
|
68
|
+
raise ForbiddenIsDefaultTaxonomyError(
|
|
69
|
+
"resource",
|
|
70
|
+
data.get("fides_key", resource.fides_key),
|
|
71
|
+
action="modify",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def validate_parent_key_exists(
|
|
76
|
+
taxonomy_type: str, parent_key: str, handler: Any
|
|
77
|
+
) -> None:
|
|
78
|
+
"""
|
|
79
|
+
Validate that the parent_key exists in the same taxonomy type.
|
|
80
|
+
This prevents IntegrityError at the database level by validating at application level.
|
|
81
|
+
"""
|
|
82
|
+
if not parent_key:
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
parent_element = handler.get_element(parent_key)
|
|
86
|
+
|
|
87
|
+
if not parent_element:
|
|
88
|
+
raise ValidationError(
|
|
89
|
+
f"Parent with key '{parent_key}' not found in taxonomy '{taxonomy_type}'"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def generate_taxonomy_fides_key(
|
|
94
|
+
taxonomy_type: str,
|
|
95
|
+
name: str,
|
|
96
|
+
parent_key: Optional[str] = None,
|
|
97
|
+
handler: Optional[Any] = None,
|
|
98
|
+
) -> str:
|
|
99
|
+
"""
|
|
100
|
+
Generate a fides_key from a name for taxonomy elements.
|
|
101
|
+
"""
|
|
102
|
+
# Get the actual model class name for key generation
|
|
103
|
+
if handler and hasattr(handler, "get_model"):
|
|
104
|
+
model_class = handler.get_model()
|
|
105
|
+
fides_key = get_key_from_data({"name": name}, model_class.__name__)
|
|
106
|
+
else:
|
|
107
|
+
# Fallback to using taxonomy_type
|
|
108
|
+
fides_key = get_key_from_data({"name": name}, taxonomy_type)
|
|
109
|
+
|
|
110
|
+
# Add parent prefix if this is not a root level taxonomy node
|
|
111
|
+
if parent_key:
|
|
112
|
+
fides_key = f"{parent_key}.{fides_key}"
|
|
113
|
+
|
|
114
|
+
return fides_key
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def check_for_taxonomy_reactivation(
|
|
118
|
+
db: Session, taxonomy_type: str, element_data: Dict, handler: Any
|
|
119
|
+
) -> Optional[Any]:
|
|
120
|
+
"""
|
|
121
|
+
Check if this is a reactivation case for existing disabled elements.
|
|
122
|
+
Returns the disabled element if it exists and should be reactivated.
|
|
123
|
+
"""
|
|
124
|
+
# Only check for reactivation if no fides_key is provided but name is
|
|
125
|
+
if element_data.get("fides_key") or not element_data.get("name"):
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
# Check the specific model table for disabled elements
|
|
129
|
+
model_class = handler.get_model()
|
|
130
|
+
disabled_element = (
|
|
131
|
+
db.query(model_class)
|
|
132
|
+
.filter(
|
|
133
|
+
model_class.active.is_(False),
|
|
134
|
+
model_class.name == element_data["name"],
|
|
135
|
+
)
|
|
136
|
+
.first()
|
|
137
|
+
)
|
|
138
|
+
return disabled_element
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def handle_taxonomy_reactivation(
|
|
142
|
+
db: Session, taxonomy_type: str, element: Any, element_data: Dict, handler: Any
|
|
143
|
+
) -> Any:
|
|
144
|
+
"""
|
|
145
|
+
Handle reactivation of a disabled taxonomy element.
|
|
146
|
+
"""
|
|
147
|
+
# Generate fides_key if not provided (needed for reactivation)
|
|
148
|
+
updated_data = element_data.copy()
|
|
149
|
+
if not updated_data.get("fides_key") and updated_data.get("name"):
|
|
150
|
+
updated_data["fides_key"] = generate_taxonomy_fides_key(
|
|
151
|
+
taxonomy_type, updated_data["name"], updated_data.get("parent_key"), handler
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Ensure it's marked as active and activate parents accordingly
|
|
155
|
+
updated_data["active"] = True
|
|
156
|
+
activate_taxonomy_parents(element, db)
|
|
157
|
+
|
|
158
|
+
# Update the element with new data
|
|
159
|
+
element.update(db, data=updated_data)
|
|
160
|
+
return element
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link data-next-font="" rel="preconnect" href="/" crossorigin="anonymous"/><link rel="preload" href="/_next/static/css/
|
|
1
|
+
<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link data-next-font="" rel="preconnect" href="/" crossorigin="anonymous"/><link rel="preload" href="/_next/static/css/a72179b1754aadd3.css" as="style"/><link rel="stylesheet" href="/_next/static/css/a72179b1754aadd3.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/_next/static/chunks/polyfills-42372ed130431b0a.js"></script><script src="/_next/static/chunks/webpack-69658aeaf6155d89.js" defer=""></script><script src="/_next/static/chunks/framework-c92fc3344e6fd165.js" defer=""></script><script src="/_next/static/chunks/main-090643377c8254e6.js" defer=""></script><script src="/_next/static/chunks/pages/_app-2c10f6b217b7978b.js" defer=""></script><script src="/_next/static/chunks/pages/404-9174cdb70126c2c5.js" defer=""></script><script src="/_next/static/qvk5eMANVfwYkdURE7fgG/_buildManifest.js" defer=""></script><script src="/_next/static/qvk5eMANVfwYkdURE7fgG/_ssgManifest.js" defer=""></script><style>.data-ant-cssinjs-cache-path{content:"";}</style></head><body><div id="__next"><div style="height:100%;display:flex"></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/404","query":{},"buildId":"qvk5eMANVfwYkdURE7fgG","nextExport":true,"autoExport":true,"isFallback":false,"scriptLoader":[]}</script></body></html>
|