Bug 1598223 - Import doh-rollout src from github into browser/extensions. r=mixedpuppy

Based on upstream 0bb6e992f7

Differential Revision: https://phabricator.services.mozilla.com/D54617

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Nihanth Subramanya 2019-11-28 16:21:52 +00:00
Родитель 97e8182bb2
Коммит c6da6883d3
21 изменённых файлов: 2009 добавлений и 0 удалений

Просмотреть файл

@ -0,0 +1,31 @@
{
"extensionName": {
"message": "DoH Roll-Out",
"description": "DNS over HTTPS add-on name for a gradual release of DoH"
},
"extensionDescription": {
"message": "Mozilla-Add-On, das die Einführung von DoH unterstützt"
},
"doorhangerName": {
"message": "Sicherere, verschlüsselte DNS-Lookups",
"description": "Bold title is shown to ask the user if they wish to disable DoH"
},
"doorhangerBody": {
"message": "Ihre Privatsphäre ist uns wichtig. Firefox leitet Ihre DNS-Anfragen jetzt nach Möglichkeit sicher an einen von Cloudflare bereitgestellten Dienst weiter, um Sie beim Surfen zu schützen.",
"description": "This text is shown to ask the user if they wish to disable DoH"
},
"doorhangerButtonOk": {
"message": "Ok, verstanden",
"description": "When the user clicks this they are confirming they know DoH is enabled"
},
"doorhangerButtonOkAccessKey": {
"message": "O"
},
"doorhangerButtonCancel2": {
"message": "Deaktivieren",
"description": "Pressing this button disables DoH"
},
"doorhangerButtonCancelAccessKey": {
"message": "S"
}
}

Просмотреть файл

@ -0,0 +1,31 @@
{
"extensionName": {
"message": "DoH Roll-Out",
"description": "DNS over HTTPS add-on name for a gradual release of DoH"
},
"extensionDescription": {
"message": "Mozilla add-on that supports the roll-out of DoH"
},
"doorhangerName": {
"message": "More secure, encrypted DNS lookups",
"description": "Bold title is shown to ask the user if they wish to disable DoH"
},
"doorhangerBody": {
"message": "Your privacy matters. Firefox now securely routes your DNS requests whenever possible to a service provided by Cloudflare to protect you while you browse.",
"description": "This text is shown to ask the user if they wish to disable DoH"
},
"doorhangerButtonOk": {
"message": "OK, Got It",
"description": "When the user clicks this they are confirming they know DoH is enabled"
},
"doorhangerButtonOkAccessKey": {
"message": "O"
},
"doorhangerButtonCancel2": {
"message": "Disable",
"description": "Pressing this button disables DoH"
},
"doorhangerButtonCancelAccessKey": {
"message": "D"
}
}

Просмотреть файл

@ -0,0 +1,31 @@
{
"extensionName": {
"message": "DoH Roll-Out",
"description": "DNS over HTTPS add-on name for a gradual release of DoH"
},
"extensionDescription": {
"message": "Mozilla add-on that supports the roll-out of DoH"
},
"doorhangerName": {
"message": "More secure, encrypted DNS lookups",
"description": "Bold title is shown to ask the user if they wish to disable DoH"
},
"doorhangerBody": {
"message": "Your privacy matters. Firefox now securely routes your DNS requests whenever possible to a service provided by Cloudflare to protect you while you browse.",
"description": "This text is shown to ask the user if they wish to disable DoH"
},
"doorhangerButtonOk": {
"message": "OK, Got It",
"description": "When the user clicks this they are confirming they know DoH is enabled"
},
"doorhangerButtonOkAccessKey": {
"message": "O"
},
"doorhangerButtonCancel2": {
"message": "Disable",
"description": "Pressing this button disables DoH"
},
"doorhangerButtonCancelAccessKey": {
"message": "D"
}
}

Просмотреть файл

@ -0,0 +1,32 @@
{
"extensionName": {
"message": "DoH Roll-Out",
"description": "DNS over HTTPS add-on name for a gradual release of DoH"
},
"extensionDescription": {
"message": "Mozilla add-on that supports the roll-out of DoH"
},
"doorhangerName": {
"message": "More secure, encrypted DNS lookups",
"description": "Bold title is shown to ask the user if they wish to disable DoH"
},
"doorhangerBody": {
"message": "Your privacy matters. Firefox now securely routes your DNS requests whenever possible to a service provided by Cloudflare to protect you while you browse.",
"description": "This text is shown to ask the user if they wish to disable DoH"
},
"doorhangerButtonOk": {
"message": "OK, Got It",
"description": "When the user clicks this they are confirming they know DoH is enabled"
},
"doorhangerButtonOkAccessKey": {
"message": "O"
},
"doorhangerButtonCancel2": {
"message": "Disable",
"description": "Pressing this button disables DoH"
},
"doorhangerButtonCancelAccessKey": {
"message": "D"
}
}

Просмотреть файл

@ -0,0 +1,31 @@
{
"extensionName": {
"message": "Lanzamiento de DoH",
"description": "DNS over HTTPS add-on name for a gradual release of DoH"
},
"extensionDescription": {
"message": "Complemento de Mozilla para el lanzamiento de DoH"
},
"doorhangerName": {
"message": "Búsquedas de DNS cifradas y más seguras",
"description": "Bold title is shown to ask the user if they wish to disable DoH"
},
"doorhangerBody": {
"message": "Tu privacidad nos importa. Ahora y siempre que sea posible, Firefox redireccionará tus solicitudes de DNS de forma segura a un servicio de Cloudfare para que estés protegido mientras navegues.",
"description": "This text is shown to ask the user if they wish to disable DoH"
},
"doorhangerButtonOk": {
"message": "OK, entendido",
"description": "When the user clicks this they are confirming they know DoH is enabled"
},
"doorhangerButtonOkAccessKey": {
"message": "O"
},
"doorhangerButtonCancel2": {
"message": "Desactivar",
"description": "Pressing this button disables DoH"
},
"doorhangerButtonCancelAccessKey": {
"message": "D"
}
}

Просмотреть файл

@ -0,0 +1,31 @@
{
"extensionName": {
"message": "Lanzamiento de DoH",
"description": "DNS over HTTPS add-on name for a gradual release of DoH"
},
"extensionDescription": {
"message": "Complemento de Mozilla para el lanzamiento de DoH"
},
"doorhangerName": {
"message": "Búsquedas de DNS cifradas y más seguras",
"description": "Bold title is shown to ask the user if they wish to disable DoH"
},
"doorhangerBody": {
"message": "Tu privacidad es importante. Firefox ahora enruta de forma segura tus solicitudes de DNS siempre que sea posible a un servicio proporcionado por Cloudflare para protegerlo mientras navega.",
"description": "This text is shown to ask the user if they wish to disable DoH"
},
"doorhangerButtonOk": {
"message": "OK, entendido",
"description": "When the user clicks this they are confirming they know DoH is enabled"
},
"doorhangerButtonOkAccessKey": {
"message": "O"
},
"doorhangerButtonCancel2": {
"message": "Deshabilitar",
"description": "Pressing this button disables DoH"
},
"doorhangerButtonCancelAccessKey": {
"message": "D"
}
}

