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

Braze Override for Email #4187

Merged
merged 4 commits into from
Sep 29, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ The types of changes are:
### Fixed
- TCF overlay can initialize its consent preferences from a cookie [#4124](https://github.com/ethyca/fides/pull/4124)
- Various improvements to the TCF modal such as vendor storage disclosures, vendor counts, privacy policies, etc. [#4167](https://github.com/ethyca/fides/pull/4167)
- An issue where Braze could not mask an email due to formatting [#4187](https://github.com/ethyca/fides/pull/4187)

## [2.21.0](https://github.com/ethyca/fides/compare/2.20.2...2.21.0)

Expand Down Expand Up @@ -55,7 +56,7 @@ The types of changes are:

## [2.20.2](https://github.com/ethyca/fides/compare/2.20.1...2.20.2)

### Fixed
### Fixed
- added version_added, version_deprecated, and replaced_by to data use, data subject, and data category APIs [#4135](https://github.com/ethyca/fides/pull/4135)
- Update fides.js to not fetch experience client-side if pre-fetched experience is empty [#4149](https://github.com/ethyca/fides/pull/4149)
- Erasure privacy requests now pause for input if there are any manual process integrations [#4115](https://github.com/ethyca/fides/pull/4115)
Expand Down
13 changes: 2 additions & 11 deletions data/saas/config/braze_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ saas_config:
type: braze
description: A sample schema representing the Braze connector for Fides
user_guide: https://docs.ethyca.com/user-guides/integrations/saas-integrations/braze
version: 0.0.4
version: 0.0.5

connector_params:
- name: domain
Expand Down Expand Up @@ -125,16 +125,7 @@ saas_config:
identity: phone_number
data_path: users
update:
method: POST
path: /users/track
body: |
{
"attributes": [
{
<all_object_fields>
}
]
}
request_override: braze_user_update
- name: subscription_groups
requests:
read:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from json import dumps
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,
)
from fides.api.util.saas_util import PRIVACY_REQUEST_ID


@register("braze_user_update", [SaaSRequestType.UPDATE])
def braze_user_update(
client: AuthenticatedClient,
param_values_per_row: List[Dict[str, Any]],
policy: Policy,
privacy_request: PrivacyRequest,
secrets: Dict[str, Any],
) -> int:
rows_updated = 0
# each update_params dict correspond to a record that needs to be updated
for row_param_values in param_values_per_row:
# check if the privacy_request targeted emails for erasure,
# if so rewrite with a format that can be accepted by Braze
# regardless of the masking strategy in use
all_object_fields = row_param_values["all_object_fields"]

if "user.contact.email" in policy.get_erasure_target_categories():
privacy_request_id = row_param_values[PRIVACY_REQUEST_ID]
all_object_fields["email"] = f"{privacy_request_id}@company.com"

update_body = dumps({"attributes": [all_object_fields]})
client.send(
SaaSRequestParams(
method=HTTPMethod.POST,
headers={"Content-Type": "application/json"},
path="/users/track",
body=update_body,
)
)
rows_updated += 1
return rows_updated
15 changes: 6 additions & 9 deletions tests/fixtures/saas/braze_fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,15 +184,15 @@ def braze_erasure_data(

assert response.ok

sleep(30)

error_message = (
f"User with email {braze_erasure_identity_email} could not be added to Braze"
)
poll_for_existence(
_user_exists,
(braze_erasure_identity_email, braze_secrets),
error_message=error_message,
interval=60,
verification_count=10,
)

yield response_data
Expand Down Expand Up @@ -231,17 +231,14 @@ def _user_exists(braze_erasure_identity_email: str, braze_secrets):
}
body = {
"email_address": braze_erasure_identity_email,
"fields_to_export": ["email"],
"fields_to_export": ["email", "first_name"],
}

user_response = requests.post(
response = requests.post(
url=f"{base_url}/users/export/ids",
json=body,
headers=headers,
)
users = response.json().get("users")

# we expect 404 if user doesn't exist
if 404 == user_response.status_code:
return None

return user_response.json()
return response if users and users[0]["first_name"] != "MASKED" else None
49 changes: 10 additions & 39 deletions tests/ops/integration_tests/saas/test_braze_task.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import random
from time import sleep

import pytest
import requests

from fides.api.graph.graph import DatasetGraph
from fides.api.models.privacy_request import PrivacyRequest
Expand All @@ -11,7 +9,9 @@
from fides.api.task import graph_task
from fides.api.task.graph_task import get_cached_data_for_erasures
from fides.config import CONFIG
from tests.fixtures.saas.braze_fixtures import _user_exists
from tests.ops.graph.graph_test_util import assert_rows_match
from tests.ops.test_helpers.saas_test_utils import poll_for_existence


@pytest.mark.integration_saas
Expand Down Expand Up @@ -159,7 +159,7 @@ async def test_braze_access_request_task_with_phone_number(
async def test_braze_erasure_request_task(
db,
policy,
erasure_policy_string_rewrite,
erasure_policy_string_rewrite_name_and_email,
braze_connection_config,
braze_dataset_config,
braze_erasure_identity_email,
Expand Down Expand Up @@ -213,7 +213,7 @@ async def test_braze_erasure_request_task(

x = await graph_task.run_erasure(
privacy_request,
erasure_policy_string_rewrite,
erasure_policy_string_rewrite_name_and_email,
graph,
[braze_connection_config],
identity_kwargs,
Expand All @@ -226,41 +226,12 @@ async def test_braze_erasure_request_task(
f"{dataset_name}:subscription_groups": 0,
}

sleep(30)

# Verifying field is masked
braze_secrets = braze_connection_config.secrets
base_url = f"https://{braze_secrets['domain']}"
headers = {
"Authorization": f"Bearer {braze_secrets['api_key']}",
}
body = {
"email_address": braze_erasure_identity_email,
"fields_to_export": [
"braze_id",
"country",
"dob",
"email",
"external_id",
"first_name",
"gender",
"home_city",
"language",
"last_name",
"phone",
"user_aliases",
],
}

user_response = requests.post(
url=f"{base_url}/users/export/ids",
json=body,
headers=headers,
# _users_exists will return None if the first_name has been masked
poll_for_existence(
_user_exists,
(braze_erasure_identity_email, braze_connection_config.secrets),
interval=30,
existence_desired=False,
)
users = user_response.json().get("users")

for user in users:
assert user["first_name"] == "MASKED"
assert user["last_name"] == "MASKED"

CONFIG.execution.masking_strict = temp_masking
Loading