From 0135810b1e3022e9623ccf3692fed1d07281a87f Mon Sep 17 00:00:00 2001 From: Robert Keyser <39230492+RobertKeyser@users.noreply.github.com> Date: Tue, 6 Feb 2024 14:14:00 -0600 Subject: [PATCH] CON-9 Talkable Connector (#4589) Co-authored-by: Adrian Galvan --- CHANGELOG.md | 1 + data/saas/config/talkable_config.yml | 60 +++++++++++++ data/saas/dataset/talkable_dataset.yml | 63 ++++++++++++++ data/saas/icon/talkable.svg | 31 +++++++ tests/fixtures/saas/talkable_fixtures.py | 84 +++++++++++++++++++ .../saas/test_talkable_task.py | 44 ++++++++++ 6 files changed, 283 insertions(+) create mode 100644 data/saas/config/talkable_config.yml create mode 100644 data/saas/dataset/talkable_dataset.yml create mode 100644 data/saas/icon/talkable.svg create mode 100644 tests/fixtures/saas/talkable_fixtures.py create mode 100644 tests/ops/integration_tests/saas/test_talkable_task.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 72777a82a4..c371a32e5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ The types of changes are: ### Added - Add enum and registry of supported languages [#4592](https://github.com/ethyca/fides/pull/4592) +- Access and erasure support for Talkable [#4589](https://github.com/ethyca/fides/pull/4589) ### Fixed diff --git a/data/saas/config/talkable_config.yml b/data/saas/config/talkable_config.yml new file mode 100644 index 0000000000..8339d58bbb --- /dev/null +++ b/data/saas/config/talkable_config.yml @@ -0,0 +1,60 @@ +saas_config: + fides_key: + name: Talkable + type: talkable + description: A sample schema representing the talkable connector for Fides + version: 0.1.0 + + connector_params: + - name: domain + default_value: www.talkable.com + - name: site_slug + label: Site slug + description: Your Talkable site ID + - name: api_key + label: API key + sensitive: True + + client_config: + protocol: https + host: + authentication: + strategy: bearer + configuration: + token: + + test_request: + method: GET + path: /api/v2/campaigns + query_params: + - name: site_slug + value: + param_values: + - name: site_slug + connector_param: site_slug + + endpoints: + - name: person + requests: + read: + method: GET + path: /api/v2/people//personal_data + query_params: + - name: site_slug + value: + data_path: result.person + param_values: + - name: email + identity: email + - name: site_slug + connector_param: site_slug + update: + method: POST + path: /api/v2/people//anonymize + body: | + {"site_slug":""} + param_values: + - name: email + identity: email + - name: site_slug + connector_param: site_slug diff --git a/data/saas/dataset/talkable_dataset.yml b/data/saas/dataset/talkable_dataset.yml new file mode 100644 index 0000000000..b68284bec9 --- /dev/null +++ b/data/saas/dataset/talkable_dataset.yml @@ -0,0 +1,63 @@ +dataset: + - fides_key: + name: Talkable Dataset + description: A sample dataset representing the talkable connector for Fides + collections: + - name: person + fields: + - name: created_at + data_categories: [system.operations] + fidesops_meta: + data_type: string + - name: customer_id + data_categories: [user.unique_id] + fidesops_meta: + primary_key: True + - name: email + data_categories: [user.contact.email] + fidesops_meta: + data_type: string + - name: first_name + data_categories: [user.name.first] + fidesops_meta: + data_type: string + - name: last_name + data_categories: [user.name.last] + fidesops_meta: + data_type: string + - name: username + data_categories: [user.account.username] + fidesops_meta: + data_type: string + - name: origins + fidesops_meta: + data_type: "object[]" + fields: + - name: type + data_categories: [system.operations] + fidesops_meta: + data_type: string + - name: created_at + data_categories: [system.operations] + fidesops_meta: + data_type: string + - name: ip_address + data_categories: [user.device.ip_address] + fidesops_meta: + data_type: string + - name: order_number + data_categories: [system.operations] + fidesops_meta: + data_type: string + - name: subtotal + data_categories: [system.operations] + fidesops_meta: + data_type: integer + - name: shipping_address + data_categories: [user.contact.address] + fidesops_meta: + data_type: string + - name: shipping_zip + data_categories: [user.contact.address.postal_code] + fidesops_meta: + data_type: string diff --git a/data/saas/icon/talkable.svg b/data/saas/icon/talkable.svg new file mode 100644 index 0000000000..ba51458525 --- /dev/null +++ b/data/saas/icon/talkable.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/fixtures/saas/talkable_fixtures.py b/tests/fixtures/saas/talkable_fixtures.py new file mode 100644 index 0000000000..34dedb9027 --- /dev/null +++ b/tests/fixtures/saas/talkable_fixtures.py @@ -0,0 +1,84 @@ +from typing import Any, Dict, Generator + +import pydash +import pytest +import requests + +from tests.ops.integration_tests.saas.connector_runner import ( + ConnectorRunner, + generate_random_email, +) +from tests.ops.test_helpers.vault_client import get_secrets + +secrets = get_secrets("talkable") + + +@pytest.fixture(scope="session") +def talkable_secrets(saas_config) -> Dict[str, Any]: + return { + "domain": pydash.get(saas_config, "talkable.domain") or secrets["domain"], + "site_slug": pydash.get(saas_config, "talkable.site_slug") + or secrets["site_slug"], + "api_key": pydash.get(saas_config, "talkable.api_key") or secrets["api_key"], + } + + +@pytest.fixture(scope="session") +def talkable_identity_email(saas_config) -> str: + return ( + pydash.get(saas_config, "talkable.identity_email") or secrets["identity_email"] + ) + + +@pytest.fixture(scope="function") +def talkable_erasure_identity_email() -> str: + return generate_random_email() + + +@pytest.fixture(scope="function") +def talkable_erasure_data( + talkable_erasure_identity_email: str, + talkable_secrets, +) -> Generator: + # create the data needed for erasure tests here + base_url = f"https://{talkable_secrets['domain']}" + headers = { + "Authorization": f"Bearer {talkable_secrets['api_key']}", + } + + # person + body = { + "site_slug": talkable_secrets["site_slug"], + "data": { + "first_name": "Ethyca", + "last_name": "RTF", + "email": talkable_erasure_identity_email, + "phone_number": "+19515551234", + "username": "ethycatrtf", + "gated_param_subscribed": False, + "unsubscribed": False, + }, + } + + people_response = requests.put( + url=f"{base_url}/api/v2/people/{talkable_erasure_identity_email}", + headers=headers, + json=body, + ) + person = people_response.json()["result"]["person"] + + yield person + + +@pytest.fixture +def talkable_runner( + db, + cache, + talkable_secrets, +) -> ConnectorRunner: + return ConnectorRunner( + db, + cache, + "talkable", + talkable_secrets, + ) diff --git a/tests/ops/integration_tests/saas/test_talkable_task.py b/tests/ops/integration_tests/saas/test_talkable_task.py new file mode 100644 index 0000000000..fc4c033feb --- /dev/null +++ b/tests/ops/integration_tests/saas/test_talkable_task.py @@ -0,0 +1,44 @@ +import pytest + +from fides.api.models.policy import Policy +from tests.ops.integration_tests.saas.connector_runner import ConnectorRunner + + +@pytest.mark.integration_saas +class TestTalkableConnector: + def test_connection(self, talkable_runner: ConnectorRunner): + talkable_runner.test_connection() + + async def test_access_request( + self, talkable_runner: ConnectorRunner, policy, talkable_identity_email: str + ): + access_results = await talkable_runner.access_request( + access_policy=policy, identities={"email": talkable_identity_email} + ) + + # verify we only returned data for our identity email + assert ( + access_results["talkable_instance:person"][0]["email"] + == talkable_identity_email + ) + + async def test_non_strict_erasure_request( + self, + talkable_runner: ConnectorRunner, + policy: Policy, + erasure_policy_string_rewrite: Policy, + talkable_erasure_identity_email: str, + talkable_erasure_data, + ): + ( + access_results, + erasure_results, + ) = await talkable_runner.non_strict_erasure_request( + access_policy=policy, + erasure_policy=erasure_policy_string_rewrite, + identities={"email": talkable_erasure_identity_email}, + ) + + assert erasure_results == { + "talkable_instance:person": 1, + }