summaryrefslogtreecommitdiffstats
path: root/comm/mail/components/enterprisepolicies
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/components/enterprisepolicies')
-rw-r--r--comm/mail/components/enterprisepolicies/Policies.sys.mjs1758
-rw-r--r--comm/mail/components/enterprisepolicies/content/aboutPolicies.js410
-rw-r--r--comm/mail/components/enterprisepolicies/content/aboutPolicies.xhtml107
-rw-r--r--comm/mail/components/enterprisepolicies/content/policies-active.svg6
-rw-r--r--comm/mail/components/enterprisepolicies/content/policies-documentation.svg6
-rw-r--r--comm/mail/components/enterprisepolicies/content/policies-error.svg6
-rw-r--r--comm/mail/components/enterprisepolicies/helpers/ProxyPolicies.sys.mjs111
-rw-r--r--comm/mail/components/enterprisepolicies/helpers/moz.build12
-rw-r--r--comm/mail/components/enterprisepolicies/jar.mn10
-rw-r--r--comm/mail/components/enterprisepolicies/moz.build23
-rw-r--r--comm/mail/components/enterprisepolicies/schemas/configuration.json10
-rw-r--r--comm/mail/components/enterprisepolicies/schemas/moz.build12
-rw-r--r--comm/mail/components/enterprisepolicies/schemas/policies-schema.json634
-rw-r--r--comm/mail/components/enterprisepolicies/schemas/schema.sys.mjs16
-rw-r--r--comm/mail/components/enterprisepolicies/tests/browser/browser.ini32
-rw-r--r--comm/mail/components/enterprisepolicies/tests/browser/browser_policies_setAndLockPref_API.js179
-rw-r--r--comm/mail/components/enterprisepolicies/tests/browser/browser_policy_app_auto_update.js92
-rw-r--r--comm/mail/components/enterprisepolicies/tests/browser/browser_policy_app_update.js41
-rw-r--r--comm/mail/components/enterprisepolicies/tests/browser/browser_policy_background_app_update.js104
-rw-r--r--comm/mail/components/enterprisepolicies/tests/browser/browser_policy_block_about.js87
-rw-r--r--comm/mail/components/enterprisepolicies/tests/browser/browser_policy_cookie_settings.js323
-rw-r--r--comm/mail/components/enterprisepolicies/tests/browser/browser_policy_disable_masterpassword.js90
-rw-r--r--comm/mail/components/enterprisepolicies/tests/browser/browser_policy_disable_safemode.js49
-rw-r--r--comm/mail/components/enterprisepolicies/tests/browser/browser_policy_disable_telemetry.js21
-rw-r--r--comm/mail/components/enterprisepolicies/tests/browser/browser_policy_downloads.js147
-rw-r--r--comm/mail/components/enterprisepolicies/tests/browser/browser_policy_extensions.js120
-rw-r--r--comm/mail/components/enterprisepolicies/tests/browser/browser_policy_extensionsettings.js261
-rw-r--r--comm/mail/components/enterprisepolicies/tests/browser/browser_policy_extensionsettings2.js73
-rw-r--r--comm/mail/components/enterprisepolicies/tests/browser/browser_policy_handlers.js183
-rw-r--r--comm/mail/components/enterprisepolicies/tests/browser/browser_policy_masterpassword.js104
-rw-r--r--comm/mail/components/enterprisepolicies/tests/browser/browser_policy_passwordmanager.js27
-rw-r--r--comm/mail/components/enterprisepolicies/tests/browser/disable_app_update/browser.ini15
-rw-r--r--comm/mail/components/enterprisepolicies/tests/browser/disable_app_update/browser_policy_disable_app_update.js109
-rw-r--r--comm/mail/components/enterprisepolicies/tests/browser/disable_app_update/config_disable_app_update.json5
-rw-r--r--comm/mail/components/enterprisepolicies/tests/browser/disable_developer_tools/browser.ini13
-rw-r--r--comm/mail/components/enterprisepolicies/tests/browser/disable_developer_tools/browser_policy_disable_developer_tools.js55
-rw-r--r--comm/mail/components/enterprisepolicies/tests/browser/disable_developer_tools/config_disable_developer_tools.json5
-rw-r--r--comm/mail/components/enterprisepolicies/tests/browser/extensionsettings.html23
-rw-r--r--comm/mail/components/enterprisepolicies/tests/browser/hardware_acceleration/browser.ini12
-rw-r--r--comm/mail/components/enterprisepolicies/tests/browser/hardware_acceleration/browser_policy_hardware_acceleration.js9
-rw-r--r--comm/mail/components/enterprisepolicies/tests/browser/hardware_acceleration/disable_hardware_acceleration.json5
-rw-r--r--comm/mail/components/enterprisepolicies/tests/browser/head.js103
-rw-r--r--comm/mail/components/enterprisepolicies/tests/browser/policytest_v0.1.xpibin0 -> 305 bytes
-rw-r--r--comm/mail/components/enterprisepolicies/tests/browser/policytest_v0.2.xpibin0 -> 297 bytes
-rw-r--r--comm/mail/components/enterprisepolicies/tests/moz.build16
-rw-r--r--comm/mail/components/enterprisepolicies/tests/xpcshell/head.js140
-rw-r--r--comm/mail/components/enterprisepolicies/tests/xpcshell/test_3rdparty.js22
-rw-r--r--comm/mail/components/enterprisepolicies/tests/xpcshell/test_appupdatepin.js80
-rw-r--r--comm/mail/components/enterprisepolicies/tests/xpcshell/test_appupdateurl.js25
-rw-r--r--comm/mail/components/enterprisepolicies/tests/xpcshell/test_bug1658259.js44
-rw-r--r--comm/mail/components/enterprisepolicies/tests/xpcshell/test_clear_blocked_cookies.js118
-rw-r--r--comm/mail/components/enterprisepolicies/tests/xpcshell/test_macosparser_unflatten.js110
-rw-r--r--comm/mail/components/enterprisepolicies/tests/xpcshell/test_policy_search_engine.js490
-rw-r--r--comm/mail/components/enterprisepolicies/tests/xpcshell/test_preferences.js255
-rw-r--r--comm/mail/components/enterprisepolicies/tests/xpcshell/test_proxy.js122
-rw-r--r--comm/mail/components/enterprisepolicies/tests/xpcshell/test_requestedlocales.js47
-rw-r--r--comm/mail/components/enterprisepolicies/tests/xpcshell/test_runOnce_helper.js21
-rw-r--r--comm/mail/components/enterprisepolicies/tests/xpcshell/test_simple_pref_policies.js378
-rw-r--r--comm/mail/components/enterprisepolicies/tests/xpcshell/test_sorted_alphabetically.js48
-rw-r--r--comm/mail/components/enterprisepolicies/tests/xpcshell/xpcshell.ini18
60 files changed, 7342 insertions, 0 deletions
diff --git a/comm/mail/components/enterprisepolicies/Policies.sys.mjs b/comm/mail/components/enterprisepolicies/Policies.sys.mjs
new file mode 100644
index 0000000000..d35e9a2d30
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/Policies.sys.mjs
@@ -0,0 +1,1758 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
+
+import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
+
+const lazy = {};
+
+XPCOMUtils.defineLazyServiceGetters(lazy, {
+ gCertDB: ["@mozilla.org/security/x509certdb;1", "nsIX509CertDB"],
+ gExternalProtocolService: [
+ "@mozilla.org/uriloader/external-protocol-service;1",
+ "nsIExternalProtocolService",
+ ],
+ gHandlerService: [
+ "@mozilla.org/uriloader/handler-service;1",
+ "nsIHandlerService",
+ ],
+ gMIMEService: ["@mozilla.org/mime;1", "nsIMIMEService"],
+});
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
+ FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
+ ProxyPolicies: "resource:///modules/policies/ProxyPolicies.sys.mjs",
+});
+
+const PREF_LOGLEVEL = "browser.policies.loglevel";
+const ABOUT_CONTRACT = "@mozilla.org/network/protocol/about;1?what=";
+
+const isXpcshell = Services.env.exists("XPCSHELL_TEST_PROFILE_DIR");
+
+XPCOMUtils.defineLazyGetter(lazy, "log", () => {
+ let { ConsoleAPI } = ChromeUtils.importESModule(
+ "resource://gre/modules/Console.sys.mjs"
+ );
+ return new ConsoleAPI({
+ prefix: "Policies.jsm",
+ // tip: set maxLogLevel to "debug" and use log.debug() to create detailed
+ // messages during development. See LOG_LEVELS in Console.jsm for details.
+ maxLogLevel: "error",
+ maxLogLevelPref: PREF_LOGLEVEL,
+ });
+});
+
+/*
+ * ============================
+ * = POLICIES IMPLEMENTATIONS =
+ * ============================
+ *
+ * The Policies object below is where the implementation for each policy
+ * happens. An object for each policy should be defined, containing
+ * callback functions that will be called by the engine.
+ *
+ * See the _callbacks object in EnterprisePolicies.js for the list of
+ * possible callbacks and an explanation of each.
+ *
+ * Each callback will be called with two parameters:
+ * - manager
+ * This is the EnterprisePoliciesManager singleton object from
+ * EnterprisePolicies.js
+ *
+ * - param
+ * The parameter defined for this policy in policies-schema.json.
+ * It will be different for each policy. It could be a boolean,
+ * a string, an array or a complex object. All parameters have
+ * been validated according to the schema, and no unknown
+ * properties will be present on them.
+ *
+ * The callbacks will be bound to their parent policy object.
+ */
+export var Policies = {
+ // Used for cleaning up policies.
+ // Use the same timing that you used for setting up the policy.
+ _cleanup: {
+ onBeforeAddons(manager) {
+ if (Cu.isInAutomation || isXpcshell) {
+ console.log("_cleanup from onBeforeAddons");
+ clearBlockedAboutPages();
+ }
+ },
+ onProfileAfterChange(manager) {
+ if (Cu.isInAutomation || isXpcshell) {
+ console.log("_cleanup from onProfileAfterChange");
+ }
+ },
+ onBeforeUIStartup(manager) {
+ if (Cu.isInAutomation || isXpcshell) {
+ console.log("_cleanup from onBeforeUIStartup");
+ }
+ },
+ onAllWindowsRestored(manager) {
+ if (Cu.isInAutomation || isXpcshell) {
+ console.log("_cleanup from onAllWindowsRestored");
+ }
+ },
+ },
+
+ "3rdparty": {
+ onBeforeAddons(manager, param) {
+ manager.setExtensionPolicies(param.Extensions);
+ },
+ },
+
+ AppAutoUpdate: {
+ onBeforeUIStartup(manager, param) {
+ // Logic feels a bit reversed here, but it's correct. If AppAutoUpdate is
+ // true, we disallow turning off auto updating, and visa versa.
+ if (param) {
+ manager.disallowFeature("app-auto-updates-off");
+ } else {
+ manager.disallowFeature("app-auto-updates-on");
+ }
+ },
+ },
+
+ AppUpdatePin: {
+ validate(param) {
+ // This is the version when pinning was introduced. Attempting to set a
+ // pin before this will not work, because Balrog's pinning table will
+ // never have the necessary entry.
+ const earliestPinMajorVersion = 102;
+ const earliestPinMinorVersion = 0;
+
+ let pinParts = param.split(".");
+
+ if (pinParts.length < 2) {
+ lazy.log.error("AppUpdatePin has too few dots.");
+ return false;
+ }
+ if (pinParts.length > 3) {
+ lazy.log.error("AppUpdatePin has too many dots.");
+ return false;
+ }
+
+ const trailingPinPart = pinParts.pop();
+ if (trailingPinPart != "") {
+ lazy.log.error("AppUpdatePin does not end with a trailing dot.");
+ return false;
+ }
+
+ const pinMajorVersionStr = pinParts.shift();
+ if (!pinMajorVersionStr.length) {
+ lazy.log.error("AppUpdatePin's major version is empty.");
+ return false;
+ }
+ if (!/^\d+$/.test(pinMajorVersionStr)) {
+ lazy.log.error(
+ "AppUpdatePin's major version contains a non-numeric character."
+ );
+ return false;
+ }
+ if (/^0/.test(pinMajorVersionStr)) {
+ lazy.log.error("AppUpdatePin's major version contains a leading 0.");
+ return false;
+ }
+ const pinMajorVersionInt = parseInt(pinMajorVersionStr, 10);
+ if (isNaN(pinMajorVersionInt)) {
+ lazy.log.error(
+ "AppUpdatePin's major version could not be parsed to an integer."
+ );
+ return false;
+ }
+ if (pinMajorVersionInt < earliestPinMajorVersion) {
+ lazy.log.error(
+ `AppUpdatePin must not be earlier than '${earliestPinMajorVersion}.${earliestPinMinorVersion}.'.`
+ );
+ return false;
+ }
+
+ if (pinParts.length) {
+ const pinMinorVersionStr = pinParts.shift();
+ if (!pinMinorVersionStr.length) {
+ lazy.log.error("AppUpdatePin's minor version is empty.");
+ return false;
+ }
+ if (!/^\d+$/.test(pinMinorVersionStr)) {
+ lazy.log.error(
+ "AppUpdatePin's minor version contains a non-numeric character."
+ );
+ return false;
+ }
+ if (/^0\d/.test(pinMinorVersionStr)) {
+ lazy.log.error("AppUpdatePin's minor version contains a leading 0.");
+ return false;
+ }
+ const pinMinorVersionInt = parseInt(pinMinorVersionStr, 10);
+ if (isNaN(pinMinorVersionInt)) {
+ lazy.log.error(
+ "AppUpdatePin's minor version could not be parsed to an integer."
+ );
+ return false;
+ }
+ if (
+ pinMajorVersionInt == earliestPinMajorVersion &&
+ pinMinorVersionInt < earliestPinMinorVersion
+ ) {
+ lazy.log.error(
+ `AppUpdatePin must not be earlier than '${earliestPinMajorVersion}.${earliestPinMinorVersion}.'.`
+ );
+ return false;
+ }
+ }
+
+ return true;
+ },
+ // No additional implementation needed here. UpdateService.sys.mjs will
+ // check for this policy directly when determining the update URL.
+ },
+
+ AppUpdateURL: {
+ // No implementation needed here. UpdateService.sys.mjs will check for this
+ // policy directly when determining the update URL.
+ },
+
+ Authentication: {
+ onBeforeAddons(manager, param) {
+ let locked = true;
+ if ("Locked" in param) {
+ locked = param.Locked;
+ }
+
+ if ("SPNEGO" in param) {
+ PoliciesUtils.setDefaultPref(
+ "network.negotiate-auth.trusted-uris",
+ param.SPNEGO.join(", "),
+ locked
+ );
+ }
+ if ("Delegated" in param) {
+ PoliciesUtils.setDefaultPref(
+ "network.negotiate-auth.delegation-uris",
+ param.Delegated.join(", "),
+ locked
+ );
+ }
+ if ("NTLM" in param) {
+ PoliciesUtils.setDefaultPref(
+ "network.automatic-ntlm-auth.trusted-uris",
+ param.NTLM.join(", "),
+ locked
+ );
+ }
+ if ("AllowNonFQDN" in param) {
+ if ("NTLM" in param.AllowNonFQDN) {
+ PoliciesUtils.setDefaultPref(
+ "network.automatic-ntlm-auth.allow-non-fqdn",
+ param.AllowNonFQDN.NTLM,
+ locked
+ );
+ }
+ if ("SPNEGO" in param.AllowNonFQDN) {
+ PoliciesUtils.setDefaultPref(
+ "network.negotiate-auth.allow-non-fqdn",
+ param.AllowNonFQDN.SPNEGO,
+ locked
+ );
+ }
+ }
+ if ("AllowProxies" in param) {
+ if ("NTLM" in param.AllowProxies) {
+ PoliciesUtils.setDefaultPref(
+ "network.automatic-ntlm-auth.allow-proxies",
+ param.AllowProxies.NTLM,
+ locked
+ );
+ }
+ if ("SPNEGO" in param.AllowProxies) {
+ PoliciesUtils.setDefaultPref(
+ "network.negotiate-auth.allow-proxies",
+ param.AllowProxies.SPNEGO,
+ locked
+ );
+ }
+ }
+ if ("PrivateBrowsing" in param) {
+ PoliciesUtils.setDefaultPref(
+ "network.auth.private-browsing-sso",
+ param.PrivateBrowsing,
+ locked
+ );
+ }
+ },
+ },
+
+ BackgroundAppUpdate: {
+ onBeforeAddons(manager, param) {
+ if (param) {
+ manager.disallowFeature("app-background-update-off");
+ } else {
+ manager.disallowFeature("app-background-update-on");
+ }
+ },
+ },
+
+ BlockAboutAddons: {
+ onBeforeUIStartup(manager, param) {
+ if (param) {
+ blockAboutPage(manager, "about:addons", true);
+ }
+ },
+ },
+
+ BlockAboutConfig: {
+ onBeforeUIStartup(manager, param) {
+ if (param) {
+ blockAboutPage(manager, "about:config");
+ setAndLockPref("devtools.chrome.enabled", false);
+ }
+ },
+ },
+
+ BlockAboutProfiles: {
+ onBeforeUIStartup(manager, param) {
+ if (param) {
+ blockAboutPage(manager, "about:profiles");
+ }
+ },
+ },
+
+ BlockAboutSupport: {
+ onBeforeUIStartup(manager, param) {
+ if (param) {
+ blockAboutPage(manager, "about:support");
+ }
+ },
+ },
+
+ CaptivePortal: {
+ onBeforeAddons(manager, param) {
+ setAndLockPref("network.captive-portal-service.enabled", param);
+ },
+ },
+
+ Certificates: {
+ onBeforeAddons(manager, param) {
+ if ("ImportEnterpriseRoots" in param) {
+ setAndLockPref(
+ "security.enterprise_roots.enabled",
+ param.ImportEnterpriseRoots
+ );
+ }
+ if ("Install" in param) {
+ (async () => {
+ let dirs = [];
+ let platform = AppConstants.platform;
+ if (platform == "win") {
+ dirs = [
+ // Ugly, but there is no official way to get %USERNAME\AppData\Roaming\Mozilla.
+ Services.dirsvc.get("XREUSysExt", Ci.nsIFile).parent,
+ // Even more ugly, but there is no official way to get %USERNAME\AppData\Local\Mozilla.
+ Services.dirsvc.get("DefProfLRt", Ci.nsIFile).parent.parent,
+ ];
+ } else if (platform == "macosx" || platform == "linux") {
+ dirs = [
+ // These two keys are named wrong. They return the Mozilla directory.
+ Services.dirsvc.get("XREUserNativeManifests", Ci.nsIFile),
+ Services.dirsvc.get("XRESysNativeManifests", Ci.nsIFile),
+ ];
+ }
+ dirs.unshift(Services.dirsvc.get("XREAppDist", Ci.nsIFile));
+ for (let certfilename of param.Install) {
+ let certfile;
+ try {
+ certfile = Cc["@mozilla.org/file/local;1"].createInstance(
+ Ci.nsIFile
+ );
+ certfile.initWithPath(certfilename);
+ } catch (e) {
+ for (let dir of dirs) {
+ certfile = dir.clone();
+ certfile.append(
+ platform == "linux" ? "certificates" : "Certificates"
+ );
+ certfile.append(certfilename);
+ if (certfile.exists()) {
+ break;
+ }
+ }
+ }
+ let file;
+ try {
+ file = await File.createFromNsIFile(certfile);
+ } catch (e) {
+ lazy.log.error(`Unable to find certificate - ${certfilename}`);
+ continue;
+ }
+ let reader = new FileReader();
+ reader.onloadend = function () {
+ if (reader.readyState != reader.DONE) {
+ lazy.log.error(`Unable to read certificate - ${certfile.path}`);
+ return;
+ }
+ let certFile = reader.result;
+ let certFileArray = [];
+ for (let i = 0; i < certFile.length; i++) {
+ certFileArray.push(certFile.charCodeAt(i));
+ }
+ let cert;
+ try {
+ cert = lazy.gCertDB.constructX509(certFileArray);
+ } catch (e) {
+ lazy.log.debug(
+ `constructX509 failed with error '${e}' - trying constructX509FromBase64.`
+ );
+ try {
+ // It might be PEM instead of DER.
+ cert = lazy.gCertDB.constructX509FromBase64(
+ pemToBase64(certFile)
+ );
+ } catch (ex) {
+ lazy.log.error(
+ `Unable to add certificate - ${certfile.path}`,
+ ex
+ );
+ }
+ }
+ if (cert) {
+ if (
+ lazy.gCertDB.isCertTrusted(
+ cert,
+ Ci.nsIX509Cert.CA_CERT,
+ Ci.nsIX509CertDB.TRUSTED_SSL
+ )
+ ) {
+ // Certificate is already installed.
+ return;
+ }
+ try {
+ lazy.gCertDB.addCert(certFile, "CT,CT,");
+ } catch (e) {
+ // It might be PEM instead of DER.
+ lazy.gCertDB.addCertFromBase64(
+ pemToBase64(certFile),
+ "CT,CT,"
+ );
+ }
+ }
+ };
+ reader.readAsBinaryString(file);
+ }
+ })();
+ }
+ },
+ },
+
+ Cookies: {
+ onBeforeUIStartup(manager, param) {
+ addAllowDenyPermissions("cookie", param.Allow, param.Block);
+
+ if (param.Block) {
+ const hosts = param.Block.map(url => url.hostname)
+ .sort()
+ .join("\n");
+ runOncePerModification("clearCookiesForBlockedHosts", hosts, () => {
+ for (let blocked of param.Block) {
+ Services.cookies.removeCookiesWithOriginAttributes(
+ "{}",
+ blocked.hostname
+ );
+ }
+ });
+ }
+
+ if (
+ param.Default !== undefined ||
+ param.AcceptThirdParty !== undefined ||
+ param.Locked
+ ) {
+ const ACCEPT_COOKIES = 0;
+ const REJECT_THIRD_PARTY_COOKIES = 1;
+ const REJECT_ALL_COOKIES = 2;
+ const REJECT_UNVISITED_THIRD_PARTY = 3;
+
+ let newCookieBehavior = ACCEPT_COOKIES;
+ if (param.Default !== undefined && !param.Default) {
+ newCookieBehavior = REJECT_ALL_COOKIES;
+ } else if (param.AcceptThirdParty) {
+ if (param.AcceptThirdParty == "never") {
+ newCookieBehavior = REJECT_THIRD_PARTY_COOKIES;
+ } else if (param.AcceptThirdParty == "from-visited") {
+ newCookieBehavior = REJECT_UNVISITED_THIRD_PARTY;
+ }
+ }
+
+ PoliciesUtils.setDefaultPref(
+ "network.cookie.cookieBehavior",
+ newCookieBehavior,
+ param.Locked
+ );
+ PoliciesUtils.setDefaultPref(
+ "network.cookie.cookieBehavior.pbmode",
+ newCookieBehavior,
+ param.Locked
+ );
+ }
+
+ if (param.ExpireAtSessionEnd != undefined) {
+ lazy.log.error(
+ "'ExpireAtSessionEnd' has been deprecated and it has no effect anymore."
+ );
+ }
+ },
+ },
+
+ DefaultDownloadDirectory: {
+ onBeforeAddons(manager, param) {
+ PoliciesUtils.setDefaultPref(
+ "browser.download.dir",
+ replacePathVariables(param)
+ );
+ // If a custom download directory is being used, just lock folder list to 2.
+ setAndLockPref("browser.download.folderList", 2);
+ },
+ },
+
+ DisableAppUpdate: {
+ onBeforeAddons(manager, param) {
+ if (param) {
+ manager.disallowFeature("appUpdate");
+ }
+ },
+ },
+
+ DisableBuiltinPDFViewer: {
+ onBeforeAddons(manager, param) {
+ if (param) {
+ setAndLockPref("pdfjs.disabled", true);
+ }
+ },
+ },
+
+ DisabledCiphers: {
+ onBeforeAddons(manager, param) {
+ let cipherPrefs = {
+ TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ "security.ssl3.ecdhe_rsa_aes_128_gcm_sha256",
+ TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ "security.ssl3.ecdhe_ecdsa_aes_128_gcm_sha256",
+ TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
+ "security.ssl3.ecdhe_ecdsa_chacha20_poly1305_sha256",
+ TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+ "security.ssl3.ecdhe_rsa_chacha20_poly1305_sha256",
+ TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+ "security.ssl3.ecdhe_ecdsa_aes_256_gcm_sha384",
+ TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+ "security.ssl3.ecdhe_rsa_aes_256_gcm_sha384",
+ TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ "security.ssl3.ecdhe_rsa_aes_128_sha",
+ TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+ "security.ssl3.ecdhe_ecdsa_aes_128_sha",
+ TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ "security.ssl3.ecdhe_rsa_aes_256_sha",
+ TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+ "security.ssl3.ecdhe_ecdsa_aes_256_sha",
+ TLS_DHE_RSA_WITH_AES_128_CBC_SHA: "security.ssl3.dhe_rsa_aes_128_sha",
+ TLS_DHE_RSA_WITH_AES_256_CBC_SHA: "security.ssl3.dhe_rsa_aes_256_sha",
+ TLS_RSA_WITH_AES_128_GCM_SHA256: "security.ssl3.rsa_aes_128_gcm_sha256",
+ TLS_RSA_WITH_AES_256_GCM_SHA384: "security.ssl3.rsa_aes_256_gcm_sha384",
+ TLS_RSA_WITH_AES_128_CBC_SHA: "security.ssl3.rsa_aes_128_sha",
+ TLS_RSA_WITH_AES_256_CBC_SHA: "security.ssl3.rsa_aes_256_sha",
+ TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+ "security.ssl3.deprecated.rsa_des_ede3_sha",
+ };
+
+ for (let cipher in param) {
+ setAndLockPref(cipherPrefs[cipher], !param[cipher]);
+ }
+ },
+ },
+
+ DisableDeveloperTools: {
+ onBeforeAddons(manager, param) {
+ if (param) {
+ setAndLockPref("devtools.policy.disabled", true);
+ setAndLockPref("devtools.chrome.enabled", false);
+
+ manager.disallowFeature("devtools");
+ blockAboutPage(manager, "about:debugging");
+ blockAboutPage(manager, "about:devtools-toolbox");
+ }
+ },
+ },
+
+ DisableMasterPasswordCreation: {
+ onBeforeUIStartup(manager, param) {
+ if (param) {
+ manager.disallowFeature("createMasterPassword");
+ }
+ },
+ },
+
+ DisablePasswordReveal: {
+ onBeforeUIStartup(manager, param) {
+ if (param) {
+ manager.disallowFeature("passwordReveal");
+ }
+ },
+ },
+
+ DisableSafeMode: {
+ onBeforeUIStartup(manager, param) {
+ if (param) {
+ manager.disallowFeature("safeMode");
+ }
+ },
+ },
+
+ DisableSecurityBypass: {
+ onBeforeUIStartup(manager, param) {
+ if ("InvalidCertificate" in param) {
+ setAndLockPref(
+ "security.certerror.hideAddException",
+ param.InvalidCertificate
+ );
+ }
+
+ if ("SafeBrowsing" in param) {
+ setAndLockPref(
+ "browser.safebrowsing.allowOverride",
+ !param.SafeBrowsing
+ );
+ }
+ },
+ },
+
+ DisableSystemAddonUpdate: {
+ onBeforeAddons(manager, param) {
+ if (param) {
+ manager.disallowFeature("SysAddonUpdate");
+ }
+ },
+ },
+
+ DisableTelemetry: {
+ onBeforeAddons(manager, param) {
+ if (param) {
+ setAndLockPref("datareporting.healthreport.uploadEnabled", false);
+ setAndLockPref("datareporting.policy.dataSubmissionEnabled", false);
+ setAndLockPref("toolkit.telemetry.archive.enabled", false);
+ blockAboutPage(manager, "about:telemetry");
+ }
+ },
+ },
+
+ DNSOverHTTPS: {
+ onBeforeAddons(manager, param) {
+ let locked = false;
+ if ("Locked" in param) {
+ locked = param.Locked;
+ }
+ if ("Enabled" in param) {
+ let mode = param.Enabled ? 2 : 5;
+ PoliciesUtils.setDefaultPref("network.trr.mode", mode, locked);
+ }
+ if ("ProviderURL" in param) {
+ PoliciesUtils.setDefaultPref(
+ "network.trr.uri",
+ param.ProviderURL.href,
+ locked
+ );
+ }
+ if ("ExcludedDomains" in param) {
+ PoliciesUtils.setDefaultPref(
+ "network.trr.excluded-domains",
+ param.ExcludedDomains.join(","),
+ locked
+ );
+ }
+ },
+ },
+
+ DownloadDirectory: {
+ onBeforeAddons(manager, param) {
+ setAndLockPref("browser.download.dir", replacePathVariables(param));
+ // If a custom download directory is being used, just lock folder list to 2.
+ setAndLockPref("browser.download.folderList", 2);
+ // Per Chrome spec, user can't choose to download every time
+ // if this is set.
+ setAndLockPref("browser.download.useDownloadDir", true);
+ },
+ },
+
+ Extensions: {
+ onBeforeUIStartup(manager, param) {
+ let uninstallingPromise = Promise.resolve();
+ if ("Uninstall" in param) {
+ uninstallingPromise = runOncePerModification(
+ "extensionsUninstall",
+ JSON.stringify(param.Uninstall),
+ async () => {
+ // If we're uninstalling add-ons, re-run the extensionsInstall runOnce even if it hasn't
+ // changed, which will allow add-ons to be updated.
+ Services.prefs.clearUserPref(
+ "browser.policies.runOncePerModification.extensionsInstall"
+ );
+ let addons = await lazy.AddonManager.getAddonsByIDs(
+ param.Uninstall
+ );
+ for (let addon of addons) {
+ if (addon) {
+ try {
+ await addon.uninstall();
+ } catch (e) {
+ // This can fail for add-ons that can't be uninstalled.
+ lazy.log.debug(
+ `Add-on ID (${addon.id}) couldn't be uninstalled.`
+ );
+ }
+ }
+ }
+ }
+ );
+ }
+ if ("Install" in param) {
+ runOncePerModification(
+ "extensionsInstall",
+ JSON.stringify(param.Install),
+ async () => {
+ await uninstallingPromise;
+ for (let location of param.Install) {
+ let uri;
+ try {
+ // We need to try as a file first because
+ // Windows paths are valid URIs.
+ // This is done for legacy support (old API)
+ let xpiFile = new lazy.FileUtils.File(location);
+ uri = Services.io.newFileURI(xpiFile);
+ } catch (e) {
+ uri = Services.io.newURI(location);
+ }
+ installAddonFromURL(uri.spec);
+ }
+ }
+ );
+ }
+ if ("Locked" in param) {
+ for (let ID of param.Locked) {
+ manager.disallowFeature(`uninstall-extension:${ID}`);
+ manager.disallowFeature(`disable-extension:${ID}`);
+ }
+ }
+ },
+ },
+
+ ExtensionSettings: {
+ onBeforeAddons(manager, param) {
+ try {
+ manager.setExtensionSettings(param);
+ } catch (e) {
+ lazy.log.error("Invalid ExtensionSettings");
+ }
+ },
+ async onBeforeUIStartup(manager, param) {
+ let extensionSettings = param;
+ let blockAllExtensions = false;
+ if ("*" in extensionSettings) {
+ if (
+ "installation_mode" in extensionSettings["*"] &&
+ extensionSettings["*"].installation_mode == "blocked"
+ ) {
+ blockAllExtensions = true;
+ // Turn off discovery pane in about:addons
+ setAndLockPref("extensions.getAddons.showPane", false);
+ // Turn off recommendations
+ setAndLockPref(
+ "extensions.htmlaboutaddons.recommendations.enable",
+ false
+ );
+ // Block about:debugging
+ blockAboutPage(manager, "about:debugging");
+ }
+ if ("restricted_domains" in extensionSettings["*"]) {
+ let restrictedDomains = Services.prefs
+ .getCharPref("extensions.webextensions.restrictedDomains")
+ .split(",");
+ setAndLockPref(
+ "extensions.webextensions.restrictedDomains",
+ restrictedDomains
+ .concat(extensionSettings["*"].restricted_domains)
+ .join(",")
+ );
+ }
+ }
+ let addons = await lazy.AddonManager.getAllAddons();
+ let allowedExtensions = [];
+ for (let extensionID in extensionSettings) {
+ if (extensionID == "*") {
+ // Ignore global settings
+ continue;
+ }
+ if ("installation_mode" in extensionSettings[extensionID]) {
+ if (
+ extensionSettings[extensionID].installation_mode ==
+ "force_installed" ||
+ extensionSettings[extensionID].installation_mode ==
+ "normal_installed"
+ ) {
+ if (!extensionSettings[extensionID].install_url) {
+ throw new Error(`Missing install_url for ${extensionID}`);
+ }
+ installAddonFromURL(
+ extensionSettings[extensionID].install_url,
+ extensionID,
+ addons.find(addon => addon.id == extensionID)
+ );
+ manager.disallowFeature(`uninstall-extension:${extensionID}`);
+ if (
+ extensionSettings[extensionID].installation_mode ==
+ "force_installed"
+ ) {
+ manager.disallowFeature(`disable-extension:${extensionID}`);
+ }
+ allowedExtensions.push(extensionID);
+ } else if (
+ extensionSettings[extensionID].installation_mode == "allowed"
+ ) {
+ allowedExtensions.push(extensionID);
+ } else if (
+ extensionSettings[extensionID].installation_mode == "blocked"
+ ) {
+ if (addons.find(addon => addon.id == extensionID)) {
+ // Can't use the addon from getActiveAddons since it doesn't have uninstall.
+ let addon = await lazy.AddonManager.getAddonByID(extensionID);
+ try {
+ await addon.uninstall();
+ } catch (e) {
+ // This can fail for add-ons that can't be uninstalled.
+ lazy.log.debug(
+ `Add-on ID (${addon.id}) couldn't be uninstalled.`
+ );
+ }
+ }
+ }
+ }
+ }
+ if (blockAllExtensions) {
+ for (let addon of addons) {
+ if (
+ addon.isSystem ||
+ addon.isBuiltin ||
+ !(addon.scope & lazy.AddonManager.SCOPE_PROFILE)
+ ) {
+ continue;
+ }
+ if (!allowedExtensions.includes(addon.id)) {
+ try {
+ // Can't use the addon from getActiveAddons since it doesn't have uninstall.
+ let addonToUninstall = await lazy.AddonManager.getAddonByID(
+ addon.id
+ );
+ await addonToUninstall.uninstall();
+ } catch (e) {
+ // This can fail for add-ons that can't be uninstalled.
+ lazy.log.debug(
+ `Add-on ID (${addon.id}) couldn't be uninstalled.`
+ );
+ }
+ }
+ }
+ }
+ },
+ },
+
+ ExtensionUpdate: {
+ onBeforeAddons(manager, param) {
+ if (!param) {
+ setAndLockPref("extensions.update.enabled", param);
+ }
+ },
+ },
+
+ Handlers: {
+ onBeforeAddons(manager, param) {
+ if ("mimeTypes" in param) {
+ for (let mimeType in param.mimeTypes) {
+ let mimeInfo = param.mimeTypes[mimeType];
+ let realMIMEInfo = lazy.gMIMEService.getFromTypeAndExtension(
+ mimeType,
+ ""
+ );
+ processMIMEInfo(mimeInfo, realMIMEInfo);
+ }
+ }
+ if ("extensions" in param) {
+ for (let extension in param.extensions) {
+ let mimeInfo = param.extensions[extension];
+ try {
+ let realMIMEInfo = lazy.gMIMEService.getFromTypeAndExtension(
+ "",
+ extension
+ );
+ processMIMEInfo(mimeInfo, realMIMEInfo);
+ } catch (e) {
+ lazy.log.error(`Invalid file extension (${extension})`);
+ }
+ }
+ }
+ if ("schemes" in param) {
+ for (let scheme in param.schemes) {
+ let handlerInfo = param.schemes[scheme];
+ let realHandlerInfo =
+ lazy.gExternalProtocolService.getProtocolHandlerInfo(scheme);
+ processMIMEInfo(handlerInfo, realHandlerInfo);
+ }
+ }
+ },
+ },
+
+ HardwareAcceleration: {
+ onBeforeAddons(manager, param) {
+ if (!param) {
+ setAndLockPref("layers.acceleration.disabled", true);
+ }
+ },
+ },
+
+ InstallAddonsPermission: {
+ onBeforeUIStartup(manager, param) {
+ if ("Allow" in param) {
+ addAllowDenyPermissions("install", param.Allow, null);
+ }
+ if ("Default" in param) {
+ setAndLockPref("xpinstall.enabled", param.Default);
+ if (!param.Default) {
+ blockAboutPage(manager, "about:debugging");
+ manager.disallowFeature("xpinstall");
+ }
+ }
+ },
+ },
+
+ ManualAppUpdateOnly: {
+ onBeforeAddons(manager, param) {
+ if (param) {
+ manager.disallowFeature("autoAppUpdateChecking");
+ }
+ },
+ },
+
+ NetworkPrediction: {
+ onBeforeAddons(manager, param) {
+ setAndLockPref("network.dns.disablePrefetch", !param);
+ setAndLockPref("network.dns.disablePrefetchFromHTTPS", !param);
+ },
+ },
+
+ OfferToSaveLogins: {
+ onBeforeUIStartup(manager, param) {
+ setAndLockPref("signon.rememberSignons", param);
+ setAndLockPref("services.passwordSavingEnabled", param);
+ },
+ },
+
+ OfferToSaveLoginsDefault: {
+ onBeforeUIStartup(manager, param) {
+ let policies = Services.policies.getActivePolicies();
+ if ("OfferToSaveLogins" in policies) {
+ lazy.log.error(
+ `OfferToSaveLoginsDefault ignored because OfferToSaveLogins is present.`
+ );
+ } else {
+ PoliciesUtils.setDefaultPref("signon.rememberSignons", param);
+ }
+ },
+ },
+
+ PasswordManagerEnabled: {
+ onBeforeUIStartup(manager, param) {
+ if (!param) {
+ blockAboutPage(manager, "about:logins", true);
+ setAndLockPref("pref.privacy.disable_button.view_passwords", true);
+ }
+ setAndLockPref("signon.rememberSignons", param);
+ },
+ },
+
+ PDFjs: {
+ onBeforeAddons(manager, param) {
+ if ("Enabled" in param) {
+ setAndLockPref("pdfjs.disabled", !param.Enabled);
+ }
+ if ("EnablePermissions" in param) {
+ setAndLockPref("pdfjs.enablePermissions", !param.Enabled);
+ }
+ },
+ },
+
+ Preferences: {
+ onBeforeAddons(manager, param) {
+ const allowedPrefixes = [
+ "accessibility.",
+ "app.update.",
+ "browser.",
+ "calendar.",
+ "chat.",
+ "datareporting.policy.",
+ "dom.",
+ "extensions.",
+ "general.autoScroll",
+ "general.smoothScroll",
+ "geo.",
+ "gfx.",
+ "intl.",
+ "layers.",
+ "layout.",
+ "mail.",
+ "mailnews.",
+ "media.",
+ "network.",
+ "pdfjs.",
+ "places.",
+ "print.",
+ "signon.",
+ "spellchecker.",
+ "ui.",
+ "widget.",
+ ];
+ const allowedSecurityPrefs = [
+ "security.default_personal_cert",
+ "security.insecure_connection_text.enabled",
+ "security.insecure_connection_text.pbmode.enabled",
+ "security.insecure_field_warning.contextual.enabled",
+ "security.mixed_content.block_active_content",
+ "security.osclientcerts.autoload",
+ "security.ssl.errorReporting.enabled",
+ "security.tls.hello_downgrade_check",
+ "security.tls.version.enable-deprecated",
+ "security.warn_submit_secure_to_insecure",
+ ];
+ const blockedPrefs = [
+ "app.update.channel",
+ "app.update.lastUpdateTime",
+ "app.update.migrated",
+ ];
+
+ for (let preference in param) {
+ if (blockedPrefs.includes(preference)) {
+ lazy.log.error(
+ `Unable to set preference ${preference}. Preference not allowed for security reasons.`
+ );
+ continue;
+ }
+ if (preference.startsWith("security.")) {
+ if (!allowedSecurityPrefs.includes(preference)) {
+ lazy.log.error(
+ `Unable to set preference ${preference}. Preference not allowed for security reasons.`
+ );
+ continue;
+ }
+ } else if (
+ !allowedPrefixes.some(prefix => preference.startsWith(prefix))
+ ) {
+ lazy.log.error(
+ `Unable to set preference ${preference}. Preference not allowed for stability reasons.`
+ );
+ continue;
+ }
+ if (typeof param[preference] != "object") {
+ // Legacy policy preferences
+ setAndLockPref(preference, param[preference]);
+ } else {
+ if (param[preference].Status == "clear") {
+ Services.prefs.clearUserPref(preference);
+ continue;
+ }
+
+ if (param[preference].Status == "user") {
+ var prefBranch = Services.prefs;
+ } else {
+ prefBranch = Services.prefs.getDefaultBranch("");
+ }
+
+ try {
+ switch (typeof param[preference].Value) {
+ case "boolean":
+ prefBranch.setBoolPref(preference, param[preference].Value);
+ break;
+
+ case "number":
+ if (!Number.isInteger(param[preference].Value)) {
+ throw new Error(`Non-integer value for ${preference}`);
+ }
+
+ // This is ugly, but necessary. On Windows GPO and macOS
+ // configs, booleans are converted to 0/1. In the previous
+ // Preferences implementation, the schema took care of
+ // automatically converting these values to booleans.
+ // Since we allow arbitrary prefs now, we have to do
+ // something different. See bug 1666836.
+ if (
+ prefBranch.getPrefType(preference) == prefBranch.PREF_INT ||
+ ![0, 1].includes(param[preference].Value)
+ ) {
+ prefBranch.setIntPref(preference, param[preference].Value);
+ } else {
+ prefBranch.setBoolPref(preference, !!param[preference].Value);
+ }
+ break;
+
+ case "string":
+ prefBranch.setStringPref(preference, param[preference].Value);
+ break;
+ }
+ } catch (e) {
+ lazy.log.error(
+ `Unable to set preference ${preference}. Probable type mismatch.`
+ );
+ }
+
+ if (param[preference].Status == "locked") {
+ Services.prefs.lockPref(preference);
+ }
+ }
+ }
+ },
+ },
+
+ PrimaryPassword: {
+ onAllWindowsRestored(manager, param) {
+ if (param) {
+ manager.disallowFeature("removeMasterPassword");
+ } else {
+ manager.disallowFeature("createMasterPassword");
+ }
+ },
+ },
+
+ PromptForDownloadLocation: {
+ onBeforeAddons(manager, param) {
+ setAndLockPref("browser.download.useDownloadDir", !param);
+ },
+ },
+
+ Proxy: {
+ onBeforeAddons(manager, param) {
+ if (param.Locked) {
+ manager.disallowFeature("changeProxySettings");
+ lazy.ProxyPolicies.configureProxySettings(param, setAndLockPref);
+ } else {
+ lazy.ProxyPolicies.configureProxySettings(
+ param,
+ PoliciesUtils.setDefaultPref
+ );
+ }
+ },
+ },
+
+ RequestedLocales: {
+ onBeforeAddons(manager, param) {
+ let requestedLocales;
+ if (Array.isArray(param)) {
+ requestedLocales = param;
+ } else if (param) {
+ requestedLocales = param.split(",");
+ } else {
+ requestedLocales = [];
+ }
+ runOncePerModification(
+ "requestedLocales",
+ JSON.stringify(requestedLocales),
+ () => {
+ Services.locale.requestedLocales = requestedLocales;
+ }
+ );
+ },
+ },
+
+ SearchEngines: {
+ onBeforeUIStartup(manager, param) {
+ if (param.PreventInstalls) {
+ manager.disallowFeature("installSearchEngine", true);
+ }
+ },
+ onAllWindowsRestored(manager, param) {
+ Services.search.init().then(async () => {
+ // Adding of engines is handled by the SearchService in the init().
+ // Remove can happen after those are added - no engines are allowed
+ // to replace the application provided engines, even if they have been
+ // removed.
+ if (param.Remove) {
+ // Only rerun if the list of engine names has changed.
+ await runOncePerModification(
+ "removeSearchEngines",
+ JSON.stringify(param.Remove),
+ async function () {
+ for (let engineName of param.Remove) {
+ let engine = Services.search.getEngineByName(engineName);
+ if (engine) {
+ try {
+ await Services.search.removeEngine(engine);
+ } catch (ex) {
+ lazy.log.error("Unable to remove the search engine", ex);
+ }
+ }
+ }
+ }
+ );
+ }
+ if (param.Default) {
+ await runOncePerModification(
+ "setDefaultSearchEngine",
+ param.Default,
+ async () => {
+ let defaultEngine;
+ try {
+ defaultEngine = Services.search.getEngineByName(param.Default);
+ if (!defaultEngine) {
+ throw new Error("No engine by that name could be found");
+ }
+ } catch (ex) {
+ lazy.log.error(
+ `Search engine lookup failed when attempting to set ` +
+ `the default engine. Requested engine was ` +
+ `"${param.Default}".`,
+ ex
+ );
+ }
+ if (defaultEngine) {
+ try {
+ await Services.search.setDefault(
+ defaultEngine,
+ Ci.nsISearchService.CHANGE_REASON_ENTERPRISE
+ );
+ } catch (ex) {
+ lazy.log.error("Unable to set the default search engine", ex);
+ }
+ }
+ }
+ );
+ }
+ if (param.DefaultPrivate) {
+ await runOncePerModification(
+ "setDefaultPrivateSearchEngine",
+ param.DefaultPrivate,
+ async () => {
+ let defaultPrivateEngine;
+ try {
+ defaultPrivateEngine = Services.search.getEngineByName(
+ param.DefaultPrivate
+ );
+ if (!defaultPrivateEngine) {
+ throw new Error("No engine by that name could be found");
+ }
+ } catch (ex) {
+ lazy.log.error(
+ `Search engine lookup failed when attempting to set ` +
+ `the default private engine. Requested engine was ` +
+ `"${param.DefaultPrivate}".`,
+ ex
+ );
+ }
+ if (defaultPrivateEngine) {
+ try {
+ await Services.search.setDefaultPrivate(
+ defaultPrivateEngine,
+ Ci.nsISearchService.CHANGE_REASON_ENTERPRISE
+ );
+ } catch (ex) {
+ lazy.log.error(
+ "Unable to set the default private search engine",
+ ex
+ );
+ }
+ }
+ }
+ );
+ }
+ });
+ },
+ },
+
+ SSLVersionMax: {
+ onBeforeAddons(manager, param) {
+ let tlsVersion;
+ switch (param) {
+ case "tls1":
+ tlsVersion = 1;
+ break;
+ case "tls1.1":
+ tlsVersion = 2;
+ break;
+ case "tls1.2":
+ tlsVersion = 3;
+ break;
+ case "tls1.3":
+ tlsVersion = 4;
+ break;
+ }
+ setAndLockPref("security.tls.version.max", tlsVersion);
+ },
+ },
+
+ SSLVersionMin: {
+ onBeforeAddons(manager, param) {
+ let tlsVersion;
+ switch (param) {
+ case "tls1":
+ tlsVersion = 1;
+ break;
+ case "tls1.1":
+ tlsVersion = 2;
+ break;
+ case "tls1.2":
+ tlsVersion = 3;
+ break;
+ case "tls1.3":
+ tlsVersion = 4;
+ break;
+ }
+ setAndLockPref("security.tls.version.min", tlsVersion);
+ },
+ },
+};
+
+/*
+ * ====================
+ * = HELPER FUNCTIONS =
+ * ====================
+ *
+ * The functions below are helpers to be used by several policies.
+ */
+
+/**
+ * setAndLockPref
+ *
+ * Sets the _default_ value of a pref, and locks it (meaning that
+ * the default value will always be returned, independent from what
+ * is stored as the user value).
+ * The value is only changed in memory, and not stored to disk.
+ *
+ * @param {string} prefName
+ * The pref to be changed
+ * @param {boolean,number,string} prefValue
+ * The value to set and lock
+ */
+export function setAndLockPref(prefName, prefValue) {
+ PoliciesUtils.setDefaultPref(prefName, prefValue, true);
+}
+
+/**
+ * setDefaultPref
+ *
+ * Sets the _default_ value of a pref and optionally locks it.
+ * The value is only changed in memory, and not stored to disk.
+ *
+ * @param {string} prefName
+ * The pref to be changed
+ * @param {boolean,number,string} prefValue
+ * The value to set
+ * @param {boolean} locked
+ * Optionally lock the pref
+ */
+export var PoliciesUtils = {
+ setDefaultPref(prefName, prefValue, locked = false) {
+ if (Services.prefs.prefIsLocked(prefName)) {
+ Services.prefs.unlockPref(prefName);
+ }
+
+ let defaults = Services.prefs.getDefaultBranch("");
+
+ switch (typeof prefValue) {
+ case "boolean":
+ defaults.setBoolPref(prefName, prefValue);
+ break;
+
+ case "number":
+ if (!Number.isInteger(prefValue)) {
+ throw new Error(`Non-integer value for ${prefName}`);
+ }
+
+ // This is ugly, but necessary. On Windows GPO and macOS
+ // configs, booleans are converted to 0/1. In the previous
+ // Preferences implementation, the schema took care of
+ // automatically converting these values to booleans.
+ // Since we allow arbitrary prefs now, we have to do
+ // something different. See bug 1666836.
+ if (
+ defaults.getPrefType(prefName) == defaults.PREF_INT ||
+ ![0, 1].includes(prefValue)
+ ) {
+ defaults.setIntPref(prefName, prefValue);
+ } else {
+ defaults.setBoolPref(prefName, !!prefValue);
+ }
+ break;
+
+ case "string":
+ defaults.setStringPref(prefName, prefValue);
+ break;
+ }
+
+ if (locked) {
+ Services.prefs.lockPref(prefName);
+ }
+ },
+};
+
+/**
+ * addAllowDenyPermissions
+ *
+ * Helper function to call the permissions manager (Services.perms.addFromPrincipal)
+ * for two arrays of URLs.
+ *
+ * @param {string} permissionName
+ * The name of the permission to change
+ * @param {Array} allowList
+ * The list of URLs to be set as ALLOW_ACTION for the chosen permission.
+ * @param {Array} blockList
+ * The list of URLs to be set as DENY_ACTION for the chosen permission.
+ */
+function addAllowDenyPermissions(permissionName, allowList, blockList) {
+ allowList = allowList || [];
+ blockList = blockList || [];
+
+ for (let origin of allowList) {
+ try {
+ Services.perms.addFromPrincipal(
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin),
+ permissionName,
+ Ci.nsIPermissionManager.ALLOW_ACTION,
+ Ci.nsIPermissionManager.EXPIRE_POLICY
+ );
+ } catch (ex) {
+ lazy.log
+ .error(`Added by default for ${permissionName} permission in the permission
+ manager - ${origin.href}`);
+ }
+ }
+
+ for (let origin of blockList) {
+ Services.perms.addFromPrincipal(
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin),
+ permissionName,
+ Ci.nsIPermissionManager.DENY_ACTION,
+ Ci.nsIPermissionManager.EXPIRE_POLICY
+ );
+ }
+}
+
+/**
+ * runOnce
+ *
+ * Helper function to run a callback only once per policy.
+ *
+ * @param {string} actionName
+ * A given name which will be used to track if this callback has run.
+ * @param {Function} callback
+ * The callback to run only once.
+ */
+export function runOnce(actionName, callback) {
+ let prefName = `browser.policies.runonce.${actionName}`;
+ if (Services.prefs.getBoolPref(prefName, false)) {
+ lazy.log.debug(
+ `Not running action ${actionName} again because it has already run.`
+ );
+ return;
+ }
+ Services.prefs.setBoolPref(prefName, true);
+ callback();
+}
+
+/**
+ * runOncePerModification
+ *
+ * Helper function similar to runOnce. The difference is that runOnce runs the
+ * callback once when the policy is set, then never again.
+ * runOncePerModification runs the callback once each time the policy value
+ * changes from its previous value.
+ * If the callback that was passed is an async function, you can await on this
+ * function to await for the callback.
+ *
+ * @param {string} actionName
+ * A given name which will be used to track if this callback has run.
+ * This string will be part of a pref name.
+ * @param {string} policyValue
+ * The current value of the policy. This will be compared to previous
+ * values given to this function to determine if the policy value has
+ * changed. Regardless of the data type of the policy, this must be a
+ * string.
+ * @param {Function} callback
+ * The callback to be run when the pref value changes
+ * @returns Promise
+ * A promise that will resolve once the callback finishes running.
+ *
+ */
+async function runOncePerModification(actionName, policyValue, callback) {
+ let prefName = `browser.policies.runOncePerModification.${actionName}`;
+ let oldPolicyValue = Services.prefs.getStringPref(prefName, undefined);
+ if (policyValue === oldPolicyValue) {
+ lazy.log.debug(
+ `Not running action ${actionName} again because the policy's value is unchanged`
+ );
+ return Promise.resolve();
+ }
+ Services.prefs.setStringPref(prefName, policyValue);
+ return callback();
+}
+
+/**
+ * clearRunOnceModification
+ *
+ * Helper function that clears a runOnce policy.
+ */
+function clearRunOnceModification(actionName) {
+ let prefName = `browser.policies.runOncePerModification.${actionName}`;
+ Services.prefs.clearUserPref(prefName);
+}
+
+function replacePathVariables(path) {
+ if (path.includes("${home}")) {
+ return path.replace("${home}", lazy.FileUtils.getFile("Home", []).path);
+ }
+ return path;
+}
+
+/**
+ * installAddonFromURL
+ *
+ * Helper function that installs an addon from a URL
+ * and verifies that the addon ID matches.
+ */
+function installAddonFromURL(url, extensionID, addon) {
+ if (
+ addon &&
+ addon.sourceURI &&
+ addon.sourceURI.spec == url &&
+ !addon.sourceURI.schemeIs("file")
+ ) {
+ // It's the same addon, don't reinstall.
+ return;
+ }
+ lazy.AddonManager.getInstallForURL(url, {
+ telemetryInfo: { source: "enterprise-policy" },
+ }).then(install => {
+ if (install.addon && install.addon.appDisabled) {
+ lazy.log.error(`Incompatible add-on - ${install.addon.id}`);
+ install.cancel();
+ return;
+ }
+ let listener = {
+ /* eslint-disable-next-line no-shadow */
+ onDownloadEnded: install => {
+ // Install failed, error will be reported elsewhere.
+ if (!install.addon) {
+ return;
+ }
+ if (extensionID && install.addon.id != extensionID) {
+ lazy.log.error(
+ `Add-on downloaded from ${url} had unexpected id (got ${install.addon.id} expected ${extensionID})`
+ );
+ install.removeListener(listener);
+ install.cancel();
+ }
+ if (install.addon.appDisabled) {
+ lazy.log.error(`Incompatible add-on - ${url}`);
+ install.removeListener(listener);
+ install.cancel();
+ }
+ if (
+ addon &&
+ Services.vc.compare(addon.version, install.addon.version) == 0
+ ) {
+ lazy.log.debug(
+ "Installation cancelled because versions are the same"
+ );
+ install.removeListener(listener);
+ install.cancel();
+ }
+ },
+ onDownloadFailed: () => {
+ install.removeListener(listener);
+ lazy.log.error(
+ `Download failed - ${lazy.AddonManager.errorToString(
+ install.error
+ )} - ${url}`
+ );
+ clearRunOnceModification("extensionsInstall");
+ },
+ onInstallFailed: () => {
+ install.removeListener(listener);
+ lazy.log.error(
+ `Installation failed - ${lazy.AddonManager.errorToString(
+ install.error
+ )} - {url}`
+ );
+ },
+ /* eslint-disable-next-line no-shadow */
+ onInstallEnded: (install, addon) => {
+ if (addon.type == "theme") {
+ addon.enable();
+ }
+ install.removeListener(listener);
+ lazy.log.debug(`Installation succeeded - ${url}`);
+ },
+ };
+ install.addListener(listener);
+ install.install();
+ });
+}
+
+let gBlockedAboutPages = [];
+
+function clearBlockedAboutPages() {
+ gBlockedAboutPages = [];
+}
+
+function blockAboutPage(manager, feature, neededOnContentProcess = false) {
+ addChromeURLBlocker();
+ gBlockedAboutPages.push(feature);
+
+ try {
+ let aboutModule = Cc[ABOUT_CONTRACT + feature.split(":")[1]].getService(
+ Ci.nsIAboutModule
+ );
+ let chromeURL = aboutModule.getChromeURI(Services.io.newURI(feature)).spec;
+ gBlockedAboutPages.push(chromeURL);
+ } catch (e) {
+ // Some about pages don't have chrome URLS (compat)
+ }
+}
+
+let ChromeURLBlockPolicy = {
+ shouldLoad(contentLocation, loadInfo, mimeTypeGuess) {
+ let contentType = loadInfo.externalContentPolicyType;
+ if (
+ (contentLocation.scheme != "chrome" &&
+ contentLocation.scheme != "about") ||
+ (contentType != Ci.nsIContentPolicy.TYPE_DOCUMENT &&
+ contentType != Ci.nsIContentPolicy.TYPE_SUBDOCUMENT)
+ ) {
+ return Ci.nsIContentPolicy.ACCEPT;
+ }
+ if (
+ gBlockedAboutPages.some(function (aboutPage) {
+ return contentLocation.spec.startsWith(aboutPage);
+ })
+ ) {
+ return Ci.nsIContentPolicy.REJECT_POLICY;
+ }
+ return Ci.nsIContentPolicy.ACCEPT;
+ },
+ shouldProcess(contentLocation, loadInfo, mimeTypeGuess) {
+ return Ci.nsIContentPolicy.ACCEPT;
+ },
+ classDescription: "Policy Engine Content Policy",
+ contractID: "@mozilla-org/policy-engine-content-policy-service;1",
+ classID: Components.ID("{ba7b9118-cabc-4845-8b26-4215d2a59ed7}"),
+ QueryInterface: ChromeUtils.generateQI(["nsIContentPolicy"]),
+ createInstance(iid) {
+ return this.QueryInterface(iid);
+ },
+};
+
+function addChromeURLBlocker() {
+ if (Cc[ChromeURLBlockPolicy.contractID]) {
+ return;
+ }
+
+ let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+ registrar.registerFactory(
+ ChromeURLBlockPolicy.classID,
+ ChromeURLBlockPolicy.classDescription,
+ ChromeURLBlockPolicy.contractID,
+ ChromeURLBlockPolicy
+ );
+
+ Services.catMan.addCategoryEntry(
+ "content-policy",
+ ChromeURLBlockPolicy.contractID,
+ ChromeURLBlockPolicy.contractID,
+ false,
+ true
+ );
+}
+
+function pemToBase64(pem) {
+ return pem
+ .replace(/(.*)-----BEGIN CERTIFICATE-----/, "")
+ .replace(/-----END CERTIFICATE-----(.*)/, "")
+ .replace(/[\r\n]/g, "");
+}
+
+function processMIMEInfo(mimeInfo, realMIMEInfo) {
+ if ("handlers" in mimeInfo) {
+ let firstHandler = true;
+ for (let handler of mimeInfo.handlers) {
+ // handler can be null which means they don't
+ // want a preferred handler.
+ if (handler) {
+ let handlerApp;
+ if ("path" in handler) {
+ try {
+ let file = new lazy.FileUtils.File(handler.path);
+ handlerApp = Cc[
+ "@mozilla.org/uriloader/local-handler-app;1"
+ ].createInstance(Ci.nsILocalHandlerApp);
+ handlerApp.executable = file;
+ } catch (ex) {
+ lazy.log.error(
+ `Unable to create handler executable (${handler.path})`
+ );
+ continue;
+ }
+ } else if ("uriTemplate" in handler) {
+ let templateURL = new URL(handler.uriTemplate);
+ if (templateURL.protocol != "https:") {
+ lazy.log.error(
+ `Web handler must be https (${handler.uriTemplate})`
+ );
+ continue;
+ }
+ if (
+ !templateURL.pathname.includes("%s") &&
+ !templateURL.search.includes("%s")
+ ) {
+ lazy.log.error(
+ `Web handler must contain %s (${handler.uriTemplate})`
+ );
+ continue;
+ }
+ handlerApp = Cc[
+ "@mozilla.org/uriloader/web-handler-app;1"
+ ].createInstance(Ci.nsIWebHandlerApp);
+ handlerApp.uriTemplate = handler.uriTemplate;
+ } else {
+ lazy.log.error("Invalid handler");
+ continue;
+ }
+ if ("name" in handler) {
+ handlerApp.name = handler.name;
+ }
+ realMIMEInfo.possibleApplicationHandlers.appendElement(handlerApp);
+ if (firstHandler) {
+ realMIMEInfo.preferredApplicationHandler = handlerApp;
+ }
+ }
+ firstHandler = false;
+ }
+ }
+ if ("action" in mimeInfo) {
+ let action = realMIMEInfo[mimeInfo.action];
+ if (
+ action == realMIMEInfo.useHelperApp &&
+ !realMIMEInfo.possibleApplicationHandlers.length
+ ) {
+ lazy.log.error("useHelperApp requires a handler");
+ return;
+ }
+ realMIMEInfo.preferredAction = action;
+ }
+ if ("ask" in mimeInfo) {
+ realMIMEInfo.alwaysAskBeforeHandling = mimeInfo.ask;
+ }
+ lazy.gHandlerService.store(realMIMEInfo);
+}
diff --git a/comm/mail/components/enterprisepolicies/content/aboutPolicies.js b/comm/mail/components/enterprisepolicies/content/aboutPolicies.js
new file mode 100644
index 0000000000..850286e001
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/content/aboutPolicies.js
@@ -0,0 +1,410 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+const lazy = {};
+ChromeUtils.defineESModuleGetters(lazy, {
+ schema: "resource:///modules/policies/schema.sys.mjs",
+});
+
+function col(text, className) {
+ let column = document.createElement("td");
+ if (className) {
+ column.classList.add(className);
+ }
+ let content = document.createTextNode(text);
+ column.appendChild(content);
+ return column;
+}
+
+function addMissingColumns() {
+ const table = document.getElementById("activeContent");
+ let maxColumns = 0;
+
+ // count the number of columns per row and set the max number of columns
+ for (let i = 0, length = table.rows.length; i < length; i++) {
+ if (maxColumns < table.rows[i].cells.length) {
+ maxColumns = table.rows[i].cells.length;
+ }
+ }
+
+ // add the missing columns
+ for (let i = 0, length = table.rows.length; i < length; i++) {
+ const rowLength = table.rows[i].cells.length;
+
+ if (rowLength < maxColumns) {
+ let missingColumns = maxColumns - rowLength;
+
+ while (missingColumns > 0) {
+ table.rows[i].insertCell();
+ missingColumns--;
+ }
+ }
+ }
+}
+
+/*
+ * This function generates the Active Policies content to be displayed by calling
+ * a recursive function called generatePolicy() according to the policy schema.
+ */
+
+function generateActivePolicies(data) {
+ let new_cont = document.getElementById("activeContent");
+ new_cont.classList.add("active-policies");
+
+ let policy_count = 0;
+
+ for (let policyName in data) {
+ const color_class = ++policy_count % 2 === 0 ? "even" : "odd";
+
+ if (lazy.schema.properties[policyName].type == "array") {
+ for (let count in data[policyName]) {
+ let isFirstRow = count == 0;
+ let isLastRow = count == data[policyName].length - 1;
+ let row = document.createElement("tr");
+ row.classList.add(color_class);
+ row.appendChild(col(isFirstRow ? policyName : ""));
+ generatePolicy(
+ data[policyName][count],
+ row,
+ 1,
+ new_cont,
+ isLastRow,
+ data[policyName].length > 1
+ );
+ }
+ } else if (lazy.schema.properties[policyName].type == "object") {
+ let count = 0;
+ for (let obj in data[policyName]) {
+ let isFirstRow = count == 0;
+ let isLastRow = count == Object.keys(data[policyName]).length - 1;
+ let row = document.createElement("tr");
+ row.classList.add(color_class);
+ row.appendChild(col(isFirstRow ? policyName : ""));
+ row.appendChild(col(obj));
+ generatePolicy(
+ data[policyName][obj],
+ row,
+ 2,
+ new_cont,
+ isLastRow,
+ true
+ );
+ count++;
+ }
+ } else {
+ let row = document.createElement("tr");
+ row.appendChild(col(policyName));
+ row.appendChild(col(JSON.stringify(data[policyName])));
+ row.classList.add(color_class, "last_row");
+ new_cont.appendChild(row);
+ }
+ }
+
+ if (policy_count < 1) {
+ let current_tab = document.querySelector(".active");
+ if (Services.policies.status == Services.policies.ACTIVE) {
+ current_tab.classList.add("no-specified-policies");
+ } else {
+ current_tab.classList.add("inactive-service");
+ }
+ }
+
+ addMissingColumns();
+}
+
+/*
+ * This is a helper recursive function that iterates levels of each
+ * policy and formats the content to be displayed accordingly.
+ */
+
+function generatePolicy(data, row, depth, new_cont, islast, arr_sep = false) {
+ const color_class = row.classList.contains("odd") ? "odd" : "even";
+
+ if (Array.isArray(data)) {
+ for (let count in data) {
+ if (count == 0) {
+ if (count == data.length - 1) {
+ generatePolicy(
+ data[count],
+ row,
+ depth + 1,
+ new_cont,
+ islast ? islast : false,
+ true
+ );
+ } else {
+ generatePolicy(data[count], row, depth + 1, new_cont, false, false);
+ }
+ } else if (count == data.length - 1) {
+ let last_row = document.createElement("tr");
+ last_row.classList.add(color_class, "arr_sep");
+
+ for (let i = 0; i < depth; i++) {
+ last_row.appendChild(col(""));
+ }
+
+ generatePolicy(
+ data[count],
+ last_row,
+ depth + 1,
+ new_cont,
+ islast ? islast : false,
+ arr_sep
+ );
+ } else {
+ let new_row = document.createElement("tr");
+ new_row.classList.add(color_class);
+
+ for (let i = 0; i < depth; i++) {
+ new_row.appendChild(col(""));
+ }
+
+ generatePolicy(data[count], new_row, depth + 1, new_cont, false, false);
+ }
+ }
+ } else if (typeof data == "object" && Object.keys(data).length > 0) {
+ let count = 0;
+ for (let obj in data) {
+ if (count == 0) {
+ row.appendChild(col(obj));
+ if (count == Object.keys(data).length - 1) {
+ generatePolicy(
+ data[obj],
+ row,
+ depth + 1,
+ new_cont,
+ islast ? islast : false,
+ arr_sep
+ );
+ } else {
+ generatePolicy(data[obj], row, depth + 1, new_cont, false, false);
+ }
+ } else if (count == Object.keys(data).length - 1) {
+ let last_row = document.createElement("tr");
+ for (let i = 0; i < depth; i++) {
+ last_row.appendChild(col(""));
+ }
+
+ last_row.appendChild(col(obj));
+ last_row.classList.add(color_class);
+
+ if (arr_sep) {
+ last_row.classList.add("arr_sep");
+ }
+
+ generatePolicy(
+ data[obj],
+ last_row,
+ depth + 1,
+ new_cont,
+ islast ? islast : false,
+ false
+ );
+ } else {
+ let new_row = document.createElement("tr");
+ new_row.classList.add(color_class);
+
+ for (let i = 0; i < depth; i++) {
+ new_row.appendChild(col(""));
+ }
+
+ new_row.appendChild(col(obj));
+ generatePolicy(data[obj], new_row, depth + 1, new_cont, false, false);
+ }
+ count++;
+ }
+ } else {
+ row.appendChild(col(JSON.stringify(data)));
+
+ if (arr_sep) {
+ row.classList.add("arr_sep");
+ }
+ if (islast) {
+ row.classList.add("last_row");
+ }
+ new_cont.appendChild(row);
+ }
+}
+
+function generateErrors() {
+ const consoleStorage = Cc["@mozilla.org/consoleAPI-storage;1"];
+ const storage = consoleStorage.getService(Ci.nsIConsoleAPIStorage);
+ const consoleEvents = storage.getEvents();
+ const prefixes = [
+ "Enterprise Policies",
+ "JsonSchemaValidator.jsm",
+ "Policies.jsm",
+ "GPOParser.jsm",
+ "Enterprise Policies Child",
+ "BookmarksPolicies.jsm",
+ "ProxyPolicies.jsm",
+ "WebsiteFilter Policy",
+ "macOSPoliciesParser.jsm",
+ ];
+
+ let new_cont = document.getElementById("errorsContent");
+ new_cont.classList.add("errors");
+
+ let flag = false;
+ for (let err of consoleEvents) {
+ if (prefixes.includes(err.prefix)) {
+ flag = true;
+ let row = document.createElement("tr");
+ row.appendChild(col(err.arguments[0]));
+ new_cont.appendChild(row);
+ }
+ }
+ if (!flag) {
+ let errors_tab = document.getElementById("category-errors");
+ errors_tab.style.display = "none";
+ }
+}
+
+function generateDocumentation() {
+ let new_cont = document.getElementById("documentationContent");
+ new_cont.setAttribute("id", "documentationContent");
+
+ // map specific policies to a different string ID, to allow updates to
+ // existing descriptions
+ let string_mapping = {
+ BackgroundAppUpdate: "BackgroundAppUpdate2",
+ Certificates: "CertificatesDescription",
+ };
+
+ for (let policyName in lazy.schema.properties) {
+ let main_tbody = document.createElement("tbody");
+ main_tbody.classList.add("collapsible");
+ main_tbody.addEventListener("click", function () {
+ let content = this.nextElementSibling;
+ content.classList.toggle("content");
+ });
+ let row = document.createElement("tr");
+ row.appendChild(col(policyName));
+ let descriptionColumn = col("");
+ let stringID = string_mapping[policyName] || policyName;
+ descriptionColumn.setAttribute("data-l10n-id", `policy-${stringID}`);
+ row.appendChild(descriptionColumn);
+ main_tbody.appendChild(row);
+ let sec_tbody = document.createElement("tbody");
+ sec_tbody.classList.add("content");
+ sec_tbody.classList.add("content-style");
+ let schema_row = document.createElement("tr");
+ if (lazy.schema.properties[policyName].properties) {
+ let column = col(
+ JSON.stringify(lazy.schema.properties[policyName].properties, null, 1),
+ "schema"
+ );
+ column.colSpan = "2";
+ schema_row.appendChild(column);
+ sec_tbody.appendChild(schema_row);
+ } else if (lazy.schema.properties[policyName].items) {
+ let column = col(
+ JSON.stringify(lazy.schema.properties[policyName], null, 1),
+ "schema"
+ );
+ column.colSpan = "2";
+ schema_row.appendChild(column);
+ sec_tbody.appendChild(schema_row);
+ } else {
+ let column = col(
+ "type: " + lazy.schema.properties[policyName].type,
+ "schema"
+ );
+ column.colSpan = "2";
+ schema_row.appendChild(column);
+ sec_tbody.appendChild(schema_row);
+ if (lazy.schema.properties[policyName].enum) {
+ let enum_row = document.createElement("tr");
+ column = col(
+ "enum: " +
+ JSON.stringify(lazy.schema.properties[policyName].enum, null, 1),
+ "schema"
+ );
+ column.colSpan = "2";
+ enum_row.appendChild(column);
+ sec_tbody.appendChild(enum_row);
+ }
+ }
+ new_cont.appendChild(main_tbody);
+ new_cont.appendChild(sec_tbody);
+ }
+}
+
+let gInited = false;
+function init() {
+ if (gInited) {
+ return;
+ }
+ gInited = true;
+
+ let data = Services.policies.getActivePolicies();
+ generateActivePolicies(data);
+ generateErrors();
+ generateDocumentation();
+
+ // Event delegation on #categories element
+ let menu = document.getElementById("categories");
+ for (let category of menu.children) {
+ category.addEventListener("click", () => show(category));
+ }
+
+ if (location.hash) {
+ let sectionButton = document.getElementById(
+ "category-" + location.hash.substring(1)
+ );
+ if (sectionButton) {
+ sectionButton.click();
+ }
+ }
+
+ window.addEventListener("hashchange", function () {
+ if (location.hash) {
+ let sectionButton = document.getElementById(
+ "category-" + location.hash.substring(1)
+ );
+ sectionButton.click();
+ }
+ });
+}
+
+function show(button) {
+ let current_tab = document.querySelector(".active");
+ let category = button.getAttribute("id").substring("category-".length);
+ let content = document.getElementById(category);
+ if (current_tab == content) {
+ return;
+ }
+ saveScrollPosition(current_tab.id);
+ current_tab.classList.remove("active");
+ current_tab.hidden = true;
+ content.classList.add("active");
+ content.hidden = false;
+
+ let current_button = document.querySelector("[selected=true]");
+ current_button.removeAttribute("selected");
+ button.setAttribute("selected", "true");
+
+ let title = document.getElementById("sectionTitle");
+ title.textContent = button.children[1].textContent;
+ location.hash = category;
+ restoreScrollPosition(category);
+}
+
+const scrollPositions = {};
+function saveScrollPosition(category) {
+ const mainContent = document.querySelector(".main-content");
+ scrollPositions[category] = mainContent.scrollTop;
+}
+
+function restoreScrollPosition(category) {
+ const scrollY = scrollPositions[category] || 0;
+ const mainContent = document.querySelector(".main-content");
+ mainContent.scrollTo(0, scrollY);
+}
diff --git a/comm/mail/components/enterprisepolicies/content/aboutPolicies.xhtml b/comm/mail/components/enterprisepolicies/content/aboutPolicies.xhtml
new file mode 100644
index 0000000000..6ded7130a4
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/content/aboutPolicies.xhtml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+# 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 http://mozilla.org/MPL/2.0/.
+-->
+<!DOCTYPE html>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title data-l10n-id="about-policies-title" />
+ <link
+ rel="stylesheet"
+ href="chrome://messenger/skin/aboutPolicies.css"
+ type="text/css"
+ />
+ <link rel="localization" href="branding/brand.ftl" />
+ <link rel="localization" href="messenger/policies/aboutPolicies.ftl" />
+ <link
+ rel="localization"
+ href="messenger/policies/policies-descriptions.ftl"
+ />
+ <script
+ type="application/javascript"
+ src="chrome://messenger/content/policies/aboutPolicies.js"
+ />
+ </head>
+ <body id="body" onload="init()">
+ <div id="categories">
+ <div class="category" selected="true" id="category-active">
+ <img
+ class="category-icon"
+ src="chrome://messenger/content/policies/policies-active.svg"
+ alt=""
+ />
+ <label class="category-name" data-l10n-id="active-policies-tab"></label>
+ </div>
+ <div class="category" id="category-documentation">
+ <img
+ class="category-icon"
+ src="chrome://messenger/content/policies/policies-documentation.svg"
+ alt=""
+ />
+ <label class="category-name" data-l10n-id="documentation-tab"></label>
+ </div>
+ <div class="category" id="category-errors">
+ <img
+ class="category-icon"
+ src="chrome://messenger/content/policies/policies-error.svg"
+ alt=""
+ />
+ <label class="category-name" data-l10n-id="errors-tab"></label>
+ </div>
+ </div>
+ <div class="main-content">
+ <div class="header">
+ <div
+ id="sectionTitle"
+ class="header-name"
+ data-l10n-id="active-policies-tab"
+ />
+ </div>
+
+ <div id="active" class="tab active">
+ <h3
+ class="inactive-service-message"
+ data-l10n-id="inactive-message"
+ ></h3>
+ <h3
+ class="no-specified-policies-message"
+ data-l10n-id="no-specified-policies-message"
+ ></h3>
+ <table>
+ <thead>
+ <tr>
+ <th data-l10n-id="policy-name" />
+ <th data-l10n-id="policy-value" />
+ </tr>
+ </thead>
+ <tbody id="activeContent" />
+ </table>
+ </div>
+
+ <div id="documentation" class="tab" hidden="true">
+ <table>
+ <thead>
+ <tr>
+ <th data-l10n-id="policy-name" />
+ </tr>
+ </thead>
+ <tbody id="documentationContent" />
+ </table>
+ </div>
+
+ <div id="errors" class="tab" hidden="true">
+ <table>
+ <thead>
+ <tr>
+ <th data-l10n-id="policy-errors" />
+ </tr>
+ </thead>
+ <tbody id="errorsContent" />
+ </table>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/comm/mail/components/enterprisepolicies/content/policies-active.svg b/comm/mail/components/enterprisepolicies/content/policies-active.svg
new file mode 100644
index 0000000000..9ec258d4e0
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/content/policies-active.svg
@@ -0,0 +1,6 @@
+<!-- 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 http://mozilla.org/MPL/2.0/. -->
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg">
+ <path fill="context-fill" fill-opacity="context-fill-opacity" d="M11 10a1 1 0 01-1-1V5H3v8a1 1 0 001 1h2a1 1 0 010 2H3a2 2 0 01-2-2V4c0-1.1.9-2 2-2h1.05a2.5 2.5 0 014.9 0H10a2 2 0 012 2v5a1 1 0 01-1 1zm-1-6V3H8.95a1 1 0 01-.98-.8 1.5 1.5 0 00-2.94 0 1 1 0 01-.98.8H3v1zM6.5 2a.5.5 0 110 1 .5.5 0 010-1zm-2 5a.5.5 0 010-1h4a.5.5 0 010 1zm0 2a.5.5 0 010-1h2a.5.5 0 010 1zm0 2a.5.5 0 110-1h3a.5.5 0 110 1zm5.16 5a.67.67 0 01-.47-.2l-2-2a.67.67 0 01.94-.95l1.44 1.44 4.22-6.02a.67.67 0 011.1.77L10.2 15.7a.67.67 0 01-.55.29z"/>
+</svg>
diff --git a/comm/mail/components/enterprisepolicies/content/policies-documentation.svg b/comm/mail/components/enterprisepolicies/content/policies-documentation.svg
new file mode 100644
index 0000000000..a2817d3514
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/content/policies-documentation.svg
@@ -0,0 +1,6 @@
+<!-- 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 http://mozilla.org/MPL/2.0/. -->
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg">
+ <path fill="context-fill" fill-opacity="context-fill-opacity" d="M11 7a1 1 0 01-1-1V5H3v8a1 1 0 001 1h2a1 1 0 110 2H3a2 2 0 01-2-2V4c0-1.1.9-2 2-2h1.05a2.5 2.5 0 014.9 0H10a2 2 0 012 2v2a1 1 0 01-1 1zm-1-3V3H8.95a1 1 0 01-.98-.8 1.5 1.5 0 00-2.94 0 1 1 0 01-.98.8H3v1zM6.5 2a.5.5 0 110 1 .5.5 0 010-1zm-2 5a.5.5 0 110-1h4a.5.5 0 010 1zm0 2a.5.5 0 110-1h2a.5.5 0 010 1zm0 2a.5.5 0 110-1h1a.5.5 0 110 1zm6.5 5a4 4 0 110-8 4 4 0 010 8zm.46-2a.46.46 0 10-.92 0 .46.46 0 00.92 0zm-1.06-3c0-.3.25-.6.6-.6s.6.3.6.6c0 .12-.06.25-.16.39-.08.1-.16.17-.23.24l-.1.09-.01.02a1.3 1.3 0 00-.5 1.06.4.4 0 10.8 0c0-.17.04-.26.08-.3a.61.61 0 01.08-.1l.05-.05.07-.07.04-.03c.12-.11.24-.24.35-.37.16-.2.33-.5.33-.88a1.4 1.4 0 00-2.8 0 .4.4 0 10.8 0z"/>
+</svg>
diff --git a/comm/mail/components/enterprisepolicies/content/policies-error.svg b/comm/mail/components/enterprisepolicies/content/policies-error.svg
new file mode 100644
index 0000000000..fad8ddd632
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/content/policies-error.svg
@@ -0,0 +1,6 @@
+<!-- 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 http://mozilla.org/MPL/2.0/. -->
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg">
+ <path fill="context-fill" fill-opacity="context-fill-opacity" d="M11 7a1 1 0 01-1-1V5H3v8a1 1 0 001 1h1a1 1 0 110 2H3a2 2 0 01-2-2V4c0-1.1.9-2 2-2h1.05a2.5 2.5 0 014.9 0H10a2 2 0 012 2v2a1 1 0 01-1 1zm-1-3V3H8.95a1 1 0 01-.98-.8 1.5 1.5 0 00-2.94 0 1 1 0 01-.98.8H3v1zM6.5 2a.5.5 0 110 1 .5.5 0 010-1zm-2 5a.5.5 0 110-1h4a.5.5 0 010 1zm0 2a.5.5 0 110-1h3a.5.5 0 010 1zm0 2a.5.5 0 110-1h2a.5.5 0 110 1zm10.4 3.34A1.15 1.15 0 0113.83 16h-5.7a1.15 1.15 0 01-1-1.66l2.85-5.7a1.15 1.15 0 012.06 0zm-4.44-3.78v1.73a.58.58 0 001.15 0v-1.73a.58.58 0 10-1.15 0zm.57 4.13a.68.68 0 100-1.36.68.68 0 000 1.36z"/>
+</svg>
diff --git a/comm/mail/components/enterprisepolicies/helpers/ProxyPolicies.sys.mjs b/comm/mail/components/enterprisepolicies/helpers/ProxyPolicies.sys.mjs
new file mode 100644
index 0000000000..086a928c60
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/helpers/ProxyPolicies.sys.mjs
@@ -0,0 +1,111 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
+
+const lazy = {};
+
+const PREF_LOGLEVEL = "browser.policies.loglevel";
+
+XPCOMUtils.defineLazyGetter(lazy, "log", () => {
+ let { ConsoleAPI } = ChromeUtils.importESModule(
+ "resource://gre/modules/Console.sys.mjs"
+ );
+ return new ConsoleAPI({
+ prefix: "ProxyPolicies.jsm",
+ // tip: set maxLogLevel to "debug" and use log.debug() to create detailed
+ // messages during development. See LOG_LEVELS in Console.jsm for details.
+ maxLogLevel: "error",
+ maxLogLevelPref: PREF_LOGLEVEL,
+ });
+});
+
+// Don't use const here because this is accessed by
+// tests through the BackstagePass object.
+export var PROXY_TYPES_MAP = new Map([
+ ["none", Ci.nsIProtocolProxyService.PROXYCONFIG_DIRECT],
+ ["system", Ci.nsIProtocolProxyService.PROXYCONFIG_SYSTEM],
+ ["manual", Ci.nsIProtocolProxyService.PROXYCONFIG_MANUAL],
+ ["autoDetect", Ci.nsIProtocolProxyService.PROXYCONFIG_WPAD],
+ ["autoConfig", Ci.nsIProtocolProxyService.PROXYCONFIG_PAC],
+]);
+
+export var ProxyPolicies = {
+ configureProxySettings(param, setPref) {
+ if (param.Mode) {
+ setPref("network.proxy.type", PROXY_TYPES_MAP.get(param.Mode));
+ }
+
+ if (param.AutoConfigURL) {
+ setPref("network.proxy.autoconfig_url", param.AutoConfigURL.href);
+ }
+
+ if (param.UseProxyForDNS !== undefined) {
+ setPref("network.proxy.socks_remote_dns", param.UseProxyForDNS);
+ }
+
+ if (param.AutoLogin !== undefined) {
+ setPref("signon.autologin.proxy", param.AutoLogin);
+ }
+
+ if (param.SOCKSVersion !== undefined) {
+ if (param.SOCKSVersion != 4 && param.SOCKSVersion != 5) {
+ lazy.log.error("Invalid SOCKS version");
+ } else {
+ setPref("network.proxy.socks_version", param.SOCKSVersion);
+ }
+ }
+
+ if (param.Passthrough !== undefined) {
+ setPref("network.proxy.no_proxies_on", param.Passthrough);
+ }
+
+ if (param.UseHTTPProxyForAllProtocols !== undefined) {
+ setPref(
+ "network.proxy.share_proxy_settings",
+ param.UseHTTPProxyForAllProtocols
+ );
+ }
+
+ if (param.FTPProxy) {
+ lazy.log.warn("FTPProxy support was removed in bug 1574475");
+ }
+
+ function setProxyHostAndPort(type, address) {
+ let url;
+ try {
+ // Prepend https just so we can use the URL parser
+ // instead of parsing manually.
+ url = new URL(`https://${address}`);
+ } catch (e) {
+ lazy.log.error(`Invalid address for ${type} proxy: ${address}`);
+ return;
+ }
+
+ setPref(`network.proxy.${type}`, url.hostname);
+ if (url.port) {
+ setPref(`network.proxy.${type}_port`, Number(url.port));
+ }
+ }
+
+ if (param.HTTPProxy) {
+ setProxyHostAndPort("http", param.HTTPProxy);
+
+ // network.proxy.share_proxy_settings is a UI feature, not handled by the
+ // network code. That pref only controls if the checkbox is checked, and
+ // then we must manually set the other values.
+ if (param.UseHTTPProxyForAllProtocols) {
+ param.SSLProxy = param.SOCKSProxy = param.HTTPProxy;
+ }
+ }
+
+ if (param.SSLProxy) {
+ setProxyHostAndPort("ssl", param.SSLProxy);
+ }
+
+ if (param.SOCKSProxy) {
+ setProxyHostAndPort("socks", param.SOCKSProxy);
+ }
+ },
+};
diff --git a/comm/mail/components/enterprisepolicies/helpers/moz.build b/comm/mail/components/enterprisepolicies/helpers/moz.build
new file mode 100644
index 0000000000..5f07c95153
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/helpers/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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 http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Thunderbird", "OS Integration")
+
+EXTRA_JS_MODULES.policies += [
+ "ProxyPolicies.sys.mjs",
+]
diff --git a/comm/mail/components/enterprisepolicies/jar.mn b/comm/mail/components/enterprisepolicies/jar.mn
new file mode 100644
index 0000000000..b344553811
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/jar.mn
@@ -0,0 +1,10 @@
+# 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 http://mozilla.org/MPL/2.0/.
+
+messenger.jar:
+ content/messenger/policies/aboutPolicies.xhtml (content/aboutPolicies.xhtml)
+ content/messenger/policies/aboutPolicies.js (content/aboutPolicies.js)
+ content/messenger/policies/policies-active.svg (content/policies-active.svg)
+ content/messenger/policies/policies-documentation.svg (content/policies-documentation.svg)
+ content/messenger/policies/policies-error.svg (content/policies-error.svg)
diff --git a/comm/mail/components/enterprisepolicies/moz.build b/comm/mail/components/enterprisepolicies/moz.build
new file mode 100644
index 0000000000..ac89d4fc17
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/moz.build
@@ -0,0 +1,23 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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 http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Thunderbird", "OS Integration")
+
+DIRS += [
+ "helpers",
+ "schemas",
+]
+
+TEST_DIRS += ["tests"]
+
+EXTRA_JS_MODULES.policies += [
+ "Policies.sys.mjs",
+]
+
+JAR_MANIFESTS += [
+ "jar.mn",
+]
diff --git a/comm/mail/components/enterprisepolicies/schemas/configuration.json b/comm/mail/components/enterprisepolicies/schemas/configuration.json
new file mode 100644
index 0000000000..8d3e9e43c2
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/schemas/configuration.json
@@ -0,0 +1,10 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "type": "object",
+ "properties": {
+ "policies": {
+ "$ref": "policies.json"
+ }
+ },
+ "required": ["policies"]
+}
diff --git a/comm/mail/components/enterprisepolicies/schemas/moz.build b/comm/mail/components/enterprisepolicies/schemas/moz.build
new file mode 100644
index 0000000000..658f0b1ed7
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/schemas/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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 http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Thunderbird", "OS Integration")
+
+EXTRA_PP_JS_MODULES.policies += [
+ "schema.sys.mjs",
+]
diff --git a/comm/mail/components/enterprisepolicies/schemas/policies-schema.json b/comm/mail/components/enterprisepolicies/schemas/policies-schema.json
new file mode 100644
index 0000000000..c49aa5ac16
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/schemas/policies-schema.json
@@ -0,0 +1,634 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "type": "object",
+ "properties": {
+ "3rdparty": {
+ "type": "object",
+ "properties": {
+ "Extensions": {
+ "type": "object",
+ "patternProperties": {
+ "^.*$": {
+ "type": "JSON"
+ }
+ }
+ }
+ }
+ },
+
+ "AppAutoUpdate": {
+ "type": "boolean"
+ },
+
+ "AppUpdatePin": {
+ "type": "string"
+ },
+
+ "AppUpdateURL": {
+ "type": "URL"
+ },
+
+ "Authentication": {
+ "type": "object",
+ "properties": {
+ "SPNEGO": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "Delegated": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "NTLM": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "AllowNonFQDN": {
+ "type": "object",
+ "properties": {
+ "SPNEGO": {
+ "type": "boolean"
+ },
+
+ "NTLM": {
+ "type": "boolean"
+ }
+ }
+ },
+ "AllowProxies": {
+ "type": "object",
+ "properties": {
+ "SPNEGO": {
+ "type": "boolean"
+ },
+
+ "NTLM": {
+ "type": "boolean"
+ }
+ }
+ },
+ "Locked": {
+ "type": "boolean"
+ },
+ "PrivateBrowsing": {
+ "type": "boolean"
+ }
+ }
+ },
+
+ "BackgroundAppUpdate": {
+ "type": "boolean"
+ },
+
+ "BlockAboutAddons": {
+ "type": "boolean"
+ },
+
+ "BlockAboutConfig": {
+ "type": "boolean"
+ },
+
+ "BlockAboutProfiles": {
+ "type": "boolean"
+ },
+
+ "BlockAboutSupport": {
+ "type": "boolean"
+ },
+
+ "CaptivePortal": {
+ "type": "boolean"
+ },
+
+ "Certificates": {
+ "type": "object",
+ "properties": {
+ "ImportEnterpriseRoots": {
+ "type": "boolean"
+ },
+ "Install": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ },
+
+ "Cookies": {
+ "type": "object",
+ "properties": {
+ "Allow": {
+ "type": "array",
+ "strict": false,
+ "items": {
+ "type": "origin"
+ }
+ },
+
+ "Block": {
+ "type": "array",
+ "strict": false,
+ "items": {
+ "type": "origin"
+ }
+ },
+
+ "Default": {
+ "type": "boolean"
+ },
+
+ "AcceptThirdParty": {
+ "type": "string",
+ "enum": ["always", "never", "from-visited"]
+ },
+
+ "ExpireAtSessionEnd": {
+ "type": "boolean"
+ },
+
+ "Locked": {
+ "type": "boolean"
+ }
+ }
+ },
+
+ "DefaultDownloadDirectory": {
+ "type": "string"
+ },
+
+ "DisableAppUpdate": {
+ "type": "boolean"
+ },
+
+ "DisableBuiltinPDFViewer": {
+ "type": "boolean"
+ },
+
+ "DisabledCiphers": {
+ "type": "object",
+ "properties": {
+ "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": {
+ "type": "boolean"
+ },
+ "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": {
+ "type": "boolean"
+ },
+ "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256": {
+ "type": "boolean"
+ },
+ "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256": {
+ "type": "boolean"
+ },
+ "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": {
+ "type": "boolean"
+ },
+ "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": {
+ "type": "boolean"
+ },
+ "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": {
+ "type": "boolean"
+ },
+ "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": {
+ "type": "boolean"
+ },
+ "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": {
+ "type": "boolean"
+ },
+ "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": {
+ "type": "boolean"
+ },
+ "TLS_DHE_RSA_WITH_AES_128_CBC_SHA": {
+ "type": "boolean"
+ },
+ "TLS_DHE_RSA_WITH_AES_256_CBC_SHA": {
+ "type": "boolean"
+ },
+ "TLS_RSA_WITH_AES_128_GCM_SHA256": {
+ "type": "boolean"
+ },
+ "TLS_RSA_WITH_AES_256_GCM_SHA384": {
+ "type": "boolean"
+ },
+ "TLS_RSA_WITH_AES_128_CBC_SHA": {
+ "type": "boolean"
+ },
+ "TLS_RSA_WITH_AES_256_CBC_SHA": {
+ "type": "boolean"
+ },
+ "TLS_RSA_WITH_3DES_EDE_CBC_SHA": {
+ "type": "boolean"
+ }
+ }
+ },
+
+ "DisableDeveloperTools": {
+ "type": "boolean"
+ },
+
+ "DisableMasterPasswordCreation": {
+ "type": "boolean"
+ },
+
+ "DisablePasswordReveal": {
+ "type": "boolean"
+ },
+
+ "DisableSafeMode": {
+ "type": "boolean"
+ },
+
+ "DisableSecurityBypass": {
+ "type": "object",
+ "properties": {
+ "InvalidCertificate": {
+ "type": "boolean"
+ },
+
+ "SafeBrowsing": {
+ "type": "boolean"
+ }
+ }
+ },
+
+ "DisableSystemAddonUpdate": {
+ "type": "boolean"
+ },
+
+ "DisableTelemetry": {
+ "type": "boolean"
+ },
+
+ "DNSOverHTTPS": {
+ "type": "object",
+ "properties": {
+ "Enabled": {
+ "type": "boolean"
+ },
+ "ProviderURL": {
+ "type": "URLorEmpty"
+ },
+ "ExcludedDomains": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "Locked": {
+ "type": "boolean"
+ }
+ }
+ },
+
+ "DownloadDirectory": {
+ "type": "string"
+ },
+
+ "Extensions": {
+ "type": "object",
+ "properties": {
+ "Install": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "Uninstall": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "Locked": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ },
+
+ "ExtensionSettings": {
+ "type": "object",
+ "properties": {
+ "*": {
+ "type": "object",
+ "properties": {
+ "installation_mode": {
+ "type": "string",
+ "enum": ["allowed", "blocked"]
+ },
+ "allowed_types": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": ["extension", "dictionary", "locale", "theme"]
+ }
+ },
+ "blocked_install_message": {
+ "type": "string"
+ },
+ "install_sources": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "restricted_domains": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ },
+ "patternProperties": {
+ "^.*$": {
+ "type": "object",
+ "properties": {
+ "installation_mode": {
+ "type": "string",
+ "enum": [
+ "allowed",
+ "blocked",
+ "force_installed",
+ "normal_installed"
+ ]
+ },
+ "install_url": {
+ "type": "string"
+ },
+ "blocked_install_message": {
+ "type": "string"
+ },
+ "updates_disabled": {
+ "type": "boolean"
+ }
+ }
+ }
+ }
+ },
+
+ "ExtensionUpdate": {
+ "type": "boolean"
+ },
+
+ "Handlers": {
+ "type": "object",
+ "patternProperties": {
+ "^(mimeTypes|extensions|schemes)$": {
+ "type": "object",
+ "patternProperties": {
+ "^.*$": {
+ "type": "object",
+ "properties": {
+ "action": {
+ "type": "string",
+ "enum": ["saveToDisk", "useHelperApp", "useSystemDefault"]
+ },
+ "ask": {
+ "type": "boolean"
+ },
+ "handlers": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "path": {
+ "type": "string"
+ },
+ "uriTemplate": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+
+ "HardwareAcceleration": {
+ "type": "boolean"
+ },
+
+ "InstallAddonsPermission": {
+ "type": "object",
+ "properties": {
+ "Allow": {
+ "type": "array",
+ "strict": false,
+ "items": {
+ "type": "origin"
+ }
+ },
+ "Default": {
+ "type": "boolean"
+ }
+ }
+ },
+
+ "ManualAppUpdateOnly": {
+ "type": "boolean"
+ },
+
+ "NetworkPrediction": {
+ "type": "boolean"
+ },
+
+ "OfferToSaveLogins": {
+ "type": "boolean"
+ },
+
+ "OfferToSaveLoginsDefault": {
+ "type": "boolean"
+ },
+
+ "PasswordManagerEnabled": {
+ "type": "boolean"
+ },
+
+ "PDFjs": {
+ "type": "object",
+ "properties": {
+ "Enabled": {
+ "type": "boolean"
+ },
+ "EnablePermissions": {
+ "type": "boolean"
+ }
+ }
+ },
+
+ "Preferences": {
+ "type": "object",
+ "patternProperties": {
+ "^.*$": {
+ "type": ["number", "boolean", "string", "object"],
+ "properties": {
+ "Value": {
+ "type": ["number", "boolean", "string"]
+ },
+ "Status": {
+ "type": "string",
+ "enum": ["default", "locked", "user", "clear"]
+ }
+ }
+ }
+ }
+ },
+
+ "PrimaryPassword": {
+ "type": "boolean"
+ },
+
+ "PromptForDownloadLocation": {
+ "type": "boolean"
+ },
+
+ "Proxy": {
+ "type": "object",
+ "properties": {
+ "Mode": {
+ "type": "string",
+ "enum": ["none", "system", "manual", "autoDetect", "autoConfig"]
+ },
+
+ "Locked": {
+ "type": "boolean"
+ },
+
+ "AutoConfigURL": {
+ "type": "URLorEmpty"
+ },
+
+ "FTPProxy": {
+ "type": "string"
+ },
+
+ "HTTPProxy": {
+ "type": "string"
+ },
+
+ "SSLProxy": {
+ "type": "string"
+ },
+
+ "SOCKSProxy": {
+ "type": "string"
+ },
+
+ "SOCKSVersion": {
+ "type": "number",
+ "enum": [4, 5]
+ },
+
+ "UseHTTPProxyForAllProtocols": {
+ "type": "boolean"
+ },
+
+ "Passthrough": {
+ "type": "string"
+ },
+
+ "UseProxyForDNS": {
+ "type": "boolean"
+ },
+
+ "AutoLogin": {
+ "type": "boolean"
+ }
+ }
+ },
+
+ "RequestedLocales": {
+ "type": ["string", "array"],
+ "items": {
+ "type": "string"
+ }
+ },
+
+ "SearchEngines": {
+ "enterprise_only": true,
+
+ "type": "object",
+ "properties": {
+ "Add": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": ["Name", "URLTemplate"],
+
+ "properties": {
+ "Name": {
+ "type": "string"
+ },
+ "IconURL": {
+ "type": "URLorEmpty"
+ },
+ "Alias": {
+ "type": "string"
+ },
+ "Description": {
+ "type": "string"
+ },
+ "Encoding": {
+ "type": "string"
+ },
+ "Method": {
+ "type": "string",
+ "enum": ["GET", "POST"]
+ },
+ "URLTemplate": {
+ "type": "string"
+ },
+ "PostData": {
+ "type": "string"
+ },
+ "SuggestURLTemplate": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "Default": {
+ "type": "string"
+ },
+ "DefaultPrivate": {
+ "type": "string"
+ },
+ "PreventInstalls": {
+ "type": "boolean"
+ },
+ "Remove": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ },
+
+ "SSLVersionMax": {
+ "type": "string",
+ "enum": ["tls1", "tls1.1", "tls1.2", "tls1.3"]
+ },
+
+ "SSLVersionMin": {
+ "type": "string",
+ "enum": ["tls1", "tls1.1", "tls1.2", "tls1.3"]
+ }
+ }
+}
diff --git a/comm/mail/components/enterprisepolicies/schemas/schema.sys.mjs b/comm/mail/components/enterprisepolicies/schemas/schema.sys.mjs
new file mode 100644
index 0000000000..671a8cfca1
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/schemas/schema.sys.mjs
@@ -0,0 +1,16 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+const initialSchema =
+#include policies-schema.json
+
+export let schema = initialSchema;
+
+export function modifySchemaForTests(customSchema) {
+ if (customSchema) {
+ schema = customSchema;
+ } else {
+ schema = initialSchema;
+ }
+}
diff --git a/comm/mail/components/enterprisepolicies/tests/browser/browser.ini b/comm/mail/components/enterprisepolicies/tests/browser/browser.ini
new file mode 100644
index 0000000000..9c709131a2
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/browser/browser.ini
@@ -0,0 +1,32 @@
+[DEFAULT]
+head = head.js
+prefs =
+ mail.provider.suppress_dialog_on_startup=true
+ mail.spotlight.firstRunDone=true
+ mail.winsearch.firstRunDone=true
+ mailnews.start_page.override_url=about:blank
+ mailnews.start_page.url=about:blank
+subsuite = thunderbird
+support-files =
+ policytest_v0.1.xpi
+ policytest_v0.2.xpi
+ extensionsettings.html
+
+[browser_policies_setAndLockPref_API.js]
+[browser_policy_app_auto_update.js]
+skip-if = os == 'win' && msix # Updater is disabled in MSIX builds
+[browser_policy_app_update.js]
+skip-if = os == 'win' && msix # Updater is disabled in MSIX builds
+[browser_policy_background_app_update.js]
+skip-if = os == 'win' && msix # Updater is disabled in MSIX builds
+[browser_policy_block_about.js]
+[browser_policy_cookie_settings.js]
+[browser_policy_disable_safemode.js]
+[browser_policy_disable_telemetry.js]
+[browser_policy_downloads.js]
+[browser_policy_extensions.js]
+[browser_policy_extensionsettings.js]
+[browser_policy_extensionsettings2.js]
+[browser_policy_handlers.js]
+[browser_policy_masterpassword.js]
+[browser_policy_passwordmanager.js]
diff --git a/comm/mail/components/enterprisepolicies/tests/browser/browser_policies_setAndLockPref_API.js b/comm/mail/components/enterprisepolicies/tests/browser/browser_policies_setAndLockPref_API.js
new file mode 100644
index 0000000000..0cad8e5aa3
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/browser/browser_policies_setAndLockPref_API.js
@@ -0,0 +1,179 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let { Policies, setAndLockPref, PoliciesUtils } = ChromeUtils.importESModule(
+ "resource:///modules/policies/Policies.sys.mjs"
+);
+
+add_task(async function test_API_directly() {
+ await setupPolicyEngineWithJson("");
+ setAndLockPref("policies.test.boolPref", true);
+ checkLockedPref("policies.test.boolPref", true);
+
+ // Check that a previously-locked pref can be changed
+ // (it will be unlocked first).
+ setAndLockPref("policies.test.boolPref", false);
+ checkLockedPref("policies.test.boolPref", false);
+
+ setAndLockPref("policies.test.intPref", 2);
+ checkLockedPref("policies.test.intPref", 2);
+
+ setAndLockPref("policies.test.stringPref", "policies test");
+ checkLockedPref("policies.test.stringPref", "policies test");
+
+ PoliciesUtils.setDefaultPref(
+ "policies.test.lockedPref",
+ "policies test",
+ true
+ );
+ checkLockedPref("policies.test.lockedPref", "policies test");
+
+ // Test that user values do not override the prefs, and the get*Pref call
+ // still return the value set through setAndLockPref
+ Services.prefs.setBoolPref("policies.test.boolPref", true);
+ checkLockedPref("policies.test.boolPref", false);
+
+ Services.prefs.setIntPref("policies.test.intPref", 10);
+ checkLockedPref("policies.test.intPref", 2);
+
+ Services.prefs.setStringPref("policies.test.stringPref", "policies test");
+ checkLockedPref("policies.test.stringPref", "policies test");
+
+ try {
+ // Test that a non-integer value is correctly rejected, even though
+ // typeof(val) == "number"
+ setAndLockPref("policies.test.intPref", 1.5);
+ ok(false, "Integer value should be rejected");
+ } catch (ex) {
+ ok(true, "Integer value was rejected");
+ }
+});
+
+add_task(async function test_API_through_policies() {
+ // Ensure that the values received by the policies have the correct
+ // type to make sure things are properly working.
+
+ // Implement functions to handle the three simple policies
+ // that will be added to the schema.
+ Policies.bool_policy = {
+ onBeforeUIStartup(manager, param) {
+ setAndLockPref("policies.test2.boolPref", param);
+ },
+ };
+
+ Policies.int_policy = {
+ onBeforeUIStartup(manager, param) {
+ setAndLockPref("policies.test2.intPref", param);
+ },
+ };
+
+ Policies.string_policy = {
+ onBeforeUIStartup(manager, param) {
+ setAndLockPref("policies.test2.stringPref", param);
+ },
+ };
+
+ await setupPolicyEngineWithJson(
+ // policies.json
+ {
+ policies: {
+ bool_policy: true,
+ int_policy: 42,
+ string_policy: "policies test 2",
+ },
+ },
+
+ // custom schema
+ {
+ properties: {
+ bool_policy: {
+ type: "boolean",
+ },
+
+ int_policy: {
+ type: "integer",
+ },
+
+ string_policy: {
+ type: "string",
+ },
+ },
+ }
+ );
+
+ is(
+ Services.policies.status,
+ Ci.nsIEnterprisePolicies.ACTIVE,
+ "Engine is active"
+ );
+
+ // The expected values come from config_setAndLockPref.json
+ checkLockedPref("policies.test2.boolPref", true);
+ checkLockedPref("policies.test2.intPref", 42);
+ checkLockedPref("policies.test2.stringPref", "policies test 2");
+
+ delete Policies.bool_policy;
+ delete Policies.int_policy;
+ delete Policies.string_policy;
+});
+
+add_task(async function test_pref_tracker() {
+ // Tests the test harness functionality that tracks usage of
+ // the setAndLockPref and setDefualtPref APIs.
+
+ let defaults = Services.prefs.getDefaultBranch("");
+
+ // Test prefs that had a default value and got changed to another
+ defaults.setIntPref("test1.pref1", 10);
+ defaults.setStringPref("test1.pref2", "test");
+
+ setAndLockPref("test1.pref1", 20);
+ PoliciesUtils.setDefaultPref("test1.pref2", "NEW VALUE");
+ setAndLockPref("test1.pref3", "NEW VALUE");
+ PoliciesUtils.setDefaultPref("test1.pref4", 20);
+
+ PoliciesPrefTracker.restoreDefaultValues();
+
+ is(
+ Services.prefs.getIntPref("test1.pref1"),
+ 10,
+ "Expected value for test1.pref1"
+ );
+ is(
+ Services.prefs.getStringPref("test1.pref2"),
+ "test",
+ "Expected value for test1.pref2"
+ );
+ is(
+ Services.prefs.prefIsLocked("test1.pref1"),
+ false,
+ "test1.pref1 got unlocked"
+ );
+ ok(
+ !Services.prefs.getStringPref("test1.pref3", undefined),
+ "test1.pref3 should have had its value unset"
+ );
+ is(
+ Services.prefs.getIntPref("test1.pref4", -1),
+ -1,
+ "test1.pref4 should have had its value unset"
+ );
+
+ // Test a pref that had a default value and a user value
+ defaults.setIntPref("test2.pref1", 10);
+ Services.prefs.setIntPref("test2.pref1", 20);
+
+ setAndLockPref("test2.pref1", 20);
+
+ PoliciesPrefTracker.restoreDefaultValues();
+
+ is(Services.prefs.getIntPref("test2.pref1"), 20, "Correct user value");
+ is(defaults.getIntPref("test2.pref1"), 10, "Correct default value");
+ is(
+ Services.prefs.prefIsLocked("test2.pref1"),
+ false,
+ "felipe pref is not locked"
+ );
+});
diff --git a/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_app_auto_update.js b/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_app_auto_update.js
new file mode 100644
index 0000000000..433408642f
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_app_auto_update.js
@@ -0,0 +1,92 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+ChromeUtils.defineESModuleGetters(this, {
+ UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs",
+});
+
+async function test_app_update_auto(expectedEnabled, expectedLocked) {
+ let actualEnabled = await UpdateUtils.getAppUpdateAutoEnabled();
+ is(
+ actualEnabled,
+ expectedEnabled,
+ `Actual auto update enabled setting should match the expected value of ${expectedEnabled}`
+ );
+
+ let actualLocked = UpdateUtils.appUpdateAutoSettingIsLocked();
+ is(
+ actualLocked,
+ expectedLocked,
+ `Auto update enabled setting ${
+ expectedLocked ? "should" : "should not"
+ } be locked`
+ );
+
+ let setSuccess = true;
+ try {
+ await UpdateUtils.setAppUpdateAutoEnabled(actualEnabled);
+ } catch (error) {
+ setSuccess = false;
+ }
+ is(
+ setSuccess,
+ !expectedLocked,
+ `Setting auto update ${expectedLocked ? "should" : "should not"} fail`
+ );
+
+ let tabmail = document.getElementById("tabmail");
+ let prefsTabMode = tabmail.tabModes.preferencesTab;
+
+ let prefsDocument = await new Promise(resolve => {
+ Services.obs.addObserver(function documentLoaded(subject) {
+ if (subject.URL == "about:preferences") {
+ Services.obs.removeObserver(documentLoaded, "chrome-document-loaded");
+ resolve(subject);
+ }
+ }, "chrome-document-loaded");
+ window.openPreferencesTab("paneGeneral", "updateApp");
+ });
+
+ await new Promise(resolve => setTimeout(resolve));
+
+ is(
+ prefsDocument.getElementById("updateSettingsContainer").hidden,
+ expectedLocked,
+ `When auto update ${
+ expectedLocked ? "is" : "isn't"
+ } locked, the corresponding preferences entry ${
+ expectedLocked ? "should" : "shouldn't"
+ } be hidden`
+ );
+
+ tabmail.closeTab(prefsTabMode.tabs[0]);
+}
+
+add_task(async function test_app_auto_update_policy() {
+ let originalUpdateAutoValue = await UpdateUtils.getAppUpdateAutoEnabled();
+ registerCleanupFunction(async () => {
+ await UpdateUtils.setAppUpdateAutoEnabled(originalUpdateAutoValue);
+ });
+
+ await UpdateUtils.setAppUpdateAutoEnabled(true);
+ await test_app_update_auto(true, false);
+
+ await setupPolicyEngineWithJson({
+ policies: {
+ AppAutoUpdate: false,
+ },
+ });
+ await test_app_update_auto(false, true);
+
+ await setupPolicyEngineWithJson({});
+ await UpdateUtils.setAppUpdateAutoEnabled(false);
+ await test_app_update_auto(false, false);
+
+ await setupPolicyEngineWithJson({
+ policies: {
+ AppAutoUpdate: true,
+ },
+ });
+ await test_app_update_auto(true, true);
+});
diff --git a/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_app_update.js b/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_app_update.js
new file mode 100644
index 0000000000..14a9c92bc5
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_app_update.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+ChromeUtils.defineESModuleGetters(this, {
+ UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs",
+});
+var updateService = Cc["@mozilla.org/updates/update-service;1"].getService(
+ Ci.nsIApplicationUpdateService
+);
+
+// This test is intended to ensure that nsIUpdateService::canCheckForUpdates
+// is true before the "DisableAppUpdate" policy is applied. Testing that
+// nsIUpdateService::canCheckForUpdates is false after the "DisableAppUpdate"
+// policy is applied needs to occur in a different test since the policy does
+// not properly take effect unless it is applied during application startup.
+add_task(async function test_updates_pre_policy() {
+ // Turn off automatic update before we set app.update.disabledForTesting to
+ // false so that we don't cause an actual update.
+ let originalUpdateAutoValue = await UpdateUtils.getAppUpdateAutoEnabled();
+ await UpdateUtils.setAppUpdateAutoEnabled(false);
+ registerCleanupFunction(async () => {
+ await UpdateUtils.setAppUpdateAutoEnabled(originalUpdateAutoValue);
+ });
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["app.update.disabledForTesting", false]],
+ });
+
+ is(
+ Services.policies.isAllowed("appUpdate"),
+ true,
+ "Since no policies have been set, appUpdate should be allowed by default"
+ );
+
+ is(
+ updateService.canCheckForUpdates,
+ true,
+ "Should be able to check for updates before any policies are in effect."
+ );
+});
diff --git a/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_background_app_update.js b/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_background_app_update.js
new file mode 100644
index 0000000000..4c0369df3d
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_background_app_update.js
@@ -0,0 +1,104 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs",
+});
+
+const PREF_NAME = "app.update.background.enabled";
+
+async function test_background_update_pref(expectedEnabled, expectedLocked) {
+ let actualEnabled = await UpdateUtils.readUpdateConfigSetting(PREF_NAME);
+ is(
+ actualEnabled,
+ expectedEnabled,
+ `Actual background update enabled setting should be ${expectedEnabled}`
+ );
+
+ let actualLocked = UpdateUtils.appUpdateSettingIsLocked(PREF_NAME);
+ is(
+ actualLocked,
+ expectedLocked,
+ `Background update enabled setting ${
+ expectedLocked ? "should" : "should not"
+ } be locked`
+ );
+
+ let setSuccess = true;
+ try {
+ await UpdateUtils.writeUpdateConfigSetting(PREF_NAME, actualEnabled);
+ } catch (error) {
+ setSuccess = false;
+ }
+ is(
+ setSuccess,
+ !expectedLocked,
+ `Setting background update pref ${
+ expectedLocked ? "should" : "should not"
+ } fail`
+ );
+
+ if (AppConstants.MOZ_UPDATER && AppConstants.MOZ_UPDATE_AGENT) {
+ let shouldShowUI =
+ !expectedLocked && UpdateUtils.PER_INSTALLATION_PREFS_SUPPORTED;
+ await BrowserTestUtils.withNewTab("about:preferences", browser => {
+ is(
+ browser.contentDocument.getElementById("backgroundUpdate").hidden,
+ !shouldShowUI,
+ `When background update ${
+ expectedLocked ? "is" : "isn't"
+ } locked, and per-installation prefs ${
+ UpdateUtils.PER_INSTALLATION_PREFS_SUPPORTED ? "are" : "aren't"
+ } supported, the corresponding preferences entry ${
+ shouldShowUI ? "shouldn't" : "should"
+ } be hidden`
+ );
+ });
+ } else {
+ // The backgroundUpdate element is #ifdef'ed out if MOZ_UPDATER and
+ // MOZ_UPDATE_AGENT are not both defined.
+ info(
+ "Warning: UI testing skipped because support for background update is " +
+ "not present"
+ );
+ }
+}
+
+add_task(async function test_background_app_update_policy() {
+ // Turn on the background update UI so we can test it.
+ await SpecialPowers.pushPrefEnv({
+ set: [["app.update.background.scheduling.enabled", true]],
+ });
+
+ const origBackgroundUpdateVal = await UpdateUtils.readUpdateConfigSetting(
+ PREF_NAME
+ );
+ registerCleanupFunction(async () => {
+ await UpdateUtils.writeUpdateConfigSetting(
+ PREF_NAME,
+ origBackgroundUpdateVal
+ );
+ });
+
+ await UpdateUtils.writeUpdateConfigSetting(PREF_NAME, true);
+ await test_background_update_pref(true, false);
+
+ await setupPolicyEngineWithJson({
+ policies: {
+ BackgroundAppUpdate: false,
+ },
+ });
+ await test_background_update_pref(false, true);
+
+ await setupPolicyEngineWithJson({});
+ await UpdateUtils.writeUpdateConfigSetting(PREF_NAME, false);
+ await test_background_update_pref(false, false);
+
+ await setupPolicyEngineWithJson({
+ policies: {
+ BackgroundAppUpdate: true,
+ },
+ });
+ await test_background_update_pref(true, true);
+});
diff --git a/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_block_about.js b/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_block_about.js
new file mode 100644
index 0000000000..4be9c7fee8
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_block_about.js
@@ -0,0 +1,87 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const { PromiseTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromiseTestUtils.sys.mjs"
+);
+
+const ABOUT_CONTRACT = "@mozilla.org/network/protocol/about;1?what=";
+
+const policiesToTest = [
+ {
+ policies: {
+ BlockAboutAddons: true,
+ },
+ urls: ["about:addons"],
+ },
+
+ {
+ policies: {
+ BlockAboutConfig: true,
+ },
+ urls: ["about:config"],
+ },
+ {
+ policies: {
+ BlockAboutProfiles: true,
+ },
+ urls: ["about:profiles"],
+ },
+
+ {
+ policies: {
+ BlockAboutSupport: true,
+ },
+ urls: ["about:support"],
+ },
+
+ {
+ policies: {
+ DisableDeveloperTools: true,
+ },
+ urls: ["about:debugging", "about:devtools-toolbox"],
+ },
+ {
+ policies: {
+ DisableTelemetry: true,
+ },
+ urls: ["about:telemetry"],
+ },
+];
+
+add_task(async function testAboutTask() {
+ for (let policyToTest of policiesToTest) {
+ let policyJSON = { policies: {} };
+ policyJSON.policies = policyToTest.policies;
+ for (let url of policyToTest.urls) {
+ if (url.startsWith("about")) {
+ let feature = url.split(":")[1];
+ let aboutModule = Cc[ABOUT_CONTRACT + feature].getService(
+ Ci.nsIAboutModule
+ );
+ let chromeURL = aboutModule.getChromeURI(Services.io.newURI(url)).spec;
+ await testPageBlockedByPolicy(policyJSON, chromeURL);
+ }
+ await testPageBlockedByPolicy(policyJSON, url);
+ }
+ }
+});
+
+async function testPageBlockedByPolicy(policyJSON, page) {
+ await EnterprisePolicyTesting.setupPolicyEngineWithJson(policyJSON);
+
+ await withNewTab({ url: "about:blank" }, async browser => {
+ BrowserTestUtils.loadURIString(browser, page);
+ await BrowserTestUtils.browserLoaded(browser, false, page, true);
+ await SpecialPowers.spawn(browser, [page], async function (innerPage) {
+ ok(
+ content.document.documentURI.startsWith(
+ "about:neterror?e=blockedByPolicy"
+ ),
+ content.document.documentURI +
+ " should start with about:neterror?e=blockedByPolicy"
+ );
+ });
+ });
+}
diff --git a/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_cookie_settings.js b/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_cookie_settings.js
new file mode 100644
index 0000000000..f43500d723
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_cookie_settings.js
@@ -0,0 +1,323 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const { UrlClassifierTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/UrlClassifierTestUtils.sys.mjs"
+);
+Services.cookies.QueryInterface(Ci.nsICookieService);
+
+function restore_prefs() {
+ // Bug 1617611: Fix all the tests broken by "cookies SameSite=lax by default"
+ Services.prefs.clearUserPref("network.cookie.sameSite.laxByDefault");
+ Services.prefs.clearUserPref("network.cookie.cookieBehavior");
+ Services.prefs.clearUserPref(
+ "network.cookieJarSettings.unblocked_for_testing"
+ );
+ Services.prefs.clearUserPref(
+ "network.cookie.rejectForeignWithExceptions.enabled"
+ );
+}
+
+registerCleanupFunction(restore_prefs);
+
+async function fake_profile_change() {
+ await new Promise(resolve => {
+ Services.obs.addObserver(function waitForDBClose() {
+ Services.obs.removeObserver(waitForDBClose, "cookie-db-closed");
+ resolve();
+ }, "cookie-db-closed");
+ Services.cookies
+ .QueryInterface(Ci.nsIObserver)
+ .observe(null, "profile-before-change", "shutdown-persist");
+ });
+ await new Promise(resolve => {
+ Services.obs.addObserver(function waitForDBOpen() {
+ Services.obs.removeObserver(waitForDBOpen, "cookie-db-read");
+ resolve();
+ }, "cookie-db-read");
+ Services.cookies
+ .QueryInterface(Ci.nsIObserver)
+ .observe(null, "profile-do-change", "");
+ });
+}
+
+async function test_cookie_settings({
+ cookiesEnabled,
+ thirdPartyCookiesEnabled,
+ cookieJarSettingsLocked,
+}) {
+ let firstPartyURI = NetUtil.newURI("https://example.com/");
+ let thirdPartyURI = NetUtil.newURI("https://example.org/");
+ let channel = NetUtil.newChannel({
+ uri: firstPartyURI,
+ loadUsingSystemPrincipal: true,
+ });
+ channel.QueryInterface(
+ Ci.nsIHttpChannelInternal
+ ).forceAllowThirdPartyCookie = true;
+ Services.cookies.removeAll();
+ Services.cookies.setCookieStringFromHttp(firstPartyURI, "key=value", channel);
+ Services.cookies.setCookieStringFromHttp(thirdPartyURI, "key=value", channel);
+
+ let expectedFirstPartyCookies = 1;
+ let expectedThirdPartyCookies = 1;
+ if (!cookiesEnabled) {
+ expectedFirstPartyCookies = 0;
+ }
+ if (!cookiesEnabled || !thirdPartyCookiesEnabled) {
+ expectedThirdPartyCookies = 0;
+ }
+ is(
+ Services.cookies.countCookiesFromHost(firstPartyURI.host),
+ expectedFirstPartyCookies,
+ "Number of first-party cookies should match expected"
+ );
+ is(
+ Services.cookies.countCookiesFromHost(thirdPartyURI.host),
+ expectedThirdPartyCookies,
+ "Number of third-party cookies should match expected"
+ );
+
+ // Add a cookie so we can check if it persists past the end of the session
+ // but, first remove existing cookies set by this host to put us in a known state
+ Services.cookies.removeAll();
+ Services.cookies.setCookieStringFromHttp(
+ firstPartyURI,
+ "key=value; max-age=1000",
+ channel
+ );
+
+ await fake_profile_change();
+
+ // Now check if the cookie persisted or not
+ let expectedCookieCount = 1;
+ if (!cookiesEnabled) {
+ expectedCookieCount = 0;
+ }
+ is(
+ Services.cookies.countCookiesFromHost(firstPartyURI.host),
+ expectedCookieCount,
+ "Number of cookies was not what expected after restarting session"
+ );
+
+ is(
+ Services.prefs.prefIsLocked("network.cookie.cookieBehavior"),
+ cookieJarSettingsLocked,
+ "Cookie behavior pref lock status should be what is expected"
+ );
+
+ window.openPreferencesTab("panePrivacy");
+ await BrowserTestUtils.browserLoaded(
+ window.preferencesTabType.tab.browser,
+ undefined,
+ url => url.startsWith("about:preferences")
+ );
+ let { contentDocument } = window.preferencesTabType.tab.browser;
+ await TestUtils.waitForCondition(() =>
+ contentDocument.getElementById("acceptCookies")
+ );
+ let expectControlsDisabled = !cookiesEnabled || cookieJarSettingsLocked;
+
+ for (let id of ["acceptCookies", "showCookiesButton"]) {
+ is(
+ contentDocument.getElementById(id).disabled,
+ cookieJarSettingsLocked,
+ `#${id} disabled status should match expected`
+ );
+ }
+ for (let id of ["acceptThirdPartyMenu"]) {
+ is(
+ contentDocument.getElementById(id).disabled,
+ expectControlsDisabled,
+ `#${id} disabled status should match expected`
+ );
+ }
+
+ is(
+ contentDocument.getElementById("cookieExceptions").disabled,
+ cookieJarSettingsLocked,
+ "#cookieExceptions disabled status should matched expected"
+ );
+
+ let tabmail = document.getElementById("tabmail");
+ tabmail.closeTab(window.preferencesTabType.tab);
+}
+
+add_task(async function prepare_tracker_tables() {
+ await UrlClassifierTestUtils.addTestTrackers();
+});
+
+add_task(async function test_initial_state() {
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+ Services.prefs.setBoolPref(
+ "network.cookie.rejectForeignWithExceptions.enabled",
+ false
+ );
+ Services.prefs.setBoolPref("network.cookie.sameSite.laxByDefault", false);
+ await test_cookie_settings({
+ cookiesEnabled: true,
+ thirdPartyCookiesEnabled: true,
+ cookieJarSettingsLocked: false,
+ });
+ restore_prefs();
+});
+
+add_task(async function test_undefined_unlocked() {
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 3);
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+ Services.prefs.setBoolPref(
+ "network.cookie.rejectForeignWithExceptions.enabled",
+ false
+ );
+ await setupPolicyEngineWithJson({
+ policies: {
+ Cookies: {},
+ },
+ });
+ is(
+ Services.prefs.getIntPref("network.cookie.cookieBehavior", undefined),
+ 3,
+ "An empty cookie policy should not have changed the cookieBehavior preference"
+ );
+ restore_prefs();
+});
+
+add_task(async function test_disabled() {
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+ Services.prefs.setBoolPref(
+ "network.cookie.rejectForeignWithExceptions.enabled",
+ false
+ );
+ await setupPolicyEngineWithJson({
+ policies: {
+ Cookies: {
+ Default: false,
+ },
+ },
+ });
+
+ await test_cookie_settings({
+ cookiesEnabled: false,
+ thirdPartyCookiesEnabled: true,
+ cookieJarSettingsLocked: false,
+ });
+ restore_prefs();
+});
+
+add_task(async function test_third_party_disabled() {
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+ Services.prefs.setBoolPref(
+ "network.cookie.rejectForeignWithExceptions.enabled",
+ false
+ );
+ await setupPolicyEngineWithJson({
+ policies: {
+ Cookies: {
+ AcceptThirdParty: "never",
+ },
+ },
+ });
+
+ await test_cookie_settings({
+ cookiesEnabled: true,
+ thirdPartyCookiesEnabled: false,
+ cookieJarSettingsLocked: false,
+ });
+ restore_prefs();
+});
+
+add_task(async function test_disabled_and_third_party_disabled() {
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+ Services.prefs.setBoolPref(
+ "network.cookie.rejectForeignWithExceptions.enabled",
+ false
+ );
+ await setupPolicyEngineWithJson({
+ policies: {
+ Cookies: {
+ Default: false,
+ AcceptThirdParty: "never",
+ },
+ },
+ });
+
+ await test_cookie_settings({
+ cookiesEnabled: false,
+ thirdPartyCookiesEnabled: false,
+ cookieJarSettingsLocked: false,
+ });
+ restore_prefs();
+});
+
+add_task(async function test_disabled_and_third_party_disabled_locked() {
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+ Services.prefs.setBoolPref(
+ "network.cookie.rejectForeignWithExceptions.enabled",
+ false
+ );
+ await setupPolicyEngineWithJson({
+ policies: {
+ Cookies: {
+ Default: false,
+ AcceptThirdParty: "never",
+ Locked: true,
+ },
+ },
+ });
+
+ await test_cookie_settings({
+ cookiesEnabled: false,
+ thirdPartyCookiesEnabled: false,
+ cookieJarSettingsLocked: true,
+ });
+ restore_prefs();
+});
+
+add_task(async function test_undefined_locked() {
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+ Services.prefs.setBoolPref(
+ "network.cookie.rejectForeignWithExceptions.enabled",
+ false
+ );
+ Services.prefs.setBoolPref("network.cookie.sameSite.laxByDefault", false);
+ await setupPolicyEngineWithJson({
+ policies: {
+ Cookies: {
+ Locked: true,
+ },
+ },
+ });
+
+ await test_cookie_settings({
+ cookiesEnabled: true,
+ thirdPartyCookiesEnabled: true,
+ cookieJarSettingsLocked: true,
+ });
+ restore_prefs();
+});
+
+add_task(async function prepare_tracker_tables() {
+ await UrlClassifierTestUtils.cleanupTestTrackers();
+});
diff --git a/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_disable_masterpassword.js b/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_disable_masterpassword.js
new file mode 100644
index 0000000000..b69787422e
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_disable_masterpassword.js
@@ -0,0 +1,90 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const MASTER_PASSWORD = "omgsecret!";
+const mpToken = Cc["@mozilla.org/security/pk11tokendb;1"]
+ .getService(Ci.nsIPK11TokenDB)
+ .getInternalKeyToken();
+
+async function checkDeviceManager({ buttonIsDisabled }) {
+ let deviceManagerWindow = window.openDialog(
+ "chrome://pippki/content/device_manager.xhtml",
+ "",
+ ""
+ );
+ await BrowserTestUtils.waitForEvent(deviceManagerWindow, "load");
+
+ let tree = deviceManagerWindow.document.getElementById("device_tree");
+ ok(tree, "The device tree exists");
+
+ // Find and select the item related to the internal key token
+ for (let i = 0; i < tree.view.rowCount; i++) {
+ tree.view.selection.select(i);
+
+ try {
+ let selected_token = deviceManagerWindow.selected_slot.getToken();
+ if (selected_token.isInternalKeyToken) {
+ break;
+ }
+ } catch (e) {}
+ }
+
+ // Check to see if the button was updated correctly
+ let changePwButton =
+ deviceManagerWindow.document.getElementById("change_pw_button");
+ is(
+ changePwButton.getAttribute("disabled") == "true",
+ buttonIsDisabled,
+ "Change Password button is in the correct state: " + buttonIsDisabled
+ );
+
+ await BrowserTestUtils.closeWindow(deviceManagerWindow);
+}
+
+async function checkAboutPreferences({ checkboxIsDisabled }) {
+ await BrowserTestUtils.withNewTab(
+ "about:preferences#privacy",
+ async browser => {
+ is(
+ browser.contentDocument.getElementById("useMasterPassword").disabled,
+ checkboxIsDisabled,
+ "Primary Password checkbox is in the correct state: " +
+ checkboxIsDisabled
+ );
+ }
+ );
+}
+
+add_task(async function test_policy_disable_masterpassword() {
+ ok(!mpToken.hasPassword, "Starting the test with no password");
+
+ // No password and no policy: access to setting a primary password
+ // should be enabled.
+ await checkDeviceManager({ buttonIsDisabled: false });
+ await checkAboutPreferences({ checkboxIsDisabled: false });
+
+ await setupPolicyEngineWithJson({
+ policies: {
+ DisableMasterPasswordCreation: true,
+ },
+ });
+
+ // With the `DisableMasterPasswordCreation: true` policy active, the
+ // UI entry points for creating a Primary Password should be disabled.
+ await checkDeviceManager({ buttonIsDisabled: true });
+ await checkAboutPreferences({ checkboxIsDisabled: true });
+
+ mpToken.changePassword("", MASTER_PASSWORD);
+ ok(mpToken.hasPassword, "Master password was set");
+
+ // If a Primary Password is already set, there's no point in disabling
+ // the
+ await checkDeviceManager({ buttonIsDisabled: false });
+ await checkAboutPreferences({ checkboxIsDisabled: false });
+
+ // Clean up
+ mpToken.changePassword(MASTER_PASSWORD, "");
+ ok(!mpToken.hasPassword, "Master password was cleaned up");
+});
diff --git a/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_disable_safemode.js b/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_disable_safemode.js
new file mode 100644
index 0000000000..3ce719b5f1
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_disable_safemode.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+add_setup(async function () {
+ await setupPolicyEngineWithJson({
+ policies: {
+ DisableSafeMode: true,
+ },
+ });
+});
+
+add_task(async function test_help_menu() {
+ buildHelpMenu();
+ let safeModeMenu = document.getElementById("helpTroubleshootMode");
+ is(
+ safeModeMenu.getAttribute("disabled"),
+ "true",
+ "The `Restart with Add-ons Disabled...` item should be disabled"
+ );
+ let safeModeAppMenu = document.getElementById("appmenu_troubleshootMode");
+ is(
+ safeModeAppMenu.getAttribute("disabled"),
+ "true",
+ "The `Restart with Add-ons Disabled...` appmenu item should be disabled"
+ );
+});
+
+add_task(async function test_safemode_from_about_support() {
+ await withNewTab({ url: "about:support" }, browser => {
+ let button = content.document.getElementById("restart-in-safe-mode-button");
+ is(
+ button.getAttribute("disabled"),
+ "true",
+ "The `Restart with Add-ons Disabled...` button should be disabled"
+ );
+ });
+});
+
+add_task(async function test_safemode_from_about_profiles() {
+ await withNewTab({ url: "about:profiles" }, browser => {
+ let button = content.document.getElementById("restart-in-safe-mode-button");
+ is(
+ button.getAttribute("disabled"),
+ "true",
+ "The `Restart with Add-ons Disabled...` button should be disabled"
+ );
+ });
+});
diff --git a/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_disable_telemetry.js b/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_disable_telemetry.js
new file mode 100644
index 0000000000..600be47763
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_disable_telemetry.js
@@ -0,0 +1,21 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_policy_disable_telemetry() {
+ const { TelemetryReportingPolicy } = ChromeUtils.importESModule(
+ "resource://gre/modules/TelemetryReportingPolicy.sys.mjs"
+ );
+
+ ok(TelemetryReportingPolicy, "TelemetryReportingPolicy exists");
+ is(TelemetryReportingPolicy.canUpload(), true, "Telemetry is enabled");
+
+ await setupPolicyEngineWithJson({
+ policies: {
+ DisableTelemetry: true,
+ },
+ });
+
+ is(TelemetryReportingPolicy.canUpload(), false, "Telemetry is disabled");
+});
diff --git a/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_downloads.js b/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_downloads.js
new file mode 100644
index 0000000000..520fcc67aa
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_downloads.js
@@ -0,0 +1,147 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
+});
+
+add_task(async function test_defaultdownload() {
+ await setupPolicyEngineWithJson({
+ policies: {
+ DefaultDownloadDirectory: "${home}/Downloads",
+ PromptForDownloadLocation: false,
+ },
+ });
+
+ window.openPreferencesTab("paneGeneral");
+ await BrowserTestUtils.browserLoaded(
+ window.preferencesTabType.tab.browser,
+ undefined,
+ url => url.startsWith("about:preferences")
+ );
+ let { contentDocument } = window.preferencesTabType.tab.browser;
+ await TestUtils.waitForCondition(() =>
+ contentDocument.getElementById("alwaysAsk")
+ );
+ await new Promise(resolve =>
+ window.preferencesTabType.tab.browser.contentWindow.setTimeout(resolve)
+ );
+ is(
+ window.preferencesTabType.tab.browser.contentDocument.getElementById(
+ "alwaysAsk"
+ ).disabled,
+ true,
+ "alwaysAsk should be disabled."
+ );
+ is(
+ window.preferencesTabType.tab.browser.contentDocument.getElementById(
+ "saveTo"
+ ).selected,
+ true,
+ "saveTo should be selected."
+ );
+ is(
+ window.preferencesTabType.tab.browser.contentDocument.getElementById(
+ "saveTo"
+ ).disabled,
+ true,
+ "saveTo should be disabled."
+ );
+ let home = FileUtils.getFile("Home", []).path;
+ is(
+ Services.prefs.getStringPref("browser.download.dir"),
+ home + "/Downloads",
+ "browser.download.dir should be ${home}/Downloads."
+ );
+ is(
+ Services.prefs.getBoolPref("browser.download.useDownloadDir"),
+ true,
+ "browser.download.useDownloadDir should be true."
+ );
+ is(
+ Services.prefs.prefIsLocked("browser.download.useDownloadDir"),
+ true,
+ "browser.download.useDownloadDir should be locked."
+ );
+
+ let tabmail = document.getElementById("tabmail");
+ tabmail.closeTab(window.preferencesTabType.tab);
+});
+
+add_task(async function test_download() {
+ await setupPolicyEngineWithJson({
+ policies: {
+ DownloadDirectory: "${home}/Documents",
+ },
+ });
+
+ window.openPreferencesTab("paneGeneral");
+ await BrowserTestUtils.browserLoaded(
+ window.preferencesTabType.tab.browser,
+ undefined,
+ url => url.startsWith("about:preferences")
+ );
+ let { contentDocument } = window.preferencesTabType.tab.browser;
+ await TestUtils.waitForCondition(() =>
+ contentDocument.getElementById("alwaysAsk")
+ );
+ await new Promise(resolve =>
+ window.preferencesTabType.tab.browser.contentWindow.setTimeout(resolve)
+ );
+ is(
+ window.preferencesTabType.tab.browser.contentDocument.getElementById(
+ "alwaysAsk"
+ ).disabled,
+ true,
+ "alwaysAsk should be disabled."
+ );
+ is(
+ window.preferencesTabType.tab.browser.contentDocument.getElementById(
+ "saveTo"
+ ).selected,
+ true,
+ "saveTo should be selected."
+ );
+ is(
+ window.preferencesTabType.tab.browser.contentDocument.getElementById(
+ "saveTo"
+ ).disabled,
+ true,
+ "saveTo should be disabled."
+ );
+ is(
+ window.preferencesTabType.tab.browser.contentDocument.getElementById(
+ "downloadFolder"
+ ).disabled,
+ true,
+ "downloadFolder should be disabled."
+ );
+ is(
+ window.preferencesTabType.tab.browser.contentDocument.getElementById(
+ "chooseFolder"
+ ).disabled,
+ true,
+ "chooseFolder should be disabled."
+ );
+ let home = FileUtils.getFile("Home", []).path;
+ is(
+ Services.prefs.getStringPref("browser.download.dir"),
+ home + "/Documents",
+ "browser.download.dir should be ${home}/Documents."
+ );
+ is(
+ Services.prefs.getBoolPref("browser.download.useDownloadDir"),
+ true,
+ "browser.download.useDownloadDir should be true."
+ );
+ is(
+ Services.prefs.prefIsLocked("browser.download.useDownloadDir"),
+ true,
+ "browser.download.useDownloadDir should be locked."
+ );
+
+ let tabmail = document.getElementById("tabmail");
+ tabmail.closeTab(window.preferencesTabType.tab);
+});
diff --git a/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_extensions.js b/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_extensions.js
new file mode 100644
index 0000000000..062fa2b714
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_extensions.js
@@ -0,0 +1,120 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const ADDON_ID = "policytest@mozilla.com";
+const BASE_URL =
+ "http://mochi.test:8888/browser/comm/mail/components/enterprisepolicies/tests/browser";
+
+async function isExtensionLocked(win, addonID) {
+ let addonCard = await BrowserTestUtils.waitForCondition(() => {
+ return win.document.querySelector(`addon-card[addon-id="${addonID}"]`);
+ }, `Get addon-card for "${addonID}"`);
+ let disableBtn = addonCard.querySelector('[action="toggle-disabled"]');
+ let removeBtn = addonCard.querySelector('panel-item[action="remove"]');
+ ok(removeBtn.disabled, "Remove button should be disabled");
+ ok(disableBtn.hidden, "Disable button should be hidden");
+}
+
+add_task(async function test_addon_install() {
+ let installPromise = waitForAddonInstall(ADDON_ID);
+ await setupPolicyEngineWithJson({
+ policies: {
+ Extensions: {
+ Install: [`${BASE_URL}/policytest_v0.1.xpi`],
+ Locked: [ADDON_ID],
+ },
+ },
+ });
+ await installPromise;
+ let addon = await AddonManager.getAddonByID(ADDON_ID);
+ isnot(addon, null, "Addon not installed.");
+ is(addon.version, "0.1", "Addon version is correct");
+
+ Assert.deepEqual(
+ addon.installTelemetryInfo,
+ { source: "enterprise-policy" },
+ "Got the expected addon.installTelemetryInfo"
+ );
+});
+
+add_task(async function test_addon_locked() {
+ let tabmail = document.getElementById("tabmail");
+ let index = tabmail.tabInfo.length;
+ await window.openAddonsMgr("addons://list/extension");
+ let tab = tabmail.tabInfo[index];
+ let browser = tab.browser;
+
+ await isExtensionLocked(browser.contentWindow, ADDON_ID);
+
+ tabmail.closeTab(tab);
+});
+
+add_task(async function test_addon_reinstall() {
+ // Test that uninstalling and reinstalling the same addon ID works as expected.
+ // This can be used to update an addon.
+
+ let uninstallPromise = waitForAddonUninstall(ADDON_ID);
+ let installPromise = waitForAddonInstall(ADDON_ID);
+ await setupPolicyEngineWithJson({
+ policies: {
+ Extensions: {
+ Uninstall: [ADDON_ID],
+ Install: [`${BASE_URL}/policytest_v0.2.xpi`],
+ },
+ },
+ });
+
+ // Older version was uninstalled
+ await uninstallPromise;
+
+ // New version was installed
+ await installPromise;
+
+ let addon = await AddonManager.getAddonByID(ADDON_ID);
+ isnot(
+ addon,
+ null,
+ "Addon still exists because the policy was used to update it."
+ );
+ is(addon.version, "0.2", "New version is correct");
+});
+
+add_task(async function test_addon_uninstall() {
+ EnterprisePolicyTesting.resetRunOnceState();
+
+ let uninstallPromise = waitForAddonUninstall(ADDON_ID);
+ await setupPolicyEngineWithJson({
+ policies: {
+ Extensions: {
+ Uninstall: [ADDON_ID],
+ },
+ },
+ });
+ await uninstallPromise;
+ let addon = await AddonManager.getAddonByID(ADDON_ID);
+ is(addon, null, "Addon should be uninstalled.");
+});
+
+add_task(async function test_addon_download_failure() {
+ // Test that if the download fails, the runOnce pref
+ // is cleared so that the download will happen again.
+
+ let installPromise = waitForAddonInstall(ADDON_ID);
+ await setupPolicyEngineWithJson({
+ policies: {
+ Extensions: {
+ Install: [`${BASE_URL}/policytest_invalid.xpi`],
+ },
+ },
+ });
+
+ await installPromise;
+ is(
+ Services.prefs.prefHasUserValue(
+ "browser.policies.runOncePerModification.extensionsInstall"
+ ),
+ false,
+ "runOnce pref should be unset"
+ );
+});
diff --git a/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_extensionsettings.js b/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_extensionsettings.js
new file mode 100644
index 0000000000..7fced9c1ad
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_extensionsettings.js
@@ -0,0 +1,261 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/* eslint-disable @microsoft/sdl/no-insecure-url */
+
+const BASE_URL =
+ "http://mochi.test:8888/browser/comm/mail/components/enterprisepolicies/tests/browser/";
+
+async function openTab(url) {
+ let tab = window.openContentTab(url, null, null);
+ if (
+ tab.browser.webProgress?.isLoadingDocument ||
+ tab.browser.currentURI?.spec == "about:blank"
+ ) {
+ await BrowserTestUtils.browserLoaded(tab.browser);
+ }
+ return tab;
+}
+
+/**
+ * Wait for the given PopupNotification to display
+ *
+ * @param {string} name
+ * The name of the notification to wait for.
+ *
+ * @returns {Promise}
+ * Resolves with the notification window.
+ */
+function promisePopupNotificationShown(name) {
+ return new Promise(resolve => {
+ function popupshown() {
+ let notification = PopupNotifications.getNotification(name);
+ if (!notification) {
+ return;
+ }
+
+ ok(notification, `${name} notification shown`);
+ ok(PopupNotifications.isPanelOpen, "notification panel open");
+
+ PopupNotifications.panel.removeEventListener("popupshown", popupshown);
+ resolve(PopupNotifications.panel.firstElementChild);
+ }
+
+ PopupNotifications.panel.addEventListener("popupshown", popupshown);
+ });
+}
+
+function dismissNotification(win = window) {
+ return new Promise(resolve => {
+ function popuphidden() {
+ PopupNotifications.panel.removeEventListener("popuphidden", popuphidden);
+ resolve();
+ }
+ PopupNotifications.panel.addEventListener("popuphidden", popuphidden);
+ executeSoon(function () {
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, win);
+ });
+ });
+}
+
+add_setup(async function setupTestEnvironment() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["extensions.InstallTrigger.enabled", true],
+ ["extensions.InstallTriggerImpl.enabled", true],
+ // Relax the user input requirements while running this test.
+ ["xpinstall.userActivation.required", false],
+ ],
+ });
+});
+
+add_task(async function test_install_source_blocked_link() {
+ await setupPolicyEngineWithJson({
+ policies: {
+ ExtensionSettings: {
+ "*": {
+ install_sources: ["http://blocks.other.install.sources/*"],
+ },
+ },
+ },
+ });
+ let popupPromise = promisePopupNotificationShown(
+ "addon-install-policy-blocked"
+ );
+ let tab = await openTab(`${BASE_URL}extensionsettings.html`);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
+ content.document.getElementById("policytest").click();
+ });
+ await popupPromise;
+ await dismissNotification();
+ document.getElementById("tabmail").closeTab(tab);
+});
+
+add_task(async function test_install_source_blocked_installtrigger() {
+ await setupPolicyEngineWithJson({
+ policies: {
+ ExtensionSettings: {
+ "*": {
+ install_sources: ["http://blocks.other.install.sources/*"],
+ blocked_install_message: "blocked_install_message",
+ },
+ },
+ },
+ });
+ let popupPromise = promisePopupNotificationShown(
+ "addon-install-policy-blocked"
+ );
+ let tab = await openTab(`${BASE_URL}extensionsettings.html`);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
+ content.document.getElementById("policytest_installtrigger").click();
+ });
+ let popup = await popupPromise;
+ let description = popup.querySelector(".popup-notification-description");
+ ok(
+ description.textContent.endsWith("blocked_install_message"),
+ "Custom install message present"
+ );
+ await dismissNotification();
+ document.getElementById("tabmail").closeTab(tab);
+});
+
+add_task(async function test_install_source_blocked_otherdomain() {
+ await setupPolicyEngineWithJson({
+ policies: {
+ ExtensionSettings: {
+ "*": {
+ install_sources: ["http://mochi.test/*"],
+ },
+ },
+ },
+ });
+ let popupPromise = promisePopupNotificationShown(
+ "addon-install-policy-blocked"
+ );
+ let tab = await openTab(`${BASE_URL}extensionsettings.html`);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
+ content.document.getElementById("policytest_otherdomain").click();
+ });
+ await popupPromise;
+ await dismissNotification();
+ document.getElementById("tabmail").closeTab(tab);
+});
+
+add_task(async function test_install_source_blocked_direct() {
+ await setupPolicyEngineWithJson({
+ policies: {
+ ExtensionSettings: {
+ "*": {
+ install_sources: ["http://blocks.other.install.sources/*"],
+ },
+ },
+ },
+ });
+ let popupPromise = promisePopupNotificationShown(
+ "addon-install-policy-blocked"
+ );
+ let tab = await openTab(`${BASE_URL}extensionsettings.html`);
+
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [{ baseUrl: BASE_URL }],
+ async function ({ baseUrl }) {
+ content.document.location.href = baseUrl + "policytest_v0.1.xpi";
+ }
+ );
+ await popupPromise;
+ await dismissNotification();
+ document.getElementById("tabmail").closeTab(tab);
+});
+
+add_task(async function test_install_source_allowed_link() {
+ await setupPolicyEngineWithJson({
+ policies: {
+ ExtensionSettings: {
+ "*": {
+ install_sources: ["http://mochi.test/*"],
+ },
+ },
+ },
+ });
+ let popupPromise = promisePopupNotificationShown("addon-webext-permissions");
+ let tab = await openTab(`${BASE_URL}extensionsettings.html`);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
+ content.document.getElementById("policytest").click();
+ });
+ await popupPromise;
+ await dismissNotification();
+ document.getElementById("tabmail").closeTab(tab);
+});
+
+add_task(async function test_install_source_allowed_installtrigger() {
+ await setupPolicyEngineWithJson({
+ policies: {
+ ExtensionSettings: {
+ "*": {
+ install_sources: ["http://mochi.test/*"],
+ },
+ },
+ },
+ });
+ let popupPromise = promisePopupNotificationShown("addon-webext-permissions");
+ let tab = await openTab(`${BASE_URL}extensionsettings.html`);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
+ content.document.getElementById("policytest_installtrigger").click();
+ });
+ await popupPromise;
+ await dismissNotification();
+ document.getElementById("tabmail").closeTab(tab);
+});
+
+add_task(async function test_install_source_allowed_otherdomain() {
+ await setupPolicyEngineWithJson({
+ policies: {
+ ExtensionSettings: {
+ "*": {
+ install_sources: ["http://mochi.test/*", "http://example.org/*"],
+ },
+ },
+ },
+ });
+ let popupPromise = promisePopupNotificationShown("addon-webext-permissions");
+ let tab = await openTab(`${BASE_URL}extensionsettings.html`);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
+ content.document.getElementById("policytest_otherdomain").click();
+ });
+ await popupPromise;
+ await dismissNotification();
+ document.getElementById("tabmail").closeTab(tab);
+});
+
+add_task(async function test_install_source_allowed_direct() {
+ await setupPolicyEngineWithJson({
+ policies: {
+ ExtensionSettings: {
+ "*": {
+ install_sources: ["http://mochi.test/*"],
+ },
+ },
+ },
+ });
+ let popupPromise = promisePopupNotificationShown("addon-webext-permissions");
+ let tab = await openTab(`${BASE_URL}extensionsettings.html`);
+
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [{ baseUrl: BASE_URL }],
+ async function ({ baseUrl }) {
+ content.document.location.href = baseUrl + "policytest_v0.1.xpi";
+ }
+ );
+ await popupPromise;
+ await dismissNotification();
+ document.getElementById("tabmail").closeTab(tab);
+});
diff --git a/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_extensionsettings2.js b/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_extensionsettings2.js
new file mode 100644
index 0000000000..e335d70fe0
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_extensionsettings2.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const ADDON_ID = "policytest@mozilla.com";
+const BASE_URL =
+ "http://mochi.test:8888/browser/comm/mail/components/enterprisepolicies/tests/browser";
+
+async function isExtensionLockedAndUpdateDisabled(win, addonID) {
+ let addonCard = await BrowserTestUtils.waitForCondition(() => {
+ return win.document.querySelector(`addon-card[addon-id="${addonID}"]`);
+ }, `Get addon-card for "${addonID}"`);
+ let disableBtn = addonCard.querySelector('[action="toggle-disabled"]');
+ let removeBtn = addonCard.querySelector('panel-item[action="remove"]');
+ ok(removeBtn.disabled, "Remove button should be disabled");
+ ok(disableBtn.hidden, "Disable button should be hidden");
+ let updateRow = addonCard.querySelector(".addon-detail-row-updates");
+ is(updateRow.hidden, true, "Update row should be hidden");
+}
+
+add_task(async function test_addon_install() {
+ let installPromise = waitForAddonInstall(ADDON_ID);
+ await setupPolicyEngineWithJson({
+ policies: {
+ ExtensionSettings: {
+ "policytest@mozilla.com": {
+ install_url: `${BASE_URL}/policytest_v0.1.xpi`,
+ installation_mode: "force_installed",
+ updates_disabled: true,
+ },
+ },
+ },
+ });
+ await installPromise;
+ let addon = await AddonManager.getAddonByID(ADDON_ID);
+ isnot(addon, null, "Addon not installed.");
+ is(addon.version, "0.1", "Addon version is correct");
+
+ Assert.deepEqual(
+ addon.installTelemetryInfo,
+ { source: "enterprise-policy" },
+ "Got the expected addon.installTelemetryInfo"
+ );
+});
+
+add_task(async function test_addon_locked_update_disabled() {
+ let tabmail = document.getElementById("tabmail");
+ let index = tabmail.tabInfo.length;
+ await window.openAddonsMgr("addons://detail/" + encodeURIComponent(ADDON_ID));
+ let tab = tabmail.tabInfo[index];
+ let browser = tab.browser;
+ let win = browser.contentWindow;
+
+ await isExtensionLockedAndUpdateDisabled(win, ADDON_ID);
+
+ tabmail.closeTab(tab);
+});
+
+add_task(async function test_addon_uninstall() {
+ let uninstallPromise = waitForAddonUninstall(ADDON_ID);
+ await setupPolicyEngineWithJson({
+ policies: {
+ ExtensionSettings: {
+ "policytest@mozilla.com": {
+ installation_mode: "blocked",
+ },
+ },
+ },
+ });
+ await uninstallPromise;
+ let addon = await AddonManager.getAddonByID(ADDON_ID);
+ is(addon, null, "Addon should be uninstalled.");
+});
diff --git a/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_handlers.js b/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_handlers.js
new file mode 100644
index 0000000000..6242625756
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_handlers.js
@@ -0,0 +1,183 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const gMIMEService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+
+const gExternalProtocolService = Cc[
+ "@mozilla.org/uriloader/external-protocol-service;1"
+].getService(Ci.nsIExternalProtocolService);
+
+const gHandlerService = Cc[
+ "@mozilla.org/uriloader/handler-service;1"
+].getService(Ci.nsIHandlerService);
+
+// This seems odd, but for test purposes, this just has to be a file that we know exists,
+// and by using this file, we don't have to worry about different platforms.
+let exeFile = Services.dirsvc.get("XREExeF", Ci.nsIFile);
+
+add_task(async function test_valid_handlers() {
+ await setupPolicyEngineWithJson({
+ policies: {
+ Handlers: {
+ mimeTypes: {
+ "application/marimba": {
+ action: "useHelperApp",
+ ask: true,
+ handlers: [
+ {
+ name: "Launch",
+ path: exeFile.path,
+ },
+ ],
+ },
+ },
+ schemes: {
+ fake_scheme: {
+ action: "useHelperApp",
+ ask: false,
+ handlers: [
+ {
+ name: "Name",
+ uriTemplate: "https://www.example.org/?%s",
+ },
+ ],
+ },
+ },
+ extensions: {
+ txt: {
+ action: "saveToDisk",
+ ask: false,
+ },
+ },
+ },
+ },
+ });
+
+ let handlerInfo = gMIMEService.getFromTypeAndExtension(
+ "application/marimba",
+ ""
+ );
+ is(handlerInfo.preferredAction, handlerInfo.useHelperApp);
+ is(handlerInfo.alwaysAskBeforeHandling, true);
+ is(handlerInfo.preferredApplicationHandler.name, "Launch");
+ is(handlerInfo.preferredApplicationHandler.executable.path, exeFile.path);
+
+ handlerInfo.preferredApplicationHandler = null;
+ gHandlerService.store(handlerInfo);
+
+ handlerInfo = handlerInfo = gMIMEService.getFromTypeAndExtension(
+ "application/marimba",
+ ""
+ );
+ is(handlerInfo.preferredApplicationHandler, null);
+
+ gHandlerService.remove(handlerInfo);
+
+ handlerInfo = gExternalProtocolService.getProtocolHandlerInfo("fake_scheme");
+ is(handlerInfo.preferredAction, handlerInfo.useHelperApp);
+ is(handlerInfo.alwaysAskBeforeHandling, false);
+ is(handlerInfo.preferredApplicationHandler.name, "Name");
+ is(
+ handlerInfo.preferredApplicationHandler.uriTemplate,
+ "https://www.example.org/?%s"
+ );
+
+ handlerInfo.preferredApplicationHandler = null;
+ gHandlerService.store(handlerInfo);
+
+ handlerInfo = gExternalProtocolService.getProtocolHandlerInfo("fake_scheme");
+ is(handlerInfo.preferredApplicationHandler, null);
+
+ gHandlerService.remove(handlerInfo);
+
+ handlerInfo = gMIMEService.getFromTypeAndExtension("", "txt");
+ is(handlerInfo.preferredAction, handlerInfo.saveToDisk);
+ is(handlerInfo.alwaysAskBeforeHandling, false);
+
+ handlerInfo.preferredApplicationHandler = null;
+ gHandlerService.store(handlerInfo);
+ handlerInfo = gMIMEService.getFromTypeAndExtension("", "txt");
+ is(handlerInfo.preferredApplicationHandler, null);
+
+ gHandlerService.remove(handlerInfo);
+});
+
+add_task(async function test_no_handler() {
+ await setupPolicyEngineWithJson({
+ policies: {
+ Handlers: {
+ schemes: {
+ no_handler: {
+ action: "useHelperApp",
+ },
+ },
+ },
+ },
+ });
+
+ let handlerInfo =
+ gExternalProtocolService.getProtocolHandlerInfo("no_handler");
+ is(handlerInfo.preferredAction, handlerInfo.alwaysAsk);
+ is(handlerInfo.alwaysAskBeforeHandling, true);
+ is(handlerInfo.preferredApplicationHandler, null);
+
+ gHandlerService.remove(handlerInfo);
+});
+
+add_task(async function test_bad_web_handler1() {
+ await setupPolicyEngineWithJson({
+ policies: {
+ Handlers: {
+ schemes: {
+ bas_web_handler1: {
+ action: "useHelperApp",
+ handlers: [
+ {
+ name: "Name",
+ uriTemplate: "https://www.example.org/?%s",
+ },
+ ],
+ },
+ },
+ },
+ },
+ });
+
+ let handlerInfo =
+ gExternalProtocolService.getProtocolHandlerInfo("bad_web_handler1");
+ is(handlerInfo.preferredAction, handlerInfo.alwaysAsk);
+ is(handlerInfo.alwaysAskBeforeHandling, true);
+ is(handlerInfo.preferredApplicationHandler, null);
+
+ gHandlerService.remove(handlerInfo);
+});
+
+add_task(async function test_bad_web_handler2() {
+ await setupPolicyEngineWithJson({
+ policies: {
+ Handlers: {
+ schemes: {
+ bas_web_handler1: {
+ action: "useHelperApp",
+ handlers: [
+ {
+ name: "Name",
+ uriTemplate: "https://www.example.org/",
+ },
+ ],
+ },
+ },
+ },
+ },
+ });
+
+ let handlerInfo =
+ gExternalProtocolService.getProtocolHandlerInfo("bad_web_handler1");
+ is(handlerInfo.preferredAction, handlerInfo.alwaysAsk);
+ is(handlerInfo.alwaysAskBeforeHandling, true);
+ is(handlerInfo.preferredApplicationHandler, null);
+
+ gHandlerService.remove(handlerInfo);
+});
diff --git a/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_masterpassword.js b/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_masterpassword.js
new file mode 100644
index 0000000000..8320897341
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_masterpassword.js
@@ -0,0 +1,104 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let { LoginTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/LoginTestUtils.sys.mjs"
+);
+
+// Test that once a password is set, you can't unset it
+add_task(async function test_policy_masterpassword_set() {
+ await setupPolicyEngineWithJson({
+ policies: {
+ PrimaryPassword: true,
+ },
+ });
+
+ LoginTestUtils.primaryPassword.enable();
+
+ window.openPreferencesTab("panePrivacy");
+ await BrowserTestUtils.browserLoaded(
+ window.preferencesTabType.tab.browser,
+ undefined,
+ url => url.startsWith("about:preferences")
+ );
+ let { contentDocument } = window.preferencesTabType.tab.browser;
+ await TestUtils.waitForCondition(() =>
+ contentDocument.getElementById("useMasterPassword")
+ );
+
+ is(
+ contentDocument.getElementById("useMasterPassword").disabled,
+ true,
+ "Primary Password checkbox should be disabled"
+ );
+
+ let tabmail = document.getElementById("tabmail");
+ tabmail.closeTab(window.preferencesTabType.tab);
+
+ LoginTestUtils.primaryPassword.disable();
+});
+
+// Test that password can't be removed in changemp.xhtml
+add_task(async function test_policy_nochangemp() {
+ await setupPolicyEngineWithJson({
+ policies: {
+ PrimaryPassword: true,
+ },
+ });
+
+ LoginTestUtils.primaryPassword.enable();
+
+ let changeMPWindow = window.openDialog(
+ "chrome://mozapps/content/preferences/changemp.xhtml",
+ "",
+ ""
+ );
+ await BrowserTestUtils.waitForEvent(changeMPWindow, "load");
+
+ is(
+ changeMPWindow.document.getElementById("admin").hidden,
+ true,
+ "Admin message should not be visible because there is a password."
+ );
+
+ changeMPWindow.document.getElementById("oldpw").value =
+ LoginTestUtils.primaryPassword.masterPassword;
+
+ is(
+ changeMPWindow.document.getElementById("changemp").getButton("accept")
+ .disabled,
+ true,
+ "OK button should not be enabled if there is an old password."
+ );
+
+ await BrowserTestUtils.closeWindow(changeMPWindow);
+
+ LoginTestUtils.primaryPassword.disable();
+});
+
+// Test that admin message shows
+add_task(async function test_policy_admin() {
+ await setupPolicyEngineWithJson({
+ policies: {
+ PrimaryPassword: true,
+ },
+ });
+
+ let changeMPWindow = window.openDialog(
+ "chrome://mozapps/content/preferences/changemp.xhtml",
+ "",
+ ""
+ );
+ await BrowserTestUtils.waitForEvent(changeMPWindow, "load");
+
+ is(
+ changeMPWindow.document.getElementById("admin").hidden,
+ false,
+ true,
+ "Admin message should not be hidden because there is not a password."
+ );
+
+ await BrowserTestUtils.closeWindow(changeMPWindow);
+});
diff --git a/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_passwordmanager.js b/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_passwordmanager.js
new file mode 100644
index 0000000000..1f74d46754
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/browser/browser_policy_passwordmanager.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_pwmanagerbutton() {
+ await setupPolicyEngineWithJson({
+ policies: {
+ PasswordManagerEnabled: false,
+ },
+ });
+
+ window.openPreferencesTab("panePrivacy");
+ await BrowserTestUtils.browserLoaded(window.preferencesTabType.tab.browser);
+ await new Promise(resolve => setTimeout(resolve));
+
+ is(
+ window.preferencesTabType.tab.browser.contentDocument.getElementById(
+ "showPasswords"
+ ).disabled,
+ true,
+ "showPasswords should be disabled."
+ );
+
+ let tabmail = document.getElementById("tabmail");
+ tabmail.closeTab(window.preferencesTabType.tab);
+});
diff --git a/comm/mail/components/enterprisepolicies/tests/browser/disable_app_update/browser.ini b/comm/mail/components/enterprisepolicies/tests/browser/disable_app_update/browser.ini
new file mode 100644
index 0000000000..73162633db
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/browser/disable_app_update/browser.ini
@@ -0,0 +1,15 @@
+[DEFAULT]
+prefs =
+ mail.provider.suppress_dialog_on_startup=true
+ mail.spotlight.firstRunDone=true
+ mail.winsearch.firstRunDone=true
+ mailnews.start_page.override_url=about:blank
+ mailnews.start_page.url=about:blank
+ app.update.disabledForTesting=false
+ browser.policies.alternatePath='<test-root>/comm/mail/components/enterprisepolicies/tests/browser/disable_app_update/config_disable_app_update.json'
+subsuite = thunderbird
+support-files =
+ config_disable_app_update.json
+skip-if = os == 'win' && msix # Updater is disabled in MSIX builds
+
+[browser_policy_disable_app_update.js]
diff --git a/comm/mail/components/enterprisepolicies/tests/browser/disable_app_update/browser_policy_disable_app_update.js b/comm/mail/components/enterprisepolicies/tests/browser/disable_app_update/browser_policy_disable_app_update.js
new file mode 100644
index 0000000000..28dcd780d1
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/browser/disable_app_update/browser_policy_disable_app_update.js
@@ -0,0 +1,109 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+var updateService = Cc["@mozilla.org/updates/update-service;1"].getService(
+ Ci.nsIApplicationUpdateService
+);
+
+add_task(async function test_updates_post_policy() {
+ is(
+ Services.policies.isAllowed("appUpdate"),
+ false,
+ "appUpdate should be disabled by policy."
+ );
+
+ is(
+ updateService.canCheckForUpdates,
+ false,
+ "Should not be able to check for updates with DisableAppUpdate enabled."
+ );
+});
+
+add_task(async function test_update_preferences_ui() {
+ let tabmail = document.getElementById("tabmail");
+ let prefsTabMode = tabmail.tabModes.preferencesTab;
+
+ let prefsDocument = await new Promise(resolve => {
+ Services.obs.addObserver(function documentLoaded(subject) {
+ if (subject.URL == "about:preferences") {
+ Services.obs.removeObserver(documentLoaded, "chrome-document-loaded");
+ resolve(subject);
+ }
+ }, "chrome-document-loaded");
+ window.openPreferencesTab("paneGeneral", "updateApp");
+ });
+
+ await new Promise(resolve => setTimeout(resolve));
+
+ let setting = prefsDocument.getElementById("updateSettingsContainer");
+ is(
+ setting.hidden,
+ true,
+ "Update choices should be disabled when app update is locked by policy"
+ );
+
+ tabmail.closeTab(prefsTabMode.tabs[0]);
+});
+
+add_task(async function test_update_about_ui() {
+ let aboutDialog = await waitForAboutDialog();
+ let panelId = "policyDisabled";
+
+ await BrowserTestUtils.waitForCondition(
+ () =>
+ aboutDialog.gAppUpdater.selectedPanel &&
+ aboutDialog.gAppUpdater.selectedPanel.id == panelId,
+ 'Waiting for expected panel ID - expected "' + panelId + '"'
+ );
+ is(
+ aboutDialog.gAppUpdater.selectedPanel.id,
+ panelId,
+ "The About Dialog panel Id should equal " + panelId
+ );
+
+ // Make sure that we still remain on the "disabled by policy" panel after
+ // `AppUpdater.stop()` is called.
+ aboutDialog.gAppUpdater._appUpdater.stop();
+ is(
+ aboutDialog.gAppUpdater.selectedPanel.id,
+ panelId,
+ "The About Dialog panel Id should still equal " + panelId
+ );
+
+ aboutDialog.close();
+});
+
+/**
+ * Waits for the About Dialog to load.
+ *
+ * @returns A promise that returns the domWindow for the About Dialog and
+ * resolves when the About Dialog loads.
+ */
+function waitForAboutDialog() {
+ return new Promise(resolve => {
+ var listener = {
+ onOpenWindow: aAppWindow => {
+ Services.wm.removeListener(listener);
+
+ async function aboutDialogOnLoad() {
+ domwindow.removeEventListener("load", aboutDialogOnLoad, true);
+ let chromeURI = "chrome://messenger/content/aboutDialog.xhtml";
+ is(
+ domwindow.document.location.href,
+ chromeURI,
+ "About dialog appeared"
+ );
+ resolve(domwindow);
+ }
+
+ var domwindow = aAppWindow.docShell.domWindow;
+ domwindow.addEventListener("load", aboutDialogOnLoad, true);
+ },
+ onCloseWindow: aAppWindow => {},
+ };
+
+ Services.wm.addListener(listener);
+ openAboutDialog();
+ });
+}
diff --git a/comm/mail/components/enterprisepolicies/tests/browser/disable_app_update/config_disable_app_update.json b/comm/mail/components/enterprisepolicies/tests/browser/disable_app_update/config_disable_app_update.json
new file mode 100644
index 0000000000..f36622021f
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/browser/disable_app_update/config_disable_app_update.json
@@ -0,0 +1,5 @@
+{
+ "policies": {
+ "DisableAppUpdate": true
+ }
+}
diff --git a/comm/mail/components/enterprisepolicies/tests/browser/disable_developer_tools/browser.ini b/comm/mail/components/enterprisepolicies/tests/browser/disable_developer_tools/browser.ini
new file mode 100644
index 0000000000..b40dff605b
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/browser/disable_developer_tools/browser.ini
@@ -0,0 +1,13 @@
+[DEFAULT]
+prefs =
+ mail.provider.suppress_dialog_on_startup=true
+ mail.spotlight.firstRunDone=true
+ mail.winsearch.firstRunDone=true
+ mailnews.start_page.override_url=about:blank
+ mailnews.start_page.url=about:blank
+ browser.policies.alternatePath='<test-root>/comm/mail/components/enterprisepolicies/tests/browser/disable_developer_tools/config_disable_developer_tools.json'
+subsuite = thunderbird
+support-files =
+ config_disable_developer_tools.json
+
+[browser_policy_disable_developer_tools.js]
diff --git a/comm/mail/components/enterprisepolicies/tests/browser/disable_developer_tools/browser_policy_disable_developer_tools.js b/comm/mail/components/enterprisepolicies/tests/browser/disable_developer_tools/browser_policy_disable_developer_tools.js
new file mode 100644
index 0000000000..35ad87ab4d
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/browser/disable_developer_tools/browser_policy_disable_developer_tools.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_updates_post_policy() {
+ is(
+ Services.policies.isAllowed("devtools"),
+ false,
+ "devtools should be disabled by policy."
+ );
+
+ is(
+ Services.prefs.getBoolPref("devtools.policy.disabled"),
+ true,
+ "devtools dedicated disabled pref is set to true"
+ );
+
+ Services.prefs.setBoolPref("devtools.policy.disabled", false);
+
+ is(
+ Services.prefs.getBoolPref("devtools.policy.disabled"),
+ true,
+ "devtools dedicated disabled pref can not be updated"
+ );
+
+ await expectErrorPage("about:devtools-toolbox");
+ await expectErrorPage("about:debugging");
+
+ info("Check that devtools menu items are hidden");
+ let devtoolsMenu = window.document.getElementById("devtoolsMenu");
+ ok(devtoolsMenu.hidden, "The Web Developer item of the tools menu is hidden");
+});
+
+const expectErrorPage = async function (url) {
+ let tabmail = document.getElementById("tabmail");
+ let index = tabmail.tabInfo.length;
+ window.openContentTab("about:blank");
+ let tab = tabmail.tabInfo[index];
+ let browser = tab.browser;
+
+ BrowserTestUtils.loadURIString(browser, url);
+ await BrowserTestUtils.browserLoaded(browser, false, url, true);
+ await SpecialPowers.spawn(browser, [url], async function () {
+ ok(
+ content.document.documentURI.startsWith(
+ "about:neterror?e=blockedByPolicy"
+ ),
+ content.document.documentURI +
+ " should start with about:neterror?e=blockedByPolicy"
+ );
+ });
+
+ tabmail.closeTab(tab);
+};
diff --git a/comm/mail/components/enterprisepolicies/tests/browser/disable_developer_tools/config_disable_developer_tools.json b/comm/mail/components/enterprisepolicies/tests/browser/disable_developer_tools/config_disable_developer_tools.json
new file mode 100644
index 0000000000..08c393dec6
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/browser/disable_developer_tools/config_disable_developer_tools.json
@@ -0,0 +1,5 @@
+{
+ "policies": {
+ "DisableDeveloperTools": true
+ }
+}
diff --git a/comm/mail/components/enterprisepolicies/tests/browser/extensionsettings.html b/comm/mail/components/enterprisepolicies/tests/browser/extensionsettings.html
new file mode 100644
index 0000000000..a54c011968
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/browser/extensionsettings.html
@@ -0,0 +1,23 @@
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script type="text/javascript">
+function installTrigger(url) {
+ InstallTrigger.install({extension: url});
+}
+</script>
+</head>
+<body>
+<p>
+<a id="policytest" href="policytest_v0.1.xpi">policytest@mozilla.com</a>
+</p>
+<p>
+<a id="policytest_installtrigger" onclick="installTrigger(this.href);return false;" href="policytest_v0.1.xpi">policytest@mozilla.com</a>
+</p>
+<p>
+<a id="policytest_otherdomain" href="http://example.org:80/browser/comm/mail/components/enterprisepolicies/tests/browser/policytest_v0.1.xpi">policytest@mozilla.com</a>
+</p>
+</body>
+</html>
diff --git a/comm/mail/components/enterprisepolicies/tests/browser/hardware_acceleration/browser.ini b/comm/mail/components/enterprisepolicies/tests/browser/hardware_acceleration/browser.ini
new file mode 100644
index 0000000000..01e3e74e22
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/browser/hardware_acceleration/browser.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+prefs =
+ browser.policies.alternatePath='<test-root>/browser/components/enterprisepolicies/tests/browser/hardware_acceleration/disable_hardware_acceleration.json'
+ mail.provider.suppress_dialog_on_startup=true
+ mail.spotlight.firstRunDone=true
+ mail.winsearch.firstRunDone=true
+ mailnews.start_page.override_url=about:blank
+ mailnews.start_page.url=about:blank
+support-files =
+ disable_hardware_acceleration.json
+
+[browser_policy_hardware_acceleration.js]
diff --git a/comm/mail/components/enterprisepolicies/tests/browser/hardware_acceleration/browser_policy_hardware_acceleration.js b/comm/mail/components/enterprisepolicies/tests/browser/hardware_acceleration/browser_policy_hardware_acceleration.js
new file mode 100644
index 0000000000..59ca2a3631
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/browser/hardware_acceleration/browser_policy_hardware_acceleration.js
@@ -0,0 +1,9 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_policy_hardware_acceleration() {
+ let winUtils = Services.wm.getMostRecentWindow("").windowUtils;
+ is(winUtils.layerManagerType, "Basic", "Hardware acceleration disabled");
+});
diff --git a/comm/mail/components/enterprisepolicies/tests/browser/hardware_acceleration/disable_hardware_acceleration.json b/comm/mail/components/enterprisepolicies/tests/browser/hardware_acceleration/disable_hardware_acceleration.json
new file mode 100644
index 0000000000..acbdc0a3f4
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/browser/hardware_acceleration/disable_hardware_acceleration.json
@@ -0,0 +1,5 @@
+{
+ "policies": {
+ "HardwareAcceleration": false
+ }
+}
diff --git a/comm/mail/components/enterprisepolicies/tests/browser/head.js b/comm/mail/components/enterprisepolicies/tests/browser/head.js
new file mode 100644
index 0000000000..b557ea3d22
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/browser/head.js
@@ -0,0 +1,103 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { EnterprisePolicyTesting, PoliciesPrefTracker } =
+ ChromeUtils.importESModule(
+ "resource://testing-common/EnterprisePolicyTesting.sys.mjs"
+ );
+
+PoliciesPrefTracker.start();
+
+async function setupPolicyEngineWithJson(json, customSchema) {
+ PoliciesPrefTracker.restoreDefaultValues();
+ if (typeof json != "object") {
+ let filePath = getTestFilePath(json ? json : "non-existing-file.json");
+ return EnterprisePolicyTesting.setupPolicyEngineWithJson(
+ filePath,
+ customSchema
+ );
+ }
+ return EnterprisePolicyTesting.setupPolicyEngineWithJson(json, customSchema);
+}
+
+function checkLockedPref(prefName, prefValue) {
+ EnterprisePolicyTesting.checkPolicyPref(prefName, prefValue, true);
+}
+
+function checkUnlockedPref(prefName, prefValue) {
+ EnterprisePolicyTesting.checkPolicyPref(prefName, prefValue, false);
+}
+
+async function withNewTab(options, taskFn) {
+ let tab = window.openContentTab(options.url);
+ await BrowserTestUtils.browserLoaded(tab.browser);
+
+ let result = await taskFn(tab.browser);
+
+ let tabmail = document.getElementById("tabmail");
+ tabmail.closeTab(tab);
+ return Promise.resolve(result);
+}
+
+add_setup(async function policies_headjs_startWithCleanSlate() {
+ if (Services.policies.status != Ci.nsIEnterprisePolicies.INACTIVE) {
+ await setupPolicyEngineWithJson("");
+ }
+ is(
+ Services.policies.status,
+ Ci.nsIEnterprisePolicies.INACTIVE,
+ "Engine is inactive at the start of the test"
+ );
+});
+
+registerCleanupFunction(async function policies_headjs_finishWithCleanSlate() {
+ if (Services.policies.status != Ci.nsIEnterprisePolicies.INACTIVE) {
+ await setupPolicyEngineWithJson("");
+ }
+ is(
+ Services.policies.status,
+ Ci.nsIEnterprisePolicies.INACTIVE,
+ "Engine is inactive at the end of the test"
+ );
+
+ EnterprisePolicyTesting.resetRunOnceState();
+ PoliciesPrefTracker.stop();
+});
+
+function waitForAddonInstall(addon_id) {
+ return new Promise(resolve => {
+ let listener = {
+ onInstallEnded(install, addon) {
+ if (addon.id == addon_id) {
+ AddonManager.removeInstallListener(listener);
+ resolve();
+ }
+ },
+ onDownloadFailed() {
+ AddonManager.removeInstallListener(listener);
+ resolve();
+ },
+ onInstallFailed() {
+ AddonManager.removeInstallListener(listener);
+ resolve();
+ },
+ };
+ AddonManager.addInstallListener(listener);
+ });
+}
+
+function waitForAddonUninstall(addon_id) {
+ return new Promise(resolve => {
+ let listener = {};
+ listener.onUninstalled = addon => {
+ if (addon.id == addon_id) {
+ AddonManager.removeAddonListener(listener);
+ resolve();
+ }
+ };
+ AddonManager.addAddonListener(listener);
+ });
+}
diff --git a/comm/mail/components/enterprisepolicies/tests/browser/policytest_v0.1.xpi b/comm/mail/components/enterprisepolicies/tests/browser/policytest_v0.1.xpi
new file mode 100644
index 0000000000..ee2a6289ee
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/browser/policytest_v0.1.xpi
Binary files differ
diff --git a/comm/mail/components/enterprisepolicies/tests/browser/policytest_v0.2.xpi b/comm/mail/components/enterprisepolicies/tests/browser/policytest_v0.2.xpi
new file mode 100644
index 0000000000..59d589eba9
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/browser/policytest_v0.2.xpi
Binary files differ
diff --git a/comm/mail/components/enterprisepolicies/tests/moz.build b/comm/mail/components/enterprisepolicies/tests/moz.build
new file mode 100644
index 0000000000..c5014bbc67
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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 http://mozilla.org/MPL/2.0/.
+
+BROWSER_CHROME_MANIFESTS += [
+ "browser/browser.ini",
+ "browser/disable_app_update/browser.ini",
+ "browser/disable_developer_tools/browser.ini",
+ "browser/hardware_acceleration/browser.ini",
+]
+
+XPCSHELL_TESTS_MANIFESTS += [
+ "xpcshell/xpcshell.ini",
+]
diff --git a/comm/mail/components/enterprisepolicies/tests/xpcshell/head.js b/comm/mail/components/enterprisepolicies/tests/xpcshell/head.js
new file mode 100644
index 0000000000..2fcf00a21b
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/xpcshell/head.js
@@ -0,0 +1,140 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const lazy = {};
+
+const { Preferences } = ChromeUtils.importESModule(
+ "resource://gre/modules/Preferences.sys.mjs"
+);
+const { updateAppInfo, getAppInfo } = ChromeUtils.importESModule(
+ "resource://testing-common/AppInfo.sys.mjs"
+);
+const { FileTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/FileTestUtils.sys.mjs"
+);
+const { PermissionTestUtils } = ChromeUtils.import(
+ "resource://testing-common/PermissionTestUtils.jsm"
+);
+ChromeUtils.defineESModuleGetters(lazy, {
+ SearchTestUtils: "resource://testing-common/SearchTestUtils.sys.mjs",
+});
+const { EnterprisePolicyTesting } = ChromeUtils.importESModule(
+ "resource://testing-common/EnterprisePolicyTesting.sys.mjs"
+);
+
+updateAppInfo({
+ name: "XPCShell",
+ ID: "xpcshell@tests.mozilla.org",
+ version: "48",
+ platformVersion: "48",
+});
+
+// This initializes the policy engine for xpcshell tests
+let policies = Cc["@mozilla.org/enterprisepolicies;1"].getService(
+ Ci.nsIObserver
+);
+policies.observe(null, "policies-startup", null);
+
+async function setupPolicyEngineWithJson(json, customSchema) {
+ if (typeof json != "object") {
+ let filePath = do_get_file(json ? json : "non-existing-file.json").path;
+ return EnterprisePolicyTesting.setupPolicyEngineWithJson(
+ filePath,
+ customSchema
+ );
+ }
+ return EnterprisePolicyTesting.setupPolicyEngineWithJson(json, customSchema);
+}
+
+/**
+ * Loads a new enterprise policy, and re-initialise the search service
+ * with the new policy. Also waits for the search service to write the settings
+ * file to disk.
+ *
+ * @param {object} policy
+ * The enterprise policy to use.
+ * @param {object} customSchema
+ * A custom schema to use to validate the enterprise policy.
+ */
+async function setupPolicyEngineWithJsonWithSearch(json, customSchema) {
+ Services.search.wrappedJSObject.reset();
+ if (typeof json != "object") {
+ let filePath = do_get_file(json ? json : "non-existing-file.json").path;
+ await EnterprisePolicyTesting.setupPolicyEngineWithJson(
+ filePath,
+ customSchema
+ );
+ } else {
+ await EnterprisePolicyTesting.setupPolicyEngineWithJson(json, customSchema);
+ }
+ let settingsWritten = lazy.SearchTestUtils.promiseSearchNotification(
+ "write-settings-to-disk-complete"
+ );
+ await Services.search.init();
+ return settingsWritten;
+}
+
+function checkLockedPref(prefName, prefValue) {
+ equal(
+ Preferences.locked(prefName),
+ true,
+ `Pref ${prefName} is correctly locked`
+ );
+ equal(
+ Preferences.get(prefName),
+ prefValue,
+ `Pref ${prefName} has the correct value`
+ );
+}
+
+function checkUnlockedPref(prefName, prefValue) {
+ equal(
+ Preferences.locked(prefName),
+ false,
+ `Pref ${prefName} is correctly unlocked`
+ );
+ equal(
+ Preferences.get(prefName),
+ prefValue,
+ `Pref ${prefName} has the correct value`
+ );
+}
+
+function checkUserPref(prefName, prefValue) {
+ equal(
+ Preferences.get(prefName),
+ prefValue,
+ `Pref ${prefName} has the correct value`
+ );
+}
+
+function checkClearPref(prefName, prefValue) {
+ equal(
+ Services.prefs.prefHasUserValue(prefName),
+ false,
+ `Pref ${prefName} has no user value`
+ );
+}
+
+function checkDefaultPref(prefName, prefValue) {
+ let defaultPrefBranch = Services.prefs.getDefaultBranch("");
+ let prefType = defaultPrefBranch.getPrefType(prefName);
+ notEqual(
+ prefType,
+ Services.prefs.PREF_INVALID,
+ `Pref ${prefName} is set on the default branch`
+ );
+}
+
+function checkUnsetPref(prefName) {
+ let defaultPrefBranch = Services.prefs.getDefaultBranch("");
+ let prefType = defaultPrefBranch.getPrefType(prefName);
+ equal(
+ prefType,
+ Services.prefs.PREF_INVALID,
+ `Pref ${prefName} is not set on the default branch`
+ );
+}
diff --git a/comm/mail/components/enterprisepolicies/tests/xpcshell/test_3rdparty.js b/comm/mail/components/enterprisepolicies/tests/xpcshell/test_3rdparty.js
new file mode 100644
index 0000000000..b9dabb758d
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/xpcshell/test_3rdparty.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+add_setup(async function () {
+ await setupPolicyEngineWithJson({
+ policies: {
+ "3rdparty": {
+ Extensions: {
+ "3rdparty-policy@mozilla.com": {
+ string: "value",
+ },
+ },
+ },
+ },
+ });
+
+ let extensionPolicy = Services.policies.getExtensionPolicy(
+ "3rdparty-policy@mozilla.com"
+ );
+ deepEqual(extensionPolicy, { string: "value" });
+});
diff --git a/comm/mail/components/enterprisepolicies/tests/xpcshell/test_appupdatepin.js b/comm/mail/components/enterprisepolicies/tests/xpcshell/test_appupdatepin.js
new file mode 100644
index 0000000000..01e3810a05
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/xpcshell/test_appupdatepin.js
@@ -0,0 +1,80 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+/**
+ * Note that these tests only ensure that the pin is properly added to the
+ * update URL and to the telemetry. They do not test that the update applied
+ * will be of the correct version. This is because we are not attempting to have
+ * Thunderbird check if the update provided is valid given the pin, we are leaving
+ * it to the update server (Balrog) to find and serve the correct version.
+ */
+
+async function test_update_pin(pinString, pinIsValid = true) {
+ await setupPolicyEngineWithJson({
+ policies: {
+ AppUpdateURL: "https://www.example.com/update.xml",
+ AppUpdatePin: pinString,
+ },
+ });
+ Services.telemetry.clearScalars();
+
+ equal(
+ Services.policies.status,
+ Ci.nsIEnterprisePolicies.ACTIVE,
+ "Engine is active"
+ );
+
+ let policies = Services.policies.getActivePolicies();
+ equal(
+ "AppUpdatePin" in policies,
+ pinIsValid,
+ "AppUpdatePin policy should only be active if the pin was valid."
+ );
+
+ let checker = Cc["@mozilla.org/updates/update-checker;1"].getService(
+ Ci.nsIUpdateChecker
+ );
+ let updateURL = await checker.getUpdateURL(checker.BACKGROUND_CHECK);
+
+ let expected = pinIsValid
+ ? `https://www.example.com/update.xml?pin=${pinString}`
+ : "https://www.example.com/update.xml";
+
+ equal(updateURL, expected, "App Update URL should match expected URL.");
+
+ let scalars = TelemetryTestUtils.getProcessScalars("parent", false, true);
+ if (pinIsValid) {
+ TelemetryTestUtils.assertScalar(
+ scalars,
+ "update.version_pin",
+ pinString,
+ "Update pin telemetry should be set"
+ );
+ } else {
+ TelemetryTestUtils.assertScalarUnset(scalars, "update.version_pin");
+ }
+}
+
+add_task(async function test_app_update_pin() {
+ await test_update_pin("102.");
+ await test_update_pin("102.0.");
+ await test_update_pin("102.1.");
+ await test_update_pin("102.1.1", false);
+ await test_update_pin("102.1.1.", false);
+ await test_update_pin("102", false);
+ await test_update_pin("foobar", false);
+ await test_update_pin("-102.1.", false);
+ await test_update_pin("102.-1.", false);
+ await test_update_pin("102a.1.", false);
+ await test_update_pin("102.1a.", false);
+ await test_update_pin("0102.1.", false);
+ // Should not accept version numbers that will never be in Balrog's pinning
+ // table (i.e. versions before 102.0).
+ await test_update_pin("101.1.", false);
+});
diff --git a/comm/mail/components/enterprisepolicies/tests/xpcshell/test_appupdateurl.js b/comm/mail/components/enterprisepolicies/tests/xpcshell/test_appupdateurl.js
new file mode 100644
index 0000000000..48d04e1a8d
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/xpcshell/test_appupdateurl.js
@@ -0,0 +1,25 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_app_update_URL() {
+ await setupPolicyEngineWithJson({
+ policies: {
+ AppUpdateURL: "https://www.example.com/",
+ },
+ });
+
+ equal(
+ Services.policies.status,
+ Ci.nsIEnterprisePolicies.ACTIVE,
+ "Engine is active"
+ );
+
+ let checker = Cc["@mozilla.org/updates/update-checker;1"].getService(
+ Ci.nsIUpdateChecker
+ );
+ let expected = await checker.getUpdateURL(checker.BACKGROUND_CHECK);
+
+ equal("https://www.example.com/", expected, "Correct app update URL");
+});
diff --git a/comm/mail/components/enterprisepolicies/tests/xpcshell/test_bug1658259.js b/comm/mail/components/enterprisepolicies/tests/xpcshell/test_bug1658259.js
new file mode 100644
index 0000000000..1449e664c2
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/xpcshell/test_bug1658259.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_bug1658259_1() {
+ await setupPolicyEngineWithJson({
+ policies: {
+ OfferToSaveLogins: false,
+ OfferToSaveLoginsDefault: true,
+ },
+ });
+ checkLockedPref("signon.rememberSignons", false);
+});
+
+add_task(async function test_bug1658259_2() {
+ await setupPolicyEngineWithJson({
+ policies: {
+ OfferToSaveLogins: true,
+ OfferToSaveLoginsDefault: false,
+ },
+ });
+ checkLockedPref("signon.rememberSignons", true);
+});
+
+add_task(async function test_bug1658259_3() {
+ await setupPolicyEngineWithJson({
+ policies: {
+ OfferToSaveLoginsDefault: true,
+ OfferToSaveLogins: false,
+ },
+ });
+ checkLockedPref("signon.rememberSignons", false);
+});
+
+add_task(async function test_bug1658259_4() {
+ await setupPolicyEngineWithJson({
+ policies: {
+ OfferToSaveLoginsDefault: false,
+ OfferToSaveLogins: true,
+ },
+ });
+ checkLockedPref("signon.rememberSignons", true);
+});
diff --git a/comm/mail/components/enterprisepolicies/tests/xpcshell/test_clear_blocked_cookies.js b/comm/mail/components/enterprisepolicies/tests/xpcshell/test_clear_blocked_cookies.js
new file mode 100644
index 0000000000..17149f787f
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/xpcshell/test_clear_blocked_cookies.js
@@ -0,0 +1,118 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const HOSTNAME_DOMAIN = "browser_policy_clear_blocked_cookies.com";
+const ORIGIN_DOMAIN = "browser_policy_clear_blocked_cookies.org";
+
+add_setup(async function () {
+ const expiry = Date.now() + 24 * 60 * 60;
+ Services.cookies.add(
+ HOSTNAME_DOMAIN,
+ "/",
+ "secure",
+ "true",
+ true,
+ false,
+ false,
+ expiry,
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTPS
+ );
+ Services.cookies.add(
+ HOSTNAME_DOMAIN,
+ "/",
+ "insecure",
+ "true",
+ false,
+ false,
+ false,
+ expiry,
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTP
+ );
+ Services.cookies.add(
+ ORIGIN_DOMAIN,
+ "/",
+ "secure",
+ "true",
+ true,
+ false,
+ false,
+ expiry,
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTPS
+ );
+ Services.cookies.add(
+ ORIGIN_DOMAIN,
+ "/",
+ "insecure",
+ "true",
+ false,
+ false,
+ false,
+ expiry,
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTP
+ );
+ Services.cookies.add(
+ "example.net",
+ "/",
+ "secure",
+ "true",
+ true,
+ false,
+ false,
+ expiry,
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTPS
+ );
+ await setupPolicyEngineWithJson({
+ policies: {
+ Cookies: {
+ Block: [`http://${HOSTNAME_DOMAIN}`, `https://${ORIGIN_DOMAIN}:8080`],
+ },
+ },
+ });
+});
+
+function retrieve_all_cookies(host) {
+ const values = [];
+ for (let cookie of Services.cookies.getCookiesFromHost(host, {})) {
+ values.push({
+ host: cookie.host,
+ name: cookie.name,
+ path: cookie.path,
+ });
+ }
+ return values;
+}
+
+add_task(async function test_cookies_for_blocked_sites_cleared() {
+ const cookies = {
+ hostname: retrieve_all_cookies(HOSTNAME_DOMAIN),
+ origin: retrieve_all_cookies(ORIGIN_DOMAIN),
+ keep: retrieve_all_cookies("example.net"),
+ };
+ const expected = {
+ hostname: [],
+ origin: [],
+ keep: [{ host: "example.net", name: "secure", path: "/" }],
+ };
+ equal(
+ JSON.stringify(cookies),
+ JSON.stringify(expected),
+ "All stored cookies for blocked origins should be cleared"
+ );
+});
+
+add_task(function teardown() {
+ for (let host of [HOSTNAME_DOMAIN, ORIGIN_DOMAIN, "example.net"]) {
+ Services.cookies.removeCookiesWithOriginAttributes("{}", host);
+ }
+});
diff --git a/comm/mail/components/enterprisepolicies/tests/xpcshell/test_macosparser_unflatten.js b/comm/mail/components/enterprisepolicies/tests/xpcshell/test_macosparser_unflatten.js
new file mode 100644
index 0000000000..096852612c
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/xpcshell/test_macosparser_unflatten.js
@@ -0,0 +1,110 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let { macOSPoliciesParser } = ChromeUtils.importESModule(
+ "resource://gre/modules/policies/macOSPoliciesParser.sys.mjs"
+);
+
+add_task(async function test_object_unflatten() {
+ // Note: these policies are just examples and they won't actually
+ // run through the policy engine on this test. We're just testing
+ // that the unflattening algorithm produces the correct output.
+ let input = {
+ DisplayBookmarksToolbar: true,
+
+ Homepage__URL: "https://www.mozilla.org",
+ Homepage__Locked: "true",
+ Homepage__Additional__0: "https://extra-homepage-1.example.com",
+ Homepage__Additional__1: "https://extra-homepage-2.example.com",
+
+ WebsiteFilter__Block__0: "*://*.example.org/*",
+ WebsiteFilter__Block__1: "*://*.example.net/*",
+ WebsiteFilter__Exceptions__0: "*://*.example.org/*exception*",
+
+ Permissions__Camera__Allow__0: "https://www.example.com",
+
+ Permissions__Notifications__Allow__0: "https://www.example.com",
+ Permissions__Notifications__Allow__1: "https://www.example.org",
+ Permissions__Notifications__Block__0: "https://www.example.net",
+
+ Permissions__Notifications__BlockNewRequests: true,
+ Permissions__Notifications__Locked: true,
+
+ Bookmarks__0__Title: "Bookmark 1",
+ Bookmarks__0__URL: "https://bookmark1.example.com",
+
+ Bookmarks__1__Title: "Bookmark 2",
+ Bookmarks__1__URL: "https://bookmark2.example.com",
+ Bookmarks__1__Folder: "Folder",
+ };
+
+ let expected = {
+ DisplayBookmarksToolbar: true,
+
+ Homepage: {
+ URL: "https://www.mozilla.org",
+ Locked: "true",
+ Additional: [
+ "https://extra-homepage-1.example.com",
+ "https://extra-homepage-2.example.com",
+ ],
+ },
+
+ WebsiteFilter: {
+ Block: ["*://*.example.org/*", "*://*.example.net/*"],
+ Exceptions: ["*://*.example.org/*exception*"],
+ },
+
+ Permissions: {
+ Camera: {
+ Allow: ["https://www.example.com"],
+ },
+
+ Notifications: {
+ Allow: ["https://www.example.com", "https://www.example.org"],
+ Block: ["https://www.example.net"],
+ BlockNewRequests: true,
+ Locked: true,
+ },
+ },
+
+ Bookmarks: [
+ {
+ Title: "Bookmark 1",
+ URL: "https://bookmark1.example.com",
+ },
+ {
+ Title: "Bookmark 2",
+ URL: "https://bookmark2.example.com",
+ Folder: "Folder",
+ },
+ ],
+ };
+
+ let unflattened = macOSPoliciesParser.unflatten(input);
+
+ deepEqual(unflattened, expected, "Input was unflattened correctly.");
+});
+
+add_task(async function test_array_unflatten() {
+ let input = {
+ Foo__1: 1,
+ Foo__5: 5,
+ Foo__10: 10,
+ Foo__30: 30,
+ Foo__51: 51, // This one should not be included as the limit is 50
+ };
+
+ let unflattened = macOSPoliciesParser.unflatten(input);
+ equal(unflattened.Foo.length, 31, "Array size is correct");
+
+ let expected = {
+ Foo: [, 1, , , , 5], // eslint-disable-line no-sparse-arrays
+ };
+ expected.Foo[10] = 10;
+ expected.Foo[30] = 30;
+
+ deepEqual(unflattened, expected, "Array was unflattened correctly.");
+});
diff --git a/comm/mail/components/enterprisepolicies/tests/xpcshell/test_policy_search_engine.js b/comm/mail/components/enterprisepolicies/tests/xpcshell/test_policy_search_engine.js
new file mode 100644
index 0000000000..be16829867
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/xpcshell/test_policy_search_engine.js
@@ -0,0 +1,490 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const { SearchTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/SearchTestUtils.sys.mjs"
+);
+const { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+var { AddonTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/AddonTestUtils.sys.mjs"
+);
+
+Services.prefs.setBoolPref("browser.search.log", true);
+SearchTestUtils.init(this);
+
+AddonTestUtils.init(this, false);
+AddonTestUtils.createAppInfo(
+ "xpcshell@tests.mozilla.org",
+ "XPCShell",
+ "48",
+ "48"
+);
+
+add_setup(async () => {
+ await AddonTestUtils.promiseStartupManager();
+ await Services.search.init();
+ console.log("done init");
+});
+
+add_task(async function test_install_and_set_default() {
+ // Make sure we are starting in an expected state to avoid false positive
+ // test results.
+ Assert.notEqual(
+ (await Services.search.getDefault()).name,
+ "MozSearch",
+ "Default search engine should not be MozSearch when test starts"
+ );
+ Assert.equal(
+ Services.search.getEngineByName("Foo"),
+ null,
+ 'Engine "Foo" should not be present when test starts'
+ );
+
+ await setupPolicyEngineWithJsonWithSearch({
+ policies: {
+ SearchEngines: {
+ Add: [
+ {
+ Name: "MozSearch",
+ URLTemplate: "http://example.com/?q={searchTerms}",
+ },
+ ],
+ Default: "MozSearch",
+ },
+ },
+ });
+ // Get in line, because the Search policy callbacks are async.
+ await TestUtils.waitForTick();
+
+ // If this passes, it means that the new search engine was properly installed
+ // *and* was properly set as the default.
+ Assert.equal(
+ (await Services.search.getDefault()).name,
+ "MozSearch",
+ "Specified search engine should be the default"
+ );
+
+ // Clean up
+ await setupPolicyEngineWithJsonWithSearch({});
+ EnterprisePolicyTesting.resetRunOnceState();
+});
+
+add_task(async function test_install_and_set_default_private() {
+ // Make sure we are starting in an expected state to avoid false positive
+ // test results.
+ Assert.notEqual(
+ (await Services.search.getDefaultPrivate()).name,
+ "MozSearch",
+ "Default search engine should not be MozSearch when test starts"
+ );
+ Assert.equal(
+ Services.search.getEngineByName("Foo"),
+ null,
+ 'Engine "Foo" should not be present when test starts'
+ );
+
+ await setupPolicyEngineWithJsonWithSearch({
+ policies: {
+ SearchEngines: {
+ Add: [
+ {
+ Name: "MozSearch",
+ URLTemplate: "http://example.com/?q={searchTerms}",
+ },
+ ],
+ DefaultPrivate: "MozSearch",
+ },
+ },
+ });
+ // Get in line, because the Search policy callbacks are async.
+ await TestUtils.waitForTick();
+
+ // If this passes, it means that the new search engine was properly installed
+ // *and* was properly set as the default.
+ Assert.equal(
+ (await Services.search.getDefaultPrivate()).name,
+ "MozSearch",
+ "Specified search engine should be the default private engine"
+ );
+
+ // Clean up
+ await setupPolicyEngineWithJsonWithSearch({});
+ EnterprisePolicyTesting.resetRunOnceState();
+});
+
+// Same as the last test, but with "PreventInstalls" set to true to make sure
+// it does not prevent search engines from being installed properly
+add_task(async function test_install_and_set_default_prevent_installs() {
+ Assert.notEqual(
+ (await Services.search.getDefault()).name,
+ "MozSearch",
+ "Default search engine should not be MozSearch when test starts"
+ );
+ Assert.equal(
+ Services.search.getEngineByName("Foo"),
+ null,
+ 'Engine "Foo" should not be present when test starts'
+ );
+
+ await setupPolicyEngineWithJsonWithSearch({
+ policies: {
+ SearchEngines: {
+ Add: [
+ {
+ Name: "MozSearch",
+ URLTemplate: "http://example.com/?q={searchTerms}",
+ },
+ ],
+ Default: "MozSearch",
+ PreventInstalls: true,
+ },
+ },
+ });
+ // Get in line, because the Search policy callbacks are async.
+ await TestUtils.waitForTick();
+
+ Assert.equal(
+ (await Services.search.getDefault()).name,
+ "MozSearch",
+ "Specified search engine should be the default"
+ );
+
+ // Clean up
+ await setupPolicyEngineWithJsonWithSearch({});
+ EnterprisePolicyTesting.resetRunOnceState();
+});
+
+add_task(async function test_install_and_remove() {
+ let iconURL =
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=";
+
+ Assert.equal(
+ Services.search.getEngineByName("Foo"),
+ null,
+ 'Engine "Foo" should not be present when test starts'
+ );
+
+ await setupPolicyEngineWithJsonWithSearch({
+ policies: {
+ SearchEngines: {
+ Add: [
+ {
+ Name: "Foo",
+ URLTemplate: "http://example.com/?q={searchTerms}",
+ IconURL: iconURL,
+ },
+ ],
+ },
+ },
+ });
+ // Get in line, because the Search policy callbacks are async.
+ await TestUtils.waitForTick();
+
+ // If this passes, it means that the new search engine was properly installed
+
+ let engine = Services.search.getEngineByName("Foo");
+ Assert.notEqual(engine, null, "Specified search engine should be installed");
+
+ Assert.equal(
+ engine.wrappedJSObject.iconURI.spec,
+ iconURL,
+ "Icon should be present"
+ );
+ Assert.equal(
+ engine.wrappedJSObject.queryCharset,
+ "UTF-8",
+ "Should default to utf-8"
+ );
+
+ await setupPolicyEngineWithJsonWithSearch({
+ policies: {
+ SearchEngines: {
+ Remove: ["Foo"],
+ },
+ },
+ });
+ // Get in line, because the Search policy callbacks are async.
+ await TestUtils.waitForTick();
+
+ // If this passes, it means that the specified engine was properly removed
+ Assert.equal(
+ Services.search.getEngineByName("Foo"),
+ null,
+ "Specified search engine should not be installed"
+ );
+
+ await setupPolicyEngineWithJsonWithSearch({});
+ EnterprisePolicyTesting.resetRunOnceState();
+});
+
+add_task(async function test_install_post_method_engine() {
+ Assert.equal(
+ Services.search.getEngineByName("Post"),
+ null,
+ 'Engine "Post" should not be present when test starts'
+ );
+
+ await setupPolicyEngineWithJsonWithSearch({
+ policies: {
+ SearchEngines: {
+ Add: [
+ {
+ Name: "Post",
+ Method: "POST",
+ PostData: "q={searchTerms}&anotherParam=yes",
+ URLTemplate: "http://example.com/",
+ },
+ ],
+ },
+ },
+ });
+ // Get in line, because the Search policy callbacks are async.
+ await TestUtils.waitForTick();
+
+ let engine = Services.search.getEngineByName("Post");
+ Assert.notEqual(engine, null, "Specified search engine should be installed");
+
+ Assert.equal(
+ engine.wrappedJSObject._urls[0].method,
+ "POST",
+ "Method should be POST"
+ );
+
+ let submission = engine.getSubmission("term", "text/html");
+ Assert.notEqual(submission.postData, null, "Post data should not be null");
+
+ let scriptableInputStream = Cc[
+ "@mozilla.org/scriptableinputstream;1"
+ ].createInstance(Ci.nsIScriptableInputStream);
+ scriptableInputStream.init(submission.postData);
+ Assert.equal(
+ scriptableInputStream.read(scriptableInputStream.available()),
+ "q=term&anotherParam=yes",
+ "Post data should be present"
+ );
+
+ await setupPolicyEngineWithJsonWithSearch({});
+ EnterprisePolicyTesting.resetRunOnceState();
+});
+
+add_task(async function test_install_with_encoding() {
+ // Make sure we are starting in an expected state to avoid false positive
+ // test results.
+ Assert.equal(
+ Services.search.getEngineByName("Encoding"),
+ null,
+ 'Engine "Encoding" should not be present when test starts'
+ );
+
+ await setupPolicyEngineWithJsonWithSearch({
+ policies: {
+ SearchEngines: {
+ Add: [
+ {
+ Name: "Encoding",
+ Encoding: "windows-1252",
+ URLTemplate: "http://example.com/?q={searchTerms}",
+ },
+ ],
+ },
+ },
+ });
+ // Get in line, because the Search policy callbacks are async.
+ await TestUtils.waitForTick();
+
+ let engine = Services.search.getEngineByName("Encoding");
+ Assert.equal(
+ engine.wrappedJSObject.queryCharset,
+ "windows-1252",
+ "Should have correct encoding"
+ );
+
+ // Clean up
+ await setupPolicyEngineWithJsonWithSearch({});
+ EnterprisePolicyTesting.resetRunOnceState();
+});
+
+add_task(async function test_install_and_update() {
+ await setupPolicyEngineWithJsonWithSearch({
+ policies: {
+ SearchEngines: {
+ Add: [
+ {
+ Name: "ToUpdate",
+ URLTemplate: "http://initial.example.com/?q={searchTerms}",
+ },
+ ],
+ },
+ },
+ });
+ // Get in line, because the Search policy callbacks are async.
+ await TestUtils.waitForTick();
+
+ let engine = Services.search.getEngineByName("ToUpdate");
+ Assert.notEqual(engine, null, "Specified search engine should be installed");
+
+ Assert.equal(
+ engine.getSubmission("test").uri.spec,
+ "http://initial.example.com/?q=test",
+ "Initial submission URL should be correct."
+ );
+
+ await setupPolicyEngineWithJsonWithSearch({
+ policies: {
+ SearchEngines: {
+ Add: [
+ {
+ Name: "ToUpdate",
+ URLTemplate: "http://update.example.com/?q={searchTerms}",
+ },
+ ],
+ },
+ },
+ });
+ // Get in line, because the Search policy callbacks are async.
+ await TestUtils.waitForTick();
+
+ engine = Services.search.getEngineByName("ToUpdate");
+ Assert.notEqual(engine, null, "Specified search engine should be installed");
+
+ Assert.equal(
+ engine.getSubmission("test").uri.spec,
+ "http://update.example.com/?q=test",
+ "Updated Submission URL should be correct."
+ );
+
+ // Clean up
+ await setupPolicyEngineWithJsonWithSearch({});
+ EnterprisePolicyTesting.resetRunOnceState();
+});
+
+add_task(async function test_install_with_suggest() {
+ // Make sure we are starting in an expected state to avoid false positive
+ // test results.
+ Assert.equal(
+ Services.search.getEngineByName("Suggest"),
+ null,
+ 'Engine "Suggest" should not be present when test starts'
+ );
+
+ await setupPolicyEngineWithJsonWithSearch({
+ policies: {
+ SearchEngines: {
+ Add: [
+ {
+ Name: "Suggest",
+ URLTemplate: "http://example.com/?q={searchTerms}",
+ SuggestURLTemplate: "http://suggest.example.com/?q={searchTerms}",
+ },
+ ],
+ },
+ },
+ });
+ // Get in line, because the Search policy callbacks are async.
+ await TestUtils.waitForTick();
+
+ let engine = Services.search.getEngineByName("Suggest");
+
+ Assert.equal(
+ engine.getSubmission("test", "application/x-suggestions+json").uri.spec,
+ "http://suggest.example.com/?q=test",
+ "Updated Submission URL should be correct."
+ );
+
+ // Clean up
+ await setupPolicyEngineWithJsonWithSearch({});
+ EnterprisePolicyTesting.resetRunOnceState();
+});
+
+add_task(async function test_install_and_restart_keeps_settings() {
+ // Make sure we are starting in an expected state to avoid false positive
+ // test results.
+ Assert.equal(
+ Services.search.getEngineByName("Settings"),
+ null,
+ 'Engine "Settings" should not be present when test starts'
+ );
+
+ await setupPolicyEngineWithJsonWithSearch({
+ policies: {
+ SearchEngines: {
+ Add: [
+ {
+ Name: "Settings",
+ URLTemplate: "http://example.com/?q={searchTerms}",
+ },
+ ],
+ },
+ },
+ });
+ // Get in line, because the Search policy callbacks are async.
+ await TestUtils.waitForTick();
+
+ let settingsWritten = SearchTestUtils.promiseSearchNotification(
+ "write-settings-to-disk-complete"
+ );
+ let engine = Services.search.getEngineByName("Settings");
+ engine.hidden = true;
+ engine.alias = "settings";
+ await settingsWritten;
+
+ await setupPolicyEngineWithJsonWithSearch({
+ policies: {
+ SearchEngines: {
+ Add: [
+ {
+ Name: "Settings",
+ URLTemplate: "http://example.com/?q={searchTerms}",
+ },
+ ],
+ },
+ },
+ });
+
+ engine = Services.search.getEngineByName("Settings");
+
+ Assert.ok(engine.hidden, "Should have kept the engine hidden after restart");
+ Assert.equal(
+ engine.alias,
+ "settings",
+ "Should have kept the engine alias after restart"
+ );
+
+ // Clean up
+ await setupPolicyEngineWithJsonWithSearch({});
+ EnterprisePolicyTesting.resetRunOnceState();
+});
+
+add_task(async function test_reset_default() {
+ await setupPolicyEngineWithJsonWithSearch({
+ policies: {
+ SearchEngines: {
+ Remove: ["DuckDuckGo"],
+ },
+ },
+ });
+ // Get in line, because the Search policy callbacks are async.
+ await TestUtils.waitForTick();
+
+ let engine = Services.search.getEngineByName("DuckDuckGo");
+
+ Assert.equal(
+ engine.hidden,
+ true,
+ "Application specified engine should be hidden."
+ );
+
+ await Services.search.restoreDefaultEngines();
+
+ engine = Services.search.getEngineByName("DuckDuckGo");
+ Assert.equal(
+ engine.hidden,
+ false,
+ "Application specified engine should not be hidden"
+ );
+
+ EnterprisePolicyTesting.resetRunOnceState();
+});
diff --git a/comm/mail/components/enterprisepolicies/tests/xpcshell/test_preferences.js b/comm/mail/components/enterprisepolicies/tests/xpcshell/test_preferences.js
new file mode 100644
index 0000000000..6ad883fe42
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/xpcshell/test_preferences.js
@@ -0,0 +1,255 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const OLD_PREFERENCES_TESTS = [
+ {
+ policies: {
+ Preferences: {
+ "network.IDN_show_punycode": true,
+ "accessibility.force_disabled": 1,
+ "security.default_personal_cert": "Select Automatically",
+ // "geo.enabled": 1,
+ "extensions.getAddons.showPane": 0,
+ },
+ },
+ lockedPrefs: {
+ "network.IDN_show_punycode": true,
+ "accessibility.force_disabled": 1,
+ "security.default_personal_cert": "Select Automatically",
+ // "geo.enabled": true,
+ "extensions.getAddons.showPane": false,
+ },
+ },
+];
+
+const NEW_PREFERENCES_TESTS = [
+ {
+ policies: {
+ Preferences: {
+ "browser.policies.test.default.boolean": {
+ Value: true,
+ Status: "default",
+ },
+ "browser.policies.test.default.string": {
+ Value: "string",
+ Status: "default",
+ },
+ "browser.policies.test.default.number": {
+ Value: 11,
+ Status: "default",
+ },
+ "browser.policies.test.locked.boolean": {
+ Value: true,
+ Status: "locked",
+ },
+ "browser.policies.test.locked.string": {
+ Value: "string",
+ Status: "locked",
+ },
+ "browser.policies.test.locked.number": {
+ Value: 11,
+ Status: "locked",
+ },
+ "browser.policies.test.user.boolean": {
+ Value: true,
+ Status: "user",
+ },
+ "browser.policies.test.user.string": {
+ Value: "string",
+ Status: "user",
+ },
+ "browser.policies.test.user.number": {
+ Value: 11,
+ Status: "user",
+ },
+ "mail.openMessageBehavior": {
+ Value: 1,
+ Status: "locked",
+ },
+ "mailnews.display.prefer_plaintext": {
+ Value: true,
+ Status: "locked",
+ },
+ "chat.enabled": {
+ Value: false,
+ Status: "locked",
+ },
+ "calendar.agenda.days": {
+ Value: 21,
+ Status: "locked",
+ },
+ },
+ },
+ defaultPrefs: {
+ "browser.policies.test.default.boolean": true,
+ "browser.policies.test.default.string": "string",
+ "browser.policies.test.default.number": 11,
+ },
+ lockedPrefs: {
+ "browser.policies.test.locked.boolean": true,
+ "browser.policies.test.locked.string": "string",
+ "browser.policies.test.locked.number": 11,
+ "mail.openMessageBehavior": 1,
+ "mailnews.display.prefer_plaintext": true,
+ "chat.enabled": false,
+ "calendar.agenda.days": 21,
+ },
+ userPrefs: {
+ "browser.policies.test.user.boolean": true,
+ "browser.policies.test.user.string": "string",
+ "browser.policies.test.user.number": 11,
+ },
+ },
+ {
+ policies: {
+ Preferences: {
+ "browser.policies.test.user.boolean": {
+ Status: "clear",
+ },
+ "browser.policies.test.user.string": {
+ Status: "clear",
+ },
+ "browser.policies.test.user.number": {
+ Status: "clear",
+ },
+ },
+ },
+
+ clearPrefs: {
+ "browser.policies.test.user.boolean": true,
+ "browser.policies.test.user.string": "string",
+ "browser.policies.test.user.number": 11,
+ },
+ },
+];
+
+const BAD_PREFERENCES_TESTS = [
+ {
+ policies: {
+ Preferences: {
+ "not.a.valid.branch": {
+ Value: true,
+ Status: "default",
+ },
+ "security.turn_off_all_security_so_that_viruses_can_take_over_this_computer":
+ {
+ Value: true,
+ Status: "default",
+ },
+ },
+ },
+ defaultPrefs: {
+ "not.a.valid.branch": true,
+ "security.turn_off_all_security_so_that_viruses_can_take_over_this_computer": true,
+ },
+ },
+];
+
+add_task(async function test_old_preferences() {
+ for (let test of OLD_PREFERENCES_TESTS) {
+ await setupPolicyEngineWithJson({
+ policies: test.policies,
+ });
+
+ info("Checking policy: " + Object.keys(test.policies)[0]);
+
+ for (let [prefName, prefValue] of Object.entries(test.lockedPrefs || {})) {
+ checkLockedPref(prefName, prefValue);
+ }
+ }
+});
+
+add_task(async function test_new_preferences() {
+ for (let test of NEW_PREFERENCES_TESTS) {
+ await setupPolicyEngineWithJson({
+ policies: test.policies,
+ });
+
+ info("Checking policy: " + Object.keys(test.policies)[0]);
+
+ for (let [prefName, prefValue] of Object.entries(test.lockedPrefs || {})) {
+ checkLockedPref(prefName, prefValue);
+ }
+
+ for (let [prefName, prefValue] of Object.entries(test.defaultPrefs || {})) {
+ checkDefaultPref(prefName, prefValue);
+ }
+
+ for (let [prefName, prefValue] of Object.entries(test.userPrefs || {})) {
+ checkUserPref(prefName, prefValue);
+ }
+
+ for (let [prefName, prefValue] of Object.entries(test.clearPrefs || {})) {
+ checkClearPref(prefName, prefValue);
+ }
+ }
+});
+
+add_task(async function test_bad_preferences() {
+ for (let test of BAD_PREFERENCES_TESTS) {
+ await setupPolicyEngineWithJson({
+ policies: test.policies,
+ });
+
+ info("Checking policy: " + Object.keys(test.policies)[0]);
+
+ for (let prefName of Object.entries(test.defaultPrefs || {})) {
+ checkUnsetPref(prefName);
+ }
+ }
+});
+
+add_task(async function test_user_default_preference() {
+ Services.prefs
+ .getDefaultBranch("")
+ .setBoolPref("browser.policies.test.override", true);
+
+ await setupPolicyEngineWithJson({
+ policies: {
+ Preferences: {
+ "browser.policies.test.override": {
+ Value: true,
+ Status: "user",
+ },
+ },
+ },
+ });
+
+ checkUserPref("browser.policies.test.override", true);
+});
+
+add_task(async function test_security_preference() {
+ await setupPolicyEngineWithJson({
+ policies: {
+ Preferences: {
+ "security.this.should.not.work": {
+ Value: true,
+ Status: "default",
+ },
+ },
+ },
+ });
+
+ checkUnsetPref("security.this.should.not.work");
+});
+
+add_task(async function test_bug_1666836() {
+ await setupPolicyEngineWithJson({
+ policies: {
+ Preferences: {
+ "browser.tabs.warnOnClose": {
+ Value: 0,
+ Status: "default",
+ },
+ },
+ },
+ });
+
+ equal(
+ Preferences.get("browser.tabs.warnOnClose"),
+ false,
+ `browser.tabs.warnOnClose should be false`
+ );
+});
diff --git a/comm/mail/components/enterprisepolicies/tests/xpcshell/test_proxy.js b/comm/mail/components/enterprisepolicies/tests/xpcshell/test_proxy.js
new file mode 100644
index 0000000000..ef5ad1e178
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/xpcshell/test_proxy.js
@@ -0,0 +1,122 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+add_task(async function test_proxy_modes_and_autoconfig() {
+ // Directly test the proxy Mode and AutoconfigURL parameters through
+ // the API instead of the policy engine, because the test harness
+ // uses these prefs, and changing them interfere with the harness.
+
+ // Checks that every Mode value translates correctly to the expected pref value
+ let { ProxyPolicies, PROXY_TYPES_MAP } = ChromeUtils.importESModule(
+ "resource:///modules/policies/ProxyPolicies.sys.mjs"
+ );
+
+ for (let [mode, expectedValue] of PROXY_TYPES_MAP) {
+ ProxyPolicies.configureProxySettings({ Mode: mode }, (_, value) => {
+ equal(value, expectedValue, "Correct proxy mode");
+ });
+ }
+
+ let autoconfigURL = new URL("data:text/plain,test");
+ ProxyPolicies.configureProxySettings(
+ { AutoConfigURL: autoconfigURL },
+ (_, value) => {
+ equal(value, autoconfigURL.href, "AutoconfigURL correctly set");
+ }
+ );
+});
+
+add_task(async function test_proxy_boolean_settings() {
+ // Tests that both false and true values are correctly set and locked
+ await setupPolicyEngineWithJson({
+ policies: {
+ Proxy: {
+ UseProxyForDNS: false,
+ AutoLogin: false,
+ },
+ },
+ });
+
+ checkUnlockedPref("network.proxy.socks_remote_dns", false);
+ checkUnlockedPref("signon.autologin.proxy", false);
+
+ await setupPolicyEngineWithJson({
+ policies: {
+ Proxy: {
+ UseProxyForDNS: true,
+ AutoLogin: true,
+ },
+ },
+ });
+
+ checkUnlockedPref("network.proxy.socks_remote_dns", true);
+ checkUnlockedPref("signon.autologin.proxy", true);
+});
+
+add_task(async function test_proxy_socks_and_passthrough() {
+ await setupPolicyEngineWithJson({
+ policies: {
+ Proxy: {
+ SOCKSVersion: 4,
+ Passthrough: "a, b, c",
+ },
+ },
+ });
+
+ checkUnlockedPref("network.proxy.socks_version", 4);
+ checkUnlockedPref("network.proxy.no_proxies_on", "a, b, c");
+});
+
+add_task(async function test_proxy_addresses() {
+ function checkProxyPref(proxytype, address, port) {
+ checkUnlockedPref(`network.proxy.${proxytype}`, address);
+ checkUnlockedPref(`network.proxy.${proxytype}_port`, port);
+ }
+
+ await setupPolicyEngineWithJson({
+ policies: {
+ Proxy: {
+ HTTPProxy: "http.proxy.example.com:10",
+ SSLProxy: "ssl.proxy.example.com:30",
+ SOCKSProxy: "socks.proxy.example.com:40",
+ },
+ },
+ });
+
+ checkProxyPref("http", "http.proxy.example.com", 10);
+ checkProxyPref("ssl", "ssl.proxy.example.com", 30);
+ checkProxyPref("socks", "socks.proxy.example.com", 40);
+
+ // Do the same, but now use the UseHTTPProxyForAllProtocols option
+ // and check that it takes effect.
+ await setupPolicyEngineWithJson({
+ policies: {
+ Proxy: {
+ HTTPProxy: "http.proxy.example.com:10",
+ // FTP support was removed in bug 1574475
+ // Setting an FTPProxy should result in a warning but should not fail
+ FTPProxy: "ftp.proxy.example.com:20",
+ SSLProxy: "ssl.proxy.example.com:30",
+ SOCKSProxy: "socks.proxy.example.com:40",
+ UseHTTPProxyForAllProtocols: true,
+ },
+ },
+ });
+
+ checkProxyPref("http", "http.proxy.example.com", 10);
+ checkProxyPref("ssl", "http.proxy.example.com", 10);
+ checkProxyPref("socks", "http.proxy.example.com", 10);
+
+ // Make sure the FTPProxy setting did nothing
+ Assert.equal(
+ Preferences.has("network.proxy.ftp"),
+ false,
+ "network.proxy.ftp should not be set"
+ );
+ Assert.equal(
+ Preferences.has("network.proxy.ftp_port"),
+ false,
+ "network.proxy.ftp_port should not be set"
+ );
+});
diff --git a/comm/mail/components/enterprisepolicies/tests/xpcshell/test_requestedlocales.js b/comm/mail/components/enterprisepolicies/tests/xpcshell/test_requestedlocales.js
new file mode 100644
index 0000000000..6c298cee5a
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/xpcshell/test_requestedlocales.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const REQ_LOC_CHANGE_EVENT = "intl:requested-locales-changed";
+
+function promiseLocaleChanged(requestedLocale) {
+ return new Promise(resolve => {
+ let localeObserver = {
+ observe(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case REQ_LOC_CHANGE_EVENT:
+ let reqLocs = Services.locale.requestedLocales;
+ equal(reqLocs[0], requestedLocale);
+ Services.obs.removeObserver(localeObserver, REQ_LOC_CHANGE_EVENT);
+ resolve();
+ }
+ },
+ };
+ Services.obs.addObserver(localeObserver, REQ_LOC_CHANGE_EVENT);
+ });
+}
+
+add_task(async function test_requested_locale_array() {
+ let originalLocales = Services.locale.requestedLocales;
+ let localePromise = promiseLocaleChanged("de");
+ await setupPolicyEngineWithJson({
+ policies: {
+ RequestedLocales: ["de"],
+ },
+ });
+ await localePromise;
+ Services.locale.requestedLocales = originalLocales;
+});
+
+add_task(async function test_requested_locale_string() {
+ let originalLocales = Services.locale.requestedLocales;
+ let localePromise = promiseLocaleChanged("fr");
+ await setupPolicyEngineWithJson({
+ policies: {
+ RequestedLocales: "fr",
+ },
+ });
+ await localePromise;
+ Services.locale.requestedLocales = originalLocales;
+});
diff --git a/comm/mail/components/enterprisepolicies/tests/xpcshell/test_runOnce_helper.js b/comm/mail/components/enterprisepolicies/tests/xpcshell/test_runOnce_helper.js
new file mode 100644
index 0000000000..c8e73b3422
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/xpcshell/test_runOnce_helper.js
@@ -0,0 +1,21 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let { runOnce } = ChromeUtils.importESModule(
+ "resource:///modules/policies/Policies.sys.mjs"
+);
+
+let runCount = 0;
+function callback() {
+ runCount++;
+}
+
+add_task(async function test_runonce_helper() {
+ runOnce("test_action", callback);
+ equal(runCount, 1, "Callback ran for the first time.");
+
+ runOnce("test_action", callback);
+ equal(runCount, 1, "Callback didn't run again.");
+});
diff --git a/comm/mail/components/enterprisepolicies/tests/xpcshell/test_simple_pref_policies.js b/comm/mail/components/enterprisepolicies/tests/xpcshell/test_simple_pref_policies.js
new file mode 100644
index 0000000000..90da242a72
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/xpcshell/test_simple_pref_policies.js
@@ -0,0 +1,378 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/*
+ * Use this file to add tests to policies that are
+ * simple pref flips.
+ *
+ * It's best to make a test to actually test the feature
+ * instead of the pref flip, but if that feature is well
+ * covered by tests, including that its pref actually works,
+ * it's OK to have the policy test here just to ensure
+ * that the right pref values are set.
+ */
+
+const POLICIES_TESTS = [
+ /*
+ * Example:
+ * {
+ * // Policies to be set at once through the engine
+ * policies: { "DisableFoo": true, "ConfigureBar": 42 },
+ *
+ * // Locked prefs to check
+ * lockedPrefs: { "feature.foo": false },
+ *
+ * // Unlocked prefs to check
+ * unlockedPrefs: { "bar.baz": 42 }
+ * },
+ */
+
+ // POLICY: RememberPasswords
+ {
+ policies: { OfferToSaveLogins: false },
+ lockedPrefs: { "signon.rememberSignons": false },
+ },
+ {
+ policies: { OfferToSaveLogins: true },
+ lockedPrefs: { "signon.rememberSignons": true },
+ },
+
+ // POLICY: DisableSecurityBypass
+ {
+ policies: {
+ DisableSecurityBypass: {
+ InvalidCertificate: true,
+ SafeBrowsing: true,
+ },
+ },
+ lockedPrefs: {
+ "security.certerror.hideAddException": true,
+ "browser.safebrowsing.allowOverride": false,
+ },
+ },
+
+ // POLICY: DisableBuiltinPDFViewer
+ {
+ policies: { DisableBuiltinPDFViewer: true },
+ lockedPrefs: { "pdfjs.disabled": true },
+ },
+
+ // POLICY: Authentication
+ {
+ policies: {
+ Authentication: {
+ SPNEGO: ["a.com", "b.com"],
+ Delegated: ["a.com", "b.com"],
+ NTLM: ["a.com", "b.com"],
+ AllowNonFQDN: {
+ SPNEGO: true,
+ NTLM: true,
+ },
+ AllowProxies: {
+ SPNEGO: false,
+ NTLM: false,
+ },
+ PrivateBrowsing: true,
+ },
+ },
+ lockedPrefs: {
+ "network.negotiate-auth.trusted-uris": "a.com, b.com",
+ "network.negotiate-auth.delegation-uris": "a.com, b.com",
+ "network.automatic-ntlm-auth.trusted-uris": "a.com, b.com",
+ "network.automatic-ntlm-auth.allow-non-fqdn": true,
+ "network.negotiate-auth.allow-non-fqdn": true,
+ "network.automatic-ntlm-auth.allow-proxies": false,
+ "network.negotiate-auth.allow-proxies": false,
+ "network.auth.private-browsing-sso": true,
+ },
+ },
+
+ // POLICY: Authentication (unlocked)
+ {
+ policies: {
+ Authentication: {
+ SPNEGO: ["a.com", "b.com"],
+ Delegated: ["a.com", "b.com"],
+ NTLM: ["a.com", "b.com"],
+ AllowNonFQDN: {
+ SPNEGO: true,
+ NTLM: true,
+ },
+ AllowProxies: {
+ SPNEGO: false,
+ NTLM: false,
+ },
+ PrivateBrowsing: true,
+ Locked: false,
+ },
+ },
+ unlockedPrefs: {
+ "network.negotiate-auth.trusted-uris": "a.com, b.com",
+ "network.negotiate-auth.delegation-uris": "a.com, b.com",
+ "network.automatic-ntlm-auth.trusted-uris": "a.com, b.com",
+ "network.automatic-ntlm-auth.allow-non-fqdn": true,
+ "network.negotiate-auth.allow-non-fqdn": true,
+ "network.automatic-ntlm-auth.allow-proxies": false,
+ "network.negotiate-auth.allow-proxies": false,
+ "network.auth.private-browsing-sso": true,
+ },
+ },
+
+ // POLICY: Certificates (true)
+ {
+ policies: {
+ Certificates: {
+ ImportEnterpriseRoots: true,
+ },
+ },
+ lockedPrefs: {
+ "security.enterprise_roots.enabled": true,
+ },
+ },
+
+ // POLICY: Certificates (false)
+ {
+ policies: {
+ Certificates: {
+ ImportEnterpriseRoots: false,
+ },
+ },
+ lockedPrefs: {
+ "security.enterprise_roots.enabled": false,
+ },
+ },
+
+ // POLICY: InstallAddons.Default (block addon installs)
+ {
+ policies: {
+ InstallAddonsPermission: {
+ Default: false,
+ },
+ },
+ lockedPrefs: {
+ "xpinstall.enabled": false,
+ },
+ },
+
+ // POLICY: DNSOverHTTPS Locked
+ {
+ policies: {
+ DNSOverHTTPS: {
+ Enabled: true,
+ ProviderURL: "http://example.com/provider",
+ ExcludedDomains: ["example.com", "example.org"],
+ Locked: true,
+ },
+ },
+ lockedPrefs: {
+ "network.trr.mode": 2,
+ "network.trr.uri": "http://example.com/provider",
+ "network.trr.excluded-domains": "example.com,example.org",
+ },
+ },
+
+ // POLICY: DNSOverHTTPS Unlocked
+ {
+ policies: {
+ DNSOverHTTPS: {
+ Enabled: false,
+ ProviderURL: "http://example.com/provider",
+ ExcludedDomains: ["example.com", "example.org"],
+ },
+ },
+ unlockedPrefs: {
+ "network.trr.mode": 5,
+ "network.trr.uri": "http://example.com/provider",
+ "network.trr.excluded-domains": "example.com,example.org",
+ },
+ },
+
+ // POLICY: SSLVersionMin/SSLVersionMax (1)
+ {
+ policies: {
+ SSLVersionMin: "tls1",
+ SSLVersionMax: "tls1.1",
+ },
+ lockedPrefs: {
+ "security.tls.version.min": 1,
+ "security.tls.version.max": 2,
+ },
+ },
+
+ // POLICY: SSLVersionMin/SSLVersionMax (2)
+ {
+ policies: {
+ SSLVersionMin: "tls1.2",
+ SSLVersionMax: "tls1.3",
+ },
+ lockedPrefs: {
+ "security.tls.version.min": 3,
+ "security.tls.version.max": 4,
+ },
+ },
+
+ // POLICY: CaptivePortal
+ {
+ policies: {
+ CaptivePortal: false,
+ },
+ lockedPrefs: {
+ "network.captive-portal-service.enabled": false,
+ },
+ },
+
+ // POLICY: NetworkPrediction
+ {
+ policies: {
+ NetworkPrediction: false,
+ },
+ lockedPrefs: {
+ "network.dns.disablePrefetch": true,
+ "network.dns.disablePrefetchFromHTTPS": true,
+ },
+ },
+
+ // POLICY: ExtensionUpdate
+ {
+ policies: {
+ ExtensionUpdate: false,
+ },
+ lockedPrefs: {
+ "extensions.update.enabled": false,
+ },
+ },
+
+ // POLICY: OfferToSaveLoginsDefault
+ {
+ policies: {
+ OfferToSaveLoginsDefault: false,
+ },
+ unlockedPrefs: {
+ "signon.rememberSignons": false,
+ },
+ },
+
+ // POLICY: PDFjs
+
+ {
+ policies: {
+ PDFjs: {
+ Enabled: false,
+ EnablePermissions: true,
+ },
+ },
+ lockedPrefs: {
+ "pdfjs.disabled": true,
+ "pdfjs.enablePermissions": true,
+ },
+ },
+
+ // POLICY: DisabledCiphers
+ {
+ policies: {
+ DisabledCiphers: {
+ TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: false,
+ TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: false,
+ TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: false,
+ TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: false,
+ TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: false,
+ TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: false,
+ TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: false,
+ TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: false,
+ TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: false,
+ TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: false,
+ TLS_DHE_RSA_WITH_AES_128_CBC_SHA: false,
+ TLS_DHE_RSA_WITH_AES_256_CBC_SHA: false,
+ TLS_RSA_WITH_AES_128_GCM_SHA256: false,
+ TLS_RSA_WITH_AES_256_GCM_SHA384: false,
+ TLS_RSA_WITH_AES_128_CBC_SHA: false,
+ TLS_RSA_WITH_AES_256_CBC_SHA: false,
+ TLS_RSA_WITH_3DES_EDE_CBC_SHA: false,
+ },
+ },
+ lockedPrefs: {
+ "security.ssl3.ecdhe_rsa_aes_128_gcm_sha256": true,
+ "security.ssl3.ecdhe_ecdsa_aes_128_gcm_sha256": true,
+ "security.ssl3.ecdhe_ecdsa_chacha20_poly1305_sha256": true,
+ "security.ssl3.ecdhe_rsa_chacha20_poly1305_sha256": true,
+ "security.ssl3.ecdhe_ecdsa_aes_256_gcm_sha384": true,
+ "security.ssl3.ecdhe_rsa_aes_256_gcm_sha384": true,
+ "security.ssl3.ecdhe_rsa_aes_128_sha": true,
+ "security.ssl3.ecdhe_ecdsa_aes_128_sha": true,
+ "security.ssl3.ecdhe_rsa_aes_256_sha": true,
+ "security.ssl3.ecdhe_ecdsa_aes_256_sha": true,
+ "security.ssl3.dhe_rsa_aes_128_sha": true,
+ "security.ssl3.dhe_rsa_aes_256_sha": true,
+ "security.ssl3.rsa_aes_128_gcm_sha256": true,
+ "security.ssl3.rsa_aes_256_gcm_sha384": true,
+ "security.ssl3.rsa_aes_128_sha": true,
+ "security.ssl3.rsa_aes_256_sha": true,
+ "security.ssl3.deprecated.rsa_des_ede3_sha": true,
+ },
+ },
+
+ {
+ policies: {
+ DisabledCiphers: {
+ TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: true,
+ TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: true,
+ TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: true,
+ TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: true,
+ TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: true,
+ TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: true,
+ TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: true,
+ TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: true,
+ TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: true,
+ TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: true,
+ TLS_DHE_RSA_WITH_AES_128_CBC_SHA: true,
+ TLS_DHE_RSA_WITH_AES_256_CBC_SHA: true,
+ TLS_RSA_WITH_AES_128_GCM_SHA256: true,
+ TLS_RSA_WITH_AES_256_GCM_SHA384: true,
+ TLS_RSA_WITH_AES_128_CBC_SHA: true,
+ TLS_RSA_WITH_AES_256_CBC_SHA: true,
+ TLS_RSA_WITH_3DES_EDE_CBC_SHA: true,
+ },
+ },
+ lockedPrefs: {
+ "security.ssl3.ecdhe_rsa_aes_128_gcm_sha256": false,
+ "security.ssl3.ecdhe_ecdsa_aes_128_gcm_sha256": false,
+ "security.ssl3.ecdhe_ecdsa_chacha20_poly1305_sha256": false,
+ "security.ssl3.ecdhe_rsa_chacha20_poly1305_sha256": false,
+ "security.ssl3.ecdhe_ecdsa_aes_256_gcm_sha384": false,
+ "security.ssl3.ecdhe_rsa_aes_256_gcm_sha384": false,
+ "security.ssl3.ecdhe_rsa_aes_128_sha": false,
+ "security.ssl3.ecdhe_ecdsa_aes_128_sha": false,
+ "security.ssl3.ecdhe_rsa_aes_256_sha": false,
+ "security.ssl3.ecdhe_ecdsa_aes_256_sha": false,
+ "security.ssl3.dhe_rsa_aes_128_sha": false,
+ "security.ssl3.dhe_rsa_aes_256_sha": false,
+ "security.ssl3.rsa_aes_128_gcm_sha256": false,
+ "security.ssl3.rsa_aes_256_gcm_sha384": false,
+ "security.ssl3.rsa_aes_128_sha": false,
+ "security.ssl3.rsa_aes_256_sha": false,
+ "security.ssl3.deprecated.rsa_des_ede3_sha": false,
+ },
+ },
+];
+
+add_task(async function test_policy_simple_prefs() {
+ for (let test of POLICIES_TESTS) {
+ await setupPolicyEngineWithJson({
+ policies: test.policies,
+ });
+
+ info("Checking policy: " + Object.keys(test.policies)[0]);
+
+ for (let [prefName, prefValue] of Object.entries(test.lockedPrefs || {})) {
+ checkLockedPref(prefName, prefValue);
+ }
+
+ for (let [prefName, prefValue] of Object.entries(
+ test.unlockedPrefs || {}
+ )) {
+ checkUnlockedPref(prefName, prefValue);
+ }
+ }
+});
diff --git a/comm/mail/components/enterprisepolicies/tests/xpcshell/test_sorted_alphabetically.js b/comm/mail/components/enterprisepolicies/tests/xpcshell/test_sorted_alphabetically.js
new file mode 100644
index 0000000000..0d246c850c
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/xpcshell/test_sorted_alphabetically.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function checkArrayIsSorted(array, msg) {
+ let sorted = true;
+ let sortedArray = array.slice().sort(function (a, b) {
+ return a.localeCompare(b);
+ });
+
+ for (let i = 0; i < array.length; i++) {
+ if (array[i] != sortedArray[i]) {
+ sorted = false;
+ break;
+ }
+ }
+ ok(sorted, msg);
+}
+
+add_task(async function test_policies_sorted() {
+ let { schema } = ChromeUtils.importESModule(
+ "resource:///modules/policies/schema.sys.mjs"
+ );
+ let { Policies } = ChromeUtils.importESModule(
+ "resource:///modules/policies/Policies.sys.mjs"
+ );
+
+ checkArrayIsSorted(
+ Object.keys(schema.properties),
+ "policies-schema.json is alphabetically sorted."
+ );
+ checkArrayIsSorted(
+ Object.keys(Policies),
+ "Policies.jsm is alphabetically sorted."
+ );
+});
+
+add_task(async function check_naming_conventions() {
+ let { schema } = ChromeUtils.importESModule(
+ "resource:///modules/policies/schema.sys.mjs"
+ );
+ equal(
+ Object.keys(schema.properties).some(key => key.includes("__")),
+ false,
+ "Can't use __ in a policy name as it's used as a delimiter"
+ );
+});
diff --git a/comm/mail/components/enterprisepolicies/tests/xpcshell/xpcshell.ini b/comm/mail/components/enterprisepolicies/tests/xpcshell/xpcshell.ini
new file mode 100644
index 0000000000..ab47cca7bd
--- /dev/null
+++ b/comm/mail/components/enterprisepolicies/tests/xpcshell/xpcshell.ini
@@ -0,0 +1,18 @@
+[DEFAULT]
+firefox-appdir = browser
+head = head.js
+
+[test_3rdparty.js]
+[test_appupdatepin.js]
+[test_appupdateurl.js]
+[test_bug1658259.js]
+[test_clear_blocked_cookies.js]
+[test_macosparser_unflatten.js]
+skip-if = os != 'mac'
+[test_policy_search_engine.js]
+[test_preferences.js]
+[test_proxy.js]
+[test_requestedlocales.js]
+[test_runOnce_helper.js]
+[test_simple_pref_policies.js]
+[test_sorted_alphabetically.js]