Просмотреть файл

@ -0,0 +1,31 @@
{
"extensionName": {
"message": "Déploiement de DoH",
"description": "DNS over HTTPS add-on name for a gradual release of DoH"
},
"extensionDescription": {
"message": "Module complémentaire de Mozilla pour le déploiement de DoH"
},
"doorhangerName": {
"message": "Des requêtes DNS chiffrées et plus sûres",
"description": "Bold title is shown to ask the user if they wish to disable DoH"
},
"doorhangerBody": {
"message": "Le respect de votre vie privée est important. Désormais, et lorsque cela est possible, Firefox envoie vos requêtes DNS de manière sécurisée vers un service fourni par Cloudflare pour vous protéger pendant votre navigation.",
"description": "This text is shown to ask the user if they wish to disable DoH"
},
"doorhangerButtonOk": {
"message": "Jai compris",
"description": "When the user clicks this they are confirming they know DoH is enabled"
},
"doorhangerButtonOkAccessKey": {
"message": "c"
},
"doorhangerButtonCancel2": {
"message": "Désactiver",
"description": "Pressing this button disables DoH"
},
"doorhangerButtonCancelAccessKey": {
"message": "D"
}
}

Просмотреть файл

@ -0,0 +1,31 @@
{
"extensionName": {
"message": "Lancio DoH",
"description": "DNS over HTTPS add-on name for a gradual release of DoH"
},
"extensionDescription": {
"message": "Componente aggiuntivo realizzato da Mozilla per il lancio di DoH"
},
"doorhangerName": {
"message": "Ricerche DNS più sicure e crittate",
"description": "Bold title is shown to ask the user if they wish to disable DoH"
},
"doorhangerBody": {
"message": "La tua privacy è importante. Firefox ora indirizza in modo sicuro le richieste DNS, quando possibile, a un servizio fornito da Cloudflare per proteggerti durante la navigazione.",
"description": "This text is shown to ask the user if they wish to disable DoH"
},
"doorhangerButtonOk": {
"message": "OK, ricevuto",
"description": "When the user clicks this they are confirming they know DoH is enabled"
},
"doorhangerButtonOkAccessKey": {
"message": "O"
},
"doorhangerButtonCancel2": {
"message": "Disattiva",
"description": "Pressing this button disables DoH"
},
"doorhangerButtonCancelAccessKey": {
"message": "D"
}
}

Просмотреть файл

@ -0,0 +1,31 @@
{
"extensionName": {
"message": "Развертывание DoH",
"description": "DNS over HTTPS add-on name for a gradual release of DoH"
},
"extensionDescription": {
"message": "Дополнение Mozilla, поддерживающее развертывание DoH"
},
"doorhangerName": {
"message": "Более безопасный, зашифрованный поиск адресов сайтов в DNS",
"description": "Bold title is shown to ask the user if they wish to disable DoH"
},
"doorhangerBody": {
"message": "Ваша приватность имеет значение. Теперь, если это возможно, Firefox безопасно перенаправляет ваши DNS-запросы в службу Cloudflare, чтобы защитить вас во время интернет-серфинга.",
"description": "This text is shown to ask the user if they wish to disable DoH"
},
"doorhangerButtonOk": {
"message": "Понятно",
"description": "When the user clicks this they are confirming they know DoH is enabled"
},
"doorhangerButtonOkAccessKey": {
"message": "я"
},
"doorhangerButtonCancel2": {
"message": "Отключить",
"description": "Pressing this button disables DoH"
},
"doorhangerButtonCancelAccessKey": {
"message": "ю"
}
}

Просмотреть файл

@ -0,0 +1,31 @@
{
"extensionName": {
"message": "DoH 降世",
"description": "DNS over HTTPS add-on name for a gradual release of DoH"
},
"extensionDescription": {
"message": "附加组件Mozilla 之 DoH 降世"
},
"doorhangerName": {
"message": "更安全、经加密的 DNS 查询",
"description": "Bold title is shown to ask the user if they wish to disable DoH"
},
"doorhangerBody": {
"message": "隐私是公民的基本权利。Firefox 现在会尽可能使用 CloudFlare 所提供服务进行 DNS 请求,让网络浏览更安全。",
"description": "This text is shown to ask the user if they wish to disable DoH"
},
"doorhangerButtonOk": {
"message": "我知道了",
"description": "When the user clicks this they are confirming they know DoH is enabled"
},
"doorhangerButtonOkAccessKey": {
"message": "O"
},
"doorhangerButtonCancel2": {
"message": "禁用",
"description": "Pressing this button disables DoH"
},
"doorhangerButtonCancelAccessKey": {
"message": "D"
}
}

Просмотреть файл

