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

Putting dataset test results behind security flag #5573

Merged
merged 5 commits into from
Dec 9, 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 @@ -19,6 +19,7 @@ The types of changes are:

### Added
- New page in the Cookie House sample app to demonstrate the use of embedding the FidesJS SDK on the page [#5564](https://github.com/ethyca/fides/pull/5564)
- Adding `dsr_testing_tools_enabled` security setting [#5573](https://github.com/ethyca/fides/pull/5573)

### Fixed
- SaaS integrations using `oauth_client_credentials` now properly update their access token when editing the secrets.
Expand Down
9 changes: 9 additions & 0 deletions src/fides/api/api/v1/endpoints/dataset_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
HTTP_200_OK,
HTTP_204_NO_CONTENT,
HTTP_400_BAD_REQUEST,
HTTP_403_FORBIDDEN,
HTTP_404_NOT_FOUND,
HTTP_415_UNSUPPORTED_MEDIA_TYPE,
HTTP_422_UNPROCESSABLE_ENTITY,
Expand Down Expand Up @@ -82,6 +83,7 @@
V1_URL_PREFIX,
YAML_DATASETS,
)
from fides.config import CONFIG

from fides.api.models.sql_models import ( # type: ignore[attr-defined] # isort: skip
Dataset as CtlDataset,
Expand Down Expand Up @@ -856,6 +858,13 @@ def test_connection_datasets(
fides_key: FidesKey,
unlabeled_identities: UnlabeledIdentities,
) -> Dict[str, Any]:

if not CONFIG.security.dsr_testing_tools_enabled:
raise HTTPException(
status_code=HTTP_403_FORBIDDEN,
detail="DSR testing tools are not enabled.",
)

dataset_config = DatasetConfig.filter(
db=db,
conditions=(
Expand Down
8 changes: 6 additions & 2 deletions src/fides/api/api/v1/endpoints/privacy_request_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -2662,10 +2662,14 @@ def get_test_privacy_request_results(
# Escape datetime and ObjectId values
raw_data = privacy_request.get_raw_access_results()
escaped_json = json.dumps(raw_data, indent=2, default=storage_json_encoder)
escaped_data = json.loads(escaped_json)
results = json.loads(escaped_json)

return {
"privacy_request_id": privacy_request.id,
"status": privacy_request.status,
"results": escaped_data,
"results": (
results
if CONFIG.security.dsr_testing_tools_enabled
else "DSR testing tools are not enabled, results will not be shown."
galvana marked this conversation as resolved.
Show resolved Hide resolved
),
}
7 changes: 5 additions & 2 deletions src/fides/api/models/connectionconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,10 +220,13 @@ def authorized(self) -> bool:
return False

# hard-coding to avoid cyclic dependency
if authentication.strategy not in ["oauth2_authorization_code", "oauth2_client_credentials"]:
if authentication.strategy not in [
"oauth2_authorization_code",
"oauth2_client_credentials",
]:
return False

return bool(self.secrets and 'access_token' in self.secrets.keys())
return bool(self.secrets and "access_token" in self.secrets.keys())

@property
def name_or_key(self) -> str:
Expand Down
2 changes: 1 addition & 1 deletion src/fides/api/schemas/privacy_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,4 +401,4 @@ class FilteredPrivacyRequestResults(FidesSchema):

privacy_request_id: str
status: PrivacyRequestStatus
results: Dict[str, Any]
results: Union[Dict[str, Any], str]
4 changes: 4 additions & 0 deletions src/fides/config/security_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ class SecuritySettings(FidesSettings):
default=False,
description="If set to True, the user interface will display a download button for subject requests.",
)
dsr_testing_tools_enabled: bool = Field(
default=False,
description="If set to True, contributor and owner roles will be able to run test privacy requests.",
)
subject_request_download_link_ttl_seconds: int = Field(
default=432000,
description="The number of seconds that a pre-signed download URL when using S3 storage will be valid. The default is equal to 5 days.",
Expand Down
16 changes: 16 additions & 0 deletions tests/fixtures/application_fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -3453,6 +3453,22 @@ def subject_request_download_ui_enabled():
CONFIG.security.subject_request_download_ui_enabled = original_value


@pytest.fixture(scope="function")
def dsr_testing_tools_enabled():
original_value = CONFIG.security.dsr_testing_tools_enabled
CONFIG.security.dsr_testing_tools_enabled = True
yield
CONFIG.security.dsr_testing_tools_enabled = original_value


@pytest.fixture(scope="function")
def dsr_testing_tools_disabled():
original_value = CONFIG.security.dsr_testing_tools_enabled
CONFIG.security.dsr_testing_tools_enabled = False
yield
CONFIG.security.dsr_testing_tools_enabled = original_value


@pytest.fixture(scope="function")
def system_with_no_uses(db: Session) -> Generator[System, None, None]:
system = System.create(
Expand Down
31 changes: 28 additions & 3 deletions tests/ops/api/v1/endpoints/test_dataset_test_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ def test_dataset_test_not_authenticated(
response = api_client.post(dataset_url + "/test", headers={})
assert response.status_code == 401

@pytest.mark.usefixtures("dsr_testing_tools_enabled")
def test_dataset_test_wrong_scope(
self,
dataset_config,
Expand All @@ -220,7 +221,6 @@ def test_dataset_test_wrong_scope(
response = api_client.post(dataset_url + "/test", headers=auth_header)
assert response.status_code == 403

@pytest.mark.usefixtures("default_access_policy")
@pytest.mark.parametrize(
"auth_header,expected_status",
[
Expand All @@ -231,6 +231,7 @@ def test_dataset_test_wrong_scope(
("approver_auth_header", HTTP_403_FORBIDDEN),
],
)
@pytest.mark.usefixtures("default_access_policy", "dsr_testing_tools_enabled")
def test_dataset_test_with_roles(
self,
dataset_config,
Expand All @@ -249,6 +250,7 @@ def test_dataset_test_with_roles(
)
assert response.status_code == expected_status

@pytest.mark.usefixtures("dsr_testing_tools_enabled")
def test_dataset_test_connection_does_not_exist(
self,
api_client: TestClient,
Expand All @@ -263,6 +265,7 @@ def test_dataset_test_connection_does_not_exist(
)
assert response.status_code == 404

@pytest.mark.usefixtures("dsr_testing_tools_enabled")
def test_dataset_test_dataset_does_not_exist(
self,
connection_config,
Expand Down Expand Up @@ -290,7 +293,7 @@ def test_dataset_test_dataset_does_not_exist(
),
],
)
@pytest.mark.usefixtures("default_access_policy")
@pytest.mark.usefixtures("default_access_policy", "dsr_testing_tools_enabled")
def test_dataset_test_invalid_payloads(
self,
connection_config,
Expand All @@ -310,7 +313,9 @@ def test_dataset_test_invalid_payloads(
assert response.status_code == HTTP_400_BAD_REQUEST
assert response.json()["detail"] == expected_response

@pytest.mark.usefixtures("default_access_policy", "postgres_integration_db")
@pytest.mark.usefixtures(
"default_access_policy", "postgres_integration_db", "dsr_testing_tools_enabled"
)
def test_dataset_test(
self,
connection_config,
Expand All @@ -327,3 +332,23 @@ def test_dataset_test(
)
assert response.status_code == HTTP_200_OK
assert "privacy_request_id" in response.json().keys()

@pytest.mark.usefixtures(
"default_access_policy", "postgres_integration_db", "dsr_testing_tools_disabled"
)
def test_dataset_test_disabled(
self,
connection_config,
dataset_config,
api_client: TestClient,
generate_auth_header,
) -> None:
dataset_url = get_connection_dataset_url(connection_config, dataset_config)
auth_header = generate_auth_header(scopes=[DATASET_TEST])
response = api_client.post(
dataset_url + "/test",
headers=auth_header,
json={"email": "[email protected]"},
)
assert response.status_code == HTTP_403_FORBIDDEN
assert response.json()["detail"] == "DSR testing tools are not enabled."
57 changes: 54 additions & 3 deletions tests/ops/api/v1/endpoints/test_privacy_request_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -8392,7 +8392,7 @@ def test_filtered_results_wrong_scope(
response = api_client.get(url, headers=auth_header)
assert response.status_code == 403

@pytest.mark.usefixtures("default_access_policy")
@pytest.mark.usefixtures("default_access_policy", "dsr_testing_tools_enabled")
@pytest.mark.parametrize(
"auth_header,expected_status",
[
Expand Down Expand Up @@ -8427,7 +8427,9 @@ def test_filtered_results_with_roles(
assert response.status_code == expected_status

@pytest.mark.integration_postgres
@pytest.mark.usefixtures("default_access_policy", "postgres_integration_db")
@pytest.mark.usefixtures(
"default_access_policy", "postgres_integration_db", "dsr_testing_tools_enabled"
)
def test_filtered_results_postgres(
self,
connection_config,
Expand Down Expand Up @@ -8462,8 +8464,57 @@ def test_filtered_results_postgres(
"results",
}

@pytest.mark.integration_postgres
@pytest.mark.usefixtures(
"default_access_policy",
"postgres_integration_db",
"dsr_testing_tools_enabled",
)
def test_filtered_results_postgres_access_testing_disabled(
self,
connection_config,
postgres_example_test_dataset_config,
api_client: TestClient,
generate_auth_header,
) -> None:
dataset_url = get_connection_dataset_url(
connection_config, postgres_example_test_dataset_config
)
auth_header = generate_auth_header(scopes=[DATASET_TEST])
response = api_client.post(
dataset_url + "/test",
headers=auth_header,
json={"email": "[email protected]"},
)
assert response.status_code == HTTP_200_OK

original_value = CONFIG.security.dsr_testing_tools_enabled
CONFIG.security.dsr_testing_tools_enabled = False

privacy_request_id = response.json()["privacy_request_id"]
url = V1_URL_PREFIX + PRIVACY_REQUEST_FILTERED_RESULTS.format(
privacy_request_id=privacy_request_id
)
auth_header = generate_auth_header(scopes=[PRIVACY_REQUEST_READ_ACCESS_RESULTS])
response = api_client.get(
url,
headers=auth_header,
)
assert response.status_code == HTTP_200_OK
assert set(response.json().keys()) == {
"privacy_request_id",
"status",
"results",
}
assert (
response.json()["results"]
== "DSR testing tools are not enabled, results will not be shown."
)

CONFIG.security.dsr_testing_tools_enabled = original_value

@pytest.mark.integration_mongo
@pytest.mark.usefixtures("default_access_policy")
@pytest.mark.usefixtures("default_access_policy", "dsr_testing_tools_enabled")
def test_filtered_results_mongo(
self,
mongo_connection_config,
Expand Down
Loading