278 lines
8.4 KiB
JavaScript
278 lines
8.4 KiB
JavaScript
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
const lazy = {};
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
JsonSchema: "resource://gre/modules/JsonSchema.sys.mjs",
|
|
RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
|
|
});
|
|
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
|
|
|
ChromeUtils.defineLazyGetter(lazy, "logConsole", () => {
|
|
return console.createInstance({
|
|
prefix: "FingerprintingWebCompatService",
|
|
maxLogLevelPref:
|
|
"privacy.fingerprintingProtection.WebCompatService.logLevel",
|
|
});
|
|
});
|
|
|
|
const SCHEMA = `{
|
|
"type": "object",
|
|
"title": "Fingerprinting Overrides",
|
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
"required": [
|
|
"firstPartyDomain",
|
|
"overrides"
|
|
],
|
|
"properties": {
|
|
"overrides": {
|
|
"type": "string",
|
|
"pattern": "^[+-][A-Za-z]+(?:,[+-][A-Za-z]+)*$",
|
|
"description": "The fingerprinting overrides. See https://searchfox.org/mozilla-central/source/toolkit/components/resistfingerprinting/RFPTargets.inc for details."
|
|
},
|
|
"firstPartyDomain": {
|
|
"type": "string",
|
|
"pattern": "^(\\*|(?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,6}$",
|
|
"description": "The first-party domain associated with the override. Use '*' to match all domains. Only legit domains allowed."
|
|
},
|
|
"thirdPartyDomain": {
|
|
"type": "string",
|
|
"pattern": "^(\\*|(?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,6}$",
|
|
"description": "The third-party domain associated with the override. Use '*' to match all domains. Only legit domains allowed. Leave this field empty if the override is only for the first-party context."
|
|
}
|
|
}
|
|
}`;
|
|
|
|
const COLLECTION_NAME = "fingerprinting-protection-overrides";
|
|
const PREF_GRANULAR_OVERRIDES =
|
|
"privacy.fingerprintingProtection.granularOverrides";
|
|
const PREF_GRANULAR_OVERRIDES_BASELINE =
|
|
"privacy.baselineFingerprintingProtection.granularOverrides";
|
|
const PREF_REMOTE_OVERRIDES_ENABLED =
|
|
"privacy.fingerprintingProtection.remoteOverrides.enabled";
|
|
|
|
XPCOMUtils.defineLazyPreferenceGetter(
|
|
lazy,
|
|
"granularOverridesPref",
|
|
PREF_GRANULAR_OVERRIDES
|
|
);
|
|
|
|
XPCOMUtils.defineLazyPreferenceGetter(
|
|
lazy,
|
|
"baselineGranularOverridesPref",
|
|
PREF_GRANULAR_OVERRIDES_BASELINE
|
|
);
|
|
|
|
XPCOMUtils.defineLazyPreferenceGetter(
|
|
lazy,
|
|
"remoteOverridesEnabled",
|
|
PREF_REMOTE_OVERRIDES_ENABLED
|
|
);
|
|
|
|
/**
|
|
* The object represents a fingerprinting override.
|
|
*/
|
|
export class FingerprintingOverride {
|
|
classID = Components.ID("{07f45442-1806-44be-9230-12eb79de9bac}");
|
|
QueryInterface = ChromeUtils.generateQI(["nsIFingerprintingOverride"]);
|
|
|
|
constructor(firstPartyDomain, thirdPartyDomain, overrides, isBaseline) {
|
|
this.firstPartyDomain = firstPartyDomain;
|
|
this.thirdPartyDomain = thirdPartyDomain;
|
|
this.overrides = overrides;
|
|
this.isBaseline = isBaseline;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The singleton service that is responsible for the WebCompat of the
|
|
* fingerprinting protection. It gets fingerprinting overrides from remote
|
|
* settings and the local test pref.
|
|
*/
|
|
export class FingerprintingWebCompatService {
|
|
classId = Components.ID("{e7b1da06-2594-4670-aea4-131070baca4c}");
|
|
QueryInterface = ChromeUtils.generateQI([
|
|
"nsIFingerprintingWebCompatService",
|
|
]);
|
|
#initialized = false;
|
|
#remoteOverrides;
|
|
#granularOverrides;
|
|
#rs;
|
|
#validator;
|
|
|
|
#isParentProcess;
|
|
|
|
constructor() {
|
|
this.#isParentProcess =
|
|
Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_DEFAULT;
|
|
|
|
this.#remoteOverrides = new Set();
|
|
this.#granularOverrides = new Set();
|
|
|
|
this.#rs = lazy.RemoteSettings(COLLECTION_NAME);
|
|
this.#validator = new lazy.JsonSchema.Validator(SCHEMA);
|
|
}
|
|
|
|
async init() {
|
|
lazy.logConsole.debug("init");
|
|
// We can only access remote settings from the parent process. So, never
|
|
// init it in the content process.
|
|
if (!this.#isParentProcess) {
|
|
throw new Error(
|
|
"Shouldn't init FingerprintingWebCompatService in content processes."
|
|
);
|
|
}
|
|
|
|
// Return if we have initiated.
|
|
if (this.#initialized) {
|
|
return;
|
|
}
|
|
this.#initialized = true;
|
|
|
|
// Register listener to import overrides when the overrides pref changes.
|
|
Services.prefs.addObserver(PREF_GRANULAR_OVERRIDES, this);
|
|
Services.prefs.addObserver(PREF_GRANULAR_OVERRIDES_BASELINE, this);
|
|
|
|
// Register the sync event for the remote settings updates.
|
|
this.#rs.on("sync", async _ => {
|
|
await this.#importRemoteSettingsOverrides();
|
|
this.#populateOverrides();
|
|
});
|
|
|
|
// Get the remote overrides from the remote settings.
|
|
await this.#importRemoteSettingsOverrides();
|
|
|
|
// Get the granular overrides from the pref.
|
|
this.#importPrefOverrides();
|
|
|
|
// Populate the overrides to the nsRFPService.
|
|
this.#populateOverrides();
|
|
|
|
lazy.logConsole.debug("Init completes");
|
|
}
|
|
|
|
// Import fingerprinting overrides from the local granular pref.
|
|
#importPrefOverrides() {
|
|
lazy.logConsole.debug("importLocalGranularOverrides");
|
|
|
|
// Clear overrides before we update.
|
|
this.#granularOverrides.clear();
|
|
|
|
for (const [pref, isBaseline] of [
|
|
[lazy.baselineGranularOverridesPref, true],
|
|
[lazy.granularOverridesPref, false],
|
|
]) {
|
|
let overrides;
|
|
try {
|
|
overrides = JSON.parse(pref || "[]");
|
|
} catch (error) {
|
|
lazy.logConsole.error(
|
|
`Failed to parse granular override JSON string: Not a valid JSON.`,
|
|
error
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Ensure we have an array we can iterate over and not an object.
|
|
if (!Array.isArray(overrides)) {
|
|
lazy.logConsole.error(
|
|
"Failed to parse granular overrides JSON String: Not an array."
|
|
);
|
|
return;
|
|
}
|
|
|
|
for (let override of overrides) {
|
|
// Validate the override.
|
|
let { valid, errors } = this.#validator.validate(override);
|
|
|
|
if (!valid) {
|
|
lazy.logConsole.debug("Override validation error", override, errors);
|
|
continue;
|
|
}
|
|
|
|
this.#granularOverrides.add(
|
|
this.#createFingerprintingOverrideFrom(override, isBaseline)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Import fingerprinting overrides from the remote settings.
|
|
async #importRemoteSettingsOverrides() {
|
|
lazy.logConsole.debug("importRemoteSettingsOverrides");
|
|
|
|
let entries;
|
|
try {
|
|
entries = await this.#rs.get();
|
|
} catch (e) {}
|
|
|
|
this.#onRemoteUpdate(entries || []);
|
|
}
|
|
|
|
#onRemoteUpdate(entries) {
|
|
lazy.logConsole.debug("onUpdateEntries", { entries });
|
|
|
|
if (!lazy.remoteOverridesEnabled) {
|
|
lazy.logConsole.debug("Abort remote overrides");
|
|
return;
|
|
}
|
|
|
|
// Clear all overrides before we update the overrides.
|
|
this.#remoteOverrides.clear();
|
|
|
|
for (let entry of entries) {
|
|
this.#remoteOverrides.add(this.#createFingerprintingOverrideFrom(entry));
|
|
}
|
|
}
|
|
|
|
#createFingerprintingOverrideFrom(entry, isBaselineOverride = null) {
|
|
// If the isBaselineOverride is not provided, we will use the value from the
|
|
// entry. If the entry doesn't have the isBaseline field, we will default to
|
|
// false.
|
|
// We default to false because we didn't have the baseline mode in the past,
|
|
// and the existing entries don't have the isBaseline field.
|
|
const isBaseline = isBaselineOverride ?? entry.isBaseline ?? false;
|
|
return new FingerprintingOverride(
|
|
entry.firstPartyDomain,
|
|
entry.thirdPartyDomain,
|
|
entry.overrides,
|
|
isBaseline
|
|
);
|
|
}
|
|
|
|
#populateOverrides() {
|
|
lazy.logConsole.debug("populateOverrides");
|
|
|
|
// Create the array that contains all overrides. We explicitly concat the
|
|
// overrides from testing pref after the ones from remote settings to ensure
|
|
// that the testing pref will take precedence.
|
|
let overrides = Array.from(this.#remoteOverrides).concat(
|
|
Array.from(this.#granularOverrides)
|
|
);
|
|
|
|
// Set the remote override to the RFP service.
|
|
Services.rfp.setFingerprintingOverrides(Array.from(overrides));
|
|
}
|
|
|
|
observe(subject, topic, prefName) {
|
|
if (
|
|
prefName != PREF_GRANULAR_OVERRIDES &&
|
|
prefName != PREF_GRANULAR_OVERRIDES_BASELINE
|
|
) {
|
|
return;
|
|
}
|
|
|
|
this.#importPrefOverrides();
|
|
this.#populateOverrides();
|
|
}
|
|
|
|
shutdown() {
|
|
lazy.logConsole.debug("shutdown");
|
|
|
|
Services.prefs.removeObserver(PREF_GRANULAR_OVERRIDES, this);
|
|
Services.prefs.removeObserver(PREF_GRANULAR_OVERRIDES_BASELINE, this);
|
|
}
|
|
}
|