@ -0,0 +1,642 @@
"use strict";
/* global browser, runHeuristics */
// Cache showConsoleLogs/DOH_DEBUG_PREF pref. This value is set during setup.start(),
// based on the `doh-rollout.debug` pref. When the pref is changed, this is updated.
let showConsoleLogs;
async function log() {
// eslint-disable-next-line no-constant-condition
if (showConsoleLogs) {
// eslint-disable-next-line no-console
console.log(...arguments);
}
}
// Gate-keeping pref to run the add-on
const DOH_ENABLED_PREF = "doh-rollout.enabled";
// Pref that sets DoH to on/off. It has multiple modes:
// 0: Off (default)
// 1: null (No setting)
// 2: Enabled, but will fall back to 0 on DNS lookup failure
// 3: Always on.
// 4: null (No setting)
// 5: Never on.
const TRR_MODE_PREF = "network.trr.mode";
// This preference is set to TRUE when DoH has been enabled via the add-on. It will
// allow the add-on to continue to function without the aid of the Normandy-triggered pref
// of "doh-rollout.enabled". Note that instead of setting it to false, it is cleared.
const DOH_SELF_ENABLED_PREF = "doh-rollout.self-enabled";
// This pref is part of a cache mechanism to see if the heuristics dictated a change in the DoH settings
const DOH_PREVIOUS_TRR_MODE_PREF = "doh-rollout.previous.trr.mode";
// Set after doorhanger has been interacted with by the user
const DOH_DOORHANGER_SHOWN_PREF = "doh-rollout.doorhanger-shown";
// Records if the user opted in/out of DoH study by clicking on doorhanger
const DOH_DOORHANGER_USER_DECISION_PREF = "doh-rollout.doorhanger-decision";
// Records if user has decided to opt out of study, either by disabling via the doorhanger,
// unchecking "DNS-over-HTTPS" with about:preferences, or manually setting network.trr.mode
const DOH_DISABLED_PREF = "doh-rollout.disable-heuristics";
// Set to true when a user has ANY enterprise policy set, making sure to not run
// heuristics, overwritting the policy.
const DOH_SKIP_HEURISTICS_PREF = "doh-rollout.skipHeuristicsCheck";
// Records when the add-on has been run once. This is in place to only check
// network.trr.mode for prefHasUserValue on first run.
const DOH_DONE_FIRST_RUN_PREF = "doh-rollout.doneFirstRun";
// This pref is set once a migration function has ran, updating local storage items to the
// new doh-rollot.X namespace. This applies to both `doneFirstRun` and `skipHeuristicsCheck`.
const DOH_BALROG_MIGRATION_PREF = "doh-rollout.balrog-migration-done";
// If set to true, debug logging will be enabled.
const DOH_DEBUG_PREF = "doh-rollout.debug";
const stateManager = {
async setState(state) {
log("setState: ", state);
browser.experiments.preferences.state.set({ value: state });
switch (state) {
case "uninstalled":
break;
case "disabled":
rollout.setSetting(TRR_MODE_PREF, 0);
break;
case "manuallyDisabled":
browser.experiments.preferences.clearUserPref(DOH_SELF_ENABLED_PREF);
break;
case "UIOk":
rollout.setSetting(DOH_SELF_ENABLED_PREF, true);
break;
case "enabled":
rollout.setSetting(TRR_MODE_PREF, 2);
rollout.setSetting(DOH_SELF_ENABLED_PREF, true);
break;
case "UIDisabled":
rollout.setSetting(TRR_MODE_PREF, 5);
browser.experiments.preferences.clearUserPref(DOH_SELF_ENABLED_PREF);
break;
}
await browser.experiments.heuristics.sendStatePing(state);
await stateManager.rememberTRRMode();
},
async rememberTRRMode() {
let curMode = await browser.experiments.preferences.getIntPref(
TRR_MODE_PREF,
0
);
log("Saving current trr mode:", curMode);
await rollout.setSetting(DOH_PREVIOUS_TRR_MODE_PREF, curMode, true);
},
async rememberDoorhangerShown() {
// This will be shown on startup and netChange events until a user clicks
// to confirm/disable DoH or presses the esc key (confirming)
log("Remembering that doorhanger has been shown");
await rollout.setSetting(DOH_DOORHANGER_SHOWN_PREF, true);
},
async rememberDoorhangerDecision(decision) {
log("Remember doorhanger decision:", decision);
await rollout.setSetting(DOH_DOORHANGER_USER_DECISION_PREF, decision, true);
},
async rememberDisableHeuristics() {
log("Remembering to never run heuristics again");
await rollout.setSetting(DOH_DISABLED_PREF, true);
},
async shouldRunHeuristics() {
// Check if heuristics has been disabled from rememberDisableHeuristics()
let disableHeuristics = await rollout.getSetting(DOH_DISABLED_PREF, false);
if (disableHeuristics) {
// Do not modify DoH for this user.
log("disableHeuristics has been enabled.");
return false;
}
let prevMode = await rollout.getSetting(DOH_PREVIOUS_TRR_MODE_PREF, 0);
let curMode = await browser.experiments.preferences.getIntPref(
TRR_MODE_PREF,
0
);
log("Comparing previous trr mode to current mode:", prevMode, curMode);
// Don't run heuristics if:
// 1) Previous doesn't mode equals current mode, i.e. user overrode our changes
// 2) TRR mode equals 5, i.e. user clicked "No" on doorhanger
// 3) TRR mode equals 3, i.e. user enabled "strictly on" for DoH
// 4) They've been disabled in the past for the reasons listed above
//
// In other words, if the user has made their own decision for DoH,
// then we want to respect that and never run the heuristics again
// On Mismatch - run never run again (make init check a function)
if (prevMode !== curMode) {
log("Mismatched, curMode: ", curMode);
// Cache results for Telemetry send, including setting eval reason
let results = await runHeuristics();
results.evaluateReason = "userModified";
if (curMode === 0 || curMode === 5) {
// If user has manually set trr.mode to 0, and it was previously something else.
browser.experiments.heuristics.sendHeuristicsPing(
"disable_doh",
results
);
browser.experiments.preferences.clearUserPref(DOH_SELF_ENABLED_PREF);
await stateManager.rememberDisableHeuristics();
} else {
// Check if trr.mode is not in default value.
await rollout.trrModePrefHasUserValue(
"shouldRunHeuristics_mismatch",
results
);
}
return false;
}
return true;
},
async shouldShowDoorhanger() {
let doorhangerShown = await rollout.getSetting(
DOH_DOORHANGER_SHOWN_PREF,
false
);
log("Should show doorhanger:", !doorhangerShown);
return !doorhangerShown;
},
async showDoorHangerAndEnableDoH() {
browser.experiments.doorhanger.onDoorhangerAccept.addListener(
rollout.doorhangerAcceptListener
);
browser.experiments.doorhanger.onDoorhangerDecline.addListener(
rollout.doorhangerDeclineListener
);
await browser.experiments.doorhanger.show({
name: browser.i18n.getMessage("doorhangerName"),
text: "<> " + browser.i18n.getMessage("doorhangerBody"),
okLabel: browser.i18n.getMessage("doorhangerButtonOk"),
okAccessKey: browser.i18n.getMessage("doorhangerButtonOkAccessKey"),
cancelLabel: browser.i18n.getMessage("doorhangerButtonCancel2"),
cancelAccessKey: browser.i18n.getMessage(
"doorhangerButtonCancelAccessKey"
),
});
// Be default, enable DoH when showing the doorhanger,
// if heuristics returned no reason to not run.
await stateManager.setState("enabled");
},
};
let notificationTime = new Date().getTime() / 1000;
const rollout = {
async doorhangerAcceptListener(tabId) {
log("Doorhanger accepted on tab", tabId);
await stateManager.setState("UIOk");
await stateManager.rememberDoorhangerDecision("UIOk");
await stateManager.rememberDoorhangerShown();
},
async doorhangerDeclineListener(tabId) {
log("Doorhanger declined on tab", tabId);
await stateManager.setState("UIDisabled");
await stateManager.rememberDoorhangerDecision("UIDisabled");
let results = await runHeuristics();
results.evaluateReason = "doorhangerDecline";
browser.experiments.heuristics.sendHeuristicsPing("disable_doh", results);
await stateManager.rememberDisableHeuristics();
await stateManager.rememberDoorhangerShown();
},
async netChangeListener() {
// Possible race condition between multiple notifications?
let curTime = new Date().getTime() / 1000;
let timePassed = curTime - notificationTime;
log("Time passed since last network change:", timePassed);
if (timePassed < 30) {
return;
}
notificationTime = curTime;
// Run heuristics to determine if DoH should be disabled
let decision = await rollout.heuristics("netChange");
if (decision === "disable_doh") {
await stateManager.setState("disabled");
} else {
await stateManager.setState("enabled");
}
},
async heuristics(evaluateReason) {
// Run heuristics defined in heuristics.js and experiments/heuristics/api.js
let results = await runHeuristics();
// Check if DoH should be disabled
let disablingDoh = Object.values(results).some(
item => item === "disable_doh"
);
let decision;
if (disablingDoh) {
decision = "disable_doh";
} else {
decision = "enable_doh";
}
log("Heuristics decision on " + evaluateReason + ": " + decision);
// Send Telemetry on results of heuristics
results.evaluateReason = evaluateReason;
browser.experiments.heuristics.sendHeuristicsPing(decision, results);
return decision;
},
async getSetting(name, defaultValue) {
if (defaultValue === undefined) {
throw new Error(
`Missing defaultValue argument when trying to fetch pref: ${JSON.stringify(
name
)}`
);
}
switch (typeof defaultValue) {
case "boolean":
log({
context: "getSetting",
type: "boolean",
name,
value: await browser.experiments.preferences.getBoolPref(
name,
defaultValue
),
});
return await browser.experiments.preferences.getBoolPref(
name,
defaultValue
);
case "number":
log({
context: "getSetting",
type: "number",
name,
value: await browser.experiments.preferences.getIntPref(
name,
defaultValue
),
});
return await browser.experiments.preferences.getIntPref(
name,
defaultValue
);
case "string":
log({
context: "getSetting",
type: "string",
name,
value: await browser.experiments.preferences.getCharPref(
name,
defaultValue
),
});
return await browser.experiments.preferences.getCharPref(
name,
defaultValue
);
}
},
/**
* Exposed
*
* @param {type} name description
* @param {type} value description
* @return {type} description
*/
async setSetting(name, value) {
// Based on type of pref, set pref accordingly
switch (typeof value) {
case "boolean":
await browser.experiments.preferences.setBoolPref(name, value);
break;
case "number":
await browser.experiments.preferences.setIntPref(name, value);
break;
case "string":
await browser.experiments.preferences.setCharPref(name, value);
break;
default:
throw new Error("setSetting typeof value unknown!");
}
},
async trrModePrefHasUserValue(event, results) {
results.evaluateReason = event;
// Reset skipHeuristicsCheck
await rollout.setSetting(DOH_SKIP_HEURISTICS_PREF, false);
// This confirms if a user has modified DoH (via the TRR_MODE_PREF) outside of the addon
// This runs only on the FIRST time that add-on is enabled and if the stored pref
// mismatches the current pref (Meaning something outside of the add-on has changed it)
if (await browser.experiments.preferences.prefHasUserValue(TRR_MODE_PREF)) {
// Send ping that user had specific trr.mode pref set before add-on study was ran.
// Note that this does not include the trr.mode - just that the addon cannot be ran.
browser.experiments.heuristics.sendHeuristicsPing(
"prefHasUserValue",
results
);
browser.experiments.preferences.clearUserPref(DOH_SELF_ENABLED_PREF);
await stateManager.rememberDisableHeuristics();
}
},
async enterprisePolicyCheck(event, results) {
results.evaluateReason = event;
// Reset skipHeuristicsCheck
await rollout.setSetting(DOH_SKIP_HEURISTICS_PREF, false);
// Check for Policies before running the rest of the heuristics
let policyEnableDoH = await browser.experiments.heuristics.checkEnterprisePolicies();
switch (policyEnableDoH) {
case "enable_doh":
log("Policy requires DoH enabled.");
browser.experiments.heuristics.sendHeuristicsPing(
policyEnableDoH,
results
);
break;
case "policy_without_doh":
log("Policy does not mention DoH.");
await stateManager.setState("disabled");
browser.experiments.heuristics.sendHeuristicsPing(
policyEnableDoH,
results
);
break;
case "disable_doh":
log("Policy requires DoH to be disabled.");
browser.experiments.heuristics.sendHeuristicsPing(
policyEnableDoH,
results
);
break;
case "no_policy_set":
}
// Determine to skip additional heuristics (by presence of an enterprise policy)
if (policyEnableDoH === "no_policy_set") {
// Resetting skipHeuristicsCheck in case a user had a policy and then removed it!
await rollout.setSetting(DOH_SKIP_HEURISTICS_PREF, false);
} else {
// Don't check for prefHasUserValue if policy is set to disable DoH
await rollout.setSetting(DOH_SKIP_HEURISTICS_PREF, true);
}
},
async migrateLocalStoragePrefs() {
// Migrate updated local storage item names. If this has already been done once, skip the migration
const isMigrated = await browser.experiments.preferences.getBoolPref(
DOH_BALROG_MIGRATION_PREF,
false
);
if (isMigrated) {
log("User has been migrated.");
return;
}
// Check all local storage keys from v1.0.4 users and migrate them to prefs.
// This only applies to keys that have a value.
const legacyLocalStorageKeys = [
"doneFirstRun",
"skipHeuristicsCheck",
DOH_ENABLED_PREF,
DOH_PREVIOUS_TRR_MODE_PREF,
DOH_DOORHANGER_SHOWN_PREF,
DOH_DOORHANGER_USER_DECISION_PREF,
DOH_DISABLED_PREF,
];
for (let item of legacyLocalStorageKeys) {
let data = await browser.storage.local.get(item);
let value = data[item];
log({ context: "migration", item, value });
if (data.hasOwnProperty(item)) {
let migratedName = item;
if (!item.startsWith("doh-rollout.")) {
migratedName = "doh-rollout." + item;
}
await this.setSetting(migratedName, value);
}
}
// Set pref to skip this function in the future.
browser.experiments.preferences.setBoolPref(
DOH_BALROG_MIGRATION_PREF,
true
);
log("Remembering that this user has been migrated.");
},
async init() {
log("calling init");
// Check if the add-on has run before
let doneFirstRun = await rollout.getSetting(DOH_DONE_FIRST_RUN_PREF, false);
// Register the events for sending pings
browser.experiments.heuristics.setupTelemetry();
// Cache runHeuristics results for first run/start up checks
let results = await runHeuristics();
if (!doneFirstRun) {
log("first run!");
await rollout.setSetting(DOH_DONE_FIRST_RUN_PREF, true);
// Check if user has a set a custom pref only on first run, not on each startup
await this.trrModePrefHasUserValue("first_run", results);
await this.enterprisePolicyCheck("first_run", results);
} else {
log("not first run!");
await this.enterprisePolicyCheck("startup", results);
}
// Only run the heuristics if user hasn't explicitly enabled/disabled DoH
let skipHeuristicsCheck = await rollout.getSetting(
DOH_SKIP_HEURISTICS_PREF,
false
);
log("skipHeuristicsCheck: ", skipHeuristicsCheck);
if (!skipHeuristicsCheck) {
let shouldRunHeuristics = await stateManager.shouldRunHeuristics();
if (shouldRunHeuristics) {
await rollout.main();
}
}
// Listen for network change events to run heuristics again
browser.experiments.netChange.onConnectionChanged.addListener(async () => {
log("onConnectionChanged");
// Only run the heuristics if user hasn't explicitly enabled/disabled DoH
let shouldRunHeuristics = await stateManager.shouldRunHeuristics();
let shouldShowDoorhanger = await stateManager.shouldShowDoorhanger();
if (shouldRunHeuristics) {
const netChangeDecision = await rollout.heuristics("netChange");
if (netChangeDecision === "disable_doh") {
await stateManager.setState("disabled");
} else if (shouldShowDoorhanger) {
await stateManager.showDoorHangerAndEnableDoH();
} else {
await stateManager.setState("enabled");
}
}
});
},
async main() {
// Listen to the captive portal when it unlocks
browser.captivePortal.onStateChanged.addListener(rollout.onReady);
// If the captive portal is already unlocked or doesn't exist,
// run the measurement
let captiveState = await browser.captivePortal.getState();
log("Captive state:", captiveState);
if (captiveState === "unlocked_portal" || captiveState === "not_captive") {
await rollout.onReady({ state: captiveState });
}
},
async onReady(details) {
// Now that we're here, stop listening to the captive portal
browser.captivePortal.onStateChanged.removeListener(rollout.onReady);
// Only proceed if we're not behind a captive portal
if (
details.state !== "unlocked_portal" &&
details.state !== "not_captive"
) {
return;
}
// Run startup heuristics to determine if DoH should be disabled
let decision = await rollout.heuristics("startup");
let shouldShowDoorhanger = await stateManager.shouldShowDoorhanger();
if (decision === "disable_doh") {
await stateManager.setState("disabled");
// If the heuristics say to enable DoH, determine if the doorhanger
// should be shown
} else if (shouldShowDoorhanger) {
await stateManager.showDoorHangerAndEnableDoH();
} else {
// Doorhanger has been shown before and did not opt-out
await stateManager.setState("enabled");
}
},
};
async function checkNormandyAddonStudy() {
const study = await browser.normandyAddonStudy.getStudy();
if (typeof study === "undefined" || study === undefined) {
log("No Normandy study detected!");
return false;
}
const branch = study.branch;
switch (branch) {
case "doh-rollout-heuristics":
return true;
case "doh-rollout-disabled":
return false;
default:
throw new Error(`Unexpected study branch: ${JSON.stringify(branch)}`);
}
}
const setup = {
async start() {
showConsoleLogs = await browser.experiments.preferences.getBoolPref(
DOH_DEBUG_PREF,
false
);
// Run Migration First, to continue to run rest of start up logic
await rollout.migrateLocalStoragePrefs();
const isNormandyStudy = await checkNormandyAddonStudy();
const isAddonDisabled = await rollout.getSetting(DOH_DISABLED_PREF, false);
const runAddonPref = await rollout.getSetting(DOH_ENABLED_PREF, false);
const runAddonBypassPref = await rollout.getSetting(
DOH_SELF_ENABLED_PREF,
false
);
const runAddonDoorhangerDecision = await rollout.getSetting(
DOH_DOORHANGER_USER_DECISION_PREF,
""
);
const runAddonPreviousTRRMode = await rollout.getSetting(
DOH_PREVIOUS_TRR_MODE_PREF,
0
);
if (isAddonDisabled) {
// Regardless of pref, the user has chosen/heuristics dictated that this add-on should be disabled.
// DoH status will not be modified from whatever the current setting is at runtime
log(
"Addon has been disabled. DoH status will not be modified from current setting"
);
await stateManager.rememberDisableHeuristics();
return;
}
if (
runAddonPref ||
runAddonBypassPref ||
runAddonDoorhangerDecision === "UIOk" ||
runAddonDoorhangerDecision === "enabled" ||
runAddonPreviousTRRMode === 2 ||
runAddonPreviousTRRMode === 0 ||
isNormandyStudy
) {
// Confirms that the Normandy/default branch gate keeping pref is set to true,
// the self-enabled pref has been activated or if a user has accepted/enabled
// DoH if the normandyAddonStudy branch is set to "enable"!
rollout.init();
} else {
log(
"Init not ran on startup. Watching `doh-rollout.enabled` pref for change event"
);
}
// Set listener for Normandy pref update past inital startup
browser.experiments.preferences.onPrefChanged.addListener(() =>
this.start()
);
},
};
setup.start();

