summaryrefslogtreecommitdiffstats
path: root/netwerk/test/unit/test_ntlm_proxy_auth.js
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/test/unit/test_ntlm_proxy_auth.js')
-rw-r--r--netwerk/test/unit/test_ntlm_proxy_auth.js408
1 files changed, 408 insertions, 0 deletions
diff --git a/netwerk/test/unit/test_ntlm_proxy_auth.js b/netwerk/test/unit/test_ntlm_proxy_auth.js
new file mode 100644
index 0000000000..b6cb27890b
--- /dev/null
+++ b/netwerk/test/unit/test_ntlm_proxy_auth.js
@@ -0,0 +1,408 @@
+// Unit tests for a NTLM authenticated proxy
+//
+// Currently the tests do not determine whether the Authentication dialogs have
+// been displayed.
+//
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.importESModule(
+ "resource://testing-common/httpd.sys.mjs"
+);
+
+ChromeUtils.defineLazyGetter(this, "URL", function () {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+function AuthPrompt() {}
+
+AuthPrompt.prototype = {
+ user: "guest",
+ pass: "guest",
+
+ QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt2"]),
+
+ promptAuth: function ap_promptAuth(channel, level, authInfo) {
+ authInfo.username = this.user;
+ authInfo.password = this.pass;
+
+ return true;
+ },
+
+ asyncPromptAuth: function ap_async(chan, cb, ctx, lvl, info) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+};
+
+function Requestor() {}
+
+Requestor.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]),
+
+ getInterface: function requestor_gi(iid) {
+ if (iid.equals(Ci.nsIAuthPrompt2)) {
+ // Allow the prompt to store state by caching it here
+ if (!this.prompt) {
+ this.prompt = new AuthPrompt();
+ }
+ return this.prompt;
+ }
+
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ },
+
+ prompt: null,
+};
+
+function makeChan(url, loadingUrl) {
+ var principal = Services.scriptSecurityManager.createContentPrincipal(
+ Services.io.newURI(loadingUrl),
+ {}
+ );
+ return NetUtil.newChannel({
+ uri: url,
+ loadingPrincipal: principal,
+ securityFlags: Ci.nsILoadInfo.SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
+ });
+}
+
+function TestListener(resolve) {
+ this.resolve = resolve;
+}
+TestListener.prototype.onStartRequest = function (request, context) {
+ // Need to do the instanceof to allow request.responseStatus
+ // to be read.
+ if (!(request instanceof Ci.nsIHttpChannel)) {
+ dump("Expecting an HTTP channel");
+ }
+
+ Assert.equal(expectedResponse, request.responseStatus, "HTTP Status code");
+};
+TestListener.prototype.onStopRequest = function (request, context, status) {
+ Assert.equal(expectedRequests, requestsMade, "Number of requests made ");
+ Assert.equal(
+ exptTypeOneCount,
+ ntlmTypeOneCount,
+ "Number of type one messages received"
+ );
+ Assert.equal(
+ exptTypeTwoCount,
+ ntlmTypeTwoCount,
+ "Number of type two messages received"
+ );
+
+ this.resolve();
+};
+TestListener.prototype.onDataAvaiable = function (
+ request,
+ context,
+ stream,
+ offset,
+ count
+) {
+ read_stream(stream, count);
+};
+
+// NTLM Messages, for the received type 1 and 3 messages only check that they
+// are of the expected type.
+const NTLM_TYPE1_PREFIX = "NTLM TlRMTVNTUAABAAAA";
+const NTLM_TYPE2_PREFIX = "NTLM TlRMTVNTUAACAAAA";
+const NTLM_TYPE3_PREFIX = "NTLM TlRMTVNTUAADAAAA";
+const NTLM_PREFIX_LEN = 21;
+
+const PROXY_CHALLENGE =
+ NTLM_TYPE2_PREFIX +
+ "DgAOADgAAAAFgooCqLNOPe2aZOAAAAAAAAAAAFAAUABGAAAA" +
+ "BgEAAAAAAA9HAFcATAAtAE0ATwBaAAIADgBHAFcATAAtAE0A" +
+ "TwBaAAEADgBHAFcATAAtAE0ATwBaAAQAAgAAAAMAEgBsAG8A" +
+ "YwBhAGwAaABvAHMAdAAHAAgAOKEwGEZL0gEAAAAA";
+
+// Proxy responses for the happy path scenario.
+// i.e. successful proxy auth
+//
+function successfulAuth(metadata, response) {
+ let authorization;
+ let authPrefix;
+ switch (requestsMade) {
+ case 0:
+ // Proxy - First request to the Proxy resppond with a 407 to start auth
+ response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
+ response.setHeader("Proxy-Authenticate", "NTLM", false);
+ break;
+ case 1:
+ // Proxy - Expecting a type 1 negotiate message from the client
+ authorization = metadata.getHeader("Proxy-Authorization");
+ authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
+ Assert.equal(NTLM_TYPE1_PREFIX, authPrefix, "Expecting a Type 1 message");
+ response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
+ response.setHeader("Proxy-Authenticate", PROXY_CHALLENGE, false);
+ break;
+ case 2:
+ // Proxy - Expecting a type 3 Authenticate message from the client
+ // Will respond with a 401 to start web server auth sequence
+ authorization = metadata.getHeader("Proxy-Authorization");
+ authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
+ Assert.equal(NTLM_TYPE3_PREFIX, authPrefix, "Expecting a Type 3 message");
+ response.setStatusLine(metadata.httpVersion, 200, "Successful");
+ break;
+ default:
+ // We should be authenticated and further requests are permitted
+ authorization = metadata.getHeader("Proxy-Authorization");
+ Assert.isnull(authorization);
+ response.setStatusLine(metadata.httpVersion, 200, "Successful");
+ }
+ requestsMade++;
+}
+
+// Proxy responses simulating an invalid proxy password
+// Note: that the connection should not be reused after the
+// proxy auth fails.
+//
+function failedAuth(metadata, response) {
+ let authorization;
+ let authPrefix;
+ switch (requestsMade) {
+ case 0:
+ // Proxy - First request respond with a 407 to initiate auth sequence
+ response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
+ response.setHeader("Proxy-Authenticate", "NTLM", false);
+ break;
+ case 1:
+ // Proxy - Expecting a type 1 negotiate message from the client
+ authorization = metadata.getHeader("Proxy-Authorization");
+ authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
+ Assert.equal(NTLM_TYPE1_PREFIX, authPrefix, "Expecting a Type 1 message");
+ response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
+ response.setHeader("Proxy-Authenticate", PROXY_CHALLENGE, false);
+ break;
+ case 2:
+ // Proxy - Expecting a type 3 Authenticate message from the client
+ // Respond with a 407 to indicate invalid credentials
+ authorization = metadata.getHeader("Proxy-Authorization");
+ authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
+ Assert.equal(NTLM_TYPE3_PREFIX, authPrefix, "Expecting a Type 3 message");
+ response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
+ response.setHeader("Proxy-Authenticate", "NTLM", false);
+ break;
+ default:
+ // Strictly speaking the connection should not be reused at this point
+ // commenting out for now.
+ dump("ERROR: NTLM Proxy Authentication, connection should not be reused");
+ // assert.fail();
+ response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
+ }
+ requestsMade++;
+}
+//
+// Simulate a connection reset once the connection has been authenticated
+// Detects bug 486508
+//
+function connectionReset(metadata, response) {
+ let authorization;
+ let authPrefix;
+ switch (requestsMade) {
+ case 0:
+ // Proxy - First request to the Proxy resppond with a 407 to start auth
+ response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
+ response.setHeader("Proxy-Authenticate", "NTLM", false);
+ break;
+ case 1:
+ // Proxy - Expecting a type 1 negotiate message from the client
+ authorization = metadata.getHeader("Proxy-Authorization");
+ authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
+ Assert.equal(NTLM_TYPE1_PREFIX, authPrefix, "Expecting a Type 1 message");
+ ntlmTypeOneCount++;
+ response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
+ response.setHeader("Proxy-Authenticate", PROXY_CHALLENGE, false);
+ break;
+ case 2:
+ authorization = metadata.getHeader("Proxy-Authorization");
+ authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
+ Assert.equal(NTLM_TYPE3_PREFIX, authPrefix, "Expecting a Type 3 message");
+ ntlmTypeTwoCount++;
+ try {
+ response.seizePower();
+ response.finish();
+ } catch (e) {
+ Assert.ok(false, "unexpected exception" + e);
+ }
+ break;
+ default:
+ // Should not get any further requests on this channel
+ dump("ERROR: NTLM Proxy Authentication, connection should not be reused");
+ Assert.ok(false);
+ }
+ requestsMade++;
+}
+
+//
+// Reset the connection after a negotiate message has been received
+//
+function connectionReset02(metadata, response) {
+ var connectionNumber = httpserver.connectionNumber;
+ switch (requestsMade) {
+ case 0:
+ // Proxy - First request to the Proxy respond with a 407 to start auth
+ response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
+ response.setHeader("Proxy-Authenticate", "NTLM", false);
+ Assert.equal(connectionNumber, httpserver.connectionNumber);
+ break;
+ case 1:
+ // eslint-disable-next-line no-fallthrough
+ default:
+ // Proxy - Expecting a type 1 negotiate message from the client
+ Assert.equal(connectionNumber, httpserver.connectionNumber);
+ var authorization = metadata.getHeader("Proxy-Authorization");
+ var authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
+ Assert.equal(NTLM_TYPE1_PREFIX, authPrefix, "Expecting a Type 1 message");
+ ntlmTypeOneCount++;
+ try {
+ response.seizePower();
+ response.finish();
+ } catch (e) {
+ Assert.ok(false, "unexpected exception" + e);
+ }
+ }
+ requestsMade++;
+}
+
+var httpserver = null;
+function setup() {
+ httpserver = new HttpServer();
+ httpserver.start(-1);
+
+ Services.prefs.setCharPref("network.proxy.http", "localhost");
+ Services.prefs.setIntPref(
+ "network.proxy.http_port",
+ httpserver.identity.primaryPort
+ );
+ Services.prefs.setCharPref("network.proxy.no_proxies_on", "");
+ Services.prefs.setIntPref("network.proxy.type", 1);
+ Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
+
+ registerCleanupFunction(async () => {
+ 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.proxy.allow_hijacking_localhost");
+
+ await httpserver.stop();
+ });
+}
+setup();
+
+var expectedRequests = 0; // Number of HTTP requests that are expected
+var requestsMade = 0; // The number of requests that were made
+var expectedResponse = 0; // The response code
+// Note that any test failures in the HTTP handler
+// will manifest as a 500 response code
+
+// Common test setup
+// Parameters:
+// path - path component of the URL
+// handler - http handler function for the httpserver
+// requests - expected number oh http requests
+// response - expected http response code
+// clearCache - clear the authentication cache before running the test
+function setupTest(path, handler, requests, response, clearCache) {
+ requestsMade = 0;
+ expectedRequests = requests;
+ expectedResponse = response;
+
+ // clear the auth cache if requested
+ if (clearCache) {
+ dump("Clearing auth cache");
+ Cc["@mozilla.org/network/http-auth-manager;1"]
+ .getService(Ci.nsIHttpAuthManager)
+ .clearAll();
+ }
+
+ return new Promise(resolve => {
+ var chan = makeChan(URL + path, URL);
+ httpserver.registerPathHandler(path, handler);
+ chan.notificationCallbacks = new Requestor();
+ chan.asyncOpen(new TestListener(resolve));
+ });
+}
+
+let ntlmTypeOneCount = 0; // The number of NTLM type one messages received
+let exptTypeOneCount = 0; // The number of NTLM type one messages that should be received
+let ntlmTypeTwoCount = 0; // The number of NTLM type two messages received
+let exptTypeTwoCount = 0; // The number of NTLM type two messages that should received
+
+// Happy code path
+// Successful proxy auth.
+async function test_happy_path() {
+ dump("RUNNING TEST: test_happy_path");
+ await setupTest("/auth", successfulAuth, 3, 200, 1);
+}
+
+// Failed proxy authentication
+async function test_failed_auth() {
+ dump("RUNNING TEST:failed auth ");
+ await setupTest("/auth", failedAuth, 4, 407, 1);
+}
+
+// Test connection reset, after successful auth
+async function test_connection_reset() {
+ dump("RUNNING TEST:connection reset ");
+ ntlmTypeOneCount = 0;
+ ntlmTypeTwoCount = 0;
+ exptTypeOneCount = 1;
+ exptTypeTwoCount = 1;
+ await setupTest("/auth", connectionReset, 3, 500, 1);
+}
+
+// Test connection reset after sending a negotiate.
+// Client should retry request using the same connection
+async function test_connection_reset02() {
+ dump("RUNNING TEST:connection reset ");
+ ntlmTypeOneCount = 0;
+ ntlmTypeTwoCount = 0;
+ let maxRetryAttempt = 5;
+ exptTypeOneCount = maxRetryAttempt;
+ exptTypeTwoCount = 0;
+
+ Services.prefs.setIntPref(
+ "network.http.request.max-attempts",
+ maxRetryAttempt
+ );
+
+ await setupTest("/auth", connectionReset02, maxRetryAttempt + 1, 500, 1);
+}
+
+add_task(
+ { pref_set: [["network.auth.use_redirect_for_retries", false]] },
+ test_happy_path
+);
+add_task(
+ { pref_set: [["network.auth.use_redirect_for_retries", false]] },
+ test_failed_auth
+);
+add_task(
+ { pref_set: [["network.auth.use_redirect_for_retries", false]] },
+ test_connection_reset
+);
+add_task(
+ { pref_set: [["network.auth.use_redirect_for_retries", false]] },
+ test_connection_reset02
+);
+
+add_task(
+ { pref_set: [["network.auth.use_redirect_for_retries", true]] },
+ test_happy_path
+);
+add_task(
+ { pref_set: [["network.auth.use_redirect_for_retries", true]] },
+ test_failed_auth
+);
+add_task(
+ { pref_set: [["network.auth.use_redirect_for_retries", true]] },
+ test_connection_reset
+);
+add_task(
+ { pref_set: [["network.auth.use_redirect_for_retries", true]] },
+ test_connection_reset02
+);