Skip to content
This repository has been archived by the owner on Nov 30, 2022. It is now read-only.

[SaaS Connector] Outreach (Erasures) #619

Merged
merged 54 commits into from
Jun 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
da0434e
Refactoring SaaS authentication in preparation for OAuth2
galvana May 12, 2022
a4c5e38
Formatting fixes
galvana May 12, 2022
c580312
Prepping new authentication module for merge with main
galvana May 12, 2022
6877f31
Merge branch 'main' into 488-saas-connector-authentication-strategies
galvana May 12, 2022
4002821
Adding missing class docstrings
galvana May 12, 2022
27e2013
Formatting fix
galvana May 12, 2022
69b3caa
Giving Segment more time for data to be available
galvana May 12, 2022
df8d2c3
Implementing PR recommendations
galvana May 16, 2022
6d11225
Formatting fix
galvana May 16, 2022
0160603
Fixing unit tests
galvana May 17, 2022
8d0a17d
Fixing return type for assign_placeholders
galvana May 17, 2022
d242140
Reorganizing imports to avoid cyclic dependency
galvana May 17, 2022
c9d9993
Starting point for OAuth2 authentication strategy
galvana May 18, 2022
4bec1b1
Adding mocks to OAuth2 strategy tests
galvana May 19, 2022
6b3f757
Adding get_authorization_url to OAuth2 strategy
galvana May 20, 2022
cd38680
Adding authorization and OAuth2 callback endpoints
galvana May 24, 2022
12417ca
Merge branch 'main' into 490-saas-connector-oauth2-authentication-str…
galvana May 24, 2022
8b7bfc0
Adding tests for new API endpoints
galvana May 24, 2022
cb848c2
Fixing linter errors
galvana May 24, 2022
19e6606
Fixing pylint and mypy errors
galvana May 25, 2022
8112992
Fixing unit test
galvana May 25, 2022
6440414
Fixing unit test
galvana May 25, 2022
d60fb00
Adding tests for scenarios without a refresh request and without an e…
galvana May 25, 2022
7e05582
Fixing pagination tests
galvana May 25, 2022
9f0b203
Fixing remaining pagination tests
galvana May 25, 2022
d641ded
Resolving merge conflict
galvana May 25, 2022
10983cb
Starting point for Outreach connector
galvana May 26, 2022
1caec91
First round of changes based on PR feedback
galvana May 28, 2022
0f7a3b4
Passing in db session where possible
galvana May 31, 2022
88a20c6
Fixing type hints
galvana May 31, 2022
89508be
Fixing type hints
galvana May 31, 2022
5514472
Merge branch 'main' into 490-saas-connector-oauth2-authentication-str…
galvana May 31, 2022
201a7d1
Fixing down_revision
galvana May 31, 2022
cd74c12
Merge branch '490-saas-connector-oauth2-authentication-strategy' into…
galvana May 31, 2022
04566fc
Adding "exact" and "case_sensitive" options to filter post-processor
galvana Jun 1, 2022
55b1bf9
Merge branch '583-saas-connectors-extend-filter-post-processor' into …
galvana Jun 1, 2022
dc33d5d
Updating changelog
galvana Jun 1, 2022
52645e4
Simplifying instance check
galvana Jun 1, 2022
31c20ff
Fixing unit test
galvana Jun 1, 2022
ec01ffb
Merge branch '583-saas-connectors-extend-filter-post-processor' into …
galvana Jun 2, 2022
95769fe
Dataset and access config for Outreach
galvana Jun 2, 2022
44755b0
Updating collection names
galvana Jun 2, 2022
7261e64
Updating changelog
galvana Jun 2, 2022
ba2f3e7
Merge branch 'main' into 255-saas-connector-outreach
galvana Jun 2, 2022
99a84e7
Starting point for Outreach erasures
galvana Jun 8, 2022
69a3e02
Adding erasure requests for prospects and recipients
galvana Jun 9, 2022
8c6fd64
Merge branch '255-saas-connector-outreach' into 618-saas-connector-ou…
galvana Jun 9, 2022
7bc412c
Merge branch 'main' into 618-saas-connector-outreach-erasure
galvana Jun 9, 2022
30183f0
Reverting incorrect changelog updates
galvana Jun 9, 2022
412580f
Too far
galvana Jun 9, 2022
7a22c1c
Updating changelog
galvana Jun 9, 2022
a2416af
Update saas_config.toml
galvana Jun 13, 2022
d68f068
Merge branch 'main' into 618-saas-connector-outreach-erasure
galvana Jun 13, 2022
d077d08
Skipping test for erasures
galvana Jun 13, 2022
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 @@ -26,6 +26,7 @@ The types of changes are:
* Celery as a dependency for use in the execution layer [#610](https://github.com/ethyca/fidesops/pull/610)
* Cache and Surface Resume/Restart Instructions [#591](https://github.com/ethyca/fidesops/pull/591)
* Allow disabling a ConnectionConfig [#637](https://github.com/ethyca/fidesops/pull/637)
* Erasure support for Outreach connector [#619](https://github.com/ethyca/fidesops/pull/619)

### Changed

Expand Down
43 changes: 43 additions & 0 deletions data/saas/config/outreach_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ saas_config:

connector_params:
- name: domain
- name: requester_email
description: The email of the Outreach user to associate with each automated compliance request (data_protection_request)
- name: client_id
- name: client_secret
- name: redirect_uri
- name: page_size
description: The number of entries to return per page

client_config:
protocol: https
Expand Down Expand Up @@ -83,6 +86,26 @@ saas_config:
- name: email
identity: email
data_path: data
delete:
method: POST
path: /api/v2/complianceRequests
param_values:
- name: requester_email
connector_param: requester_email
- name: email
identity: email
body: |
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In YAML the pipe | is used to indicate a "block scalar" value (multiline text). This allows us to denote JSON payloads inline with YAML while avoiding having to escape single or double quotes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's cool, good thing i got to review this PR and learn something new :)

{
"data": {
"type": "complianceRequest",
"attributes": {
"requester_email": "<requester_email>",
"request_type": "Delete",
"object_type": "Prospect",
"request_object_email": "<email>"
}
}
}
- name: recipients
requests:
read:
Expand Down Expand Up @@ -110,3 +133,23 @@ saas_config:
identity: email
exact: False
case_sensitive: False
delete:
method: POST
path: /api/v2/complianceRequests
param_values:
- name: requester_email
connector_param: requester_email
- name: email
identity: email
body: |
{
"data": {
"type": "complianceRequest",
"attributes": {
"requester_email": "<requester_email>",
"request_type": "Delete",
"object_type": "Recipient",
"request_object_email": "<email>"
}
}
}
4 changes: 4 additions & 0 deletions data/saas/dataset/outreach_dataset.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ dataset:
fields:
- name: id
data_categories: [system.operations]
fidesops_meta:
primary_key: True
- name: attributes
fields:
- name: createdAt
Expand All @@ -23,6 +25,8 @@ dataset:
fields:
- name: id
data_categories: [system.operations]
fidesops_meta:
primary_key: True
- name: attributes
fields:
- name: addressCity
Expand Down
4 changes: 3 additions & 1 deletion saas_config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,6 @@ domain = ""
client_id = ""
client_secret = ""
redirect_uri = ""
page_size = ""
page_size = ""
requester_email = ""
identity_email = ""
1 change: 1 addition & 0 deletions src/fidesops/schemas/saas/saas_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ class ConnectorParam(BaseModel):
"""Used to define the required parameters for the connector (user-provided and constants)"""

name: str
description: Optional[str]
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding this to include descriptions for fields that need more of an explanation than what can just be assumed from the name alone. Currently there is no additional logic in Fidesops that makes use of this information but this could be helpful when for when we have a UI to create/manage SaaS configs.



class SaaSConfig(BaseModel):
Expand Down
58 changes: 58 additions & 0 deletions tests/fixtures/saas/outreach_fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import pydash
import pytest
import requests
from fideslib.core.config import load_toml
from sqlalchemy.orm import Session

Expand All @@ -13,6 +14,7 @@
ConnectionType,
)
from fidesops.models.datasetconfig import DatasetConfig
from fidesops.util import cryptographic_util
from tests.fixtures.application_fixtures import load_dataset
from tests.fixtures.saas_example_fixtures import load_config

Expand All @@ -24,6 +26,8 @@ def outreach_secrets():
return {
"domain": pydash.get(saas_config, "outreach.domain")
or os.environ.get("OUTREACH_DOMAIN"),
"requester_email": pydash.get(saas_config, "outreach.requester_email")
or os.environ.get("OUTREACH_REQUESTER_EMAIL"),
"client_id": pydash.get(saas_config, "outreach.client_id")
or os.environ.get("OUTREACH_CLIENT_ID"),
"client_secret": pydash.get(saas_config, "outreach.client_secret")
Expand All @@ -42,6 +46,11 @@ def outreach_identity_email():
)


@pytest.fixture(scope="function")
def outreach_erasure_identity_email() -> str:
return f"{cryptographic_util.generate_secure_random_string(13)}@email.com"


@pytest.fixture
def outreach_config() -> Dict[str, Any]:
return load_config("data/saas/config/outreach_config.yml")
Expand Down Expand Up @@ -92,3 +101,52 @@ def outreach_dataset_config(
)
yield dataset
dataset.delete(db=db)


@pytest.fixture(scope="function")
def outreach_create_erasure_data(
outreach_connection_config: ConnectionConfig, outreach_erasure_identity_email: str
) -> None:

outreach_secrets = outreach_connection_config.secrets
base_url = f"https://{outreach_secrets['domain']}"
headers = {
"Authorization": f"Bearer {outreach_secrets['access_token']}",
}

# prospect
prospect_data = {
"data": {
"type": "prospect",
"attributes": {
"emails": [outreach_erasure_identity_email],
"firstName": "Ethyca",
"lastName": "RTF",
},
}
}

response = requests.post(
url=f"{base_url}/api/v2/prospects",
headers=headers,
json=prospect_data,
)
assert response.ok

# recipient
recipient_data = {
"data": {
"type": "recipient",
"attributes": {
"recipientType": "to",
"value": outreach_erasure_identity_email,
},
}
}

response = requests.post(
url=f"{base_url}/api/v2/recipients",
headers=headers,
json=recipient_data,
)
assert response.ok
63 changes: 63 additions & 0 deletions tests/integration_tests/saas/test_outreach_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import pytest

from fidesops.core.config import config
from fidesops.graph.graph import DatasetGraph
from fidesops.models.privacy_request import PrivacyRequest
from fidesops.schemas.redis_cache import PrivacyRequestIdentity
from fidesops.task import graph_task
from fidesops.task.filter_results import filter_data_categories
from fidesops.task.graph_task import get_cached_data_for_erasures
from tests.graph.graph_test_util import assert_rows_match


Expand Down Expand Up @@ -80,3 +82,64 @@ def test_outreach_access_request_task(
assert set(filtered_results[f"{dataset_name}:recipients"][0].keys()) == {
"attributes",
}


@pytest.mark.skip(reason="Currently unable to test OAuth2 connectors")
@pytest.mark.integration_saas
@pytest.mark.integration_outreach
def test_outreach_erasure_request_task(
db,
policy,
erasure_policy_string_rewrite,
outreach_connection_config,
outreach_dataset_config,
outreach_erasure_identity_email,
outreach_create_erasure_data,
) -> None:
"""Full erasure request based on the Outreach SaaS config"""
config.execution.MASKING_STRICT = False # Allow Delete

privacy_request = PrivacyRequest(
id=f"test_outreach_erasure_request_task_{random.randint(0, 1000)}"
)
identity = PrivacyRequestIdentity(**{"email": outreach_erasure_identity_email})
privacy_request.cache_identity(identity)

dataset_name = outreach_connection_config.get_saas_config().fides_key
merged_graph = outreach_dataset_config.get_graph()
graph = DatasetGraph(merged_graph)

v = graph_task.run_access_request(
privacy_request,
policy,
graph,
[outreach_connection_config],
{"email": outreach_erasure_identity_email},
)

# verify staged data is available for erasure
assert_rows_match(
v[f"{dataset_name}:prospects"],
min_size=1,
keys=["type", "id", "attributes", "relationships", "links"],
)
assert_rows_match(
v[f"{dataset_name}:recipients"],
min_size=1,
keys=["type", "id", "attributes", "links"],
)

x = graph_task.run_erasure(
privacy_request,
erasure_policy_string_rewrite,
graph,
[outreach_connection_config],
{"email": outreach_erasure_identity_email},
get_cached_data_for_erasures(privacy_request.id),
)

# Assert erasure request made to prospects and recipients
# cannot verify success immediately as this can take days, weeks to process
assert x == {f"{dataset_name}:prospects": 1, f"{dataset_name}:recipients": 1}

config.execution.MASKING_STRICT = True