Просмотреть файл

@ -0,0 +1,134 @@
"use strict";
/* global Services, ChromeUtils, BrowserWindowTracker,
ExtensionCommon, ExtensionAPI */
/* exported doorhanger */
ChromeUtils.import("resource://gre/modules/Console.jsm");
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
ChromeUtils.import("resource://gre/modules/ExtensionUtils.jsm");
var { EventManager, EventEmitter } = ExtensionCommon;
const {
Management: {
global: { tabTracker },
},
} = ChromeUtils.import("resource://gre/modules/Extension.jsm", null);
ChromeUtils.defineModuleGetter(
this,
"BrowserWindowTracker",
"resource:///modules/BrowserWindowTracker.jsm"
);
/** Return most recent NON-PRIVATE browser window, so that we can
* manipulate chrome elements on it.
*/
function getMostRecentBrowserWindow() {
return BrowserWindowTracker.getTopWindow({
private: false,
allowPopups: false,
});
}
class DoorhangerEventEmitter extends EventEmitter {
async emitShow({
name,
text,
okLabel,
okAccessKey,
cancelLabel,
cancelAccessKey,
}) {
const self = this;
const recentWindow = getMostRecentBrowserWindow();
const browser = recentWindow.gBrowser.selectedBrowser;
const tabId = tabTracker.getBrowserTabId(browser);
const primaryAction = {
disableHighlight: false,
label: okLabel,
accessKey: okAccessKey,
callback: () => {
self.emit("doorhanger-accept", tabId);
},
};
const secondaryActions = [
{
label: cancelLabel,
accessKey: cancelAccessKey,
callback: () => {
self.emit("doorhanger-decline", tabId);
},
},
];
let learnMoreURL = Services.urlFormatter.formatURL(
"https://support.mozilla.org/%LOCALE%/kb/firefox-dns-over-https"
);
const options = {
hideClose: true,
persistWhileVisible: true,
persistent: true,
autofocus: true,
name,
popupIconURL: "chrome://browser/skin/connection-secure.svg",
learnMoreURL,
escAction: "buttoncommand",
removeOnDismissal: false,
};
recentWindow.PopupNotifications.show(
browser,
"doh-first-time",
text,
null,
primaryAction,
secondaryActions,
options
);
}
}
var doorhanger = class doorhanger extends ExtensionAPI {
getAPI(context) {
const doorhangerEventEmitter = new DoorhangerEventEmitter();
return {
experiments: {
doorhanger: {
async show(properties) {
await doorhangerEventEmitter.emitShow(properties);
},
onDoorhangerAccept: new EventManager({
context,
name: "doorhanger.onDoorhangerAccept",
register: fire => {
let listener = (value, tabId) => {
fire.async(tabId);
};
doorhangerEventEmitter.on("doorhanger-accept", listener);
return () => {
doorhangerEventEmitter.off("doorhanger-accept", listener);
};
},
}).api(),
onDoorhangerDecline: new EventManager({
context,
name: "doorhanger.onDoorhangerDecline",
register: fire => {
let listener = (value, tabId) => {
fire.async(tabId);
};
doorhangerEventEmitter.on("doorhanger-decline", listener);
return () => {
doorhangerEventEmitter.off("doorhanger-decline", listener);
};
},
}).api(),
},
},
};
}
};

