diff options
Diffstat (limited to 'comm/mail/components/enterprisepolicies')
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 Binary files differnew file mode 100644 index 0000000000..ee2a6289ee --- /dev/null +++ b/comm/mail/components/enterprisepolicies/tests/browser/policytest_v0.1.xpi diff --git a/comm/mail/components/enterprisepolicies/tests/browser/policytest_v0.2.xpi b/comm/mail/components/enterprisepolicies/tests/browser/policytest_v0.2.xpi Binary files differnew file mode 100644 index 0000000000..59d589eba9 --- /dev/null +++ b/comm/mail/components/enterprisepolicies/tests/browser/policytest_v0.2.xpi 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] |