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

fides ints 121 Marigold Engage by Sailthru Access and Erasure #4826

Merged
merged 20 commits into from
May 1, 2024
Merged
Show file tree
Hide file tree
Changes from 10 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
37 changes: 37 additions & 0 deletions data/saas/config/marigold_engage_config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
saas_config:
fides_key: <instance_fides_key>
name: Marigold Engage by Sailthru
type: marigold_engage
description: A sample schema representing the Marigold Engage via Sailthru connector for Fides
version: 0.1.0

connector_params:
- name: domain
default_value: api.sailthru.com
- name: api_key
label: API key
sensitive: True
- name: secret
label: Marigold secret
sensitive: True

client_config:
protocol: https
host: <domain>

test_request:
request_override: marigold_engage_test

endpoints:
- name: user
requests:
read:
request_override: marigold_engage_user_read
param_values:
- name: email
identity: email
delete:
request_override: marigold_engage_user_delete
param_values:
- name: email
identity: email
48 changes: 48 additions & 0 deletions data/saas/dataset/marigold_engage_dataset.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
dataset:
- fides_key: <instance_fides_key>
name: Marigold Engage Dataset
description: A sample dataset representing the Marigold Engage integration for Fides
collections:
- name: user
fields:
- name: activity
fidesops_meta:
data_type: object
fields:
- name: create_time
data_categories: [system.operations]
fidesops_meta:
data_type: string
- name: engagement
data_categories: [system.operations]
fidesops_meta:
data_type: string
- name: keys
fidesops_meta:
data_type: object
fields:
- name: sid
data_categories: [system.operations]
fidesops_meta:
data_type: string
- name: cookie
data_categories: [user.device.cookie_id]
fidesops_meta:
data_type: string
- name: email
data_categories: [user.contact.email]
fidesops_meta:
primary_key: True
data_type: string
- name: lists
- name: optout_email
data_categories: [system.operations]
fidesops_meta:
data_type: string
- name: smart_lists
# the vars here could be holding any kind of data, I'm not sure how best to cope with that
- name: vars
- name: purchases
- name: device
- name: purchase_incomplete
- name: lifetime
4 changes: 4 additions & 0 deletions data/saas/icon/marigold_engage.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,104 @@
import hashlib
import json
from typing import Any, Dict, List

from fides.api.graph.traversal import TraversalNode
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,
)
from fides.api.util.collection_util import Row


@register("marigold_engage_user_read", [SaaSRequestType.READ])
def marigold_engage_user_read(
client: AuthenticatedClient,
node: TraversalNode,
policy: Policy,
privacy_request: PrivacyRequest,
input_data: Dict[str, List[Any]],
secrets: Dict[str, Any],
) -> List[Row]:
"""Calls Marigold Engage's `GET /user` endpoint with a signed payload."""

output = []
emails = input_data.get("email", [])

Check warning on line 29 in src/fides/api/service/saas_request/override_implementations/marigold_engage_request_overrides.py

View check run for this annotation

Codecov / codecov/patch

src/fides/api/service/saas_request/override_implementations/marigold_engage_request_overrides.py#L28-L29

Added lines #L28 - L29 were not covered by tests
for email in emails:
payload = {

Check warning on line 31 in src/fides/api/service/saas_request/override_implementations/marigold_engage_request_overrides.py

View check run for this annotation

Codecov / codecov/patch

src/fides/api/service/saas_request/override_implementations/marigold_engage_request_overrides.py#L31

Added line #L31 was not covered by tests
"id": email,
"key": "email",
"fields": {
"activity": 1,
"engagement": 1,
"keys": 1,
"lists": 1,
"optout_email": 1,
"smart_lists": 1,
"vars": 1,
"purchases": 1,
"device": 1,
"purchase_incomplete": 1,
"lifetime": 1,
},
}
response = client.send(

Check warning on line 48 in src/fides/api/service/saas_request/override_implementations/marigold_engage_request_overrides.py

View check run for this annotation

Codecov / codecov/patch

src/fides/api/service/saas_request/override_implementations/marigold_engage_request_overrides.py#L48

Added line #L48 was not covered by tests
SaaSRequestParams(
method=HTTPMethod.GET,
path="/user",
query_params=signed_payload(secrets, payload),
)
)
user = response.json()
output.append(user)

Check warning on line 56 in src/fides/api/service/saas_request/override_implementations/marigold_engage_request_overrides.py

View check run for this annotation

Codecov / codecov/patch

src/fides/api/service/saas_request/override_implementations/marigold_engage_request_overrides.py#L55-L56

Added lines #L55 - L56 were not covered by tests

return output

Check warning on line 58 in src/fides/api/service/saas_request/override_implementations/marigold_engage_request_overrides.py

View check run for this annotation

Codecov / codecov/patch

src/fides/api/service/saas_request/override_implementations/marigold_engage_request_overrides.py#L58

Added line #L58 was not covered by tests


@register("marigold_engage_user_delete", [SaaSRequestType.DELETE])
def marigold_engage_user_delete(
client: AuthenticatedClient,
param_values_per_row: List[Dict[str, Any]],
policy: Policy,
privacy_request: PrivacyRequest,
secrets: Dict[str, Any],
) -> int:
"""Calls Marigold Engage's `DELETE /user` endpoint with a signed payload."""

