Skip to content

Commit

Permalink
LA 83 Fix Salesforce Erasure Data Flow (#5452)
Browse files Browse the repository at this point in the history
  • Loading branch information
Vagoasdf authored Nov 6, 2024
1 parent 0234347 commit 0e36182
Show file tree
Hide file tree
Showing 4 changed files with 283 additions and 1,167 deletions.
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,
)
)
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

0 comments on commit 0e36182

Please sign in to comment.