Просмотреть файл

@ -0,0 +1,48 @@
[
{
"namespace": "experiments.doorhanger",
"description":
"Can be used to show a notification to users",
"functions": [
{
"name": "show",
"type": "function",
"description": "Show the notification to the user.",
"async": true,
"parameters": [
{
"type": "object",
"name": "properties",
"description": "Text properties for the doorhanger",
"properties": {
"name": {"type": "string", "description": "Name of the doorhanger"},
"text": {"type": "string", "description": "Description in the doorhanger"},
"okLabel": {"type": "string"},
"okAccessKey": {"type": "string"},
"cancelLabel": {"type": "string"},
"cancelAccessKey": {"type": "string"}
}
}
]
}
],
"events": [
{
"name": "onDoorhangerAccept",
"type": "function",
"description": "The user clicked 'OK, Got It'",
"parameters": [
{"type": "integer", "name": "tabId", "minimum": 0}
]
},
{
"name": "onDoorhangerDecline",
"type": "function",
"description": "The user clicked 'Disable Protection'",
"parameters": [
{"type": "integer", "name": "tabId", "minimum": 0}
]
}
]
}
]

Просмотреть файл

@ -0,0 +1,166 @@
"use strict";
/* exported heuristics */
/* global Cc, Ci, Components, ExtensionAPI, Services */
let Cu3 = Components.utils;
Cu3.import("resource://gre/modules/Services.jsm");
function log() {
// eslint-disable-next-line no-constant-condition
if (false) {
// eslint-disable-next-line no-console
console.log(...arguments);
}
}
let pcs = Cc["@mozilla.org/parental-controls-service;1"].getService(
Ci.nsIParentalControlsService
);
const TELEMETRY_CATEGORY = "doh";
const TELEMETRY_EVENTS = {
evaluate: {
methods: ["evaluate"],
objects: ["heuristics"],
extra_keys: [
"google",
"youtube",
"zscalerCanary",
"canary",
"modifiedRoots",
"browserParent",
"thirdPartyRoots",
"policy",
"evaluateReason",
],
record_on_release: true,
},
state: {
methods: ["state"],
objects: [
"loaded",
"enabled",
"disabled",
"manuallyDisabled",
"uninstalled",
"UIOk",
"UIDisabled",
],
extra_keys: [],
record_on_release: true,
},
};
const heuristicsManager = {
setupTelemetry() {
// Set up the Telemetry for the heuristics and addon state
Services.telemetry.registerEvents(TELEMETRY_CATEGORY, TELEMETRY_EVENTS);
},
sendHeuristicsPing(decision, results) {
log("Sending a heuristics ping", decision, results);
Services.telemetry.recordEvent(
TELEMETRY_CATEGORY,
"evaluate",
"heuristics",
decision,
results
);
},
sendStatePing(state) {
log("Sending an addon state ping", state);
Services.telemetry.recordEvent(TELEMETRY_CATEGORY, "state", state, "null");
},
async checkEnterprisePolicies() {
if (Services.policies.status === Services.policies.ACTIVE) {
let policies = Services.policies.getActivePolicies();
if (!("DNSOverHTTPS" in policies)) {
// If DoH isn't in the policy, return that there is a policy (but no DoH specifics)
return "policy_without_doh";
}
let dohPolicy = policies.DNSOverHTTPS;
if (dohPolicy.Enabled === true) {
// If DoH is enabled in the policy, enable it
return "enable_doh";
}
// If DoH is disabled in the policy, disable it
return "disable_doh";
}
// Default return, meaning no policy related to DNSOverHTTPS
return "no_policy_set";
},
async checkParentalControls() {
let enabled = pcs.parentalControlsEnabled;
if (enabled) {
return "disable_doh";
}
return "enable_doh";
},
async checkThirdPartyRoots() {
let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
Ci.nsIX509CertDB
);
let allCerts = certdb.getCerts();
// Pre 71 code (Bug 1577836)
if (allCerts.getEnumerator) {
allCerts = allCerts.getEnumerator();
}
for (let cert of allCerts) {
if (
certdb.isCertTrusted(
cert,
Ci.nsIX509Cert.CA_CERT,
Ci.nsIX509CertDB.TRUSTED_SSL
)
) {
if (!cert.isBuiltInRoot) {
// this cert is a trust anchor that wasn't shipped with the browser
return "disable_doh";
}
}
}
return "enable_doh";
},
};
var heuristics = class heuristics extends ExtensionAPI {
getAPI() {
return {
experiments: {
heuristics: {
setupTelemetry() {
heuristicsManager.setupTelemetry();
},
sendHeuristicsPing(decision, results) {
heuristicsManager.sendHeuristicsPing(decision, results);
},
sendStatePing(state) {
heuristicsManager.sendStatePing(state);
},
async checkEnterprisePolicies() {
let result = await heuristicsManager.checkEnterprisePolicies();
return result;
},
async checkParentalControls() {
let result = await heuristicsManager.checkParentalControls();
return result;
},
async checkThirdPartyRoots() {
let result = await heuristicsManager.checkThirdPartyRoots();
return result;
},
},
},
};
}
};