rows_deleted = 0

Check warning on line 71 in src/fides/api/service/saas_request/override_implementations/marigold_engage_request_overrides.py

View check run for this annotation

Codecov / codecov/patch

src/fides/api/service/saas_request/override_implementations/marigold_engage_request_overrides.py#L71

Added line #L71 was not covered by tests
for row_param_values in param_values_per_row:
email = row_param_values["email"]
client.send(

Check warning on line 74 in src/fides/api/service/saas_request/override_implementations/marigold_engage_request_overrides.py

View check run for this annotation

Codecov / codecov/patch

src/fides/api/service/saas_request/override_implementations/marigold_engage_request_overrides.py#L73-L74

Added lines #L73 - L74 were not covered by tests
SaaSRequestParams(
method=HTTPMethod.DELETE,
path="/user",
query_params=signed_payload(secrets, {"id": email}),
)
)
rows_deleted += 1
return rows_deleted

Check warning on line 82 in src/fides/api/service/saas_request/override_implementations/marigold_engage_request_overrides.py

View check run for this annotation

Codecov / codecov/patch

src/fides/api/service/saas_request/override_implementations/marigold_engage_request_overrides.py#L81-L82

Added lines #L81 - L82 were not covered by tests


def signed_payload(secrets: Dict[str, Any], payload: Dict[str, Any]) -> Dict[str, Any]:
"""
Creates a signed payload dictionary with an MD5 hash of the secret, API key, format, and payload.
"""

# the signature is the md5 hash of the concatenated string
# of secret, API key, format, and stringified payload
stringified_payload = json.dumps(payload)
parameter_values = (

Check warning on line 93 in src/fides/api/service/saas_request/override_implementations/marigold_engage_request_overrides.py

View check run for this annotation

Codecov / codecov/patch

src/fides/api/service/saas_request/override_implementations/marigold_engage_request_overrides.py#L92-L93

Added lines #L92 - L93 were not covered by tests
f'{secrets["secret"]}{secrets["api_key"]}json{stringified_payload}'
)
hash_value = hashlib.md5(parameter_values.encode())
sig = hash_value.hexdigest()

Check warning on line 97 in src/fides/api/service/saas_request/override_implementations/marigold_engage_request_overrides.py

View check run for this annotation

Codecov / codecov/patch

src/fides/api/service/saas_request/override_implementations/marigold_engage_request_overrides.py#L96-L97

Added lines #L96 - L97 were not covered by tests

return {

Check warning on line 99 in src/fides/api/service/saas_request/override_implementations/marigold_engage_request_overrides.py

View check run for this annotation

Codecov / codecov/patch

src/fides/api/service/saas_request/override_implementations/marigold_engage_request_overrides.py#L99

Added line #L99 was not covered by tests
"api_key": secrets["api_key"],
"sig": sig,
"format": "json",
"json": stringified_payload,
}
71 changes: 71 additions & 0 deletions tests/fixtures/saas/marigold_engage_fixtures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from typing import Any, Dict

import pydash
import pytest
import requests

from fides.api.service.saas_request.override_implementations.marigold_engage_request_overrides import (
signed_payload,
)
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("marigold_engage")

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


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


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


@pytest.fixture
def marigold_engage_erasure_data(
marigold_engage_secrets,
marigold_engage_erasure_identity_email: str,
) -> None:
base_url = f'https://{marigold_engage_secrets["domain"]}/user'
response = requests.request(
"POST",
base_url,
headers={"Content-Type": "application/x-www-form-urlencoded"},
data=signed_payload(
marigold_engage_secrets, {"id": marigold_engage_erasure_identity_email}
),
)
assert response.ok


@pytest.fixture
def marigold_engage_runner(
db,
cache,
marigold_engage_secrets,
) -> ConnectorRunner:
return ConnectorRunner(
db,
cache,
"marigold_engage",
marigold_engage_secrets,
)
40 changes: 40 additions & 0 deletions tests/ops/integration_tests/saas/test_marigold_engage_task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import pytest

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


@pytest.mark.integration_saas
class TestMarigoldEngageConnector:
def test_connection(self, marigold_engage_runner: ConnectorRunner):
marigold_engage_runner.test_connection()

async def test_access_request(
self,
marigold_engage_runner: ConnectorRunner,
policy,
marigold_engage_identity_email: str,
):
access_results = await marigold_engage_runner.access_request(
access_policy=policy, identities={"email": marigold_engage_identity_email}
)
for user in access_results["marigold_engage_instance:user"]:
assert user["keys"]["email"] == marigold_engage_identity_email

async def test_non_strict_erasure_request(
self,
marigold_engage_runner: ConnectorRunner,
policy: Policy,
erasure_policy_string_rewrite: Policy,
marigold_engage_erasure_identity_email: str,
marigold_engage_erasure_data,
):
(
_,
erasure_results,
) = await marigold_engage_runner.non_strict_erasure_request(
access_policy=policy,
erasure_policy=erasure_policy_string_rewrite,
identities={"email": marigold_engage_erasure_identity_email},
)
assert erasure_results == {"marigold_engage_instance:user": 1}
Loading