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

Add integration for Alchemer Erasure #4925

Merged
merged 49 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
2f6bfb6
Initial commit
MarcGEthyca May 24, 2024
210b806
Minor fixes
MarcGEthyca May 24, 2024
c30a408
non_strict_erasure started
MarcGEthyca May 24, 2024
6bf24be
light fixes
MarcGEthyca May 28, 2024
a4483e0
Passing test connection and erasure stage 1
MarcGEthyca May 28, 2024
39923a6
passing but still no primary key in dataset
MarcGEthyca May 28, 2024
1d223aa
Adding icon
MarcGEthyca May 30, 2024
a9e75c3
Merge branch 'main' into fides_ints_2_alchemer
MarcGEthyca May 31, 2024
ddb6862
Merge branch 'main' into fides_ints_2_alchemer
MarcGEthyca May 31, 2024
ace305a
Fixes
MarcGEthyca Jun 4, 2024
12d131a
testing
MarcGEthyca Jun 4, 2024
061098f
corrected assert
MarcGEthyca Jun 4, 2024
7b03f23
Fixed assert
MarcGEthyca Jun 4, 2024
e9ce01a
clean up config
MarcGEthyca Jun 4, 2024
a4c0780
tests failing with the correct message
MarcGEthyca Jun 4, 2024
886b5ca
prototype override
MarcGEthyca Jun 6, 2024
225723b
reverting
MarcGEthyca Jun 6, 2024
b10d2f5
comment change
MarcGEthyca Jun 6, 2024
b1df8f7
override testing
MarcGEthyca Jun 6, 2024
5818da4
small fixes
MarcGEthyca Jun 7, 2024
9f11396
updating to erasure by override
MarcGEthyca Jun 19, 2024
6d770c6
staging changes
MarcGEthyca Jun 19, 2024
48873a0
changes to make it erasure only
MarcGEthyca Jun 19, 2024
df83878
added empty return
MarcGEthyca Jun 19, 2024
b161f70
Merge branch 'main' into fides_ints_2_alchemer
MarcGEthyca Jun 19, 2024
68b36e7
added int to return
MarcGEthyca Jun 19, 2024
175df2f
readding hardcoded email
MarcGEthyca Jun 19, 2024
360f27e
comment editing
MarcGEthyca Jun 19, 2024
399a0b9
trying to get past initial auto testing errors
MarcGEthyca Jun 19, 2024
b1110ce
changing to int
MarcGEthyca Jun 19, 2024
610a00b
removed unused import
MarcGEthyca Jun 19, 2024
d3ad0e0
fixed comment
MarcGEthyca Jun 25, 2024
b0285fe
Fixes - currently all tests passing
MarcGEthyca Jul 11, 2024
4309546
Merge branch 'main' into fides_ints_2_alchemer
MarcGEthyca Jul 11, 2024
da6566b
cleanup
MarcGEthyca Jul 11, 2024
c76ba40
fixes and cleanup
MarcGEthyca Jul 11, 2024
3cfbd9f
cleanup some naming changes
MarcGEthyca Jul 11, 2024
216cc14
all tests passing, UI working
MarcGEthyca Jul 12, 2024
d3aea0f
static_checks changes
MarcGEthyca Jul 12, 2024
00ce568
Merge branch 'main' into fides_ints_2_alchemer
MarcGEthyca Jul 12, 2024
39fe216
fixed casing
MarcGEthyca Jul 12, 2024
9d38a18
fixed naming, swapped to use exiting connection
MarcGEthyca Jul 12, 2024
add510f
removed comments
MarcGEthyca Jul 12, 2024
d092e83
Update tests/fixtures/saas/alchemer_fixtures.py
MarcGEthyca Jul 15, 2024
85849ac
fixes
MarcGEthyca Jul 15, 2024
6bb6a85
fixes
MarcGEthyca Jul 15, 2024
9343a11
Merge branch 'main' into fides_ints_2_alchemer
MarcGEthyca Jul 15, 2024
e943b9b
static_checks changes
MarcGEthyca Jul 15, 2024
fa48624
Merge branch 'main' into fides_ints_2_alchemer
MarcGEthyca Jul 15, 2024
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
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.40.0...main)