Просмотреть файл

@ -0,0 +1,106 @@
[
{
"namespace": "experiments.heuristics",
"description": "Heuristics for disabling DNS-over-HTTPS (DoH)",
"functions": [
{
"name": "setupTelemetry",
"type": "function",
"description": "Sets up the Telemetry for the addon",
"parameters": [
],
"async": false
},
{
"name": "sendHeuristicsPing",
"type": "function",
"description": "Sends a ping for the results of the heuristics",
"parameters": [
{
"name": "decision",
"type": "string"
},
{
"name": "results",
"type": "object",
"properties": {
"google": {
"description": "Indicates whether Google safe-search is enabled",
"type": "string"
},
"youtube": {
"description": "Indicates whether YouTube safe-search is enabled",
"type": "string"
},
"zscalerCanary": {
"description": "Indicates whether Zscaler's Shift is enabled",
"type": "string"
},
"canary": {
"description": "Indicates whether global canary domain was filtered",
"type": "string"
},
"modifiedRoots": {
"description": "Indicates whether enterprise roots are enabled",
"type": "string"
},
"browserParent": {
"description": "Indicates whether browser has enabled parental controls",
"type": "string"
},
"thirdPartyRoots": {
"description": "Indicates whether third-party roots are enabled",
"type": "string"
},
"policy": {
"description": "Indicates whether browser policy blocks DoH",
"type": "string"
},
"evaluateReason": {
"description": "Reason why we are running heuristics, e.g. startup",
"type": "string"
}
}
}
],
"async": false
},
{
"name": "sendStatePing",
"type": "function",
"description": "Sends a ping for the state of the addon",
"parameters": [
{
"name": "state",
"type": "string"
}
],
"async": false
},
{
"name": "checkEnterprisePolicies",
"type": "function",
"description": "Checks for enterprise policies",
"parameters": [
],
"async": true
},
{
"name": "checkParentalControls",
"type": "function",
"description": "Checks for browser-based parental controls",
"parameters": [
],
"async": true
},
{
"name": "checkThirdPartyRoots",
"type": "function",
"description": "Checks for third party roots",
"parameters": [
],
"async": true
}
]
}
]

Просмотреть файл

