diff --git a/clients/ops/admin-ui/src/constants.ts b/clients/ops/admin-ui/src/constants.ts index 452a1b421..f35558089 100644 --- a/clients/ops/admin-ui/src/constants.ts +++ b/clients/ops/admin-ui/src/constants.ts @@ -65,6 +65,14 @@ export const USER_PRIVILEGES: UserPrivileges[] = [ privilege: "View roles", scope: "user-permission:read", }, + { + privilege: "Upload privacy request data", + scope: "privacy-request:upload_data", + }, + { + privilege: "View privacy request data", + scope: "privacy-request:view_data", + }, ]; // API ROUTES diff --git a/src/fidesops/ops/models/connectionconfig.py b/src/fidesops/ops/models/connectionconfig.py index 23dab4aa1..bc7036ccf 100644 --- a/src/fidesops/ops/models/connectionconfig.py +++ b/src/fidesops/ops/models/connectionconfig.py @@ -2,7 +2,7 @@ import enum from datetime import datetime -from typing import Any, Dict, Optional, Type +from typing import TYPE_CHECKING, Any, Dict, Optional, Type from fideslib.db.base import Base from fideslib.db.base_class import get_key_from_data @@ -10,7 +10,7 @@ from sqlalchemy import Boolean, Column, DateTime, Enum, String, event from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.ext.mutable import MutableDict -from sqlalchemy.orm import Session +from sqlalchemy.orm import Session, relationship from sqlalchemy_utils.types.encrypted.encrypted_type import ( AesGcmEngine, StringEncryptedType, @@ -20,6 +20,9 @@ from fidesops.ops.db.base_class import JSONTypeOverride from fidesops.ops.schemas.saas.saas_config import SaaSConfig +if TYPE_CHECKING: + from fidesops.ops.models.manual_webhook import AccessManualWebhook + class ConnectionTestStatus(enum.Enum): """Enum for supplying statuses of validating credentials for a Connection Config to the user""" @@ -119,6 +122,13 @@ class ConnectionConfig(Base): MutableDict.as_mutable(JSONB), index=False, unique=False, nullable=True ) + access_manual_webhook = relationship( + "AccessManualWebhook", + back_populates="connection_config", + cascade="delete", + uselist=False, + ) + @classmethod def create_without_saving( cls: Type[ConnectionConfig], db: Session, *, data: dict[str, Any] diff --git a/src/fidesops/ops/models/manual_webhook.py b/src/fidesops/ops/models/manual_webhook.py index 19294b669..77fc17d41 100644 --- a/src/fidesops/ops/models/manual_webhook.py +++ b/src/fidesops/ops/models/manual_webhook.py @@ -6,7 +6,7 @@ from sqlalchemy import Column, ForeignKey, String from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.ext.mutable import MutableList -from sqlalchemy.orm import Session, backref, relationship +from sqlalchemy.orm import Session, relationship from fidesops.ops.models.connectionconfig import ConnectionConfig @@ -20,10 +20,13 @@ class AccessManualWebhook(Base): """ connection_config_id = Column( - String, ForeignKey(ConnectionConfig.id_field_path), unique=True, nullable=False + String, + ForeignKey(ConnectionConfig.id_field_path), + unique=True, + nullable=False, ) connection_config = relationship( - ConnectionConfig, backref=backref("access_manual_webhook", uselist=False) + "ConnectionConfig", back_populates="access_manual_webhook", uselist=False ) fields = Column(MutableList.as_mutable(JSONB), nullable=False) diff --git a/tests/ops/api/v1/endpoints/test_connection_config_endpoints.py b/tests/ops/api/v1/endpoints/test_connection_config_endpoints.py index 9c7791536..4a2e4b1e7 100644 --- a/tests/ops/api/v1/endpoints/test_connection_config_endpoints.py +++ b/tests/ops/api/v1/endpoints/test_connection_config_endpoints.py @@ -20,6 +20,7 @@ from fidesops.ops.api.v1.urn_registry import CONNECTIONS, SAAS_CONFIG, V1_URL_PREFIX from fidesops.ops.graph.config import CollectionAddress from fidesops.ops.models.connectionconfig import ConnectionConfig, ConnectionType +from fidesops.ops.models.manual_webhook import AccessManualWebhook from fidesops.ops.models.policy import CurrentStep from fidesops.ops.models.privacy_request import CheckpointActionRequired, ManualAction from fidesops.ops.schemas.email.email import EmailActionType @@ -819,6 +820,45 @@ def test_delete_connection_config( is None ) + def test_delete_manual_webhook_connection_config( + self, + url, + api_client: TestClient, + db: Session, + generate_auth_header, + integration_manual_webhook_config, + access_manual_webhook, + ) -> None: + """Assert both the connection config and its webhook are deleted""" + assert ( + db.query(AccessManualWebhook).filter_by(id=access_manual_webhook.id).first() + is not None + ) + + assert ( + db.query(ConnectionConfig) + .filter_by(key=integration_manual_webhook_config.key) + .first() + is not None + ) + + url = f"{V1_URL_PREFIX}{CONNECTIONS}/{integration_manual_webhook_config.key}" + auth_header = generate_auth_header(scopes=[CONNECTION_DELETE]) + resp = api_client.delete(url, headers=auth_header) + assert resp.status_code == 204 + + assert ( + db.query(AccessManualWebhook).filter_by(id=access_manual_webhook.id).first() + is None + ) + + assert ( + db.query(ConnectionConfig) + .filter_by(key=integration_manual_webhook_config.key) + .first() + is None + ) + class TestPutConnectionConfigSecrets: @pytest.fixture(scope="function") diff --git a/tests/ops/fixtures/manual_webhook_fixtures.py b/tests/ops/fixtures/manual_webhook_fixtures.py index 86da3dbe1..3516c4316 100644 --- a/tests/ops/fixtures/manual_webhook_fixtures.py +++ b/tests/ops/fixtures/manual_webhook_fixtures.py @@ -1,6 +1,7 @@ from uuid import uuid4 import pytest +from sqlalchemy.orm.exc import ObjectDeletedError from fidesops.ops.models.connectionconfig import ( AccessLevel, @@ -22,7 +23,10 @@ def integration_manual_webhook_config(db) -> ConnectionConfig: }, ) yield connection_config - connection_config.delete(db) + try: + connection_config.delete(db) + except ObjectDeletedError: + pass @pytest.fixture(scope="function") diff --git a/tests/ops/models/test_connectionconfig.py b/tests/ops/models/test_connectionconfig.py index 5a0836b90..cce77944e 100644 --- a/tests/ops/models/test_connectionconfig.py +++ b/tests/ops/models/test_connectionconfig.py @@ -154,3 +154,14 @@ def test_connection_type_human_readable(self): def test_connection_type_human_readable_invalid(self): with pytest.raises(ValueError): ConnectionType("nonmapped_type").human_readable() + + def test_manual_webhook( + self, integration_manual_webhook_config, access_manual_webhook + ): + assert ( + integration_manual_webhook_config.access_manual_webhook + == access_manual_webhook + ) + assert ( + access_manual_webhook.connection_config == integration_manual_webhook_config + )