Bug 1395886 Add support for proxy authentication
MozReview-Commit-ID: BDghp5uro35
--- a/testing/marionette/client/marionette_driver/geckoinstance.py
+++ b/testing/marionette/client/marionette_driver/geckoinstance.py
@@ -117,16 +117,19 @@ class GeckoInstance(object):
# Ensure blocklist updates don't hit the network
"services.settings.server": "http://%(server)s/dummy/blocklist/",
# Disable password capture, so that tests that include forms aren"t
# influenced by the presence of the persistent doorhanger notification
"signon.rememberSignons": False,
+ # Prevent popup for proxy authentication when a login is stored
+ "signon.autologin.proxy": True,
+
# Prevent starting into safe mode after application crashes
"toolkit.startup.max_resumed_crashes": -1,
# We want to collect telemetry, but we don't want to send in the results
"toolkit.telemetry.server": "https://%(server)s/dummy/telemetry/",
# Enabling the support for File object creation in the content process.
"dom.file.createInChild": True,
--- a/testing/marionette/components/marionette.js
+++ b/testing/marionette/components/marionette.js
@@ -253,16 +253,19 @@ const RECOMMENDED_PREFS = new Map([
// Ensure blocklist updates do not hit the network
["services.settings.server", "http://%(server)s/dummy/blocklist/"],
// Do not automatically fill sign-in forms with known usernames and
// passwords
["signon.autofillForms", false],
+ // Prevent popup for proxy authentication when a login is stored
+ ["signon.autologin.proxy", true],
+
// Disable password capture, so that tests that include forms are not
// influenced by the presence of the persistent doorhanger notification
["signon.rememberSignons", false],
// Disable first-run welcome page
["startup.homepage_welcome_url", "about:blank"],
["startup.homepage_welcome_url.additional", ""],
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_proxy.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_proxy.py
@@ -63,16 +63,36 @@ class TestProxyCapabilities(MarionetteTe
"socksProxy": proxy_hostname,
"socksVersion": 4,
}}
self.marionette.start_session(capabilities)
self.assertEqual(self.marionette.session_capabilities["proxy"],
capabilities["proxy"])
+ def test_proxy_type_manual_with_authentication(self):
+ proxy_hostname = "104.236.137.24"
+ username = "username"
+ password = "password"
+ capabilities = {"proxy": {
+ "proxyType": "manual",
+ "ftpProxy": "{}:{}@{}:3128".format(username, password, proxy_hostname),
+ "httpProxy": "{}:{}@{}:3128".format(username, password, proxy_hostname),
+ "sslProxy": "{}:{}@{}:3128".format(username, password, proxy_hostname),
+ }}
+
+ self.marionette.start_session(capabilities)
+ self.assertEqual(self.marionette.session_capabilities["proxy"],
+ capabilities["proxy"])
+
+ url = "https://www.google.com/search?q=my+ip+address"
+ self.marionette.navigate(url)
+ self.assertEqual(self.marionette.get_url(), url)
+ self.assertIn("104.236.137.24", self.marionette.page_source)
+
def test_proxy_type_manual_socks_requires_version(self):
proxy_port = 4444
proxy_hostname = "marionette.test"
proxy_host = "{}:{}".format(proxy_hostname, proxy_port)
capabilities = {"proxy": {
"proxyType": "manual",
"socksProxy": proxy_host,
}}
--- a/testing/marionette/session.js
+++ b/testing/marionette/session.js
@@ -1,16 +1,19 @@
/* 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";
Cu.importGlobalProperties(["URL"]);
+ChromeUtils.import("resource://gre/modules/Log.jsm");
+const logger = Log.repository.getLogger("Marionette");
+
ChromeUtils.import("resource://gre/modules/Preferences.jsm");
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("chrome://marionette/content/assert.js");
const {
InvalidArgumentError,
} = ChromeUtils.import("chrome://marionette/content/error.js", {});
const {
@@ -123,16 +126,31 @@ session.Proxy = class {
* Sets Firefox proxy settings.
*
* @return {boolean}
* True if proxy settings were updated as a result of calling this
* function, or false indicating that this function acted as
* a no-op.
*/
init() {
+ function addProxyLogin(login) {
+ if (!login) return;
+ let logins = Services.logins.findLogins(
+ {}, login.hostname, "", login.httpRealm, {});
+ if (logins.length) {
+ if (login.username != logins[0].username ||
+ login.password != logins[0].password) {
+ // Throw exception here?
+ logger.debug("Cannot add two different logins for the same host");
+ }
+ } else {
+ Services.logins.addLogin(login);
+ }
+ }
+
switch (this.proxyType) {
case "autodetect":
Preferences.set("network.proxy.type", 4);
return true;
case "direct":
Preferences.set("network.proxy.type", 0);
return true;
@@ -140,30 +158,33 @@ session.Proxy = class {
case "manual":
Preferences.set("network.proxy.type", 1);
if (this.ftpProxy) {
Preferences.set("network.proxy.ftp", this.ftpProxy);
if (Number.isInteger(this.ftpProxyPort)) {
Preferences.set("network.proxy.ftp_port", this.ftpProxyPort);
}
+ addProxyLogin(this.ftpLogin);
}
if (this.httpProxy) {
Preferences.set("network.proxy.http", this.httpProxy);
if (Number.isInteger(this.httpProxyPort)) {
Preferences.set("network.proxy.http_port", this.httpProxyPort);
}
+ addProxyLogin(this.httpLogin);
}
if (this.sslProxy) {
Preferences.set("network.proxy.ssl", this.sslProxy);
if (Number.isInteger(this.sslProxyPort)) {
Preferences.set("network.proxy.ssl_port", this.sslProxyPort);
}
+ addProxyLogin(this.sslLogin);
}
if (this.socksProxy) {
Preferences.set("network.proxy.socks", this.socksProxy);
if (Number.isInteger(this.socksProxyPort)) {
Preferences.set("network.proxy.socks_port", this.socksProxyPort);
}
if (this.socksVersion) {
@@ -235,26 +256,38 @@ session.Proxy = class {
if (!Number.isInteger(port)) {
if (scheme === "socks") {
port = null;
} else {
port = Services.io.getProtocolHandler(scheme).defaultPort;
}
}
- if (url.username != "" ||
- url.password != "" ||
- url.pathname != "/" ||
+ let login = null;
+ if (url.username !== "") {
+ login = Cc["@mozilla.org/login-manager/loginInfo;1"]
+ .createInstance(Ci.nsILoginInfo);
+ login.init(
+ `moz-proxy://${url.hostname}:${port}`,
+ null,
+ "Squid proxy-caching web server",
+ url.username,
+ url.password,
+ "",
+ "");
+ }
+
+ if (url.pathname != "/" ||
url.search != "" ||
url.hash != "") {
throw new InvalidArgumentError(
- `${host} was not of the form host[:port]`);
+ `${host} was not of the form [username:password@]host[:port]`);
}
- return [hostname, port];
+ return [hostname, port, login];
}
let p = new session.Proxy();
if (typeof json == "undefined" || json === null) {
return p;
}
assert.object(json, pprint`Expected "proxy" to be an object, got ${json}`);
@@ -273,23 +306,23 @@ session.Proxy = class {
case "pac":
p.proxyAutoconfigUrl = assert.string(json.proxyAutoconfigUrl,
`Expected "proxyAutoconfigUrl" to be a string, ` +
pprint`got ${json.proxyAutoconfigUrl}`);
break;
case "manual":
if (typeof json.ftpProxy != "undefined") {
- [p.ftpProxy, p.ftpProxyPort] = fromHost("ftp", json.ftpProxy);
+ [p.ftpProxy, p.ftpProxyPort, p.ftpLogin] = fromHost("ftp", json.ftpProxy);
}
if (typeof json.httpProxy != "undefined") {
- [p.httpProxy, p.httpProxyPort] = fromHost("http", json.httpProxy);
+ [p.httpProxy, p.httpProxyPort, p.ftpLogin] = fromHost("http", json.httpProxy);
}
if (typeof json.sslProxy != "undefined") {
- [p.sslProxy, p.sslProxyPort] = fromHost("https", json.sslProxy);
+ [p.sslProxy, p.sslProxyPort, p.sslLogin] = fromHost("https", json.sslProxy);
}
if (typeof json.socksProxy != "undefined") {
[p.socksProxy, p.socksProxyPort] = fromHost("socks", json.socksProxy);
p.socksVersion = assert.positiveInteger(json.socksVersion);
}
if (typeof json.noProxy != "undefined") {
let entries = assert.array(json.noProxy,
pprint`Expected "noProxy" to be an array, got ${json.noProxy}`);
@@ -322,17 +355,27 @@ session.Proxy = class {
if (!hostname) {
return null;
}
// Add brackets around IPv6 addresses
hostname = addBracketsToIpv6Hostname(hostname);
if (port != null) {
- return `${hostname}:${port}`;
+ hostname = `${hostname}:${port}`;
+ }
+
+ let logins = Services.logins.findLogins(
+ {},
+ `moz-proxy://${hostname}`,
+ null,
+ "Squid proxy-caching web server",
+ {});
+ if (logins.length) {
+ hostname = `${logins[0].username}:${logins[0].password}@${hostname}`;
}
return hostname;
}
let excludes = this.noProxy;
if (excludes) {
excludes = excludes.map(addBracketsToIpv6Hostname);