diff options
Diffstat (limited to '')
134 files changed, 14800 insertions, 0 deletions
diff --git a/browser/components/enterprisepolicies/Policies.sys.mjs b/browser/components/enterprisepolicies/Policies.sys.mjs new file mode 100644 index 0000000000..f7828d67ee --- /dev/null +++ b/browser/components/enterprisepolicies/Policies.sys.mjs @@ -0,0 +1,2765 @@ +/* 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"], + gXulStore: ["@mozilla.org/xul/xulstore;1", "nsIXULStore"], +}); + +ChromeUtils.defineESModuleGetters(lazy, { + BookmarksPolicies: "resource:///modules/policies/BookmarksPolicies.sys.mjs", + FileUtils: "resource://gre/modules/FileUtils.sys.mjs", + ProxyPolicies: "resource:///modules/policies/ProxyPolicies.sys.mjs", + WebsiteFilter: "resource:///modules/policies/WebsiteFilter.sys.mjs", +}); + +XPCOMUtils.defineLazyModuleGetters(lazy, { + AddonManager: "resource://gre/modules/AddonManager.jsm", + CustomizableUI: "resource:///modules/CustomizableUI.jsm", +}); + +const PREF_LOGLEVEL = "browser.policies.loglevel"; +const BROWSER_DOCUMENT_URL = AppConstants.BROWSER_CHROME_URL; +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.sys.mjs 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); + }, + }, + + AllowedDomainsForApps: { + onBeforeAddons(manager, param) { + Services.obs.addObserver(function(subject, topic, data) { + let channel = subject.QueryInterface(Ci.nsIHttpChannel); + if (channel.URI.host.endsWith(".google.com")) { + channel.setRequestHeader("X-GoogApps-Allowed-Domains", param, true); + } + }, "http-on-modify-request"); + }, + }, + + 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.jsm will check + // for this policy directly when determining the update URL. + }, + + AppUpdateURL: { + // No implementation needed here. UpdateService.jsm 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 + ); + } + }, + }, + + AutoLaunchProtocolsFromOrigins: { + onBeforeAddons(manager, param) { + for (let info of param) { + addAllowDenyPermissions( + `open-protocol-handler^${info.protocol}`, + info.allowed_origins + ); + } + }, + }, + + 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"); + manager.disallowFeature("aboutSupport"); + } + }, + }, + + Bookmarks: { + onAllWindowsRestored(manager, param) { + lazy.BookmarksPolicies.processBookmarks(param); + }, + }, + + 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.AllowSession) { + for (let origin of param.AllowSession) { + try { + Services.perms.addFromPrincipal( + Services.scriptSecurityManager.createContentPrincipalFromOrigin( + origin + ), + "cookie", + Ci.nsICookiePermission.ACCESS_SESSION, + Ci.nsIPermissionManager.EXPIRE_POLICY + ); + } catch (ex) { + lazy.log.error( + `Unable to add cookie session permission - ${origin.href}` + ); + } + } + } + + 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.ExpireAtSessionEnd != undefined) { + lazy.log.error( + "'ExpireAtSessionEnd' has been deprecated and it has no effect anymore." + ); + } + + // New Cookie Behavior option takes precendence + let defaultPref = Services.prefs.getDefaultBranch(""); + let newCookieBehavior = defaultPref.getIntPref( + "network.cookie.cookieBehavior" + ); + let newCookieBehaviorPB = defaultPref.getIntPref( + "network.cookie.cookieBehavior.pbmode" + ); + if ("Behavior" in param || "BehaviorPrivateBrowsing" in param) { + let behaviors = { + accept: Ci.nsICookieService.BEHAVIOR_ACCEPT, + "reject-foreign": Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN, + reject: Ci.nsICookieService.BEHAVIOR_REJECT, + "limit-foreign": Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN, + "reject-tracker": Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER, + "reject-tracker-and-partition-foreign": + Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN, + }; + if ("Behavior" in param) { + newCookieBehavior = behaviors[param.Behavior]; + } + if ("BehaviorPrivateBrowsing" in param) { + newCookieBehaviorPB = behaviors[param.BehaviorPrivateBrowsing]; + } + } else { + // Default, AcceptThirdParty, and RejectTracker are being + // deprecated in favor of Behavior. They will continue + // to be supported, though. + if ( + param.Default !== undefined || + param.AcceptThirdParty !== undefined || + param.RejectTracker !== undefined || + param.Locked + ) { + newCookieBehavior = Ci.nsICookieService.BEHAVIOR_ACCEPT; + if (param.Default !== undefined && !param.Default) { + newCookieBehavior = Ci.nsICookieService.BEHAVIOR_REJECT; + } else if (param.AcceptThirdParty) { + if (param.AcceptThirdParty == "never") { + newCookieBehavior = Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN; + } else if (param.AcceptThirdParty == "from-visited") { + newCookieBehavior = Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN; + } + } else if (param.RejectTracker) { + newCookieBehavior = Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER; + } + } + // With the old cookie policy, we made private browsing the same. + newCookieBehaviorPB = newCookieBehavior; + } + // We set the values no matter what just in case the policy was only used to lock. + PoliciesUtils.setDefaultPref( + "network.cookie.cookieBehavior", + newCookieBehavior, + param.Locked + ); + PoliciesUtils.setDefaultPref( + "network.cookie.cookieBehavior.pbmode", + newCookieBehaviorPB, + param.Locked + ); + }, + }, + + 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]); + } + }, + }, + + DisableDefaultBrowserAgent: { + // The implementation of this policy is in the default browser agent itself + // (/toolkit/mozapps/defaultagent); we need an entry for it here so that it + // shows up in about:policies as a real policy and not as an error. + }, + + 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"); + blockAboutPage(manager, "about:profiling"); + } + }, + }, + + DisableFeedbackCommands: { + onBeforeUIStartup(manager, param) { + if (param) { + manager.disallowFeature("feedbackCommands"); + } + }, + }, + + DisableFirefoxAccounts: { + onBeforeAddons(manager, param) { + if (param) { + setAndLockPref("identity.fxaccounts.enabled", false); + setAndLockPref("browser.aboutwelcome.enabled", false); + } + }, + }, + + DisableFirefoxScreenshots: { + onBeforeAddons(manager, param) { + if (param) { + setAndLockPref("extensions.screenshots.disabled", true); + } + }, + }, + + DisableFirefoxStudies: { + onBeforeAddons(manager, param) { + if (param) { + manager.disallowFeature("Shield"); + setAndLockPref( + "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons", + false + ); + setAndLockPref( + "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features", + false + ); + } + }, + }, + + DisableForgetButton: { + onProfileAfterChange(manager, param) { + if (param) { + setAndLockPref("privacy.panicButton.enabled", false); + } + }, + }, + + DisableFormHistory: { + onBeforeUIStartup(manager, param) { + if (param) { + setAndLockPref("browser.formfill.enable", false); + } + }, + }, + + DisableMasterPasswordCreation: { + onBeforeUIStartup(manager, param) { + if (param) { + manager.disallowFeature("createMasterPassword"); + } + }, + }, + + DisablePasswordReveal: { + onBeforeUIStartup(manager, param) { + if (param) { + manager.disallowFeature("passwordReveal"); + } + }, + }, + + DisablePocket: { + onBeforeAddons(manager, param) { + if (param) { + setAndLockPref("extensions.pocket.enabled", false); + } + }, + }, + + DisablePrivateBrowsing: { + onBeforeAddons(manager, param) { + if (param) { + manager.disallowFeature("privatebrowsing"); + blockAboutPage(manager, "about:privatebrowsing", true); + setAndLockPref("browser.privatebrowsing.autostart", false); + } + }, + }, + + DisableProfileImport: { + onBeforeUIStartup(manager, param) { + if (param) { + manager.disallowFeature("profileImport"); + setAndLockPref( + "browser.newtabpage.activity-stream.migrationExpired", + true + ); + } + }, + }, + + DisableProfileRefresh: { + onBeforeUIStartup(manager, param) { + if (param) { + manager.disallowFeature("profileRefresh"); + setAndLockPref("browser.disableResetPrompt", true); + } + }, + }, + + 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 + ); + } + }, + }, + + DisableSetDesktopBackground: { + onBeforeUIStartup(manager, param) { + if (param) { + manager.disallowFeature("setDesktopBackground"); + } + }, + }, + + 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"); + } + }, + }, + + DisableThirdPartyModuleBlocking: { + onBeforeUIStartup(manager, param) { + if (param) { + manager.disallowFeature("thirdPartyModuleBlocking"); + } + }, + }, + + DisplayBookmarksToolbar: { + onBeforeUIStartup(manager, param) { + let visibility; + if (typeof param === "boolean") { + visibility = param ? "always" : "newtab"; + } else { + visibility = param; + } + // This policy is meant to change the default behavior, not to force it. + // If this policy was already applied and the user chose to re-hide the + // bookmarks toolbar, do not show it again. + runOncePerModification("displayBookmarksToolbar", visibility, () => { + let visibilityPref = "browser.toolbars.bookmarks.visibility"; + Services.prefs.setCharPref(visibilityPref, visibility); + }); + }, + }, + + DisplayMenuBar: { + onBeforeUIStartup(manager, param) { + let value; + if ( + typeof param === "boolean" || + param == "default-on" || + param == "default-off" + ) { + switch (param) { + case "default-on": + value = "false"; + break; + case "default-off": + value = "true"; + break; + default: + value = (!param).toString(); + break; + } + // This policy is meant to change the default behavior, not to force it. + // If this policy was already applied and the user chose to re-hide the + // menu bar, do not show it again. + runOncePerModification("displayMenuBar", value, () => { + lazy.gXulStore.setValue( + BROWSER_DOCUMENT_URL, + "toolbar-menubar", + "autohide", + value + ); + }); + } else { + switch (param) { + case "always": + value = "false"; + break; + case "never": + // Make sure Alt key doesn't show the menubar + setAndLockPref("ui.key.menuAccessKeyFocuses", false); + value = "true"; + break; + } + lazy.gXulStore.setValue( + BROWSER_DOCUMENT_URL, + "toolbar-menubar", + "autohide", + value + ); + manager.disallowFeature("hideShowMenuBar"); + } + }, + }, + + 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 + ); + } + }, + }, + + DontCheckDefaultBrowser: { + onBeforeUIStartup(manager, param) { + setAndLockPref("browser.shell.checkDefaultBrowser", !param); + }, + }, + + 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); + }, + }, + + EnableTrackingProtection: { + onBeforeUIStartup(manager, param) { + if (param.Value) { + PoliciesUtils.setDefaultPref( + "privacy.trackingprotection.enabled", + true, + param.Locked + ); + PoliciesUtils.setDefaultPref( + "privacy.trackingprotection.pbmode.enabled", + true, + param.Locked + ); + } else { + setAndLockPref("privacy.trackingprotection.enabled", false); + setAndLockPref("privacy.trackingprotection.pbmode.enabled", false); + } + if ("Cryptomining" in param) { + PoliciesUtils.setDefaultPref( + "privacy.trackingprotection.cryptomining.enabled", + param.Cryptomining, + param.Locked + ); + } + if ("Fingerprinting" in param) { + PoliciesUtils.setDefaultPref( + "privacy.trackingprotection.fingerprinting.enabled", + param.Fingerprinting, + param.Locked + ); + } + if ("Exceptions" in param) { + addAllowDenyPermissions("trackingprotection", param.Exceptions); + } + }, + }, + + EncryptedMediaExtensions: { + onBeforeAddons(manager, param) { + let locked = false; + if ("Locked" in param) { + locked = param.Locked; + } + if ("Enabled" in param) { + PoliciesUtils.setDefaultPref( + "media.eme.enabled", + param.Enabled, + locked + ); + } + }, + }, + + ExemptDomainFileTypePairsFromFileTypeDownloadWarnings: { + // This policy is handled directly in EnterprisePoliciesParent.jsm + // and requires no validation (It's done by the schema). + }, + + 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); + } + }, + }, + + FirefoxHome: { + onBeforeAddons(manager, param) { + let locked = param.Locked || false; + if ("Search" in param) { + PoliciesUtils.setDefaultPref( + "browser.newtabpage.activity-stream.showSearch", + param.Search, + locked + ); + } + if ("TopSites" in param) { + PoliciesUtils.setDefaultPref( + "browser.newtabpage.activity-stream.feeds.topsites", + param.TopSites, + locked + ); + } + if ("SponsoredTopSites" in param) { + PoliciesUtils.setDefaultPref( + "browser.newtabpage.activity-stream.showSponsoredTopSites", + param.SponsoredTopSites, + locked + ); + } + if ("Highlights" in param) { + PoliciesUtils.setDefaultPref( + "browser.newtabpage.activity-stream.feeds.section.highlights", + param.Highlights, + locked + ); + } + if ("Pocket" in param) { + PoliciesUtils.setDefaultPref( + "browser.newtabpage.activity-stream.feeds.system.topstories", + param.Pocket, + locked + ); + PoliciesUtils.setDefaultPref( + "browser.newtabpage.activity-stream.feeds.section.topstories", + param.Pocket, + locked + ); + } + if ("SponsoredPocket" in param) { + PoliciesUtils.setDefaultPref( + "browser.newtabpage.activity-stream.showSponsored", + param.SponsoredPocket, + locked + ); + } + if ("Snippets" in param) { + PoliciesUtils.setDefaultPref( + "browser.newtabpage.activity-stream.feeds.snippets", + param.Snippets, + locked + ); + } + }, + }, + + FlashPlugin: { + onBeforeUIStartup(manager, param) { + addAllowDenyPermissions("plugin:flash", param.Allow, param.Block); + + const FLASH_NEVER_ACTIVATE = 0; + const FLASH_ASK_TO_ACTIVATE = 1; + + let flashPrefVal; + if (param.Default === undefined || param.Default) { + flashPrefVal = FLASH_ASK_TO_ACTIVATE; + } else { + flashPrefVal = FLASH_NEVER_ACTIVATE; + } + if (param.Locked) { + setAndLockPref("plugin.state.flash", flashPrefVal); + } else if (param.Default !== undefined) { + PoliciesUtils.setDefaultPref("plugin.state.flash", flashPrefVal); + } + }, + }, + + GoToIntranetSiteForSingleWordEntryInAddressBar: { + onBeforeAddons(manager, param) { + setAndLockPref("browser.fixup.dns_first_for_single_words", 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); + } + }, + }, + + Homepage: { + onBeforeUIStartup(manager, param) { + if ("StartPage" in param && param.StartPage == "none") { + // For blank startpage, we use about:blank rather + // than messing with browser.startup.page + param.URL = new URL("about:blank"); + } + // |homepages| will be a string containing a pipe-separated ('|') list of + // URLs because that is what the "Home page" section of about:preferences + // (and therefore what the pref |browser.startup.homepage|) accepts. + if ("URL" in param) { + let homepages = param.URL.href; + if (param.Additional && param.Additional.length) { + homepages += "|" + param.Additional.map(url => url.href).join("|"); + } + PoliciesUtils.setDefaultPref( + "browser.startup.homepage", + homepages, + param.Locked + ); + if (param.Locked) { + setAndLockPref( + "pref.browser.homepage.disable_button.current_page", + true + ); + setAndLockPref( + "pref.browser.homepage.disable_button.bookmark_page", + true + ); + setAndLockPref( + "pref.browser.homepage.disable_button.restore_default", + true + ); + } else { + // Clear out old run once modification that is no longer used. + clearRunOnceModification("setHomepage"); + } + // If a homepage has been set via policy, show the home button + if (param.URL != "about:blank") { + manager.disallowFeature("removeHomeButtonByDefault"); + } + } + if (param.StartPage) { + let prefValue; + switch (param.StartPage) { + case "homepage": + case "homepage-locked": + case "none": + prefValue = 1; + break; + case "previous-session": + prefValue = 3; + break; + } + PoliciesUtils.setDefaultPref( + "browser.startup.page", + prefValue, + param.StartPage == "homepage-locked" + ); + } + }, + }, + + 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"); + setAndLockPref( + "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons", + false + ); + setAndLockPref( + "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features", + false + ); + manager.disallowFeature("xpinstall"); + } + } + }, + }, + + LegacyProfiles: { + // Handled in nsToolkitProfileService.cpp (Windows only) + }, + + LegacySameSiteCookieBehaviorEnabled: { + onBeforeAddons(manager, param) { + PoliciesUtils.setDefaultPref( + "network.cookie.sameSite.laxByDefault", + !param + ); + }, + }, + + LegacySameSiteCookieBehaviorEnabledForDomainList: { + onBeforeAddons(manager, param) { + PoliciesUtils.setDefaultPref( + "network.cookie.sameSite.laxByDefault.disabledHosts", + param.join(",") + ); + }, + }, + + LocalFileLinks: { + onBeforeAddons(manager, param) { + // If there are existing capabilities, lock them with the policy pref. + let policyNames = Services.prefs + .getCharPref("capability.policy.policynames", "") + .split(" "); + policyNames.push("localfilelinks_policy"); + setAndLockPref("capability.policy.policynames", policyNames.join(" ")); + setAndLockPref( + "capability.policy.localfilelinks_policy.checkloaduri.enabled", + "allAccess" + ); + setAndLockPref( + "capability.policy.localfilelinks_policy.sites", + param.join(" ") + ); + }, + }, + + ManagedBookmarks: {}, + + ManualAppUpdateOnly: { + onBeforeAddons(manager, param) { + if (param) { + manager.disallowFeature("autoAppUpdateChecking"); + } + }, + }, + + NetworkPrediction: { + onBeforeAddons(manager, param) { + setAndLockPref("network.dns.disablePrefetch", !param); + setAndLockPref("network.dns.disablePrefetchFromHTTPS", !param); + }, + }, + + NewTabPage: { + onBeforeAddons(manager, param) { + setAndLockPref("browser.newtabpage.enabled", param); + }, + }, + + NoDefaultBookmarks: { + onProfileAfterChange(manager, param) { + if (param) { + manager.disallowFeature("defaultBookmarks"); + } + }, + }, + + 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); + } + }, + }, + + OverrideFirstRunPage: { + onProfileAfterChange(manager, param) { + let url = param ? param : ""; + setAndLockPref("startup.homepage_welcome_url", url); + setAndLockPref("browser.aboutwelcome.enabled", false); + }, + }, + + OverridePostUpdatePage: { + onProfileAfterChange(manager, param) { + let url = param ? param.href : ""; + setAndLockPref("startup.homepage_override_url", url); + // The pref startup.homepage_override_url is only used + // as a fallback when the update.xml file hasn't provided + // a specific post-update URL. + manager.disallowFeature("postUpdateCustomPage"); + }, + }, + + PasswordManagerEnabled: { + onBeforeUIStartup(manager, param) { + if (!param) { + blockAboutPage(manager, "about:logins", true); + setAndLockPref("pref.privacy.disable_button.view_passwords", true); + } + setAndLockPref("signon.rememberSignons", param); + }, + }, + + PasswordManagerExceptions: { + onBeforeUIStartup(manager, param) { + addAllowDenyPermissions("login-saving", null, param); + }, + }, + + PDFjs: { + onBeforeAddons(manager, param) { + if ("Enabled" in param) { + setAndLockPref("pdfjs.disabled", !param.Enabled); + } + if ("EnablePermissions" in param) { + setAndLockPref("pdfjs.enablePermissions", param.EnablePermissions); + } + }, + }, + + Permissions: { + onBeforeUIStartup(manager, param) { + if (param.Camera) { + addAllowDenyPermissions( + "camera", + param.Camera.Allow, + param.Camera.Block + ); + setDefaultPermission("camera", param.Camera); + } + + if (param.Microphone) { + addAllowDenyPermissions( + "microphone", + param.Microphone.Allow, + param.Microphone.Block + ); + setDefaultPermission("microphone", param.Microphone); + } + + if (param.Autoplay) { + addAllowDenyPermissions( + "autoplay-media", + param.Autoplay.Allow, + param.Autoplay.Block + ); + if ("Default" in param.Autoplay) { + let prefValue; + switch (param.Autoplay.Default) { + case "allow-audio-video": + prefValue = 0; + break; + case "block-audio": + prefValue = 1; + break; + case "block-audio-video": + prefValue = 5; + break; + } + PoliciesUtils.setDefaultPref( + "media.autoplay.default", + prefValue, + param.Autoplay.Locked + ); + } + } + + if (param.Location) { + addAllowDenyPermissions( + "geo", + param.Location.Allow, + param.Location.Block + ); + setDefaultPermission("geo", param.Location); + } + + if (param.Notifications) { + addAllowDenyPermissions( + "desktop-notification", + param.Notifications.Allow, + param.Notifications.Block + ); + setDefaultPermission("desktop-notification", param.Notifications); + } + + if ("VirtualReality" in param) { + addAllowDenyPermissions( + "xr", + param.VirtualReality.Allow, + param.VirtualReality.Block + ); + setDefaultPermission("xr", param.VirtualReality); + } + }, + }, + + PictureInPicture: { + onBeforeAddons(manager, param) { + if ("Enabled" in param) { + PoliciesUtils.setDefaultPref( + "media.videocontrols.picture-in-picture.video-toggle.enabled", + param.Enabled + ); + } + if (param.Locked) { + Services.prefs.lockPref( + "media.videocontrols.picture-in-picture.video-toggle.enabled" + ); + } + }, + }, + + PopupBlocking: { + onBeforeUIStartup(manager, param) { + addAllowDenyPermissions("popup", param.Allow, null); + + if (param.Locked) { + let blockValue = true; + if (param.Default !== undefined && !param.Default) { + blockValue = false; + } + setAndLockPref("dom.disable_open_during_load", blockValue); + } else if (param.Default !== undefined) { + PoliciesUtils.setDefaultPref( + "dom.disable_open_during_load", + !!param.Default + ); + } + }, + }, + + Preferences: { + onBeforeAddons(manager, param) { + const allowedPrefixes = [ + "accessibility.", + "app.update.", + "browser.", + "datareporting.policy.", + "dom.", + "extensions.", + "general.autoScroll", + "general.smoothScroll", + "geo.", + "gfx.", + "intl.", + "keyword.enabled", + "layers.", + "layout.", + "media.", + "network.", + "pdfjs.", + "places.", + "print.", + "signon.", + "spellchecker.", + "toolkit.legacyUserProfileCustomizations.stylesheets", + "ui.", + "widget.", + ]; + const allowedSecurityPrefs = [ + "security.block_fileuri_script_with_wrong_mime", + "security.default_personal_cert", + "security.insecure_connection_text.enabled", + "security.insecure_connection_text.pbmode.enabled", + "security.mixed_content.block_active_content", + "security.osclientcerts.autoload", + "security.ssl.errorReporting.enabled", + "security.tls.enable_0rtt_data", + "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", + "browser.vpn_promo.disallowed_regions", + ]; + + 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; + } + ); + }, + }, + + SanitizeOnShutdown: { + onBeforeUIStartup(manager, param) { + if (typeof param === "boolean") { + setAndLockPref("privacy.sanitize.sanitizeOnShutdown", param); + setAndLockPref("privacy.clearOnShutdown.cache", param); + setAndLockPref("privacy.clearOnShutdown.cookies", param); + setAndLockPref("privacy.clearOnShutdown.downloads", param); + setAndLockPref("privacy.clearOnShutdown.formdata", param); + setAndLockPref("privacy.clearOnShutdown.history", param); + setAndLockPref("privacy.clearOnShutdown.sessions", param); + setAndLockPref("privacy.clearOnShutdown.siteSettings", param); + setAndLockPref("privacy.clearOnShutdown.offlineApps", param); + } else { + let locked = true; + // Needed to preserve original behavior in perpetuity. + let lockDefaultPrefs = true; + if ("Locked" in param) { + locked = param.Locked; + lockDefaultPrefs = false; + } + PoliciesUtils.setDefaultPref( + "privacy.sanitize.sanitizeOnShutdown", + true, + locked + ); + if ("Cache" in param) { + PoliciesUtils.setDefaultPref( + "privacy.clearOnShutdown.cache", + param.Cache, + locked + ); + } else { + PoliciesUtils.setDefaultPref( + "privacy.clearOnShutdown.cache", + false, + lockDefaultPrefs + ); + } + if ("Cookies" in param) { + PoliciesUtils.setDefaultPref( + "privacy.clearOnShutdown.cookies", + param.Cookies, + locked + ); + } else { + PoliciesUtils.setDefaultPref( + "privacy.clearOnShutdown.cookies", + false, + lockDefaultPrefs + ); + } + if ("Downloads" in param) { + PoliciesUtils.setDefaultPref( + "privacy.clearOnShutdown.downloads", + param.Downloads, + locked + ); + } else { + PoliciesUtils.setDefaultPref( + "privacy.clearOnShutdown.downloads", + false, + lockDefaultPrefs + ); + } + if ("FormData" in param) { + PoliciesUtils.setDefaultPref( + "privacy.clearOnShutdown.formdata", + param.FormData, + locked + ); + } else { + PoliciesUtils.setDefaultPref( + "privacy.clearOnShutdown.formdata", + false, + lockDefaultPrefs + ); + } + if ("History" in param) { + PoliciesUtils.setDefaultPref( + "privacy.clearOnShutdown.history", + param.History, + locked + ); + } else { + PoliciesUtils.setDefaultPref( + "privacy.clearOnShutdown.history", + false, + lockDefaultPrefs + ); + } + if ("Sessions" in param) { + PoliciesUtils.setDefaultPref( + "privacy.clearOnShutdown.sessions", + param.Sessions, + locked + ); + } else { + PoliciesUtils.setDefaultPref( + "privacy.clearOnShutdown.sessions", + false, + lockDefaultPrefs + ); + } + if ("SiteSettings" in param) { + PoliciesUtils.setDefaultPref( + "privacy.clearOnShutdown.siteSettings", + param.SiteSettings, + locked + ); + } + if ("OfflineApps" in param) { + PoliciesUtils.setDefaultPref( + "privacy.clearOnShutdown.offlineApps", + param.OfflineApps, + locked + ); + } + } + }, + }, + + SearchBar: { + onAllWindowsRestored(manager, param) { + // This policy is meant to change the default behavior, not to force it. + // If this policy was already applied and the user chose move the search + // bar, don't move it again. + runOncePerModification("searchInNavBar", param, () => { + if (param == "separate") { + lazy.CustomizableUI.addWidgetToArea( + "search-container", + lazy.CustomizableUI.AREA_NAVBAR, + lazy.CustomizableUI.getPlacementOfWidget("urlbar-container") + .position + 1 + ); + } else if (param == "unified") { + lazy.CustomizableUI.removeWidgetFromArea("search-container"); + } + }); + }, + }, + + 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 + ); + } + } + } + ); + } + }); + }, + }, + + SearchSuggestEnabled: { + onBeforeAddons(manager, param) { + setAndLockPref("browser.urlbar.suggest.searches", param); + setAndLockPref("browser.search.suggest.enabled", param); + }, + }, + + SecurityDevices: { + onProfileAfterChange(manager, param) { + let securityDevices = param; + let pkcs11db = Cc["@mozilla.org/security/pkcs11moduledb;1"].getService( + Ci.nsIPKCS11ModuleDB + ); + for (let deviceName in securityDevices) { + let foundModule = false; + for (let module of pkcs11db.listModules()) { + if (module && module.libName === securityDevices[deviceName]) { + foundModule = true; + break; + } + } + if (foundModule) { + continue; + } + try { + pkcs11db.addModule(deviceName, securityDevices[deviceName], 0, 0); + } catch (ex) { + lazy.log.error(`Unable to add security device ${deviceName}`); + lazy.log.debug(ex); + } + } + }, + }, + + ShowHomeButton: { + onBeforeAddons(manager, param) { + if (param) { + manager.disallowFeature("removeHomeButtonByDefault"); + } + }, + onAllWindowsRestored(manager, param) { + if (param) { + let homeButtonPlacement = lazy.CustomizableUI.getPlacementOfWidget( + "home-button" + ); + if (!homeButtonPlacement) { + let placement = lazy.CustomizableUI.getPlacementOfWidget( + "forward-button" + ); + lazy.CustomizableUI.addWidgetToArea( + "home-button", + lazy.CustomizableUI.AREA_NAVBAR, + placement.position + 2 + ); + } + } else { + lazy.CustomizableUI.removeWidgetFromArea("home-button"); + } + }, + }, + + 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); + }, + }, + + StartDownloadsInTempDirectory: { + onBeforeAddons(manager, param) { + setAndLockPref("browser.download.start_downloads_in_tmp_dir", param); + }, + }, + + SupportMenu: { + onProfileAfterChange(manager, param) { + manager.setSupportMenu(param); + }, + }, + + UserMessaging: { + onBeforeAddons(manager, param) { + let locked = false; + if ("Locked" in param) { + locked = param.Locked; + } + if ("WhatsNew" in param) { + PoliciesUtils.setDefaultPref( + "browser.messaging-system.whatsNewPanel.enabled", + param.WhatsNew, + locked + ); + } + if ("ExtensionRecommendations" in param) { + PoliciesUtils.setDefaultPref( + "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons", + param.ExtensionRecommendations, + locked + ); + } + if ("FeatureRecommendations" in param) { + PoliciesUtils.setDefaultPref( + "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features", + param.FeatureRecommendations, + locked + ); + } + if ("UrlbarInterventions" in param && !param.UrlbarInterventions) { + manager.disallowFeature("urlbarinterventions"); + } + if ("SkipOnboarding" in param) { + PoliciesUtils.setDefaultPref( + "browser.aboutwelcome.enabled", + !param.SkipOnboarding, + locked + ); + } + if ("MoreFromMozilla" in param) { + PoliciesUtils.setDefaultPref( + "browser.preferences.moreFromMozilla", + param.MoreFromMozilla, + locked + ); + } + }, + }, + + UseSystemPrintDialog: { + onBeforeAddons(manager, param) { + setAndLockPref("print.prefer_system_dialog", param); + }, + }, + + WebsiteFilter: { + onBeforeUIStartup(manager, param) { + lazy.WebsiteFilter.init(param.Block || [], param.Exceptions || []); + }, + }, + + WindowsSSO: { + onBeforeAddons(manager, param) { + setAndLockPref("network.http.windows-sso.enabled", param); + }, + }, +}; + +/* + * ==================== + * = 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); + } + }, +}; + +/** + * setDefaultPermission + * + * Helper function to set preferences appropriately for the policy + * + * @param {string} policyName + * The name of the policy to set + * @param {object} policyParam + * The object containing param for the policy + */ +function setDefaultPermission(policyName, policyParam) { + if ("BlockNewRequests" in policyParam) { + let prefName = "permissions.default." + policyName; + + if (policyParam.BlockNewRequests) { + PoliciesUtils.setDefaultPref(prefName, 2, policyParam.Locked); + } else { + PoliciesUtils.setDefaultPref(prefName, 0, policyParam.Locked); + } + } +} + +/** + * 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) { + // It's possible if the origin was invalid, we'll have a string instead of an origin. + lazy.log.error( + `Unable to add ${permissionName} permission for ${origin.href || + origin}` + ); + } + } + + 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 {Functon} callback + * The callback to run only once. + */ +// eslint-disable-next-line no-unused-vars +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}", + Services.dirsvc.get("Home", Ci.nsIFile).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}`); + }, + }; + // If it's a local file install, onDownloadEnded is never called. + // So we call it manually, to handle some error cases. + if (url.startsWith("file:")) { + listener.onDownloadEnded(install); + if (install.state == lazy.AddonManager.STATE_CANCELLED) { + return; + } + } + 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; + } + let contentLocationSpec = contentLocation.spec.toLowerCase(); + if ( + gBlockedAboutPages.some(function(aboutPage) { + return contentLocationSpec.startsWith(aboutPage.toLowerCase()); + }) + ) { + 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/browser/components/enterprisepolicies/content/aboutPolicies.css b/browser/components/enterprisepolicies/content/aboutPolicies.css new file mode 100644 index 0000000000..e84ae785ee --- /dev/null +++ b/browser/components/enterprisepolicies/content/aboutPolicies.css @@ -0,0 +1,170 @@ +/* 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 url("chrome://global/skin/in-content/common.css"); + +@media not print { + html, body { + height: 100%; + } +} + +body { + display: flex; + align-items: stretch; +} + +#sectionTitle { + float: inline-start; + padding-inline-start: 1rem; +} + +/** Categories **/ + +.category { + cursor: pointer; + /* Center category names */ + display: flex; + align-items: center; +} + +.category .category-name { + pointer-events: none; +} + +#category-active { + background-image: url("chrome://browser/content/policies/policies-active.svg"); +} + +#category-documentation { + background-image: url("chrome://browser/content/policies/policies-documentation.svg"); +} + +#category-errors { + background-image: url("chrome://browser/content/policies/policies-error.svg"); +} + +/** Content area **/ + +.main-content { + flex: 1; + scroll-padding: 25px; +} + +.tab { + padding: 0.5em 0; +} + +.tab table { + width: 100%; +} + +tbody tr { + transition: background cubic-bezier(.07, .95, 0, 1) 250ms; +} + +tbody tr:hover { + background-color: var(--in-content-item-hover); + color: var(--in-content-item-hover-text); +} + +th, td, table { + border-collapse: collapse; + border: none; + text-align: start; +} + +th { + padding: 1rem; + font-size: larger; +} + +td { + padding: 1rem; +} + +/* + * In Documentation Tab, this property sets the policies row in an + * alternate color scheme of white and grey as each policy comprises + * of two tbody tags, one for the description and the other for the + * collapsible information block. + */ + +.active-policies tr.odd:not(:hover), +.errors tr:nth-child(odd):not(:hover), +tbody:nth-child(4n + 1) { + background-color: var(--in-content-box-background-odd); +} + +.arr_sep.odd:not(:last-child) td:not(:first-child) { + border-bottom: 2px solid #f9f9fa; +} + +.arr_sep.even:not(:last-child) td:not(:first-child) { + border-bottom: 2px solid #ededf0; +} + +.last_row:not(:last-child) td { + border-bottom: 2px solid #d7d7db !important; +} + +.icon { + background-position: center center; + background-repeat: no-repeat; + background-size: 16px; + -moz-context-properties: fill; + display: inline-block; + fill: var(--newtab-icon-primary-color); + height: 14px; + vertical-align: middle; + width: 14px; + margin-top: -.125rem; + margin-left: .5rem; +} + +.collapsible { + cursor: pointer; + border: none; + outline: none; +} + +.content { + display: none; +} + +.content-style { + background-color: var(--in-content-box-background); + color: var(--blue-50); +} + +tbody.collapsible td { + padding-bottom: 1rem; +} + +.schema { + font-family: monospace; + white-space: pre; + direction: ltr; +} + +/* + * The Active tab has two messages: one for when the policy service + * is inactive and another for when the there are no specified + * policies. The three classes below control which message to display + * or to show the policy table. + */ +.no-specified-policies > table, +.inactive-service > table { + display: none; +} + +:not(.no-specified-policies) > .no-specified-policies-message, +:not(.inactive-service) > .inactive-service-message { + display: none; +} + +.no-specified-policies-message, +.inactive-service-message { + padding: 1rem; +} diff --git a/browser/components/enterprisepolicies/content/aboutPolicies.html b/browser/components/enterprisepolicies/content/aboutPolicies.html new file mode 100644 index 0000000000..83ae157605 --- /dev/null +++ b/browser/components/enterprisepolicies/content/aboutPolicies.html @@ -0,0 +1,76 @@ +<!-- +# 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> + <head> + <meta charset="utf-8"> + <meta http-equiv="Content-Security-Policy" content="default-src chrome:; object-src 'none'"> + <meta name="color-scheme" content="light dark"> + <title data-l10n-id="about-policies-title"></title> + <link rel="stylesheet" href="chrome://browser/content/policies/aboutPolicies.css"> + <link rel="localization" href="branding/brand.ftl"> + <link rel="localization" href="browser/branding/brandings.ftl"> + <link rel="localization" href="browser/branding/sync-brand.ftl"> + <link rel="localization" href="browser/aboutPolicies.ftl"> + <link rel="localization" href="browser/policies/policies-descriptions.ftl"> + <script src="chrome://browser/content/policies/aboutPolicies.js"></script> + </head> + <body id="body"> + <div id="categories"> + <div class="category" selected="true" id="category-active" tabindex="0"> + <label class="category-name" data-l10n-id="active-policies-tab"></label> + </div> + <div class="category" id="category-documentation" tabindex="0"> + <label class="category-name" data-l10n-id="documentation-tab"></label> + </div> + <div class="category" id="category-errors" tabindex="0"> + <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> + + <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> + <th data-l10n-id="policy-value"></th> + </tr> + </thead> + <tbody id="activeContent"></tbody> + </table> + </div> + + <div id="documentation" class="tab" hidden="true"> + <table> + <thead> + <tr> + <th data-l10n-id="policy-name"></th> + </tr> + </thead> + <tbody id="documentationContent"></tbody> + </table> + </div> + + <div id="errors" class="tab" hidden="true"> + <table> + <thead> + <tr> + <th data-l10n-id="policy-errors"></th> + </tr> + </thead> + <tbody id="errorsContent"></tbody> + </table> + </div> + </div> + </body> +</html> diff --git a/browser/components/enterprisepolicies/content/aboutPolicies.js b/browser/components/enterprisepolicies/content/aboutPolicies.js new file mode 100644 index 0000000000..1de10d74b2 --- /dev/null +++ b/browser/components/enterprisepolicies/content/aboutPolicies.js @@ -0,0 +1,429 @@ +/* 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" +); + +XPCOMUtils.defineLazyModuleGetters(this, { + schema: "resource:///modules/policies/schema.jsm", +}); + +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 link(text) { + let column = document.createElement("td"); + let a = document.createElement("a"); + a.href = + "https://github.com/mozilla/policy-templates/blob/master/README.md#" + text; + a.target = "_blank"; + let content = document.createTextNode(text); + a.appendChild(content); + column.appendChild(a); + 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 (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 (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) { + 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", + DisableMasterPasswordCreation: "DisablePrimaryPasswordCreation", + DisableSetDesktopBackground: "DisableSetAsDesktopBackground", + FirefoxHome: "FirefoxHome2", + Permissions: "Permissions2", + SanitizeOnShutdown: "SanitizeOnShutdown2", + WindowsSSO: "Windows10SSO", + }; + + for (let policyName in 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(link(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 (schema.properties[policyName].properties) { + let column = col( + JSON.stringify(schema.properties[policyName].properties, null, 1), + "schema" + ); + column.colSpan = "2"; + schema_row.appendChild(column); + sec_tbody.appendChild(schema_row); + } else if (schema.properties[policyName].items) { + let column = col( + JSON.stringify(schema.properties[policyName], null, 1), + "schema" + ); + column.colSpan = "2"; + schema_row.appendChild(column); + sec_tbody.appendChild(schema_row); + } else { + let column = col("type: " + schema.properties[policyName].type, "schema"); + column.colSpan = "2"; + schema_row.appendChild(column); + sec_tbody.appendChild(schema_row); + if (schema.properties[policyName].enum) { + let enum_row = document.createElement("tr"); + column = col( + "enum: " + + JSON.stringify(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; +window.onload = function() { + 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)); + category.addEventListener("keypress", function(event) { + if (event.keyCode == KeyEvent.DOM_VK_RETURN) { + 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.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/browser/components/enterprisepolicies/content/policies-active.svg b/browser/components/enterprisepolicies/content/policies-active.svg new file mode 100644 index 0000000000..9f60117649 --- /dev/null +++ b/browser/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 10c-.5522847 0-1-.44771525-1-1v-4h-7v8c0 .5522847.44771525 1 1 1h2c.55228475 0 1 .4477153 1 1s-.44771525 1-1 1h-3c-1.1045695 0-2-.8954305-2-2v-10c0-1.1045695.8954305-2 2-2h1.05c.23659623-1.16516199 1.26105919-2.00250628 2.45-2.00250628s2.21340377.83734429 2.45 2.00250628h1.05c1.1045695 0 2 .8954305 2 2v5c0 .55228475-.4477153 1-1 1zm-1-6v-1h-1.051c-.47526862.000097-.88494628-.33433375-.98-.8-.14293517-.69793844-.7570756-1.19905191-1.4695-1.19905191s-1.32656483.50111347-1.4695 1.19905191c-.09505372.46566625-.50473138.800097-.98.8h-1.05v1zm-3.5-2c.27614237 0 .5.22385763.5.5s-.22385763.5-.5.5-.5-.22385763-.5-.5.22385763-.5.5-.5zm-2 5c-.27614237 0-.5-.22385763-.5-.5s.22385763-.5.5-.5h4c.27614237 0 .5.22385763.5.5s-.22385763.5-.5.5zm0 2c-.27614237 0-.5-.22385763-.5-.5s.22385763-.5.5-.5h2c.27614237 0 .5.22385763.5.5s-.22385763.5-.5.5zm0 2c-.27614237 0-.5-.2238576-.5-.5s.22385763-.5.5-.5h3c.27614237 0 .5.2238576.5.5s-.22385763.5-.5.5zm5.16250363 4.9969649c-.17706448-.0000378-.34686306-.070407-.47204764-.1956294l-2.00303103-2.003031c-.25303103-.2619823-.24941233-.6784164.00813326-.935962s.67397967-.2611643.93596203-.0081333l1.44017931 1.4401793 4.21704794-6.02444961c.2127301-.29815587.6259441-.36927468.9261129-.15939456.3001689.20988012.3752209.62239779.1682098.92455241l-4.6737391 6.67677006c-.1126024.1627768-.29162177.2672048-.48873957.2850981-.01935032.0009766-.03873758.0009766-.0580879 0z"/> +</svg> diff --git a/browser/components/enterprisepolicies/content/policies-documentation.svg b/browser/components/enterprisepolicies/content/policies-documentation.svg new file mode 100644 index 0000000000..455a59b999 --- /dev/null +++ b/browser/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 7.00250628c-.5522847 0-1-.44771525-1-1v-1h-7v8.00000002c0 .5522847.44771525 1 1 1h2c.55228475 0 1 .4477152 1 1 0 .5522847-.44771525 1-1 1h-3c-1.1045695 0-2-.8954305-2-2v-10.00000002c0-1.1045695.8954305-2 2-2h1.05c.23659623-1.16516199 1.26105919-2.00250628 2.45-2.00250628s2.21340377.83734429 2.45 2.00250628h1.05c1.1045695 0 2 .8954305 2 2v2c0 .55228475-.4477153 1-1 1zm-1-3v-1h-1.051c-.47526862.000097-.88494628-.33433374-.98-.8-.14293517-.69793844-.7570756-1.19905191-1.4695-1.19905191s-1.32656483.50111347-1.4695 1.19905191c-.09505372.46566626-.50473138.800097-.98.8h-1.05v1zm-3.5-2c.27614237 0 .5.22385763.5.5 0 .27614238-.22385763.5-.5.5s-.5-.22385762-.5-.5c0-.27614237.22385763-.5.5-.5zm-2 5c-.27614237 0-.5-.22385762-.5-.5 0-.27614237.22385763-.5.5-.5h4c.27614237 0 .5.22385763.5.5 0 .27614238-.22385763.5-.5.5zm0 2c-.27614237 0-.5-.22385762-.5-.5 0-.27614237.22385763-.5.5-.5h2c.27614237 0 .5.22385763.5.5 0 .27614238-.22385763.5-.5.5zm0 2.00000002c-.27614237 0-.5-.2238576-.5-.5s.22385763-.5.5-.5h1c.27614237 0 .5.2238576.5.5s-.22385763.5-.5.5zm6.5 4.9974937c-2.2092 0-4-1.7908-4-4s1.7908-4 4-4 4 1.7908 4 4-1.7908 4-4 4zm.46-2c0-.254051-.205949-.46-.46-.46s-.46.205949-.46.46.205949.46.46.46.46-.205949.46-.46zm-1.06-3c0-.3056.2464-.6.6-.6s.6.2944.6.6c0 .1244-.0576.254-.1632.3896-.0796.1016-.1544.172-.228.24-.0308.0288-.0612.0572-.092.0876-.0062816.0061251-.0126828.0121262-.0192.018-.052.0468-.1864.168-.2792.3028-.1304.1896-.2184.434-.2184.762 0 .2209139.1790861.4.4.4s.4-.1790861.4-.4c0-.1724.0424-.258.0776-.3088.0204-.0296.0456-.0576.0788-.09.016-.0156.032-.03.0516-.048l.0012-.0008.0036-.0032c.02-.018.0456-.0416.07-.0664l.0344-.032c.1262825-.1138504.2434416-.2374293.3504-.3696.16-.2048.3324-.5056.3324-.8812 0-.6944-.5536-1.4-1.4-1.4s-1.4.7056-1.4 1.4c0 .2209139.1790861.4.4.4s.4-.1790861.4-.4z"/> +</svg> diff --git a/browser/components/enterprisepolicies/content/policies-error.svg b/browser/components/enterprisepolicies/content/policies-error.svg new file mode 100644 index 0000000000..2024f793f3 --- /dev/null +++ b/browser/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 7.00250628c-.5522847 0-1-.44771525-1-1v-1h-7v8.00000002c0 .5522847.44771525 1 1 1h1c.55228475 0 1 .4477152 1 1 0 .5522847-.44771525 1-1 1h-2c-1.1045695 0-2-.8954305-2-2v-10.00000002c0-1.1045695.8954305-2 2-2h1.05c.23659623-1.16516199 1.26105919-2.00250628 2.45-2.00250628s2.21340377.83734429 2.45 2.00250628h1.05c1.1045695 0 2 .8954305 2 2v2c0 .55228475-.4477153 1-1 1zm-1-3v-1h-1.051c-.47526862.000097-.88494628-.33433374-.98-.8-.14293517-.69793844-.7570756-1.19905191-1.4695-1.19905191s-1.32656483.50111347-1.4695 1.19905191c-.09505372.46566626-.50473138.800097-.98.8h-1.05v1zm-3.5-2c.27614237 0 .5.22385763.5.5 0 .27614238-.22385763.5-.5.5s-.5-.22385762-.5-.5c0-.27614237.22385763-.5.5-.5zm-2 5c-.27614237 0-.5-.22385762-.5-.5 0-.27614237.22385763-.5.5-.5h4c.27614237 0 .5.22385763.5.5 0 .27614238-.22385763.5-.5.5zm0 2c-.27614237 0-.5-.22385762-.5-.5 0-.27614237.22385763-.5.5-.5h3c.27614237 0 .5.22385763.5.5 0 .27614238-.22385763.5-.5.5zm0 2.00000002c-.27614237 0-.5-.2238576-.5-.5s.22385763-.5.5-.5h2c.27614237 0 .5.2238576.5.5s-.22385763.5-.5.5zm10.4094737 3.3385315c.1818196.3660605.1556801.8011195-.0686611 1.1427768-.2243412.3416572-.6131685.5385659-1.0213389.5172232h-5.70000002c-.39222659-.0104155-.75207561-.2200879-.95454083-.5561802-.20246523-.3360923-.21960272-.7522174-.04545917-1.1038198l2.85-5.69999999c.19423612-.39115456.59327402-.63853153 1.03000002-.63853153.4367259 0 .8357638.24737697 1.03.63853153zm-4.45-3.78v1.73c.0381214.2884432.2840486.5040066.575.5040066s.5368786-.2155634.575-.5040066v-1.73c.0295426-.2235324-.0731401-.4439283-.2632845-.56510838-.1901445-.12118005-.4332866-.12118005-.623431 0-.1901445.12118008-.2928271.34157598-.2632845.56510838zm.57 4.13c.3755536 0 .68-.3044464.68-.68 0-.2750343-.1656767-.522987-.4197753-.6282381s-.5465787-.0470731-.7410573.1474055c-.1944787.1944786-.2526566.4869588-.1474055.7410573.1052511.2540986.3532038.4197753.6282381.4197753z"/> +</svg> diff --git a/browser/components/enterprisepolicies/docs/index.rst b/browser/components/enterprisepolicies/docs/index.rst new file mode 100644 index 0000000000..c9528161d8 --- /dev/null +++ b/browser/components/enterprisepolicies/docs/index.rst @@ -0,0 +1,18 @@ +Enterprise Policies +=================== + +Introduction +------------ + +Firefox provides policies to manage various aspects of Firefox. The best documentation is in `GitHub <https://github.com/mozilla/policy-templates/>`_. + +Kiosk Mode +---------- + +The kiosk mode provided by Firefox on the command line (--kiosk) is a very basic mode intended for kiosks where the content loaded in the browser is strictly limited by the owner of the kiosk and either there is no keyboard or keyboard access is limited (particularly Ctrl and Alt). It is their responsibility to ensure the content does not surprise/confuse users or break browser UI in this setup. + +It does three main things: + +1. Switch all main browser windows (not popup windows) into full screen mode. +2. Don't show the context menu. +3. Don't show status for URLs or pageloading. diff --git a/browser/components/enterprisepolicies/helpers/BookmarksPolicies.sys.mjs b/browser/components/enterprisepolicies/helpers/BookmarksPolicies.sys.mjs new file mode 100644 index 0000000000..47b706c7b1 --- /dev/null +++ b/browser/components/enterprisepolicies/helpers/BookmarksPolicies.sys.mjs @@ -0,0 +1,301 @@ +/* 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/. */ + +/* + * A Bookmark object received through the policy engine will be an + * object with the following properties: + * + * - URL (URL) + * (required) The URL for this bookmark + * + * - Title (string) + * (required) The title for this bookmark + * + * - Placement (string) + * (optional) Either "toolbar" or "menu". If missing or invalid, + * "toolbar" will be used + * + * - Folder (string) + * (optional) The name of the folder to put this bookmark into. + * If present, a folder with this name will be created in the + * chosen placement above, and the bookmark will be created there. + * If missing, the bookmark will be created directly into the + * chosen placement. + * + * - Favicon (URL) + * (optional) An http:, https: or data: URL with the favicon. + * If possible, we recommend against using this property, in order + * to keep the json file small. + * If a favicon is not provided through the policy, it will be loaded + * naturally after the user first visits the bookmark. + * + * + * Note: The Policy Engine automatically converts the strings given to + * the URL and favicon properties into a URL object. + * + * The schema for this object is defined in policies-schema.json. + */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs", +}); + +const PREF_LOGLEVEL = "browser.policies.loglevel"; + +XPCOMUtils.defineLazyGetter(lazy, "log", () => { + let { ConsoleAPI } = ChromeUtils.importESModule( + "resource://gre/modules/Console.sys.mjs" + ); + return new ConsoleAPI({ + prefix: "BookmarksPolicies.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, + }); +}); + +export const BookmarksPolicies = { + // These prefixes must only contain characters + // allowed by PlacesUtils.isValidGuid + BOOKMARK_GUID_PREFIX: "PolB-", + FOLDER_GUID_PREFIX: "PolF-", + + /* + * Process the bookmarks specified by the policy engine. + * + * @param param + * This will be an array of bookmarks objects, as + * described on the top of this file. + */ + processBookmarks(param) { + calculateLists(param).then(async function addRemoveBookmarks(results) { + for (let bookmark of results.add.values()) { + await insertBookmark(bookmark).catch(lazy.log.error); + } + for (let bookmark of results.remove.values()) { + await lazy.PlacesUtils.bookmarks.remove(bookmark).catch(lazy.log.error); + } + for (let bookmark of results.emptyFolders.values()) { + await lazy.PlacesUtils.bookmarks.remove(bookmark).catch(lazy.log.error); + } + + lazy.gFoldersMapPromise.then(map => map.clear()); + }); + }, +}; + +/* + * This function calculates the differences between the existing bookmarks + * that are managed by the policy engine (which are known through a guid + * prefix) and the specified bookmarks in the policy file. + * They can differ if the policy file has changed. + * + * @param specifiedBookmarks + * This will be an array of bookmarks objects, as + * described on the top of this file. + */ +async function calculateLists(specifiedBookmarks) { + // --------- STEP 1 --------- + // Build two Maps (one with the existing bookmarks, another with + // the specified bookmarks), to make iteration quicker. + + // LIST A + // MAP of url (string) -> bookmarks objects from the Policy Engine + let specifiedBookmarksMap = new Map(); + for (let bookmark of specifiedBookmarks) { + specifiedBookmarksMap.set(bookmark.URL.href, bookmark); + } + + // LIST B + // MAP of url (string) -> bookmarks objects from Places + let existingBookmarksMap = new Map(); + await lazy.PlacesUtils.bookmarks.fetch( + { guidPrefix: BookmarksPolicies.BOOKMARK_GUID_PREFIX }, + bookmark => existingBookmarksMap.set(bookmark.url.href, bookmark) + ); + + // --------- STEP 2 --------- + // + // /=====/====\=====\ + // / / \ \ + // | | | | + // | A | {} | B | + // | | | | + // \ \ / / + // \=====\====/=====/ + // + // Find the intersection of the two lists. Items in the intersection + // are removed from the original lists. + // + // The items remaining in list A are new bookmarks to be added. + // The items remaining in list B are old bookmarks to be removed. + // + // There's nothing to do with items in the intersection, so there's no + // need to keep track of them. + // + // BONUS: It's necessary to keep track of the folder names that were + // seen, to make sure we remove the ones that were left empty. + + let foldersSeen = new Set(); + + for (let [url, item] of specifiedBookmarksMap) { + foldersSeen.add(item.Folder); + + if (existingBookmarksMap.has(url)) { + lazy.log.debug(`Bookmark intersection: ${url}`); + // If this specified bookmark exists in the existing bookmarks list, + // we can remove it from both lists as it's in the intersection. + specifiedBookmarksMap.delete(url); + existingBookmarksMap.delete(url); + } + } + + for (let url of specifiedBookmarksMap.keys()) { + lazy.log.debug(`Bookmark to add: ${url}`); + } + + for (let url of existingBookmarksMap.keys()) { + lazy.log.debug(`Bookmark to remove: ${url}`); + } + + // SET of folders to be deleted (bookmarks object from Places) + let foldersToRemove = new Set(); + + // If no bookmarks will be deleted, then no folder will + // need to be deleted either, so this next section can be skipped. + if (existingBookmarksMap.size > 0) { + await lazy.PlacesUtils.bookmarks.fetch( + { guidPrefix: BookmarksPolicies.FOLDER_GUID_PREFIX }, + folder => { + if (!foldersSeen.has(folder.title)) { + lazy.log.debug(`Folder to remove: ${folder.title}`); + foldersToRemove.add(folder); + } + } + ); + } + + return { + add: specifiedBookmarksMap, + remove: existingBookmarksMap, + emptyFolders: foldersToRemove, + }; +} + +async function insertBookmark(bookmark) { + let parentGuid = await getParentGuid(bookmark.Placement, bookmark.Folder); + + await lazy.PlacesUtils.bookmarks.insert({ + url: Services.io.newURI(bookmark.URL.href), + title: bookmark.Title, + guid: lazy.PlacesUtils.generateGuidWithPrefix( + BookmarksPolicies.BOOKMARK_GUID_PREFIX + ), + parentGuid, + }); + + if (bookmark.Favicon) { + setFaviconForBookmark(bookmark); + } +} + +function setFaviconForBookmark(bookmark) { + let faviconURI; + let nullPrincipal = Services.scriptSecurityManager.createNullPrincipal({}); + + switch (bookmark.Favicon.protocol) { + case "data:": + // data urls must first call replaceFaviconDataFromDataURL, using a + // fake URL. Later, it's needed to call setAndFetchFaviconForPage + // with the same URL. + faviconURI = Services.io.newURI("fake-favicon-uri:" + bookmark.URL.href); + + lazy.PlacesUtils.favicons.replaceFaviconDataFromDataURL( + faviconURI, + bookmark.Favicon.href, + 0 /* max expiration length */, + nullPrincipal + ); + break; + + case "http:": + case "https:": + faviconURI = Services.io.newURI(bookmark.Favicon.href); + break; + + default: + lazy.log.error( + `Bad URL given for favicon on bookmark "${bookmark.Title}"` + ); + return; + } + + lazy.PlacesUtils.favicons.setAndFetchFaviconForPage( + Services.io.newURI(bookmark.URL.href), + faviconURI, + false /* forceReload */, + lazy.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, + null, + nullPrincipal + ); +} + +// Cache of folder names to guids to be used by the getParentGuid +// function. The name consists in the parentGuid (which should always +// be the menuGuid or the toolbarGuid) + the folder title. This is to +// support having the same folder name in both the toolbar and menu. +XPCOMUtils.defineLazyGetter(lazy, "gFoldersMapPromise", () => { + return new Promise(resolve => { + let foldersMap = new Map(); + return lazy.PlacesUtils.bookmarks + .fetch( + { + guidPrefix: BookmarksPolicies.FOLDER_GUID_PREFIX, + }, + result => { + foldersMap.set(`${result.parentGuid}|${result.title}`, result.guid); + } + ) + .then(() => resolve(foldersMap)); + }); +}); + +async function getParentGuid(placement, folderTitle) { + // Defaults to toolbar if no placement was given. + let parentGuid = + placement == "menu" + ? lazy.PlacesUtils.bookmarks.menuGuid + : lazy.PlacesUtils.bookmarks.toolbarGuid; + + if (!folderTitle) { + // If no folderTitle is given, this bookmark is to be placed directly + // into the toolbar or menu. + return parentGuid; + } + + let foldersMap = await lazy.gFoldersMapPromise; + let folderName = `${parentGuid}|${folderTitle}`; + + if (foldersMap.has(folderName)) { + return foldersMap.get(folderName); + } + + let guid = lazy.PlacesUtils.generateGuidWithPrefix( + BookmarksPolicies.FOLDER_GUID_PREFIX + ); + await lazy.PlacesUtils.bookmarks.insert({ + type: lazy.PlacesUtils.bookmarks.TYPE_FOLDER, + title: folderTitle, + guid, + parentGuid, + }); + + foldersMap.set(folderName, guid); + return guid; +} diff --git a/browser/components/enterprisepolicies/helpers/ProxyPolicies.sys.mjs b/browser/components/enterprisepolicies/helpers/ProxyPolicies.sys.mjs new file mode 100644 index 0000000000..8f2ba0b2a2 --- /dev/null +++ b/browser/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 PREF_LOGLEVEL = "browser.policies.loglevel"; + +const lazy = {}; + +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.sys.mjs for details. + maxLogLevel: "error", + maxLogLevelPref: PREF_LOGLEVEL, + }); +}); + +// Don't use const here because this is acessed 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/browser/components/enterprisepolicies/helpers/WebsiteFilter.sys.mjs b/browser/components/enterprisepolicies/helpers/WebsiteFilter.sys.mjs new file mode 100644 index 0000000000..8be0c9e204 --- /dev/null +++ b/browser/components/enterprisepolicies/helpers/WebsiteFilter.sys.mjs @@ -0,0 +1,188 @@ +/* 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/. */ + +/* + * This module implements the policy to block websites from being visited, + * or to only allow certain websites to be visited. + * + * The blocklist takes as input an array of MatchPattern strings, as documented + * at https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Match_patterns. + * + * The exceptions list takes the same as input. This list opens up + * exceptions for rules on the blocklist that might be too strict. + * + * In addition to that, this allows the user to create an allowlist approach, + * by using the special "<all_urls>" pattern for the blocklist, and then + * adding all allowlisted websites on the exceptions list. + * + * Note that this module only blocks top-level website navigations and embeds. + * It does not block any other accesses to these urls: image tags, scripts, XHR, etc., + * because that could cause unexpected breakage. This is a policy to block + * users from visiting certain websites, and not from blocking any network + * connections to those websites. If the admin is looking for that, the recommended + * way is to configure that with extensions or through a company firewall. + */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +const LIST_LENGTH_LIMIT = 1000; + +const PREF_LOGLEVEL = "browser.policies.loglevel"; + +const lazy = {}; + +XPCOMUtils.defineLazyGetter(lazy, "log", () => { + let { ConsoleAPI } = ChromeUtils.importESModule( + "resource://gre/modules/Console.sys.mjs" + ); + return new ConsoleAPI({ + prefix: "WebsiteFilter Policy", + // tip: set maxLogLevel to "debug" and use log.debug() to create detailed + // messages during development. See LOG_LEVELS in Console.sys.mjs for details. + maxLogLevel: "error", + maxLogLevelPref: PREF_LOGLEVEL, + }); +}); + +export let WebsiteFilter = { + init(blocklist, exceptionlist) { + let blockArray = [], + exceptionArray = []; + + for (let i = 0; i < blocklist.length && i < LIST_LENGTH_LIMIT; i++) { + try { + let pattern = new MatchPattern(blocklist[i].toLowerCase()); + blockArray.push(pattern); + lazy.log.debug( + `Pattern added to WebsiteFilter. Block: ${blocklist[i]}` + ); + } catch (e) { + lazy.log.error( + `Invalid pattern on WebsiteFilter. Block: ${blocklist[i]}` + ); + } + } + + this._blockPatterns = new MatchPatternSet(blockArray); + + for (let i = 0; i < exceptionlist.length && i < LIST_LENGTH_LIMIT; i++) { + try { + let pattern = new MatchPattern(exceptionlist[i].toLowerCase()); + exceptionArray.push(pattern); + lazy.log.debug( + `Pattern added to WebsiteFilter. Exception: ${exceptionlist[i]}` + ); + } catch (e) { + lazy.log.error( + `Invalid pattern on WebsiteFilter. Exception: ${exceptionlist[i]}` + ); + } + } + + if (exceptionArray.length) { + this._exceptionsPatterns = new MatchPatternSet(exceptionArray); + } + + let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); + + if (!registrar.isContractIDRegistered(this.contractID)) { + registrar.registerFactory( + this.classID, + this.classDescription, + this.contractID, + this + ); + + Services.catMan.addCategoryEntry( + "content-policy", + this.contractID, + this.contractID, + false, + true + ); + } + // We have to do this to catch 30X redirects. + // See bug 456957. + Services.obs.addObserver(this, "http-on-examine-response", true); + }, + + shouldLoad(contentLocation, loadInfo, mimeTypeGuess) { + let contentType = loadInfo.externalContentPolicyType; + let url = contentLocation.spec; + if (contentLocation.scheme == "view-source") { + url = contentLocation.pathQueryRef; + } else if (url.toLowerCase().startsWith("about:reader")) { + url = decodeURIComponent( + url.toLowerCase().substr("about:reader?url=".length) + ); + } + if ( + contentType == Ci.nsIContentPolicy.TYPE_DOCUMENT || + contentType == Ci.nsIContentPolicy.TYPE_SUBDOCUMENT + ) { + if (this._blockPatterns.matches(url.toLowerCase())) { + if ( + !this._exceptionsPatterns || + !this._exceptionsPatterns.matches(url.toLowerCase()) + ) { + return Ci.nsIContentPolicy.REJECT_POLICY; + } + } + } + return Ci.nsIContentPolicy.ACCEPT; + }, + shouldProcess(contentLocation, loadInfo, mimeTypeGuess) { + return Ci.nsIContentPolicy.ACCEPT; + }, + observe(subject, topic, data) { + try { + let channel = subject.QueryInterface(Ci.nsIHttpChannel); + if ( + !channel.isDocument || + channel.responseStatus < 300 || + channel.responseStatus >= 400 + ) { + return; + } + let location = channel.getResponseHeader("location"); + // location might not be a fully qualified URL + let url; + try { + url = new URL(location); + } catch (e) { + url = new URL(location, channel.URI.spec); + } + if (this._blockPatterns.matches(url.href.toLowerCase())) { + if ( + !this._exceptionsPatterns || + !this._exceptionsPatterns.matches(url.href.toLowerCase()) + ) { + channel.cancel(Cr.NS_ERROR_BLOCKED_BY_POLICY); + } + } + } catch (e) {} + }, + classDescription: "Policy Engine File Content Policy", + contractID: "@mozilla-org/policy-engine-file-content-policy-service;1", + classID: Components.ID("{c0bbb557-813e-4e25-809d-b46a531a258f}"), + QueryInterface: ChromeUtils.generateQI([ + "nsIContentPolicy", + "nsIObserver", + "nsISupportsWeakReference", + ]), + createInstance(iid) { + return this.QueryInterface(iid); + }, + isAllowed(url) { + if (this._blockPatterns?.matches(url.toLowerCase())) { + if ( + !this._exceptionsPatterns || + !this._exceptionsPatterns.matches(url.toLowerCase()) + ) { + return false; + } + } + return true; + }, +}; diff --git a/browser/components/enterprisepolicies/helpers/moz.build b/browser/components/enterprisepolicies/helpers/moz.build new file mode 100644 index 0000000000..4429fa5928 --- /dev/null +++ b/browser/components/enterprisepolicies/helpers/moz.build @@ -0,0 +1,14 @@ +# -*- 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 = ("Firefox", "Enterprise Policies") + +EXTRA_JS_MODULES.policies += [ + "BookmarksPolicies.sys.mjs", + "ProxyPolicies.sys.mjs", + "WebsiteFilter.sys.mjs", +] diff --git a/browser/components/enterprisepolicies/helpers/sample.json b/browser/components/enterprisepolicies/helpers/sample.json new file mode 100644 index 0000000000..fcaefe3fcc --- /dev/null +++ b/browser/components/enterprisepolicies/helpers/sample.json @@ -0,0 +1,18 @@ +{ + "policies": { + "BlockAboutProfiles": true, + "DontCheckDefaultBrowser": true, + + "FlashPlugin": { + "Allow": [ + "https://www.example.com" + ], + + "Block": [ + "https://www.example.org" + ] + }, + + "CreateMasterPassword": false + } +} diff --git a/browser/components/enterprisepolicies/helpers/sample_bookmarks.json b/browser/components/enterprisepolicies/helpers/sample_bookmarks.json new file mode 100644 index 0000000000..5393526aab --- /dev/null +++ b/browser/components/enterprisepolicies/helpers/sample_bookmarks.json @@ -0,0 +1,37 @@ +{ + "policies": { + "DisplayBookmarksToolbar": true, + + "Bookmarks": [ + { + "Title": "Bookmark 1", + "URL": "https://bookmark1.example.com" + }, + { + "Title": "Bookmark 2", + "URL": "https://bookmark2.example.com", + "Favicon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gwMDAsTBZbkNwAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAABNElEQVQ4y8WSsU0DURBE3yyWIaAJaqAAN4DPSL6AlIACKIEOyJEgRsIgOOkiInJqgAKowNg7BHdn7MOksNl+zZ//dvbDf5cAiklp22BdVtXdeTEpDYDB9m1VzU6OJuVp2NdEQCaI96fH2YHG4+mDduKYNMYINTcjcGbXzQVDEAphG0k48zUsajIbnAiMIXThpW8EICE0RAK4dvoKg9NIcTiQ589otyHOZLnwqK5nLwBFUZ4igc3iM0d1ff8CMC6mZ6Ihiaqq3gi1aUAnArD00SW1fq5OLBg0ymYmSZsR2/t4e/rGyCLW0sbp3oq+yTYqVgytQWui2FS7XYF7GFprY921T4CNQt8zr47dNzCkIX7y/jBtH+v+RGMQrc828W8pApnZbmEVQp/Ae7BlOy2ttib81/UFc+WRWEbjckIAAAAASUVORK5CYII=", + "Folder": "Folder 1" + }, + { + "Title": "Bookmark 3", + "URL": "https://bookmark3.example.com", + "Favicon": "https://www.mozilla.org/favicon.ico", + "Placement": "menu" + }, + { + "Title": "Bookmark 4", + "URL": "https://bookmark4.example.com", + "Favicon": "https://www.mozilla.org/favicon.ico", + "Folder": "Folder 1" + }, + { + "Title": "Bookmark 5", + "URL": "https://bookmark5.example.com", + "Favicon": "https://www.mozilla.org/favicon.ico", + "Placement": "menu", + "Folder": "Folder 2" + } + ] + } +}
\ No newline at end of file diff --git a/browser/components/enterprisepolicies/helpers/sample_proxy.json b/browser/components/enterprisepolicies/helpers/sample_proxy.json new file mode 100644 index 0000000000..0daaf70409 --- /dev/null +++ b/browser/components/enterprisepolicies/helpers/sample_proxy.json @@ -0,0 +1,13 @@ +{ + "policies": { + "Proxy": { + "Mode": "manual", + "Locked": true, + "HTTPProxy": "www.example.com:42", + "UseHTTPProxyForAllProtocols": true, + "Passthrough": "foo, bar, baz", + "SOCKSVersion": 4, + "UseProxyForDNS": true + } + } +} diff --git a/browser/components/enterprisepolicies/helpers/sample_websitefilter.json b/browser/components/enterprisepolicies/helpers/sample_websitefilter.json new file mode 100644 index 0000000000..d70b6cdea5 --- /dev/null +++ b/browser/components/enterprisepolicies/helpers/sample_websitefilter.json @@ -0,0 +1,14 @@ +{ + "policies": { + "WebsiteFilter": { + "Block": [ + "*://*.mozilla.org/*", + "invalid_pattern" + ], + + "Exceptions": [ + "*://*.mozilla.org/*about*" + ] + } + } +} diff --git a/browser/components/enterprisepolicies/jar.mn b/browser/components/enterprisepolicies/jar.mn new file mode 100644 index 0000000000..353d6dba75 --- /dev/null +++ b/browser/components/enterprisepolicies/jar.mn @@ -0,0 +1,11 @@ +# 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.jar: + content/browser/policies/aboutPolicies.css (content/aboutPolicies.css) + content/browser/policies/aboutPolicies.html (content/aboutPolicies.html) + content/browser/policies/aboutPolicies.js (content/aboutPolicies.js) + content/browser/policies/policies-active.svg (content/policies-active.svg) + content/browser/policies/policies-documentation.svg (content/policies-documentation.svg) + content/browser/policies/policies-error.svg (content/policies-error.svg) diff --git a/browser/components/enterprisepolicies/moz.build b/browser/components/enterprisepolicies/moz.build new file mode 100644 index 0000000000..047108084b --- /dev/null +++ b/browser/components/enterprisepolicies/moz.build @@ -0,0 +1,25 @@ +# -*- 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 = ("Firefox", "Enterprise Policies") + +SPHINX_TREES["docs"] = "docs" + +DIRS += [ + "helpers", + "schemas", +] + +TEST_DIRS += ["tests"] + +EXTRA_JS_MODULES.policies += [ + "Policies.sys.mjs", +] + +FINAL_LIBRARY = "browsercomps" + +JAR_MANIFESTS += ["jar.mn"] diff --git a/browser/components/enterprisepolicies/schemas/configuration.json b/browser/components/enterprisepolicies/schemas/configuration.json new file mode 100644 index 0000000000..8d3e9e43c2 --- /dev/null +++ b/browser/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/browser/components/enterprisepolicies/schemas/moz.build b/browser/components/enterprisepolicies/schemas/moz.build new file mode 100644 index 0000000000..19e17705e7 --- /dev/null +++ b/browser/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 = ("Firefox", "Enterprise Policies") + +EXTRA_PP_JS_MODULES.policies += [ + "schema.jsm", +] diff --git a/browser/components/enterprisepolicies/schemas/policies-schema.json b/browser/components/enterprisepolicies/schemas/policies-schema.json new file mode 100644 index 0000000000..43262396ce --- /dev/null +++ b/browser/components/enterprisepolicies/schemas/policies-schema.json @@ -0,0 +1,1340 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + + "3rdparty": { + "type": "object", + "properties": { + "Extensions" : { + "type": "object", + "patternProperties": { + "^.*$": { + "type": "JSON" + } + } + } + } + }, + + "AllowedDomainsForApps": { + "type": "string" + }, + + "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" + } + } + }, + + "AutoLaunchProtocolsFromOrigins": { + "type": ["array", "JSON"], + "items": { + "type": "object", + "properties": { + "allowed_origins": { + "type": "array", + "items": { + "type": "origin" + } + }, + "protocol": { + "type": "string" + }, + "required": ["allowed_origins", "protocol"] + } + } + }, + + "BackgroundAppUpdate": { + "type": "boolean" + }, + + "BlockAboutAddons": { + "type": "boolean" + }, + + "BlockAboutConfig": { + "type": "boolean" + }, + + "BlockAboutProfiles": { + "type": "boolean" + }, + + "BlockAboutSupport": { + "type": "boolean" + }, + + "Bookmarks": { + "type": "array", + "items": { + "type": "object", + "properties": { + "Title": { + "type": "string" + }, + + "URL": { + "type": "URL" + }, + + "Favicon": { + "type": "URLorEmpty" + }, + + "Placement": { + "type": "string", + "enum": ["toolbar", "menu"] + }, + + "Folder": { + "type": "string" + } + }, + "required": ["Title", "URL"] + } + }, + + "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" + } + }, + + "AllowSession": { + "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"] + }, + + "RejectTracker": { + "type": "boolean" + }, + + "ExpireAtSessionEnd": { + "type": "boolean" + }, + + "Locked": { + "type": "boolean" + }, + + "Behavior": { + "type": "string", + "enum": ["accept", "reject-foreign", "reject", "limit-foreign", "reject-tracker", "reject-tracker-and-partition-foreign"] + }, + + "BehaviorPrivateBrowsing": { + "type": "string", + "enum": ["accept", "reject-foreign", "reject", "limit-foreign", "reject-tracker", "reject-tracker-and-partition-foreign"] + } + + } + }, + + "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" + } + } + }, + + "DisableDefaultBrowserAgent": { + "type": "boolean" + }, + + "DisableDeveloperTools": { + "type": "boolean" + }, + + "DisableFeedbackCommands": { + "type": "boolean" + }, + + "DisableFirefoxAccounts": { + "type": "boolean" + }, + + "DisableFirefoxScreenshots": { + "type": "boolean" + }, + + "DisableFirefoxStudies": { + "type": "boolean" + }, + + "DisableForgetButton": { + "type": "boolean" + }, + + "DisableFormHistory": { + "type": "boolean" + }, + + "DisableMasterPasswordCreation": { + "type": "boolean" + }, + + "DisablePasswordReveal": { + "type": "boolean" + }, + + "DisablePocket": { + "type": "boolean" + }, + + "DisablePrivateBrowsing": { + "type": "boolean" + }, + + "DisableProfileImport": { + "type": "boolean" + }, + + "DisableProfileRefresh": { + "type": "boolean" + }, + + "DisableSafeMode": { + "type": "boolean" + }, + + "DisableSecurityBypass": { + "type": "object", + "properties": { + "InvalidCertificate": { + "type": "boolean" + }, + + "SafeBrowsing": { + "type": "boolean" + } + } + }, + + "DisableSetDesktopBackground": { + "type": "boolean" + }, + + "DisableSystemAddonUpdate": { + "type": "boolean" + }, + + "DisableTelemetry": { + "type": "boolean" + }, + + "DisableThirdPartyModuleBlocking": { + "type": "boolean" + }, + + "DisplayBookmarksToolbar": { + "type": ["boolean", "string"], + "enum": ["always", "never", "newtab"] + }, + + "DisplayMenuBar": { + "type": ["boolean", "string"], + "enum": ["always", "never", "default-on", "default-off"] + }, + + "DNSOverHTTPS": { + "type": "object", + "properties": { + "Enabled": { + "type": "boolean" + }, + "ProviderURL": { + "type": "URLorEmpty" + }, + "ExcludedDomains": { + "type": "array", + "items": { + "type": "string" + } + }, + "Locked": { + "type": "boolean" + } + } + }, + + "DontCheckDefaultBrowser": { + "type": "boolean" + }, + + "DownloadDirectory": { + "type": "string" + }, + + "EnableTrackingProtection": { + "type": "object", + "properties": { + "Value": { + "type": "boolean" + }, + "Locked": { + "type": "boolean" + }, + "Cryptomining": { + "type": "boolean" + }, + "Fingerprinting": { + "type": "boolean" + }, + "Exceptions": { + "type": "array", + "strict": false, + "items": { + "type": "origin" + } + } + } + }, + + "EncryptedMediaExtensions": { + "type": "object", + "properties": { + "Enabled": { + "type": "boolean" + }, + "Locked": { + "type": "boolean" + } + } + }, + + "ExemptDomainFileTypePairsFromFileTypeDownloadWarnings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "file_extension": { + "type": "string" + }, + "domains": { + "type": "array", + "items": { + "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", "JSON"], + "properties": { + "*": { + "type": "object", + "properties": { + "installation_mode": { + "type": "string", + "enum": ["allowed", "blocked"] + }, + "allowed_types": { + "type": "array", + "items": { + "type": "string", + "enum": ["extension", "dictionary", "locale", "theme", "sitepermission"] + } + }, + "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" + }, + + "FirefoxHome": { + "type": "object", + "properties": { + "Search": { + "type": "boolean" + }, + "TopSites": { + "type": "boolean" + }, + "SponsoredTopSites": { + "type": "boolean" + }, + "Highlights": { + "type": "boolean" + }, + "Pocket": { + "type": "boolean" + }, + "SponsoredPocket": { + "type": "boolean" + }, + "Snippets": { + "type": "boolean" + }, + "Locked": { + "type": "boolean" + } + } + }, + + "FlashPlugin": { + "type": "object", + "properties": { + "Allow": { + "type": "array", + "strict": false, + "items": { + "type": "origin" + } + }, + + "Block": { + "type": "array", + "strict": false, + "items": { + "type": "origin" + } + }, + + "Default": { + "type": "boolean" + }, + + "Locked": { + "type": "boolean" + } + } + }, + + "GoToIntranetSiteForSingleWordEntryInAddressBar": { + "type": "boolean" + }, + + "Handlers": { + "type": ["object", "JSON"], + "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" + }, + + "Homepage": { + "type": "object", + "properties": { + "URL": { + "type": "URL" + }, + "Locked": { + "type": "boolean" + }, + "Additional": { + "type": "array", + "strict": false, + "items": { + "type": "URL" + } + }, + "StartPage": { + "type": "string", + "enum": ["none", "homepage", "previous-session", "homepage-locked"] + } + } + }, + + "InstallAddonsPermission": { + "type": "object", + "properties": { + "Allow": { + "type": "array", + "strict": false, + "items": { + "type": "origin" + } + }, + "Default": { + "type": "boolean" + } + } + }, + + "LegacyProfiles": { + "type": "boolean" + }, + + "LegacySameSiteCookieBehaviorEnabled": { + "type": "boolean" + }, + + "LegacySameSiteCookieBehaviorEnabledForDomainList": { + "type": "array", + "items": { + "type": "string" + } + }, + + "LocalFileLinks": { + "type": "array", + "items": { + "type": "string" + } + }, + + "ManagedBookmarks": { + "items": { + "properties": { + "children": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "toplevel_name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "children": { + "items": { + "type": "JSON" + }, + "type": "array" + } + }, + "type": "object" + }, + "type": "array" + }, + "name": { + "type": "string" + }, + "toplevel_name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "type": "object" + }, + "type": ["array", "JSON"] + }, + + "ManualAppUpdateOnly": { + "type": "boolean" + }, + + "NetworkPrediction": { + "type": "boolean" + }, + + "NewTabPage": { + "type": "boolean" + }, + + "NoDefaultBookmarks": { + "type": "boolean" + }, + + "OfferToSaveLogins": { + "type": "boolean" + }, + + "OfferToSaveLoginsDefault": { + "type": "boolean" + }, + + "OverrideFirstRunPage": { + "type": "string", + }, + + "OverridePostUpdatePage": { + "type": "URLorEmpty" + }, + + "PasswordManagerEnabled": { + "type": "boolean" + }, + + "PasswordManagerExceptions": { + "type": "array", + "strict": false, + "items": { + "type": "origin" + } + }, + + "PDFjs": { + "type": "object", + "properties": { + "Enabled": { + "type": "boolean" + }, + "EnablePermissions": { + "type": "boolean" + } + } + }, + + "Permissions": { + "type": "object", + "properties": { + "Camera": { + "type": "object", + "properties": { + "Allow": { + "type": "array", + "strict": false, + "items": { + "type": "origin" + } + }, + + "Block": { + "type": "array", + "strict": false, + "items": { + "type": "origin" + } + }, + + "BlockNewRequests": { + "type": "boolean" + }, + + "Locked": { + "type": "boolean" + } + } + }, + + "Microphone": { + "type": "object", + "properties": { + "Allow": { + "type": "array", + "strict": false, + "items": { + "type": "origin" + } + }, + + "Block": { + "type": "array", + "strict": false, + "items": { + "type": "origin" + } + }, + + "BlockNewRequests": { + "type": "boolean" + }, + + "Locked": { + "type": "boolean" + } + } + }, + + "Autoplay": { + "type": "object", + "properties": { + "Default": { + "type": "string", + "enum": ["allow-audio-video", "block-audio", "block-audio-video"] + }, + + "Allow": { + "type": "array", + "strict": false, + "items": { + "type": "origin" + } + }, + + "Block": { + "type": "array", + "strict": false, + "items": { + "type": "origin" + } + }, + + "Locked": { + "type": "boolean" + } + } + }, + + "Location": { + "type": "object", + "properties": { + "Allow": { + "type": "array", + "strict": false, + "items": { + "type": "origin" + } + }, + + "Block": { + "type": "array", + "strict": false, + "items": { + "type": "origin" + } + }, + + "BlockNewRequests": { + "type": "boolean" + }, + + "Locked": { + "type": "boolean" + } + } + }, + + "Notifications": { + "type": "object", + "properties": { + "Allow": { + "type": "array", + "strict": false, + "items": { + "type": "origin" + } + }, + + "Block": { + "type": "array", + "strict": false, + "items": { + "type": "origin" + } + }, + + "BlockNewRequests": { + "type": "boolean" + }, + + "Locked": { + "type": "boolean" + } + } + }, + + "VirtualReality": { + "type": "object", + "properties": { + "Allow": { + "type": "array", + "strict": false, + "items": { + "type": "origin" + } + }, + + "Block": { + "type": "array", + "strict": false, + "items": { + "type": "origin" + } + }, + + "BlockNewRequests": { + "type": "boolean" + }, + + "Locked": { + "type": "boolean" + } + } + } + } + }, + + "PictureInPicture": { + "type": "object", + "properties": { + "Enabled": { + "type": "boolean" + }, + "Locked": { + "type": "boolean" + } + } + }, + + "PopupBlocking": { + "type": "object", + "properties": { + "Allow": { + "type": "array", + "strict": false, + "items": { + "type": "origin" + } + }, + + "Default": { + "type": "boolean" + }, + + "Locked": { + "type": "boolean" + } + } + }, + + "Preferences": { + "type": ["object", "JSON"], + "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" + } + }, + + "SanitizeOnShutdown": { + "type": ["boolean", "object"], + "properties": { + "Cache": { + "type": "boolean" + }, + "Cookies": { + "type": "boolean" + }, + "Downloads": { + "type": "boolean" + }, + "FormData": { + "type": "boolean" + }, + "History": { + "type": "boolean" + }, + "Sessions": { + "type": "boolean" + }, + "SiteSettings": { + "type": "boolean" + }, + "OfflineApps": { + "type": "boolean" + }, + "Locked": { + "type": "boolean" + } + } + }, + + "SearchBar": { + "type": "string", + "enum": ["unified", "separate"] + }, + + "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" + } + } + } + }, + + "SearchSuggestEnabled": { + "type": "boolean" + }, + + "SecurityDevices": { + "type": "object", + "patternProperties": { + "^.*$": { "type": "string" } + } + }, + + "ShowHomeButton": { + "type": "boolean" + }, + + "SSLVersionMax": { + "type": "string", + "enum": ["tls1", "tls1.1", "tls1.2", "tls1.3"] + }, + + "SSLVersionMin": { + "type": "string", + "enum": ["tls1", "tls1.1", "tls1.2", "tls1.3"] + }, + + "StartDownloadsInTempDirectory": { + "type": "boolean" + }, + + "SupportMenu": { + "type": "object", + "properties": { + "Title": { + "type": "string" + }, + "URL": { + "type": "URL" + }, + "AccessKey": { + "type": "string" + } + }, + "required": ["Title", "URL"], + }, + + "UserMessaging": { + "type": "object", + "properties": { + "WhatsNew": { + "type": "boolean" + }, + "ExtensionRecommendations": { + "type": "boolean" + }, + "FeatureRecommendations": { + "type": "boolean" + }, + "UrlbarInterventions": { + "type": "boolean" + }, + "SkipOnboarding": { + "type": "boolean" + }, + "MoreFromMozilla": { + "type": "boolean" + }, + "Locked": { + "type": "boolean" + } + } + }, + + "UseSystemPrintDialog": { + "type": "boolean" + }, + + "WebsiteFilter": { + "type": "object", + "properties": { + "Block": { + "type": "array", + "items": { + "type": "string" + } + }, + + "Exceptions": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + + "WindowsSSO": { + "type": "boolean" + } + } +} diff --git a/browser/components/enterprisepolicies/schemas/schema.jsm b/browser/components/enterprisepolicies/schemas/schema.jsm new file mode 100644 index 0000000000..54ae8751aa --- /dev/null +++ b/browser/components/enterprisepolicies/schemas/schema.jsm @@ -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/. */ + +"use strict"; + +const EXPORTED_SYMBOLS = ["schema"]; + +const schema = +#include policies-schema.json diff --git a/browser/components/enterprisepolicies/tests/browser/301.sjs b/browser/components/enterprisepolicies/tests/browser/301.sjs new file mode 100644 index 0000000000..adf0f0891d --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/301.sjs @@ -0,0 +1,8 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +function handleRequest(request, response) { + response.setStatusLine(request.httpVersion, 301, "Moved Permanently"); + response.setHeader("Location", "policy_websitefilter_block.html"); +} diff --git a/browser/components/enterprisepolicies/tests/browser/302.sjs b/browser/components/enterprisepolicies/tests/browser/302.sjs new file mode 100644 index 0000000000..4aee85baac --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/302.sjs @@ -0,0 +1,8 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +function handleRequest(request, response) { + response.setStatusLine(request.httpVersion, 302, "Moved Temporarily"); + response.setHeader("Location", "policy_websitefilter_block.html"); +} diff --git a/browser/components/enterprisepolicies/tests/browser/404.sjs b/browser/components/enterprisepolicies/tests/browser/404.sjs new file mode 100644 index 0000000000..923e8082b1 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/404.sjs @@ -0,0 +1,3 @@ +function handleRequest(request, response) { + response.setStatusLine(request.httpVersion, 404, "Not Found"); +} diff --git a/browser/components/enterprisepolicies/tests/browser/browser.ini b/browser/components/enterprisepolicies/tests/browser/browser.ini new file mode 100644 index 0000000000..f055b5ce50 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser.ini @@ -0,0 +1,72 @@ +[DEFAULT] +support-files = + head.js + opensearch.html + opensearchEngine.xml + policytest_v0.1.xpi + policytest_v0.2.xpi + policy_websitefilter_block.html + policy_websitefilter_exception.html + policy_websitefilter_savelink.html + ../../../../../toolkit/components/antitracking/test/browser/page.html + ../../../../../toolkit/components/antitracking/test/browser/subResources.sjs + extensionsettings.html + 301.sjs + 302.sjs + 404.sjs + +[browser_policies_getActivePolicies.js] +skip-if = os != 'mac' +[browser_policies_notice_in_aboutpreferences.js] +[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_block_about.js] +[browser_policy_block_about_support.js] +[browser_policy_block_set_desktop_background.js] +[browser_policy_bookmarks.js] +[browser_policy_cookie_settings.js] +https_first_disabled = true +[browser_policy_disable_feedback_commands.js] +[browser_policy_disable_fxaccounts.js] +skip-if = (verify && debug && (os == 'mac')) +[browser_policy_disable_masterpassword.js] +[browser_policy_disable_password_reveal.js] +[browser_policy_disable_pocket.js] +[browser_policy_disable_popup_blocker.js] +[browser_policy_disable_privatebrowsing.js] +[browser_policy_disable_profile_reset.js] +[browser_policy_disable_profile_import.js] +[browser_policy_disable_safemode.js] +[browser_policy_disable_shield.js] +[browser_policy_disable_telemetry.js] +[browser_policy_display_bookmarks.js] +[browser_policy_display_menu.js] +[browser_policy_extensions.js] +[browser_policy_downloads.js] +support-files = + !/browser/components/downloads/test/browser/foo.txt + !/browser/components/downloads/test/browser/foo.txt^headers^ +[browser_policy_extensionsettings.js] +https_first_disabled = true +[browser_policy_extensionsettings2.js] +[browser_policy_firefoxhome.js] +[browser_policy_handlers.js] +[browser_policy_masterpassword.js] +[browser_policy_masterpassword_aboutlogins.js] +[browser_policy_masterpassword_doorhanger.js] +[browser_policy_offertosavelogins.js] +[browser_policy_override_postupdatepage.js] +[browser_policy_pageinfo_permissions.js] +[browser_policy_passwordmanager.js] +[browser_policy_search_engine.js] +[browser_policy_searchbar.js] +[browser_policy_set_homepage.js] +[browser_policy_set_startpage.js] +[browser_policy_support_menu.js] +[browser_policy_usermessaging.js] +[browser_policy_websitefilter.js] +[browser_policy_background_app_update.js] +skip-if = os == 'win' && msix # Updater is disabled in MSIX builds diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policies_getActivePolicies.js b/browser/components/enterprisepolicies/tests/browser/browser_policies_getActivePolicies.js new file mode 100644 index 0000000000..7702cb3785 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policies_getActivePolicies.js @@ -0,0 +1,52 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function test_active_policies() { + await setupPolicyEngineWithJson({ + policies: { + DisablePrivateBrowsing: true, + }, + }); + + let expected = { + DisablePrivateBrowsing: true, + }; + + Assert.deepEqual( + await Services.policies.getActivePolicies(), + expected, + "Active policies parsed correctly" + ); +}); + +add_task(async function test_wrong_policies() { + await setupPolicyEngineWithJson({ + policies: { + BlockAboutSupport: [true], + }, + }); + + let expected = {}; + + Assert.deepEqual( + await Services.policies.getActivePolicies(), + expected, + "Wrong policies ignored" + ); +}); + +add_task(async function test_content_process() { + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function() { + try { + Services.policies.getActivePolicies(); + } catch (ex) { + is( + ex.result, + Cr.NS_ERROR_XPC_JSOBJECT_HAS_NO_FUNCTION_NAMED, + "Function getActivePolicies() doesn't have a valid definition in the content process" + ); + } + }); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policies_notice_in_aboutpreferences.js b/browser/components/enterprisepolicies/tests/browser/browser_policies_notice_in_aboutpreferences.js new file mode 100644 index 0000000000..75c17ddf36 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policies_notice_in_aboutpreferences.js @@ -0,0 +1,19 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function test_notice_in_aboutprefences() { + await setupPolicyEngineWithJson({ + policies: { + DummyPolicy: true, + }, + }); + + await BrowserTestUtils.withNewTab("about:preferences", async browser => { + ok( + !browser.contentDocument.getElementById("policies-container").hidden, + "The Policies notice was made visible in about:preferences" + ); + }); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policies_setAndLockPref_API.js b/browser/components/enterprisepolicies/tests/browser/browser_policies_setAndLockPref_API.js new file mode 100644 index 0000000000..0cad8e5aa3 --- /dev/null +++ b/browser/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/browser/components/enterprisepolicies/tests/browser/browser_policy_app_auto_update.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_app_auto_update.js new file mode 100644 index 0000000000..0cae8369e4 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_app_auto_update.js @@ -0,0 +1,77 @@ +/* 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` + ); + + await BrowserTestUtils.withNewTab("about:preferences", browser => { + is( + browser.contentDocument.getElementById("updateSettingsContainer").hidden, + expectedLocked, + `When auto update ${ + expectedLocked ? "is" : "isn't" + } locked, the corresponding preferences entry ${ + expectedLocked ? "should" : "shouldn't" + } be hidden` + ); + }); +} + +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/browser/components/enterprisepolicies/tests/browser/browser_policy_app_update.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_app_update.js new file mode 100644 index 0000000000..14a9c92bc5 --- /dev/null +++ b/browser/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/browser/components/enterprisepolicies/tests/browser/browser_policy_background_app_update.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_background_app_update.js new file mode 100644 index 0000000000..a529e79be7 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_background_app_update.js @@ -0,0 +1,99 @@ +/* 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_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() { + 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/browser/components/enterprisepolicies/tests/browser/browser_policy_block_about.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_block_about.js new file mode 100644 index 0000000000..1c8956356f --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_block_about.js @@ -0,0 +1,50 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +const ABOUT_CONTRACT = "@mozilla.org/network/protocol/about;1?what="; + +const policiesToTest = [ + { + policies: { + BlockAboutAddons: true, + }, + urls: ["about:addons", "about:ADDONS"], + }, + { + policies: { + BlockAboutConfig: true, + }, + urls: ["about:config", "about:Config"], + }, + { + policies: { + BlockAboutProfiles: true, + }, + urls: ["about:profiles", "about:pRofiles"], + }, + { + policies: { + BlockAboutSupport: true, + }, + urls: ["about:support", "about:suPPort"], + }, +]; + +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].toLowerCase(); + let aboutModule = Cc[ABOUT_CONTRACT + feature].getService( + Ci.nsIAboutModule + ); + let chromeURL = aboutModule.getChromeURI(Services.io.newURI(url)).spec; + await testPageBlockedByPolicy(chromeURL, policyJSON); + } + await testPageBlockedByPolicy(url, policyJSON); + } + } +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_block_about_support.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_block_about_support.js new file mode 100644 index 0000000000..b3c9ca432c --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_block_about_support.js @@ -0,0 +1,41 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +add_setup(async function() { + await setupPolicyEngineWithJson({ + policies: { + BlockAboutSupport: true, + }, + }); +}); + +add_task(async function test_help_menu() { + buildHelpMenu(); + let troubleshootingInfoMenu = document.getElementById("troubleShooting"); + is( + troubleshootingInfoMenu.getAttribute("disabled"), + "true", + "The `More Troubleshooting Information` item should be disabled" + ); +}); + +add_task(async function test_about_memory() { + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "about:memory" + ); + + await SpecialPowers.spawn(tab.linkedBrowser, [], async function() { + let aboutSupportLink = content.document.querySelector( + "a[href='about:support']" + ); + + Assert.ok( + !aboutSupportLink, + "The link to about:support at the bottom of the page should not exist" + ); + }); + + await BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_block_set_desktop_background.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_block_set_desktop_background.js new file mode 100644 index 0000000000..ae67dbf2c2 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_block_set_desktop_background.js @@ -0,0 +1,50 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +add_setup(async function() { + await setupPolicyEngineWithJson({ + policies: { + DisableSetDesktopBackground: true, + }, + }); +}); + +add_task(async function test_check_set_desktop_background() { + const imageUrl = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gwMDAsTBZbkNwAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAABNElEQVQ4y8WSsU0DURBE3yyWIaAJaqAAN4DPSL6AlIACKIEOyJEgRsIgOOkiInJqgAKowNg7BHdn7MOksNl+zZ//dvbDf5cAiklp22BdVtXdeTEpDYDB9m1VzU6OJuVp2NdEQCaI96fH2YHG4+mDduKYNMYINTcjcGbXzQVDEAphG0k48zUsajIbnAiMIXThpW8EICE0RAK4dvoKg9NIcTiQ589otyHOZLnwqK5nLwBFUZ4igc3iM0d1ff8CMC6mZ6Ihiaqq3gi1aUAnArD00SW1fq5OLBg0ymYmSZsR2/t4e/rGyCLW0sbp3oq+yTYqVgytQWui2FS7XYF7GFprY921T4CNQt8zr47dNzCkIX7y/jBtH+v+RGMQrc828W8pApnZbmEVQp/Ae7BlOy2ttib81/UFc+WRWEbjckIAAAAASUVORK5CYII="; + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + imageUrl, + true + ); + + // Right click on the image and wait for the context menu to open + let contextMenu = document.getElementById("contentAreaContextMenu"); + let promiseContextMenuOpen = BrowserTestUtils.waitForEvent( + contextMenu, + "popupshown" + ); + await BrowserTestUtils.synthesizeMouse( + "img", + 0, + 0, + { + type: "contextmenu", + button: 2, + centered: true, + }, + gBrowser.selectedBrowser + ); + await promiseContextMenuOpen; + info("Context Menu Shown"); + + let buttonElement = document.getElementById("context-setDesktopBackground"); + is( + buttonElement.hidden, + true, + 'The "Set Desktop Background" context menu element should be hidden' + ); + contextMenu.hidePopup(); + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_bookmarks.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_bookmarks.js new file mode 100644 index 0000000000..851290947e --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_bookmarks.js @@ -0,0 +1,320 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { PlacesUtils } = ChromeUtils.importESModule( + "resource://gre/modules/PlacesUtils.sys.mjs" +); + +const FAVICON_DATA = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gwMDAsTBZbkNwAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAABNElEQVQ4y8WSsU0DURBE3yyWIaAJaqAAN4DPSL6AlIACKIEOyJEgRsIgOOkiInJqgAKowNg7BHdn7MOksNl+zZ//dvbDf5cAiklp22BdVtXdeTEpDYDB9m1VzU6OJuVp2NdEQCaI96fH2YHG4+mDduKYNMYINTcjcGbXzQVDEAphG0k48zUsajIbnAiMIXThpW8EICE0RAK4dvoKg9NIcTiQ589otyHOZLnwqK5nLwBFUZ4igc3iM0d1ff8CMC6mZ6Ihiaqq3gi1aUAnArD00SW1fq5OLBg0ymYmSZsR2/t4e/rGyCLW0sbp3oq+yTYqVgytQWui2FS7XYF7GFprY921T4CNQt8zr47dNzCkIX7y/jBtH+v+RGMQrc828W8pApnZbmEVQp/Ae7BlOy2ttib81/UFc+WRWEbjckIAAAAASUVORK5CYII="; + +const { BookmarksPolicies } = ChromeUtils.importESModule( + "resource:///modules/policies/BookmarksPolicies.sys.mjs" +); + +let CURRENT_POLICY; + +const basePath = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "http://mochi.test:8888" +); + +const BASE_POLICY = { + policies: { + DisplayBookmarksToolbar: true, + Bookmarks: [ + { + Title: "Bookmark 1", + URL: "https://bookmark1.example.com/", + Favicon: FAVICON_DATA, + }, + { + Title: "Bookmark 2", + URL: "https://bookmark2.example.com/", + Favicon: `${basePath}/404.sjs`, + }, + { + Title: "Bookmark 3", + URL: "https://bookmark3.example.com/", + Folder: "Folder 1", + }, + { + Title: "Bookmark 4", + URL: "https://bookmark4.example.com/", + Placement: "menu", + }, + { + Title: "Bookmark 5", + URL: "https://bookmark5.example.com/", + Folder: "Folder 1", + }, + { + Title: "Bookmark 6", + URL: "https://bookmark6.example.com/", + Placement: "menu", + Folder: "Folder 2", + }, + ], + }, +}; + +/* + * ================================= + * = HELPER FUNCTIONS FOR THE TEST = + * ================================= + */ +function deepClone(obj) { + return JSON.parse(JSON.stringify(obj)); +} + +function findBookmarkInPolicy(bookmark) { + // Find the entry in the given policy that corresponds + // to this bookmark object from Places. + for (let entry of CURRENT_POLICY.policies.Bookmarks) { + if (entry.Title == bookmark.title) { + return entry; + } + } + return null; +} + +async function promiseAllChangesMade({ itemsToAdd, itemsToRemove }) { + return new Promise(resolve => { + let listener = events => { + for (const event of events) { + switch (event.type) { + case "bookmark-added": + itemsToAdd--; + if (itemsToAdd == 0 && itemsToRemove == 0) { + PlacesUtils.observers.removeListener( + ["bookmark-added", "bookmark-removed"], + listener + ); + resolve(); + } + break; + case "bookmark-removed": + itemsToRemove--; + if (itemsToAdd == 0 && itemsToRemove == 0) { + PlacesUtils.observers.removeListener( + ["bookmark-added", "bookmark-removed"], + listener + ); + resolve(); + } + break; + } + } + }; + PlacesUtils.observers.addListener( + ["bookmark-added", "bookmark-removed"], + listener + ); + }); +} + +/* + * ================== + * = CHECK FUNCTION = + * ================== + * + * Performs all the checks comparing what was given in + * the policy JSON with what was retrieved from Places. + */ +async function check({ expectedNumberOfFolders }) { + let bookmarks = [], + folders = []; + + await PlacesUtils.bookmarks.fetch( + { guidPrefix: BookmarksPolicies.BOOKMARK_GUID_PREFIX }, + r => { + bookmarks.push(r); + } + ); + await PlacesUtils.bookmarks.fetch( + { guidPrefix: BookmarksPolicies.FOLDER_GUID_PREFIX }, + r => { + folders.push(r); + } + ); + + let foldersToGuids = new Map(); + + for (let folder of folders) { + is( + folder.type, + PlacesUtils.bookmarks.TYPE_FOLDER, + "Correct type for folder" + ); + foldersToGuids.set(folder.title, folder.guid); + } + + // For simplification and accuracy purposes, the number of expected + // folders is manually specified in the test. + is( + folders.length, + expectedNumberOfFolders, + "Correct number of folders expected" + ); + is( + foldersToGuids.size, + expectedNumberOfFolders, + "There aren't two different folders with the same name" + ); + + is( + CURRENT_POLICY.policies.Bookmarks.length, + bookmarks.length, + "The correct number of bookmarks exist" + ); + + for (let bookmark of bookmarks) { + is( + bookmark.type, + PlacesUtils.bookmarks.TYPE_BOOKMARK, + "Correct type for bookmark" + ); + + let entry = findBookmarkInPolicy(bookmark); + + is(bookmark.title, entry.Title, "Title matches"); + is(bookmark.url.href, entry.URL, "URL matches"); + + let expectedPlacementGuid; + if (entry.Folder) { + expectedPlacementGuid = foldersToGuids.get(entry.Folder); + } else { + expectedPlacementGuid = + entry.Placement == "menu" + ? PlacesUtils.bookmarks.menuGuid + : PlacesUtils.bookmarks.toolbarGuid; + } + + is(bookmark.parentGuid, expectedPlacementGuid, "Correctly placed"); + } +} + +/* + * ================ + * = ACTUAL TESTS = + * ================ + * + * Note: the order of these tests is important, as we want to test not + * only the end result of each configuration, but also the diff algorithm + * that will add or remove bookmarks depending on how the policy changed. + */ + +add_task(async function test_initial_bookmarks() { + // Make a copy of the policy because we will be adding/removing entries from it + CURRENT_POLICY = deepClone(BASE_POLICY); + + await Promise.all([ + promiseAllChangesMade({ + itemsToAdd: 8, // 6 bookmarks + 2 folders + itemsToRemove: 0, + }), + setupPolicyEngineWithJson(CURRENT_POLICY), + ]); + + await check({ expectedNumberOfFolders: 2 }); +}); + +add_task(async function checkFavicon() { + let bookmark1url = CURRENT_POLICY.policies.Bookmarks[0].URL; + + let result = await new Promise(resolve => { + PlacesUtils.favicons.getFaviconDataForPage( + Services.io.newURI(bookmark1url), + (uri, _, data) => resolve({ uri, data }) + ); + }); + + is( + result.uri.spec, + "fake-favicon-uri:" + bookmark1url, + "Favicon URI is correct" + ); + // data is an array of octets, which will be a bit hard to compare against + // FAVICON_DATA, which is base64 encoded. Checking the expected length should + // be good indication that this is working properly. + is(result.data.length, 464, "Favicon data has the correct length"); + + let faviconsExpiredNotification = TestUtils.topicObserved( + "places-favicons-expired" + ); + PlacesUtils.favicons.expireAllFavicons(); + await faviconsExpiredNotification; +}); + +add_task(async function test_remove_Bookmark_2() { + // Continuing from the previous policy: + // + // Remove the 2nd bookmark. It is inside "Folder 1", but that + // folder won't be empty, so it must not be removed. + CURRENT_POLICY.policies.Bookmarks.splice(3, 1); + + await Promise.all([ + promiseAllChangesMade({ + itemsToAdd: 0, + itemsToRemove: 1, // 1 bookmark + }), + setupPolicyEngineWithJson(CURRENT_POLICY), + ]); + + await check({ expectedNumberOfFolders: 2 }); +}); + +add_task(async function test_remove_Bookmark_5() { + // Continuing from the previous policy: + // + // Remove the last bookmark in the policy, + // which means the "Folder 2" should also disappear + CURRENT_POLICY.policies.Bookmarks.splice(-1, 1); + + await Promise.all([ + promiseAllChangesMade({ + itemsToAdd: 0, + itemsToRemove: 2, // 1 bookmark and 1 folder + }), + setupPolicyEngineWithJson(CURRENT_POLICY), + ]); + + await check({ expectedNumberOfFolders: 1 }); +}); + +add_task(async function test_revert_to_original_policy() { + CURRENT_POLICY = deepClone(BASE_POLICY); + + // Reverts to the original policy, which means that: + // - "Bookmark 2" + // - "Bookmark 5" + // - "Folder 2" + // should be recreated + await Promise.all([ + promiseAllChangesMade({ + itemsToAdd: 3, // 2 bookmarks and 1 folder + itemsToRemove: 0, + }), + setupPolicyEngineWithJson(CURRENT_POLICY), + ]); + + await check({ expectedNumberOfFolders: 2 }); +}); + +// Leave this one at the end, so that it cleans up any +// bookmarks created during this test. +add_task(async function test_empty_all_bookmarks() { + CURRENT_POLICY = { policies: { Bookmarks: [] } }; + + await Promise.all([ + promiseAllChangesMade({ + itemsToAdd: 0, + itemsToRemove: 8, // 6 bookmarks and 2 folders + }), + setupPolicyEngineWithJson(CURRENT_POLICY), + ]); + + check({ expectedNumberOfFolders: 0 }); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_cookie_settings.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_cookie_settings.js new file mode 100644 index 0000000000..ba4f50fdc9 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_cookie_settings.js @@ -0,0 +1,367 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +const { UrlClassifierTestUtils } = ChromeUtils.import( + "resource://testing-common/UrlClassifierTestUtils.jsm" +); +Services.cookies.QueryInterface(Ci.nsICookieService); + +function restore_prefs() { + 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", null); + }); + 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, + rejectTrackers, + 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; SameSite=None; Secure;", + channel + ); + Services.cookies.setCookieStringFromHttp( + thirdPartyURI, + "key=value; SameSite=None; Secure;", + 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; SameSite=None; Secure;", + 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" + ); + + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "about:preferences" + ); + + BrowserTestUtils.removeTab(tab); + + if (rejectTrackers) { + tab = await BrowserTestUtils.addTab( + gBrowser, + "http://example.net/browser/browser/components/enterprisepolicies/tests/browser/page.html" + ); + let browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + await SpecialPowers.spawn(tab.linkedBrowser, [], async function() { + // Load the script twice + { + let src = content.document.createElement("script"); + let p = new content.Promise((resolve, reject) => { + src.onload = resolve; + src.onerror = reject; + }); + content.document.body.appendChild(src); + src.src = + "https://tracking.example.org/browser/browser/components/enterprisepolicies/tests/browser/subResources.sjs?what=script"; + await p; + } + { + let src = content.document.createElement("script"); + let p = new content.Promise(resolve => { + src.onload = resolve; + }); + content.document.body.appendChild(src); + src.src = + "https://tracking.example.org/browser/browser/components/enterprisepolicies/tests/browser/subResources.sjs?what=script"; + await p; + } + }); + BrowserTestUtils.removeTab(tab); + await fetch( + "https://tracking.example.org/browser/browser/components/enterprisepolicies/tests/browser/subResources.sjs?result&what=script" + ) + .then(r => r.text()) + .then(text => { + is(text, "0", '"Reject Tracker" pref should match what is expected'); + }); + } +} + +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 + ); + + 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 + ); + await setupPolicyEngineWithJson({ + policies: { + Cookies: { + Locked: true, + }, + }, + }); + + await test_cookie_settings({ + cookiesEnabled: true, + thirdPartyCookiesEnabled: true, + cookieJarSettingsLocked: true, + }); + restore_prefs(); +}); + +add_task(async function test_cookie_reject_trackers() { + Services.prefs.setBoolPref( + "network.cookieJarSettings.unblocked_for_testing", + true + ); + Services.prefs.setBoolPref( + "network.cookie.rejectForeignWithExceptions.enabled", + false + ); + await setupPolicyEngineWithJson({ + policies: { + Cookies: { + RejectTracker: true, + }, + }, + }); + + await test_cookie_settings({ + cookiesEnabled: true, + thirdPartyCookiesEnabled: true, + rejectTrackers: true, + cookieJarSettingsLocked: false, + }); + restore_prefs(); +}); + +add_task(async function prepare_tracker_tables() { + await UrlClassifierTestUtils.cleanupTestTrackers(); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_feedback_commands.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_feedback_commands.js new file mode 100644 index 0000000000..b89788e1cf --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_feedback_commands.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* the buidHelpMenu() function comes from browser/base/content/utilityOverlay.js */ + +const NORMAL_PAGE = "http://example.com"; +const PHISH_PAGE = "http://www.itisatrap.org/firefox/its-a-trap.html"; + +async function checkItemsAreDisabled(url) { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url, + // The phishing page doesn't send a load notification + waitForLoad: false, + waitForStateStop: true, + }, + async function checkItems() { + buildHelpMenu(); + + let reportMenu = document.getElementById( + "menu_HelpPopup_reportPhishingtoolmenu" + ); + is( + reportMenu.getAttribute("disabled"), + "true", + "The `Report Deceptive Site` item should be disabled" + ); + + let errorMenu = document.getElementById( + "menu_HelpPopup_reportPhishingErrortoolmenu" + ); + is( + errorMenu.getAttribute("disabled"), + "true", + "The `This isn’t a deceptive site` item should be disabled" + ); + } + ); +} + +add_task(async function test_policy_feedback_commands() { + await setupPolicyEngineWithJson({ + policies: { + DisableFeedbackCommands: true, + }, + }); + + /* from browser/base/content/utilityOverlay.js */ + buildHelpMenu(); + + let feedbackPageMenu = document.getElementById("feedbackPage"); + is( + feedbackPageMenu.getAttribute("disabled"), + "true", + "The `Submit Feedback...` item should be disabled" + ); + + await checkItemsAreDisabled(NORMAL_PAGE); + await checkItemsAreDisabled(PHISH_PAGE); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_fxaccounts.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_fxaccounts.js new file mode 100644 index 0000000000..7872647042 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_fxaccounts.js @@ -0,0 +1,16 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function test_policy_disable_fxaccounts() { + is(gSync.FXA_ENABLED, true, "Sync is enabled before setting the policy."); + + await setupPolicyEngineWithJson({ + policies: { + DisableFirefoxAccounts: true, + }, + }); + + is(gSync.FXA_ENABLED, false, "Sync is disabled after setting the policy."); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_masterpassword.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_masterpassword.js new file mode 100644 index 0000000000..d9b3f3da62 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_masterpassword.js @@ -0,0 +1,91 @@ +/* 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, + "Master 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/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_password_reveal.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_password_reveal.js new file mode 100644 index 0000000000..503d421da7 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_password_reveal.js @@ -0,0 +1,43 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function test_hidden_reveal_password() { + await setupPolicyEngineWithJson({ + policies: { + DisablePasswordReveal: true, + }, + }); + + let aboutLoginsTab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + url: "about:logins", + }); + + let browser = gBrowser.selectedBrowser; + + await SpecialPowers.spawn(browser, [], () => { + let loginList = Cu.waiveXrays(content.document.querySelector("login-list")); + let createButton = loginList._createLoginButton; + ok( + !createButton.disabled, + "Create button should not be disabled initially" + ); + let loginItem = Cu.waiveXrays(content.document.querySelector("login-item")); + + createButton.click(); + + let passwordReveal = loginItem.shadowRoot.querySelector( + ".reveal-password-checkbox" + ); + is(passwordReveal.hidden, true, "Password reveal button should be hidden"); + + // Bug 1696948 + let passwordInput = loginItem.shadowRoot.querySelector( + "input[name='password']" + ); + isnot(passwordInput, null, "Password field should be in the DOM"); + }); + BrowserTestUtils.removeTab(aboutLoginsTab); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_pocket.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_pocket.js new file mode 100644 index 0000000000..170d1af767 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_pocket.js @@ -0,0 +1,29 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const PREF_POCKET = "extensions.pocket.enabled"; + +async function checkPocket(shouldBeEnabled) { + return BrowserTestUtils.waitForCondition(() => { + return ( + !!CustomizableUI.getWidget("save-to-pocket-button") == shouldBeEnabled + ); + }, "Expecting Pocket to be " + shouldBeEnabled); +} + +add_task(async function test_disable_firefox_screenshots() { + await BrowserTestUtils.withNewTab("data:text/html,Test", async function() { + // Sanity check to make sure Pocket is enabled on tests + await checkPocket(true); + + await setupPolicyEngineWithJson({ + policies: { + DisablePocket: true, + }, + }); + + await checkPocket(false); + }); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_popup_blocker.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_popup_blocker.js new file mode 100644 index 0000000000..e6bac62570 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_popup_blocker.js @@ -0,0 +1,149 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +function restore_prefs() { + Services.prefs.clearUserPref("dom.disable_open_during_load"); +} + +let ORIGINAL_PREF_VALUE = undefined; +add_setup(async function() { + // It seems that this pref is given a special testing value for some reason. + // Unset that value for this test, but save the old value + if (Services.prefs.prefHasUserValue("dom.disable_open_during_load")) { + ORIGINAL_PREF_VALUE = Services.prefs.getBoolPref( + "dom.disable_open_during_load" + ); + Services.prefs.clearUserPref("dom.disable_open_during_load"); + } +}); +registerCleanupFunction(async function cleanup_prefs() { + if (ORIGINAL_PREF_VALUE === undefined) { + Services.prefs.clearUserPref("dom.disable_open_during_load"); + } else { + Services.prefs.setBoolPref( + "dom.disable_open_during_load", + ORIGINAL_PREF_VALUE + ); + } +}); + +async function test_popup_blocker_disabled({ disabled, locked }) { + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "about:preferences#privacy" + ); + await SpecialPowers.spawn( + tab.linkedBrowser, + [{ disabled, locked }], + // eslint-disable-next-line no-shadow + async function({ disabled, locked }) { + let checkbox = content.document.getElementById("popupPolicy"); + is( + checkbox.checked, + !disabled, + "Checkbox checked state should match policy's Block status" + ); + is( + checkbox.disabled, + locked, + "Checkbox disabled state should match policy's Locked status" + ); + } + ); + BrowserTestUtils.removeTab(tab); + + is( + Services.prefs.prefIsLocked("dom.disable_open_during_load"), + locked, + "Flash pref lock state should match policy lock state" + ); +} + +add_task(async function test_initial_state() { + await test_popup_blocker_disabled({ disabled: false, locked: false }); +}); + +add_task(async function test_empty_policy() { + await setupPolicyEngineWithJson({ + policies: { + PopupBlocking: {}, + }, + }); + + await test_popup_blocker_disabled({ disabled: false, locked: false }); + + restore_prefs(); +}); + +add_task(async function test_block() { + await setupPolicyEngineWithJson({ + policies: { + PopupBlocking: { + Default: true, + }, + }, + }); + + await test_popup_blocker_disabled({ disabled: false, locked: false }); + + restore_prefs(); +}); + +add_task(async function test_block_locked() { + await setupPolicyEngineWithJson({ + policies: { + PopupBlocking: { + Default: true, + Locked: true, + }, + }, + }); + + await test_popup_blocker_disabled({ disabled: false, locked: true }); + + restore_prefs(); +}); + +add_task(async function test_locked() { + await setupPolicyEngineWithJson({ + policies: { + PopupBlocking: { + Locked: true, + }, + }, + }); + + await test_popup_blocker_disabled({ disabled: false, locked: true }); + + restore_prefs(); +}); + +add_task(async function test_disabled() { + await setupPolicyEngineWithJson({ + policies: { + PopupBlocking: { + Default: false, + }, + }, + }); + + await test_popup_blocker_disabled({ disabled: true, locked: false }); + + restore_prefs(); +}); + +add_task(async function test_disabled_locked() { + await setupPolicyEngineWithJson({ + policies: { + PopupBlocking: { + Default: false, + Locked: true, + }, + }, + }); + + await test_popup_blocker_disabled({ disabled: true, locked: true }); + + restore_prefs(); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_privatebrowsing.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_privatebrowsing.js new file mode 100644 index 0000000000..74781f3094 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_privatebrowsing.js @@ -0,0 +1,36 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { PrivateBrowsingUtils } = ChromeUtils.importESModule( + "resource://gre/modules/PrivateBrowsingUtils.sys.mjs" +); + +add_setup(async function() { + await setupPolicyEngineWithJson({ + policies: { + DisablePrivateBrowsing: true, + }, + }); +}); + +add_task(async function test_privatebrowsing_disabled() { + is( + PrivateBrowsingUtils.enabled, + false, + "Private browsing should be disabled" + ); + let newWin = await BrowserTestUtils.openNewBrowserWindow(); + let privateBrowsingCommand = newWin.document.getElementById( + "Tools:PrivateBrowsing" + ); + is( + privateBrowsingCommand.hidden, + true, + "The private browsing command should be hidden" + ); + await BrowserTestUtils.closeWindow(newWin); + + await testPageBlockedByPolicy("about:privatebrowsing"); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_profile_import.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_profile_import.js new file mode 100644 index 0000000000..594bd6a359 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_profile_import.js @@ -0,0 +1,71 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +async function openLibrary() { + return new Promise(resolve => { + let library = window.openDialog( + "chrome://browser/content/places/places.xhtml", + "", + "chrome,toolbar=yes,dialog=no,resizable" + ); + waitForFocus(() => resolve(library), library); + }); +} + +add_task(async function test_disable_profile_import() { + await setupPolicyEngineWithJson({ + policies: { + DisableProfileImport: true, + }, + }); + let library = await openLibrary(); + + let menu = library.document.getElementById("maintenanceButtonPopup"); + let promisePopupShown = BrowserTestUtils.waitForEvent(menu, "popupshown"); + menu.openPopup(); + await promisePopupShown; + + let profileImportButton = library.document.getElementById("browserImport"); + is( + profileImportButton.disabled, + true, + "Profile Import button should be disabled" + ); + + let promisePopupHidden = BrowserTestUtils.waitForEvent(menu, "popuphidden"); + menu.hidePopup(); + await promisePopupHidden; + + await BrowserTestUtils.closeWindow(library); + + checkLockedPref("browser.newtabpage.activity-stream.migrationExpired", true); +}); + +add_task(async function test_file_menu() { + gFileMenu.updateImportCommandEnabledState(); + + let command = document.getElementById("cmd_file_importFromAnotherBrowser"); + ok( + command.getAttribute("disabled"), + "The `Import from Another Browser…` File menu item command should be disabled" + ); + + if (Services.appinfo.OS == "Darwin") { + // We would need to have a lot of boilerplate to open the menus on Windows + // and Linux to test this there. + let menuitem = document.getElementById("menu_importFromAnotherBrowser"); + ok( + menuitem.disabled, + "The `Import from Another Browser…` File menu item should be disabled" + ); + } +}); + +add_task(async function test_import_button() { + await PlacesUIUtils.maybeAddImportButton(); + ok( + !document.getElementById("import-button"), + "Import button should be hidden." + ); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_profile_reset.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_profile_reset.js new file mode 100644 index 0000000000..2a60ca964e --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_profile_reset.js @@ -0,0 +1,76 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +let { ResetProfile } = ChromeUtils.importESModule( + "resource://gre/modules/ResetProfile.sys.mjs" +); + +// For this test to work properly, this profile actually needs to be +// "reset-able", which requires that it be recognized by the profile service +add_setup(async function() { + let profileDirectory = Services.dirsvc.get("ProfD", Ci.nsIFile); + let profileName = profileDirectory.leafName; + let profileService = Cc["@mozilla.org/toolkit/profile-service;1"].getService( + Ci.nsIToolkitProfileService + ); + let createdProfile = profileService.createProfile( + profileDirectory, + profileName + ); + profileService.flush(); + registerCleanupFunction(async function cleanup() { + // Pass false to remove it from the profile service without deleting files. + createdProfile.remove(false); + }); +}); + +async function test_reset_disabled({ disabled }) { + is( + ResetProfile.resetSupported(), + !disabled, + "Reset should only be supported if policy has not been applied" + ); + is( + Services.prefs.getBoolPref("browser.disableResetPrompt", undefined), + disabled, + "Reset prompt should only be shown if policy has not been applied" + ); + is( + Services.prefs.prefIsLocked("browser.disableResetPrompt"), + disabled, + "Reset prompt pref should be locked if the policy has been applied" + ); + + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "about:support" + ); + await SpecialPowers.spawn(tab.linkedBrowser, [{ disabled }], async function({ + // eslint-disable-next-line no-shadow + disabled, + }) { + let resetBox = content.document.getElementById("reset-box"); + let elementStyle = content.window.getComputedStyle(resetBox); + let expectedDisplayValue = disabled ? "none" : "block"; + is( + elementStyle.display, + expectedDisplayValue, + "about:support Reset button box should be hidden" + ); + }); + await BrowserTestUtils.removeTab(tab); +} + +add_task(async function test_initial_conditions() { + await test_reset_disabled({ disabled: false }); +}); + +add_task(async function test_policy_disable_reset() { + await setupPolicyEngineWithJson({ + policies: { + DisableProfileRefresh: true, + }, + }); + await test_reset_disabled({ disabled: true }); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_safemode.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_safemode.js new file mode 100644 index 0000000000..f1d1dd4fc4 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_safemode.js @@ -0,0 +1,57 @@ +/* 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("helpSafeMode"); + is( + safeModeMenu.getAttribute("disabled"), + "true", + "The `Restart with Add-ons Disabled...` item should be disabled" + ); +}); + +add_task(async function test_safemode_from_about_support() { + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "about:support" + ); + + await SpecialPowers.spawn(tab.linkedBrowser, [], async function() { + 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" + ); + }); + + await BrowserTestUtils.removeTab(tab); +}); + +add_task(async function test_safemode_from_about_profiles() { + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "about:profiles" + ); + + await SpecialPowers.spawn(tab.linkedBrowser, [], async function() { + 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" + ); + }); + + await BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_shield.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_shield.js new file mode 100644 index 0000000000..fc4944e0b0 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_shield.js @@ -0,0 +1,68 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function test_policy_disable_shield() { + const { RecipeRunner } = ChromeUtils.import( + "resource://normandy/lib/RecipeRunner.jsm" + ); + const { BaseAction } = ChromeUtils.import( + "resource://normandy/actions/BaseAction.jsm" + ); + const { BaseStudyAction } = ChromeUtils.import( + "resource://normandy/actions/BaseStudyAction.jsm" + ); + + const baseAction = new BaseAction(); + const baseStudyAction = new BaseStudyAction(); + + await SpecialPowers.pushPrefEnv({ + set: [ + ["app.normandy.api_url", "https://localhost/selfsupport-dummy/"], + ["app.shield.optoutstudies.enabled", true], + ], + }); + + ok(RecipeRunner, "RecipeRunner exists"); + + RecipeRunner.checkPrefs(); + ok(RecipeRunner.enabled, "RecipeRunner is enabled"); + + baseAction._preExecution(); + is( + baseAction.state, + BaseAction.STATE_PREPARING, + "Base action is not disabled" + ); + + baseStudyAction._preExecution(); + is( + baseStudyAction.state, + BaseAction.STATE_PREPARING, + "Base study action is not disabled" + ); + + await setupPolicyEngineWithJson({ + policies: { + DisableFirefoxStudies: true, + }, + }); + + RecipeRunner.checkPrefs(); + ok(RecipeRunner.enabled, "RecipeRunner is still enabled"); + + baseAction._preExecution(); + is( + baseAction.state, + BaseAction.STATE_PREPARING, + "Base action is not disabled" + ); + + baseStudyAction._preExecution(); + is( + baseStudyAction.state, + BaseAction.STATE_DISABLED, + "Base study action is disabled" + ); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_telemetry.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_telemetry.js new file mode 100644 index 0000000000..3070df0d88 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_telemetry.js @@ -0,0 +1,28 @@ +/* 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"); + is( + Services.prefs.getBoolPref("toolkit.telemetry.archive.enabled"), + false, + "Telemetry archive should be disabled." + ); + + await testPageBlockedByPolicy("about:telemetry"); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_display_bookmarks.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_display_bookmarks.js new file mode 100644 index 0000000000..8c43cbc8e1 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_display_bookmarks.js @@ -0,0 +1,86 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Since testing will apply the policy after the browser has already started, +// we will need to open a new window to actually see the toolbar + +add_task(async function test_personaltoolbar_shown_old() { + await setupPolicyEngineWithJson({ + policies: { + DisplayBookmarksToolbar: true, + }, + }); + let newWin = await BrowserTestUtils.openNewBrowserWindow(); + let menuBar = newWin.document.getElementById("PersonalToolbar"); + is( + menuBar.getAttribute("collapsed"), + "false", + "The bookmarks toolbar should not be hidden" + ); + + await BrowserTestUtils.closeWindow(newWin); +}); + +add_task(async function test_personaltoolbar_shown() { + await setupPolicyEngineWithJson({ + policies: { + DisplayBookmarksToolbar: "always", + }, + }); + + let newWin = await BrowserTestUtils.openNewBrowserWindow(); + let menuBar = newWin.document.getElementById("PersonalToolbar"); + is( + menuBar.getAttribute("collapsed"), + "false", + "The bookmarks toolbar should not be hidden" + ); + + await BrowserTestUtils.closeWindow(newWin); +}); + +add_task(async function test_personaltoolbar_hidden() { + await setupPolicyEngineWithJson({ + policies: { + DisplayBookmarksToolbar: "never", + }, + }); + + let newWin = await BrowserTestUtils.openNewBrowserWindow(); + let menuBar = newWin.document.getElementById("PersonalToolbar"); + is( + menuBar.getAttribute("collapsed"), + "true", + "The bookmarks toolbar should be hidden" + ); + + await BrowserTestUtils.closeWindow(newWin); +}); + +add_task(async function test_personaltoolbar_newtabonly() { + await setupPolicyEngineWithJson({ + policies: { + DisplayBookmarksToolbar: "newtab", + }, + }); + + let newWin = await BrowserTestUtils.openNewBrowserWindow(); + let menuBar = newWin.document.getElementById("PersonalToolbar"); + is( + menuBar.getAttribute("collapsed"), + "true", + "The bookmarks toolbar should be hidden" + ); + + await BrowserTestUtils.openNewForegroundTab(newWin.gBrowser, "about:newtab"); + menuBar = newWin.document.getElementById("PersonalToolbar"); + is( + menuBar.getAttribute("collapsed"), + "false", + "The bookmarks toolbar should not be hidden" + ); + + await BrowserTestUtils.closeWindow(newWin); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_display_menu.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_display_menu.js new file mode 100644 index 0000000000..9560a1aaf1 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_display_menu.js @@ -0,0 +1,84 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function test_menu_shown_boolean() { + await setupPolicyEngineWithJson({ + policies: { + DisplayMenuBar: true, + }, + }); + + // Since testing will apply the policy after the browser has already started, + // we will need to open a new window to actually see the menu bar + let newWin = await BrowserTestUtils.openNewBrowserWindow(); + let menubar = newWin.document.getElementById("toolbar-menubar"); + is( + menubar.getAttribute("autohide"), + "false", + "The menu bar should not be hidden" + ); + + await BrowserTestUtils.closeWindow(newWin); +}); + +add_task(async function test_menu_shown_string() { + await setupPolicyEngineWithJson({ + policies: { + DisplayMenuBar: "default-on", + }, + }); + + // Since testing will apply the policy after the browser has already started, + // we will need to open a new window to actually see the menu bar + let newWin = await BrowserTestUtils.openNewBrowserWindow(); + let menubar = newWin.document.getElementById("toolbar-menubar"); + is( + menubar.getAttribute("autohide"), + "false", + "The menu bar should not be hidden" + ); + + await BrowserTestUtils.closeWindow(newWin); +}); + +add_task(async function test_menubar_on() { + await setupPolicyEngineWithJson({ + policies: { + DisplayMenuBar: "always", + }, + }); + + let newWin = await BrowserTestUtils.openNewBrowserWindow(); + let menubar = newWin.document.getElementById("toolbar-menubar"); + is( + menubar.hasAttribute("inactive"), + false, + "Menu bar should not have inactive" + ); + is( + menubar.hasAttribute("toolbarname"), + false, + "Menu bar should not have a toolbarname" + ); + await BrowserTestUtils.closeWindow(newWin); +}); + +add_task(async function test_menubar_off() { + await setupPolicyEngineWithJson({ + policies: { + DisplayMenuBar: "never", + }, + }); + + let newWin = await BrowserTestUtils.openNewBrowserWindow(); + let menubar = newWin.document.getElementById("toolbar-menubar"); + is(menubar.hasAttribute("inactive"), true, "Menu bar should have inactive"); + is( + menubar.hasAttribute("toolbarname"), + false, + "Menu bar should not have a toolbarname" + ); + await BrowserTestUtils.closeWindow(newWin); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_downloads.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_downloads.js new file mode 100644 index 0000000000..f86a00b799 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_downloads.js @@ -0,0 +1,148 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const UCT_URI = "chrome://mozapps/content/downloads/unknownContentType.xhtml"; + +add_task(async function test_defaultdownload() { + await setupPolicyEngineWithJson({ + policies: { + DefaultDownloadDirectory: "${home}/Downloads", + PromptForDownloadLocation: false, + }, + }); + + await BrowserTestUtils.withNewTab("about:preferences", async browser => { + is( + browser.contentDocument.getElementById("alwaysAsk").disabled, + true, + "alwaysAsk should be disabled." + ); + let home = Services.dirsvc.get("Home", Ci.nsIFile).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." + ); + }); +}); + +add_task(async function test_download() { + await setupPolicyEngineWithJson({ + policies: { + DownloadDirectory: "${home}/Documents", + }, + }); + + await BrowserTestUtils.withNewTab("about:preferences", async browser => { + is( + browser.contentDocument.getElementById("alwaysAsk").disabled, + true, + "alwaysAsk should be disabled." + ); + is( + browser.contentDocument.getElementById("downloadFolder").disabled, + true, + "downloadFolder should be disabled." + ); + is( + browser.contentDocument.getElementById("chooseFolder").disabled, + true, + "chooseFolder should be disabled." + ); + let home = Services.dirsvc.get("Home", Ci.nsIFile).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." + ); + }); +}); + +async function setDownloadDir() { + let tmpDir = PathUtils.join( + PathUtils.tempDir, + "testsavedir" + Math.floor(Math.random() * 2 ** 32) + ); + // Create this dir if it doesn't exist (ignores existing dirs) + await IOUtils.makeDirectory(tmpDir); + registerCleanupFunction(async function() { + try { + await IOUtils.remove(tmpDir, { recursive: true }); + } catch (e) { + Cu.reportError(e); + } + }); + Services.prefs.setIntPref("browser.download.folderList", 2); + Services.prefs.setCharPref("browser.download.dir", tmpDir); + return tmpDir; +} + +add_task(async function test_tmpdir_download() { + await setupPolicyEngineWithJson({ + policies: { + StartDownloadsInTempDirectory: true, + }, + }); + + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.download.always_ask_before_handling_new_types", true], + ["browser.helperApps.deleteTempFileOnExit", true], + ], + }); + + let dlDir = new FileUtils.File(await setDownloadDir()); + registerCleanupFunction(() => { + Services.prefs.clearUserPref("browser.download.dir"); + Services.prefs.clearUserPref("browser.download.folderList"); + }); + + // Wait for the download prompting dialog + let dialogPromise = BrowserTestUtils.domWindowOpenedAndLoaded( + null, + win => win.document.documentURI == UCT_URI + ); + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: + "https://example.com/browser/browser/components/downloads/test/browser/foo.txt", + waitForLoad: false, + waitForStop: true, + }, + async function() { + let dialogWin = await dialogPromise; + let tempFile = dialogWin.dialog.mLauncher.targetFile; + isnot( + tempFile.parent.path, + dlDir.path, + "Should not have put temp file in the downloads dir." + ); + + dialogWin.close(); + } + ); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_extensions.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_extensions.js new file mode 100644 index 0000000000..7d1313548b --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_extensions.js @@ -0,0 +1,117 @@ +/* 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/browser/components/enterprisepolicies/tests/browser"; + +async function isExtensionLocked(win, addonID) { + let addonCard = await TestUtils.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 tab = await BrowserTestUtils.openNewForegroundTab(gBrowser); + const win = await BrowserOpenAddonsMgr("addons://list/extension"); + + await isExtensionLocked(win, ADDON_ID); + + BrowserTestUtils.removeTab(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/browser/components/enterprisepolicies/tests/browser/browser_policy_extensionsettings.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_extensionsettings.js new file mode 100644 index 0000000000..cc7088cccd --- /dev/null +++ b/browser/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"; + +const BASE_URL = + "http://mochi.test:8888/browser/browser/components/enterprisepolicies/tests/browser/"; + +/** + * 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); + }); +} + +add_setup(async function setupTestEnvironment() { + // Once InstallTrigger is removed, the tests targeting InstallTrigger should + // be removed or adapted to don't use InstallTrigger. + 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 BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: BASE_URL + "extensionsettings.html", + waitForStateStop: true, + }); + + await SpecialPowers.spawn(tab.linkedBrowser, [], () => { + content.document.getElementById("policytest").click(); + }); + await popupPromise; + BrowserTestUtils.removeTab(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 BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: BASE_URL + "extensionsettings.html", + waitForStateStop: true, + }); + + 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" + ); + BrowserTestUtils.removeTab(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 BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: BASE_URL + "extensionsettings.html", + waitForStateStop: true, + }); + + await SpecialPowers.spawn(tab.linkedBrowser, [], () => { + content.document.getElementById("policytest_otherdomain").click(); + }); + await popupPromise; + BrowserTestUtils.removeTab(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 BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: BASE_URL + "extensionsettings.html", + waitForStateStop: true, + }); + + await SpecialPowers.spawn( + tab.linkedBrowser, + [{ baseUrl: BASE_URL }], + async function({ baseUrl }) { + content.document.location.href = baseUrl + "policytest_v0.1.xpi"; + } + ); + await popupPromise; + BrowserTestUtils.removeTab(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 BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: BASE_URL + "extensionsettings.html", + waitForStateStop: true, + }); + + await SpecialPowers.spawn(tab.linkedBrowser, [], () => { + content.document.getElementById("policytest").click(); + }); + await popupPromise; + BrowserTestUtils.removeTab(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 BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: BASE_URL + "extensionsettings.html", + waitForStateStop: true, + }); + + await SpecialPowers.spawn(tab.linkedBrowser, [], () => { + content.document.getElementById("policytest_installtrigger").click(); + }); + await popupPromise; + BrowserTestUtils.removeTab(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 BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: BASE_URL + "extensionsettings.html", + waitForStateStop: true, + }); + + await SpecialPowers.spawn(tab.linkedBrowser, [], () => { + content.document.getElementById("policytest_otherdomain").click(); + }); + await popupPromise; + BrowserTestUtils.removeTab(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 BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: BASE_URL + "extensionsettings.html", + waitForStateStop: true, + }); + + await SpecialPowers.spawn( + tab.linkedBrowser, + [{ baseUrl: BASE_URL }], + async function({ baseUrl }) { + content.document.location.href = baseUrl + "policytest_v0.1.xpi"; + } + ); + await popupPromise; + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_extensionsettings2.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_extensionsettings2.js new file mode 100644 index 0000000000..612448ee4e --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_extensionsettings2.js @@ -0,0 +1,71 @@ +/* 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/browser/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 tab = await BrowserTestUtils.openNewForegroundTab(gBrowser); + const win = await BrowserOpenAddonsMgr( + "addons://detail/" + encodeURIComponent(ADDON_ID) + ); + + await isExtensionLockedAndUpdateDisabled(win, ADDON_ID); + + BrowserTestUtils.removeTab(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/browser/components/enterprisepolicies/tests/browser/browser_policy_firefoxhome.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_firefoxhome.js new file mode 100644 index 0000000000..37d810c1fc --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_firefoxhome.js @@ -0,0 +1,132 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_setup(async function() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.newtabpage.activity-stream.feeds.section.highlights", true], + ], + }); +}); + +add_task(async function test_firefox_home_without_policy_without_pocket() { + let tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: "about:home", + waitForStateStop: true, + }); + + await SpecialPowers.spawn(tab.linkedBrowser, [], function() { + let search = content.document.querySelector(".search-wrapper"); + isnot(search, null, "Search section should be there."); + let topsites = content.document.querySelector( + "section[data-section-id='topsites']" + ); + isnot(topsites, null, "Top Sites section should be there."); + let highlights = content.document.querySelector( + "section[data-section-id='highlights']" + ); + isnot(highlights, null, "Highlights section should be there."); + }); + BrowserTestUtils.removeTab(tab); + await SpecialPowers.popPrefEnv(); +}); + +add_task(async function test_firefox_home_with_policy() { + await SpecialPowers.pushPrefEnv({ + set: [ + [ + "browser.newtabpage.activity-stream.discoverystream.endpointSpocsClear", + "", + ], + ], + }); + + await setupPolicyEngineWithJson({ + policies: { + FirefoxHome: { + Search: false, + TopSites: false, + Highlights: false, + Snippets: false, + }, + }, + }); + + let tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: "about:home", + waitForStateStop: true, + }); + + await SpecialPowers.spawn(tab.linkedBrowser, [], function() { + let search = content.document.querySelector(".search-wrapper"); + is(search, null, "Search section should not be there."); + let topsites = content.document.querySelector( + "section[data-section-id='topsites']" + ); + is(topsites, null, "Top Sites section should not be there."); + let highlights = content.document.querySelector( + "section[data-section-id='highlights']" + ); + is(highlights, null, "Highlights section should not be there."); + }); + BrowserTestUtils.removeTab(tab); + await SpecialPowers.popPrefEnv(); +}); + +add_task(async function test_firefoxhome_preferences_set() { + await SpecialPowers.pushPrefEnv({ + set: [ + [ + "browser.newtabpage.activity-stream.discoverystream.endpointSpocsClear", + "", + ], + ], + }); + + await setupPolicyEngineWithJson({ + policies: { + FirefoxHome: { + Search: false, + TopSites: false, + SponsoredTopSites: false, + Highlights: false, + Pocket: false, + SponsoredPocket: false, + Snippets: false, + Locked: true, + }, + }, + }); + + await BrowserTestUtils.withNewTab("about:preferences#home", async browser => { + let data = { + Search: "browser.newtabpage.activity-stream.showSearch", + TopSites: "browser.newtabpage.activity-stream.feeds.topsites", + SponsoredTopSites: + "browser.newtabpage.activity-stream.showSponsoredTopSites", + Highlights: "browser.newtabpage.activity-stream.feeds.section.highlights", + Pocket: "browser.newtabpage.activity-stream.feeds.section.topstories", + SponsoredPocket: "browser.newtabpage.activity-stream.showSponsored", + Snippets: "browser.newtabpage.activity-stream.feeds.snippets", + }; + for (let [section, preference] of Object.entries(data)) { + is( + browser.contentDocument.querySelector( + `checkbox[preference='${preference}']` + ).disabled, + true, + `${section} checkbox should be disabled` + ); + } + }); + await setupPolicyEngineWithJson({ + policies: { + FirefoxHome: {}, + }, + }); + await SpecialPowers.popPrefEnv(); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_handlers.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_handlers.js new file mode 100644 index 0000000000..7ce301726c --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_handlers.js @@ -0,0 +1,186 @@ +/* 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: "http://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: "http://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/browser/components/enterprisepolicies/tests/browser/browser_policy_masterpassword.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_masterpassword.js new file mode 100644 index 0000000000..96440a61ca --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_masterpassword.js @@ -0,0 +1,95 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +let { LoginTestUtils } = ChromeUtils.import( + "resource://testing-common/LoginTestUtils.jsm" +); + +// 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(); + + await BrowserTestUtils.withNewTab( + "about:preferences#privacy", + async browser => { + is( + browser.contentDocument.getElementById("useMasterPassword").disabled, + true, + "Master Password checkbox should be disabled" + ); + } + ); + + 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/browser/components/enterprisepolicies/tests/browser/browser_policy_masterpassword_aboutlogins.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_masterpassword_aboutlogins.js new file mode 100644 index 0000000000..2084acee46 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_masterpassword_aboutlogins.js @@ -0,0 +1,76 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +let { LoginTestUtils } = ChromeUtils.import( + "resource://testing-common/LoginTestUtils.jsm" +); + +// Test that create in about:logins asks for primary password +add_task(async function test_policy_admin() { + await setupPolicyEngineWithJson({ + policies: { + PrimaryPassword: true, + }, + }); + + let aboutLoginsTab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + url: "about:logins", + }); + + let browser = gBrowser.selectedBrowser; + + // Fake the subdialog + let dialogURL = ""; + let originalOpenDialog = window.openDialog; + window.openDialog = function(aDialogURL, unused, unused2, aCallback) { + dialogURL = aDialogURL; + if (aCallback) { + aCallback(); + } + }; + + await SpecialPowers.spawn(browser, [], async () => { + let loginList = Cu.waiveXrays(content.document.querySelector("login-list")); + let createButton = loginList._createLoginButton; + ok( + !createButton.disabled, + "Create button should not be disabled initially" + ); + let loginItem = Cu.waiveXrays(content.document.querySelector("login-item")); + + createButton.click(); + + let usernameInput = loginItem.shadowRoot.querySelector( + "input[name='username']" + ); + let originInput = loginItem.shadowRoot.querySelector( + "input[name='origin']" + ); + let passwordInput = loginItem.shadowRoot.querySelector( + "input[name='password']" + ); + + originInput.value = "https://www.example.org"; + usernameInput.value = "testuser1"; + passwordInput.value = "testpass1"; + + let saveChangesButton = loginItem.shadowRoot.querySelector( + ".save-changes-button" + ); + saveChangesButton.click(); + }); + await TestUtils.waitForCondition( + () => dialogURL, + "wait for open to get called asynchronously" + ); + is( + dialogURL, + "chrome://mozapps/content/preferences/changemp.xhtml", + "clicking on the save-changes-button should open the masterpassword dialog" + ); + window.openDialog = originalOpenDialog; + BrowserTestUtils.removeTab(aboutLoginsTab); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_masterpassword_doorhanger.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_masterpassword_doorhanger.js new file mode 100644 index 0000000000..c055138a49 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_masterpassword_doorhanger.js @@ -0,0 +1,78 @@ +/** + * Test that the doorhanger notification for password saving is populated with + * the correct values in various password capture cases. + */ + +/* import-globals-from ../../../../../toolkit/components/passwordmgr/test/browser/head.js */ + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/toolkit/components/passwordmgr/test/browser/head.js", + this +); + +add_task(async function test_policy_masterpassword_doorhanger() { + await setupPolicyEngineWithJson({ + policies: { + PrimaryPassword: true, + }, + }); + + let username = "username"; + let password = "password"; + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: + "https://example.com/browser/toolkit/components/" + + "passwordmgr/test/browser/form_basic.html", + }, + async function(browser) { + await SimpleTest.promiseFocus(browser.ownerGlobal); + + // Update the form with credentials from the test case. + info(`update form with username: ${username}, password: ${password}`); + await changeContentFormValues(browser, { + "#form-basic-username": username, + "#form-basic-password": password, + }); + + // Submit the form with the new credentials. This will cause the doorhanger + // notification to be displayed. + let formSubmittedPromise = listenForTestNotification("ShowDoorhanger"); + await SpecialPowers.spawn(browser, [], async function() { + let doc = this.content.document; + doc.getElementById("form-basic").submit(); + }); + await formSubmittedPromise; + + let expectedDoorhanger = "password-save"; + + info("Waiting for doorhanger of type: " + expectedDoorhanger); + let notif = await waitForDoorhanger(browser, expectedDoorhanger); + + // Fake the subdialog + let dialogURL = ""; + let originalOpenDialog = window.openDialog; + window.openDialog = function(aDialogURL, unused, unused2, aCallback) { + dialogURL = aDialogURL; + if (aCallback) { + aCallback(); + } + }; + + await clickDoorhangerButton(notif, REMEMBER_BUTTON); + + await TestUtils.waitForCondition( + () => dialogURL, + "wait for open to get called asynchronously" + ); + is( + dialogURL, + "chrome://mozapps/content/preferences/changemp.xhtml", + "clicking on the checkbox should open the masterpassword dialog" + ); + window.openDialog = originalOpenDialog; + } + ); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_offertosavelogins.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_offertosavelogins.js new file mode 100644 index 0000000000..5ad1fe44d1 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_offertosavelogins.js @@ -0,0 +1,23 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function test_policy_offertosavelogins() { + await setupPolicyEngineWithJson({ + policies: { + OfferToSaveLogins: false, + }, + }); + + await BrowserTestUtils.withNewTab( + "about:preferences#privacy", + async browser => { + is( + browser.contentDocument.getElementById("savePasswords").disabled, + true, + "Save passwords is disabled" + ); + } + ); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_override_postupdatepage.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_override_postupdatepage.js new file mode 100644 index 0000000000..2208ee955f --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_override_postupdatepage.js @@ -0,0 +1,132 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// This test was based on the test browser_bug538331.js + +const UPDATE_PROVIDED_PAGE = "https://default.example.com/"; +const POLICY_PROVIDED_PAGE = "https://policy.example.com/"; + +const PREF_MSTONE = "browser.startup.homepage_override.mstone"; + +/* + * The important parts for this test are: + * - actions="showURL" + * - openURL="${UPDATE_PROVIDED_PAGE}" + */ +const XML_UPDATE = `<?xml version="1.0"?> +<updates xmlns="http://www.mozilla.org/2005/app-update"> + <update appVersion="1.0" buildID="20080811053724" channel="nightly" + displayVersion="Version 1.0" installDate="1238441400314" + isCompleteUpdate="true" name="Update Test 1.0" type="minor" + detailsURL="http://example.com/" previousAppVersion="1.0" + serviceURL="https://example.com/" statusText="The Update was successfully installed" + foregroundDownload="true" + actions="showURL" + openURL="${UPDATE_PROVIDED_PAGE}"> + <patch type="complete" URL="http://example.com/" size="775" selected="true" state="succeeded"/> + </update> +</updates>`; + +add_task(async function test_override_postupdate_page() { + let originalMstone = Services.prefs.getCharPref(PREF_MSTONE); + // Set the preferences needed for the test: they will be cleared up + // after it runs. + await SpecialPowers.pushPrefEnv({ set: [[PREF_MSTONE, originalMstone]] }); + + registerCleanupFunction(async () => { + let activeUpdateFile = getActiveUpdateFile(); + activeUpdateFile.remove(false); + reloadUpdateManagerData(true); + }); + + writeUpdatesToXMLFile(XML_UPDATE); + reloadUpdateManagerData(false); + + is( + getPostUpdatePage(), + UPDATE_PROVIDED_PAGE, + "Post-update page was provided by active-update.xml." + ); + + // Now perform the same action but set the policy to override this page + await setupPolicyEngineWithJson({ + policies: { + OverridePostUpdatePage: POLICY_PROVIDED_PAGE, + }, + }); + + is( + getPostUpdatePage(), + POLICY_PROVIDED_PAGE, + "Post-update page was provided by policy." + ); +}); + +function getPostUpdatePage() { + Services.prefs.setCharPref(PREF_MSTONE, "PreviousMilestone"); + return Cc["@mozilla.org/browser/clh;1"].getService(Ci.nsIBrowserHandler) + .defaultArgs; +} + +/** + * Removes the updates.xml file and returns the nsIFile for the + * active-update.xml file. + * + * @return The nsIFile for the active-update.xml file. + */ +function getActiveUpdateFile() { + let updateRootDir = Services.dirsvc.get("UpdRootD", Ci.nsIFile); + let updatesFile = updateRootDir.clone(); + updatesFile.append("updates.xml"); + if (updatesFile.exists()) { + // The following is non-fatal. + try { + updatesFile.remove(false); + } catch (e) {} + } + let activeUpdateFile = updateRootDir.clone(); + activeUpdateFile.append("active-update.xml"); + return activeUpdateFile; +} + +/** + * Reloads the update xml files. + * + * @param skipFiles (optional) + * If true, the update xml files will not be read and the metadata will + * be reset. If false (the default), the update xml files will be read + * to populate the update metadata. + */ +function reloadUpdateManagerData(skipFiles = false) { + Cc["@mozilla.org/updates/update-manager;1"] + .getService(Ci.nsIUpdateManager) + .QueryInterface(Ci.nsIObserver) + .observe(null, "um-reload-update-data", skipFiles ? "skip-files" : ""); +} + +/** + * Writes the updates specified to the active-update.xml file. + * + * @param aText + * The updates represented as a string to write to the active-update.xml + * file. + */ +function writeUpdatesToXMLFile(aText) { + const PERMS_FILE = 0o644; + + const MODE_WRONLY = 0x02; + const MODE_CREATE = 0x08; + const MODE_TRUNCATE = 0x20; + + let activeUpdateFile = getActiveUpdateFile(); + if (!activeUpdateFile.exists()) { + activeUpdateFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE); + } + let fos = Cc["@mozilla.org/network/file-output-stream;1"].createInstance( + Ci.nsIFileOutputStream + ); + let flags = MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE; + fos.init(activeUpdateFile, flags, PERMS_FILE, 0); + fos.write(aText, aText.length); + fos.close(); +} diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_pageinfo_permissions.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_pageinfo_permissions.js new file mode 100644 index 0000000000..6ae0e85738 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_pageinfo_permissions.js @@ -0,0 +1,76 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const TEST_ORIGIN = "https://example.com"; + +/* Verifies that items on the page info page are properly disabled + when the corresponding policies are locked */ +add_task(async function test_pageinfo_permissions() { + await setupPolicyEngineWithJson({ + policies: { + Permissions: { + Camera: { + BlockNewRequests: true, + Locked: true, + }, + Microphone: { + BlockNewRequests: true, + Locked: true, + }, + Location: { + BlockNewRequests: true, + Locked: true, + }, + Notifications: { + BlockNewRequests: true, + Locked: true, + }, + VirtualReality: { + BlockNewRequests: true, + Locked: true, + }, + Autoplay: { + Default: "block-audio", + Locked: true, + }, + }, + InstallAddonsPermission: { + Default: false, + }, + PopupBlocking: { + Locked: true, + }, + Cookies: { + Locked: true, + }, + }, + }); + + let permissions = [ + "geo", + "autoplay-media", + "install", + "popup", + "desktop-notification", + "cookie", + "camera", + "microphone", + "xr", + ]; + + await BrowserTestUtils.withNewTab(TEST_ORIGIN, async function(browser) { + let pageInfo = BrowserPageInfo(TEST_ORIGIN, "permTab"); + await BrowserTestUtils.waitForEvent(pageInfo, "load"); + + for (let i = 0; i < permissions.length; i++) { + let permission = permissions[i]; + let checkbox = await TestUtils.waitForCondition(() => + pageInfo.document.getElementById(`${permission}Def`) + ); + + ok(checkbox.disabled, `${permission} checkbox should be disabled`); + } + + pageInfo.close(); + }); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_passwordmanager.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_passwordmanager.js new file mode 100644 index 0000000000..f62d6cbf50 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_passwordmanager.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_pwmanager_blocked() { + await setupPolicyEngineWithJson({ + policies: { + PasswordManagerEnabled: false, + }, + }); + + await BrowserTestUtils.withNewTab( + "about:preferences#privacy", + async browser => { + is( + browser.contentDocument.getElementById("showPasswords").disabled, + true, + "showPasswords should be disabled." + ); + } + ); + + await testPageBlockedByPolicy("about:logins"); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_search_engine.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_search_engine.js new file mode 100644 index 0000000000..d76f0a3d82 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_search_engine.js @@ -0,0 +1,109 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +XPCOMUtils.defineLazyModuleGetters(this, { + CustomizableUITestUtils: + "resource://testing-common/CustomizableUITestUtils.jsm", +}); + +let gCUITestUtils = new CustomizableUITestUtils(window); + +add_task(async function test_setup() { + await gCUITestUtils.addSearchBar(); + registerCleanupFunction(() => { + gCUITestUtils.removeSearchBar(); + }); +}); + +// |shouldWork| should be true if opensearch is expected to work and false if +// it is not. +async function test_opensearch(shouldWork) { + let searchBar = BrowserSearch.searchBar; + + let rootDir = getRootDirectory(gTestPath); + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + rootDir + "opensearch.html" + ); + let searchPopup = document.getElementById("PopupSearchAutoComplete"); + let promiseSearchPopupShown = BrowserTestUtils.waitForEvent( + searchPopup, + "popupshown" + ); + let searchBarButton = searchBar.querySelector(".searchbar-search-button"); + + searchBarButton.click(); + await promiseSearchPopupShown; + let oneOffsContainer = searchPopup.searchOneOffsContainer; + let engineElement = oneOffsContainer.querySelector( + ".searchbar-engine-one-off-add-engine" + ); + if (shouldWork) { + ok(engineElement, "There should be search engines available to add"); + ok( + searchBar.getAttribute("addengines"), + "Search bar should have addengines attribute" + ); + } else { + is( + engineElement, + null, + "There should be no search engines available to add" + ); + ok( + !searchBar.getAttribute("addengines"), + "Search bar should not have addengines attribute" + ); + } + await BrowserTestUtils.removeTab(tab); +} + +add_task(async function test_opensearch_works() { + // Clear out policies so we can test with no policies applied + await setupPolicyEngineWithJson({ + policies: {}, + }); + // Ensure that opensearch works before we make sure that it can be properly + // disabled + await test_opensearch(true); +}); + +add_task(async function setup_prevent_installs() { + await setupPolicyEngineWithJson({ + policies: { + SearchEngines: { + PreventInstalls: true, + }, + }, + }); +}); + +add_task(async function test_prevent_install_ui() { + // Check that about:preferences does not prompt user to install search engines + // if that feature is disabled + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "about:preferences#search" + ); + await SpecialPowers.spawn(tab.linkedBrowser, [], async function() { + let linkContainer = content.document.getElementById("addEnginesBox"); + if (!linkContainer.hidden) { + await ContentTaskUtils.waitForMutationCondition( + linkContainer, + { attributeFilter: ["hidden"] }, + () => linkContainer.hidden + ); + } + ok( + linkContainer.hidden, + '"Find more search engines" link should be hidden' + ); + }); + await BrowserTestUtils.removeTab(tab); +}); + +add_task(async function test_opensearch_disabled() { + // Check that search engines cannot be added via opensearch + await test_opensearch(false); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_searchbar.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_searchbar.js new file mode 100644 index 0000000000..fed3f3b498 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_searchbar.js @@ -0,0 +1,36 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_setup(async function() { + await setupPolicyEngineWithJson({ + policies: { + SearchBar: "separate", + }, + }); +}); + +add_task(async function test_menu_shown() { + let newWin = await BrowserTestUtils.openNewBrowserWindow(); + let placement = CustomizableUI.getPlacementOfWidget("search-container"); + isnot(placement, null, "Search bar has a placement"); + is( + placement.area, + CustomizableUI.AREA_NAVBAR, + "Search bar is in the nav bar" + ); + await BrowserTestUtils.closeWindow(newWin); +}); + +add_task(async function test_menu_shown() { + await setupPolicyEngineWithJson({ + policies: { + SearchBar: "unified", + }, + }); + let newWin = await BrowserTestUtils.openNewBrowserWindow(); + let placement = CustomizableUI.getPlacementOfWidget("search-container"); + is(placement, null, "Search bar has no placement"); + await BrowserTestUtils.closeWindow(newWin); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_set_homepage.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_set_homepage.js new file mode 100644 index 0000000000..0c586fc45f --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_set_homepage.js @@ -0,0 +1,115 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +registerCleanupFunction(function restore_pref_values() { + // These two prefs are set as user prefs in case the "Locked" + // option from this policy was not used. In this case, it won't + // be tracked nor restored by the PoliciesPrefTracker. + Services.prefs.clearUserPref("browser.startup.homepage"); +}); + +add_task(async function homepage_test_simple() { + await setupPolicyEngineWithJson({ + policies: { + Homepage: { + URL: "http://example1.com/", + }, + }, + }); + await check_homepage({ expectedURL: "http://example1.com/" }); +}); + +add_task(async function homepage_test_repeat_same_policy_value() { + // Simulate homepage change after policy applied + Services.prefs.setStringPref( + "browser.startup.homepage", + "http://example2.com/" + ); + Services.prefs.setIntPref("browser.startup.page", 3); + + // Policy should have no effect. Homepage has not been locked and policy value + // has not changed. We should be respecting the homepage that the user gave. + await setupPolicyEngineWithJson({ + policies: { + Homepage: { + URL: "http://example1.com/", + }, + }, + }); + await check_homepage({ + expectedURL: "http://example2.com/", + expectedPageVal: 3, + }); + Services.prefs.clearUserPref("browser.startup.page"); + Services.prefs.clearUserPref("browser.startup.homepage"); +}); + +add_task(async function homepage_test_empty_additional() { + await setupPolicyEngineWithJson({ + policies: { + Homepage: { + URL: "http://example1.com/", + Additional: [], + }, + }, + }); + await check_homepage({ expectedURL: "http://example1.com/" }); +}); + +add_task(async function homepage_test_single_additional() { + await setupPolicyEngineWithJson({ + policies: { + Homepage: { + URL: "http://example1.com/", + Additional: ["http://example2.com/"], + }, + }, + }); + await check_homepage({ + expectedURL: "http://example1.com/|http://example2.com/", + }); +}); + +add_task(async function homepage_test_multiple_additional() { + await setupPolicyEngineWithJson({ + policies: { + Homepage: { + URL: "http://example1.com/", + Additional: ["http://example2.com/", "http://example3.com/"], + }, + }, + }); + await check_homepage({ + expectedURL: + "http://example1.com/|http://example2.com/|http://example3.com/", + }); +}); + +add_task(async function homepage_test_locked() { + await setupPolicyEngineWithJson({ + policies: { + Homepage: { + URL: "http://example4.com/", + Additional: ["http://example5.com/", "http://example6.com/"], + Locked: true, + }, + }, + }); + await check_homepage({ + expectedURL: + "http://example4.com/|http://example5.com/|http://example6.com/", + locked: true, + }); +}); + +add_task(async function homepage_test_anchor_link() { + await setupPolicyEngineWithJson({ + policies: { + Homepage: { + URL: "http://example1.com/#test", + }, + }, + }); + await check_homepage({ expectedURL: "http://example1.com/#test" }); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_set_startpage.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_set_startpage.js new file mode 100644 index 0000000000..72bc87e8d6 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_set_startpage.js @@ -0,0 +1,68 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +add_setup(async function() { + // browser.startup.page is set by unittest-required/user.js, + // but we need the default value + await SpecialPowers.pushPrefEnv({ + clear: [["browser.startup.page"]], + }); +}); + +add_task(async function homepage_test_startpage_homepage() { + await setupPolicyEngineWithJson({ + policies: { + Homepage: { + URL: "http://example1.com/#test", + StartPage: "homepage", + }, + }, + }); + await check_homepage({ + expectedURL: "http://example1.com/#test", + expectedPageVal: 1, + }); +}); + +add_task(async function homepage_test_startpage_homepage_locked() { + await setupPolicyEngineWithJson({ + policies: { + Homepage: { + URL: "http://example1.com/#test", + StartPage: "homepage-locked", + Locked: true, + }, + }, + }); + await check_homepage({ + expectedURL: "http://example1.com/#test", + expectedPageVal: 1, + locked: true, + }); +}); + +add_task(async function homepage_test_startpage_none() { + await setupPolicyEngineWithJson({ + policies: { + Homepage: { + StartPage: "none", + }, + }, + }); + await check_homepage({ + expectedURL: "chrome://browser/content/blanktab.html", + expectedPageVal: 1, + }); +}); + +add_task(async function homepage_test_startpage_restore() { + await setupPolicyEngineWithJson({ + policies: { + Homepage: { + StartPage: "previous-session", + }, + }, + }); + await check_homepage({ expectedPageVal: 3 }); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_support_menu.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_support_menu.js new file mode 100644 index 0000000000..5f46ef24e4 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_support_menu.js @@ -0,0 +1,68 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +add_setup(async function() { + await setupPolicyEngineWithJson({ + policies: { + SupportMenu: { + Title: "Title", + URL: "https://example.com/", + AccessKey: "T", + }, + }, + }); +}); + +add_task(async function test_help_menu() { + is( + Services.policies.getSupportMenu().URL.href, + "https://example.com/", + "The policy should have the correct URL." + ); + buildHelpMenu(); + let supportMenu = document.getElementById("helpPolicySupport"); + is(supportMenu.hidden, false, "The policy menu should be visible."); + is( + supportMenu.getAttribute("label"), + "Title", + "The policy menu should have the correct title." + ); + is( + supportMenu.getAttribute("accesskey"), + "T", + "The policy menu should have the correct access key." + ); +}); + +add_task(async function test_help_menu_app_menu() { + is( + Services.policies.getSupportMenu().URL.href, + "https://example.com/", + "The policy should have the correct URL." + ); + let menuButton = document.getElementById("PanelUI-menu-button"); + menuButton.click(); + await BrowserTestUtils.waitForEvent(window.PanelUI.mainView, "ViewShown"); + + let helpButtonId = "appMenu-help-button2"; + document.getElementById(helpButtonId).click(); + await BrowserTestUtils.waitForEvent( + document.getElementById("PanelUI-helpView"), + "ViewShown" + ); + + let supportMenu = document.getElementById("appMenu_helpPolicySupport"); + is(supportMenu.hidden, false, "The policy menu should be visible."); + is( + supportMenu.getAttribute("label"), + "Title", + "The policy menu should have the correct title." + ); + is( + supportMenu.getAttribute("accesskey"), + "T", + "The policy menu should have the correct access key." + ); + window.PanelUI.hide(); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_usermessaging.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_usermessaging.js new file mode 100644 index 0000000000..d8a3381779 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_usermessaging.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_notice_in_aboutprefences() { + await setupPolicyEngineWithJson({ + policies: { + UserMessaging: { + MoreFromMozilla: false, + }, + }, + }); + + await BrowserTestUtils.withNewTab("about:preferences", async browser => { + let moreFromMozillaCategory = browser.contentDocument.getElementById( + "category-more-from-mozilla" + ); + ok(moreFromMozillaCategory.hidden, "The category is hidden"); + }); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_websitefilter.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_websitefilter.js new file mode 100644 index 0000000000..cf91872024 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_websitefilter.js @@ -0,0 +1,140 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +const SUPPORT_FILES_PATH = + "http://mochi.test:8888/browser/browser/components/enterprisepolicies/tests/browser/"; +const BLOCKED_PAGE = "policy_websitefilter_block.html"; +const EXCEPTION_PAGE = "policy_websitefilter_exception.html"; +const SAVELINKAS_PAGE = "policy_websitefilter_savelink.html"; + +add_task(async function test_http() { + await setupPolicyEngineWithJson({ + policies: { + WebsiteFilter: { + Block: ["*://mochi.test/*policy_websitefilter_*"], + Exceptions: ["*://mochi.test/*_websitefilter_exception*"], + }, + }, + }); + + await checkBlockedPage(SUPPORT_FILES_PATH + BLOCKED_PAGE, true); + await checkBlockedPage( + "view-source:" + SUPPORT_FILES_PATH + BLOCKED_PAGE, + true + ); + await checkBlockedPage( + "about:reader?url=" + SUPPORT_FILES_PATH + BLOCKED_PAGE, + true + ); + await checkBlockedPage( + "about:READER?url=" + SUPPORT_FILES_PATH + BLOCKED_PAGE, + true + ); + await checkBlockedPage(SUPPORT_FILES_PATH + EXCEPTION_PAGE, false); + + await checkBlockedPage(SUPPORT_FILES_PATH + "301.sjs", true); + + await checkBlockedPage(SUPPORT_FILES_PATH + "302.sjs", true); +}); + +add_task(async function test_http_mixed_case() { + await setupPolicyEngineWithJson({ + policies: { + WebsiteFilter: { + Block: ["*://mochi.test/*policy_websitefilter_*"], + Exceptions: ["*://mochi.test/*_websitefilter_exception*"], + }, + }, + }); + + await checkBlockedPage(SUPPORT_FILES_PATH + BLOCKED_PAGE.toUpperCase(), true); + await checkBlockedPage( + SUPPORT_FILES_PATH + EXCEPTION_PAGE.toUpperCase(), + false + ); +}); + +add_task(async function test_file() { + await setupPolicyEngineWithJson({ + policies: { + WebsiteFilter: { + Block: ["file:///*"], + }, + }, + }); + + await checkBlockedPage("file:///this_should_be_blocked", true); +}); + +add_task(async function test_savelink() { + await setupPolicyEngineWithJson({ + policies: { + WebsiteFilter: { + Block: ["*://mochi.test/*policy_websitefilter_block*"], + }, + }, + }); + + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + SUPPORT_FILES_PATH + SAVELINKAS_PAGE + ); + + let contextMenu = document.getElementById("contentAreaContextMenu"); + let promiseContextMenuOpen = BrowserTestUtils.waitForEvent( + contextMenu, + "popupshown" + ); + await BrowserTestUtils.synthesizeMouse( + "#savelink_blocked", + 0, + 0, + { + type: "contextmenu", + button: 2, + centered: true, + }, + gBrowser.selectedBrowser + ); + await promiseContextMenuOpen; + + let saveLink = document.getElementById("context-savelink"); + is(saveLink.disabled, true, "Save Link As should be disabled"); + + let promiseContextMenuHidden = BrowserTestUtils.waitForEvent( + contextMenu, + "popuphidden" + ); + contextMenu.hidePopup(); + await promiseContextMenuHidden; + + promiseContextMenuOpen = BrowserTestUtils.waitForEvent( + contextMenu, + "popupshown" + ); + await BrowserTestUtils.synthesizeMouse( + "#savelink_notblocked", + 0, + 0, + { + type: "contextmenu", + button: 2, + centered: true, + }, + gBrowser.selectedBrowser + ); + await promiseContextMenuOpen; + + saveLink = document.getElementById("context-savelink"); + is(saveLink.disabled, false, "Save Link As should not be disabled"); + + promiseContextMenuHidden = BrowserTestUtils.waitForEvent( + contextMenu, + "popuphidden" + ); + contextMenu.hidePopup(); + await promiseContextMenuHidden; + + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/disable_app_update/browser.ini b/browser/components/enterprisepolicies/tests/browser/disable_app_update/browser.ini new file mode 100644 index 0000000000..d311bcf910 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/disable_app_update/browser.ini @@ -0,0 +1,9 @@ +[DEFAULT] +prefs = + app.update.disabledForTesting=false + browser.policies.alternatePath='<test-root>/browser/components/enterprisepolicies/tests/browser/disable_app_update/config_disable_app_update.json' +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/browser/components/enterprisepolicies/tests/browser/disable_app_update/browser_policy_disable_app_update.js b/browser/components/enterprisepolicies/tests/browser/disable_app_update/browser_policy_disable_app_update.js new file mode 100644 index 0000000000..2cdb474355 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/disable_app_update/browser_policy_disable_app_update.js @@ -0,0 +1,121 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +ChromeUtils.defineESModuleGetters(this, { + UrlbarTestUtils: "resource://testing-common/UrlbarTestUtils.sys.mjs", +}); + +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 tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "about:preferences" + ); + + await SpecialPowers.spawn(tab.linkedBrowser, [], async function() { + let setting = content.document.getElementById("updateSettingsContainer"); + is( + setting.hidden, + true, + "Update choices should be disabled when app update is locked by policy" + ); + }); + + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function test_update_about_ui() { + let aboutDialog = await waitForAboutDialog(); + let panelId = "policyDisabled"; + + await BrowserTestUtils.waitForCondition( + () => 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. + * + * @return 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: aXULWindow => { + Services.wm.removeListener(listener); + + async function aboutDialogOnLoad() { + domwindow.removeEventListener("load", aboutDialogOnLoad, true); + let chromeURI = "chrome://browser/content/aboutDialog.xhtml"; + is( + domwindow.document.location.href, + chromeURI, + "About dialog appeared" + ); + resolve(domwindow); + } + + var domwindow = aXULWindow.docShell.domWindow; + domwindow.addEventListener("load", aboutDialogOnLoad, true); + }, + onCloseWindow: aXULWindow => {}, + }; + + Services.wm.addListener(listener); + openAboutDialog(); + }); +} + +add_task(async function test_no_update_intervention() { + await BrowserTestUtils.withNewTab("about:blank", async () => { + let context = await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "update firefox", + waitForFocus, + fireInputEvent: true, + }); + for (let result of context.results) { + Assert.notEqual(result.type, UrlbarUtils.RESULT_TYPE.TIP); + } + await UrlbarTestUtils.promisePopupClose(window, () => + window.gURLBar.blur() + ); + }); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/disable_app_update/config_disable_app_update.json b/browser/components/enterprisepolicies/tests/browser/disable_app_update/config_disable_app_update.json new file mode 100644 index 0000000000..f36622021f --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/disable_app_update/config_disable_app_update.json @@ -0,0 +1,5 @@ +{ + "policies": { + "DisableAppUpdate": true + } +} diff --git a/browser/components/enterprisepolicies/tests/browser/disable_default_bookmarks/bookmarks_policies.json b/browser/components/enterprisepolicies/tests/browser/disable_default_bookmarks/bookmarks_policies.json new file mode 100644 index 0000000000..117768efb3 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/disable_default_bookmarks/bookmarks_policies.json @@ -0,0 +1,5 @@ +{ + "policies": { + "NoDefaultBookmarks": true + } +} diff --git a/browser/components/enterprisepolicies/tests/browser/disable_default_bookmarks/browser.ini b/browser/components/enterprisepolicies/tests/browser/disable_default_bookmarks/browser.ini new file mode 100644 index 0000000000..b7469c550e --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/disable_default_bookmarks/browser.ini @@ -0,0 +1,7 @@ +[DEFAULT] +prefs = + browser.policies.alternatePath='<test-root>/browser/components/enterprisepolicies/tests/browser/disable_default_bookmarks/bookmarks_policies.json' +support-files = + bookmarks_policies.json + +[browser_policy_no_default_bookmarks.js] diff --git a/browser/components/enterprisepolicies/tests/browser/disable_default_bookmarks/browser_policy_no_default_bookmarks.js b/browser/components/enterprisepolicies/tests/browser/disable_default_bookmarks/browser_policy_no_default_bookmarks.js new file mode 100644 index 0000000000..aecd088ab3 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/disable_default_bookmarks/browser_policy_no_default_bookmarks.js @@ -0,0 +1,24 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// This test must run in a separate folder because the +// No Default Bookmarks policy needs to be present on +// the first run of the profile, and not dinamically loaded +// like most of the policies tested in the main test folder. + +add_task(async function test_no_default_bookmarks() { + let firstBookmarkOnToolbar = await PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: 0, + }); + + let firstBookmarkOnMenu = await PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.menuGuid, + index: 0, + }); + + is(firstBookmarkOnToolbar, null, "No bookmarks on toolbar"); + is(firstBookmarkOnMenu, null, "No bookmarks on menu"); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/disable_developer_tools/browser.ini b/browser/components/enterprisepolicies/tests/browser/disable_developer_tools/browser.ini new file mode 100644 index 0000000000..3db44612f8 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/disable_developer_tools/browser.ini @@ -0,0 +1,7 @@ +[DEFAULT] +prefs = + browser.policies.alternatePath='<test-root>/browser/components/enterprisepolicies/tests/browser/disable_developer_tools/config_disable_developer_tools.json' +support-files = + config_disable_developer_tools.json + +[browser_policy_disable_developer_tools.js] diff --git a/browser/components/enterprisepolicies/tests/browser/disable_developer_tools/browser_policy_disable_developer_tools.js b/browser/components/enterprisepolicies/tests/browser/disable_developer_tools/browser_policy_disable_developer_tools.js new file mode 100644 index 0000000000..b0534891c7 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/disable_developer_tools/browser_policy_disable_developer_tools.js @@ -0,0 +1,87 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { EnterprisePolicyTesting } = ChromeUtils.importESModule( + "resource://testing-common/EnterprisePolicyTesting.sys.mjs" +); +var updateService = Cc["@mozilla.org/updates/update-service;1"].getService( + Ci.nsIApplicationUpdateService +); + +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 testPageBlockedByPolicy("about:devtools-toolbox"); + await testPageBlockedByPolicy("about:debugging"); + await testPageBlockedByPolicy("about:profiling"); + + let testURL = "data:text/html;charset=utf-8,test"; + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + testURL, + false + ); + + let menuButton = document.getElementById("PanelUI-menu-button"); + menuButton.click(); + await BrowserTestUtils.waitForEvent(window.PanelUI.mainView, "ViewShown"); + let moreToolsButtonId = "appMenu-more-button2"; + document.getElementById(moreToolsButtonId).click(); + await BrowserTestUtils.waitForEvent( + document.getElementById("appmenu-moreTools"), + "ViewShown" + ); + is( + document.getElementById("appmenu-developer-tools-view").children.length, + 2, + "The developer tools are properly populated" + ); + window.PanelUI.hide(); + + BrowserTestUtils.removeTab(tab); +}); + +// Copied from ../head.js. head.js was never intended to be used with tests +// that use a JSON file versus calling setupPolicyEngineWithJson so I have +// to copy this function here versus including it. +async function testPageBlockedByPolicy(page, policyJSON) { + if (policyJSON) { + await EnterprisePolicyTesting.setupPolicyEngineWithJson(policyJSON); + } + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async browser => { + BrowserTestUtils.loadURI(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/browser/components/enterprisepolicies/tests/browser/disable_developer_tools/config_disable_developer_tools.json b/browser/components/enterprisepolicies/tests/browser/disable_developer_tools/config_disable_developer_tools.json new file mode 100644 index 0000000000..08c393dec6 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/disable_developer_tools/config_disable_developer_tools.json @@ -0,0 +1,5 @@ +{ + "policies": { + "DisableDeveloperTools": true + } +} diff --git a/browser/components/enterprisepolicies/tests/browser/disable_forget_button/browser.ini b/browser/components/enterprisepolicies/tests/browser/disable_forget_button/browser.ini new file mode 100644 index 0000000000..689f54987e --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/disable_forget_button/browser.ini @@ -0,0 +1,7 @@ +[DEFAULT] +prefs = + browser.policies.alternatePath='<test-root>/browser/components/enterprisepolicies/tests/browser/disable_forget_button/forget_button.json' +support-files = + forget_button.json + +[browser_policy_disable_forgetbutton.js] diff --git a/browser/components/enterprisepolicies/tests/browser/disable_forget_button/browser_policy_disable_forgetbutton.js b/browser/components/enterprisepolicies/tests/browser/disable_forget_button/browser_policy_disable_forgetbutton.js new file mode 100644 index 0000000000..723aad5d75 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/disable_forget_button/browser_policy_disable_forgetbutton.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_disable_forget_button() { + let widget = CustomizableUI.getWidget("panic-button"); + isnot(widget.type, "view", "Forget Button was not created"); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/disable_forget_button/forget_button.json b/browser/components/enterprisepolicies/tests/browser/disable_forget_button/forget_button.json new file mode 100644 index 0000000000..30baf64df4 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/disable_forget_button/forget_button.json @@ -0,0 +1,5 @@ +{ + "policies": { + "DisableForgetButton": true + } +} diff --git a/browser/components/enterprisepolicies/tests/browser/disable_fxscreenshots/browser.ini b/browser/components/enterprisepolicies/tests/browser/disable_fxscreenshots/browser.ini new file mode 100644 index 0000000000..9c828828c0 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/disable_fxscreenshots/browser.ini @@ -0,0 +1,8 @@ +[DEFAULT] +prefs = + browser.policies.alternatePath='<test-root>/browser/components/enterprisepolicies/tests/browser/disable_fxscreenshots/config_disable_fxscreenshots.json' + extensions.screenshots.disabled=false +support-files = + config_disable_fxscreenshots.json + +[browser_policy_disable_fxscreenshots.js] diff --git a/browser/components/enterprisepolicies/tests/browser/disable_fxscreenshots/browser_policy_disable_fxscreenshots.js b/browser/components/enterprisepolicies/tests/browser/disable_fxscreenshots/browser_policy_disable_fxscreenshots.js new file mode 100644 index 0000000000..3c08f2b177 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/disable_fxscreenshots/browser_policy_disable_fxscreenshots.js @@ -0,0 +1,35 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const PREF_DISABLE_FX_SCREENSHOTS = "extensions.screenshots.disabled"; + +async function checkScreenshots(shouldBeEnabled) { + return BrowserTestUtils.waitForCondition(() => { + return ( + !!PageActions.actionForID("screenshots_mozilla_org") == shouldBeEnabled + ); + }, "Expecting screenshots to be " + shouldBeEnabled); +} + +add_task(async function test_disable_firefox_screenshots() { + // Dynamically toggling the PREF_DISABLE_FX_SCREENSHOTS is very finicky, because + // that pref is being watched, and it makes the Firefox Screenshots system add-on + // to start or stop, causing intermittency. + // + // Firefox Screenshots is disabled by default on tests (in + // testing/profiles/common/user.js). What we do here to test this policy is to enable + // it on this specific test folder (through browser.ini) and then we let the policy + // engine be responsible for disabling Firefox Screenshots in this case. + + is( + Services.prefs.getBoolPref(PREF_DISABLE_FX_SCREENSHOTS), + true, + "Screenshots pref is disabled" + ); + + await BrowserTestUtils.withNewTab("data:text/html,Test", async function() { + await checkScreenshots(false); + }); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/disable_fxscreenshots/config_disable_fxscreenshots.json b/browser/components/enterprisepolicies/tests/browser/disable_fxscreenshots/config_disable_fxscreenshots.json new file mode 100644 index 0000000000..4caf055394 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/disable_fxscreenshots/config_disable_fxscreenshots.json @@ -0,0 +1,5 @@ +{ + "policies": { + "DisableFirefoxScreenshots": true + } +} diff --git a/browser/components/enterprisepolicies/tests/browser/extensionsettings.html b/browser/components/enterprisepolicies/tests/browser/extensionsettings.html new file mode 100644 index 0000000000..da70ebdf59 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/extensionsettings.html @@ -0,0 +1,28 @@ + +<!DOCTYPE html> +<html> +<head> +<meta charset="UTF-8"> +<script type="text/javascript"> +function installTrigger(url) { + try { + InstallTrigger.install({extension: url}); + } catch (err) { + dump(`Failed to execute InstallTrigger.install: ${err}\n`); + } + return false; +} +</script> +</head> +<body> +<p> +<a id="policytest" href="policytest_v0.1.xpi">policytest@mozilla.com</a> +</p> +<p> +<a id="policytest_installtrigger" onclick="return installTrigger(this.href);" href="policytest_v0.1.xpi">policytest@mozilla.com</a> +</p> +<p> +<a id="policytest_otherdomain" href="http://example.org:80/browser/browser/components/enterprisepolicies/tests/browser/policytest_v0.1.xpi">policytest@mozilla.com</a> +</p> +</body> +</html> diff --git a/browser/components/enterprisepolicies/tests/browser/hardware_acceleration/browser.ini b/browser/components/enterprisepolicies/tests/browser/hardware_acceleration/browser.ini new file mode 100644 index 0000000000..9f7b977e6c --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/hardware_acceleration/browser.ini @@ -0,0 +1,7 @@ +[DEFAULT] +prefs = + browser.policies.alternatePath='<test-root>/browser/components/enterprisepolicies/tests/browser/hardware_acceleration/disable_hardware_acceleration.json' +support-files = + disable_hardware_acceleration.json + +[browser_policy_hardware_acceleration.js] diff --git a/browser/components/enterprisepolicies/tests/browser/hardware_acceleration/browser_policy_hardware_acceleration.js b/browser/components/enterprisepolicies/tests/browser/hardware_acceleration/browser_policy_hardware_acceleration.js new file mode 100644 index 0000000000..436bd410d1 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/hardware_acceleration/browser_policy_hardware_acceleration.js @@ -0,0 +1,13 @@ +/* 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; + let layerManager = winUtils.layerManagerType; + ok( + layerManager == "Basic" || layerManager == "WebRender (Software)", + "Hardware acceleration disabled" + ); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/hardware_acceleration/disable_hardware_acceleration.json b/browser/components/enterprisepolicies/tests/browser/hardware_acceleration/disable_hardware_acceleration.json new file mode 100644 index 0000000000..acbdc0a3f4 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/hardware_acceleration/disable_hardware_acceleration.json @@ -0,0 +1,5 @@ +{ + "policies": { + "HardwareAcceleration": false + } +} diff --git a/browser/components/enterprisepolicies/tests/browser/head.js b/browser/components/enterprisepolicies/tests/browser/head.js new file mode 100644 index 0000000000..97fa310fcf --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/head.js @@ -0,0 +1,255 @@ +/* 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" +); + +ChromeUtils.defineModuleGetter( + this, + "HomePage", + "resource:///modules/HomePage.jsm" +); + +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); +} + +// Checks that a page was blocked by seeing if it was replaced with about:neterror +async function checkBlockedPage(url, expectedBlocked) { + let newTab = BrowserTestUtils.addTab(gBrowser); + gBrowser.selectedTab = newTab; + + if (expectedBlocked) { + let promise = BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser); + BrowserTestUtils.loadURI(gBrowser, url); + await promise; + is( + newTab.linkedBrowser.documentURI.spec.startsWith( + "about:neterror?e=blockedByPolicy" + ), + true, + "Should be blocked by policy" + ); + } else { + let promise = BrowserTestUtils.browserStopped(gBrowser, url); + BrowserTestUtils.loadURI(gBrowser, url); + await promise; + + is( + newTab.linkedBrowser.documentURI.spec, + url, + "Should not be blocked by policy" + ); + } + BrowserTestUtils.removeTab(newTab); +} + +async function check_homepage({ + expectedURL, + expectedPageVal = -1, + locked = false, +}) { + if (expectedURL) { + is(HomePage.get(), expectedURL, "Homepage URL should match expected"); + is( + Services.prefs.prefIsLocked("browser.startup.homepage"), + locked, + "Lock status of browser.startup.homepage should match expected" + ); + } + if (expectedPageVal != -1) { + is( + Services.prefs.getIntPref("browser.startup.page", -1), + expectedPageVal, + "Pref page value should match expected" + ); + is( + Services.prefs.prefIsLocked("browser.startup.page"), + locked, + "Lock status of browser.startup.page should match expected" + ); + } + + // Test that UI is disabled when the Locked property is enabled + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "about:preferences" + ); + await ContentTask.spawn( + tab.linkedBrowser, + { expectedURL, expectedPageVal, locked }, + // eslint-disable-next-line no-shadow + async function({ expectedURL, expectedPageVal, locked }) { + if (expectedPageVal != -1) { + // Only check restore checkbox for StartPage + let browserRestoreSessionCheckbox = content.document.getElementById( + "browserRestoreSession" + ); + is( + browserRestoreSessionCheckbox.disabled, + locked, + "Disabled status of session restore status should match expected" + ); + let shouldBeChecked = expectedPageVal === 3; + is( + browserRestoreSessionCheckbox.checked, + shouldBeChecked, + "Session restore status checkbox should be: " + + (shouldBeChecked ? "checked" : "unchecked") + ); + } + + if (!expectedURL) { + // If only StartPage was changed, no need to check these + return; + } + await content.gotoPref("paneHome"); + + let homepageTextbox = content.document.getElementById("homePageUrl"); + // Unfortunately this test does not work because the new UI does not fill + // default values into the URL box at the moment. + // is(homepageTextbox.value, expectedURL, + // "Homepage URL should match expected"); + + // Wait for rendering to be finished + await ContentTaskUtils.waitForCondition( + () => + content.document.getElementById("useCurrentBtn").disabled === locked + ); + + is( + homepageTextbox.disabled, + locked, + "Homepage URL text box disabled status should match expected" + ); + is( + content.document.getElementById("homeMode").disabled, + locked, + "Home mode drop down disabled status should match expected" + ); + is( + content.document.getElementById("useCurrentBtn").disabled, + locked, + '"Use current page" button disabled status should match expected' + ); + is( + content.document.getElementById("useBookmarkBtn").disabled, + locked, + '"Use bookmark" button disabled status should match expected' + ); + is( + content.document.getElementById("restoreDefaultHomePageBtn").disabled, + locked, + '"Restore defaults" button disabled status should match expected' + ); + } + ); + await BrowserTestUtils.removeTab(tab); +} + +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(addonId) { + return new Promise(resolve => { + let listener = { + onInstallEnded(install, addon) { + if (addon.id == addonId) { + AddonManager.removeInstallListener(listener); + resolve(); + } + }, + onDownloadFailed() { + AddonManager.removeInstallListener(listener); + resolve(); + }, + onInstallFailed() { + AddonManager.removeInstallListener(listener); + resolve(); + }, + }; + AddonManager.addInstallListener(listener); + }); +} + +function waitForAddonUninstall(addonId) { + return new Promise(resolve => { + let listener = {}; + listener.onUninstalled = addon => { + if (addon.id == addonId) { + AddonManager.removeAddonListener(listener); + resolve(); + } + }; + AddonManager.addAddonListener(listener); + }); +} + +async function testPageBlockedByPolicy(page, policyJSON) { + if (policyJSON) { + await EnterprisePolicyTesting.setupPolicyEngineWithJson(policyJSON); + } + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async browser => { + BrowserTestUtils.loadURI(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/browser/components/enterprisepolicies/tests/browser/homepage_button/browser.ini b/browser/components/enterprisepolicies/tests/browser/homepage_button/browser.ini new file mode 100644 index 0000000000..d2b15850a3 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/homepage_button/browser.ini @@ -0,0 +1,7 @@ +[DEFAULT] +prefs = + browser.policies.alternatePath='<test-root>/browser/components/enterprisepolicies/tests/browser/homepage_button/homepage_policies.json' +support-files = + homepage_policies.json + +[browser_show_home_button_with_homepage_policy.js] diff --git a/browser/components/enterprisepolicies/tests/browser/homepage_button/browser_show_home_button_with_homepage_policy.js b/browser/components/enterprisepolicies/tests/browser/homepage_button/browser_show_home_button_with_homepage_policy.js new file mode 100644 index 0000000000..c1eafb0a52 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/homepage_button/browser_show_home_button_with_homepage_policy.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_homepage_button_with_homepage() { + let homeButton = window.document.getElementById("home-button"); + isnot(homeButton, null, "The home button should be visible"); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/homepage_button/homepage_policies.json b/browser/components/enterprisepolicies/tests/browser/homepage_button/homepage_policies.json new file mode 100644 index 0000000000..960e68348e --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/homepage_button/homepage_policies.json @@ -0,0 +1,7 @@ +{ + "policies": { + "Homepage": { + "URL": "http://example1.com/" + } + } +} diff --git a/browser/components/enterprisepolicies/tests/browser/managedbookmarks/browser.ini b/browser/components/enterprisepolicies/tests/browser/managedbookmarks/browser.ini new file mode 100644 index 0000000000..7b6dff29ce --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/managedbookmarks/browser.ini @@ -0,0 +1,8 @@ +[DEFAULT] +prefs = + browser.policies.alternatePath='<test-root>/browser/components/enterprisepolicies/tests/browser/managedbookmarks/managedbookmarks.json' +support-files = + managedbookmarks.json + +skip-if = (os == "linux") # Popup timeout issue - see Bug 1742167 +[browser_policy_managedbookmarks.js] diff --git a/browser/components/enterprisepolicies/tests/browser/managedbookmarks/browser_policy_managedbookmarks.js b/browser/components/enterprisepolicies/tests/browser/managedbookmarks/browser_policy_managedbookmarks.js new file mode 100644 index 0000000000..be9f34bf25 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/managedbookmarks/browser_policy_managedbookmarks.js @@ -0,0 +1,213 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function test_policy_managedbookmarks() { + let managedBookmarksMenu = window.document.getElementById( + "managed-bookmarks" + ); + + is( + managedBookmarksMenu.hidden, + false, + "Managed bookmarks button should be visible." + ); + is( + managedBookmarksMenu.label, + "Folder 1", + "Managed bookmarks buttons should have correct label" + ); + + let popupShownPromise = BrowserTestUtils.waitForEvent( + managedBookmarksMenu.menupopup, + "popupshown", + false + ); + let popupHiddenPromise = BrowserTestUtils.waitForEvent( + managedBookmarksMenu.menupopup, + "popuphidden", + false + ); + managedBookmarksMenu.open = true; + await popupShownPromise; + + is( + managedBookmarksMenu.menupopup.children[0].label, + "Bookmark 1", + "Bookmark should have correct label" + ); + is( + managedBookmarksMenu.menupopup.children[0].link, + "https://example.com/", + "Bookmark should have correct link" + ); + is( + managedBookmarksMenu.menupopup.children[1].label, + "Bookmark 2", + "Bookmark should have correct label" + ); + is( + managedBookmarksMenu.menupopup.children[1].link, + "https://bookmark2.example.com/", + "Bookmark should have correct link" + ); + let subFolder = managedBookmarksMenu.menupopup.children[2]; + is(subFolder.label, "Folder 2", "Subfolder should have correct label"); + is( + subFolder.menupopup.children[0].label, + "Bookmark 3", + "Bookmark should have correct label" + ); + is( + subFolder.menupopup.children[0].link, + "https://bookmark3.example.com/", + "Bookmark should have correct link" + ); + is( + subFolder.menupopup.children[1].label, + "Bookmark 4", + "Bookmark should have correct link" + ); + is( + subFolder.menupopup.children[1].link, + "https://bookmark4.example.com/", + "Bookmark should have correct label" + ); + subFolder = managedBookmarksMenu.menupopup.children[3]; + await TestUtils.waitForCondition(() => { + // Need to wait for Fluent to translate + return subFolder.label == "Subfolder"; + }, "Subfolder should have correct label"); + is( + subFolder.menupopup.children[0].label, + "Bookmark 5", + "Bookmark should have correct label" + ); + is( + subFolder.menupopup.children[0].link, + "https://bookmark5.example.com/", + "Bookmark should have correct link" + ); + is( + subFolder.menupopup.children[1].label, + "Bookmark 6", + "Bookmark should have correct link" + ); + is( + subFolder.menupopup.children[1].link, + "https://bookmark6.example.com/", + "Bookmark should have correct label" + ); + + managedBookmarksMenu.open = false; + await popupHiddenPromise; +}); + +add_task(async function test_open_managedbookmark() { + let managedBookmarksMenu = window.document.getElementById( + "managed-bookmarks" + ); + + let promise = BrowserTestUtils.waitForEvent( + managedBookmarksMenu.menupopup, + "popupshown", + false + ); + managedBookmarksMenu.open = true; + await promise; + + let context = document.getElementById("placesContext"); + let openContextMenuPromise = BrowserTestUtils.waitForEvent( + context, + "popupshown" + ); + EventUtils.synthesizeMouseAtCenter( + managedBookmarksMenu.menupopup.children[0], + { + button: 2, + type: "contextmenu", + } + ); + await openContextMenuPromise; + info("Opened context menu"); + + ok( + document.getElementById("placesContext_open:newprivatewindow").hidden, + "Private Browsing menu should be hidden" + ); + ok( + document.getElementById("placesContext_openContainer:tabs").hidden, + "Open in Tabs should be hidden" + ); + ok( + document.getElementById("placesContext_delete").hidden, + "Delete should be hidden" + ); + + let tabCreatedPromise = BrowserTestUtils.waitForNewTab(gBrowser, null, true); + + let openInNewTabOption = document.getElementById("placesContext_open:newtab"); + context.activateItem(openInNewTabOption); + info("Click open in new tab"); + + let lastOpenedTab = await tabCreatedPromise; + Assert.equal( + lastOpenedTab.linkedBrowser.currentURI.spec, + "https://example.com/", + "Should have opened the correct URI" + ); + await BrowserTestUtils.removeTab(lastOpenedTab); +}); + +add_task(async function test_copy_managedbookmark() { + let managedBookmarksMenu = window.document.getElementById( + "managed-bookmarks" + ); + + let promise = BrowserTestUtils.waitForEvent( + managedBookmarksMenu.menupopup, + "popupshown", + false + ); + managedBookmarksMenu.open = true; + await promise; + + let context = document.getElementById("placesContext"); + let openContextMenuPromise = BrowserTestUtils.waitForEvent( + context, + "popupshown" + ); + EventUtils.synthesizeMouseAtCenter( + managedBookmarksMenu.menupopup.children[0], + { + button: 2, + type: "contextmenu", + } + ); + await openContextMenuPromise; + info("Opened context menu"); + + let copyOption = document.getElementById("placesContext_copy"); + + await new Promise((resolve, reject) => { + SimpleTest.waitForClipboard( + "https://example.com/", + () => { + context.activateItem(copyOption); + }, + resolve, + () => { + ok(false, "Clipboard copy failed"); + reject(); + } + ); + }); + + let popupHidden = BrowserTestUtils.waitForEvent( + managedBookmarksMenu.menupopup, + "popuphidden" + ); + managedBookmarksMenu.menupopup.hidePopup(); + await popupHidden; +}); diff --git a/browser/components/enterprisepolicies/tests/browser/managedbookmarks/managedbookmarks.json b/browser/components/enterprisepolicies/tests/browser/managedbookmarks/managedbookmarks.json new file mode 100644 index 0000000000..dc857f0556 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/managedbookmarks/managedbookmarks.json @@ -0,0 +1,39 @@ +{ + "policies": { + "DisablePrivateBrowsing": true, + "DisplayBookmarksToolbar": true, + "ManagedBookmarks": [{ + "toplevel_name": "Folder 1" + }, + { + "name": "Bookmark 1", + "url": "https://example.com/" + }, + { + "name": "Bookmark 2", + "url": "https://bookmark2.example.com/" + }, { + "children": [{ + "name": "Bookmark 3", + "url": "https://bookmark3.example.com/" + }, + { + "name": "Bookmark 4", + "url": "https://bookmark4.example.com/" + } + ], + "name": "Folder 2" + }, { + "children": [{ + "name": "Bookmark 5", + "url": "https://bookmark5.example.com/" + }, + { + "name": "Bookmark 6", + "url": "https://bookmark6.example.com/" + } + ] + } + ] + } +} diff --git a/browser/components/enterprisepolicies/tests/browser/opensearch.html b/browser/components/enterprisepolicies/tests/browser/opensearch.html new file mode 100644 index 0000000000..b3f12d6bd4 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/opensearch.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="UTF-8"> +<link rel="search" type="application/opensearchdescription+xml" title="newEngine" href="http://mochi.test:8888/browser/browser/components/enterprisepolicies/tests/browser/opensearchEngine.xml"> +</head> +<body></body> +</html> diff --git a/browser/components/enterprisepolicies/tests/browser/opensearchEngine.xml b/browser/components/enterprisepolicies/tests/browser/opensearchEngine.xml new file mode 100644 index 0000000000..21ddc4b9a9 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/opensearchEngine.xml @@ -0,0 +1,12 @@ +<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" + xmlns:moz="http://www.mozilla.org/2006/browser/search/"> + <ShortName>Foo</ShortName> + <Description>Foo Search</Description> + <InputEncoding>utf-8</InputEncoding> + <Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC</Image> + <Url type="text/html" method="GET" template="http://mochi.test:8888/browser/browser/components/search/test/?search"> + <Param name="test" value="{searchTerms}"/> + </Url> + <moz:SearchForm>http://mochi.test:8888/browser/browser/components/search/test/</moz:SearchForm> + <moz:Alias>fooalias</moz:Alias> +</OpenSearchDescription> diff --git a/browser/components/enterprisepolicies/tests/browser/policy_websitefilter_block.html b/browser/components/enterprisepolicies/tests/browser/policy_websitefilter_block.html new file mode 100644 index 0000000000..dd6596615d --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/policy_websitefilter_block.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="UTF-8"> + <title>This page should be blocked</title> + </head> + <body> + This page should not be seen. + </body> +</html> diff --git a/browser/components/enterprisepolicies/tests/browser/policy_websitefilter_exception.html b/browser/components/enterprisepolicies/tests/browser/policy_websitefilter_exception.html new file mode 100644 index 0000000000..bf389aac31 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/policy_websitefilter_exception.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="UTF-8"> + <title>This page should not be blocked</title> + </head> + <body> + This page should be seen. + </body> +</html> diff --git a/browser/components/enterprisepolicies/tests/browser/policy_websitefilter_savelink.html b/browser/components/enterprisepolicies/tests/browser/policy_websitefilter_savelink.html new file mode 100644 index 0000000000..d2ec93eac5 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/policy_websitefilter_savelink.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="UTF-8"> + <title>Save Link As test</title> + </head> + <body> + <a id="savelink_blocked" href="http://mochi.test:8888/browser/browser/components/enterprisepolicies/tests/browser/policy_websitefilter_block.html">Should not be saveable</a><br/> + <a id="savelink_notblocked" href="http://mochi.test:8888/browser/browser/components/enterprisepolicies/tests/browser/policy_websitefilter_no_block.html">Should be saveable</a> +</body> +</html> diff --git a/browser/components/enterprisepolicies/tests/browser/policytest_v0.1.xpi b/browser/components/enterprisepolicies/tests/browser/policytest_v0.1.xpi Binary files differnew file mode 100644 index 0000000000..ee2a6289ee --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/policytest_v0.1.xpi diff --git a/browser/components/enterprisepolicies/tests/browser/policytest_v0.2.xpi b/browser/components/enterprisepolicies/tests/browser/policytest_v0.2.xpi Binary files differnew file mode 100644 index 0000000000..59d589eba9 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/policytest_v0.2.xpi diff --git a/browser/components/enterprisepolicies/tests/browser/show_home_button/browser.ini b/browser/components/enterprisepolicies/tests/browser/show_home_button/browser.ini new file mode 100644 index 0000000000..3e6114506d --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/show_home_button/browser.ini @@ -0,0 +1,5 @@ +[DEFAULT] +support-files = + ../head.js + +[browser_policy_show_home_button.js] diff --git a/browser/components/enterprisepolicies/tests/browser/show_home_button/browser_policy_show_home_button.js b/browser/components/enterprisepolicies/tests/browser/show_home_button/browser_policy_show_home_button.js new file mode 100644 index 0000000000..c1d7242dc0 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/show_home_button/browser_policy_show_home_button.js @@ -0,0 +1,38 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* import-globals-from ../head.js */ + +"use strict"; + +add_task(async function test_home_button_shown_boolean() { + await setupPolicyEngineWithJson({ + policies: { + ShowHomeButton: true, + }, + }); + + // Since testing will apply the policy after the browser has already started, + // we will need to open a new window to actually see the menu bar + let newWin = await BrowserTestUtils.openNewBrowserWindow(); + let homeButton = newWin.document.getElementById("home-button"); + isnot(homeButton, null, "The home button should be visible"); + + await BrowserTestUtils.closeWindow(newWin); +}); + +add_task(async function test_home_button_hidden_boolean() { + await setupPolicyEngineWithJson({ + policies: { + ShowHomeButton: false, + }, + }); + + // Since testing will apply the policy after the browser has already started, + // we will need to open a new window to actually see the menu bar + let newWin = await BrowserTestUtils.openNewBrowserWindow(); + let homeButton = newWin.document.getElementById("home-button"); + is(homeButton, null, "The home button should be gone"); + + await BrowserTestUtils.closeWindow(newWin); +}); diff --git a/browser/components/enterprisepolicies/tests/browser/show_home_button/show_home_button_policies.json b/browser/components/enterprisepolicies/tests/browser/show_home_button/show_home_button_policies.json new file mode 100644 index 0000000000..960e68348e --- /dev/null +++ b/browser/components/enterprisepolicies/tests/browser/show_home_button/show_home_button_policies.json @@ -0,0 +1,7 @@ +{ + "policies": { + "Homepage": { + "URL": "http://example1.com/" + } + } +} diff --git a/browser/components/enterprisepolicies/tests/moz.build b/browser/components/enterprisepolicies/tests/moz.build new file mode 100644 index 0000000000..0795ab1747 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/moz.build @@ -0,0 +1,20 @@ +# -*- 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_default_bookmarks/browser.ini", + "browser/disable_developer_tools/browser.ini", + "browser/disable_forget_button/browser.ini", + "browser/disable_fxscreenshots/browser.ini", + "browser/hardware_acceleration/browser.ini", + "browser/homepage_button/browser.ini", + "browser/managedbookmarks/browser.ini", + "browser/show_home_button/browser.ini", +] + +XPCSHELL_TESTS_MANIFESTS += ["xpcshell/xpcshell.ini"] diff --git a/browser/components/enterprisepolicies/tests/xpcshell/config_popups_cookies_addons_flash.json b/browser/components/enterprisepolicies/tests/xpcshell/config_popups_cookies_addons_flash.json new file mode 100644 index 0000000000..785c0dcc03 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/xpcshell/config_popups_cookies_addons_flash.json @@ -0,0 +1,41 @@ +{ + "policies": { + "PopupBlocking": { + "Allow": [ + "https://www.allow.com", + "https://www.pre-existing-deny.com" + ] + }, + + "Cookies": { + "Allow": [ + "https://www.allow.com", + "https://www.pre-existing-deny.com" + ], + + "Block": [ + "https://www.deny.com", + "https://www.pre-existing-allow.com" + ] + }, + + "InstallAddonsPermission": { + "Allow": [ + "https://www.allow.com", + "https://www.pre-existing-deny.com" + ] + }, + + "FlashPlugin": { + "Allow": [ + "https://www.allow.com", + "https://www.pre-existing-deny.com" + ], + + "Block": [ + "https://www.deny.com", + "https://www.pre-existing-allow.com" + ] + } + } +} diff --git a/browser/components/enterprisepolicies/tests/xpcshell/head.js b/browser/components/enterprisepolicies/tests/xpcshell/head.js new file mode 100644 index 0000000000..5ca20e6048 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/xpcshell/head.js @@ -0,0 +1,145 @@ +/* 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 { SearchSettings } = ChromeUtils.importESModule( + "resource://gre/modules/SearchSettings.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); + +SearchSettings.SETTINGS_INVALIDATION_DELAY = 100; + +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/browser/components/enterprisepolicies/tests/xpcshell/policytest_v0.1.xpi b/browser/components/enterprisepolicies/tests/xpcshell/policytest_v0.1.xpi Binary files differnew file mode 100644 index 0000000000..ee2a6289ee --- /dev/null +++ b/browser/components/enterprisepolicies/tests/xpcshell/policytest_v0.1.xpi diff --git a/browser/components/enterprisepolicies/tests/xpcshell/test_3rdparty.js b/browser/components/enterprisepolicies/tests/xpcshell/test_3rdparty.js new file mode 100644 index 0000000000..0f53cc80c9 --- /dev/null +++ b/browser/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_task(async function setup() { + 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/browser/components/enterprisepolicies/tests/xpcshell/test_addon_update.js b/browser/components/enterprisepolicies/tests/xpcshell/test_addon_update.js new file mode 100644 index 0000000000..7b794a6e6e --- /dev/null +++ b/browser/components/enterprisepolicies/tests/xpcshell/test_addon_update.js @@ -0,0 +1,156 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { AddonTestUtils } = ChromeUtils.import( + "resource://testing-common/AddonTestUtils.jsm" +); +const { AddonManager } = ChromeUtils.import( + "resource://gre/modules/AddonManager.jsm" +); +const { ExtensionTestUtils } = ChromeUtils.import( + "resource://testing-common/ExtensionXPCShellUtils.jsm" +); + +AddonTestUtils.init(this); +AddonTestUtils.overrideCertDB(); +AddonTestUtils.appInfo = getAppInfo(); +ExtensionTestUtils.init(this); + +const server = AddonTestUtils.createHttpServer({ hosts: ["example.com"] }); +const BASE_URL = `http://example.com/data`; + +let TEST_NAME = "updatable.xpi"; + +/* Test that when a local file addon is updated, + the new version gets installed. */ +add_task(async function test_local_addon_update() { + await AddonTestUtils.promiseStartupManager(); + + let tmpDir = Services.dirsvc.get("TmpD", Ci.nsIFile); + let id = "updatable1@test"; + let xpi1 = AddonTestUtils.createTempWebExtensionFile({ + manifest: { + version: "1.0", + browser_specific_settings: { + gecko: { id }, + }, + }, + }); + xpi1.copyTo(tmpDir, TEST_NAME); + let extension = ExtensionTestUtils.expectExtension(id); + await Promise.all([ + extension.awaitStartup(), + setupPolicyEngineWithJson({ + policies: { + ExtensionSettings: { + "updatable1@test": { + installation_mode: "force_installed", + install_url: Services.io.newFileURI(tmpDir).spec + "/" + TEST_NAME, + }, + }, + }, + }), + ]); + let addon = await AddonManager.getAddonByID(id); + notEqual(addon, null, "Addon should not be null"); + equal(addon.version, "1.0", "Addon 1.0 installed"); + + let xpi2 = AddonTestUtils.createTempWebExtensionFile({ + manifest: { + version: "2.0", + browser_specific_settings: { + gecko: { id }, + }, + }, + }); + // overwrite the test file + xpi2.copyTo(tmpDir, TEST_NAME); + + extension = ExtensionTestUtils.expectExtension(id); + await Promise.all([ + extension.awaitStartup(), + setupPolicyEngineWithJson({ + policies: { + ExtensionSettings: { + "updatable1@test": { + installation_mode: "force_installed", + install_url: Services.io.newFileURI(tmpDir).spec + "/" + TEST_NAME, + }, + }, + }, + }), + ]); + + addon = await AddonManager.getAddonByID(id); + equal(addon.version, "2.0", "Addon 2.0 installed"); + + let xpifile = tmpDir.clone(); + xpifile.append(TEST_NAME); + xpifile.remove(false); +}); + +/* Test that when the url changes, + the new version gets installed. */ +add_task(async function test_newurl_addon_update() { + let id = "updatable2@test"; + + let xpi1 = AddonTestUtils.createTempWebExtensionFile({ + manifest: { + version: "1.0", + browser_specific_settings: { + gecko: { id }, + }, + }, + }); + server.registerFile("/data/policy_test1.xpi", xpi1); + + let xpi2 = AddonTestUtils.createTempWebExtensionFile({ + manifest: { + version: "2.0", + browser_specific_settings: { + gecko: { id }, + }, + }, + }); + server.registerFile("/data/policy_test2.xpi", xpi2); + + let extension = ExtensionTestUtils.expectExtension(id); + await Promise.all([ + extension.awaitStartup(), + setupPolicyEngineWithJson({ + policies: { + ExtensionSettings: { + "updatable2@test": { + installation_mode: "force_installed", + install_url: `${BASE_URL}/policy_test1.xpi`, + }, + }, + }, + }), + ]); + let addon = await AddonManager.getAddonByID(id); + notEqual(addon, null, "Addon should not be null"); + equal(addon.version, "1.0", "Addon 1.0 installed"); + + extension = ExtensionTestUtils.expectExtension(id); + await Promise.all([ + extension.awaitStartup(), + setupPolicyEngineWithJson({ + policies: { + ExtensionSettings: { + "updatable2@test": { + installation_mode: "force_installed", + install_url: `${BASE_URL}/policy_test2.xpi`, + }, + }, + }, + }), + ]); + + addon = await AddonManager.getAddonByID(id); + equal(addon.version, "2.0", "Addon 2.0 installed"); + + await AddonTestUtils.promiseShutdownManager(); +}); diff --git a/browser/components/enterprisepolicies/tests/xpcshell/test_appupdatepin.js b/browser/components/enterprisepolicies/tests/xpcshell/test_appupdatepin.js new file mode 100644 index 0000000000..6be2aa0b50 --- /dev/null +++ b/browser/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 + * Firefox 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/browser/components/enterprisepolicies/tests/xpcshell/test_appupdateurl.js b/browser/components/enterprisepolicies/tests/xpcshell/test_appupdateurl.js new file mode 100644 index 0000000000..48d04e1a8d --- /dev/null +++ b/browser/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/browser/components/enterprisepolicies/tests/xpcshell/test_bug1658259.js b/browser/components/enterprisepolicies/tests/xpcshell/test_bug1658259.js new file mode 100644 index 0000000000..1449e664c2 --- /dev/null +++ b/browser/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/browser/components/enterprisepolicies/tests/xpcshell/test_cleanup.js b/browser/components/enterprisepolicies/tests/xpcshell/test_cleanup.js new file mode 100644 index 0000000000..4171987cbb --- /dev/null +++ b/browser/components/enterprisepolicies/tests/xpcshell/test_cleanup.js @@ -0,0 +1,84 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +async function checkMessages(expectedResult) { + let onBeforeAddons = false; + let onProfileAfterChange = false; + let onBeforeUIStartup = false; + let onAllWindowsRestored = false; + + let errorListener = { + observe(subject) { + let message = subject.wrappedJSObject.arguments[0]; + if (message.includes("_cleanup from onBeforeAddons")) { + onBeforeAddons = true; + } else if (message.includes("_cleanup from onProfileAfterChange")) { + onProfileAfterChange = true; + } else if (message.includes("_cleanup from onBeforeUIStartup")) { + onBeforeUIStartup = true; + } else if (message.includes("_cleanup from onAllWindowsRestored")) { + onAllWindowsRestored = true; + } + }, + }; + + Services.console.registerListener(errorListener); + + const ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"].getService( + Ci.nsIConsoleAPIStorage + ); + ConsoleAPIStorage.addLogEventListener( + errorListener.observe, + Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal) + ); + + await setupPolicyEngineWithJson({ + policies: {}, + }); + + equal( + onBeforeAddons, + expectedResult, + "onBeforeAddons should be " + expectedResult + ); + equal( + onProfileAfterChange, + expectedResult, + "onProfileAfterChange should be" + expectedResult + ); + equal( + onBeforeUIStartup, + expectedResult, + "onBeforeUIStartup should be" + expectedResult + ); + equal( + onAllWindowsRestored, + expectedResult, + "onAllWindowsRestored should be" + expectedResult + ); +} + +/* If there is no existing policy, cleanup should not run. */ +add_task(async function test_cleanup_no_policy() { + await checkMessages(false); +}); + +add_task(async function setup_policy() { + await setupPolicyEngineWithJson({ + policies: { + BlockAboutConfig: true, + }, + }); +}); + +/* Since there was a policy, cleanup should run. */ +add_task(async function test_cleanup_with_policy() { + await checkMessages(true); +}); + +/* Since cleanup was already done, cleanup should not run again. */ +add_task(async function test_cleanup_after_policy() { + await checkMessages(false); +}); diff --git a/browser/components/enterprisepolicies/tests/xpcshell/test_clear_blocked_cookies.js b/browser/components/enterprisepolicies/tests/xpcshell/test_clear_blocked_cookies.js new file mode 100644 index 0000000000..571ae95a1b --- /dev/null +++ b/browser/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_task(async function setup() { + 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/browser/components/enterprisepolicies/tests/xpcshell/test_defaultbrowsercheck.js b/browser/components/enterprisepolicies/tests/xpcshell/test_defaultbrowsercheck.js new file mode 100644 index 0000000000..6daffa061a --- /dev/null +++ b/browser/components/enterprisepolicies/tests/xpcshell/test_defaultbrowsercheck.js @@ -0,0 +1,52 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; +const { ShellService } = ChromeUtils.import( + "resource:///modules/ShellService.jsm" +); + +add_task(async function test_default_browser_check() { + ShellService._checkedThisSession = false; + // On a normal profile, the default is true. However, this gets set to false on the + // testing profile. Let's start with true for a sanity check. + + ShellService.shouldCheckDefaultBrowser = true; + equal(ShellService.shouldCheckDefaultBrowser, true, "Sanity check"); + + await setupPolicyEngineWithJson({ + policies: { + DontCheckDefaultBrowser: true, + }, + }); + + equal( + ShellService.shouldCheckDefaultBrowser, + false, + "Policy changed it to not check" + ); + + // Try to change it to true and check that it doesn't take effect + ShellService.shouldCheckDefaultBrowser = true; + + equal(ShellService.shouldCheckDefaultBrowser, false, "Policy is enforced"); +}); + +add_task(async function test_default_browser_check() { + await setupPolicyEngineWithJson({ + policies: { + DontCheckDefaultBrowser: false, + }, + }); + + equal( + ShellService.shouldCheckDefaultBrowser, + true, + "Policy changed it to check" + ); + + // Try to change it to false and check that it doesn't take effect + ShellService.shouldCheckDefaultBrowser = false; + + equal(ShellService.shouldCheckDefaultBrowser, true, "Policy is enforced"); +}); diff --git a/browser/components/enterprisepolicies/tests/xpcshell/test_empty_policy.js b/browser/components/enterprisepolicies/tests/xpcshell/test_empty_policy.js new file mode 100644 index 0000000000..5047eeb4e0 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/xpcshell/test_empty_policy.js @@ -0,0 +1,32 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function test_empty_policy() { + await setupPolicyEngineWithJson({ + policies: { + Certificates: {}, + }, + }); + + equal( + Services.policies.status, + Ci.nsIEnterprisePolicies.INACTIVE, + "Engine is not active" + ); +}); + +add_task(async function test_empty_array() { + await setupPolicyEngineWithJson({ + policies: { + RequestedLocales: [], + }, + }); + + equal( + Services.policies.status, + Ci.nsIEnterprisePolicies.ACTIVE, + "Engine is active" + ); +}); diff --git a/browser/components/enterprisepolicies/tests/xpcshell/test_exempt_domain_file_type_pairs_from_file_type_download_warnings.js b/browser/components/enterprisepolicies/tests/xpcshell/test_exempt_domain_file_type_pairs_from_file_type_download_warnings.js new file mode 100644 index 0000000000..5d93391ce9 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/xpcshell/test_exempt_domain_file_type_pairs_from_file_type_download_warnings.js @@ -0,0 +1,66 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function test_exempt_xxx() { + await setupPolicyEngineWithJson({ + policies: { + ExemptDomainFileTypePairsFromFileTypeDownloadWarnings: [ + { + file_extension: "jnlp", + domains: ["example.com", "www.example.edu"], + }, + ], + }, + }); + equal( + Services.policies.isExemptExecutableExtension( + "https://www.example.edu", + "jnlp" + ), + true + ); + equal( + Services.policies.isExemptExecutableExtension( + "https://example.edu", + "jnlp" + ), + false + ); + equal( + Services.policies.isExemptExecutableExtension( + "https://example.com", + "jnlp" + ), + true + ); + equal( + Services.policies.isExemptExecutableExtension( + "https://www.example.com", + "jnlp" + ), + true + ); + equal( + Services.policies.isExemptExecutableExtension( + "https://wwwexample.com", + "jnlp" + ), + false + ); + equal( + Services.policies.isExemptExecutableExtension( + "https://www.example.org", + "jnlp" + ), + false + ); + equal( + Services.policies.isExemptExecutableExtension( + "https://www.example.edu", + "exe" + ), + false + ); +}); diff --git a/browser/components/enterprisepolicies/tests/xpcshell/test_extensions.js b/browser/components/enterprisepolicies/tests/xpcshell/test_extensions.js new file mode 100644 index 0000000000..cc59d2fb25 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/xpcshell/test_extensions.js @@ -0,0 +1,83 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +const { AddonTestUtils } = ChromeUtils.import( + "resource://testing-common/AddonTestUtils.jsm" +); +const { AddonManager } = ChromeUtils.import( + "resource://gre/modules/AddonManager.jsm" +); + +AddonTestUtils.init(this); +AddonTestUtils.overrideCertDB(); +AddonTestUtils.appInfo = getAppInfo(); + +const server = AddonTestUtils.createHttpServer({ hosts: ["example.com"] }); +const BASE_URL = `http://example.com/data`; + +let addonID = "policytest2@mozilla.com"; + +add_task(async function setup() { + await AddonTestUtils.promiseStartupManager(); + + let webExtensionFile = AddonTestUtils.createTempWebExtensionFile({ + manifest: { + browser_specific_settings: { + gecko: { + id: addonID, + }, + }, + }, + }); + + server.registerFile("/data/policy_test.xpi", webExtensionFile); +}); + +add_task(async function test_addon_forceinstalled_remote() { + await Promise.all([ + AddonTestUtils.promiseInstallEvent("onInstallEnded"), + setupPolicyEngineWithJson({ + policies: { + Extensions: { + Install: [BASE_URL + "/policy_test.xpi"], + Locked: [addonID], + }, + }, + }), + ]); + let addon = await AddonManager.getAddonByID(addonID); + notEqual(addon, null, "Addon should not be null"); + equal(addon.appDisabled, false, "Addon should not be disabled"); + equal( + addon.permissions & AddonManager.PERM_CAN_UNINSTALL, + 0, + "Addon should not be able to be uninstalled." + ); + equal( + addon.permissions & AddonManager.PERM_CAN_DISABLE, + 0, + "Addon should not be able to be disabled." + ); + await addon.uninstall(); +}); + +add_task(async function test_addon_forceinstalled_local() { + let addonID2 = "policytest@mozilla.com"; + + let file = Services.dirsvc.get("CurWorkD", Ci.nsIFile); + file.append("policytest_v0.1.xpi"); + await Promise.all([ + AddonTestUtils.promiseInstallEvent("onInstallEnded"), + setupPolicyEngineWithJson({ + policies: { + Extensions: { + Install: [file.path], + }, + }, + }), + ]); + let addon = await AddonManager.getAddonByID(addonID2); + notEqual(addon, null, "Addon should not be null"); + await addon.uninstall(); +}); diff --git a/browser/components/enterprisepolicies/tests/xpcshell/test_extensionsettings.js b/browser/components/enterprisepolicies/tests/xpcshell/test_extensionsettings.js new file mode 100644 index 0000000000..79081d48f1 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/xpcshell/test_extensionsettings.js @@ -0,0 +1,291 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +const { AddonTestUtils } = ChromeUtils.import( + "resource://testing-common/AddonTestUtils.jsm" +); +const { AddonManager } = ChromeUtils.import( + "resource://gre/modules/AddonManager.jsm" +); + +AddonTestUtils.init(this); +AddonTestUtils.overrideCertDB(); +AddonTestUtils.appInfo = getAppInfo(); + +const server = AddonTestUtils.createHttpServer({ hosts: ["example.com"] }); +const BASE_URL = `http://example.com/data`; + +let addonID = "policytest2@mozilla.com"; +let themeID = "policytheme@mozilla.com"; + +let fileURL; + +add_task(async function setup() { + await AddonTestUtils.promiseStartupManager(); + + let webExtensionFile = AddonTestUtils.createTempWebExtensionFile({ + manifest: { + browser_specific_settings: { + gecko: { + id: addonID, + }, + }, + }, + }); + + server.registerFile("/data/policy_test.xpi", webExtensionFile); + fileURL = Services.io + .newFileURI(webExtensionFile) + .QueryInterface(Ci.nsIFileURL); +}); + +add_task(async function test_extensionsettings() { + await setupPolicyEngineWithJson({ + policies: { + ExtensionSettings: { + "extension1@mozilla.com": { + blocked_install_message: "Extension1 error message.", + }, + "*": { + blocked_install_message: "Generic error message.", + }, + }, + }, + }); + + let extensionSettings = Services.policies.getExtensionSettings( + "extension1@mozilla.com" + ); + equal( + extensionSettings.blocked_install_message, + "Extension1 error message.", + "Should have extension specific message." + ); + extensionSettings = Services.policies.getExtensionSettings( + "extension2@mozilla.com" + ); + equal( + extensionSettings.blocked_install_message, + "Generic error message.", + "Should have generic message." + ); +}); + +add_task(async function test_addon_blocked() { + await setupPolicyEngineWithJson({ + policies: { + ExtensionSettings: { + "policytest2@mozilla.com": { + installation_mode: "blocked", + }, + }, + }, + }); + + let install = await AddonManager.getInstallForURL( + BASE_URL + "/policy_test.xpi" + ); + await install.install(); + notEqual(install.addon, null, "Addon should not be null"); + equal(install.addon.appDisabled, true, "Addon should be disabled"); + await install.addon.uninstall(); +}); + +add_task(async function test_addon_allowed() { + await setupPolicyEngineWithJson({ + policies: { + ExtensionSettings: { + "policytest2@mozilla.com": { + installation_mode: "allowed", + }, + "*": { + installation_mode: "blocked", + }, + }, + }, + }); + + let install = await AddonManager.getInstallForURL( + BASE_URL + "/policy_test.xpi" + ); + await install.install(); + notEqual(install.addon, null, "Addon should not be null"); + equal(install.addon.appDisabled, false, "Addon should not be disabled"); + await install.addon.uninstall(); +}); + +add_task(async function test_addon_uninstalled() { + let install = await AddonManager.getInstallForURL( + BASE_URL + "/policy_test.xpi" + ); + await install.install(); + notEqual(install.addon, null, "Addon should not be null"); + + await Promise.all([ + AddonTestUtils.promiseAddonEvent("onUninstalled"), + setupPolicyEngineWithJson({ + policies: { + ExtensionSettings: { + "*": { + installation_mode: "blocked", + }, + }, + }, + }), + ]); + let addon = await AddonManager.getAddonByID(addonID); + equal(addon, null, "Addon should be null"); +}); + +add_task(async function test_addon_forceinstalled() { + await Promise.all([ + AddonTestUtils.promiseInstallEvent("onInstallEnded"), + setupPolicyEngineWithJson({ + policies: { + ExtensionSettings: { + "policytest2@mozilla.com": { + installation_mode: "force_installed", + install_url: BASE_URL + "/policy_test.xpi", + }, + }, + }, + }), + ]); + let addon = await AddonManager.getAddonByID(addonID); + notEqual(addon, null, "Addon should not be null"); + equal(addon.appDisabled, false, "Addon should not be disabled"); + equal( + addon.permissions & AddonManager.PERM_CAN_UNINSTALL, + 0, + "Addon should not be able to be uninstalled." + ); + equal( + addon.permissions & AddonManager.PERM_CAN_DISABLE, + 0, + "Addon should not be able to be disabled." + ); + await addon.uninstall(); +}); + +add_task(async function test_addon_normalinstalled() { + await Promise.all([ + AddonTestUtils.promiseInstallEvent("onInstallEnded"), + setupPolicyEngineWithJson({ + policies: { + ExtensionSettings: { + "policytest2@mozilla.com": { + installation_mode: "normal_installed", + install_url: BASE_URL + "/policy_test.xpi", + }, + }, + }, + }), + ]); + let addon = await AddonManager.getAddonByID(addonID); + notEqual(addon, null, "Addon should not be null"); + equal(addon.appDisabled, false, "Addon should not be disabled"); + equal( + addon.permissions & AddonManager.PERM_CAN_UNINSTALL, + 0, + "Addon should not be able to be uninstalled." + ); + notEqual( + addon.permissions & AddonManager.PERM_CAN_DISABLE, + 0, + "Addon should be able to be disabled." + ); + await addon.uninstall(); +}); + +add_task(async function test_extensionsettings_string() { + await setupPolicyEngineWithJson({ + policies: { + ExtensionSettings: '{"*": {"installation_mode": "blocked"}}', + }, + }); + + let extensionSettings = Services.policies.getExtensionSettings("*"); + equal(extensionSettings.installation_mode, "blocked"); +}); + +add_task(async function test_extensionsettings_string() { + let restrictedDomains = Services.prefs.getCharPref( + "extensions.webextensions.restrictedDomains" + ); + await setupPolicyEngineWithJson({ + policies: { + ExtensionSettings: + '{"*": {"restricted_domains": ["example.com","example.org"]}}', + }, + }); + + let newRestrictedDomains = Services.prefs.getCharPref( + "extensions.webextensions.restrictedDomains" + ); + equal(newRestrictedDomains, restrictedDomains + ",example.com,example.org"); +}); + +add_task(async function test_theme() { + let themeFile = AddonTestUtils.createTempWebExtensionFile({ + manifest: { + browser_specific_settings: { + gecko: { + id: themeID, + }, + }, + theme: {}, + }, + }); + + server.registerFile("/data/policy_theme.xpi", themeFile); + + await Promise.all([ + AddonTestUtils.promiseInstallEvent("onInstallEnded"), + setupPolicyEngineWithJson({ + policies: { + ExtensionSettings: { + "policytheme@mozilla.com": { + installation_mode: "normal_installed", + install_url: BASE_URL + "/policy_theme.xpi", + }, + }, + }, + }), + ]); + let currentTheme = Services.prefs.getCharPref("extensions.activeThemeID"); + equal(currentTheme, themeID, "Theme should be active"); + let addon = await AddonManager.getAddonByID(themeID); + await addon.uninstall(); +}); + +add_task(async function test_addon_normalinstalled_file() { + await Promise.all([ + AddonTestUtils.promiseInstallEvent("onInstallEnded"), + setupPolicyEngineWithJson({ + policies: { + ExtensionSettings: { + "policytest2@mozilla.com": { + installation_mode: "normal_installed", + install_url: fileURL.spec, + }, + }, + }, + }), + ]); + let addon = await AddonManager.getAddonByID(addonID); + notEqual(addon, null, "Addon should not be null"); + equal(addon.appDisabled, false, "Addon should not be disabled"); + equal( + addon.permissions & AddonManager.PERM_CAN_UNINSTALL, + 0, + "Addon should not be able to be uninstalled." + ); + notEqual( + addon.permissions & AddonManager.PERM_CAN_DISABLE, + 0, + "Addon should be able to be disabled." + ); + + await addon.uninstall(); +}); diff --git a/browser/components/enterprisepolicies/tests/xpcshell/test_macosparser_unflatten.js b/browser/components/enterprisepolicies/tests/xpcshell/test_macosparser_unflatten.js new file mode 100644 index 0000000000..096852612c --- /dev/null +++ b/browser/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/browser/components/enterprisepolicies/tests/xpcshell/test_permissions.js b/browser/components/enterprisepolicies/tests/xpcshell/test_permissions.js new file mode 100644 index 0000000000..f4440e53f5 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/xpcshell/test_permissions.js @@ -0,0 +1,355 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +function URI(str) { + return Services.io.newURI(str); +} + +add_task(async function test_setup_preexisting_permissions() { + // Pre-existing ALLOW permissions that should be overridden + // with DENY. + + // No ALLOW -> DENY override for popup and install permissions, + // because their policies only supports the Allow parameter. + + PermissionTestUtils.add( + "https://www.pre-existing-allow.com", + "camera", + Ci.nsIPermissionManager.ALLOW_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); + PermissionTestUtils.add( + "https://www.pre-existing-allow.com", + "microphone", + Ci.nsIPermissionManager.ALLOW_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); + PermissionTestUtils.add( + "https://www.pre-existing-allow.com", + "geo", + Ci.nsIPermissionManager.ALLOW_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); + PermissionTestUtils.add( + "https://www.pre-existing-allow.com", + "desktop-notification", + Ci.nsIPermissionManager.ALLOW_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); + PermissionTestUtils.add( + "https://www.pre-existing-allow.com", + "autoplay-media", + Ci.nsIPermissionManager.ALLOW_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); + PermissionTestUtils.add( + "https://www.pre-existing-allow.com", + "xr", + Ci.nsIPermissionManager.ALLOW_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); + + // Pre-existing DENY permissions that should be overridden + // with ALLOW. + + PermissionTestUtils.add( + "https://www.pre-existing-deny.com", + "camera", + Ci.nsIPermissionManager.DENY_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); + PermissionTestUtils.add( + "https://www.pre-existing-deny.com", + "microphone", + Ci.nsIPermissionManager.DENY_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); + PermissionTestUtils.add( + "https://www.pre-existing-deny.com", + "geo", + Ci.nsIPermissionManager.DENY_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); + PermissionTestUtils.add( + "https://www.pre-existing-deny.com", + "desktop-notification", + Ci.nsIPermissionManager.DENY_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); + PermissionTestUtils.add( + "https://www.pre-existing-deny.com", + "autoplay-media", + Ci.nsIPermissionManager.DENY_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); + PermissionTestUtils.add( + "https://www.pre-existing-deny.com", + "xr", + Ci.nsIPermissionManager.DENY_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); +}); + +add_task(async function test_setup_activate_policies() { + await setupPolicyEngineWithJson({ + policies: { + Permissions: { + Camera: { + Allow: ["https://www.allow.com", "https://www.pre-existing-deny.com"], + Block: ["https://www.deny.com", "https://www.pre-existing-allow.com"], + }, + Microphone: { + Allow: ["https://www.allow.com", "https://www.pre-existing-deny.com"], + Block: ["https://www.deny.com", "https://www.pre-existing-allow.com"], + }, + Location: { + Allow: ["https://www.allow.com", "https://www.pre-existing-deny.com"], + Block: ["https://www.deny.com", "https://www.pre-existing-allow.com"], + }, + Notifications: { + Allow: ["https://www.allow.com", "https://www.pre-existing-deny.com"], + Block: ["https://www.deny.com", "https://www.pre-existing-allow.com"], + }, + Autoplay: { + Allow: ["https://www.allow.com", "https://www.pre-existing-deny.com"], + Block: ["https://www.deny.com", "https://www.pre-existing-allow.com"], + }, + VirtualReality: { + Allow: ["https://www.allow.com", "https://www.pre-existing-deny.com"], + Block: ["https://www.deny.com", "https://www.pre-existing-allow.com"], + }, + }, + }, + }); + equal( + Services.policies.status, + Ci.nsIEnterprisePolicies.ACTIVE, + "Engine is active" + ); +}); + +function checkPermission(url, expected, permissionName) { + let expectedValue = Ci.nsIPermissionManager[`${expected}_ACTION`]; + let uri = Services.io.newURI(`https://www.${url}`); + + equal( + PermissionTestUtils.testPermission(uri, permissionName), + expectedValue, + `Correct (${permissionName}=${expected}) for URL ${url}` + ); + + if (expected != "UNKNOWN") { + let permission = PermissionTestUtils.getPermissionObject( + uri, + permissionName, + true + ); + ok(permission, "Permission object exists"); + equal( + permission.expireType, + Ci.nsIPermissionManager.EXPIRE_POLICY, + "Permission expireType is correct" + ); + } +} + +function checkAllPermissionsForType(type, typeSupportsDeny = true) { + checkPermission("allow.com", "ALLOW", type); + checkPermission("unknown.com", "UNKNOWN", type); + checkPermission("pre-existing-deny.com", "ALLOW", type); + + if (typeSupportsDeny) { + checkPermission("deny.com", "DENY", type); + checkPermission("pre-existing-allow.com", "DENY", type); + } +} + +add_task(async function test_camera_policy() { + checkAllPermissionsForType("camera"); +}); + +add_task(async function test_microphone_policy() { + checkAllPermissionsForType("microphone"); +}); + +add_task(async function test_location_policy() { + checkAllPermissionsForType("geo"); +}); + +add_task(async function test_notifications_policy() { + checkAllPermissionsForType("desktop-notification"); +}); + +add_task(async function test_autoplay_policy() { + checkAllPermissionsForType("autoplay-media"); +}); + +add_task(async function test_xr_policy() { + checkAllPermissionsForType("xr"); +}); + +add_task(async function test_change_permission() { + // Checks that changing a permission will still retain the + // value set through the engine. + PermissionTestUtils.add( + "https://www.allow.com", + "camera", + Ci.nsIPermissionManager.DENY_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); + PermissionTestUtils.add( + "https://www.allow.com", + "microphone", + Ci.nsIPermissionManager.DENY_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); + PermissionTestUtils.add( + "https://www.allow.com", + "geo", + Ci.nsIPermissionManager.DENY_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); + PermissionTestUtils.add( + "https://www.allow.com", + "desktop-notification", + Ci.nsIPermissionManager.DENY_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); + PermissionTestUtils.add( + "https://www.allow.com", + "autoplay-media", + Ci.nsIPermissionManager.DENY_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); + PermissionTestUtils.add( + "https://www.allow.com", + "xr", + Ci.nsIPermissionManager.DENY_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); + + checkPermission("allow.com", "ALLOW", "camera"); + checkPermission("allow.com", "ALLOW", "microphone"); + checkPermission("allow.com", "ALLOW", "geo"); + checkPermission("allow.com", "ALLOW", "desktop-notification"); + checkPermission("allow.com", "ALLOW", "autoplay-media"); + checkPermission("allow.com", "ALLOW", "xr"); + + // Also change one un-managed permission to make sure it doesn't + // cause any problems to the policy engine or the permission manager. + PermissionTestUtils.add( + "https://www.unmanaged.com", + "camera", + Ci.nsIPermissionManager.DENY_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); + PermissionTestUtils.add( + "https://www.unmanaged.com", + "microphone", + Ci.nsIPermissionManager.DENY_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); + PermissionTestUtils.add( + "https://www.unmanaged.com", + "geo", + Ci.nsIPermissionManager.DENY_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); + PermissionTestUtils.add( + "https://www.unmanaged.com", + "desktop-notification", + Ci.nsIPermissionManager.DENY_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); + PermissionTestUtils.add( + "https://www.unmanaged.com", + "autoplay-media", + Ci.nsIPermissionManager.DENY_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); + PermissionTestUtils.add( + "https://www.unmanaged.com", + "xr", + Ci.nsIPermissionManager.DENY_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); +}); + +add_task(async function test_setup_trackingprotection() { + await setupPolicyEngineWithJson({ + policies: { + EnableTrackingProtection: { + Exceptions: ["https://www.allow.com"], + }, + }, + }); + equal( + Services.policies.status, + Ci.nsIEnterprisePolicies.ACTIVE, + "Engine is active" + ); +}); + +add_task(async function test_trackingprotection() { + checkPermission("allow.com", "ALLOW", "trackingprotection"); +}); + +// This seems a little out of place, but it's really a cookie +// permission, not cookies per say. +add_task(async function test_cookie_allow_session() { + await setupPolicyEngineWithJson({ + policies: { + Cookies: { + AllowSession: ["https://allowsession.example.com"], + }, + }, + }); + equal( + PermissionTestUtils.testPermission( + URI("https://allowsession.example.com"), + "cookie" + ), + Ci.nsICookiePermission.ACCESS_SESSION + ); +}); + +// This again seems out of places, but AutoLaunchProtocolsFromOrigins +// is all permissions. +add_task(async function test_autolaunchprotocolsfromorigins() { + await setupPolicyEngineWithJson({ + policies: { + AutoLaunchProtocolsFromOrigins: [ + { + allowed_origins: ["https://allowsession.example.com"], + protocol: "test-protocol", + }, + ], + }, + }); + equal( + PermissionTestUtils.testPermission( + URI("https://allowsession.example.com"), + "open-protocol-handler^test-protocol" + ), + Ci.nsIPermissionManager.ALLOW_ACTION + ); +}); + +// This again seems out of places, but PasswordManagerExceptions +// is all permissions. +add_task(async function test_passwordmanagerexceptions() { + await setupPolicyEngineWithJson({ + policies: { + PasswordManagerExceptions: ["https://pwexception.example.com"], + }, + }); + equal( + PermissionTestUtils.testPermission( + URI("https://pwexception.example.com"), + "login-saving" + ), + Ci.nsIPermissionManager.DENY_ACTION + ); +}); diff --git a/browser/components/enterprisepolicies/tests/xpcshell/test_policy_search_engine.js b/browser/components/enterprisepolicies/tests/xpcshell/test_policy_search_engine.js new file mode 100644 index 0000000000..2d2d267cae --- /dev/null +++ b/browser/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.import( + "resource://testing-common/AddonTestUtils.jsm" +); + +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/browser/components/enterprisepolicies/tests/xpcshell/test_popups_cookies_addons_flash.js b/browser/components/enterprisepolicies/tests/xpcshell/test_popups_cookies_addons_flash.js new file mode 100644 index 0000000000..04fd97344f --- /dev/null +++ b/browser/components/enterprisepolicies/tests/xpcshell/test_popups_cookies_addons_flash.js @@ -0,0 +1,139 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function test_setup_preexisting_permissions() { + // Pre-existing ALLOW permissions that should be overriden + // with DENY. + + // No ALLOW -> DENY override for popup and install permissions, + // because their policies only supports the Allow parameter. + + PermissionTestUtils.add( + "https://www.pre-existing-allow.com", + "cookie", + Ci.nsIPermissionManager.ALLOW_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); + + PermissionTestUtils.add( + "https://www.pre-existing-allow.com", + "plugin:flash", + Ci.nsIPermissionManager.ALLOW_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); + + // Pre-existing DENY permissions that should be overriden + // with ALLOW. + PermissionTestUtils.add( + "https://www.pre-existing-deny.com", + "popup", + Ci.nsIPermissionManager.DENY_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); + + PermissionTestUtils.add( + "https://www.pre-existing-deny.com", + "install", + Ci.nsIPermissionManager.DENY_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); + + PermissionTestUtils.add( + "https://www.pre-existing-deny.com", + "cookie", + Ci.nsIPermissionManager.DENY_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); + + PermissionTestUtils.add( + "https://www.pre-existing-deny.com", + "plugin:flash", + Ci.nsIPermissionManager.DENY_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); +}); + +add_task(async function test_setup_activate_policies() { + await setupPolicyEngineWithJson("config_popups_cookies_addons_flash.json"); + equal( + Services.policies.status, + Ci.nsIEnterprisePolicies.ACTIVE, + "Engine is active" + ); +}); + +function checkPermission(url, expected, permissionName) { + let expectedValue = Ci.nsIPermissionManager[`${expected}_ACTION`]; + let uri = Services.io.newURI(`https://www.${url}`); + + equal( + PermissionTestUtils.testPermission(uri, permissionName), + expectedValue, + `Correct (${permissionName}=${expected}) for URL ${url}` + ); + + if (expected != "UNKNOWN") { + let permission = PermissionTestUtils.getPermissionObject( + uri, + permissionName, + true + ); + ok(permission, "Permission object exists"); + equal( + permission.expireType, + Ci.nsIPermissionManager.EXPIRE_POLICY, + "Permission expireType is correct" + ); + } +} + +function checkAllPermissionsForType(type, typeSupportsDeny = true) { + checkPermission("allow.com", "ALLOW", type); + checkPermission("unknown.com", "UNKNOWN", type); + checkPermission("pre-existing-deny.com", "ALLOW", type); + + if (typeSupportsDeny) { + checkPermission("deny.com", "DENY", type); + checkPermission("pre-existing-allow.com", "DENY", type); + } +} + +add_task(async function test_popups_policy() { + checkAllPermissionsForType("popup", false); +}); + +add_task(async function test_webextensions_policy() { + checkAllPermissionsForType("install", false); +}); + +add_task(async function test_cookies_policy() { + checkAllPermissionsForType("cookie"); +}); + +add_task(async function test_flash_policy() { + checkAllPermissionsForType("plugin:flash"); +}); + +add_task(async function test_change_permission() { + // Checks that changing a permission will still retain the + // value set through the engine. + PermissionTestUtils.add( + "https://www.allow.com", + "cookie", + Ci.nsIPermissionManager.DENY_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); + + checkPermission("allow.com", "ALLOW", "cookie"); + + // Also change one un-managed permission to make sure it doesn't + // cause any problems to the policy engine or the permission manager. + PermissionTestUtils.add( + "https://www.unmanaged.com", + "cookie", + Ci.nsIPermissionManager.DENY_ACTION, + Ci.nsIPermissionManager.EXPIRE_SESSION + ); +}); diff --git a/browser/components/enterprisepolicies/tests/xpcshell/test_preferences.js b/browser/components/enterprisepolicies/tests/xpcshell/test_preferences.js new file mode 100644 index 0000000000..22b9164623 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/xpcshell/test_preferences.js @@ -0,0 +1,245 @@ +/* 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", + }, + }, + }, + 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, + }, + 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_JSON_preferences() { + await setupPolicyEngineWithJson({ + policies: { + Preferences: + '{"browser.policies.test.default.boolean.json": {"Value": true,"Status": "default"}}', + }, + }); + + checkDefaultPref("browser.policies.test.default.boolean.json", true); +}); + +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/browser/components/enterprisepolicies/tests/xpcshell/test_proxy.js b/browser/components/enterprisepolicies/tests/xpcshell/test_proxy.js new file mode 100644 index 0000000000..ef5ad1e178 --- /dev/null +++ b/browser/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/browser/components/enterprisepolicies/tests/xpcshell/test_requestedlocales.js b/browser/components/enterprisepolicies/tests/xpcshell/test_requestedlocales.js new file mode 100644 index 0000000000..7531d67656 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/xpcshell/test_requestedlocales.js @@ -0,0 +1,119 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +var { setTimeout } = ChromeUtils.importESModule( + "resource://gre/modules/Timer.sys.mjs" +); + +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); + }); +} + +function promiseLocaleNotChanged(requestedLocale) { + return new Promise(resolve => { + let localeObserver = { + observe(aSubject, aTopic, aData) { + switch (aTopic) { + case REQ_LOC_CHANGE_EVENT: + ok(false, "Locale should not change."); + Services.obs.removeObserver(localeObserver, REQ_LOC_CHANGE_EVENT); + resolve(); + } + }, + }; + Services.obs.addObserver(localeObserver, REQ_LOC_CHANGE_EVENT); + /* eslint-disable mozilla/no-arbitrary-setTimeout */ + setTimeout(function() { + Services.obs.removeObserver(localeObserver, REQ_LOC_CHANGE_EVENT); + resolve(); + }, 100); + }); +} + +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; +}); + +add_task(async function test_system_locale_string() { + let originalLocales = Services.locale.requestedLocales; + + let localePromise = promiseLocaleChanged("und"); + Services.locale.requestedLocales = ["und"]; + await localePromise; + + let systemLocale = Cc["@mozilla.org/intl/ospreferences;1"].getService( + Ci.mozIOSPreferences + ).systemLocale; + localePromise = promiseLocaleChanged(systemLocale); + + await setupPolicyEngineWithJson({ + policies: { + RequestedLocales: "", + }, + }); + await localePromise; + Services.locale.requestedLocales = originalLocales; +}); + +add_task(async function test_user_requested_locale_change() { + let originalLocales = Services.locale.requestedLocales; + let localePromise = promiseLocaleChanged("fr"); + await setupPolicyEngineWithJson({ + policies: { + RequestedLocales: "fr", + }, + }); + await localePromise; + + // Simulate user change of locale + localePromise = promiseLocaleChanged("de"); + Services.locale.requestedLocales = ["de"]; + await localePromise; + + localePromise = promiseLocaleNotChanged("fr"); + await setupPolicyEngineWithJson({ + policies: { + RequestedLocales: "fr", + }, + }); + await localePromise; + + Services.locale.requestedLocales = originalLocales; +}); diff --git a/browser/components/enterprisepolicies/tests/xpcshell/test_runOnce_helper.js b/browser/components/enterprisepolicies/tests/xpcshell/test_runOnce_helper.js new file mode 100644 index 0000000000..c8e73b3422 --- /dev/null +++ b/browser/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/browser/components/enterprisepolicies/tests/xpcshell/test_simple_pref_policies.js b/browser/components/enterprisepolicies/tests/xpcshell/test_simple_pref_policies.js new file mode 100644 index 0000000000..5e46c8f100 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/xpcshell/test_simple_pref_policies.js @@ -0,0 +1,990 @@ +/* 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: DisableFormHistory + { + policies: { DisableFormHistory: true }, + lockedPrefs: { "browser.formfill.enable": false }, + }, + + // POLICY: EnableTrackingProtection + { + policies: { + EnableTrackingProtection: { + Value: true, + }, + }, + unlockedPrefs: { + "privacy.trackingprotection.enabled": true, + "privacy.trackingprotection.pbmode.enabled": true, + }, + }, + { + policies: { + EnableTrackingProtection: { + Value: false, + Locked: true, + }, + }, + lockedPrefs: { + "privacy.trackingprotection.enabled": false, + "privacy.trackingprotection.pbmode.enabled": false, + }, + }, + + { + policies: { + EnableTrackingProtection: { + Cryptomining: true, + Fingerprinting: true, + Locked: true, + }, + }, + lockedPrefs: { + "privacy.trackingprotection.cryptomining.enabled": true, + "privacy.trackingprotection.fingerprinting.enabled": true, + }, + }, + + // POLICY: GoToIntranetSiteForSingleWordEntryInAddressBar + { + policies: { + GoToIntranetSiteForSingleWordEntryInAddressBar: true, + }, + lockedPrefs: { + "browser.fixup.dns_first_for_single_words": true, + }, + }, + + // POLICY: OverrideFirstRunPage + { + policies: { OverrideFirstRunPage: "https://www.example.com/" }, + lockedPrefs: { "startup.homepage_welcome_url": "https://www.example.com/" }, + }, + + // 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, + "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons": false, + "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features": false, + }, + }, + + // POLICY: SanitizeOnShutdown + { + policies: { + SanitizeOnShutdown: true, + }, + lockedPrefs: { + "privacy.sanitize.sanitizeOnShutdown": true, + "privacy.clearOnShutdown.cache": true, + "privacy.clearOnShutdown.cookies": true, + "privacy.clearOnShutdown.downloads": true, + "privacy.clearOnShutdown.formdata": true, + "privacy.clearOnShutdown.history": true, + "privacy.clearOnShutdown.sessions": true, + "privacy.clearOnShutdown.siteSettings": true, + "privacy.clearOnShutdown.offlineApps": true, + }, + }, + + { + policies: { + SanitizeOnShutdown: false, + }, + lockedPrefs: { + "privacy.sanitize.sanitizeOnShutdown": false, + "privacy.clearOnShutdown.cache": false, + "privacy.clearOnShutdown.cookies": false, + "privacy.clearOnShutdown.downloads": false, + "privacy.clearOnShutdown.formdata": false, + "privacy.clearOnShutdown.history": false, + "privacy.clearOnShutdown.sessions": false, + "privacy.clearOnShutdown.siteSettings": false, + "privacy.clearOnShutdown.offlineApps": false, + }, + }, + + { + policies: { + SanitizeOnShutdown: { + Cache: true, + }, + }, + lockedPrefs: { + "privacy.sanitize.sanitizeOnShutdown": true, + "privacy.clearOnShutdown.cache": true, + "privacy.clearOnShutdown.cookies": false, + "privacy.clearOnShutdown.downloads": false, + "privacy.clearOnShutdown.formdata": false, + "privacy.clearOnShutdown.history": false, + "privacy.clearOnShutdown.sessions": false, + }, + }, + + { + policies: { + SanitizeOnShutdown: { + Cookies: true, + }, + }, + lockedPrefs: { + "privacy.sanitize.sanitizeOnShutdown": true, + "privacy.clearOnShutdown.cache": false, + "privacy.clearOnShutdown.cookies": true, + "privacy.clearOnShutdown.downloads": false, + "privacy.clearOnShutdown.formdata": false, + "privacy.clearOnShutdown.history": false, + "privacy.clearOnShutdown.sessions": false, + }, + }, + + { + policies: { + SanitizeOnShutdown: { + Downloads: true, + }, + }, + lockedPrefs: { + "privacy.sanitize.sanitizeOnShutdown": true, + "privacy.clearOnShutdown.cache": false, + "privacy.clearOnShutdown.cookies": false, + "privacy.clearOnShutdown.downloads": true, + "privacy.clearOnShutdown.formdata": false, + "privacy.clearOnShutdown.history": false, + "privacy.clearOnShutdown.sessions": false, + }, + }, + + { + policies: { + SanitizeOnShutdown: { + FormData: true, + }, + }, + lockedPrefs: { + "privacy.sanitize.sanitizeOnShutdown": true, + "privacy.clearOnShutdown.cache": false, + "privacy.clearOnShutdown.cookies": false, + "privacy.clearOnShutdown.downloads": false, + "privacy.clearOnShutdown.formdata": true, + "privacy.clearOnShutdown.history": false, + "privacy.clearOnShutdown.sessions": false, + }, + }, + + { + policies: { + SanitizeOnShutdown: { + History: true, + }, + }, + lockedPrefs: { + "privacy.sanitize.sanitizeOnShutdown": true, + "privacy.clearOnShutdown.cache": false, + "privacy.clearOnShutdown.cookies": false, + "privacy.clearOnShutdown.downloads": false, + "privacy.clearOnShutdown.formdata": false, + "privacy.clearOnShutdown.history": true, + "privacy.clearOnShutdown.sessions": false, + }, + }, + + { + policies: { + SanitizeOnShutdown: { + Sessions: true, + }, + }, + lockedPrefs: { + "privacy.sanitize.sanitizeOnShutdown": true, + "privacy.clearOnShutdown.cache": false, + "privacy.clearOnShutdown.cookies": false, + "privacy.clearOnShutdown.downloads": false, + "privacy.clearOnShutdown.formdata": false, + "privacy.clearOnShutdown.history": false, + "privacy.clearOnShutdown.sessions": true, + }, + }, + + { + policies: { + SanitizeOnShutdown: { + SiteSettings: true, + }, + }, + lockedPrefs: { + "privacy.sanitize.sanitizeOnShutdown": true, + "privacy.clearOnShutdown.cache": false, + "privacy.clearOnShutdown.cookies": false, + "privacy.clearOnShutdown.downloads": false, + "privacy.clearOnShutdown.formdata": false, + "privacy.clearOnShutdown.history": false, + "privacy.clearOnShutdown.sessions": false, + "privacy.clearOnShutdown.siteSettings": true, + }, + }, + + { + policies: { + SanitizeOnShutdown: { + OfflineApps: true, + }, + }, + lockedPrefs: { + "privacy.sanitize.sanitizeOnShutdown": true, + "privacy.clearOnShutdown.cache": false, + "privacy.clearOnShutdown.cookies": false, + "privacy.clearOnShutdown.downloads": false, + "privacy.clearOnShutdown.formdata": false, + "privacy.clearOnShutdown.history": false, + "privacy.clearOnShutdown.sessions": false, + "privacy.clearOnShutdown.offlineApps": true, + }, + }, + + // POLICY: SanitizeOnShutdown using Locked + { + policies: { + SanitizeOnShutdown: { + Cache: true, + Locked: true, + }, + }, + lockedPrefs: { + "privacy.sanitize.sanitizeOnShutdown": true, + "privacy.clearOnShutdown.cache": true, + }, + unlockedPrefs: { + "privacy.clearOnShutdown.cookies": false, + "privacy.clearOnShutdown.downloads": false, + "privacy.clearOnShutdown.formdata": false, + "privacy.clearOnShutdown.history": false, + "privacy.clearOnShutdown.sessions": false, + }, + }, + + { + policies: { + SanitizeOnShutdown: { + Cache: true, + Cookies: false, + Locked: true, + }, + }, + lockedPrefs: { + "privacy.sanitize.sanitizeOnShutdown": true, + "privacy.clearOnShutdown.cache": true, + "privacy.clearOnShutdown.cookies": false, + }, + unlockedPrefs: { + "privacy.clearOnShutdown.downloads": false, + "privacy.clearOnShutdown.formdata": false, + "privacy.clearOnShutdown.history": false, + "privacy.clearOnShutdown.sessions": false, + }, + }, + + { + policies: { + SanitizeOnShutdown: { + Cache: true, + Locked: false, + }, + }, + unlockedPrefs: { + "privacy.sanitize.sanitizeOnShutdown": true, + "privacy.clearOnShutdown.cache": true, + "privacy.clearOnShutdown.cookies": false, + "privacy.clearOnShutdown.downloads": false, + "privacy.clearOnShutdown.formdata": false, + "privacy.clearOnShutdown.history": false, + "privacy.clearOnShutdown.sessions": false, + }, + }, + + // POLICY: DNSOverHTTPS Locked + { + policies: { + DNSOverHTTPS: { + Enabled: true, + ProviderURL: "https://example.com/provider", + ExcludedDomains: ["example.com", "example.org"], + Locked: true, + }, + }, + lockedPrefs: { + "network.trr.mode": 2, + "network.trr.uri": "https://example.com/provider", + "network.trr.excluded-domains": "example.com,example.org", + }, + }, + + // POLICY: DNSOverHTTPS Unlocked + { + policies: { + DNSOverHTTPS: { + Enabled: false, + ProviderURL: "https://example.com/provider", + ExcludedDomains: ["example.com", "example.org"], + }, + }, + unlockedPrefs: { + "network.trr.mode": 5, + "network.trr.uri": "https://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: DisableShield + { + policies: { + DisableFirefoxStudies: true, + }, + lockedPrefs: { + "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons": false, + "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features": false, + }, + }, + + // POLICY: NewTabPage + { + policies: { + NewTabPage: false, + }, + lockedPrefs: { + "browser.newtabpage.enabled": false, + }, + }, + + // POLICY: SearchSuggestEnabled + { + policies: { + SearchSuggestEnabled: false, + }, + lockedPrefs: { + "browser.urlbar.suggest.searches": false, + "browser.search.suggest.enabled": false, + }, + }, + + // POLICY: FirefoxHome + { + policies: { + FirefoxHome: { + Pocket: false, + Snippets: false, + Locked: true, + }, + }, + lockedPrefs: { + "browser.newtabpage.activity-stream.feeds.snippets": false, + "browser.newtabpage.activity-stream.feeds.system.topstories": false, + }, + }, + + // POLICY: OfferToSaveLoginsDefault + { + policies: { + OfferToSaveLoginsDefault: false, + }, + unlockedPrefs: { + "signon.rememberSignons": false, + }, + }, + + // POLICY: UserMessaging + { + policies: { + UserMessaging: { + WhatsNew: false, + SkipOnboarding: true, + Locked: true, + }, + }, + lockedPrefs: { + "browser.messaging-system.whatsNewPanel.enabled": false, + "browser.aboutwelcome.enabled": false, + }, + }, + + // POLICY: UserMessaging->SkipOnboarding false (bug 1697566) + { + policies: { + UserMessaging: { + SkipOnboarding: false, + }, + }, + unlockedPrefs: { + "browser.aboutwelcome.enabled": true, + }, + }, + + { + policies: { + UserMessaging: { + ExtensionRecommendations: false, + }, + }, + unlockedPrefs: { + "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons": false, + }, + }, + + { + policies: { + UserMessaging: { + FeatureRecommendations: false, + }, + }, + unlockedPrefs: { + "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features": false, + }, + }, + + // POLICY: Permissions->Autoplay + { + policies: { + Permissions: { + Autoplay: { + Default: "allow-audio-video", + Locked: true, + }, + }, + }, + lockedPrefs: { + "media.autoplay.default": 0, + }, + }, + + { + policies: { + Permissions: { + Autoplay: { + Default: "block-audio", + }, + }, + }, + unlockedPrefs: { + "media.autoplay.default": 1, + }, + }, + + { + policies: { + Permissions: { + Autoplay: { + Default: "block-audio-video", + }, + }, + }, + unlockedPrefs: { + "media.autoplay.default": 5, + }, + }, + + // POLICY: LegacySameSiteCookieBehaviorEnabled + + { + policies: { + LegacySameSiteCookieBehaviorEnabled: true, + }, + unlockedPrefs: { + "network.cookie.sameSite.laxByDefault": false, + }, + }, + + // POLICY: LegacySameSiteCookieBehaviorEnabledForDomainList + + { + policies: { + LegacySameSiteCookieBehaviorEnabledForDomainList: [ + "example.com", + "example.org", + ], + }, + unlockedPrefs: { + "network.cookie.sameSite.laxByDefault.disabledHosts": + "example.com,example.org", + }, + }, + + // POLICY: EncryptedMediaExtensions + + { + policies: { + EncryptedMediaExtensions: { + Enabled: false, + Locked: true, + }, + }, + lockedPrefs: { + "media.eme.enabled": false, + }, + }, + + // POLICY: PDFjs + + { + policies: { + PDFjs: { + Enabled: false, + }, + }, + lockedPrefs: { + "pdfjs.disabled": true, + }, + }, + + { + policies: { + PDFjs: { + Enabled: true, + EnablePermissions: true, + }, + }, + lockedPrefs: { + "pdfjs.disabled": false, + "pdfjs.enablePermissions": true, + }, + }, + + { + policies: { + PDFjs: { + Enabled: true, + EnablePermissions: false, + }, + }, + lockedPrefs: { + "pdfjs.disabled": false, + "pdfjs.enablePermissions": false, + }, + }, + + // POLICY: PictureInPicture + + { + policies: { + PictureInPicture: { + Enabled: false, + Locked: true, + }, + }, + lockedPrefs: { + "media.videocontrols.picture-in-picture.video-toggle.enabled": false, + }, + }, + + // 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, + }, + }, + + { + policies: { + WindowsSSO: true, + }, + lockedPrefs: { + "network.http.windows-sso.enabled": true, + }, + }, + + { + policies: { + Cookies: { + Behavior: "accept", + BehaviorPrivateBrowsing: "reject-foreign", + Locked: true, + }, + }, + lockedPrefs: { + "network.cookie.cookieBehavior": 0, + "network.cookie.cookieBehavior.pbmode": 1, + }, + }, + + { + policies: { + Cookies: { + Behavior: "reject-foreign", + BehaviorPrivateBrowsing: "reject", + Locked: true, + }, + }, + lockedPrefs: { + "network.cookie.cookieBehavior": 1, + "network.cookie.cookieBehavior.pbmode": 2, + }, + }, + + { + policies: { + Cookies: { + Behavior: "reject", + BehaviorPrivateBrowsing: "limit-foreign", + Locked: true, + }, + }, + lockedPrefs: { + "network.cookie.cookieBehavior": 2, + "network.cookie.cookieBehavior.pbmode": 3, + }, + }, + + { + policies: { + Cookies: { + Behavior: "limit-foreign", + BehaviorPrivateBrowsing: "reject-tracker", + Locked: true, + }, + }, + lockedPrefs: { + "network.cookie.cookieBehavior": 3, + "network.cookie.cookieBehavior.pbmode": 4, + }, + }, + + { + policies: { + Cookies: { + Behavior: "reject-tracker", + BehaviorPrivateBrowsing: "reject-tracker-and-partition-foreign", + Locked: true, + }, + }, + lockedPrefs: { + "network.cookie.cookieBehavior": 4, + "network.cookie.cookieBehavior.pbmode": 5, + }, + }, + { + policies: { + Cookies: { + Behavior: "reject-tracker-and-partition-foreign", + BehaviorPrivateBrowsing: "accept", + Locked: true, + }, + }, + lockedPrefs: { + "network.cookie.cookieBehavior": 5, + "network.cookie.cookieBehavior.pbmode": 0, + }, + }, + + { + policies: { + UseSystemPrintDialog: true, + }, + lockedPrefs: { + "print.prefer_system_dialog": true, + }, + }, +]; + +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/browser/components/enterprisepolicies/tests/xpcshell/test_sorted_alphabetically.js b/browser/components/enterprisepolicies/tests/xpcshell/test_sorted_alphabetically.js new file mode 100644 index 0000000000..b56a69a1b3 --- /dev/null +++ b/browser/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.import( + "resource:///modules/policies/schema.jsm" + ); + 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.import( + "resource:///modules/policies/schema.jsm" + ); + 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/browser/components/enterprisepolicies/tests/xpcshell/xpcshell.ini b/browser/components/enterprisepolicies/tests/xpcshell/xpcshell.ini new file mode 100644 index 0000000000..61a0e86359 --- /dev/null +++ b/browser/components/enterprisepolicies/tests/xpcshell/xpcshell.ini @@ -0,0 +1,31 @@ +[DEFAULT] +skip-if = toolkit == 'android' # bug 1730213 +firefox-appdir = browser +head = head.js +support-files = + policytest_v0.1.xpi + +[test_3rdparty.js] +[test_addon_update.js] +[test_appupdateurl.js] +[test_bug1658259.js] +[test_cleanup.js] +[test_clear_blocked_cookies.js] +[test_defaultbrowsercheck.js] +[test_empty_policy.js] +[test_exempt_domain_file_type_pairs_from_file_type_download_warnings.js] +[test_extensions.js] +[test_extensionsettings.js] +[test_macosparser_unflatten.js] +skip-if = os != 'mac' +[test_permissions.js] +[test_policy_search_engine.js] +[test_popups_cookies_addons_flash.js] +support-files = config_popups_cookies_addons_flash.json +[test_preferences.js] +[test_proxy.js] +[test_requestedlocales.js] +[test_runOnce_helper.js] +[test_simple_pref_policies.js] +[test_sorted_alphabetically.js] +[test_appupdatepin.js] |