Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

// Unit tests for a NTLM authenticated proxy, proxying for a NTLM authenticated
// web server.
//
// Currently the tests do not determine whether the Authentication dialogs have
// been displayed.
//
"use strict";
const { HttpServer } = ChromeUtils.importESModule(
);
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() {
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) {
// 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 () {
Assert.equal(expectedRequests, requestsMade, "Number of requests made ");
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 NTLM_CHALLENGE =
NTLM_TYPE2_PREFIX +
"DAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAAR" +
"ABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUg" +
"BWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwB" +
"lAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA=";
const PROXY_CHALLENGE =
NTLM_TYPE2_PREFIX +
"DgAOADgAAAAFgooCqLNOPe2aZOAAAAAAAAAAAFAAUABGAAAA" +
"BgEAAAAAAA9HAFcATAAtAE0ATwBaAAIADgBHAFcATAAtAE0A" +
"TwBaAAEADgBHAFcATAAtAE0ATwBaAAQAAgAAAAMAEgBsAG8A" +
"YwBhAGwAaABvAHMAdAAHAAgAOKEwGEZL0gEAAAAA";
// Proxy and Web server responses for the happy path scenario.
// i.e. successful proxy auth and successful web server auth
//
function authHandler(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, 401, "Unauthorized");
response.setHeader("WWW-Authenticate", "NTLM", false);
break;
case 3:
// Web Server - Expecting a type 1 negotiate message from the client
authorization = metadata.getHeader("Authorization");
authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
Assert.equal(NTLM_TYPE1_PREFIX, authPrefix, "Expecting a Type 1 message");
response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
response.setHeader("WWW-Authenticate", NTLM_CHALLENGE, false);
break;
case 4:
// Web Server - Expecting a type 3 Authenticate message from the client
authorization = metadata.getHeader("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("Authorization");
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 authHandlerInvalidProxyPassword(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
// and reaching here should be an error, but have commented out for now
//dump( "ERROR: NTLM Proxy Authentication, connection should not be reused");
//Assert.fail();
response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
}
requestsMade++;
}
// Proxy and web server responses simulating a successful Proxy auth
// and a failed web server auth
// Note: the connection should not be reused once the password failure is
// detected
function authHandlerInvalidWebPassword(metadata, response) {
let authorization;
let authPrefix;
switch (requestsMade) {
case 0:
// Proxy - First request return a 407 to start Proxy 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", NTLM_CHALLENGE, false);
break;
case 2:
// Proxy - Expecting a type 3 Authenticate message from the client
// Responds with a 401 to start web server auth
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, 401, "Unauthorized");
response.setHeader("WWW-Authenticate", "NTLM", false);
break;
case 3:
// Web Server - Expecting a type 1 negotiate message from the client
authorization = metadata.getHeader("Authorization");
authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
Assert.equal(NTLM_TYPE1_PREFIX, authPrefix, "Expecting a Type 1 message");
response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
response.setHeader("WWW-Authenticate", NTLM_CHALLENGE, false);
break;
case 4:
// Web Server - Expecting a type 3 Authenticate message from the client
// Respond with a 401 to restart the auth sequence.
authorization = metadata.getHeader("Authorization");
authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
Assert.equal(NTLM_TYPE3_PREFIX, authPrefix, "Expecting a Type 1 message");
response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
break;
default:
// We should not get called past step 4
Assert.ok(
false,
"ERROR: NTLM Auth failed connection should not be reused"
);
}
requestsMade++;
}
// Tests to run test_bad_proxy_pass and test_bad_web_pass are split into two stages
// so that once we determine how detect password dialog displays we can check
// that the are displayed correctly, i.e. proxy password should not be prompted
// for when retrying the web server password
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));
});
}
// Happy code path
// Succesful proxy and web server auth.
add_task(async function test_happy_path() {
dump("RUNNING TEST: test_happy_path");
await setupTest("/auth", authHandler, 5, 200, 1);
});
// Failed proxy authentication
add_task(async function test_bad_proxy_pass_stage01() {
dump("RUNNING TEST: test_bad_proxy_pass_stage01");
await setupTest("/auth", authHandlerInvalidProxyPassword, 4, 407, 1);
});
// Successful logon after failed proxy auth
add_task(async function test_bad_proxy_pass_stage02() {
dump("RUNNING TEST: test_bad_proxy_pass_stage02");
await setupTest("/auth", authHandler, 5, 200, 0);
});
// successful proxy logon, unsuccessful web server sign on
add_task(async function test_bad_web_pass_stage01() {
dump("RUNNING TEST: test_bad_web_pass_stage01");
await setupTest("/auth", authHandlerInvalidWebPassword, 5, 401, 1);
});
// successful logon after failed web server auth.
add_task(async function test_bad_web_pass_stage02() {
dump("RUNNING TEST: test_bad_web_pass_stage02");
await setupTest("/auth", authHandler, 5, 200, 0);
});