From e7f51d859e060fd22bcd70f97d9ac6a07425c297 Mon Sep 17 00:00:00 2001 From: Allison King Date: Mon, 16 Oct 2023 11:35:54 -0400 Subject: [PATCH 01/21] Update various TS types with latest compass changes --- clients/fides-js/src/lib/consent-types.ts | 2 +- clients/fides-js/src/lib/tcf/types.ts | 5 +++++ .../types/api/models/SavePrivacyPreferencesResponse.ts | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/clients/fides-js/src/lib/consent-types.ts b/clients/fides-js/src/lib/consent-types.ts index 74a95dbfef..d5f70ffdfd 100644 --- a/clients/fides-js/src/lib/consent-types.ts +++ b/clients/fides-js/src/lib/consent-types.ts @@ -211,7 +211,7 @@ export enum ConsentMethod { export type PrivacyPreferencesRequest = { browser_identity: Identity; code?: string; - tc_string?: string; + fides_string?: string; preferences?: Array; purpose_consent_preferences?: Array; purpose_legitimate_interests_preferences?: Array; diff --git a/clients/fides-js/src/lib/tcf/types.ts b/clients/fides-js/src/lib/tcf/types.ts index 375e18f9d9..1a35a16cf3 100644 --- a/clients/fides-js/src/lib/tcf/types.ts +++ b/clients/fides-js/src/lib/tcf/types.ts @@ -165,6 +165,11 @@ export type TCFVendorRelationships = { special_purposes?: Array; features?: Array; special_features?: Array; + cookie_max_age_seconds?: number; + uses_cookies?: boolean; + cookie_refresh?: boolean; + uses_non_cookie_access?: boolean; + legitimate_interest_disclosure_url?: string; }; export type TCFVendorSave = { diff --git a/clients/privacy-center/types/api/models/SavePrivacyPreferencesResponse.ts b/clients/privacy-center/types/api/models/SavePrivacyPreferencesResponse.ts index 086759e8ee..dea544de10 100644 --- a/clients/privacy-center/types/api/models/SavePrivacyPreferencesResponse.ts +++ b/clients/privacy-center/types/api/models/SavePrivacyPreferencesResponse.ts @@ -3,6 +3,7 @@ /* eslint-disable */ import type { CurrentPrivacyPreferenceSchema } from "./CurrentPrivacyPreferenceSchema"; +import type { TCMobileData } from "./TCMobileData"; /** * Response schema when saving privacy preferences @@ -18,4 +19,5 @@ export type SavePrivacyPreferencesResponse = { special_feature_preferences?: Array; system_consent_preferences?: Array; system_legitimate_interests_preferences?: Array; + fides_mobile_data?: TCMobileData; }; From e8c5a75fc082f3d5c7838c0517f2881e892d2724 Mon Sep 17 00:00:00 2001 From: Allison King Date: Mon, 16 Oct 2023 11:42:18 -0400 Subject: [PATCH 02/21] Simplify some code now that vendor relationships contain _all_ vendors --- clients/fides-js/src/lib/tcf/vendors.ts | 29 ++++++------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/clients/fides-js/src/lib/tcf/vendors.ts b/clients/fides-js/src/lib/tcf/vendors.ts index b7aab96567..dec60dabd8 100644 --- a/clients/fides-js/src/lib/tcf/vendors.ts +++ b/clients/fides-js/src/lib/tcf/vendors.ts @@ -51,22 +51,12 @@ export const vendorIsAc = (vendorId: TCFVendorRelationships["id"]) => decodeVendorId(vendorId).source === VendorSources.AC; export const uniqueGvlVendorIds = (experience: PrivacyExperience): number[] => { - const { - tcf_vendor_consents: vendorConsents = [], - tcf_vendor_legitimate_interests: vendorLegints = [], - } = experience; + const { tcf_vendor_relationships: vendors = [] } = experience; - // List of i.e. [gvl.2, gacp.3, gvl.4] - const universalIds = Array.from( - new Set([ - ...vendorConsents.map((v) => v.id), - ...vendorLegints.map((v) => v.id), - ]) - ); // Filter to just i.e. [gvl.2, gvl.4] - const gvlIds = universalIds.filter((uid) => - vendorGvlEntry(uid, experience.gvl) - ); + const gvlIds = vendors + .map((v) => v.id) + .filter((uid) => vendorGvlEntry(uid, experience.gvl)); // Return [2,4] as numbers return gvlIds.map((uid) => +decodeVendorId(uid).id); }; @@ -83,15 +73,10 @@ const transformVendorDataToVendorRecords = ({ isFidesSystem: boolean; }) => { const records: VendorRecord[] = []; - const uniqueVendorIds = Array.from( - new Set([...consents.map((c) => c.id), ...legints.map((l) => l.id)]) - ); - uniqueVendorIds.forEach((id) => { - const vendorConsent = consents.find((v) => v.id === id); - const vendorLegint = legints.find((v) => v.id === id); - const relationship = relationships.find((r) => r.id === id); + relationships.forEach((relationship) => { + const vendorConsent = consents.find((v) => v.id === relationship.id); + const vendorLegint = legints.find((v) => v.id === relationship.id); const record: VendorRecord = { - id, ...relationship, ...vendorConsent, ...vendorLegint, From 6aa9ee307362d12c41836ca8a18d7ed559617285 Mon Sep 17 00:00:00 2001 From: Allison King Date: Mon, 16 Oct 2023 14:25:25 -0400 Subject: [PATCH 03/21] Use cookie data from TCFVendorRelationships instead of GVL --- .../fides-js/src/components/tcf/TcfVendors.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/clients/fides-js/src/components/tcf/TcfVendors.tsx b/clients/fides-js/src/components/tcf/TcfVendors.tsx index 2357d1a891..146617a3ca 100644 --- a/clients/fides-js/src/components/tcf/TcfVendors.tsx +++ b/clients/fides-js/src/components/tcf/TcfVendors.tsx @@ -158,13 +158,13 @@ const DataCategories = ({ ); }; -const StorageDisclosure = ({ vendor }: { vendor: Vendor }) => { +const StorageDisclosure = ({ vendor }: { vendor: VendorRecord }) => { const { name, - usesCookies, - usesNonCookieAccess, - cookieMaxAgeSeconds, - cookieRefresh, + uses_cookies: usesCookies, + uses_non_cookie_access: usesNonCookieAccess, + cookie_max_age_seconds: cookieMaxAgeSeconds, + cookie_refresh: cookieRefresh, } = vendor; let disclosure = ""; if (usesCookies) { @@ -247,13 +247,15 @@ const TcfVendors = ({ experience.gvl?.dataCategories; return (
- {gvlVendor ? : null} +
{url?.privacy ? ( Privacy policy ) : null} - {url?.legIntClaim ? ( - + {vendor.legitimate_interest_disclosure_url ? ( + Legitimate interest disclosure ) : null} From 5dc270afb34d39c93105fbd2106a22a446619986 Mon Sep 17 00:00:00 2001 From: Allison King Date: Mon, 16 Oct 2023 14:34:53 -0400 Subject: [PATCH 04/21] Add privacy_policy_url to TCFVendorRelationships --- .../src/types/api/models/TCFVendorRelationships.ts | 1 + src/fides/api/schemas/tcf.py | 1 + src/fides/api/util/tcf/tcf_experience_contents.py | 4 ++++ tests/ops/util/test_tcf_experience_contents.py | 9 +++++++++ 4 files changed, 15 insertions(+) diff --git a/clients/admin-ui/src/types/api/models/TCFVendorRelationships.ts b/clients/admin-ui/src/types/api/models/TCFVendorRelationships.ts index a5eb8d1dd4..1a23116a88 100644 --- a/clients/admin-ui/src/types/api/models/TCFVendorRelationships.ts +++ b/clients/admin-ui/src/types/api/models/TCFVendorRelationships.ts @@ -20,4 +20,5 @@ export type TCFVendorRelationships = { cookie_refresh?: boolean; uses_non_cookie_access?: boolean; legitimate_interest_disclosure_url?: string; + privacy_policy_url?: string; }; diff --git a/src/fides/api/schemas/tcf.py b/src/fides/api/schemas/tcf.py index e16ee3932b..6167f01c31 100644 --- a/src/fides/api/schemas/tcf.py +++ b/src/fides/api/schemas/tcf.py @@ -115,6 +115,7 @@ class TCFVendorRelationships(CommonVendorFields): cookie_refresh: Optional[bool] uses_non_cookie_access: Optional[bool] legitimate_interest_disclosure_url: Optional[AnyUrl] + privacy_policy_url: Optional[AnyUrl] class TCFFeatureRecord(NonVendorSection, Feature): diff --git a/src/fides/api/util/tcf/tcf_experience_contents.py b/src/fides/api/util/tcf/tcf_experience_contents.py index 780f1f5021..2cbb151e76 100644 --- a/src/fides/api/util/tcf/tcf_experience_contents.py +++ b/src/fides/api/util/tcf/tcf_experience_contents.py @@ -191,6 +191,7 @@ def get_matching_privacy_declarations(db: Session) -> Query: System.legitimate_interest_disclosure_url.label( "system_legitimate_interest_disclosure_url" ), + System.privacy_policy.label("system_privacy_policy"), System.vendor_id, PrivacyDeclaration.data_use, PrivacyDeclaration.legal_basis_for_processing, @@ -465,6 +466,9 @@ def populate_vendor_relationships_basic_attributes( vendor_relationship_record.legitimate_interest_disclosure_url = ( privacy_declaration_row.system_legitimate_interest_disclosure_url ) + vendor_relationship_record.privacy_policy_url = ( + privacy_declaration_row.system_privacy_policy + ) return vendor_map diff --git a/tests/ops/util/test_tcf_experience_contents.py b/tests/ops/util/test_tcf_experience_contents.py index a782e5a90f..ea8839f916 100644 --- a/tests/ops/util/test_tcf_experience_contents.py +++ b/tests/ops/util/test_tcf_experience_contents.py @@ -231,6 +231,7 @@ def test_system_has_declaration_no_features_special_features_special_purposes( assert vendor_relationship.uses_non_cookie_access is False assert vendor_relationship.cookie_refresh is False assert vendor_relationship.legitimate_interest_disclosure_url is None + assert vendor_relationship.privacy_policy_url is None @pytest.mark.usefixtures("tcf_system") def test_system_exists_with_tcf_purpose_and_vendor(self, db): @@ -274,6 +275,7 @@ def test_system_exists_with_tcf_purpose_and_vendor(self, db): assert not hasattr( tcf_contents.tcf_vendor_consents[0], "legitimate_interest_disclosure_url" ) + assert not hasattr(tcf_contents.tcf_vendor_consents[0], "privacy_policy_url") assert len(tcf_contents.tcf_vendor_consents[0].purpose_consents) == 1 assert tcf_contents.tcf_vendor_consents[0].purpose_consents[0].id == 8 @@ -294,6 +296,7 @@ def test_system_exists_with_tcf_purpose_and_vendor(self, db): tcf_contents.tcf_vendor_relationships[0].legitimate_interest_disclosure_url is None ) + assert tcf_contents.tcf_vendor_relationships[0].privacy_policy_url is None assert len(tcf_contents.tcf_vendor_relationships[0].special_purposes) == 1 assert tcf_contents.tcf_vendor_relationships[0].special_purposes[0].id == 1 @@ -309,6 +312,7 @@ def test_system_exists_with_tcf_purpose_and_vendor_including_tcf_fields_set( tcf_system.cookie_refresh = True tcf_system.uses_non_cookie_access = True tcf_system.legitimate_interest_disclosure_url = "http://test.com/disclosure_url" + tcf_system.privacy_policy = "http://test.com/privacy_url" tcf_system.save(db) tcf_contents = get_tcf_contents(db) @@ -350,6 +354,7 @@ def test_system_exists_with_tcf_purpose_and_vendor_including_tcf_fields_set( assert not hasattr( tcf_contents.tcf_vendor_consents[0], "legitimate_interest_disclosure_url" ) + assert not hasattr(tcf_contents.tcf_vendor_consents[0], "privacy_policy_url") assert len(tcf_contents.tcf_vendor_consents[0].purpose_consents) == 1 assert tcf_contents.tcf_vendor_consents[0].purpose_consents[0].id == 8 @@ -372,6 +377,10 @@ def test_system_exists_with_tcf_purpose_and_vendor_including_tcf_fields_set( tcf_contents.tcf_vendor_relationships[0].legitimate_interest_disclosure_url == "http://test.com/disclosure_url" ) + assert ( + tcf_contents.tcf_vendor_relationships[0].privacy_policy_url + == "http://test.com/privacy_url" + ) assert len(tcf_contents.tcf_vendor_relationships[0].special_purposes) == 1 assert tcf_contents.tcf_vendor_relationships[0].special_purposes[0].id == 1 From 5601de7633706576933fb922cf95a5c2af995953 Mon Sep 17 00:00:00 2001 From: Allison King Date: Mon, 16 Oct 2023 14:38:46 -0400 Subject: [PATCH 05/21] Render privacy policy url from vendor --- clients/fides-js/src/components/tcf/TcfVendors.tsx | 11 ++++------- clients/fides-js/src/lib/tcf/types.ts | 6 +----- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/clients/fides-js/src/components/tcf/TcfVendors.tsx b/clients/fides-js/src/components/tcf/TcfVendors.tsx index 146617a3ca..1b2d0bed86 100644 --- a/clients/fides-js/src/components/tcf/TcfVendors.tsx +++ b/clients/fides-js/src/components/tcf/TcfVendors.tsx @@ -5,7 +5,6 @@ import { GvlDataRetention, EmbeddedLineItem, GvlDataCategories, - GvlVendorUrl, GvlDataDeclarations, VendorRecord, } from "../../lib/tcf/types"; @@ -238,10 +237,6 @@ const TcfVendors = ({ } renderToggleChild={(vendor) => { const gvlVendor = vendorGvlEntry(vendor.id, experience.gvl); - // @ts-ignore the IAB-TCF lib doesn't support GVL v3 types yet - const url: GvlVendorUrl | undefined = gvlVendor?.urls.find( - (u: GvlVendorUrl) => u.langId === "en" - ); const dataCategories: GvlDataCategories | undefined = // @ts-ignore the IAB-TCF lib doesn't support GVL v3 types yet experience.gvl?.dataCategories; @@ -249,8 +244,10 @@ const TcfVendors = ({
- {url?.privacy ? ( - Privacy policy + {vendor?.privacy_policy_url ? ( + + Privacy policy + ) : null} {vendor.legitimate_interest_disclosure_url ? ( ; From 2e46ef9c3356c81aef057ff3f51698c3627ada8c Mon Sep 17 00:00:00 2001 From: Allison King Date: Mon, 16 Oct 2023 16:41:51 -0400 Subject: [PATCH 06/21] Add retention period field --- .../src/types/api/models/TCFVendorConsentRecord.ts | 1 + .../api/models/TCFVendorLegitimateInterestsRecord.ts | 1 + src/fides/api/schemas/tcf.py | 12 ++++++++++-- src/fides/api/util/tcf/tcf_experience_contents.py | 2 ++ tests/fixtures/application_fixtures.py | 2 ++ tests/ops/util/test_tcf_experience_contents.py | 2 ++ 6 files changed, 18 insertions(+), 2 deletions(-) diff --git a/clients/admin-ui/src/types/api/models/TCFVendorConsentRecord.ts b/clients/admin-ui/src/types/api/models/TCFVendorConsentRecord.ts index 3b6b1e7555..b8ec351701 100644 --- a/clients/admin-ui/src/types/api/models/TCFVendorConsentRecord.ts +++ b/clients/admin-ui/src/types/api/models/TCFVendorConsentRecord.ts @@ -9,6 +9,7 @@ import type { UserConsentPreference } from "./UserConsentPreference"; * Schema for a TCF Vendor with Consent legal basis */ export type TCFVendorConsentRecord = { + retention_period?: string; id: string; has_vendor_id?: boolean; name?: string; diff --git a/clients/admin-ui/src/types/api/models/TCFVendorLegitimateInterestsRecord.ts b/clients/admin-ui/src/types/api/models/TCFVendorLegitimateInterestsRecord.ts index 012b4354b8..f77fcd7edb 100644 --- a/clients/admin-ui/src/types/api/models/TCFVendorLegitimateInterestsRecord.ts +++ b/clients/admin-ui/src/types/api/models/TCFVendorLegitimateInterestsRecord.ts @@ -9,6 +9,7 @@ import type { UserConsentPreference } from "./UserConsentPreference"; * Schema for a TCF Vendor with Legitimate interests legal basis */ export type TCFVendorLegitimateInterestsRecord = { + retention_period?: string; id: string; has_vendor_id?: boolean; name?: string; diff --git a/src/fides/api/schemas/tcf.py b/src/fides/api/schemas/tcf.py index 6167f01c31..494fbd9783 100644 --- a/src/fides/api/schemas/tcf.py +++ b/src/fides/api/schemas/tcf.py @@ -76,7 +76,15 @@ class CommonVendorFields(FidesSchema): description: Optional[str] -class TCFVendorConsentRecord(UserSpecificConsentDetails, CommonVendorFields): +class CommonVendorLegalBasisFields(FidesSchema): + """Fields shared by the vendor sections that are split out by legal basis""" + + retention_period: Optional[str] + + +class TCFVendorConsentRecord( + UserSpecificConsentDetails, CommonVendorFields, CommonVendorLegalBasisFields +): """Schema for a TCF Vendor with Consent legal basis""" purpose_consents: List[EmbeddedLineItem] = [] @@ -90,7 +98,7 @@ def add_default_preference(cls, values: Dict[str, Any]) -> Dict[str, Any]: class TCFVendorLegitimateInterestsRecord( - UserSpecificConsentDetails, CommonVendorFields + UserSpecificConsentDetails, CommonVendorFields, CommonVendorLegalBasisFields ): """Schema for a TCF Vendor with Legitimate interests legal basis""" diff --git a/src/fides/api/util/tcf/tcf_experience_contents.py b/src/fides/api/util/tcf/tcf_experience_contents.py index 2cbb151e76..55ee77695d 100644 --- a/src/fides/api/util/tcf/tcf_experience_contents.py +++ b/src/fides/api/util/tcf/tcf_experience_contents.py @@ -196,6 +196,7 @@ def get_matching_privacy_declarations(db: Session) -> Query: PrivacyDeclaration.data_use, PrivacyDeclaration.legal_basis_for_processing, PrivacyDeclaration.features, + PrivacyDeclaration.retention_period, ) .join(PrivacyDeclaration, System.id == PrivacyDeclaration.system_id) .filter( @@ -406,6 +407,7 @@ def build_purpose_or_feature_section_and_update_vendor_map( has_vendor_id=bool( vendor_id ), # Has_vendor_id will let us separate data between systems and vendors + retention_period=privacy_declaration_row.retention_period, ) # Embed the purpose/feature under the system if it doesn't exist diff --git a/tests/fixtures/application_fixtures.py b/tests/fixtures/application_fixtures.py index 72d82a7372..ab3b87f748 100644 --- a/tests/fixtures/application_fixtures.py +++ b/tests/fixtures/application_fixtures.py @@ -2807,6 +2807,7 @@ def tcf_system(db: Session) -> System: "legal_basis_for_processing": "Consent", "egress": None, "ingress": None, + "retention_period": "3-5 days", }, ) @@ -2823,6 +2824,7 @@ def tcf_system(db: Session) -> System: "legal_basis_for_processing": "Legitimate interests", "egress": None, "ingress": None, + "retention_period": "1 day", }, ) diff --git a/tests/ops/util/test_tcf_experience_contents.py b/tests/ops/util/test_tcf_experience_contents.py index ea8839f916..8388c21dbf 100644 --- a/tests/ops/util/test_tcf_experience_contents.py +++ b/tests/ops/util/test_tcf_experience_contents.py @@ -266,6 +266,7 @@ def test_system_exists_with_tcf_purpose_and_vendor(self, db): tcf_contents.tcf_vendor_consents[0].description == "My TCF System Description" ) + assert tcf_contents.tcf_vendor_consents[0].retention_period == "3-5 days" # assert some additional TCF attributes are NOT set on the consents object - only on VendorRelationships assert not hasattr( @@ -345,6 +346,7 @@ def test_system_exists_with_tcf_purpose_and_vendor_including_tcf_fields_set( tcf_contents.tcf_vendor_consents[0].description == "My TCF System Description" ) + assert tcf_contents.tcf_vendor_consents[0].retention_period == "3-5 days" # assert some additional TCF attributes are NOT set on the consents object - only on VendorRelationships assert not hasattr( From cd898b1ccaf2c9445d4a28105f5c7e474293168f Mon Sep 17 00:00:00 2001 From: Allison King Date: Mon, 16 Oct 2023 17:27:08 -0400 Subject: [PATCH 07/21] Actually put retention period on embedded data --- src/fides/api/schemas/tcf.py | 36 +++++++++++-------- .../api/util/tcf/tcf_experience_contents.py | 5 +-- .../ops/util/test_tcf_experience_contents.py | 12 +++++-- 3 files changed, 34 insertions(+), 19 deletions(-) diff --git a/src/fides/api/schemas/tcf.py b/src/fides/api/schemas/tcf.py index 494fbd9783..5dd227f166 100644 --- a/src/fides/api/schemas/tcf.py +++ b/src/fides/api/schemas/tcf.py @@ -29,7 +29,13 @@ class NonVendorSection(UserSpecificConsentDetails): systems: List[EmbeddedVendor] = [] # Systems that use this TCF attribute -class TCFPurposeConsentRecord(NonVendorSection, MappedPurpose): +class CommonPurposeFields(FidesSchema): + """Fields shared between the two purpose sections of the TCF Experience""" + + retention_period: Optional[str] + + +class TCFPurposeConsentRecord(NonVendorSection, MappedPurpose, CommonPurposeFields): """Schema for a TCF Purpose with Consent Legal Basis returned in the TCF Overlay Experience""" @root_validator @@ -40,7 +46,9 @@ def add_default_preference(cls, values: Dict[str, Any]) -> Dict[str, Any]: return values -class TCFPurposeLegitimateInterestsRecord(NonVendorSection, MappedPurpose): +class TCFPurposeLegitimateInterestsRecord( + NonVendorSection, MappedPurpose, CommonPurposeFields +): """Schema for a TCF Purpose with Legitimate Interests Legal Basis returned in the TCF Overlay Experience""" @root_validator @@ -67,6 +75,12 @@ class EmbeddedLineItem(FidesSchema): name: str +class EmbeddedPurpose(EmbeddedLineItem): + """Sparse details for an embedded purpose beneath a system or vendor section. Read-only.""" + + retention_period: Optional[str] + + class CommonVendorFields(FidesSchema): """Fields shared between the three vendor sections of the TCF Experience""" @@ -76,18 +90,10 @@ class CommonVendorFields(FidesSchema): description: Optional[str] -class CommonVendorLegalBasisFields(FidesSchema): - """Fields shared by the vendor sections that are split out by legal basis""" - - retention_period: Optional[str] - - -class TCFVendorConsentRecord( - UserSpecificConsentDetails, CommonVendorFields, CommonVendorLegalBasisFields -): +class TCFVendorConsentRecord(UserSpecificConsentDetails, CommonVendorFields): """Schema for a TCF Vendor with Consent legal basis""" - purpose_consents: List[EmbeddedLineItem] = [] + purpose_consents: List[EmbeddedPurpose] = [] @root_validator def add_default_preference(cls, values: Dict[str, Any]) -> Dict[str, Any]: @@ -98,11 +104,11 @@ def add_default_preference(cls, values: Dict[str, Any]) -> Dict[str, Any]: class TCFVendorLegitimateInterestsRecord( - UserSpecificConsentDetails, CommonVendorFields, CommonVendorLegalBasisFields + UserSpecificConsentDetails, CommonVendorFields ): """Schema for a TCF Vendor with Legitimate interests legal basis""" - purpose_legitimate_interests: List[EmbeddedLineItem] = [] + purpose_legitimate_interests: List[EmbeddedPurpose] = [] @root_validator def add_default_preference(cls, values: Dict[str, Any]) -> Dict[str, Any]: @@ -115,7 +121,7 @@ def add_default_preference(cls, values: Dict[str, Any]) -> Dict[str, Any]: class TCFVendorRelationships(CommonVendorFields): """Collects the other relationships for a given vendor - no preferences are saved here""" - special_purposes: List[EmbeddedLineItem] = [] + special_purposes: List[EmbeddedPurpose] = [] features: List[EmbeddedLineItem] = [] special_features: List[EmbeddedLineItem] = [] cookie_max_age_seconds: Optional[int] diff --git a/src/fides/api/util/tcf/tcf_experience_contents.py b/src/fides/api/util/tcf/tcf_experience_contents.py index 55ee77695d..d218e317c4 100644 --- a/src/fides/api/util/tcf/tcf_experience_contents.py +++ b/src/fides/api/util/tcf/tcf_experience_contents.py @@ -255,6 +255,7 @@ def _add_top_level_record_to_purpose_or_feature_section( non_vendor_record_map: Dict[int, NonVendorRecord], is_purpose_type: bool, use_or_feature: str, + declaration: PrivacyDeclaration, ) -> Optional[NonVendorRecord]: """ Create a purpose or feature record and add to the top-level sections. @@ -274,7 +275,7 @@ def _add_top_level_record_to_purpose_or_feature_section( # Transform the base gvl record into the TCF record type that has more elements for TCF display. # Will be a top-level section. top_level_tcf_record: NonVendorRecord = tcf_component_type( - **fideslang_gvl_record.dict() + **fideslang_gvl_record.dict(), retention_period=declaration.retention_period ) # Add the TCF record to the top-level section in-place if it does not exist @@ -390,6 +391,7 @@ def build_purpose_or_feature_section_and_update_vendor_map( non_vendor_record_map=non_vendor_record_map, is_purpose_type=is_purpose_section, use_or_feature=attribute, + declaration=privacy_declaration_row, ) if not top_level_tcf_record: @@ -407,7 +409,6 @@ def build_purpose_or_feature_section_and_update_vendor_map( has_vendor_id=bool( vendor_id ), # Has_vendor_id will let us separate data between systems and vendors - retention_period=privacy_declaration_row.retention_period, ) # Embed the purpose/feature under the system if it doesn't exist diff --git a/tests/ops/util/test_tcf_experience_contents.py b/tests/ops/util/test_tcf_experience_contents.py index 8388c21dbf..cb2a2f0a56 100644 --- a/tests/ops/util/test_tcf_experience_contents.py +++ b/tests/ops/util/test_tcf_experience_contents.py @@ -266,7 +266,6 @@ def test_system_exists_with_tcf_purpose_and_vendor(self, db): tcf_contents.tcf_vendor_consents[0].description == "My TCF System Description" ) - assert tcf_contents.tcf_vendor_consents[0].retention_period == "3-5 days" # assert some additional TCF attributes are NOT set on the consents object - only on VendorRelationships assert not hasattr( @@ -346,7 +345,6 @@ def test_system_exists_with_tcf_purpose_and_vendor_including_tcf_fields_set( tcf_contents.tcf_vendor_consents[0].description == "My TCF System Description" ) - assert tcf_contents.tcf_vendor_consents[0].retention_period == "3-5 days" # assert some additional TCF attributes are NOT set on the consents object - only on VendorRelationships assert not hasattr( @@ -360,6 +358,10 @@ def test_system_exists_with_tcf_purpose_and_vendor_including_tcf_fields_set( assert len(tcf_contents.tcf_vendor_consents[0].purpose_consents) == 1 assert tcf_contents.tcf_vendor_consents[0].purpose_consents[0].id == 8 + assert ( + tcf_contents.tcf_vendor_consents[0].purpose_consents[0].retention_period + == "3-5 days" + ) assert tcf_contents.tcf_vendor_relationships[0].id == "gvl.42" assert tcf_contents.tcf_vendor_relationships[0].name == "TCF System Test" @@ -784,6 +786,12 @@ def test_duplicate_data_uses_on_system(self, tcf_system, db): .id == 3 ) + assert ( + tcf_contents.tcf_vendor_legitimate_interests[0] + .purpose_legitimate_interests[0] + .retention_period + == "1 day" + ) def test_add_different_data_uses_that_correspond_to_same_purpose( self, tcf_system, db From 287d4a91bf7328ea515566a4a6a404949873e609 Mon Sep 17 00:00:00 2001 From: Allison King Date: Mon, 16 Oct 2023 17:41:10 -0400 Subject: [PATCH 08/21] Update TS types again --- clients/admin-ui/src/types/api/index.ts | 1 + .../admin-ui/src/types/api/models/EmbeddedPurpose.ts | 12 ++++++++++++ .../src/types/api/models/TCFPurposeConsentRecord.ts | 1 + .../models/TCFPurposeLegitimateInterestsRecord.ts | 1 + .../src/types/api/models/TCFSpecialPurposeRecord.ts | 1 + .../src/types/api/models/TCFVendorConsentRecord.ts | 5 ++--- .../api/models/TCFVendorLegitimateInterestsRecord.ts | 5 ++--- .../src/types/api/models/TCFVendorRelationships.ts | 3 ++- 8 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 clients/admin-ui/src/types/api/models/EmbeddedPurpose.ts diff --git a/clients/admin-ui/src/types/api/index.ts b/clients/admin-ui/src/types/api/index.ts index 8b1f622fc9..b21c1d10a2 100644 --- a/clients/admin-ui/src/types/api/index.ts +++ b/clients/admin-ui/src/types/api/index.ts @@ -122,6 +122,7 @@ export type { DynamoDBDocsSchema } from "./models/DynamoDBDocsSchema"; export { EdgeDirection } from "./models/EdgeDirection"; export type { EmailDocsSchema } from "./models/EmailDocsSchema"; export type { EmbeddedLineItem } from "./models/EmbeddedLineItem"; +export type { EmbeddedPurpose } from "./models/EmbeddedPurpose"; export type { EmbeddedVendor } from "./models/EmbeddedVendor"; export type { Endpoint } from "./models/Endpoint"; export { EnforcementLevel } from "./models/EnforcementLevel"; diff --git a/clients/admin-ui/src/types/api/models/EmbeddedPurpose.ts b/clients/admin-ui/src/types/api/models/EmbeddedPurpose.ts new file mode 100644 index 0000000000..faf672f2f3 --- /dev/null +++ b/clients/admin-ui/src/types/api/models/EmbeddedPurpose.ts @@ -0,0 +1,12 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * Sparse details for an embedded purpose beneath a system or vendor section. Read-only. + */ +export type EmbeddedPurpose = { + id: number; + name: string; + retention_period?: string; +}; diff --git a/clients/admin-ui/src/types/api/models/TCFPurposeConsentRecord.ts b/clients/admin-ui/src/types/api/models/TCFPurposeConsentRecord.ts index 1ad3783865..edbccd6bb9 100644 --- a/clients/admin-ui/src/types/api/models/TCFPurposeConsentRecord.ts +++ b/clients/admin-ui/src/types/api/models/TCFPurposeConsentRecord.ts @@ -9,6 +9,7 @@ import type { UserConsentPreference } from "./UserConsentPreference"; * Schema for a TCF Purpose with Consent Legal Basis returned in the TCF Overlay Experience */ export type TCFPurposeConsentRecord = { + retention_period?: string; /** * Official GVL purpose ID. Used for linking with other records, e.g. vendors, cookies, etc. */ diff --git a/clients/admin-ui/src/types/api/models/TCFPurposeLegitimateInterestsRecord.ts b/clients/admin-ui/src/types/api/models/TCFPurposeLegitimateInterestsRecord.ts index 4786b0d351..d3a00ad755 100644 --- a/clients/admin-ui/src/types/api/models/TCFPurposeLegitimateInterestsRecord.ts +++ b/clients/admin-ui/src/types/api/models/TCFPurposeLegitimateInterestsRecord.ts @@ -9,6 +9,7 @@ import type { UserConsentPreference } from "./UserConsentPreference"; * Schema for a TCF Purpose with Legitimate Interests Legal Basis returned in the TCF Overlay Experience */ export type TCFPurposeLegitimateInterestsRecord = { + retention_period?: string; /** * Official GVL purpose ID. Used for linking with other records, e.g. vendors, cookies, etc. */ diff --git a/clients/admin-ui/src/types/api/models/TCFSpecialPurposeRecord.ts b/clients/admin-ui/src/types/api/models/TCFSpecialPurposeRecord.ts index 56d976898f..d3a2a45622 100644 --- a/clients/admin-ui/src/types/api/models/TCFSpecialPurposeRecord.ts +++ b/clients/admin-ui/src/types/api/models/TCFSpecialPurposeRecord.ts @@ -10,6 +10,7 @@ import type { UserConsentPreference } from "./UserConsentPreference"; * records where consent was previously served if applicable. */ export type TCFSpecialPurposeRecord = { + retention_period?: string; /** * Official GVL purpose ID. Used for linking with other records, e.g. vendors, cookies, etc. */ diff --git a/clients/admin-ui/src/types/api/models/TCFVendorConsentRecord.ts b/clients/admin-ui/src/types/api/models/TCFVendorConsentRecord.ts index b8ec351701..e50e2c5fca 100644 --- a/clients/admin-ui/src/types/api/models/TCFVendorConsentRecord.ts +++ b/clients/admin-ui/src/types/api/models/TCFVendorConsentRecord.ts @@ -2,14 +2,13 @@ /* tslint:disable */ /* eslint-disable */ -import type { EmbeddedLineItem } from "./EmbeddedLineItem"; +import type { EmbeddedPurpose } from "./EmbeddedPurpose"; import type { UserConsentPreference } from "./UserConsentPreference"; /** * Schema for a TCF Vendor with Consent legal basis */ export type TCFVendorConsentRecord = { - retention_period?: string; id: string; has_vendor_id?: boolean; name?: string; @@ -19,5 +18,5 @@ export type TCFVendorConsentRecord = { outdated_preference?: UserConsentPreference; current_served?: boolean; outdated_served?: boolean; - purpose_consents?: Array; + purpose_consents?: Array; }; diff --git a/clients/admin-ui/src/types/api/models/TCFVendorLegitimateInterestsRecord.ts b/clients/admin-ui/src/types/api/models/TCFVendorLegitimateInterestsRecord.ts index f77fcd7edb..0d93d13bd3 100644 --- a/clients/admin-ui/src/types/api/models/TCFVendorLegitimateInterestsRecord.ts +++ b/clients/admin-ui/src/types/api/models/TCFVendorLegitimateInterestsRecord.ts @@ -2,14 +2,13 @@ /* tslint:disable */ /* eslint-disable */ -import type { EmbeddedLineItem } from "./EmbeddedLineItem"; +import type { EmbeddedPurpose } from "./EmbeddedPurpose"; import type { UserConsentPreference } from "./UserConsentPreference"; /** * Schema for a TCF Vendor with Legitimate interests legal basis */ export type TCFVendorLegitimateInterestsRecord = { - retention_period?: string; id: string; has_vendor_id?: boolean; name?: string; @@ -19,5 +18,5 @@ export type TCFVendorLegitimateInterestsRecord = { outdated_preference?: UserConsentPreference; current_served?: boolean; outdated_served?: boolean; - purpose_legitimate_interests?: Array; + purpose_legitimate_interests?: Array; }; diff --git a/clients/admin-ui/src/types/api/models/TCFVendorRelationships.ts b/clients/admin-ui/src/types/api/models/TCFVendorRelationships.ts index 1a23116a88..07c27fbc7f 100644 --- a/clients/admin-ui/src/types/api/models/TCFVendorRelationships.ts +++ b/clients/admin-ui/src/types/api/models/TCFVendorRelationships.ts @@ -3,6 +3,7 @@ /* eslint-disable */ import type { EmbeddedLineItem } from "./EmbeddedLineItem"; +import type { EmbeddedPurpose } from "./EmbeddedPurpose"; /** * Collects the other relationships for a given vendor - no preferences are saved here @@ -12,7 +13,7 @@ export type TCFVendorRelationships = { has_vendor_id?: boolean; name?: string; description?: string; - special_purposes?: Array; + special_purposes?: Array; features?: Array; special_features?: Array; cookie_max_age_seconds?: number; From 057f4a82d542fcf56b0bdb7925cbd0db5345187b Mon Sep 17 00:00:00 2001 From: Allison King Date: Mon, 16 Oct 2023 18:04:45 -0400 Subject: [PATCH 09/21] Add retention period to special purpose records --- src/fides/api/schemas/tcf.py | 4 ++-- tests/ops/util/test_tcf_experience_contents.py | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/fides/api/schemas/tcf.py b/src/fides/api/schemas/tcf.py index 5dd227f166..a6dd8ee68d 100644 --- a/src/fides/api/schemas/tcf.py +++ b/src/fides/api/schemas/tcf.py @@ -30,7 +30,7 @@ class NonVendorSection(UserSpecificConsentDetails): class CommonPurposeFields(FidesSchema): - """Fields shared between the two purpose sections of the TCF Experience""" + """Fields shared between the purpose sections of the TCF Experience""" retention_period: Optional[str] @@ -59,7 +59,7 @@ def add_default_preference(cls, values: Dict[str, Any]) -> Dict[str, Any]: return values -class TCFSpecialPurposeRecord(NonVendorSection, MappedPurpose): +class TCFSpecialPurposeRecord(NonVendorSection, MappedPurpose, CommonPurposeFields): @root_validator def add_default_preference(cls, values: Dict[str, Any]) -> Dict[str, Any]: """Default preference for special purposes is acknowledge""" diff --git a/tests/ops/util/test_tcf_experience_contents.py b/tests/ops/util/test_tcf_experience_contents.py index cb2a2f0a56..2c367e7d1e 100644 --- a/tests/ops/util/test_tcf_experience_contents.py +++ b/tests/ops/util/test_tcf_experience_contents.py @@ -299,6 +299,12 @@ def test_system_exists_with_tcf_purpose_and_vendor(self, db): assert tcf_contents.tcf_vendor_relationships[0].privacy_policy_url is None assert len(tcf_contents.tcf_vendor_relationships[0].special_purposes) == 1 assert tcf_contents.tcf_vendor_relationships[0].special_purposes[0].id == 1 + assert ( + tcf_contents.tcf_vendor_relationships[0] + .special_purposes[0] + .retention_period + == "1 day" + ) def test_system_exists_with_tcf_purpose_and_vendor_including_tcf_fields_set( self, db, tcf_system From 5f83cb5064cf8862bab1db8b1fa4891fca779759 Mon Sep 17 00:00:00 2001 From: Allison King Date: Mon, 16 Oct 2023 18:05:29 -0400 Subject: [PATCH 10/21] Derive retention from privacy experience response --- .../src/components/tcf/TcfVendors.tsx | 89 ++++++++----------- clients/fides-js/src/lib/tcf/types.ts | 20 +++-- 2 files changed, 49 insertions(+), 60 deletions(-) diff --git a/clients/fides-js/src/components/tcf/TcfVendors.tsx b/clients/fides-js/src/components/tcf/TcfVendors.tsx index 1b2d0bed86..9c90b931ae 100644 --- a/clients/fides-js/src/components/tcf/TcfVendors.tsx +++ b/clients/fides-js/src/components/tcf/TcfVendors.tsx @@ -2,11 +2,10 @@ import { VNode, h } from "preact"; import { useMemo, useState } from "preact/hooks"; import { Vendor } from "@iabtechlabtcf/core"; import { - GvlDataRetention, - EmbeddedLineItem, GvlDataCategories, GvlDataDeclarations, VendorRecord, + EmbeddedPurpose, } from "../../lib/tcf/types"; import { PrivacyExperience } from "../../lib/consent-types"; import { UpdateEnabledIds } from "./TcfOverlay"; @@ -20,30 +19,25 @@ import DoubleToggleTable from "./DoubleToggleTable"; const FILTERS = [{ name: "All vendors" }, { name: "IAB TCF vendors" }]; -interface Retention { - mapping: Record; - default: number; -} - const VendorDetails = ({ label, lineItems, - dataRetention, }: { label: string; - lineItems: EmbeddedLineItem[] | undefined; - dataRetention?: Retention; + lineItems: EmbeddedPurpose[] | undefined; }) => { if (!lineItems || lineItems.length === 0) { return null; } + const hasRetentionInfo = lineItems.some((li) => li.retention_period); + return ( - {dataRetention ? ( + {hasRetentionInfo ? ( @@ -51,22 +45,16 @@ const VendorDetails = ({ - {lineItems.map((item) => { - let retention: string | number = "N/A"; - if (dataRetention) { - retention = dataRetention.mapping[item.id] ?? dataRetention.default; - } - return ( - - - {dataRetention ? ( - - ) : null} - - ); - })} + {lineItems.map((item) => ( + + + {hasRetentionInfo ? ( + + ) : null} + + ))}
{label} Retention
{item.name} - {retention == null ? "N/A" : `${retention} day(s)`} -
{item.name} + {item.retention_period ?? "N/A"} +
); @@ -75,11 +63,9 @@ const VendorDetails = ({ const PurposeVendorDetails = ({ purposes, specialPurposes, - gvlVendor, }: { - purposes: EmbeddedLineItem[] | undefined; - specialPurposes: EmbeddedLineItem[] | undefined; - gvlVendor: Vendor | undefined; + purposes: EmbeddedPurpose[] | undefined; + specialPurposes: EmbeddedPurpose[] | undefined; }) => { const emptyPurposes = purposes ? purposes.length === 0 : true; const emptySpecialPurposes = specialPurposes @@ -89,34 +75,34 @@ const PurposeVendorDetails = ({ if (emptyPurposes && emptySpecialPurposes) { return null; } - // @ts-ignore our TCF lib does not have GVL v3 types yet - const dataRetention: GvlDataRetention | undefined = gvlVendor?.dataRetention; + // // @ts-ignore our TCF lib does not have GVL v3 types yet + // const dataRetention: GvlDataRetention | undefined = gvlVendor?.dataRetention; return (
); @@ -263,7 +249,6 @@ const TcfVendors = ({ ...(vendor.purpose_legitimate_interests || []), ]} specialPurposes={vendor.special_purposes} - gvlVendor={gvlVendor} /> ; + purpose_consents?: Array; }; export type TCFVendorLegitimateInterestsRecord = { @@ -154,7 +163,7 @@ export type TCFVendorLegitimateInterestsRecord = { outdated_preference?: UserConsentPreference; current_served?: boolean; outdated_served?: boolean; - purpose_legitimate_interests?: Array; + purpose_legitimate_interests?: Array; }; export type TCFVendorRelationships = { @@ -162,7 +171,7 @@ export type TCFVendorRelationships = { has_vendor_id?: boolean; name?: string; description?: string; - special_purposes?: Array; + special_purposes?: Array; features?: Array; special_features?: Array; cookie_max_age_seconds?: number; @@ -269,11 +278,6 @@ export type GVLJson = Pick< // GVL types—we should be able to get these from the library at some point, // but since they are on GVL 2.2, the types aren't quite right for GVL 3. -export interface GvlDataRetention { - stdRetention: number; - purposes: Record; - specialPurposes: Record; -} interface GvlDataCategory { id: number; name: string; From b3ae24744466950ce328231255b52bc2931ad137 Mon Sep 17 00:00:00 2001 From: Allison King Date: Mon, 16 Oct 2023 18:07:34 -0400 Subject: [PATCH 11/21] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85adf11c88..3bd80d8078 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,9 @@ The types of changes are: ### Added - Added a `FidesPreferenceToggled` event to Fides.js to track when user preferences change without being saved [#4253](https://github.com/ethyca/fides/pull/4253) +### Changed +- Derive cookie storage info, privacy policy and legitimate interest disclosure URLs, and data retention data from the data map instead of directly from gvl.json [#4286](https://github.com/ethyca/fides/pull/4286) + ## [2.22.0](https://github.com/ethyca/fides/compare/2.21.0...2.22.0) ### Added From 3c9131af17c21b5a1cdbeb0d764b7b586f8475d6 Mon Sep 17 00:00:00 2001 From: Allison King Date: Mon, 16 Oct 2023 18:22:18 -0400 Subject: [PATCH 12/21] Make sure test data fills in relationship arrays --- .../privacy-center/cypress/e2e/consent-banner-tcf.cy.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts b/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts index 1fd006bf47..7aa52e7f56 100644 --- a/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts +++ b/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts @@ -255,6 +255,7 @@ describe("Fides-js TCF", () => { cy.fixture("consent/experience_tcf.json").then((payload) => { const experience = payload.items[0]; experience.tcf_vendor_consents.push(newVendor); + experience.tcf_vendor_relationships.push(newVendor); stubConfig({ options: { isOverlayEnabled: true, @@ -721,6 +722,7 @@ describe("Fides-js TCF", () => { id: "test", purpose_legitimate_interests: [{ id: 4, name: purpose4.name }], }); + experience.tcf_vendor_relationships?.push({ ...vendor, id: "test" }); stubConfig({ options: { @@ -870,13 +872,13 @@ describe("Fides-js TCF", () => { ], }; AC_IDS.forEach((id, idx) => { + const vendor = { ...baseVendor, id: `gacp.${id}`, name: `AC ${id}` }; experience.tcf_vendor_consents.push({ - ...baseVendor, - id: `gacp.${id}`, - name: `AC ${id}`, + ...vendor, // Set some of these vendors without purpose_consents purpose_consents: idx % 2 === 0 ? [] : baseVendor.purpose_consents, }); + experience.tcf_vendor_relationships.push(vendor); }); stubConfig({ From 7feace9e1f3a50e21aa02911d0a42db969010926 Mon Sep 17 00:00:00 2001 From: Allison King Date: Tue, 17 Oct 2023 10:23:49 -0400 Subject: [PATCH 13/21] Update cypress tests --- .../cypress/e2e/consent-banner-tcf.cy.ts | 47 +++++++++++++++++++ .../fixtures/consent/experience_tcf.json | 41 ++++++++++++---- 2 files changed, 79 insertions(+), 9 deletions(-) diff --git a/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts b/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts index 7aa52e7f56..23244cf04b 100644 --- a/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts +++ b/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts @@ -306,6 +306,53 @@ describe("Fides-js TCF", () => { }); }); + it("can render extra vendor info such as cookie and retention data", () => { + cy.get("#fides-tab-Vendors").click(); + cy.get(".fides-notice-toggle-title").contains(VENDOR_1.name).click(); + cy.get(".fides-disclosure-visible").within(() => { + // Check urls + cy.get("a") + .contains("Privacy policy") + .should("have.attr", "href") + .and("contain", "https://www.example.com/privacy"); + cy.get("a") + .contains("Legitimate interest disclosure") + .should("have.attr", "href") + .and( + "contain", + "https://www.example.com/legitimate_interest_disclosure" + ); + + // Check retention periods + [PURPOSE_4, PURPOSE_6, PURPOSE_7, PURPOSE_9].forEach((purpose) => { + // In the fixture, all retention are `${id} days` + cy.get("tr") + .contains(purpose.name) + .parent() + .contains(`${purpose.id} days`); + }); + cy.get("tr") + .contains(SPECIAL_PURPOSE_1.name) + .parent() + .contains(`${SPECIAL_PURPOSE_1.id} day`); + + // Check cookie disclosure + cy.get("p").contains( + 'Captify stores cookies with a maximum duration of about 5 Day(s). These cookies may be refreshed. This vendor also uses other methods like "local storage" to store and access information on your device.' + ); + }); + // Check the cookie disclosure on the system + // First close the vendor + cy.get(".fides-notice-toggle-title").contains(VENDOR_1.name).click(); + // Then open the system + cy.get(".fides-notice-toggle-title").contains(SYSTEM_1.name).click(); + cy.get(".fides-disclosure-visible").within(() => { + cy.get("p").contains( + "Fides System stores cookies with a maximum duration of about 5 Day(s)" + ); + }); + }); + it("can group toggle and fire FidesPreferenceToggled events", () => { // Toggle just legitimate interests cy.getByTestId("toggle-Purposes").click(); diff --git a/clients/privacy-center/cypress/fixtures/consent/experience_tcf.json b/clients/privacy-center/cypress/fixtures/consent/experience_tcf.json index c65a801f5e..9ead0830ba 100644 --- a/clients/privacy-center/cypress/fixtures/consent/experience_tcf.json +++ b/clients/privacy-center/cypress/fixtures/consent/experience_tcf.json @@ -63,6 +63,7 @@ "id": "pri_da3252e2-b604-49fd-b493-70a9960dce66", "tcf_purpose_consents": [ { + "retention_period": "4 days", "id": 4, "name": "Use profiles to select personalised advertising", "description": "Advertising presented to you on this service can be based on your advertising profiles, which can reflect your activity on this service or other websites or apps (like the forms you submit, content you look at), possible interests and personal aspects.", @@ -88,6 +89,7 @@ "systems": [] }, { + "retention_period": "6 days", "id": 6, "name": "Use profiles to select personalised content", "description": "Content presented to you on this service can be based on your content personalisation profiles, which can reflect your activity on this or other services (for instance, the forms you submit, content you look at), possible interests and personal aspects, such as by adapting the order in which content is shown to you, so that it is even easier for you to find (non-advertising) content that matches your interests.", @@ -110,6 +112,7 @@ "systems": [] }, { + "retention_period": "7 days", "id": 7, "name": "Measure advertising performance", "description": "Information regarding which advertising is presented to you and how you interact with it can be used to determine how well an advert has worked for you or other users and whether the goals of the advertising were reached. For instance, whether you saw an ad, whether you clicked on it, whether it led you to buy a product or visit a website, etc. This is very helpful to understand the relevance of advertising campaigns.", @@ -132,6 +135,7 @@ "systems": [] }, { + "retention_period": "9 days", "id": 9, "name": "Understand audiences through statistics or combinations of data from different sources", "description": "Reports can be generated based on the combination of data sets (like user profiles, statistics, market research, analytics data) regarding your interactions and those of other users with advertising or (non-advertising) content to identify common characteristics (for instance, to determine which target audiences are more receptive to an ad campaign or to certain contents).", @@ -156,6 +160,7 @@ ], "tcf_purpose_legitimate_interests": [ { + "retention_period": "2 days", "id": 2, "name": "Use limited data to select advertising", "description": "Advertising presented to you on this service can be based on limited data, such as the website or app you are using, your non-precise location, your device type or which content you are (or have been) interacting with (for example, to limit the number of times an ad is presented to you).", @@ -184,6 +189,7 @@ ], "tcf_special_purposes": [ { + "retention_period": "1 day", "id": 1, "name": "Ensure security, prevent and detect fraud, and fix errors", "description": "Your data can be used to monitor for and prevent unusual and possibly fraudulent activity (for example, regarding advertising, ad clicks by bots), and ensure systems and processes work properly and securely. It can also be used to correct any problems you, the publisher or the advertiser may encounter in the delivery of content and ads and in your interaction with them.", @@ -281,19 +287,23 @@ "purpose_consents": [ { "id": 4, - "name": "Use profiles to select personalised advertising" + "name": "Use profiles to select personalised advertising", + "retention_period": "4 days" }, { "id": 6, - "name": "Use profiles to select personalised content" + "name": "Use profiles to select personalised content", + "retention_period": "6 days" }, { "id": 7, - "name": "Measure advertising performance" + "name": "Measure advertising performance", + "retention_period": "7 days" }, { "id": 9, - "name": "Understand audiences through statistics or combinations of data from different sources" + "name": "Understand audiences through statistics or combinations of data from different sources", + "retention_period": "9 days" } ] } @@ -301,6 +311,8 @@ "tcf_vendor_legitimate_interests": [], "tcf_vendor_relationships": [ { + "cookie_max_age_seconds": 360000, + "cookie_refresh": true, "id": "2", "has_vendor_id": true, "name": "Captify", @@ -308,11 +320,16 @@ "special_purposes": [ { "id": 1, - "name": "Ensure security, prevent and detect fraud, and fix errors" + "name": "Ensure security, prevent and detect fraud, and fix errors", + "retention_period": "1 day" } ], + "uses_cookies": true, + "uses_non_cookie_access": true, "features": [], - "special_features": [] + "special_features": [], + "privacy_policy_url": "https://www.example.com/privacy", + "legitimate_interest_disclosure_url": "https://www.example.com/legitimate_interest_disclosure" } ], "tcf_consent_systems": [], @@ -330,13 +347,16 @@ "purpose_legitimate_interests": [ { "id": 2, - "name": "Use limited data to select advertising" + "name": "Use limited data to select advertising", + "retention_period": "2 days" } ] } ], "tcf_system_relationships": [ { + "cookie_max_age_seconds": 400000, + "cookie_refresh": false, "id": "ctl_b3dde2d5-e535-4d9a-bf6e-a3b6beb01761", "has_vendor_id": false, "name": "Fides System", @@ -344,7 +364,8 @@ "special_purposes": [ { "id": 1, - "name": "Ensure security, prevent and detect fraud, and fix errors" + "name": "Ensure security, prevent and detect fraud, and fix errors", + "retention_period": "1 day" } ], "features": [ @@ -353,7 +374,9 @@ "name": "Match and combine data from other data sources" } ], - "special_features": [] + "special_features": [], + "uses_cookies": true, + "uses_non_cookie_access": false } ], "created_at": "2023-09-26T18:59:40.416181+00:00", From c748ef5345401157f43c835563c6ec78e780126f Mon Sep 17 00:00:00 2001 From: Allison King Date: Tue, 17 Oct 2023 14:51:54 -0400 Subject: [PATCH 14/21] Specify "day(s)" for retention period --- .../features/system/SystemInformationForm.tsx | 2 +- .../src/components/tcf/TcfVendors.tsx | 44 +++++-------------- .../cypress/e2e/consent-banner-tcf.cy.ts | 6 +-- .../fixtures/consent/experience_tcf.json | 14 +++--- 4 files changed, 22 insertions(+), 44 deletions(-) diff --git a/clients/admin-ui/src/features/system/SystemInformationForm.tsx b/clients/admin-ui/src/features/system/SystemInformationForm.tsx index 1e31710967..006707b4e2 100644 --- a/clients/admin-ui/src/features/system/SystemInformationForm.tsx +++ b/clients/admin-ui/src/features/system/SystemInformationForm.tsx @@ -432,7 +432,7 @@ const SystemInformationForm = ({ /> diff --git a/clients/fides-js/src/components/tcf/TcfVendors.tsx b/clients/fides-js/src/components/tcf/TcfVendors.tsx index 9c90b931ae..855d4783db 100644 --- a/clients/fides-js/src/components/tcf/TcfVendors.tsx +++ b/clients/fides-js/src/components/tcf/TcfVendors.tsx @@ -50,7 +50,7 @@ const VendorDetails = ({ {item.name} {hasRetentionInfo ? ( - {item.retention_period ?? "N/A"} + {`${item.retention_period} day(s)` ?? "N/A"} ) : null} @@ -75,35 +75,11 @@ const PurposeVendorDetails = ({ if (emptyPurposes && emptySpecialPurposes) { return null; } - // // @ts-ignore our TCF lib does not have GVL v3 types yet - // const dataRetention: GvlDataRetention | undefined = gvlVendor?.dataRetention; return (
- - + +
); }; @@ -157,12 +133,14 @@ const StorageDisclosure = ({ vendor }: { vendor: VendorRecord }) => { ? Math.ceil(cookieMaxAgeSeconds / 60 / 60 / 24) : 0; disclosure = `${name} stores cookies with a maximum duration of about ${days} Day(s).`; - } - if (cookieRefresh) { - disclosure = `${disclosure} These cookies may be refreshed.`; - } - if (usesNonCookieAccess) { - disclosure = `${disclosure} This vendor also uses other methods like "local storage" to store and access information on your device.`; + if (cookieRefresh) { + disclosure = `${disclosure} These cookies may be refreshed.`; + } + if (usesNonCookieAccess) { + disclosure = `${disclosure} This vendor also uses other methods like "local storage" to store and access information on your device.`; + } + } else if (usesNonCookieAccess) { + disclosure = `${name} uses methods like "local storage" to store and access information on your device.`; } return

{disclosure}

; diff --git a/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts b/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts index 23244cf04b..e24d463fc6 100644 --- a/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts +++ b/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts @@ -325,16 +325,16 @@ describe("Fides-js TCF", () => { // Check retention periods [PURPOSE_4, PURPOSE_6, PURPOSE_7, PURPOSE_9].forEach((purpose) => { - // In the fixture, all retention are `${id} days` + // In the fixture, all retention periods are their id's cy.get("tr") .contains(purpose.name) .parent() - .contains(`${purpose.id} days`); + .contains(`${purpose.id} day(s)`); }); cy.get("tr") .contains(SPECIAL_PURPOSE_1.name) .parent() - .contains(`${SPECIAL_PURPOSE_1.id} day`); + .contains(`${SPECIAL_PURPOSE_1.id} day(s)`); // Check cookie disclosure cy.get("p").contains( diff --git a/clients/privacy-center/cypress/fixtures/consent/experience_tcf.json b/clients/privacy-center/cypress/fixtures/consent/experience_tcf.json index 9ead0830ba..900a39ea12 100644 --- a/clients/privacy-center/cypress/fixtures/consent/experience_tcf.json +++ b/clients/privacy-center/cypress/fixtures/consent/experience_tcf.json @@ -160,7 +160,7 @@ ], "tcf_purpose_legitimate_interests": [ { - "retention_period": "2 days", + "retention_period": "2", "id": 2, "name": "Use limited data to select advertising", "description": "Advertising presented to you on this service can be based on limited data, such as the website or app you are using, your non-precise location, your device type or which content you are (or have been) interacting with (for example, to limit the number of times an ad is presented to you).", @@ -189,7 +189,7 @@ ], "tcf_special_purposes": [ { - "retention_period": "1 day", + "retention_period": "1", "id": 1, "name": "Ensure security, prevent and detect fraud, and fix errors", "description": "Your data can be used to monitor for and prevent unusual and possibly fraudulent activity (for example, regarding advertising, ad clicks by bots), and ensure systems and processes work properly and securely. It can also be used to correct any problems you, the publisher or the advertiser may encounter in the delivery of content and ads and in your interaction with them.", @@ -288,22 +288,22 @@ { "id": 4, "name": "Use profiles to select personalised advertising", - "retention_period": "4 days" + "retention_period": "4" }, { "id": 6, "name": "Use profiles to select personalised content", - "retention_period": "6 days" + "retention_period": "6" }, { "id": 7, "name": "Measure advertising performance", - "retention_period": "7 days" + "retention_period": "7" }, { "id": 9, "name": "Understand audiences through statistics or combinations of data from different sources", - "retention_period": "9 days" + "retention_period": "9" } ] } @@ -321,7 +321,7 @@ { "id": 1, "name": "Ensure security, prevent and detect fraud, and fix errors", - "retention_period": "1 day" + "retention_period": "1" } ], "uses_cookies": true, From 08a7375f99ab6b6898407c4ca7802e8a98275386 Mon Sep 17 00:00:00 2001 From: Allison King Date: Tue, 17 Oct 2023 16:13:31 -0400 Subject: [PATCH 15/21] Add retention period only in EmbeddedPurpose --- src/fides/api/schemas/tcf.py | 14 +++----------- .../api/util/tcf/tcf_experience_contents.py | 19 +++++++++++++++++-- tests/fixtures/application_fixtures.py | 4 ++-- .../ops/util/test_tcf_experience_contents.py | 6 +++--- 4 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/fides/api/schemas/tcf.py b/src/fides/api/schemas/tcf.py index a6dd8ee68d..509edb1722 100644 --- a/src/fides/api/schemas/tcf.py +++ b/src/fides/api/schemas/tcf.py @@ -29,13 +29,7 @@ class NonVendorSection(UserSpecificConsentDetails): systems: List[EmbeddedVendor] = [] # Systems that use this TCF attribute -class CommonPurposeFields(FidesSchema): - """Fields shared between the purpose sections of the TCF Experience""" - - retention_period: Optional[str] - - -class TCFPurposeConsentRecord(NonVendorSection, MappedPurpose, CommonPurposeFields): +class TCFPurposeConsentRecord(NonVendorSection, MappedPurpose): """Schema for a TCF Purpose with Consent Legal Basis returned in the TCF Overlay Experience""" @root_validator @@ -46,9 +40,7 @@ def add_default_preference(cls, values: Dict[str, Any]) -> Dict[str, Any]: return values -class TCFPurposeLegitimateInterestsRecord( - NonVendorSection, MappedPurpose, CommonPurposeFields -): +class TCFPurposeLegitimateInterestsRecord(NonVendorSection, MappedPurpose): """Schema for a TCF Purpose with Legitimate Interests Legal Basis returned in the TCF Overlay Experience""" @root_validator @@ -59,7 +51,7 @@ def add_default_preference(cls, values: Dict[str, Any]) -> Dict[str, Any]: return values -class TCFSpecialPurposeRecord(NonVendorSection, MappedPurpose, CommonPurposeFields): +class TCFSpecialPurposeRecord(NonVendorSection, MappedPurpose): @root_validator def add_default_preference(cls, values: Dict[str, Any]) -> Dict[str, Any]: """Default preference for special purposes is acknowledge""" diff --git a/src/fides/api/util/tcf/tcf_experience_contents.py b/src/fides/api/util/tcf/tcf_experience_contents.py index d218e317c4..55aa2a74be 100644 --- a/src/fides/api/util/tcf/tcf_experience_contents.py +++ b/src/fides/api/util/tcf/tcf_experience_contents.py @@ -27,6 +27,7 @@ ) from fides.api.schemas.base_class import FidesSchema from fides.api.schemas.tcf import ( + EmbeddedPurpose, EmbeddedVendor, TCFFeatureRecord, TCFPurposeConsentRecord, @@ -289,6 +290,8 @@ def _add_top_level_record_to_purpose_or_feature_section( def _embed_purpose_or_feature_under_system( embedded_tcf_record: NonVendorRecord, system_section: SystemSubSections, + retention_period: Optional[str], + is_purpose_section: bool, ) -> None: """ Embed a second-level TCF purpose/feature under the systems section. @@ -307,8 +310,18 @@ def _embed_purpose_or_feature_under_system( if embedded_non_vendor_record: return - # Nest new cloned TCF purpose or feature record beneath system otherwise - system_section.append(embedded_tcf_record) # type: ignore[arg-type] + if is_purpose_section: + # Build the EmbeddedPurpose record with the retention period + system_section.append( + EmbeddedPurpose( # type: ignore[arg-type] + id=embedded_tcf_record.id, + name=embedded_tcf_record.name, + retention_period=retention_period, + ) + ) + else: + # Nest new cloned feature record beneath system otherwise + system_section.append(embedded_tcf_record) def _embed_system_under_purpose_or_feature( @@ -417,6 +430,8 @@ def build_purpose_or_feature_section_and_update_vendor_map( system_section=getattr( vendor_map[system_identifier], vendor_subsection_name ), + retention_period=privacy_declaration_row.retention_period, + is_purpose_section=is_purpose_section, ) # Finally, nest the system beneath this top level non-vendor tcf record diff --git a/tests/fixtures/application_fixtures.py b/tests/fixtures/application_fixtures.py index ab3b87f748..a55c25a7b2 100644 --- a/tests/fixtures/application_fixtures.py +++ b/tests/fixtures/application_fixtures.py @@ -2807,7 +2807,7 @@ def tcf_system(db: Session) -> System: "legal_basis_for_processing": "Consent", "egress": None, "ingress": None, - "retention_period": "3-5 days", + "retention_period": "3", }, ) @@ -2824,7 +2824,7 @@ def tcf_system(db: Session) -> System: "legal_basis_for_processing": "Legitimate interests", "egress": None, "ingress": None, - "retention_period": "1 day", + "retention_period": "1", }, ) diff --git a/tests/ops/util/test_tcf_experience_contents.py b/tests/ops/util/test_tcf_experience_contents.py index 2c367e7d1e..8a5c7e3cdf 100644 --- a/tests/ops/util/test_tcf_experience_contents.py +++ b/tests/ops/util/test_tcf_experience_contents.py @@ -303,7 +303,7 @@ def test_system_exists_with_tcf_purpose_and_vendor(self, db): tcf_contents.tcf_vendor_relationships[0] .special_purposes[0] .retention_period - == "1 day" + == "1" ) def test_system_exists_with_tcf_purpose_and_vendor_including_tcf_fields_set( @@ -366,7 +366,7 @@ def test_system_exists_with_tcf_purpose_and_vendor_including_tcf_fields_set( assert tcf_contents.tcf_vendor_consents[0].purpose_consents[0].id == 8 assert ( tcf_contents.tcf_vendor_consents[0].purpose_consents[0].retention_period - == "3-5 days" + == "3" ) assert tcf_contents.tcf_vendor_relationships[0].id == "gvl.42" @@ -796,7 +796,7 @@ def test_duplicate_data_uses_on_system(self, tcf_system, db): tcf_contents.tcf_vendor_legitimate_interests[0] .purpose_legitimate_interests[0] .retention_period - == "1 day" + == "1" ) def test_add_different_data_uses_that_correspond_to_same_purpose( From cd1acffbf95fc06231e4f2bc33429757432e2c48 Mon Sep 17 00:00:00 2001 From: Allison King Date: Thu, 19 Oct 2023 11:28:15 -0400 Subject: [PATCH 16/21] Remove setting retention period on top level Purposes --- src/fides/api/util/tcf/tcf_experience_contents.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/fides/api/util/tcf/tcf_experience_contents.py b/src/fides/api/util/tcf/tcf_experience_contents.py index cdbfc03b62..90e78aca89 100644 --- a/src/fides/api/util/tcf/tcf_experience_contents.py +++ b/src/fides/api/util/tcf/tcf_experience_contents.py @@ -275,7 +275,6 @@ def _add_top_level_record_to_purpose_or_feature_section( non_vendor_record_map: Dict[int, NonVendorRecord], is_purpose_type: bool, use_or_feature: str, - declaration: PrivacyDeclaration, ) -> Optional[NonVendorRecord]: """ Create a purpose or feature record and add to the top-level sections. @@ -295,7 +294,7 @@ def _add_top_level_record_to_purpose_or_feature_section( # Transform the base gvl record into the TCF record type that has more elements for TCF display. # Will be a top-level section. top_level_tcf_record: NonVendorRecord = tcf_component_type( - **fideslang_gvl_record.dict(), retention_period=declaration.retention_period + **fideslang_gvl_record.dict() ) # Add the TCF record to the top-level section in-place if it does not exist @@ -423,7 +422,6 @@ def build_purpose_or_feature_section_and_update_vendor_map( non_vendor_record_map=non_vendor_record_map, is_purpose_type=is_purpose_section, use_or_feature=attribute, - declaration=privacy_declaration_row, ) if not top_level_tcf_record: From 38be263dbb0491df54233d1e2941246235a08faa Mon Sep 17 00:00:00 2001 From: Allison King Date: Thu, 19 Oct 2023 12:12:31 -0400 Subject: [PATCH 17/21] Add test for two vendors with same purpose, different retention periods --- .../ops/util/test_tcf_experience_contents.py | 73 ++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/tests/ops/util/test_tcf_experience_contents.py b/tests/ops/util/test_tcf_experience_contents.py index 1714296d87..7daf0891d1 100644 --- a/tests/ops/util/test_tcf_experience_contents.py +++ b/tests/ops/util/test_tcf_experience_contents.py @@ -1,8 +1,10 @@ +from uuid import uuid4 + import pytest from fideslang import MAPPED_PURPOSES from fideslang.models import LegalBasisForProcessingEnum -from fides.api.models.sql_models import PrivacyDeclaration +from fides.api.models.sql_models import PrivacyDeclaration, System from fides.api.schemas.tcf import EmbeddedVendor from fides.api.util.tcf.tcf_experience_contents import get_tcf_contents @@ -561,6 +563,75 @@ def test_system_matches_subset_of_purpose_data_uses(self, db, tcf_system): assert len(tcf_contents.tcf_vendor_consents[0].purpose_consents) == 1 assert tcf_contents.tcf_vendor_consents[0].purpose_consents[0].id == 2 + @pytest.mark.usefixtures("tcf_system") + def test_two_vendors_same_purpose_different_retention_period(self, db, tcf_system): + """Test making sure that two vendors that share the same purpose show up with + different retention periods in their EmbeddedPurposes""" + + # Create a second system with the same privacy declaration as tcf_system + # but with a different retention period + second_system = System.create( + db=db, + data={ + "fides_key": f"tcf-system_key-f{uuid4()}", + "vendor_id": "gvl.100", + "name": f"TCF System Second Test", + "description": "My Second TCF System Description", + "organization_fides_key": "default_organization", + "system_type": "Service", + "data_responsibility_title": "Processor", + "data_protection_impact_assessment": { + "is_required": False, + "progress": None, + "link": None, + }, + }, + ) + + PrivacyDeclaration.create( + db=db, + data={ + "name": "Collect data for content performance", + "system_id": second_system.id, + "data_categories": ["user.device.cookie_id"], + "data_use": "analytics.reporting.content_performance", + "data_qualifier": "aggregated.anonymized.unlinked_pseudonymized.pseudonymized.identified", + "data_subjects": ["customer"], + "dataset_references": None, + "legal_basis_for_processing": "Consent", + "egress": None, + "ingress": None, + "retention_period": "5", # the fixture already has a retention_period of 3 + }, + ) + + tcf_contents = get_tcf_contents(db) + + assert_length_of_tcf_sections( + tcf_contents, + p_c_len=1, + p_li_len=0, + f_len=0, + sp_len=1, + sf_len=0, + v_c_len=2, + v_li_len=0, + v_r_len=2, + s_c_len=0, + s_li_len=0, + s_r_len=0, + ) + + assert tcf_contents.tcf_vendor_consents[0].id == "gvl.100" + assert tcf_contents.tcf_vendor_consents[0].purpose_consents == [ + {"id": 8, "name": "Measure content performance", "retention_period": "5"} + ] + + assert tcf_contents.tcf_vendor_consents[1].id == "gvl.42" + assert tcf_contents.tcf_vendor_consents[1].purpose_consents == [ + {"id": 8, "name": "Measure content performance", "retention_period": "3"} + ] + @pytest.mark.usefixtures("tcf_system") def test_special_purposes(self, db): tcf_contents = get_tcf_contents(db) From 11f0cf2938a4fc142f375c1fdf5aab9478306227 Mon Sep 17 00:00:00 2001 From: Allison King Date: Thu, 19 Oct 2023 12:16:01 -0400 Subject: [PATCH 18/21] Remove references to top level purpose retention_period --- .../src/types/api/models/TCFPurposeConsentRecord.ts | 1 - .../api/models/TCFPurposeLegitimateInterestsRecord.ts | 1 - .../src/types/api/models/TCFSpecialPurposeRecord.ts | 1 - clients/fides-js/src/lib/tcf/types.ts | 3 --- .../cypress/fixtures/consent/experience_tcf.json | 10 ++-------- 5 files changed, 2 insertions(+), 14 deletions(-) diff --git a/clients/admin-ui/src/types/api/models/TCFPurposeConsentRecord.ts b/clients/admin-ui/src/types/api/models/TCFPurposeConsentRecord.ts index edbccd6bb9..1ad3783865 100644 --- a/clients/admin-ui/src/types/api/models/TCFPurposeConsentRecord.ts +++ b/clients/admin-ui/src/types/api/models/TCFPurposeConsentRecord.ts @@ -9,7 +9,6 @@ import type { UserConsentPreference } from "./UserConsentPreference"; * Schema for a TCF Purpose with Consent Legal Basis returned in the TCF Overlay Experience */ export type TCFPurposeConsentRecord = { - retention_period?: string; /** * Official GVL purpose ID. Used for linking with other records, e.g. vendors, cookies, etc. */ diff --git a/clients/admin-ui/src/types/api/models/TCFPurposeLegitimateInterestsRecord.ts b/clients/admin-ui/src/types/api/models/TCFPurposeLegitimateInterestsRecord.ts index d3a00ad755..4786b0d351 100644 --- a/clients/admin-ui/src/types/api/models/TCFPurposeLegitimateInterestsRecord.ts +++ b/clients/admin-ui/src/types/api/models/TCFPurposeLegitimateInterestsRecord.ts @@ -9,7 +9,6 @@ import type { UserConsentPreference } from "./UserConsentPreference"; * Schema for a TCF Purpose with Legitimate Interests Legal Basis returned in the TCF Overlay Experience */ export type TCFPurposeLegitimateInterestsRecord = { - retention_period?: string; /** * Official GVL purpose ID. Used for linking with other records, e.g. vendors, cookies, etc. */ diff --git a/clients/admin-ui/src/types/api/models/TCFSpecialPurposeRecord.ts b/clients/admin-ui/src/types/api/models/TCFSpecialPurposeRecord.ts index d3a2a45622..56d976898f 100644 --- a/clients/admin-ui/src/types/api/models/TCFSpecialPurposeRecord.ts +++ b/clients/admin-ui/src/types/api/models/TCFSpecialPurposeRecord.ts @@ -10,7 +10,6 @@ import type { UserConsentPreference } from "./UserConsentPreference"; * records where consent was previously served if applicable. */ export type TCFSpecialPurposeRecord = { - retention_period?: string; /** * Official GVL purpose ID. Used for linking with other records, e.g. vendors, cookies, etc. */ diff --git a/clients/fides-js/src/lib/tcf/types.ts b/clients/fides-js/src/lib/tcf/types.ts index 2033f2a983..0d21c658d4 100644 --- a/clients/fides-js/src/lib/tcf/types.ts +++ b/clients/fides-js/src/lib/tcf/types.ts @@ -39,7 +39,6 @@ export type EmbeddedPurpose = { // Purposes export type TCFPurposeConsentRecord = { - retention_period?: string; id: number; name: string; description: string; @@ -55,7 +54,6 @@ export type TCFPurposeConsentRecord = { }; export type TCFPurposeLegitimateInterestsRecord = { - retention_period?: string; id: number; name: string; description: string; @@ -78,7 +76,6 @@ export type TCFPurposeSave = { // Special purposes export type TCFSpecialPurposeRecord = { - retention_period?: string; id: number; name: string; description: string; diff --git a/clients/privacy-center/cypress/fixtures/consent/experience_tcf.json b/clients/privacy-center/cypress/fixtures/consent/experience_tcf.json index 900a39ea12..fd9c290ce0 100644 --- a/clients/privacy-center/cypress/fixtures/consent/experience_tcf.json +++ b/clients/privacy-center/cypress/fixtures/consent/experience_tcf.json @@ -63,7 +63,6 @@ "id": "pri_da3252e2-b604-49fd-b493-70a9960dce66", "tcf_purpose_consents": [ { - "retention_period": "4 days", "id": 4, "name": "Use profiles to select personalised advertising", "description": "Advertising presented to you on this service can be based on your advertising profiles, which can reflect your activity on this service or other websites or apps (like the forms you submit, content you look at), possible interests and personal aspects.", @@ -89,7 +88,6 @@ "systems": [] }, { - "retention_period": "6 days", "id": 6, "name": "Use profiles to select personalised content", "description": "Content presented to you on this service can be based on your content personalisation profiles, which can reflect your activity on this or other services (for instance, the forms you submit, content you look at), possible interests and personal aspects, such as by adapting the order in which content is shown to you, so that it is even easier for you to find (non-advertising) content that matches your interests.", @@ -112,7 +110,6 @@ "systems": [] }, { - "retention_period": "7 days", "id": 7, "name": "Measure advertising performance", "description": "Information regarding which advertising is presented to you and how you interact with it can be used to determine how well an advert has worked for you or other users and whether the goals of the advertising were reached. For instance, whether you saw an ad, whether you clicked on it, whether it led you to buy a product or visit a website, etc. This is very helpful to understand the relevance of advertising campaigns.", @@ -135,7 +132,6 @@ "systems": [] }, { - "retention_period": "9 days", "id": 9, "name": "Understand audiences through statistics or combinations of data from different sources", "description": "Reports can be generated based on the combination of data sets (like user profiles, statistics, market research, analytics data) regarding your interactions and those of other users with advertising or (non-advertising) content to identify common characteristics (for instance, to determine which target audiences are more receptive to an ad campaign or to certain contents).", @@ -160,7 +156,6 @@ ], "tcf_purpose_legitimate_interests": [ { - "retention_period": "2", "id": 2, "name": "Use limited data to select advertising", "description": "Advertising presented to you on this service can be based on limited data, such as the website or app you are using, your non-precise location, your device type or which content you are (or have been) interacting with (for example, to limit the number of times an ad is presented to you).", @@ -189,7 +184,6 @@ ], "tcf_special_purposes": [ { - "retention_period": "1", "id": 1, "name": "Ensure security, prevent and detect fraud, and fix errors", "description": "Your data can be used to monitor for and prevent unusual and possibly fraudulent activity (for example, regarding advertising, ad clicks by bots), and ensure systems and processes work properly and securely. It can also be used to correct any problems you, the publisher or the advertiser may encounter in the delivery of content and ads and in your interaction with them.", @@ -348,7 +342,7 @@ { "id": 2, "name": "Use limited data to select advertising", - "retention_period": "2 days" + "retention_period": "2" } ] } @@ -365,7 +359,7 @@ { "id": 1, "name": "Ensure security, prevent and detect fraud, and fix errors", - "retention_period": "1 day" + "retention_period": "1" } ], "features": [ From 0d98375d770043f11b4cbd88a4df13b90c3b455c Mon Sep 17 00:00:00 2001 From: Allison King Date: Thu, 19 Oct 2023 12:32:06 -0400 Subject: [PATCH 19/21] Handle empty states a little better --- .../src/components/tcf/TcfVendors.tsx | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/clients/fides-js/src/components/tcf/TcfVendors.tsx b/clients/fides-js/src/components/tcf/TcfVendors.tsx index 855d4783db..de50c76fbb 100644 --- a/clients/fides-js/src/components/tcf/TcfVendors.tsx +++ b/clients/fides-js/src/components/tcf/TcfVendors.tsx @@ -143,6 +143,10 @@ const StorageDisclosure = ({ vendor }: { vendor: VendorRecord }) => { disclosure = `${name} uses methods like "local storage" to store and access information on your device.`; } + if (disclosure === "") { + return null; + } + return

{disclosure}

; }; @@ -204,23 +208,28 @@ const TcfVendors = ({ const dataCategories: GvlDataCategories | undefined = // @ts-ignore the IAB-TCF lib doesn't support GVL v3 types yet experience.gvl?.dataCategories; + const hasUrls = + vendor.privacy_policy_url || + vendor.legitimate_interest_disclosure_url; return (
-
- {vendor?.privacy_policy_url ? ( - - Privacy policy - - ) : null} - {vendor.legitimate_interest_disclosure_url ? ( - - Legitimate interest disclosure - - ) : null} -
+ {hasUrls ? ( +
+ {vendor.privacy_policy_url ? ( + + Privacy policy + + ) : null} + {vendor.legitimate_interest_disclosure_url ? ( + + Legitimate interest disclosure + + ) : null} +
+ ) : null} Date: Thu, 19 Oct 2023 14:20:11 -0400 Subject: [PATCH 20/21] Handle empty retention period string --- clients/fides-js/src/components/tcf/TcfVendors.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/clients/fides-js/src/components/tcf/TcfVendors.tsx b/clients/fides-js/src/components/tcf/TcfVendors.tsx index de50c76fbb..95a942ce84 100644 --- a/clients/fides-js/src/components/tcf/TcfVendors.tsx +++ b/clients/fides-js/src/components/tcf/TcfVendors.tsx @@ -30,7 +30,7 @@ const VendorDetails = ({ return null; } - const hasRetentionInfo = lineItems.some((li) => li.retention_period); + const hasRetentionInfo = lineItems.some((li) => li.retention_period != null); return ( @@ -50,7 +50,9 @@ const VendorDetails = ({ {hasRetentionInfo ? ( ) : null} From d2e351178446acdb504f25ea6ef89f1320527b0a Mon Sep 17 00:00:00 2001 From: Allison King Date: Thu, 19 Oct 2023 14:45:04 -0400 Subject: [PATCH 21/21] Try to make test less flaky --- .../cypress/e2e/consent-banner-tcf.cy.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts b/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts index df2dcc4c37..8597b00d1e 100644 --- a/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts +++ b/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts @@ -317,14 +317,14 @@ describe("Fides-js TCF", () => { }); cy.window().then((win) => { win.__tcfapi("getTCData", 2, cy.stub().as("getTCData")); + cy.get("@getTCData") + .should("have.been.calledOnce") + .its("lastCall.args") + .then(([tcData, success]) => { + expect(success).to.eql(true); + expect(tcData.vendor.consents).to.eql({ 1: true, 2: true }); + }); }); - cy.get("@getTCData") - .should("have.been.calledOnce") - .its("lastCall.args") - .then(([tcData, success]) => { - expect(success).to.eql(true); - expect(tcData.vendor.consents).to.eql({ 1: true, 2: true }); - }); }); it("can render extra vendor info such as cookie and retention data", () => {
{item.name} - {`${item.retention_period} day(s)` ?? "N/A"} + {item.retention_period + ? `${item.retention_period} day(s)` + : "N/A"}