Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support active property on taxonomy elements #3784

Merged
merged 2 commits into from
Jul 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .fides/db_dataset.yml
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,10 @@ dataset:
data_categories:
- system.operations
data_qualifier: aggregated.anonymized.unlinked_pseudonymized.pseudonymized.identified
- name: active
data_categories:
- system.operations
data_qualifier: aggregated.anonymized.unlinked_pseudonymized.pseudonymized.identified
- name: ctl_data_qualifiers
data_qualifier: aggregated.anonymized.unlinked_pseudonymized.pseudonymized.identified
fields:
Expand Down Expand Up @@ -419,6 +423,10 @@ dataset:
data_categories:
- system.operations
data_qualifier: aggregated.anonymized.unlinked_pseudonymized.pseudonymized.identified
- name: active
data_categories:
- system.operations
data_qualifier: aggregated.anonymized.unlinked_pseudonymized.pseudonymized.identified
- name: ctl_data_subjects
data_qualifier: aggregated.anonymized.unlinked_pseudonymized.pseudonymized.identified
fields:
Expand Down Expand Up @@ -470,6 +478,10 @@ dataset:
data_categories:
- system.operations
data_qualifier: aggregated.anonymized.unlinked_pseudonymized.pseudonymized.identified
- name: active
data_categories:
- system.operations
data_qualifier: aggregated.anonymized.unlinked_pseudonymized.pseudonymized.identified
- name: ctl_data_uses
data_qualifier: aggregated.anonymized.unlinked_pseudonymized.pseudonymized.identified
fields:
Expand Down Expand Up @@ -536,6 +548,10 @@ dataset:
data_categories:
- system.operations
data_qualifier: aggregated.anonymized.unlinked_pseudonymized.pseudonymized.identified
- name: active
data_categories:
- system.operations
data_qualifier: aggregated.anonymized.unlinked_pseudonymized.pseudonymized.identified
- name: ctl_datasets
data_qualifier: aggregated.anonymized.unlinked_pseudonymized.pseudonymized.identified
fields:
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ The types of changes are:
- Removed "Custom field(s) successfully saved" toast [#3779](https://github.com/ethyca/fides/pull/3779)

### Added

- Record when consent is served [#3777](https://github.com/ethyca/fides/pull/3777)
- Add an `active` property to taxonomy elements [#3784](https://github.com/ethyca/fides/pull/3784)

### Fixed
- Privacy notice UI's list of possible regions now matches the backend's list [#3787](https://github.com/ethyca/fides/pull/3787)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""add_active_column_taxonomy_elements

Revision ID: 5abb65a8cb91
Revises: 7c562441c589
Create Date: 2023-07-14 11:08:55.169966

"""
import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = "5abb65a8cb91"
down_revision = "7c562441c589"
branch_labels = None
depends_on = None


def upgrade():
op.add_column(
"ctl_data_categories",
sa.Column("active", sa.BOOLEAN(), nullable=False, server_default="t"),
)
op.add_column(
"ctl_data_subjects",
sa.Column("active", sa.BOOLEAN(), nullable=False, server_default="t"),
)
op.add_column(
"ctl_data_uses",
sa.Column("active", sa.BOOLEAN(), nullable=False, server_default="t"),
)
op.add_column(
"ctl_data_qualifiers",
sa.Column("active", sa.BOOLEAN(), nullable=False, server_default="t"),
)


def downgrade():
op.drop_column("ctl_data_uses", "active")
op.drop_column("ctl_data_subjects", "active")
op.drop_column("ctl_data_categories", "active")
op.drop_column("ctl_data_qualifiers", "active")
9 changes: 3 additions & 6 deletions src/fides/api/api/v1/endpoints/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,13 @@
This module generates all of the routers for the boilerplate/generic
objects that don't require any extra logic.
"""
from fideslang import (
from fideslang import Dataset, Evaluation, Organization, Policy, Registry

from fides.api.schemas.taxonomy_extensions import (
DataCategory,
DataQualifier,
Dataset,
DataSubject,
DataUse,
Evaluation,
Organization,
Policy,
Registry,
)

from .router_factory import generic_router_factory # type: ignore[attr-defined]
Expand Down
4 changes: 4 additions & 0 deletions src/fides/api/models/sql_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ class DataCategory(Base, FidesBase):

parent_key = Column(Text)
is_default = Column(BOOLEAN, default=False)
active = Column(BOOLEAN, default=True, nullable=False)

@classmethod
def from_fideslang_obj(
Expand All @@ -177,6 +178,7 @@ class DataQualifier(Base, FidesBase):

parent_key = Column(Text)
is_default = Column(BOOLEAN, default=False)
active = Column(BOOLEAN, default=True, nullable=False)


class DataSubject(Base, FidesBase):
Expand All @@ -188,6 +190,7 @@ class DataSubject(Base, FidesBase):
rights = Column(JSON, nullable=True)
automated_decisions_or_profiling = Column(BOOLEAN, nullable=True)
is_default = Column(BOOLEAN, default=False)
active = Column(BOOLEAN, default=True, nullable=False)


class DataUse(Base, FidesBase):
Expand All @@ -204,6 +207,7 @@ class DataUse(Base, FidesBase):
legitimate_interest = Column(BOOLEAN, nullable=True)
legitimate_interest_impact_assessment = Column(String, nullable=True)
is_default = Column(BOOLEAN, default=False)
active = Column(BOOLEAN, default=True, nullable=False)

@staticmethod
def get_parent_uses_from_key(data_use_key: str) -> Set[str]:
Expand Down
29 changes: 29 additions & 0 deletions src/fides/api/schemas/taxonomy_extensions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""
Fides-specific extensions to the pydantic models of taxonomy elements as defined in fideslang.
"""

from fideslang.models import DataCategory as BaseDataCategory
from fideslang.models import DataQualifier as BaseDataQualifier
from fideslang.models import DataSubject as BaseDataSubject
from fideslang.models import DataUse as BaseDataUse
from pydantic import Field

active_field = Field(
default=True, description="Indicates whether the resource is currently 'active'."
)


class DataUse(BaseDataUse):
active: bool = active_field


class DataCategory(BaseDataCategory):
active: bool = active_field


class DataQualifier(BaseDataQualifier):
active: bool = active_field


class DataSubject(BaseDataSubject):
active: bool = active_field
126 changes: 126 additions & 0 deletions tests/ctl/core/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@
from fides.api.models.sql_models import Dataset, PrivacyDeclaration, System
from fides.api.oauth.roles import OWNER, VIEWER
from fides.api.schemas.system import PrivacyDeclarationResponse
from fides.api.schemas.taxonomy_extensions import (
DataCategory,
DataQualifier,
DataSubject,
DataUse,
)
from fides.api.util.endpoint_utils import API_PREFIX, CLI_SCOPE_PREFIX_MAPPING
from fides.common.api.scope_registry import (
CREATE,
Expand All @@ -49,6 +55,12 @@
CONFIG = get_config()

TAXONOMY_ENDPOINTS = ["data_category", "data_subject", "data_use", "data_qualifier"]
TAXONOMY_EXTENSIONS = {
"data_category": DataCategory,
"data_subject": DataSubject,
"data_use": DataUse,
"data_qualifier": DataQualifier,
}


# Helper Functions
Expand Down Expand Up @@ -1863,6 +1875,120 @@ def test_api_cannot_upsert_is_default(
)


@pytest.mark.integration
class TestCrudActiveProperty:
"""
Ensure `active` property is exposed properly via CRUD endpoints.
Specific tests for this property since it's a fides-specific
extension to the underlying fideslang taxonomy models.
"""

@pytest.mark.parametrize("endpoint", TAXONOMY_ENDPOINTS)
def test_api_can_update_active_on_default(
self, test_config: FidesConfig, endpoint: str
) -> None:
"""Ensure we can toggle `active` property on default taxonomy elements"""
resource = getattr(DEFAULT_TAXONOMY, endpoint)[0]
resource = TAXONOMY_EXTENSIONS[endpoint](
**resource.dict()
) # cast resource to extended model
resource.active = False
json_resource = resource.json(exclude_none=True)
result = _api.update(
url=test_config.cli.server_url,
headers=test_config.user.auth_header,
resource_type=endpoint,
json_resource=json_resource,
)
assert result.status_code == 200
assert result.json()["active"] is False

result = _api.get(
url=test_config.cli.server_url,
headers=test_config.user.auth_header,
resource_type=endpoint,
resource_id=resource.fides_key,
)
assert result.json()["active"] is False

resource.active = True
json_resource = resource.json(exclude_none=True)
result = _api.update(
url=test_config.cli.server_url,
headers=test_config.user.auth_header,
resource_type=endpoint,
json_resource=json_resource,
)
assert result.status_code == 200
assert result.json()["active"] is True

result = _api.get(
url=test_config.cli.server_url,
headers=test_config.user.auth_header,
resource_type=endpoint,
resource_id=resource.fides_key,
)
assert result.json()["active"] is True

@pytest.mark.parametrize("endpoint", TAXONOMY_ENDPOINTS)
def test_api_can_create_with_active_property(
self,
test_config: FidesConfig,
endpoint: str,
generate_auth_header,
) -> None:
"""Ensure we can create taxonomy elements with `active` property set"""
# get a default taxonomy element as a sample resource
resource = getattr(DEFAULT_TAXONOMY, endpoint)[0]
resource = TAXONOMY_EXTENSIONS[endpoint](
**resource.dict()
) # cast resource to extended model
resource.fides_key = resource.fides_key + "_test_create_active_false"
resource.is_default = False
resource.active = False
json_resource = resource.json(exclude_none=True)
token_scopes: List[str] = [f"{CLI_SCOPE_PREFIX_MAPPING[endpoint]}:{CREATE}"]
auth_header = generate_auth_header(scopes=token_scopes)
result = _api.create(
url=test_config.cli.server_url,
headers=auth_header,
resource_type=endpoint,
json_resource=json_resource,
)
assert result.status_code == 201
assert result.json()["active"] is False

result = _api.get(
url=test_config.cli.server_url,
headers=test_config.user.auth_header,
resource_type=endpoint,
resource_id=resource.fides_key,
)
assert result.json()["active"] is False

resource.fides_key = resource.fides_key + "_test_create_active_true"
resource.active = True
json_resource = resource.json(exclude_none=True)
token_scopes: List[str] = [f"{CLI_SCOPE_PREFIX_MAPPING[endpoint]}:{CREATE}"]
auth_header = generate_auth_header(scopes=token_scopes)
result = _api.create(
url=test_config.cli.server_url,
headers=auth_header,
resource_type=endpoint,
json_resource=json_resource,
)
assert result.status_code == 201
assert result.json()["active"] is True

result = _api.get(
url=test_config.cli.server_url,
headers=test_config.user.auth_header,
resource_type=endpoint,
resource_id=resource.fides_key,
)
assert result.json()["active"] is True


@pytest.mark.integration
def test_static_sink(test_config: FidesConfig) -> None:
"""Make sure we are hosting something at / and not getting a 404"""
Expand Down