diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d9bbb3416..f2211add1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ The types of changes are: - Fixed bug where Privacy Policy links would be shown without a configured URL [#4801](https://github.com/ethyca/fides/pull/4801) - Fixed bug where Language selector button was overlapping other buttons when Privacy Policy wasn't present. [#4815](https://github.com/ethyca/fides/pull/4815) - Fixed bug where icons of the Language selector were displayed too small on some sites [#4815](https://github.com/ethyca/fides/pull/4815) +- Fixed bug where GPP US National Section was incorrectly included when the State by State approach was selected [#4823]https://github.com/ethyca/fides/pull/4823 ## [2.34.0](https://github.com/ethyca/fides/compare/2.33.1...2.34.0) diff --git a/clients/fides-js/__tests__/lib/gpp/us-notices.ts b/clients/fides-js/__tests__/lib/gpp/us-notices.ts index e7ef3d2094..8ea384da14 100644 --- a/clients/fides-js/__tests__/lib/gpp/us-notices.ts +++ b/clients/fides-js/__tests__/lib/gpp/us-notices.ts @@ -76,7 +76,7 @@ const mockPrivacyExperience = (override?: Partial) => { updated_at: "2023-12-07T22:03:26.052630+00:00", gpp_settings: { enabled: true, - us_approach: GPPUSApproach.STATE, + us_approach: GPPUSApproach.NATIONAL, mspa_covered_transactions: true, mspa_opt_out_option_mode: true, mspa_service_provider_mode: false, @@ -124,7 +124,6 @@ describe("setGppNoticesProvidedFromExperience", () => { const sectionsChanged = setGppNoticesProvidedFromExperience({ cmpApi, experience, - forceGpp: false, }); expect(sectionsChanged).toEqual([]); expect(cmpApi.getGppString()).toEqual(EMPTY_GPP_STRING); @@ -140,7 +139,6 @@ describe("setGppNoticesProvidedFromExperience", () => { const sectionsChanged = setGppNoticesProvidedFromExperience({ cmpApi, experience, - forceGpp: false, }); expect(sectionsChanged).toEqual([ { name: "usnatv1", id: 7, prefix: "usnat" }, @@ -192,7 +190,6 @@ describe("setGppNoticesProvidedFromExperience", () => { const sectionsChanged = setGppNoticesProvidedFromExperience({ cmpApi, experience, - forceGpp: false, }); expect(sectionsChanged).toEqual([ { name: "usnatv1", id: 7, prefix: "usnat" }, @@ -263,7 +260,6 @@ describe("setGppNoticesProvidedFromExperience", () => { const sectionsChanged = setGppNoticesProvidedFromExperience({ cmpApi, experience, - forceGpp: false, }); expect(sectionsChanged).toEqual([ { name: "usnatv1", id: 7, prefix: "usnat" }, @@ -371,10 +367,10 @@ describe("setGppOptOutsFromCookieAndExperience", () => { cmpApi, cookie, experience, - forceGpp: false, }); expect(sectionsChanged).toEqual([]); expect(cmpApi.getGppString()).toEqual(EMPTY_GPP_STRING); + expect(cmpApi.getSection("usnatv1")).toBe(null); }); it("sets all as 0 when there is no consent object in cookie", () => { @@ -389,7 +385,6 @@ describe("setGppOptOutsFromCookieAndExperience", () => { cmpApi, cookie, experience, - forceGpp: false, }); expect(sectionsChanged).toEqual([ { name: "usnatv1", id: 7, prefix: "usnat" }, @@ -432,7 +427,6 @@ describe("setGppOptOutsFromCookieAndExperience", () => { cmpApi, cookie, experience, - forceGpp: false, }); const section = cmpApi.getSection("usnatv1"); expect(section).toEqual({ @@ -484,7 +478,6 @@ describe("setGppOptOutsFromCookieAndExperience", () => { cmpApi, cookie, experience, - forceGpp: false, }); const section = cmpApi.getSection("usnatv1"); expect(section).toEqual({ @@ -536,7 +529,6 @@ describe("setGppOptOutsFromCookieAndExperience", () => { cmpApi, cookie, experience, - forceGpp: false, }); const section = cmpApi.getSection("usnatv1"); expect(section).toEqual({ @@ -583,20 +575,11 @@ describe("setGppOptOutsFromCookieAndExperience", () => { const experience = mockPrivacyExperience({ region: "us_ca", // Set to a state privacy_notices: notices, - gpp_settings: { - enabled: true, - us_approach: GPPUSApproach.NATIONAL, // But set setting to national - mspa_covered_transactions: true, - mspa_opt_out_option_mode: true, - mspa_service_provider_mode: false, - enable_tcfeu_string: true, - }, }); setGppOptOutsFromCookieAndExperience({ cmpApi, cookie, experience, - forceGpp: false, }); const section = cmpApi.getSection("usnatv1"); expect(section).toEqual({ @@ -621,4 +604,80 @@ describe("setGppOptOutsFromCookieAndExperience", () => { }); expect(cmpApi.getGppString()).toEqual("DBABLA~BAAVVVVVVWA.QA"); }); + + it("can use state gpp fields when gpp is set to state", () => { + const cmpApi = new CmpApi(1, 1); + const cookie = mockFidesCookie({ + consent: { + data_sales_and_sharing: false, + targeted_advertising: false, + sensitive_personal_data_sharing: false, + known_child_sensitive_data_consents: false, + personal_data_consents: false, + }, + }); + const notices = [ + DATA_SALES_SHARING_NOTICE, + TARGETED_ADVERTISING_NOTICE, + SENSITIVE_PERSONAL_SHARING_NOTICE, + KNOWN_CHILD_SENSITIVE_NOTICE, + PERSONAL_DATA_NOTICE, + ]; + const experience = mockPrivacyExperience({ + region: "us_ut", // Set to a state + privacy_notices: notices, + gpp_settings: { + enabled: true, + us_approach: GPPUSApproach.STATE, // Set to state + mspa_covered_transactions: true, + mspa_opt_out_option_mode: true, + mspa_service_provider_mode: false, + enable_tcfeu_string: true, + }, + }); + setGppOptOutsFromCookieAndExperience({ + cmpApi, + cookie, + experience, + }); + const section = cmpApi.getSection("usutv1"); + expect(section).toEqual({ + Version: 1, + SharingNotice: 0, + SaleOptOutNotice: 0, + TargetedAdvertisingOptOutNotice: 0, + SensitiveDataProcessingOptOutNotice: 0, + SaleOptOut: 0, + TargetedAdvertisingOptOut: 0, + SensitiveDataProcessing: [0, 0, 0, 0, 0, 0, 0, 0], + KnownChildSensitiveDataConsents: 0, + MspaCoveredTransaction: 1, + MspaOptOutOptionMode: 1, + MspaServiceProviderMode: 2, + }); + expect(cmpApi.getGppString()).toEqual("DBABFg~BAAAAAWA"); + }); + + it("does nothing for non-supported region when gpp is set to state", () => { + const cmpApi = new CmpApi(1, 1); + const experience = mockPrivacyExperience({ + region: "us_ny", + gpp_settings: { + enabled: true, + us_approach: GPPUSApproach.STATE, // Set to state + mspa_covered_transactions: true, + mspa_opt_out_option_mode: true, + mspa_service_provider_mode: false, + enable_tcfeu_string: true, + }, + }); + const sectionsChanged = setGppNoticesProvidedFromExperience({ + cmpApi, + experience, + }); + expect(sectionsChanged).toEqual([]); + expect(cmpApi.getGppString()).toEqual(EMPTY_GPP_STRING); + expect(cmpApi.getSection("usnatv1")).toBe(null); + expect(cmpApi.getSection("usnyv1")).toBe(null); + }); }); diff --git a/clients/fides-js/docs/interfaces/FidesOptions.md b/clients/fides-js/docs/interfaces/FidesOptions.md index 0c56ba6f70..cfd59d877b 100644 --- a/clients/fides-js/docs/interfaces/FidesOptions.md +++ b/clients/fides-js/docs/interfaces/FidesOptions.md @@ -162,13 +162,3 @@ overriden at the page-level as needed. Only applicable to a TCF experience. For more details, see the [TCF CMP API technical specification](https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework/blob/master/TCFv2/IAB%20Tech%20Lab%20-%20CMP%20API%20v2.md#what-does-the-gdprapplies-value-mean) * Defaults to `true`. - -## Forcing GPP Inclusion - -In some cases it may be necessary to always support the IAB's Global Privacy Platform (GPP), even if a visiting user is from a location without a privacy regulation. Forcing the inclusion of the [GPP API](/docs/tutorials/consent-management/consent-management-configuration/cmp-javascript-apis#gpp-api) can be accomplished by including query parameter `gpp=true` on the FidesJS script: - -``` - -``` - -When the GPP API is included this way, the `applicableSections` property is set to `[-1]` whenever a user visits a page from a non-supported location. diff --git a/clients/fides-js/src/fides-ext-gpp.ts b/clients/fides-js/src/fides-ext-gpp.ts index ab8e0d748d..024e376966 100644 --- a/clients/fides-js/src/fides-ext-gpp.ts +++ b/clients/fides-js/src/fides-ext-gpp.ts @@ -137,7 +137,7 @@ const initializeGppCmpApi = () => { // If consent does not need to be resurfaced, then we can set the signal to Ready here window.addEventListener("FidesInitialized", (event) => { // TODO (PROD-1439): re-evaluate if GPP is "cheating" accessing window.Fides instead of using the event details only - const { experience, saved_consent: savedConsent, options } = window.Fides; + const { experience, saved_consent: savedConsent } = window.Fides; cmpApi.setSupportedAPIs(getSupportedApis()); // Set status to ready immediately upon initialization, if either: // A. Consent should not be resurfaced @@ -156,27 +156,28 @@ const initializeGppCmpApi = () => { if (tcSet) { cmpApi.setApplicableSections([TcfEuV2.ID]); } - setGppNoticesProvidedFromExperience({ + const sectionsSet = setGppNoticesProvidedFromExperience({ cmpApi, experience, - forceGpp: options.forceGpp, }); const sectionsChanged = setGppOptOutsFromCookieAndExperience({ cmpApi, cookie: event.detail, experience, - forceGpp: options.forceGpp, }); if (sectionsChanged.length) { cmpApi.setApplicableSections(sectionsChanged.map((s) => s.id)); } + if (!tcSet && !sectionsSet.length && !sectionsChanged.length) { + cmpApi.setApplicableSections([-1]); + } cmpApi.setSignalStatus(SignalStatus.READY); } }); window.addEventListener("FidesUIShown", (event) => { // Set US GPP notice fields - const { experience, saved_consent: savedConsent, options } = window.Fides; + const { experience, saved_consent: savedConsent } = window.Fides; if (isPrivacyExperience(experience)) { // set signal status to ready only for users with no existing prefs and if notices are all opt-in by default if ( @@ -195,7 +196,6 @@ const initializeGppCmpApi = () => { const sectionsChanged = setGppNoticesProvidedFromExperience({ cmpApi, experience, - forceGpp: options.forceGpp, }); if (sectionsChanged.length) { cmpApi.setApplicableSections(sectionsChanged.map((s) => s.id)); @@ -230,7 +230,6 @@ const initializeGppCmpApi = () => { cmpApi, cookie: event.detail, experience: window.Fides.experience, - forceGpp: window.Fides.options.forceGpp, }); if (sectionsChanged.length) { cmpApi.setApplicableSections(sectionsChanged.map((s) => s.id)); diff --git a/clients/fides-js/src/fides-tcf.ts b/clients/fides-js/src/fides-tcf.ts index 6d19077b09..52c8157183 100644 --- a/clients/fides-js/src/fides-tcf.ts +++ b/clients/fides-js/src/fides-tcf.ts @@ -221,7 +221,6 @@ const _Fides: FidesGlobal = { allowHTMLDescription: null, base64Cookie: false, fidesPrimaryColor: null, - forceGpp: false, fidesClearCookie: false, }, fides_meta: {}, diff --git a/clients/fides-js/src/fides.ts b/clients/fides-js/src/fides.ts index 927ce8c09f..ac2a4941bb 100644 --- a/clients/fides-js/src/fides.ts +++ b/clients/fides-js/src/fides.ts @@ -165,7 +165,6 @@ const _Fides: FidesGlobal = { allowHTMLDescription: null, base64Cookie: false, fidesPrimaryColor: null, - forceGpp: false, fidesClearCookie: false, }, fides_meta: {}, diff --git a/clients/fides-js/src/lib/consent-types.ts b/clients/fides-js/src/lib/consent-types.ts index 91fb7b4f35..0e06337d14 100644 --- a/clients/fides-js/src/lib/consent-types.ts +++ b/clients/fides-js/src/lib/consent-types.ts @@ -120,9 +120,6 @@ export interface FidesInitOptions { // Shows fides.js overlay UI on load deleting the fides_consent cookie as if no preferences have been saved fidesClearCookie: boolean; - - // Whether the developer forced the inclusion of the GPP extension via query param on the script tag - forceGpp: boolean; } /** diff --git a/clients/fides-js/src/lib/gpp/constants.ts b/clients/fides-js/src/lib/gpp/constants.ts index a49a6b8e85..80afa0ebcd 100644 --- a/clients/fides-js/src/lib/gpp/constants.ts +++ b/clients/fides-js/src/lib/gpp/constants.ts @@ -6,11 +6,9 @@ import { UsUtV1, UsVaV1, } from "@iabgpp/cmpapi"; +import { GPPSection } from "./types"; -export const FIDES_REGION_TO_GPP_SECTION: Record< - string, - { name: string; id: number; prefix: string } -> = { +export const FIDES_REGION_TO_GPP_SECTION: Record = { us: { name: UsNatV1.NAME, id: UsNatV1.ID, prefix: "usnat" }, us_ca: { name: UsCaV1.NAME, id: UsCaV1.ID, prefix: "usca" }, us_ct: { name: UsCtV1.NAME, id: UsCtV1.ID, prefix: "usct" }, diff --git a/clients/fides-js/src/lib/gpp/types.ts b/clients/fides-js/src/lib/gpp/types.ts index 606cde3af0..f3e7c016a0 100644 --- a/clients/fides-js/src/lib/gpp/types.ts +++ b/clients/fides-js/src/lib/gpp/types.ts @@ -57,3 +57,9 @@ export type GPPFieldMapping = { notice?: Array; mechanism?: Array; }; + +export type GPPSection = { + name: string; + id: number; + prefix?: string; +}; diff --git a/clients/fides-js/src/lib/gpp/us-notices.ts b/clients/fides-js/src/lib/gpp/us-notices.ts index 6a89515f36..38595c3343 100644 --- a/clients/fides-js/src/lib/gpp/us-notices.ts +++ b/clients/fides-js/src/lib/gpp/us-notices.ts @@ -6,7 +6,9 @@ import { CmpApi, UsNatV1Field } from "@iabgpp/cmpapi"; import { FIDES_REGION_TO_GPP_SECTION } from "./constants"; import { FidesCookie, PrivacyExperience } from "../consent-types"; -import { GPPSettings, GPPUSApproach } from "./types"; +import { GPPSection, GPPSettings, GPPUSApproach } from "./types"; + +const US_NATIONAL_REGION = "us"; const setMspaSections = ({ cmpApi, @@ -40,9 +42,15 @@ const setMspaSections = ({ }); }; +/** + * Checks if a given region is in the US based on the provided region string's prefix. + * Also returns true if the region is the US_NATIONAL_REGION. + */ +const isUsRegion = (region: string) => region?.toLowerCase().startsWith("us"); + /** * For US National, the privacy experience region is still the state where the user came from. - * However, the GPP field mapping will only contain "us", so make sure we use "us" since we are configured for the national case. + * However, the GPP field mapping will only contain "us", so make sure we use "us" (US_NATIONAL_REGION) when we are configured for the national case. * Otherwise, we can use the experience region directly. */ const deriveGppFieldRegion = ({ @@ -52,8 +60,8 @@ const deriveGppFieldRegion = ({ experienceRegion: string; usApproach: GPPUSApproach | undefined; }) => { - if (usApproach === GPPUSApproach.NATIONAL) { - return "us"; + if (isUsRegion(experienceRegion) && usApproach === GPPUSApproach.NATIONAL) { + return US_NATIONAL_REGION; } return experienceRegion; }; @@ -66,32 +74,29 @@ const deriveGppFieldRegion = ({ export const setGppNoticesProvidedFromExperience = ({ cmpApi, experience, - forceGpp, }: { cmpApi: CmpApi; experience: PrivacyExperience; - forceGpp: boolean; }) => { - const sectionsChanged = new Set<{ name: string; id: number }>(); + const sectionsChanged = new Set(); const { privacy_notices: notices = [], - region, + region: experienceRegion, gpp_settings: gppSettings, } = experience; + const usApproach = gppSettings?.us_approach; const gppRegion = deriveGppFieldRegion({ - experienceRegion: region, - usApproach: gppSettings?.us_approach, + experienceRegion, + usApproach, }); const gppSection = FIDES_REGION_TO_GPP_SECTION[gppRegion]; - if (!gppSection) { - if ( - (forceGpp && !experience?.gpp_settings) || - experience?.gpp_settings?.us_approach === GPPUSApproach.STATE - ) { - cmpApi.setApplicableSections([-1]); - return []; - } + if ( + !gppSection || + (gppRegion === US_NATIONAL_REGION && usApproach === GPPUSApproach.STATE) + ) { + // If we don't have a section, we can't set anything. + // If we're using the state approach we shouldn't return the national section, even if region is set to national. return []; } @@ -125,38 +130,39 @@ export const setGppOptOutsFromCookieAndExperience = ({ cmpApi, cookie, experience, - forceGpp, }: { cmpApi: CmpApi; cookie: FidesCookie; experience: PrivacyExperience; - forceGpp: boolean; }) => { - const sectionsChanged = new Set<{ name: string; id: number }>(); - const { consent } = cookie; + const sectionsChanged = new Set(); + const { + privacy_notices: notices = [], + region: experienceRegion, + gpp_settings: gppSettings, + } = experience; + const usApproach = gppSettings?.us_approach; const gppRegion = deriveGppFieldRegion({ - experienceRegion: experience.region, - usApproach: experience.gpp_settings?.us_approach, + experienceRegion, + usApproach, }); const gppSection = FIDES_REGION_TO_GPP_SECTION[gppRegion]; - if (!gppSection) { - if ( - (forceGpp && !experience?.gpp_settings) || - experience?.gpp_settings?.us_approach === GPPUSApproach.STATE - ) { - cmpApi.setApplicableSections([-1]); - return []; - } + if ( + !gppSection || + (gppRegion === US_NATIONAL_REGION && usApproach === GPPUSApproach.STATE) + ) { + // If we don't have a section, we can't set anything. + // If we're using the state approach, we shouldn't return the national section, even if region is set to national. return []; } + sectionsChanged.add(gppSection); + const { consent } = cookie; const noticeKeys = Object.keys(consent); noticeKeys.forEach((noticeKey) => { - const privacyNotice = experience.privacy_notices?.find( - (n) => n.notice_key === noticeKey - ); + const privacyNotice = notices?.find((n) => n.notice_key === noticeKey); const consentValue = consent[noticeKey]; if (privacyNotice) { const { gpp_field_mapping: fieldMapping } = privacyNotice; @@ -186,7 +192,7 @@ export const setGppOptOutsFromCookieAndExperience = ({ setMspaSections({ cmpApi, sectionName: gppSection.name, - gppSettings: experience.gpp_settings, + gppSettings, }); return Array.from(sectionsChanged); diff --git a/clients/privacy-center/cypress/e2e/consent-banner-gpp.cy.ts b/clients/privacy-center/cypress/e2e/consent-banner-gpp.cy.ts index a8feb1c2b7..345b20786c 100644 --- a/clients/privacy-center/cypress/e2e/consent-banner-gpp.cy.ts +++ b/clients/privacy-center/cypress/e2e/consent-banner-gpp.cy.ts @@ -356,7 +356,7 @@ describe("Fides-js GPP extension", () => { supportedAPIs, } = data.pingData; expect(signalStatus).to.eql("ready"); - expect(applicableSections).to.eql([]); + expect(applicableSections).to.eql([-1]); expect(supportedAPIs).to.eql([]); expect(gppString).to.eql("DBAA"); }); diff --git a/clients/privacy-center/pages/api/fides-js.ts b/clients/privacy-center/pages/api/fides-js.ts index 1e06c7884e..0683460938 100644 --- a/clients/privacy-center/pages/api/fides-js.ts +++ b/clients/privacy-center/pages/api/fides-js.ts @@ -183,7 +183,6 @@ export default async function handler( allowHTMLDescription: environment.settings.ALLOW_HTML_DESCRIPTION, base64Cookie: environment.settings.BASE_64_COOKIE, fidesPrimaryColor: environment.settings.FIDES_PRIMARY_COLOR, - forceGpp: forcedGppQuery === "true", fidesClearCookie: environment.settings.FIDES_CLEAR_COOKIE, }, experience: experience || undefined,