Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PROD-1243- adds ability to provide custom fides overrides path #4462

Merged
merged 6 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
eastandwestwind marked this conversation as resolved.
Show resolved Hide resolved
};

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