From eb400e17893c0ae7bf8f986742cc0d4a2dc5533e Mon Sep 17 00:00:00 2001 From: Dawn Pattison Date: Mon, 19 Jun 2023 13:29:13 -0500 Subject: [PATCH 01/90] [Backend] Remove Automatic SafeStr serialization from PrivacyExperienceConfig. (#3600) - Escape experience configs before saving in the db. Unescape before returning. - Escape default experience configs before saving from yaml file - Unescape embedded experience configs and privacy notices in the PrivacyExperience response - Unescape notices in the response before returning after create and update. --- CHANGELOG.md | 1 + .../api/api/v1/endpoints/dataset_endpoints.py | 4 +- .../privacy_experience_config_endpoints.py | 67 +++++-- .../endpoints/privacy_experience_endpoints.py | 27 ++- .../v1/endpoints/privacy_notice_endpoints.py | 44 +++-- src/fides/api/api/v1/endpoints/utils.py | 4 +- src/fides/api/models/datasetconfig.py | 5 +- src/fides/api/schemas/privacy_experience.py | 29 ++-- src/fides/api/util/consent_util.py | 34 ++-- src/fides/api/util/data_category.py | 4 +- tests/fixtures/application_fixtures.py | 6 +- .../test_connection_config_endpoints.py | 2 +- ...est_privacy_experience_config_endpoints.py | 96 +++++++++- .../test_privacy_experience_endpoints.py | 30 +++- .../test_privacy_notice_endpoints.py | 164 +++++++++++++++++- .../privacy_request/test_email_batch_send.py | 4 +- tests/ops/util/test_consent_util.py | 12 +- 17 files changed, 450 insertions(+), 83 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7be3ffb29..c04d524b25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ The types of changes are: - Disable connector dropdown in integration tab on save [#3552](https://github.com/ethyca/fides/pull/3552) - Handles an edge case for non-existent identities with the Kustomer API [#3513](https://github.com/ethyca/fides/pull/3513) - remove the configure privacy request tile from the home screen [#3555](https://github.com/ethyca/fides/pull/3555) +- Updated Privacy Experience Safe Strings Serialization [#3600](https://github.com/ethyca/fides/pull/3600/) ### Developer Experience diff --git a/src/fides/api/api/v1/endpoints/dataset_endpoints.py b/src/fides/api/api/v1/endpoints/dataset_endpoints.py index 8355c676a7..6421e8cd7c 100644 --- a/src/fides/api/api/v1/endpoints/dataset_endpoints.py +++ b/src/fides/api/api/v1/endpoints/dataset_endpoints.py @@ -52,9 +52,7 @@ convert_dataset_to_graph, to_graph_field, ) -from fides.api.models.sql_models import ( # type: ignore[attr-defined] - Dataset as CtlDataset, -) +from fides.api.models.sql_models import Dataset as CtlDataset # type: ignore[attr-defined] from fides.api.oauth.utils import verify_oauth_client from fides.api.schemas.api import BulkUpdateFailed from fides.api.schemas.dataset import ( diff --git a/src/fides/api/api/v1/endpoints/privacy_experience_config_endpoints.py b/src/fides/api/api/v1/endpoints/privacy_experience_config_endpoints.py index 604b7041eb..9da4fbaadb 100644 --- a/src/fides/api/api/v1/endpoints/privacy_experience_config_endpoints.py +++ b/src/fides/api/api/v1/endpoints/privacy_experience_config_endpoints.py @@ -1,3 +1,4 @@ +from html import escape, unescape from typing import Dict, List, Optional from fastapi import Depends, HTTPException, Security @@ -6,6 +7,7 @@ from fastapi_pagination.bases import AbstractPage from loguru import logger from sqlalchemy.orm import Query, Session +from starlette.requests import Request from starlette.status import ( HTTP_200_OK, HTTP_201_CREATED, @@ -16,7 +18,7 @@ from fides.api.api import deps from fides.api.api.v1 import scope_registry from fides.api.api.v1 import urn_registry as urls -from fides.api.api.v1.endpoints.utils import human_friendly_list +from fides.api.api.v1.endpoints.utils import human_friendly_list, transform_fields from fides.api.api.v1.scope_registry import PRIVACY_EXPERIENCE_UPDATE from fides.api.models.privacy_experience import ( ComponentType, @@ -33,6 +35,10 @@ ExperienceConfigUpdate, ) from fides.api.util.api_router import APIRouter +from fides.api.util.consent_util import ( + PRIVACY_EXPERIENCE_ESCAPE_FIELDS, + UNESCAPE_SAFESTR_HEADER, +) router = APIRouter(tags=["Privacy Experience Config"], prefix=urls.V1_URL_PREFIX) @@ -71,12 +77,13 @@ def experience_config_list( show_disabled: Optional[bool] = True, component: Optional[ComponentType] = None, region: Optional[PrivacyNoticeRegion] = None, + request: Request, ) -> AbstractPage[PrivacyExperienceConfig]: """ Returns a list of PrivacyExperienceConfig resources. These resources have common titles, descriptions, and labels that can be shared between multiple experiences. """ - + should_unescape = request.headers.get(UNESCAPE_SAFESTR_HEADER) privacy_experience_config_query: Query = db.query(PrivacyExperienceConfig) if component: @@ -100,7 +107,18 @@ def experience_config_list( ) logger.info("Loading Experience Configs with params {}.", params) - return fastapi_paginate(privacy_experience_config_query.all(), params=params) + experience_configs = privacy_experience_config_query.all() + if should_unescape: + experience_configs = [ + transform_fields( + transformation=unescape, + model=experience_config, + fields=PRIVACY_EXPERIENCE_ESCAPE_FIELDS, + ) + for experience_config in experience_configs + ] + + return fastapi_paginate(experience_configs, params=params) @router.post( @@ -125,7 +143,12 @@ def experience_config_create( """ Create Experience Config and then attempt to upsert Experiences and link to ExperienceConfig """ - experience_config_dict: Dict = experience_config_data.dict(exclude_unset=True) + privacy_experience_data = transform_fields( + transformation=escape, + model=experience_config_data, + fields=PRIVACY_EXPERIENCE_ESCAPE_FIELDS, + ) + experience_config_dict: Dict = privacy_experience_data.dict(exclude_unset=True) # Pop the regions off the request new_regions: Optional[List[PrivacyNoticeRegion]] = experience_config_dict.pop( "regions", None @@ -145,6 +168,7 @@ def experience_config_create( "Creating experience config of component '{}'.", experience_config_data.component.value, ) + experience_config = PrivacyExperienceConfig.create( db, data=experience_config_dict, check_name=False ) @@ -162,7 +186,11 @@ def experience_config_create( ) return ExperienceConfigCreateOrUpdateResponse( - experience_config=experience_config, + experience_config=transform_fields( + transformation=unescape, + model=experience_config, + fields=PRIVACY_EXPERIENCE_ESCAPE_FIELDS, + ), linked_regions=linked, unlinked_regions=unlinked, ) @@ -177,15 +205,22 @@ def experience_config_create( ], ) def experience_config_detail( - *, - db: Session = Depends(deps.get_db), - experience_config_id: str, + *, db: Session = Depends(deps.get_db), experience_config_id: str, request: Request ) -> PrivacyExperienceConfig: """ Returns a PrivacyExperienceConfig. """ logger.info("Retrieving experience config with id '{}'.", experience_config_id) - return get_experience_config_or_error(db, experience_config_id) + should_unescape = request.headers.get(UNESCAPE_SAFESTR_HEADER) + + experience_config = get_experience_config_or_error(db, experience_config_id) + if should_unescape: + experience_config = transform_fields( + transformation=unescape, + model=experience_config, + fields=PRIVACY_EXPERIENCE_ESCAPE_FIELDS, + ) + return experience_config @router.patch( @@ -233,7 +268,13 @@ def experience_config_update( detail=f"Cannot set as the default. Only one default {experience_config.component.value} config can be in the system.", # type: ignore[attr-defined] ) - experience_config_data_dict: Dict = experience_config_data.dict(exclude_unset=True) + privacy_experience_data = transform_fields( + transformation=escape, + model=experience_config_data, + fields=PRIVACY_EXPERIENCE_ESCAPE_FIELDS, + ) + + experience_config_data_dict: Dict = privacy_experience_data.dict(exclude_unset=True) # Pop the regions off the request regions: Optional[List[PrivacyNoticeRegion]] = experience_config_data_dict.pop( "regions", None @@ -284,7 +325,11 @@ def experience_config_update( ) return ExperienceConfigCreateOrUpdateResponse( - experience_config=experience_config, + experience_config=transform_fields( + transformation=unescape, + model=experience_config, + fields=PRIVACY_EXPERIENCE_ESCAPE_FIELDS, + ), linked_regions=linked, unlinked_regions=unlinked_regions, ) diff --git a/src/fides/api/api/v1/endpoints/privacy_experience_endpoints.py b/src/fides/api/api/v1/endpoints/privacy_experience_endpoints.py index 1147805262..f4d59ce336 100644 --- a/src/fides/api/api/v1/endpoints/privacy_experience_endpoints.py +++ b/src/fides/api/api/v1/endpoints/privacy_experience_endpoints.py @@ -1,4 +1,5 @@ import uuid +from html import unescape from typing import List, Optional from fastapi import Depends, HTTPException, Request, Response @@ -15,7 +16,7 @@ from fides.api.api import deps from fides.api.api.v1 import urn_registry as urls -from fides.api.api.v1.endpoints.utils import fides_limiter +from fides.api.api.v1.endpoints.utils import fides_limiter, transform_fields from fides.api.models.privacy_experience import ( ComponentType, PrivacyExperience, @@ -25,7 +26,12 @@ from fides.api.models.privacy_request import ProvidedIdentity from fides.api.schemas.privacy_experience import PrivacyExperienceResponse from fides.api.util.api_router import APIRouter -from fides.api.util.consent_util import get_fides_user_device_id_provided_identity +from fides.api.util.consent_util import ( + PRIVACY_EXPERIENCE_ESCAPE_FIELDS, + PRIVACY_NOTICE_ESCAPE_FIELDS, + UNESCAPE_SAFESTR_HEADER, + get_fides_user_device_id_provided_identity, +) from fides.core.config import CONFIG router = APIRouter(tags=["Privacy Experience"], prefix=urls.V1_URL_PREFIX) @@ -117,6 +123,7 @@ def privacy_experience_list( ) results: List[PrivacyExperience] = [] + should_unescape = request.headers.get(UNESCAPE_SAFESTR_HEADER) for privacy_experience in experience_query.order_by( PrivacyExperience.created_at.desc() ): @@ -125,6 +132,22 @@ def privacy_experience_list( ] = privacy_experience.get_related_privacy_notices( db, show_disabled, fides_user_provided_identity ) + if should_unescape: + # Unescape both the experience config and the embedded privacy notices + privacy_experience.experience_config = transform_fields( + transformation=unescape, + model=privacy_experience.experience_config, + fields=PRIVACY_EXPERIENCE_ESCAPE_FIELDS, + ) + + privacy_notices = [ + transform_fields( + transformation=unescape, + model=notice, + fields=PRIVACY_NOTICE_ESCAPE_FIELDS, + ) + for notice in privacy_notices + ] # Temporarily save privacy notices on the privacy experience object privacy_experience.privacy_notices = privacy_notices # Temporarily save "show_banner" on the privacy experience object diff --git a/src/fides/api/api/v1/endpoints/privacy_notice_endpoints.py b/src/fides/api/api/v1/endpoints/privacy_notice_endpoints.py index b770de106a..753e8c9f3e 100644 --- a/src/fides/api/api/v1/endpoints/privacy_notice_endpoints.py +++ b/src/fides/api/api/v1/endpoints/privacy_notice_endpoints.py @@ -2,9 +2,8 @@ from typing import Dict, List, Optional, Set, Tuple from fastapi import Depends, Request, Security -from fastapi_pagination import Page, Params +from fastapi_pagination import Page, Params, paginate from fastapi_pagination.bases import AbstractPage -from fastapi_pagination.ext.sqlalchemy import paginate from loguru import logger from pydantic import conlist from sqlalchemy.orm import Query, Session @@ -30,6 +29,7 @@ from fides.api.util.api_router import APIRouter from fides.api.util.consent_util import ( PRIVACY_NOTICE_ESCAPE_FIELDS, + UNESCAPE_SAFESTR_HEADER, create_privacy_notices_util, ensure_unique_ids, prepare_privacy_notice_patches, @@ -89,17 +89,21 @@ def get_privacy_notice_list( systems_applicable=systems_applicable, region=region, ) - should_unescape = request.headers.get("unescape-safestr") + should_unescape = request.headers.get(UNESCAPE_SAFESTR_HEADER) privacy_notices = notice_query.order_by(PrivacyNotice.created_at.desc()) - paginated = paginate(privacy_notices, params=params) - if should_unescape: - paginated.items = [ # type: ignore[attr-defined] + return paginate( + [ transform_fields( - transformation=unescape, model=item, fields=PRIVACY_NOTICE_ESCAPE_FIELDS + transformation=unescape, + model=notice, + fields=PRIVACY_NOTICE_ESCAPE_FIELDS, ) - for item in paginated.items # type: ignore[attr-defined] - ] - return paginated + if should_unescape + else notice + for notice in privacy_notices + ], + params=params, + ) @router.get( @@ -184,7 +188,7 @@ def get_privacy_notice( """ Return a single PrivacyNotice """ - should_unescape = request.headers.get("unescape-safestr") + should_unescape = request.headers.get(UNESCAPE_SAFESTR_HEADER) notice = get_privacy_notice_or_error(db, privacy_notice_id) if should_unescape: notice = transform_fields( @@ -219,7 +223,14 @@ def create_privacy_notices( except (ValueError, ValidationError) as e: raise HTTPException(HTTP_422_UNPROCESSABLE_ENTITY, detail=str(e)) - return created_privacy_notices + return [ + transform_fields( + transformation=unescape, + model=notice, + fields=PRIVACY_NOTICE_ESCAPE_FIELDS, + ) + for notice in created_privacy_notices + ] @router.patch( @@ -267,4 +278,11 @@ def update_privacy_notices( db, affected_regions=list(affected_regions) ) - return notices + return [ + transform_fields( + transformation=unescape, + model=notice, + fields=PRIVACY_NOTICE_ESCAPE_FIELDS, + ) + for notice in notices + ] diff --git a/src/fides/api/api/v1/endpoints/utils.py b/src/fides/api/api/v1/endpoints/utils.py index ae5689e087..3b37d3fdd6 100644 --- a/src/fides/api/api/v1/endpoints/utils.py +++ b/src/fides/api/api/v1/endpoints/utils.py @@ -137,9 +137,7 @@ def validate_start_and_end_filters( ) -def transform_fields( - transformation: Callable, model: object, fields: List[str] -) -> object: +def transform_fields(transformation: Callable, model: Base, fields: List[str]) -> Base: """ Takes a callable and returns a transformed object. """ diff --git a/src/fides/api/models/datasetconfig.py b/src/fides/api/models/datasetconfig.py index 286cebd70e..42cfd0aec0 100644 --- a/src/fides/api/models/datasetconfig.py +++ b/src/fides/api/models/datasetconfig.py @@ -19,9 +19,8 @@ ) from fides.api.graph.data_type import parse_data_type_string from fides.api.models.connectionconfig import ConnectionConfig, ConnectionType -from fides.api.models.sql_models import ( # type: ignore[attr-defined] - Dataset as CtlDataset, -) +from fides.api.models.sql_models import Dataset as CtlDataset # type: ignore[attr-defined] + from fides.api.util.saas_util import merge_datasets diff --git a/src/fides/api/schemas/privacy_experience.py b/src/fides/api/schemas/privacy_experience.py index 24010f6008..6b876b7bb0 100644 --- a/src/fides/api/schemas/privacy_experience.py +++ b/src/fides/api/schemas/privacy_experience.py @@ -6,7 +6,6 @@ from pydantic import Extra, Field, root_validator, validator from fides.api.api.v1.endpoints.utils import human_friendly_list -from fides.api.custom_types import SafeStr from fides.api.models.privacy_experience import BannerEnabled, ComponentType from fides.api.models.privacy_notice import PrivacyNoticeRegion from fides.api.schemas.base_class import FidesSchema @@ -22,14 +21,14 @@ class ExperienceConfigSchema(FidesSchema): but cannot be updated later. """ - accept_button_label: Optional[SafeStr] = Field( + accept_button_label: Optional[str] = Field( description="Overlay 'Accept button displayed on the Banner and Privacy Preferences' or Privacy Center 'Confirmation button label'" ) - acknowledge_button_label: Optional[SafeStr] = Field( + acknowledge_button_label: Optional[str] = Field( description="Overlay 'Acknowledge button label for notice only banner'" ) banner_enabled: Optional[BannerEnabled] = Field(description="Overlay 'Banner'") - description: Optional[SafeStr] = Field( + description: Optional[str] = Field( description="Overlay 'Banner Description' or Privacy Center 'Description'" ) disabled: Optional[bool] = Field( @@ -39,25 +38,25 @@ class ExperienceConfigSchema(FidesSchema): default=False, description="Whether the given ExperienceConfig is a global default", ) - privacy_policy_link_label: Optional[SafeStr] = Field( + privacy_policy_link_label: Optional[str] = Field( description="Overlay and Privacy Center 'Privacy policy link label'" ) - privacy_policy_url: Optional[SafeStr] = Field( + privacy_policy_url: Optional[str] = Field( description="Overlay and Privacy Center 'Privacy policy URl'" ) - privacy_preferences_link_label: Optional[SafeStr] = Field( + privacy_preferences_link_label: Optional[str] = Field( description="Overlay 'Privacy preferences link label'" ) regions: Optional[List[PrivacyNoticeRegion]] = Field( description="Regions using this ExperienceConfig" ) - reject_button_label: Optional[SafeStr] = Field( + reject_button_label: Optional[str] = Field( description="Overlay 'Reject button displayed on the Banner and 'Privacy Preferences' of Privacy Center 'Reject button label'" ) - save_button_label: Optional[SafeStr] = Field( + save_button_label: Optional[str] = Field( description="Overlay 'Privacy preferences 'Save' button label" ) - title: Optional[SafeStr] = Field( + title: Optional[str] = Field( description="Overlay 'Banner title' or Privacy Center 'title'" ) @@ -79,12 +78,12 @@ class ExperienceConfigCreate(ExperienceConfigSchema): It also establishes some fields _required_ for creation """ - accept_button_label: SafeStr + accept_button_label: str component: ComponentType - description: SafeStr - reject_button_label: SafeStr - save_button_label: SafeStr - title: SafeStr + description: str + reject_button_label: str + save_button_label: str + title: str @root_validator def validate_attributes(cls, values: Dict[str, Any]) -> Dict[str, Any]: diff --git a/src/fides/api/util/consent_util.py b/src/fides/api/util/consent_util.py index e64304d132..53750f0af1 100644 --- a/src/fides/api/util/consent_util.py +++ b/src/fides/api/util/consent_util.py @@ -42,6 +42,18 @@ from fides.core.config.helpers import load_file PRIVACY_NOTICE_ESCAPE_FIELDS = ["name", "description", "internal_description"] +PRIVACY_EXPERIENCE_ESCAPE_FIELDS = [ + "accept_button_label", + "acknowledge_button_label", + "description", + "privacy_policy_link_label", + "privacy_policy_url", + "privacy_preferences_link_label", + "reject_button_label", + "save_button_label", + "title", +] +UNESCAPE_SAFESTR_HEADER = "unescape-safestr" def filter_privacy_preferences_for_propagation( @@ -317,7 +329,7 @@ def create_privacy_notices_util( # should_escape flag is for when we're creating a notice # from a template. The content was already escaped in the # template - we don't want to escape twice. - privacy_notice = transform_fields( # type: ignore + privacy_notice = transform_fields( transformation=escape, model=privacy_notice, fields=PRIVACY_NOTICE_ESCAPE_FIELDS, @@ -394,7 +406,7 @@ def prepare_privacy_notice_patches( transformation=escape, model=update_data, fields=PRIVACY_NOTICE_ESCAPE_FIELDS, - ) # type: ignore + ) if update_data.id not in existing_notices: if not allow_create: raise HTTPException( @@ -563,8 +575,10 @@ def upsert_default_experience_config( experience_config_data.copy() ) # Avoids unexpected behavior on update in testing - experience_config_schema: ExperienceConfigCreateWithId = ( - ExperienceConfigCreateWithId(**experience_config_data) + experience_config_schema = transform_fields( + transformation=escape, + model=(ExperienceConfigCreateWithId(**experience_config_data)), + fields=PRIVACY_EXPERIENCE_ESCAPE_FIELDS, ) if not experience_config_schema.is_default: raise Exception("This method is for created default experience configs.") @@ -585,13 +599,13 @@ def upsert_default_experience_config( # Validating some required fields if this is an overlay ExperienceConfigCreate.from_orm(dry_update) - del experience_config_data["component"] - del experience_config_data["id"] + update_data = experience_config_schema.dict(exclude_unset=True) + del update_data["component"] + del update_data["id"] # This is important for making sure config is only updated if it actually changed - update_data = ExperienceConfigUpdate(**experience_config_data) - experience_config_data_dict: Dict = update_data.dict(exclude_unset=True) - - existing_experience_config.update(db=db, data=experience_config_data_dict) + existing_experience_config.update( + db=db, data=ExperienceConfigUpdate(**update_data).dict(exclude_unset=True) + ) return False, existing_experience_config logger.info("Creating default experience config {}", experience_config_schema.id) diff --git a/src/fides/api/util/data_category.py b/src/fides/api/util/data_category.py index ccb0e60670..ae2e8d5757 100644 --- a/src/fides/api/util/data_category.py +++ b/src/fides/api/util/data_category.py @@ -6,9 +6,7 @@ from sqlalchemy.orm import Session from fides.api import common_exceptions -from fides.api.models.sql_models import ( # type: ignore[attr-defined] - DataCategory as DataCategoryDbModel, -) +from fides.api.models.sql_models import DataCategory as DataCategoryDbModel # type: ignore[attr-defined] def generate_fides_data_categories() -> Type[EnumType]: diff --git a/tests/fixtures/application_fixtures.py b/tests/fixtures/application_fixtures.py index 4a0357b18c..6b08814b99 100644 --- a/tests/fixtures/application_fixtures.py +++ b/tests/fixtures/application_fixtures.py @@ -1445,7 +1445,7 @@ def privacy_notice(db: Session) -> Generator: data={ "name": "example privacy notice", "notice_key": "example_privacy_notice", - "description": "a sample privacy notice configuration", + "description": "user's description <script />", "regions": [ PrivacyNoticeRegion.us_ca, PrivacyNoticeRegion.us_co, @@ -2201,7 +2201,7 @@ def experience_config_privacy_center(db: Session) -> Generator: db=db, data={ "accept_button_label": "Accept all", - "description": "We care about your privacy", + "description": "user's description <script />", "component": "privacy_center", "reject_button_label": "Reject all", "save_button_label": "Save", @@ -2244,7 +2244,7 @@ def experience_config_overlay(db: Session) -> Generator: "description": "On this page you can opt in and out of these data uses cases", "disabled": False, "privacy_preferences_link_label": "Manage preferences", - "privacy_policy_link_label": "View our privacy policy", + "privacy_policy_link_label": "View our company's privacy policy", "privacy_policy_url": "example.com/privacy", "reject_button_label": "Reject all", "save_button_label": "Save", diff --git a/tests/ops/api/v1/endpoints/test_connection_config_endpoints.py b/tests/ops/api/v1/endpoints/test_connection_config_endpoints.py index ae7a18978a..f7c742b661 100644 --- a/tests/ops/api/v1/endpoints/test_connection_config_endpoints.py +++ b/tests/ops/api/v1/endpoints/test_connection_config_endpoints.py @@ -26,8 +26,8 @@ from fides.api.models.datasetconfig import DatasetConfig from fides.api.models.manual_webhook import AccessManualWebhook from fides.api.models.privacy_request import PrivacyRequestStatus -from fides.api.oauth.roles import APPROVER, OWNER, VIEWER from fides.api.models.sql_models import Dataset +from fides.api.oauth.roles import APPROVER, OWNER, VIEWER from tests.fixtures.application_fixtures import integration_secrets from tests.fixtures.saas.connection_template_fixtures import instantiate_connector diff --git a/tests/ops/api/v1/endpoints/test_privacy_experience_config_endpoints.py b/tests/ops/api/v1/endpoints/test_privacy_experience_config_endpoints.py index aaab76db50..5ea07046cb 100644 --- a/tests/ops/api/v1/endpoints/test_privacy_experience_config_endpoints.py +++ b/tests/ops/api/v1/endpoints/test_privacy_experience_config_endpoints.py @@ -70,11 +70,9 @@ def test_get_experience_config_list( experience_config_privacy_center, experience_config_overlay, ) -> None: + unescape_header = {"Unescape-Safestr": "true"} auth_header = generate_auth_header(scopes=[scopes.PRIVACY_EXPERIENCE_READ]) - response = api_client.get( - url, - headers=auth_header, - ) + response = api_client.get(url, headers={**auth_header, **unescape_header}) assert response.status_code == 200 resp = response.json() assert resp["total"] == 4 # Two default configs loaded on startup plus two here @@ -99,6 +97,9 @@ def test_get_experience_config_list( second_config = data[1] assert second_config["id"] == experience_config_privacy_center.id + assert ( + second_config["description"] == "user's description