@ -0,0 +1,62 @@
"use strict";
/* exported netChange */
/* global Cc, Ci, Components, EventManager, ExtensionAPI, Services, ExtensionCommon */
let Cu4 = Components.utils;
Cu4.import("resource://gre/modules/Services.jsm");
Cu4.import("resource://gre/modules/ExtensionCommon.jsm");
const { setTimeout } = Cu4.import("resource://gre/modules/Timer.jsm");
var { EventManager } = ExtensionCommon;
let gNetworkLinkService = Cc[
"@mozilla.org/network/network-link-service;1"
].getService(Ci.nsINetworkLinkService);
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
let netChangeWaiting = false;
var netChange = class netChange extends ExtensionAPI {
getAPI(context) {
return {
experiments: {
netChange: {
onConnectionChanged: new EventManager({
context,
name: "netChange.onConnectionChanged",
register: fire => {
let observer = async (subject, topic, data) => {
if (netChangeWaiting) {
return;
}
if (data === "changed" || data === "up") {
// Trigger the netChangeWaiting switch, initiating 5sec timeout
netChangeWaiting = true;
await sleep(60000);
if (
gNetworkLinkService.linkStatusKnown &&
gNetworkLinkService.isLinkUp
) {
fire.async(data);
}
// Reset the netChangeWaiting switch
netChangeWaiting = false;
}
};
Services.obs.addObserver(observer, "network:link-status-changed");
return () => {
Services.obs.removeObserver(
observer,
"network:link-status-changed"
);
};
},
}).api(),
},
},
};
}
};

Просмотреть файл

@ -0,0 +1,19 @@
[
{
"namespace": "experiments.netChange",
"events": [
{
"name": "onConnectionChanged",
"type": "function",
"description": "Fired when the user has changed networks",
"parameters": [
{
"type": "boolean",
"name": "connectivity",
"description": "True if we have network connectivity, false otherwise"
}
]
}
]
}
]

Просмотреть файл

@ -0,0 +1,100 @@
"use strict";
/* exported preferences */
/* global Components, ExtensionAPI, ExtensionCommon, Services */
let Cu2 = Components.utils;
Cu2.import("resource://gre/modules/Services.jsm");
Cu2.import("resource://gre/modules/ExtensionSettingsStore.jsm");
Cu2.import("resource://gre/modules/AddonManager.jsm");
Cu2.import("resource://gre/modules/NetUtil.jsm");
Cu2.import("resource://gre/modules/ExtensionPreferencesManager.jsm");
/* global ExtensionPreferencesManager */
// TODO file scope issue on experiments that join extension contexts causing redeclaration issues.
const TRR_URI_PREF = "network.trr.uri";
const TRR_DISABLE_ECS_PREF = "network.trr.disable-ECS";
ExtensionPreferencesManager.addSetting("dohRollout.state", {
prefNames: [TRR_URI_PREF, TRR_DISABLE_ECS_PREF],
setCallback() {
let prefs = {};
prefs[TRR_URI_PREF] = "https://mozilla.cloudflare-dns.com/dns-query";
prefs[TRR_DISABLE_ECS_PREF] = true;
return prefs;
},
});
var preferences = class preferences extends ExtensionAPI {
getAPI(context) {
const EventManager = ExtensionCommon.EventManager;
return {
experiments: {
preferences: {
async getIntPref(name, defaultValue) {
return Services.prefs.getIntPref(name, defaultValue);
},
async setIntPref(name, defaultValue) {
return Services.prefs.setIntPref(name, defaultValue);
},
async getBoolPref(name, defaultValue) {
return Services.prefs.getBoolPref(name, defaultValue);
},
async setBoolPref(name, defaultValue) {
return Services.prefs.setBoolPref(name, defaultValue);
},
async getCharPref(name, defaultValue) {
return Services.prefs.getCharPref(name, defaultValue);
},
async setCharPref(name, defaultValue) {
return Services.prefs.setCharPref(name, defaultValue);
},
async clearUserPref(name) {
return Services.prefs.clearUserPref(name);
},
async prefHasUserValue(name) {
return Services.prefs.prefHasUserValue(name);
},
onPrefChanged: new EventManager({
context,
name: "preferences.onPrefChanged",
register: fire => {
let observer = () => {
fire.async();
};
Services.prefs.addObserver("doh-rollout.enabled", observer);
Services.prefs.addObserver("doh-rollout.debug", observer);
return () => {
Services.prefs.removeObserver("doh-rollout.enabled", observer);
Services.prefs.removeObserver("doh-rollout.debug", observer);
};
},
}).api(),
state: Object.assign(
ExtensionPreferencesManager.getSettingsAPI(
context.extension.id,
"dohRollout.state",
() => {
throw new Error("Not supported");
},
undefined,
false,
() => {}
),
{
set: details => {
return ExtensionPreferencesManager.setSetting(
context.extension.id,
"dohRollout.state",
details.value
);
},
}
),
},
},
};
}
};

Просмотреть файл

@ -0,0 +1,212 @@
[
{
"namespace": "experiments.preferences",
"description": "Manage prefs for an addon",
"types": [
{
"id": "pref",
"type": "object",
"description": "Preference to set",
"properties": {
"name": {
"type": "string"
},
"type": {
"type": "string"
},
"value": {
"type": "any"
}
}
}
],
"events": [
{
"name": "onPrefChanged",
"type": "function",
"description": "Fired when the proxy setting is changed.",
"parameters": []
}
],
"functions": [
{
"name": "get",
"type": "function",
"description": "Gets the value of the preference manager",
"parameters": [
{
"type": "string",
"name": "settingName"
}
],
"async": true
},
{
"name": "clear",
"type": "function",
"description": "Deletes the extension and clears up",
"parameters": [
{
"type": "any",
"name": "stateName"
}
],
"async": true
},
{
"name": "set",
"type": "function",
"description": "Sets the value of the preference manager",
"parameters": [
{
"type": "string",
"name": "settingName"
},
{
"type": "any",
"name": "value"
}
],
"async": true
},
{
"name": "add",
"type": "function",
"description": "Sets up a preference manager that we can manage later",
"parameters": [
{
"type": "string",
"name": "settingName"
}
],
"async": true
},
{
"name": "getIntPref",
"type": "function",
"description": "Get the value of a integer preference",
"parameters": [
{
"type": "string",
"name": "name"
},
{
"type": "integer",
"name": "defaultValue"
}
],
"async": true
},
{
"name": "setIntPref",
"type": "function",
"description": "Sets the value of a integer preference",
"parameters": [
{
"type": "string",
"enum": ["network.trr.mode", "doh-rollout.previous.trr.mode"]
},
{
"type": "integer",
"name": "defaultValue"
}
],
"async": true
},
{
"name": "getBoolPref",
"type": "function",
"description": "Get the value of a boolean preference",
"parameters": [
{
"type": "string",
"name": "name"
},
{
"type": "boolean",
"name": "defaultValue"
}
],
"async": true
},
{
"name": "setBoolPref",
"type": "function",
"description": "Sets the value of a boolean preference",
"parameters": [
{
"type": "string",
"enum": ["doh-rollout.doorhanger-shown", "doh-rollout.self-enabled", "doh-rollout.disable-heuristics", "doh-rollout.doneFirstRun", "doh-rollout.skipHeuristicsCheck", "doh-rollout.balrog-migration-done"]
},
{
"type": "boolean",
"name": "defaultValue"
}
],
"async": true
},
{
"name": "getCharPref",
"type": "function",
"description": "Gets the value of a string preference",
"parameters": [
{
"type": "string",
"enum": ["doh-rollout.doorhanger-decision"]
},
{
"type": "string",
"name": "defaultValue"
}
],
"async": true
},
{
"name": "setCharPref",
"type": "function",
"description": "Sets the value of a string preference",
"parameters": [
{
"type": "string",
"enum": ["doh-rollout.doorhanger-decision"]
},
{
"type": "string",
"name": "defaultValue"
}
],
"async": true
},
{
"name": "clearUserPref",
"type": "function",
"description": "Resets value of prefence back to default",
"parameters": [
{
"type": "string",
"enum": ["doh-rollout.self-enabled"]
}
],
"async": true
},
{
"name": "prefHasUserValue",
"type": "function",
"description": "Check if the user has set a value of a preference",
"parameters": [
{
"type": "string",
"name": "name"
}
],
"async": true
}
],
"properties": {
"state": {
"$ref": "types.Setting",
"description": "This property controls the proxy settings"
}
}
}
]