### Added
- Added erasure support for Alchemer integration [#4925](https://github.com/ethyca/fides/pull/4925)

### Developer Experience
- Upgrade to React 18 and Chakra 2, including other dependencies [#5036](https://github.com/ethyca/fides/pull/5036)

Expand Down
46 changes: 46 additions & 0 deletions data/saas/config/alchemer_config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
saas_config:
fides_key: <instance_fides_key>
name: Alchemer
type: alchemer
description: A sample schema representing the Alchemer erasure only integration for Fides
user_guide: https://docs.ethyca.com/user-guides/integrations/saas-integrations/alchemer
version: 0.1.0

connector_params:
- name: domain
label: Domain
description: The API domain for Alchemer. Default api.alchemer.com
default_value: api.alchemer.com
- name: api_key
label: API key
description: The API key for Alchemer
sensitive: True
- name: api_key_secret
label: API key secret
description: The API key secret for Alchemer
sensitive: True

client_config:
protocol: https
host: <domain>
authentication:
strategy: api_key
configuration:
query_params:
- name: api_token
value: <api_key>
- name: api_token_secret
value: <api_key_secret>

test_request:
method: GET
path: /v5/account

endpoints:
- name: user
requests:
delete:
request_override: alchemer_user_delete
param_values:
- name: email
identity: email
9 changes: 9 additions & 0 deletions data/saas/dataset/alchemer_dataset.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
dataset:
- fides_key: <instance_fides_key>
name: alchemer_erasure_only
MarcGEthyca marked this conversation as resolved.
Show resolved Hide resolved
description: >-
A sample dataset representing the Alchemer erasure only connector for
Fides
MarcGEthyca marked this conversation as resolved.
Show resolved Hide resolved
collections:
- name: user
galvana marked this conversation as resolved.
Show resolved Hide resolved
fields: []
3 changes: 3 additions & 0 deletions data/saas/icon/alchemer.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from typing import Any, Dict, List

from fides.api.models.policy import Policy
from fides.api.models.privacy_request import PrivacyRequest
from fides.api.schemas.saas.shared_schemas import HTTPMethod, SaaSRequestParams
from fides.api.service.connectors.saas.authenticated_client import AuthenticatedClient
from fides.api.service.saas_request.saas_request_override_factory import (
SaaSRequestType,
register,
)


@register("alchemer_user_delete", [SaaSRequestType.DELETE])
def alchemer_user_delete(
client: AuthenticatedClient,
param_values_per_row: List[Dict[str, Any]],
policy: Policy,
privacy_request: PrivacyRequest,
secrets: Dict[str, Any],
) -> int:
# The delete endpoint has a structure like this
# https://api.alchemer.com/v5/contactlist/31/contactlistcontact/100012345?_method=DELETE
# where 31 is the contact list id and 100012345 is the contact id
# So we first get all the contact lists and extract their ids
# Then we query for all contacts in each list, filtering on our identity email
# Then we call a delete on the contact
rows_deleted = 0
params = {

Check warning on line 28 in src/fides/api/service/saas_request/override_implementations/alchemer_request_overrides.py

View check run for this annotation

Codecov / codecov/patch

src/fides/api/service/saas_request/override_implementations/alchemer_request_overrides.py#L27-L28

Added lines #L27 - L28 were not covered by tests
"api_token": secrets["api_key"],
"api_token_secret": secrets["api_key_secret"],
}
get_list_ids = client.send(

Check warning on line 32 in src/fides/api/service/saas_request/override_implementations/alchemer_request_overrides.py

View check run for this annotation

Codecov / codecov/patch

src/fides/api/service/saas_request/override_implementations/alchemer_request_overrides.py#L32

Added line #L32 was not covered by tests
SaaSRequestParams(
method=HTTPMethod.GET,
path="/v5/contactlist",
params=params,
)
)

list_ids_data = get_list_ids.json()
list_results = []

Check warning on line 41 in src/fides/api/service/saas_request/override_implementations/alchemer_request_overrides.py

View check run for this annotation

Codecov / codecov/patch

src/fides/api/service/saas_request/override_implementations/alchemer_request_overrides.py#L40-L41

Added lines #L40 - L41 were not covered by tests
for list_id in list_ids_data["data"]:
list_results.append(list_id["id"])

Check warning on line 43 in src/fides/api/service/saas_request/override_implementations/alchemer_request_overrides.py

View check run for this annotation

Codecov / codecov/patch

src/fides/api/service/saas_request/override_implementations/alchemer_request_overrides.py#L43

Added line #L43 was not covered by tests
for list_result in list_results:
contacts_data_call = client.send(

Check warning on line 45 in src/fides/api/service/saas_request/override_implementations/alchemer_request_overrides.py

View check run for this annotation

Codecov / codecov/patch

src/fides/api/service/saas_request/override_implementations/alchemer_request_overrides.py#L45

Added line #L45 was not covered by tests
SaaSRequestParams(
method=HTTPMethod.GET,
path=f"/v5/contactlist/{list_result}/contactlistcontact",
params=params,
)
)
contacts_data = contacts_data_call.json()

Check warning on line 52 in src/fides/api/service/saas_request/override_implementations/alchemer_request_overrides.py

View check run for this annotation

Codecov / codecov/patch

src/fides/api/service/saas_request/override_implementations/alchemer_request_overrides.py#L52

Added line #L52 was not covered by tests
for contact in contacts_data["data"]:
for row_param_values in param_values_per_row:
email = row_param_values["email"]

Check warning on line 55 in src/fides/api/service/saas_request/override_implementations/alchemer_request_overrides.py

View check run for this annotation

Codecov / codecov/patch

src/fides/api/service/saas_request/override_implementations/alchemer_request_overrides.py#L55

Added line #L55 was not covered by tests
if contact["email_address"] == email:
client.send(

Check warning on line 57 in src/fides/api/service/saas_request/override_implementations/alchemer_request_overrides.py

View check run for this annotation

Codecov / codecov/patch

src/fides/api/service/saas_request/override_implementations/alchemer_request_overrides.py#L57

Added line #L57 was not covered by tests
SaaSRequestParams(
method=HTTPMethod.DELETE,
path=f"/v5/contactlist/{list_result}/contactlistcontact/{contact['id']}",
params=params,
)
)
rows_deleted += 1

Check warning on line 64 in src/fides/api/service/saas_request/override_implementations/alchemer_request_overrides.py

View check run for this annotation

Codecov / codecov/patch

src/fides/api/service/saas_request/override_implementations/alchemer_request_overrides.py#L64

Added line #L64 was not covered by tests

return rows_deleted

Check warning on line 66 in src/fides/api/service/saas_request/override_implementations/alchemer_request_overrides.py

View check run for this annotation

Codecov / codecov/patch

src/fides/api/service/saas_request/override_implementations/alchemer_request_overrides.py#L66

Added line #L66 was not covered by tests
96 changes: 96 additions & 0 deletions tests/fixtures/saas/alchemer_fixtures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import random
import string
import time
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("alchemer")


@pytest.fixture(scope="session")
def alchemer_secrets(saas_config) -> Dict[str, Any]:
return {
"domain": pydash.get(saas_config, "alchemer.domain") or secrets["domain"],
"api_key": pydash.get(saas_config, "alchemer.api_key")
or secrets["api_key"],
"api_key_secret": pydash.get(saas_config, "alchemer.api_key_secret")
or secrets["api_key_secret"],
}


@pytest.fixture(scope="session")
def alchemer_identity_email(saas_config) -> str:
return (
pydash.get(saas_config, "alchemer.identity_email") or secrets["identity_email"]
)


@pytest.fixture
def alchemer_erasure_identity_email() -> str:
return generate_random_email()


@pytest.fixture
def alchemer_external_references() -> Dict[str, Any]:
return {}


@pytest.fixture
def alchemer_erasure_external_references() -> Dict[str, Any]:
return {}
MarcGEthyca marked this conversation as resolved.
Show resolved Hide resolved


@pytest.fixture
def alchemer_erasure_data(
alchemer_erasure_identity_email: str,
alchemer_secrets,
) -> Generator:
gen_string = string.ascii_lowercase
test_contactlist = "".join(random.choice(gen_string) for i in range(10))
x_contactlist_name = f"Ethyca Test {test_contactlist}"

base_url = f"https://{alchemer_secrets['domain']}/v5"
params = {
"api_token": alchemer_secrets["api_key"],
"api_token_secret": alchemer_secrets["api_key_secret"],
"list_name": x_contactlist_name,
}
contactlist_url = f"{base_url}/contactlist/"
response = requests.put(contactlist_url, params=params)

contactlist_id = response.json()["data"]["id"]
contactlistcontact_url = f"{contactlist_url}{contactlist_id}/contactlistcontact"
params = {
"api_token": alchemer_secrets["api_key"],
"api_token_secret": alchemer_secrets["api_key_secret"],
"email_address": alchemer_erasure_identity_email,
}
response = requests.put(contactlistcontact_url, params=params)
time.sleep(5)


@pytest.fixture
def alchemer_runner(
db,
cache,
alchemer_secrets,
alchemer_external_references,
alchemer_erasure_external_references,
MarcGEthyca marked this conversation as resolved.
Show resolved Hide resolved
) -> ConnectorRunner:
return ConnectorRunner(
db,
cache,
"alchemer",
alchemer_secrets,
external_references=alchemer_external_references,
erasure_external_references=alchemer_erasure_external_references,
MarcGEthyca marked this conversation as resolved.
Show resolved Hide resolved
)
30 changes: 30 additions & 0 deletions tests/ops/integration_tests/saas/test_alchemer_task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import pytest

from fides.api.models.policy import Policy
from tests.ops.integration_tests.saas.connector_runner import ConnectorRunner


@pytest.mark.integration_saas
MarcGEthyca marked this conversation as resolved.
Show resolved Hide resolved
class TestAlchemerConnector:
def test_connection(self, alchemer_runner: ConnectorRunner):
alchemer_runner.test_connection()

async def test_non_strict_erasure_request(
self,
alchemer_runner: ConnectorRunner,
policy: Policy,
erasure_policy_string_rewrite: Policy,
alchemer_erasure_identity_email: str,
alchemer_erasure_data,
):
(
_,
erasure_results,
) = await alchemer_runner.non_strict_erasure_request(
access_policy=policy,
erasure_policy=erasure_policy_string_rewrite,
identities={"email": alchemer_erasure_identity_email},
)
galvana marked this conversation as resolved.
Show resolved Hide resolved
assert erasure_results == {
"alchemer_instance:user": 1,
}
Loading