Skip to content

Commit

Permalink
PROD-1243- adds ability to provide custom fides overrides path (#4462)
Browse files Browse the repository at this point in the history
  • Loading branch information
eastandwestwind authored Dec 5, 2023
1 parent 5dab33f commit e53d78a
Show file tree
Hide file tree
Showing 11 changed files with 161 additions and 20 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
55 changes: 54 additions & 1 deletion clients/fides-js/__tests__/lib/consent-utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { isPrivacyExperience } from "../../src/lib/consent-utils";
import {
getWindowObjFromPath,
isPrivacyExperience,
} from "../../src/lib/consent-utils";

const MOCK_EXPERIENCE = {
id: "132345243",
Expand Down Expand Up @@ -102,3 +105,53 @@ 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);
}
);
});
8 changes: 3 additions & 5 deletions clients/fides-js/src/fides-tcf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,17 +98,14 @@ import { customGetConsentPreferences } from "./services/external/preferences";
declare global {
interface Window {
Fides: Fides;
fides_overrides: OverrideOptions;
__tcfapiLocator?: Window;
__tcfapi?: (
command: string,
version: number,
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;
}
Expand Down Expand Up @@ -246,7 +243,7 @@ const updateFidesCookieFromString = (
*/
const init = async (config: FidesConfig) => {
const optionsOverrides: Partial<FidesOptionsOverrides> =
getOptionsOverrides();
getOptionsOverrides(config);
makeStub({
gdprAppliesDefault: optionsOverrides?.fidesTcfGdprApplies,
});
Expand Down Expand Up @@ -340,6 +337,7 @@ _Fides = {
apiOptions: null,
fidesTcfGdprApplies: true,
gppExtensionPath: "",
customOptionsPath: null,
},
fides_meta: {},
identity: {},
Expand Down
8 changes: 3 additions & 5 deletions clients/fides-js/src/fides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

Expand Down Expand Up @@ -127,7 +124,7 @@ const updateCookie = async (
*/
const init = async (config: FidesConfig) => {
const optionsOverrides: Partial<FidesOptionsOverrides> =
getOptionsOverrides();
getOptionsOverrides(config);
const consentPrefsOverrides: GetPreferencesFnResp | null =
await customGetConsentPreferences(config);
// DEFER: not implemented - ability to override notice-based consent with the consentPrefsOverrides.consent obj
Expand Down Expand Up @@ -196,6 +193,7 @@ _Fides = {
apiOptions: null,
fidesTcfGdprApplies: false,
gppExtensionPath: "",
customOptionsPath: null,
},
fides_meta: {},
identity: {},
Expand Down
3 changes: 3 additions & 0 deletions clients/fides-js/src/lib/consent-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"). Defaults to window.fides_overrides
customOptionsPath: string | null;
};

export type GetPreferencesFnResp = {
Expand Down
14 changes: 14 additions & 0 deletions clients/fides-js/src/lib/consent-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
EmptyExperience,
FidesOptions,
GpcStatus,
OverrideOptions,
PrivacyExperience,
PrivacyNotice,
UserConsentPreference,
Expand Down Expand Up @@ -255,6 +256,19 @@ 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,
Expand Down
17 changes: 13 additions & 4 deletions clients/fides-js/src/lib/initialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
FidesConfig,
FidesOptionsOverrides,
FidesOptions,
OverrideOptions,
PrivacyExperience,
SaveConsentPreference,
UserGeolocation,
Expand All @@ -30,6 +31,7 @@ import {
constructFidesRegionString,
debugLog,
experienceIsValid,
getWindowObjFromPath,
isPrivacyExperience,
transformConsentToFidesUserPreference,
validateOptions,
Expand Down Expand Up @@ -152,14 +154,21 @@ const automaticallyApplyGPCPreferences = ({
* 2) window obj (second priority)
* 3) cookie value (last priority)
*/
export const getOptionsOverrides = (): Partial<FidesOptionsOverrides> => {
export const getOptionsOverrides = (
config: FidesConfig
): Partial<FidesOptionsOverrides> => {
const overrideOptions: Partial<FidesOptionsOverrides> = {};
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 customPathArr: "" | null | string[] =
config.options.customOptionsPath &&
config.options.customOptionsPath.split(".");
const windowObj: OverrideOptions | undefined =
customPathArr && customPathArr.length >= 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(
Expand Down
57 changes: 56 additions & 1 deletion clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ 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";

Expand Down Expand Up @@ -2528,6 +2532,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", () => {
Expand Down
16 changes: 12 additions & 4 deletions clients/privacy-center/cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,18 @@ 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,
};
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
Expand Down
1 change: 1 addition & 0 deletions clients/privacy-center/cypress/support/constants.ts
Original file line number Diff line number Diff line change
@@ -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";
1 change: 1 addition & 0 deletions clients/privacy-center/pages/api/fides-js.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit e53d78a

Please sign in to comment.