Bug 1429177 - Policy: Set network proxy settings. r=mixedpuppy
MozReview-Commit-ID: KPiz6fdwKc0
--- a/browser/components/enterprisepolicies/Policies.jsm
+++ b/browser/components/enterprisepolicies/Policies.jsm
@@ -7,16 +7,17 @@
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gXulStore",
"@mozilla.org/xul/xulstore;1",
"nsIXULStore");
XPCOMUtils.defineLazyModuleGetters(this, {
BookmarksPolicies: "resource:///modules/policies/BookmarksPolicies.jsm",
+ ProxyPolicies: "resource:///modules/policies/ProxyPolicies.jsm",
});
const PREF_LOGLEVEL = "browser.policies.loglevel";
const BROWSER_DOCUMENT_URL = "chrome://browser/content/browser.xul";
XPCOMUtils.defineLazyGetter(this, "log", () => {
let { ConsoleAPI } = ChromeUtils.import("resource://gre/modules/Console.jsm", {});
return new ConsoleAPI({
@@ -276,16 +277,23 @@ var Policies = {
},
"Popups": {
onBeforeUIStartup(manager, param) {
addAllowDenyPermissions("popup", param.Allow, null);
}
},
+ "Proxy": {
+ onBeforeAddons(manager, param) {
+ manager.disallowFeature("changeProxySettings");
+ ProxyPolicies.configureProxySettings(param, setAndLockPref);
+ }
+ },
+
"RememberPasswords": {
onBeforeUIStartup(manager, param) {
setAndLockPref("signon.rememberSignons", param);
}
},
};
/*
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/helpers/ProxyPolicies.jsm
@@ -0,0 +1,110 @@
+/* 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";
+
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+Cu.importGlobalProperties(["URL"]);
+
+const PREF_LOGLEVEL = "browser.policies.loglevel";
+
+XPCOMUtils.defineLazyGetter(this, "log", () => {
+ let { ConsoleAPI } = ChromeUtils.import("resource://gre/modules/Console.jsm", {});
+ return new ConsoleAPI({
+ prefix: "ProxyPolicies.jsm",
+ // tip: set maxLogLevel to "debug" and use log.debug() to create detailed
+ // messages during development. See LOG_LEVELS in Console.jsm for details.
+ maxLogLevel: "error",
+ maxLogLevelPref: PREF_LOGLEVEL,
+ });
+});
+
+// Don't use const here because this is acessed by
+// tests through the BackstagePass object.
+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],
+]);
+
+var EXPORTED_SYMBOLS = [ "ProxyPolicies" ];
+
+var ProxyPolicies = {
+ configureProxySettings(param, setAndLockPref) {
+ if (param.Mode) {
+ setAndLockPref("network.proxy.type", PROXY_TYPES_MAP.get(param.Mode));
+ }
+
+ if (param.AutoConfigURL) {
+ setAndLockPref("network.proxy.autoconfig_url", param.AutoConfigURL.spec);
+ }
+
+ if (param.UseProxyForDNS !== undefined) {
+ setAndLockPref("network.proxy.socks_remote_dns", param.UseProxyForDNS);
+ }
+
+ if (param.AutoLogin !== undefined) {
+ setAndLockPref("signon.autologin.proxy", param.AutoLogin);
+ }
+
+ if (param.SOCKSVersion !== undefined) {
+ if (param.SOCKSVersion != 4 && param.SOCKSVersion != 5) {
+ log.error("Invalid SOCKS version");
+ } else {
+ setAndLockPref("network.proxy.socks_version", param.SOCKSVersion);
+ }
+ }
+
+ if (param.Passthrough !== undefined) {
+ setAndLockPref("network.proxy.no_proxies_on", param.Passthrough);
+ }
+
+ if (param.UseHTTPProxyForAllProtocols !== undefined) {
+ setAndLockPref("network.proxy.share_proxy_settings", param.UseHTTPProxyForAllProtocols);
+ }
+
+ 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) {
+ log.error(`Invalid address for ${type} proxy: ${address}`);
+ return;
+ }
+
+ setAndLockPref(`network.proxy.${type}`, url.hostname);
+ if (url.port) {
+ setAndLockPref(`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.FTPProxy = param.SSLProxy = param.SOCKSProxy = param.HTTPProxy;
+ }
+ }
+
+ if (param.FTPProxy) {
+ setProxyHostAndPort("ftp", param.FTPProxy);
+ }
+
+ if (param.SSLProxy) {
+ setProxyHostAndPort("ssl", param.SSLProxy);
+ }
+
+ if (param.SOCKSProxy) {
+ setProxyHostAndPort("socks", param.SOCKSProxy);
+ }
+ }
+};
--- a/browser/components/enterprisepolicies/helpers/moz.build
+++ b/browser/components/enterprisepolicies/helpers/moz.build
@@ -4,9 +4,10 @@
# 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.jsm',
+ 'ProxyPolicies.jsm',
]
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/helpers/sample_proxy.json
@@ -0,0 +1,12 @@
+{
+ "policies": {
+ "Proxy": {
+ "Mode": "manual",
+ "HTTPProxy": "www.example.com:42",
+ "UseHTTPProxyForAllProtocols": true,
+ "Passthrough": "foo, bar, baz",
+ "SOCKSVersion": 4,
+ "UseProxyForDNS": true
+ }
+ }
+}
--- a/browser/components/enterprisepolicies/schemas/policies-schema.json
+++ b/browser/components/enterprisepolicies/schemas/policies-schema.json
@@ -256,16 +256,70 @@
"type": "array",
"items": {
"type": "origin"
}
}
}
},
+ "Proxy": {
+ "description": "Configure Proxy settings.",
+ "first_available": "60.0",
+
+ "type": "object",
+ "properties": {
+ "Mode": {
+ "type": "string",
+ "enum": ["none", "system", "manual", "autoDetect", "autoConfig"]
+ },
+
+ "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"
+ }
+ }
+ },
+
"RememberPasswords": {
"description": "Enforces the setting to allow Firefox to remember saved logins and passwords. Both true and false values are accepted.",
"first_available": "60.0",
"type": "boolean"
}
}
}
--- a/browser/components/enterprisepolicies/tests/browser/browser.ini
+++ b/browser/components/enterprisepolicies/tests/browser/browser.ini
@@ -28,10 +28,11 @@ support-files =
[browser_policy_disable_fxaccounts.js]
[browser_policy_disable_fxscreenshots.js]
[browser_policy_disable_masterpassword.js]
[browser_policy_disable_pocket.js]
[browser_policy_disable_privatebrowsing.js]
[browser_policy_disable_shield.js]
[browser_policy_display_bookmarks.js]
[browser_policy_display_menu.js]
+[browser_policy_proxy.js]
[browser_policy_remember_passwords.js]
[browser_policy_set_homepage.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_proxy.js
@@ -0,0 +1,126 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+function checkPref(prefName, expectedValue) {
+ let prefType, prefValue;
+ switch (typeof(expectedValue)) {
+ case "boolean":
+ prefType = Services.prefs.PREF_BOOL;
+ prefValue = Services.prefs.getBoolPref(prefName);
+ break;
+
+ case "number":
+ prefType = Services.prefs.PREF_INT;
+ prefValue = Services.prefs.getIntPref(prefName);
+ break;
+
+ case "string":
+ prefType = Services.prefs.PREF_STRING;
+ prefValue = Services.prefs.getStringPref(prefName);
+ break;
+ }
+
+ ok(Services.prefs.prefIsLocked(prefName), `Pref ${prefName} is correctly locked`);
+ is(Services.prefs.getPrefType(prefName), prefType, `Pref ${prefName} has the correct type`);
+ is(prefValue, expectedValue, `Pref ${prefName} has the correct value`);
+}
+
+add_task(async function test_proxy_modes() {
+ // Checks that every Mode value translates correctly to the expected pref value
+ let { PROXY_TYPES_MAP } = ChromeUtils.import("resource:///modules/policies/ProxyPolicies.jsm", {});
+ for (let [mode, expectedValue] of PROXY_TYPES_MAP) {
+ await setupPolicyEngineWithJson({
+ "policies": {
+ "Proxy": {
+ "Mode": mode
+ }
+ }
+ });
+ checkPref("network.proxy.type", expectedValue);
+ }
+});
+
+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,
+ }
+ }
+ });
+
+ checkPref("network.proxy.socks_remote_dns", false);
+ checkPref("signon.autologin.proxy", false);
+
+ await setupPolicyEngineWithJson({
+ "policies": {
+ "Proxy": {
+ "UseProxyForDNS": true,
+ "AutoLogin": true,
+ }
+ }
+ });
+
+ checkPref("network.proxy.socks_remote_dns", true);
+ checkPref("signon.autologin.proxy", true);
+});
+
+add_task(async function test_proxy_socks_and_passthrough() {
+ await setupPolicyEngineWithJson({
+ "policies": {
+ "Proxy": {
+ "SOCKSVersion": 4,
+ "Passthrough": "a, b, c"
+ }
+ }
+ });
+
+ checkPref("network.proxy.socks_version", 4);
+ checkPref("network.proxy.no_proxies_on", "a, b, c");
+});
+
+add_task(async function test_proxy_addresses() {
+ function checkProxyPref(proxytype, address, port) {
+ checkPref(`network.proxy.${proxytype}`, address);
+ checkPref(`network.proxy.${proxytype}_port`, port);
+ }
+
+ await setupPolicyEngineWithJson({
+ "policies": {
+ "Proxy": {
+ "HTTPProxy": "http.proxy.example.com:10",
+ "FTPProxy": "ftp.proxy.example.com:20",
+ "SSLProxy": "ssl.proxy.example.com:30",
+ "SOCKSProxy": "socks.proxy.example.com:40",
+ }
+ }
+ });
+
+ checkProxyPref("http", "http.proxy.example.com", 10);
+ checkProxyPref("ftp", "ftp.proxy.example.com", 20);
+ 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",
+ "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("ftp", "http.proxy.example.com", 10);
+ checkProxyPref("ssl", "http.proxy.example.com", 10);
+ checkProxyPref("socks", "http.proxy.example.com", 10);
+});
--- a/browser/components/preferences/connection.js
+++ b/browser/components/preferences/connection.js
@@ -256,17 +256,17 @@ var gConnectionsDialog = {
let isLocked = API_PROXY_PREFS.some(
pref => Services.prefs.prefIsLocked(pref));
function setInputsDisabledState(isControlled) {
let disabled = isLocked || isControlled;
for (let element of gConnectionsDialog.getProxyControls()) {
element.disabled = disabled;
}
- if (!isControlled) {
+ if (!isLocked) {
gConnectionsDialog.proxyTypeChanged();
}
}
if (isLocked) {
// An extension can't control this setting if any pref is locked.
hideControllingExtension(PROXY_KEY);
setInputsDisabledState(false);
--- a/toolkit/components/extensions/ext-browserSettings.js
+++ b/toolkit/components/extensions/ext-browserSettings.js
@@ -305,16 +305,21 @@ this.browserSettings = class extends Ext
),
{
set: details => {
if (AppConstants.platform === "android") {
throw new ExtensionError(
"proxyConfig is not supported on android.");
}
+ if (!Services.policies.isAllowed("changeProxySettings")) {
+ throw new ExtensionError(
+ "Proxy settings are being managed by the Policies manager.");
+ }
+
let value = details.value;
if (!PROXY_TYPES_MAP.has(value.proxyType)) {
throw new ExtensionError(
`${value.proxyType} is not a valid value for proxyType.`);
}
for (let prop of ["http", "ftp", "ssl", "socks"]) {