From b37ae817ccf91b610b60024de9bb3a5dbdc12ad5 Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Mon, 20 Nov 2023 12:07:44 -0500 Subject: [PATCH 1/6] adds ability to provide custom fides overrides path --- clients/fides-js/src/fides-tcf.ts | 8 +++----- clients/fides-js/src/fides.ts | 8 +++----- clients/fides-js/src/lib/consent-types.ts | 3 +++ clients/fides-js/src/lib/initialize.ts | 11 +++++++---- clients/privacy-center/cypress/support/commands.ts | 5 +---- clients/privacy-center/pages/api/fides-js.ts | 1 + 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/clients/fides-js/src/fides-tcf.ts b/clients/fides-js/src/fides-tcf.ts index 7b37df720b..ba2cf4f82e 100644 --- a/clients/fides-js/src/fides-tcf.ts +++ b/clients/fides-js/src/fides-tcf.ts @@ -98,6 +98,7 @@ import { customGetConsentPreferences } from "./services/external/preferences"; declare global { interface Window { Fides: Fides; + fides_overrides: OverrideOptions; __tcfapiLocator?: Window; __tcfapi?: ( command: string, @@ -105,10 +106,6 @@ declare global { callback: (tcData: TCData, success: boolean) => void, parameter?: number | string ) => void; - config: { - // DEFER (PROD-1243): support a configurable "custom options" path - tc_info: OverrideOptions; - }; __gpp?: GppFunction; __gppLocator?: Window; } @@ -246,7 +243,7 @@ const updateFidesCookieFromString = ( */ const init = async (config: FidesConfig) => { const optionsOverrides: Partial = - getOptionsOverrides(); + getOptionsOverrides(config); makeStub({ gdprAppliesDefault: optionsOverrides?.fidesTcfGdprApplies, }); @@ -340,6 +337,7 @@ _Fides = { apiOptions: null, fidesTcfGdprApplies: true, gppExtensionPath: "", + customOptionsPath: null, }, fides_meta: {}, identity: {}, diff --git a/clients/fides-js/src/fides.ts b/clients/fides-js/src/fides.ts index da5bc1c87b..4f6dcb60ff 100644 --- a/clients/fides-js/src/fides.ts +++ b/clients/fides-js/src/fides.ts @@ -80,10 +80,7 @@ import { customGetConsentPreferences } from "./services/external/preferences"; declare global { interface Window { Fides: Fides; - config: { - // DEFER (PROD-1243): support a configurable "custom options" path - tc_info: OverrideOptions; - }; + fides_overrides: OverrideOptions; } } @@ -127,7 +124,7 @@ const updateCookie = async ( */ const init = async (config: FidesConfig) => { const optionsOverrides: Partial = - getOptionsOverrides(); + getOptionsOverrides(config); const consentPrefsOverrides: GetPreferencesFnResp | null = await customGetConsentPreferences(config); // DEFER: not implemented - ability to override notice-based consent with the consentPrefsOverrides.consent obj @@ -196,6 +193,7 @@ _Fides = { apiOptions: null, fidesTcfGdprApplies: false, gppExtensionPath: "", + customOptionsPath: null, }, fides_meta: {}, identity: {}, diff --git a/clients/fides-js/src/lib/consent-types.ts b/clients/fides-js/src/lib/consent-types.ts index 5d6775b4ff..0008d4b768 100644 --- a/clients/fides-js/src/lib/consent-types.ts +++ b/clients/fides-js/src/lib/consent-types.ts @@ -90,6 +90,9 @@ export type FidesOptions = { // GPP extension path (ex: "/fides-ext-gpp.js") gppExtensionPath: string; + + // A custom path to fetch OverrideOptions (e.g. "window.config.overrides") + customOptionsPath: string | null; }; export type GetPreferencesFnResp = { diff --git a/clients/fides-js/src/lib/initialize.ts b/clients/fides-js/src/lib/initialize.ts index 5f56f522b5..f59bcaeb5b 100644 --- a/clients/fides-js/src/lib/initialize.ts +++ b/clients/fides-js/src/lib/initialize.ts @@ -22,6 +22,7 @@ import { FidesConfig, FidesOptionsOverrides, FidesOptions, + OverrideOptions, PrivacyExperience, SaveConsentPreference, UserGeolocation, @@ -152,14 +153,16 @@ const automaticallyApplyGPCPreferences = ({ * 2) window obj (second priority) * 3) cookie value (last priority) */ -export const getOptionsOverrides = (): Partial => { +export const getOptionsOverrides = (config: FidesConfig): Partial => { const overrideOptions: Partial = {}; if (typeof window !== "undefined") { // Grab query params if provided in the URL (e.g. "?fides_string=123...") const queryParams = new URLSearchParams(window.location.search); - // Grab global window object if provided (e.g. window.config.tc_info = { fides_string: "123..." }) - // DEFER (PROD-1243): support a configurable "custom options" path - const windowObj = window.config?.tc_info; + // Grab override options if exists (e.g. window.fides_overrides = { fides_string: "123..." }) + const windowObj: OverrideOptions | undefined = config.options + .customOptionsPath + ? JSON.parse(config.options.customOptionsPath) + : window.fides_overrides; // Look for each of the override options in all three locations: query params, window object, cookie FIDES_OVERRIDE_OPTIONS_VALIDATOR_MAP.forEach( diff --git a/clients/privacy-center/cypress/support/commands.ts b/clients/privacy-center/cypress/support/commands.ts index 1cd25c78d8..d5b1bd7f3a 100644 --- a/clients/privacy-center/cypress/support/commands.ts +++ b/clients/privacy-center/cypress/support/commands.ts @@ -67,10 +67,7 @@ Cypress.Commands.add( if (windowParams) { // @ts-ignore // eslint-disable-next-line no-param-reassign - win.config = { - // DEFER (PROD-1243): support a configurable "custom options" path - tc_info: windowParams, - }; + win.fides_overrides = windowParams; } // Add event listeners for Fides.js events diff --git a/clients/privacy-center/pages/api/fides-js.ts b/clients/privacy-center/pages/api/fides-js.ts index 67e1351c9d..bf72c08138 100644 --- a/clients/privacy-center/pages/api/fides-js.ts +++ b/clients/privacy-center/pages/api/fides-js.ts @@ -155,6 +155,7 @@ export default async function handler( // Custom API override functions must be passed into custom Fides extensions via Fides.init(...) apiOptions: null, gppExtensionPath: environment.settings.GPP_EXTENSION_PATH, + customOptionsPath: null, }, experience: experience || undefined, geolocation: geolocation || undefined, From c5ea3b8869c279a505ae06befa01ec7534dc5a81 Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Mon, 27 Nov 2023 16:26:35 -0500 Subject: [PATCH 2/6] allow for custom path at which to retrieve fides override options --- clients/fides-js/src/lib/initialize.ts | 22 ++++++-- .../cypress/e2e/consent-banner-tcf.cy.ts | 53 ++++++++++++++++++- .../cypress/support/commands.ts | 13 ++++- .../cypress/support/constants.ts | 1 + 4 files changed, 83 insertions(+), 6 deletions(-) diff --git a/clients/fides-js/src/lib/initialize.ts b/clients/fides-js/src/lib/initialize.ts index f59bcaeb5b..8a44e30c30 100644 --- a/clients/fides-js/src/lib/initialize.ts +++ b/clients/fides-js/src/lib/initialize.ts @@ -144,6 +144,17 @@ const automaticallyApplyGPCPreferences = ({ } }; +/** + * Get fides override options from a custom path + */ +const getWindowObjFromPath = (path: string[]): OverrideOptions | undefined => { + if (path[0] === "window") { + path.shift(); + } + // @ts-ignore + return path.reduce((record, item) => record[item], window); +}; + /** * Gets and validates override options provided through URL query params, cookie, or window obj * @@ -159,10 +170,13 @@ export const getOptionsOverrides = (config: FidesConfig): Partial= 0 + ? getWindowObjFromPath(customPathArr) + : window.fides_overrides; // Look for each of the override options in all three locations: query params, window object, cookie FIDES_OVERRIDE_OPTIONS_VALIDATOR_MAP.forEach( 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 40cf2136b6..d0c2e5d1f7 100644 --- a/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts +++ b/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts @@ -7,7 +7,7 @@ import { PrivacyExperience, } from "fides-js"; import { CookieKeyConsent } from "fides-js/src/lib/cookie"; -import { API_URL, TCF_VERSION_HASH } from "../support/constants"; +import { API_URL, TCF_VERSION_HASH, TEST_OVERRIDE_WINDOW_PATH } from "../support/constants"; import { mockCookie, mockTcfVendorObjects } from "../support/mocks"; import { OVERRIDE, stubConfig } from "../support/stubs"; @@ -2528,6 +2528,57 @@ describe("Fides-js TCF", () => { expect(tcData.vendor.legitimateInterests).to.eql({}); }); }); + + it("uses fides_string when set via window obj at custom config path", () => { + const fidesStringOverride = + "CPzevcAPzevcAGXABBENATEIAAIAAAAAAAAAAAAAAAAA.IABE,1~"; + const expectedTCString = "CPzevcAPzevcAGXABBENATEIAAIAAAAAAAAAAAAAAAAA"; // without disclosed vendors + cy.getCookie("fides_string").should("not.exist"); + cy.fixture("consent/experience_tcf.json").then((experience) => { + stubConfig( + { + options: { + isOverlayEnabled: true, + tcfEnabled: true, + // this path is hard-coded in commands.ts for ease of testing + customOptionsPath: TEST_OVERRIDE_WINDOW_PATH, + }, + experience: experience.items[0], + }, + null, + null, + null, + { fides_string: fidesStringOverride } + ); + }); + cy.window().then((win) => { + win.__tcfapi("addEventListener", 2, cy.stub().as("TCFEvent")); + }); + // Open the modal + cy.get("#fides-modal-link").click(); + + // verify CMP API + cy.get("@TCFEvent") + .its("lastCall.args") + .then(([tcData, success]) => { + expect(success).to.eql(true); + expect(tcData.tcString).to.eql(expectedTCString); + expect(tcData.eventStatus).to.eql("cmpuishown"); + expect(tcData.purpose.consents).to.eql({ + [PURPOSE_2.id]: false, + [PURPOSE_4.id]: false, + [PURPOSE_6.id]: false, + [PURPOSE_7.id]: true, + 1: false, + 2: false, + 3: false, + 5: false, + }); + expect(tcData.purpose.legitimateInterests).to.eql({}); + expect(tcData.vendor.consents).to.eql({}); + expect(tcData.vendor.legitimateInterests).to.eql({}); + }); + }); }); describe("ac string", () => { diff --git a/clients/privacy-center/cypress/support/commands.ts b/clients/privacy-center/cypress/support/commands.ts index d5b1bd7f3a..a7effec38c 100644 --- a/clients/privacy-center/cypress/support/commands.ts +++ b/clients/privacy-center/cypress/support/commands.ts @@ -67,7 +67,18 @@ Cypress.Commands.add( if (windowParams) { // @ts-ignore // eslint-disable-next-line no-param-reassign - win.fides_overrides = windowParams; + if (options?.options.customOptionsPath) { + // hard-code path for now, as dynamically assigning to win obj is challenging in Cypress + // @ts-ignore + // eslint-disable-next-line no-param-reassign + win.config = { + tc_info: undefined, + overrides: windowParams, + }; + } else { + // eslint-disable-next-line no-param-reassign + win.fides_overrides = windowParams; + } } // Add event listeners for Fides.js events diff --git a/clients/privacy-center/cypress/support/constants.ts b/clients/privacy-center/cypress/support/constants.ts index c07c946942..28801a4479 100644 --- a/clients/privacy-center/cypress/support/constants.ts +++ b/clients/privacy-center/cypress/support/constants.ts @@ -1,2 +1,3 @@ export const API_URL = Cypress.env("API_URL"); export const TCF_VERSION_HASH = "q34r3qr4"; +export const TEST_OVERRIDE_WINDOW_PATH = "window.config.overrides"; From 644b45ffed15d55e43aaff249c9303507a30a448 Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Mon, 27 Nov 2023 16:31:07 -0500 Subject: [PATCH 3/6] add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77faad4a0a..95adeea8a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ The types of changes are: ### Added - Logging when root user and client credentials are used [#4432](https://github.com/ethyca/fides/pull/4432) +- Allow for custom path at which to retrieve Fides override options [#4462](https://github.com/ethyca/fides/pull/4462) ### Changed - Run fides with non-root user [#4421](https://github.com/ethyca/fides/pull/4421) From d3db3e0519869d70698e9f55af55b5f6eb802a79 Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Tue, 5 Dec 2023 18:21:45 +0100 Subject: [PATCH 4/6] format --- clients/fides-js/src/lib/initialize.ts | 4 +++- clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/clients/fides-js/src/lib/initialize.ts b/clients/fides-js/src/lib/initialize.ts index 8a44e30c30..4bbe1c3aef 100644 --- a/clients/fides-js/src/lib/initialize.ts +++ b/clients/fides-js/src/lib/initialize.ts @@ -164,7 +164,9 @@ const getWindowObjFromPath = (path: string[]): OverrideOptions | undefined => { * 2) window obj (second priority) * 3) cookie value (last priority) */ -export const getOptionsOverrides = (config: FidesConfig): Partial => { +export const getOptionsOverrides = ( + config: FidesConfig +): Partial => { const overrideOptions: Partial = {}; if (typeof window !== "undefined") { // Grab query params if provided in the URL (e.g. "?fides_string=123...") 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 d0c2e5d1f7..ad0be071bc 100644 --- a/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts +++ b/clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts @@ -7,7 +7,11 @@ import { PrivacyExperience, } from "fides-js"; import { CookieKeyConsent } from "fides-js/src/lib/cookie"; -import { API_URL, TCF_VERSION_HASH, TEST_OVERRIDE_WINDOW_PATH } from "../support/constants"; +import { + API_URL, + TCF_VERSION_HASH, + TEST_OVERRIDE_WINDOW_PATH, +} from "../support/constants"; import { mockCookie, mockTcfVendorObjects } from "../support/mocks"; import { OVERRIDE, stubConfig } from "../support/stubs"; From 60b1c7b22311550792a7f0ba6655cc247200de0b Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Tue, 5 Dec 2023 18:44:13 +0100 Subject: [PATCH 5/6] Address cr comments, specifically adding unit tests for getWindowObjFromPath --- .../__tests__/lib/consent-utils.test.ts | 34 ++++++++++++++++++- clients/fides-js/src/lib/consent-types.ts | 2 +- clients/fides-js/src/lib/consent-utils.ts | 13 ++++++- clients/fides-js/src/lib/initialize.ts | 12 +------ 4 files changed, 47 insertions(+), 14 deletions(-) diff --git a/clients/fides-js/__tests__/lib/consent-utils.test.ts b/clients/fides-js/__tests__/lib/consent-utils.test.ts index 9a314f73db..3c25f0bb31 100644 --- a/clients/fides-js/__tests__/lib/consent-utils.test.ts +++ b/clients/fides-js/__tests__/lib/consent-utils.test.ts @@ -1,4 +1,4 @@ -import { isPrivacyExperience } from "../../src/lib/consent-utils"; +import {getWindowObjFromPath, isPrivacyExperience} from "../../src/lib/consent-utils"; const MOCK_EXPERIENCE = { id: "132345243", @@ -102,3 +102,35 @@ describe("isPrivacyExperience", () => { expect(isPrivacyExperience(obj as any)).toBe(expected); }); }); + +describe("getWindowObjFromPath", () => { + let windowSpy: any; + + beforeEach(() => { + windowSpy = jest.spyOn(window, "window", "get"); + }); + + afterEach(() => { + windowSpy.mockRestore(); + }); + const windowMock1 = { + fides_overrides: { + hello: "something" + } + } + const windowMock2 = { + overrides: { + fides: { + hello: "something-else" + } + } + } + it.each([ + { label: "path does not exist", path: ["window","nonexistent-path"], window: windowMock1, expected: undefined }, + { label: "path is one level deep", path: ["window","fides_overrides"], window: windowMock1, expected: {hello: "something"} }, + { label: "path is two levels deep", path: ["window","overrides","fides"], window: windowMock2, expected: {hello: "something-else"} }, + ])("returns $expected when path is $path and window is $window", ({ path, window, expected }) => { + windowSpy.mockImplementation(() => (window)); + expect(getWindowObjFromPath(path as any)).toStrictEqual(expected); + }); +}); diff --git a/clients/fides-js/src/lib/consent-types.ts b/clients/fides-js/src/lib/consent-types.ts index 0008d4b768..8abdbf9a5b 100644 --- a/clients/fides-js/src/lib/consent-types.ts +++ b/clients/fides-js/src/lib/consent-types.ts @@ -91,7 +91,7 @@ export type FidesOptions = { // GPP extension path (ex: "/fides-ext-gpp.js") gppExtensionPath: string; - // A custom path to fetch OverrideOptions (e.g. "window.config.overrides") + // A custom path to fetch OverrideOptions (e.g. "window.config.overrides"). Defaults to window.fides_overrides customOptionsPath: string | null; }; diff --git a/clients/fides-js/src/lib/consent-utils.ts b/clients/fides-js/src/lib/consent-utils.ts index 099c76ff10..7ed45cbaeb 100644 --- a/clients/fides-js/src/lib/consent-utils.ts +++ b/clients/fides-js/src/lib/consent-utils.ts @@ -4,7 +4,7 @@ import { ConsentMechanism, EmptyExperience, FidesOptions, - GpcStatus, + GpcStatus, OverrideOptions, PrivacyExperience, PrivacyNotice, UserConsentPreference, @@ -255,6 +255,17 @@ export const shouldResurfaceConsent = ( ); }; +/** + * Get fides override options from a custom path + */ +export const getWindowObjFromPath = (path: string[]): OverrideOptions | undefined => { + if (path[0] === "window") { + path.shift(); + } + // @ts-ignore + return path.reduce((record, item) => record[item], window); +}; + export const getGpcStatusFromNotice = ({ value, notice, diff --git a/clients/fides-js/src/lib/initialize.ts b/clients/fides-js/src/lib/initialize.ts index 4bbe1c3aef..a5896776fd 100644 --- a/clients/fides-js/src/lib/initialize.ts +++ b/clients/fides-js/src/lib/initialize.ts @@ -31,6 +31,7 @@ import { constructFidesRegionString, debugLog, experienceIsValid, + getWindowObjFromPath, isPrivacyExperience, transformConsentToFidesUserPreference, validateOptions, @@ -144,17 +145,6 @@ const automaticallyApplyGPCPreferences = ({ } }; -/** - * Get fides override options from a custom path - */ -const getWindowObjFromPath = (path: string[]): OverrideOptions | undefined => { - if (path[0] === "window") { - path.shift(); - } - // @ts-ignore - return path.reduce((record, item) => record[item], window); -}; - /** * Gets and validates override options provided through URL query params, cookie, or window obj * From 297a33c4fe43ceb52d22a8e975bc5ad4b2f5bec8 Mon Sep 17 00:00:00 2001 From: eastandwestwind Date: Tue, 5 Dec 2023 18:44:41 +0100 Subject: [PATCH 6/6] format --- .../__tests__/lib/consent-utils.test.ts | 51 +++++++++++++------ clients/fides-js/src/lib/consent-utils.ts | 7 ++- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/clients/fides-js/__tests__/lib/consent-utils.test.ts b/clients/fides-js/__tests__/lib/consent-utils.test.ts index 3c25f0bb31..40c9226d15 100644 --- a/clients/fides-js/__tests__/lib/consent-utils.test.ts +++ b/clients/fides-js/__tests__/lib/consent-utils.test.ts @@ -1,4 +1,7 @@ -import {getWindowObjFromPath, isPrivacyExperience} from "../../src/lib/consent-utils"; +import { + getWindowObjFromPath, + isPrivacyExperience, +} from "../../src/lib/consent-utils"; const MOCK_EXPERIENCE = { id: "132345243", @@ -115,22 +118,40 @@ describe("getWindowObjFromPath", () => { }); const windowMock1 = { fides_overrides: { - hello: "something" - } - } + hello: "something", + }, + }; const windowMock2 = { overrides: { fides: { - hello: "something-else" - } - } - } + hello: "something-else", + }, + }, + }; it.each([ - { label: "path does not exist", path: ["window","nonexistent-path"], window: windowMock1, expected: undefined }, - { label: "path is one level deep", path: ["window","fides_overrides"], window: windowMock1, expected: {hello: "something"} }, - { label: "path is two levels deep", path: ["window","overrides","fides"], window: windowMock2, expected: {hello: "something-else"} }, - ])("returns $expected when path is $path and window is $window", ({ path, window, expected }) => { - windowSpy.mockImplementation(() => (window)); - expect(getWindowObjFromPath(path as any)).toStrictEqual(expected); - }); + { + label: "path does not exist", + path: ["window", "nonexistent-path"], + window: windowMock1, + expected: undefined, + }, + { + label: "path is one level deep", + path: ["window", "fides_overrides"], + window: windowMock1, + expected: { hello: "something" }, + }, + { + label: "path is two levels deep", + path: ["window", "overrides", "fides"], + window: windowMock2, + expected: { hello: "something-else" }, + }, + ])( + "returns $expected when path is $path and window is $window", + ({ path, window, expected }) => { + windowSpy.mockImplementation(() => window); + expect(getWindowObjFromPath(path as any)).toStrictEqual(expected); + } + ); }); diff --git a/clients/fides-js/src/lib/consent-utils.ts b/clients/fides-js/src/lib/consent-utils.ts index 7ed45cbaeb..be546ca5f5 100644 --- a/clients/fides-js/src/lib/consent-utils.ts +++ b/clients/fides-js/src/lib/consent-utils.ts @@ -4,7 +4,8 @@ import { ConsentMechanism, EmptyExperience, FidesOptions, - GpcStatus, OverrideOptions, + GpcStatus, + OverrideOptions, PrivacyExperience, PrivacyNotice, UserConsentPreference, @@ -258,7 +259,9 @@ export const shouldResurfaceConsent = ( /** * Get fides override options from a custom path */ -export const getWindowObjFromPath = (path: string[]): OverrideOptions | undefined => { +export const getWindowObjFromPath = ( + path: string[] +): OverrideOptions | undefined => { if (path[0] === "window") { path.shift(); }