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 for consent management report #4452

Merged
merged 11 commits into from
Dec 1, 2023
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ The types of changes are:

## [Unreleased](https://github.com/ethyca/fides/compare/2.25.0...main)

### Added
- New purposes endpoint and indices to improve system lookups [#4452](https://github.com/ethyca/fides/pull/4452)

### Fixed
- Fix type errors when TCF vendors have no dataDeclaration [#4465](https://github.com/ethyca/fides/pull/4465)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""add indexes to ctl_systems and privacydeclaration

Revision ID: 7f7c2b098f5d
Revises: 1af6950f4625
Create Date: 2023-11-21 18:52:34.508076

"""
import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = "7f7c2b098f5d"
down_revision = "1af6950f4625"
branch_labels = None
depends_on = None


def upgrade():
op.execute("CREATE EXTENSION IF NOT EXISTS pg_trgm;")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pg_trgm extension has been available since Postgres 8.3 so we should be ok to enable it

op.create_index(
"ix_ctl_systems_name",
"ctl_systems",
[sa.text("name gin_trgm_ops")],
postgresql_using="gin",
Comment on lines +20 to +24
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adds a GIN index to be able to do a partial search on system name (%name%)

)


def downgrade():
op.drop_index("ix_ctl_systems_name", table_name="ctl_systems")
op.execute("DROP EXTENSION IF EXISTS pg_trgm;")
2 changes: 2 additions & 0 deletions src/fides/api/api/v1/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
privacy_notice_endpoints,
privacy_preference_endpoints,
privacy_request_endpoints,
purpose_endpoints,
registration_endpoints,
saas_config_endpoints,
served_notice_endpoints,
Expand Down Expand Up @@ -51,3 +52,4 @@
api_router.include_router(manual_webhook_endpoints.router)
api_router.include_router(registration_endpoints.router)
api_router.include_router(served_notice_endpoints.router)
api_router.include_router(purpose_endpoints.router)
34 changes: 34 additions & 0 deletions src/fides/api/api/v1/endpoints/purpose_endpoints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from fastapi import Depends, Security
from fideslang.gvl import MAPPED_PURPOSES, MAPPED_SPECIAL_PURPOSES
from sqlalchemy.orm import Session
from starlette.status import HTTP_200_OK

from fides.api.api import deps
from fides.api.oauth.utils import verify_oauth_client
from fides.api.schemas.tcf import PurposesResponse
from fides.api.util.api_router import APIRouter
from fides.common.api.v1 import urn_registry as urls

router = APIRouter(tags=["Purposes"], prefix=urls.V1_URL_PREFIX)


@router.get(
"/purposes",
dependencies=[Security(verify_oauth_client)],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not tied to any scope, this is available for any authenticated user

status_code=HTTP_200_OK,
response_model=PurposesResponse,
)
def get_purposes(
db: Session = Depends(deps.get_db),
) -> PurposesResponse:
"""
Return a map of purpose and special purpose IDs to mapped purposes which include data uses.
"""

purposes = {}
special_purposes = {}
for purpose in MAPPED_PURPOSES.values():
purposes[purpose.id] = purpose
for special_purpose in MAPPED_SPECIAL_PURPOSES.values():
special_purposes[special_purpose.id] = special_purpose
return PurposesResponse(purposes=purposes, special_purposes=special_purposes)
7 changes: 6 additions & 1 deletion src/fides/api/schemas/tcf.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
MAPPED_SPECIAL_PURPOSES,
)
from fideslang.gvl.models import Feature, MappedPurpose
from pydantic import AnyUrl, root_validator, validator
from pydantic import AnyUrl, BaseModel, root_validator, validator

from fides.api.models.privacy_notice import UserConsentPreference
from fides.api.schemas.base_class import FidesSchema
Expand Down Expand Up @@ -230,3 +230,8 @@ def validate_special_feature_id(cls, value: int) -> int:
f"Cannot save preferences against invalid special feature id: '{value}'"
)
return value


class PurposesResponse(BaseModel):
purposes: Dict[str, MappedPurpose]
special_purposes: Dict[str, MappedPurpose]
3 changes: 3 additions & 0 deletions src/fides/common/api/v1/urn_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@
"/privacy-request/transfer/{privacy_request_id}/{rule_key}"
)

# Purpose URLs
PURPOSES = "/purposes"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if it's best to make this its own URL or if it should go under an existing path



# Identity Verification URLs
ID_VERIFICATION_CONFIG = "/id-verification/config"
Expand Down
25 changes: 25 additions & 0 deletions tests/ops/api/v1/endpoints/test_purpose_endpoints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import pytest
from starlette.status import HTTP_200_OK, HTTP_401_UNAUTHORIZED

from fides.common.api.v1.urn_registry import PURPOSES, V1_URL_PREFIX


class TestGetPurposes:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simple test that just checks access based on auth header

@pytest.fixture(scope="function")
def url(self) -> str:
return V1_URL_PREFIX + PURPOSES

def test_get_purposes_unauthenticated(self, url, api_client):
response = api_client.get(url)
assert response.status_code == HTTP_401_UNAUTHORIZED

def test_get_purposes(self, url, api_client, generate_auth_header):
auth_header = generate_auth_header([])
response = api_client.get(url, headers=auth_header)
assert response.status_code == HTTP_200_OK

data = response.json()
assert "purposes" in data
assert "special_purposes" in data
assert len(data["purposes"]) == 11
assert len(data["special_purposes"]) == 2