/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* 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 tests the automatic login to the proxy with password, * if the password is stored and the browser is restarted. * * */ "use strict"; const { HttpServer } = ChromeUtils.importESModule( "resource://testing-common/httpd.sys.mjs" ); const FLAG_RETURN_FALSE = 1 << 0; const FLAG_WRONG_PASSWORD = 1 << 1; const FLAG_PREVIOUS_FAILED = 1 << 2; function AuthPrompt2(proxyFlags, hostFlags) { this.proxyCred.flags = proxyFlags; this.hostCred.flags = hostFlags; } AuthPrompt2.prototype = { proxyCred: { user: "proxy", pass: "guest", realmExpected: "intern", flags: 0, }, hostCred: { user: "host", pass: "guest", realmExpected: "extern", flags: 0 }, QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt2"]), promptAuth: function ap2_promptAuth(channel, encryptionLevel, authInfo) { try { // never HOST and PROXY set at the same time in prompt Assert.equal( (authInfo.flags & Ci.nsIAuthInformation.AUTH_HOST) != 0, (authInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) == 0 ); var isProxy = (authInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) != 0; var cred = isProxy ? this.proxyCred : this.hostCred; dump( "with flags: " + ((cred.flags & FLAG_WRONG_PASSWORD) != 0 ? "wrong password" : "") + " " + ((cred.flags & FLAG_PREVIOUS_FAILED) != 0 ? "previous failed" : "") + " " + ((cred.flags & FLAG_RETURN_FALSE) != 0 ? "return false" : "") + "\n" ); // PROXY properly set by necko (checked using realm) Assert.equal(cred.realmExpected, authInfo.realm); // PREVIOUS_FAILED properly set by necko Assert.equal( (cred.flags & FLAG_PREVIOUS_FAILED) != 0, (authInfo.flags & Ci.nsIAuthInformation.PREVIOUS_FAILED) != 0 ); if (cred.flags & FLAG_RETURN_FALSE) { cred.flags |= FLAG_PREVIOUS_FAILED; cred.flags &= ~FLAG_RETURN_FALSE; return false; } authInfo.username = cred.user; if (cred.flags & FLAG_WRONG_PASSWORD) { authInfo.password = cred.pass + ".wrong"; cred.flags |= FLAG_PREVIOUS_FAILED; // Now clear the flag to avoid an infinite loop cred.flags &= ~FLAG_WRONG_PASSWORD; } else { authInfo.password = cred.pass; cred.flags &= ~FLAG_PREVIOUS_FAILED; } } catch (e) { do_throw(e); } return true; }, asyncPromptAuth: function ap2_async( channel, callback, context, encryptionLevel, authInfo ) { var me = this; var allOverAndDead = false; executeSoon(function () { try { if (allOverAndDead) { throw new Error("already canceled"); } var ret = me.promptAuth(channel, encryptionLevel, authInfo); if (!ret) { callback.onAuthCancelled(context, true); } else { callback.onAuthAvailable(context, authInfo); } allOverAndDead = true; } catch (e) { do_throw(e); } }); return new Cancelable(function () { if (allOverAndDead) { throw new Error("can't cancel, already ran"); } callback.onAuthAvailable(context, authInfo); allOverAndDead = true; }); }, }; function Cancelable(onCancelFunc) { this.onCancelFunc = onCancelFunc; } Cancelable.prototype = { QueryInterface: ChromeUtils.generateQI(["nsICancelable"]), cancel: function cancel() { try { this.onCancelFunc(); } catch (e) { do_throw(e); } }, }; function Requestor(proxyFlags, hostFlags) { this.proxyFlags = proxyFlags; this.hostFlags = hostFlags; } Requestor.prototype = { QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]), getInterface: function requestor_gi(iid) { if (iid.equals(Ci.nsIAuthPrompt)) { dump("authprompt1 not implemented\n"); throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE); } if (iid.equals(Ci.nsIAuthPrompt2)) { try { // Allow the prompt to store state by caching it here if (!this.prompt2) { this.prompt2 = new AuthPrompt2(this.proxyFlags, this.hostFlags); } return this.prompt2; } catch (e) { do_throw(e); } } throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE); }, prompt2: null, }; var listener = { expectedCode: -1, // uninitialized onStartRequest: function test_onStartR(request) { try { // Proxy auth cancellation return failures to avoid spoofing if ( !Components.isSuccessCode(request.status) && this.expectedCode != 407 ) { do_throw("Channel should have a success code!"); } if (!(request instanceof Ci.nsIHttpChannel)) { do_throw("Expecting an HTTP channel"); } Assert.equal(this.expectedCode, request.responseStatus); // If we expect 200, the request should have succeeded Assert.equal(this.expectedCode == 200, request.requestSucceeded); var cookie = ""; try { cookie = request.getRequestHeader("Cookie"); } catch (e) {} Assert.equal(cookie, ""); } catch (e) { do_throw("Unexpected exception: " + e); } throw Components.Exception("", Cr.NS_ERROR_ABORT); }, onDataAvailable: function test_ODA() { do_throw("Should not get any data!"); }, onStopRequest: function test_onStopR(request, status) { Assert.equal(status, Cr.NS_ERROR_ABORT); if (current_test < tests.length - 1) { // First, need to clear the auth cache Cc["@mozilla.org/network/http-auth-manager;1"] .getService(Ci.nsIHttpAuthManager) .clearAll(); current_test++; tests[current_test](); } else { do_test_pending(); httpserv.stop(do_test_finished); } do_test_finished(); }, }; function makeChan(url) { if (!url) { url = "http://somesite/"; } return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true, }).QueryInterface(Ci.nsIHttpChannel); } var current_test = 0; var httpserv = null; function run_test() { httpserv = new HttpServer(); httpserv.registerPathHandler("/", proxyAuthHandler); httpserv.identity.add("http", "somesite", 80); httpserv.start(-1); Services.prefs.setCharPref("network.proxy.http", "localhost"); Services.prefs.setIntPref( "network.proxy.http_port", httpserv.identity.primaryPort ); Services.prefs.setCharPref("network.proxy.no_proxies_on", ""); Services.prefs.setIntPref("network.proxy.type", 1); // Turn off the authentication dialog blocking for this test. Services.prefs.setIntPref("network.auth.subresource-http-auth-allow", 2); Services.prefs.setBoolPref( "network.auth.non-web-content-triggered-resources-http-auth-allow", true ); registerCleanupFunction(() => { Services.prefs.clearUserPref("network.proxy.http"); Services.prefs.clearUserPref("network.proxy.http_port"); Services.prefs.clearUserPref("network.proxy.no_proxies_on"); Services.prefs.clearUserPref("network.proxy.type"); Services.prefs.clearUserPref("network.auth.subresource-http-auth-allow"); Services.prefs.clearUserPref( "network.auth.non-web-content-triggered-resources-http-auth-allow" ); }); tests[current_test](); } function test_proxy_returnfalse() { dump("\ntest: proxy returnfalse\n"); var chan = makeChan(); chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 0); listener.expectedCode = 407; // Proxy Unauthorized chan.asyncOpen(listener); do_test_pending(); } function test_proxy_wrongpw() { dump("\ntest: proxy wrongpw\n"); var chan = makeChan(); chan.notificationCallbacks = new Requestor(FLAG_WRONG_PASSWORD, 0); listener.expectedCode = 200; // Eventually OK chan.asyncOpen(listener); do_test_pending(); } function test_all_ok() { dump("\ntest: all ok\n"); var chan = makeChan(); chan.notificationCallbacks = new Requestor(0, 0); listener.expectedCode = 200; // OK chan.asyncOpen(listener); do_test_pending(); } function test_proxy_407_cookie() { var chan = makeChan(); chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 0); chan.setRequestHeader("X-Set-407-Cookie", "1", false); listener.expectedCode = 407; // Proxy Unauthorized chan.asyncOpen(listener); do_test_pending(); } function test_proxy_200_cookie() { var chan = makeChan(); chan.notificationCallbacks = new Requestor(0, 0); chan.setRequestHeader("X-Set-407-Cookie", "1", false); listener.expectedCode = 200; // OK chan.asyncOpen(listener); do_test_pending(); } function test_host_returnfalse() { dump("\ntest: host returnfalse\n"); var chan = makeChan(); chan.notificationCallbacks = new Requestor(0, FLAG_RETURN_FALSE); listener.expectedCode = 401; // Host Unauthorized chan.asyncOpen(listener); do_test_pending(); } function test_host_wrongpw() { dump("\ntest: host wrongpw\n"); var chan = makeChan(); chan.notificationCallbacks = new Requestor(0, FLAG_WRONG_PASSWORD); listener.expectedCode = 200; // Eventually OK chan.asyncOpen(listener); do_test_pending(); } function test_proxy_wrongpw_host_wrongpw() { dump("\ntest: proxy wrongpw, host wrongpw\n"); var chan = makeChan(); chan.notificationCallbacks = new Requestor( FLAG_WRONG_PASSWORD, FLAG_WRONG_PASSWORD ); listener.expectedCode = 200; // OK chan.asyncOpen(listener); do_test_pending(); } function test_proxy_wrongpw_host_returnfalse() { dump("\ntest: proxy wrongpw, host return false\n"); var chan = makeChan(); chan.notificationCallbacks = new Requestor( FLAG_WRONG_PASSWORD, FLAG_RETURN_FALSE ); listener.expectedCode = 401; // Host Unauthorized chan.asyncOpen(listener); do_test_pending(); } var tests = [ test_proxy_returnfalse, test_proxy_wrongpw, test_all_ok, test_proxy_407_cookie, test_proxy_200_cookie, test_host_returnfalse, test_host_wrongpw, test_proxy_wrongpw_host_wrongpw, test_proxy_wrongpw_host_returnfalse, ]; // PATH HANDLERS // Proxy function proxyAuthHandler(metadata, response) { try { var realm = "intern"; // btoa("proxy:guest"), but that function is not available here var expectedHeader = "Basic cHJveHk6Z3Vlc3Q="; var body; if ( metadata.hasHeader("Proxy-Authorization") && metadata.getHeader("Proxy-Authorization") == expectedHeader ) { dump("proxy password ok\n"); response.setHeader( "Proxy-Authenticate", 'Basic realm="' + realm + '"', false ); hostAuthHandler(metadata, response); } else { dump("proxy password required\n"); response.setStatusLine( metadata.httpVersion, 407, "Unauthorized by HTTP proxy" ); response.setHeader( "Proxy-Authenticate", 'Basic realm="' + realm + '"', false ); if (metadata.hasHeader("X-Set-407-Cookie")) { response.setHeader("Set-Cookie", "chewy", false); } body = "failed"; response.bodyOutputStream.write(body, body.length); } } catch (e) { do_throw(e); } } // Host /auth function hostAuthHandler(metadata, response) { try { var realm = "extern"; // btoa("host:guest"), but that function is not available here var expectedHeader = "Basic aG9zdDpndWVzdA=="; var body; if ( metadata.hasHeader("Authorization") && metadata.getHeader("Authorization") == expectedHeader ) { dump("host password ok\n"); response.setStatusLine( metadata.httpVersion, 200, "OK, authorized for host" ); response.setHeader( "WWW-Authenticate", 'Basic realm="' + realm + '"', false ); body = "success"; } else { dump("host password required\n"); response.setStatusLine( metadata.httpVersion, 401, "Unauthorized by HTTP server host" ); response.setHeader( "WWW-Authenticate", 'Basic realm="' + realm + '"', false ); body = "failed"; } response.bodyOutputStream.write(body, body.length); } catch (e) { do_throw(e); } }