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

LA 83 Fix Salesforce Erasure Data Flow #5452

Merged
merged 14 commits into from
Nov 6, 2024
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ The types of changes are:
### Fixed
- Fix rendering of subfield names in D&D tables [#5439](https://github.com/ethyca/fides/pull/5439)
- Fix "Save" button on system source/destination page not working [#5469](https://github.com/ethyca/fides/pull/5469)
- Updating Salesforce erasure request with overrides so it properly passes validation. Removing Account endpoint since it does not contain user data [#5452](https://github.com/ethyca/fides/pull/5452)

### Developer Experience
- Added Carbon Icons to FidesUI [#5416](https://github.com/ethyca/fides/pull/5416)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
from json import dumps
from typing import Any, Dict, List

from loguru import logger

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,
)


def truncate_fields_to_40_characters(masked_object_fields: Dict) -> Dict:
"""
Check if the masked field is over 40 characters long, if so truncate it to 40 characters.
"""
for key in masked_object_fields:
if isinstance(masked_object_fields[key], str) and len(masked_object_fields[key]) > 40:
logger.info("Truncating {key} field to 40 characters")
masked_object_fields[key] = masked_object_fields[key][:40]
return masked_object_fields



def mask_email(masked_object_fields: Dict, email_field: str) -> Dict:
"""
Masking Email properly so it does not breaks validation rules
"""
if email_field in masked_object_fields:
masked_object_fields[email_field] = "masked@company.com"
return masked_object_fields


@register("salesforce_contacts_update", [SaaSRequestType.UPDATE])
def salesforce_contacts_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:

masked_object_fields = row_param_values["masked_object_fields"]

masked_object_fields = mask_email(masked_object_fields, "Email")

masked_object_fields = truncate_fields_to_40_characters(masked_object_fields)

update_body = dumps(masked_object_fields)
contact_id = row_param_values["contact_id"]
client.send(
SaaSRequestParams(
method=HTTPMethod.PATCH,
headers={"Content-Type": "application/json"},
path=f"/services/data/v54.0/sobjects/Contact/{contact_id}",
body=update_body,
galvana marked this conversation as resolved.
Show resolved Hide resolved
)
)
rows_updated += 1
return rows_updated


@register("salesforce_cases_update", [SaaSRequestType.UPDATE])
def salesforce_cases_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:
masked_object_fields = row_param_values["masked_object_fields"]

masked_object_fields = mask_email(masked_object_fields, "SuppliedEmail")

masked_object_fields = truncate_fields_to_40_characters(masked_object_fields)

update_body = dumps(masked_object_fields)
case_id = row_param_values["case_id"]
client.send(
SaaSRequestParams(
method=HTTPMethod.PATCH,
headers={"Content-Type": "application/json"},
path=f"/services/data/v54.0/sobjects/Case/{case_id}",
body=update_body,
)
)
rows_updated += 1
return rows_updated


@register("salesforce_leads_update", [SaaSRequestType.UPDATE])
def salesforce_leads_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:
masked_object_fields = row_param_values["masked_object_fields"]

masked_object_fields = mask_email(masked_object_fields, "Email")

masked_object_fields = truncate_fields_to_40_characters(masked_object_fields)

update_body = dumps(masked_object_fields)
lead_id = row_param_values["lead_id"]
client.send(
SaaSRequestParams(
method=HTTPMethod.PATCH,
headers={"Content-Type": "application/json"},
path=f"/services/data/v54.0/sobjects/Lead/{lead_id}",
body=update_body,
)
)
rows_updated += 1
return rows_updated
76 changes: 10 additions & 66 deletions tests/fixtures/saas/salesforce_fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
load_dataset_with_replacement,
)
from tests.ops.test_helpers.vault_client import get_secrets
from tests.ops.integration_tests.saas.connector_runner import ConnectorRunner

secrets = get_secrets("salesforce")

Expand Down Expand Up @@ -62,72 +63,6 @@ def salesforce_erasure_identity_email():
return f"{cryptographic_util.generate_secure_random_string(13)}@email.com"


@pytest.fixture
def salesforce_config() -> Dict[str, Any]:
return load_config_with_replacement(
"data/saas/config/salesforce_config.yml",
"<instance_fides_key>",
"salesforce_instance",
)


@pytest.fixture
def salesforce_dataset() -> Dict[str, Any]:
return load_dataset_with_replacement(
"data/saas/dataset/salesforce_dataset.yml",
"<instance_fides_key>",
"salesforce_instance",
)[0]


@pytest.fixture(scope="function")
def salesforce_connection_config(
db: session,
salesforce_config,
salesforce_secrets,
) -> Generator:
fides_key = salesforce_config["fides_key"]
connection_config = ConnectionConfig.create(
db=db,
data={
"key": fides_key,
"name": fides_key,
"connection_type": ConnectionType.saas,
"access": AccessLevel.write,
"secrets": salesforce_secrets,
"saas_config": salesforce_config,
},
)
yield connection_config
connection_config.delete(db)


@pytest.fixture
def salesforce_dataset_config(
db: Session,
salesforce_connection_config: ConnectionConfig,
salesforce_dataset: Dict[str, Any],
) -> Generator:
fides_key = salesforce_dataset["fides_key"]
salesforce_connection_config.name = fides_key
salesforce_connection_config.key = fides_key
salesforce_connection_config.save(db=db)

ctl_dataset = CtlDataset.create_from_dataset_dict(db, salesforce_dataset)

dataset = DatasetConfig.create(
db=db,
data={
"connection_config_id": salesforce_connection_config.id,
"fides_key": fides_key,
"ctl_dataset_id": ctl_dataset.id,
},
)
yield dataset
dataset.delete(db=db)
ctl_dataset.delete(db=db)


@pytest.fixture(scope="function")
def salesforce_create_erasure_data(
salesforce_erasure_identity_email, salesforce_secrets
Expand Down Expand Up @@ -251,3 +186,12 @@ def salesforce_create_erasure_data(
headers=headers,
)
assert account_response.status_code == HTTP_404_NOT_FOUND


@pytest.fixture
def salesforce_runner(
db,
cache,
salesforce_secrets,
) -> ConnectorRunner:
return ConnectorRunner(db, cache, "salesforce", salesforce_secrets)
Loading
Loading