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

Adding OAuth2 client credentials support for Auth0 connector #2820

Merged
merged 8 commits into from
Mar 21, 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ The types of changes are:

### Changed
* Removed dataset based email connectors [#2782](https://github.com/ethyca/fides/pull/2782)
* Changed Auth0's authentication strategy from `bearer` to `oauth2_client_credentials` [#2820](https://github.com/ethyca/fides/pull/2820)

### Fixed
* Fixed issue where the scopes list passed into FidesUserPermission could get mutated with the total_scopes call [#2883](https://github.com/ethyca/fides/pull/2883)
Expand Down
39 changes: 28 additions & 11 deletions data/saas/config/auth0_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,41 @@ saas_config:
fides_key: <instance_fides_key>
name: Auth0 SaaS Config
type: auth0
description: A sample schema representing the Auth0 connector for Fidesops
version: 0.0.1
description: A sample schema representing the Auth0 connector for Fides
version: 0.0.2

connector_params:
- name: domain
- name: access_token
- name: client_id
label: Client ID
- name: client_secret

client_config:
protocol: https
host: <domain>
authentication:
strategy: bearer
strategy: oauth2_client_credentials
configuration:
token: <access_token>
token_request:
method: POST
path: /oauth/token
body: |
{
"grant_type": "client_credentials",
"audience": "https://<domain>/api/v2/",
"client_id": "<client_id>",
"client_secret": "<client_secret>"
}
refresh_request:
method: POST
path: /oauth/token
body: |
{
"grant_type": "client_credentials",
"audience": "https://<domain>/api/v2/",
"client_id": "<client_id>",
"client_secret": "<client_secret>"
}

test_request:
method: GET
Expand All @@ -36,13 +57,9 @@ saas_config:
param_values:
- name: email
identity: email
update:
method: PATCH
delete:
method: DELETE
path: /api/v2/users/<user_id>
body: |
{
<masked_object_fields>
}
param_values:
- name: user_id
references:
Expand Down
45 changes: 28 additions & 17 deletions tests/fixtures/saas/auth0_fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@
secrets = get_secrets("auth0")


@pytest.fixture(scope="function")
@pytest.fixture(scope="session")
def auth0_secrets(saas_config):
return {
"domain": pydash.get(saas_config, "auth0.domain") or secrets["domain"],
"access_token": pydash.get(saas_config, "auth0.access_token")
or secrets["access_token"],
"client_id": pydash.get(saas_config, "auth0.client_id") or secrets["client_id"],
"client_secret": pydash.get(saas_config, "auth0.client_secret")
or secrets["client_secret"],
}


Expand All @@ -44,6 +45,20 @@ def auth0_erasure_identity_email():
return f"{cryptographic_util.generate_secure_random_string(13)}@email.com"


@pytest.fixture(scope="session")
def auth0_token(auth0_secrets) -> str:
response = requests.post(
url=f"https://{auth0_secrets['domain']}/oauth/token",
json={
"grant_type": "client_credentials",
"client_id": auth0_secrets["client_id"],
"client_secret": auth0_secrets["client_secret"],
"audience": f"https://{auth0_secrets['domain']}/api/v2/",
},
)
return response.json()["access_token"]


@pytest.fixture
def auth0_config() -> Dict[str, Any]:
return load_config_with_replacement(
Expand Down Expand Up @@ -104,15 +119,15 @@ def auth0_dataset_config(

@pytest.fixture(scope="function")
def auth0_access_data(
auth0_connection_config, auth0_identity_email, auth0_secrets
auth0_connection_config, auth0_identity_email, auth0_secrets, auth0_token
) -> Generator:
"""
Updates user password to have some data in user_logs
"""

base_url = f"https://{auth0_secrets['domain']}"

headers = {"Authorization": f"Bearer {auth0_secrets['access_token']}"}
headers = {"Authorization": f"Bearer {auth0_token}"}
user_response = requests.get(
url=f"{base_url}/api/v2/users-by-email?email={auth0_identity_email}",
headers=headers,
Expand All @@ -135,7 +150,7 @@ def auth0_access_data(

@pytest.fixture(scope="function")
def auth0_erasure_data(
auth0_connection_config, auth0_erasure_identity_email, auth0_secrets
auth0_connection_config, auth0_erasure_identity_email, auth0_secrets, auth0_token
) -> Generator:
"""
Creates a dynamic test data record for erasure tests.
Expand All @@ -158,18 +173,18 @@ def auth0_erasure_data(
"password": "P@ssword123",
"verify_email": False,
}
headers = {"Authorization": f"Bearer {auth0_secrets['access_token']}"}
headers = {"Authorization": f"Bearer {auth0_token}"}
users_response = requests.post(
url=f"{base_url}/api/v2/users", json=body, headers=headers
)
user = users_response.json()
assert users_response.ok
error_message = (
f"User with email {auth0_erasure_identity_email} could not be added to auth0"
f"User with email {auth0_erasure_identity_email} could not be added to Auth0"
)
poll_for_existence(
_user_exists,
(auth0_erasure_identity_email, auth0_secrets),
(auth0_erasure_identity_email, auth0_secrets, auth0_token),
error_message=error_message,
)
yield user
Expand All @@ -184,23 +199,19 @@ def auth0_erasure_data(
assert user_delete_response.status_code == HTTP_204_NO_CONTENT


def _user_exists(auth0_erasure_identity_email: str, auth0_secrets):
def _user_exists(auth0_erasure_identity_email: str, auth0_secrets, auth0_token):
"""
Confirm whether user exists by calling user search by email api and comparing resulting firstname str.
Confirm whether user exists by calling user search by email API and comparing resulting firstname str.
Returns user ID if it exists, returns None if it does not.
"""
base_url = f"https://{auth0_secrets['domain']}"
headers = {
"Authorization": f"Bearer {auth0_secrets['access_token']}",
"Authorization": f"Bearer {auth0_token}",
}

user_response = requests.get(
url=f"{base_url}/api/v2/users-by-email?email={auth0_erasure_identity_email}",
headers=headers,
)

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

return user_response.json()
return user_response.json() if user_response.json() else None
26 changes: 10 additions & 16 deletions tests/ops/integration_tests/saas/test_auth0_task.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import random

import pytest
import requests

from fides.api.ops.graph.graph import DatasetGraph
from fides.api.ops.models.privacy_request import PrivacyRequest
Expand All @@ -10,17 +9,17 @@
from fides.api.ops.task import graph_task
from fides.api.ops.task.graph_task import get_cached_data_for_erasures
from fides.core.config import CONFIG
from tests.fixtures.saas.auth0_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.skip(reason="Pending development of OAuth2 JWT Bearer authentication")
@pytest.mark.integration_saas
@pytest.mark.integration_auth0
def test_auth0_connection_test(auth0_connection_config) -> None:
get_connector(auth0_connection_config).test_connection()


@pytest.mark.skip(reason="Pending development of OAuth2 JWT Bearer authentication")
@pytest.mark.integration_saas
@pytest.mark.integration_auth0
async def test_auth0_access_request_task(
Expand Down Expand Up @@ -93,7 +92,6 @@ async def test_auth0_access_request_task(
)


@pytest.mark.skip(reason="Pending development of OAuth2 JWT Bearer authentication")
@pytest.mark.integration_saas
@pytest.mark.integration_auth0
async def test_auth0_erasure_request_task(
Expand All @@ -104,6 +102,7 @@ async def test_auth0_erasure_request_task(
auth0_dataset_config,
auth0_erasure_identity_email,
auth0_erasure_data,
auth0_token,
) -> None:
"""Full erasure request based on the Auth0 SaaS config"""

Expand All @@ -118,7 +117,8 @@ async def test_auth0_erasure_request_task(
graph = DatasetGraph(merged_graph)

temp_masking = CONFIG.execution.masking_strict
CONFIG.execution.masking_strict = True
CONFIG.execution.masking_strict = False

v = await graph_task.run_access_request(
privacy_request,
policy,
Expand Down Expand Up @@ -160,17 +160,11 @@ async def test_auth0_erasure_request_task(
f"{dataset_name}:user_logs": 0,
}

# Verifying field is masked
auth0_secrets = auth0_connection_config.secrets
base_url = f"https://{auth0_secrets['domain']}"
headers = {
"Authorization": f"Bearer {auth0_secrets['access_token']}",
}
user_response = requests.get(
url=f"{base_url}/api/v2/users-by-email?email={auth0_erasure_identity_email}",
headers=headers,
# Verifying user is deleted
poll_for_existence(
_user_exists,
(auth0_erasure_identity_email, auth0_connection_config.secrets, auth0_token),
existence_desired=False,
)
user = user_response.json()
assert user[0]["name"] == "MASKED"

CONFIG.execution.masking_strict = temp_masking