Просмотреть файл

@ -0,0 +1,147 @@
"use strict";
/* global browser */
/* exported runHeuristics */
const GLOBAL_CANARY = "use-application-dns.net";
// TODO: Confirm that this error message corresponds to NXDOMAIN
const NXDOMAIN_ERR = "NS_ERROR_UNKNOWN_HOST";
async function dnsLookup(hostname) {
let flags = ["disable_trr", "disable_ipv6", "bypass_cache"];
let addresses, err;
try {
let response = await browser.dns.resolve(hostname, flags);
addresses = response.addresses;
} catch (e) {
addresses = [null];
err = e.message;
}
return { addresses, err };
}
async function dnsListLookup(domainList) {
let results = [];
for (let i = 0; i < domainList.length; i++) {
let domain = domainList[i];
let { addresses } = await dnsLookup(domain);
results = results.concat(addresses);
}
return results;
}
async function safeSearch() {
const providerList = [
{
name: "google",
unfiltered: ["www.google.com", "google.com"],
safeSearch: ["forcesafesearch.google.com"],
},
{
name: "youtube",
unfiltered: [
"www.youtube.com",
"m.youtube.com",
"youtubei.googleapis.com",
"youtube.googleapis.com",
"www.youtube-nocookie.com",
],
safeSearch: ["restrict.youtube.com", "restrictmoderate.youtube.com"],
},
];
// Compare strict domain lookups to non-strict domain lookups
let safeSearchChecks = {};
for (let i = 0; i < providerList.length; i++) {
let providerObj = providerList[i];
let providerName = providerObj.name;
safeSearchChecks[providerName] = "enable_doh";
let results = {};
results.unfilteredAnswers = await dnsListLookup(providerObj.unfiltered);
results.safeSearchAnswers = await dnsListLookup(providerObj.safeSearch);
// Given a provider, check if the answer for any safe search domain
// matches the answer for any default domain
for (let j = 0; j < results.safeSearchAnswers.length; j++) {
let answer = results.safeSearchAnswers[j];
if (answer === null) {
continue;
}
let safeSearchEnabled = results.unfilteredAnswers.includes(answer);
if (safeSearchEnabled) {
safeSearchChecks[providerName] = "disable_doh";
}
}
}
return safeSearchChecks;
}
async function zscalerCanary() {
const ZSCALER_CANARY = "sitereview.zscaler.com";
let { addresses } = await dnsLookup(ZSCALER_CANARY);
for (let j = 0; j < addresses.length; j++) {
let answer = addresses[j];
if (
answer == "213.152.228.242" ||
answer == "199.168.151.251" ||
answer == "8.25.203.30"
) {
// if sitereview.zscaler.com resolves to either one of the 3 IPs above,
// Zscaler Shift service is in use, don't enable DoH
return "disable_doh";
}
}
return "enable_doh";
}
// TODO: Confirm the expected behavior when filtering is on
async function globalCanary() {
let { addresses, err } = await dnsLookup(GLOBAL_CANARY);
if (err === NXDOMAIN_ERR) {
return "disable_doh";
}
if (addresses.length === 0) {
return "disable_doh";
}
return "enable_doh";
}
async function modifiedRoots() {
// Check for presence of enterprise_roots cert pref. If enabled, disable DoH
let rootsEnabled = await browser.experiments.preferences.getBoolPref(
"security.enterprise_roots.enabled",
false
);
if (rootsEnabled) {
return "disable_doh";
}
return "enable_doh";
}
async function runHeuristics() {
let safeSearchChecks = await safeSearch();
let zscalerCheck = await zscalerCanary();
let canaryCheck = await globalCanary();
let modifiedRootsCheck = await modifiedRoots();
// Check other heuristics through privileged code
let browserParentCheck = await browser.experiments.heuristics.checkParentalControls();
let enterpriseCheck = await browser.experiments.heuristics.checkEnterprisePolicies();
let thirdPartyRootsCheck = await browser.experiments.heuristics.checkThirdPartyRoots();
// Return result of each heuristic
let heuristics = {
google: safeSearchChecks.google,
youtube: safeSearchChecks.youtube,
zscalerCanary: zscalerCheck,
canary: canaryCheck,
modifiedRoots: modifiedRootsCheck,
browserParent: browserParentCheck,
thirdPartyRoots: thirdPartyRootsCheck,
policy: enterpriseCheck,
};
return heuristics;
}

Просмотреть файл

@ -0,0 +1,62 @@
{
"manifest_version": 2,
"name": "__MSG_extensionName__",
"default_locale": "en_US",
"description": "__MSG_extensionDescription__",
"version": "1.2.0rc2",
"hidden": true,
"applications": {
"gecko": {
"id": "doh-rollout@mozilla.org"
}
},
"permissions": [
"captivePortal",
"dns",
"normandyAddonStudy",
"storage",
"telemetry"
],
"background": {
"scripts": ["heuristics.js", "background.js"]
},
"experiment_apis": {
"preferences": {
"schema": "experiments/preferences/schema.json",
"parent": {
"scopes": ["addon_parent"],
"script": "experiments/preferences/api.js",
"paths": [["experiments", "preferences"]]
}
},
"heuristics": {
"schema": "experiments/heuristics/schema.json",
"parent": {
"scopes": ["addon_parent"],
"script": "experiments/heuristics/api.js",
"paths": [["experiments", "heuristics"]]
}
},
"netChange": {
"schema": "experiments/netChange/schema.json",
"parent": {
"scopes": ["addon_parent"],
"script": "experiments/netChange/api.js",
"paths": [["experiments", "netChange"]]
}
},
"doorhanger": {
"schema": "experiments/doorhanger/schema.json",
"parent": {
"scopes": ["addon_parent"],
"script": "experiments/doorhanger/api.js",
"paths": [["experiments", "doorhanger"]]
}
}
}
}