1 // Unit tests for a NTLM authenticated proxy
3 // Currently the tests do not determine whether the Authentication dialogs have
9 const { HttpServer } = ChromeUtils.importESModule(
10 "resource://testing-common/httpd.sys.mjs"
13 ChromeUtils.defineLazyGetter(this, "URL", function () {
14 return "http://localhost:" + httpserver.identity.primaryPort;
17 function AuthPrompt() {}
19 AuthPrompt.prototype = {
23 QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt2"]),
25 promptAuth: function ap_promptAuth(channel, level, authInfo) {
26 authInfo.username = this.user;
27 authInfo.password = this.pass;
32 asyncPromptAuth: function ap_async(chan, cb, ctx, lvl, info) {
33 throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
37 function Requestor() {}
39 Requestor.prototype = {
40 QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]),
42 getInterface: function requestor_gi(iid) {
43 if (iid.equals(Ci.nsIAuthPrompt2)) {
44 // Allow the prompt to store state by caching it here
46 this.prompt = new AuthPrompt();
51 throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
57 function makeChan(url, loadingUrl) {
58 var principal = Services.scriptSecurityManager.createContentPrincipal(
59 Services.io.newURI(loadingUrl),
62 return NetUtil.newChannel({
64 loadingPrincipal: principal,
65 securityFlags: Ci.nsILoadInfo.SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT,
66 contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
70 function TestListener(resolve) {
71 this.resolve = resolve;
73 TestListener.prototype.onStartRequest = function (request, context) {
74 // Need to do the instanceof to allow request.responseStatus
76 if (!(request instanceof Ci.nsIHttpChannel)) {
77 dump("Expecting an HTTP channel");
80 Assert.equal(expectedResponse, request.responseStatus, "HTTP Status code");
82 TestListener.prototype.onStopRequest = function (request, context, status) {
83 Assert.equal(expectedRequests, requestsMade, "Number of requests made ");
87 "Number of type one messages received"
92 "Number of type two messages received"
97 TestListener.prototype.onDataAvaiable = function (
104 read_stream(stream, count);
107 // NTLM Messages, for the received type 1 and 3 messages only check that they
108 // are of the expected type.
109 const NTLM_TYPE1_PREFIX = "NTLM TlRMTVNTUAABAAAA";
110 const NTLM_TYPE2_PREFIX = "NTLM TlRMTVNTUAACAAAA";
111 const NTLM_TYPE3_PREFIX = "NTLM TlRMTVNTUAADAAAA";
112 const NTLM_PREFIX_LEN = 21;
114 const PROXY_CHALLENGE =
116 "DgAOADgAAAAFgooCqLNOPe2aZOAAAAAAAAAAAFAAUABGAAAA" +
117 "BgEAAAAAAA9HAFcATAAtAE0ATwBaAAIADgBHAFcATAAtAE0A" +
118 "TwBaAAEADgBHAFcATAAtAE0ATwBaAAQAAgAAAAMAEgBsAG8A" +
119 "YwBhAGwAaABvAHMAdAAHAAgAOKEwGEZL0gEAAAAA";
121 // Proxy responses for the happy path scenario.
122 // i.e. successful proxy auth
124 function successfulAuth(metadata, response) {
127 switch (requestsMade) {
129 // Proxy - First request to the Proxy resppond with a 407 to start auth
130 response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
131 response.setHeader("Proxy-Authenticate", "NTLM", false);
134 // Proxy - Expecting a type 1 negotiate message from the client
135 authorization = metadata.getHeader("Proxy-Authorization");
136 authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
137 Assert.equal(NTLM_TYPE1_PREFIX, authPrefix, "Expecting a Type 1 message");
138 response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
139 response.setHeader("Proxy-Authenticate", PROXY_CHALLENGE, false);
142 // Proxy - Expecting a type 3 Authenticate message from the client
143 // Will respond with a 401 to start web server auth sequence
144 authorization = metadata.getHeader("Proxy-Authorization");
145 authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
146 Assert.equal(NTLM_TYPE3_PREFIX, authPrefix, "Expecting a Type 3 message");
147 response.setStatusLine(metadata.httpVersion, 200, "Successful");
150 // We should be authenticated and further requests are permitted
151 authorization = metadata.getHeader("Proxy-Authorization");
152 Assert.isnull(authorization);
153 response.setStatusLine(metadata.httpVersion, 200, "Successful");
158 // Proxy responses simulating an invalid proxy password
159 // Note: that the connection should not be reused after the
162 function failedAuth(metadata, response) {
165 switch (requestsMade) {
167 // Proxy - First request respond with a 407 to initiate auth sequence
168 response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
169 response.setHeader("Proxy-Authenticate", "NTLM", false);
172 // Proxy - Expecting a type 1 negotiate message from the client
173 authorization = metadata.getHeader("Proxy-Authorization");
174 authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
175 Assert.equal(NTLM_TYPE1_PREFIX, authPrefix, "Expecting a Type 1 message");
176 response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
177 response.setHeader("Proxy-Authenticate", PROXY_CHALLENGE, false);
180 // Proxy - Expecting a type 3 Authenticate message from the client
181 // Respond with a 407 to indicate invalid credentials
182 authorization = metadata.getHeader("Proxy-Authorization");
183 authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
184 Assert.equal(NTLM_TYPE3_PREFIX, authPrefix, "Expecting a Type 3 message");
185 response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
186 response.setHeader("Proxy-Authenticate", "NTLM", false);
189 // Strictly speaking the connection should not be reused at this point
190 // commenting out for now.
191 dump("ERROR: NTLM Proxy Authentication, connection should not be reused");
193 response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
198 // Simulate a connection reset once the connection has been authenticated
199 // Detects bug 486508
201 function connectionReset(metadata, response) {
204 switch (requestsMade) {
206 // Proxy - First request to the Proxy resppond with a 407 to start auth
207 response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
208 response.setHeader("Proxy-Authenticate", "NTLM", false);
211 // Proxy - Expecting a type 1 negotiate message from the client
212 authorization = metadata.getHeader("Proxy-Authorization");
213 authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
214 Assert.equal(NTLM_TYPE1_PREFIX, authPrefix, "Expecting a Type 1 message");
216 response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
217 response.setHeader("Proxy-Authenticate", PROXY_CHALLENGE, false);
220 authorization = metadata.getHeader("Proxy-Authorization");
221 authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
222 Assert.equal(NTLM_TYPE3_PREFIX, authPrefix, "Expecting a Type 3 message");
225 response.seizePower();
228 Assert.ok(false, "unexpected exception" + e);
232 // Should not get any further requests on this channel
233 dump("ERROR: NTLM Proxy Authentication, connection should not be reused");
240 // Reset the connection after a negotiate message has been received
242 function connectionReset02(metadata, response) {
243 var connectionNumber = httpserver.connectionNumber;
244 switch (requestsMade) {
246 // Proxy - First request to the Proxy respond with a 407 to start auth
247 response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
248 response.setHeader("Proxy-Authenticate", "NTLM", false);
249 Assert.equal(connectionNumber, httpserver.connectionNumber);
252 // eslint-disable-next-line no-fallthrough
254 // Proxy - Expecting a type 1 negotiate message from the client
255 Assert.equal(connectionNumber, httpserver.connectionNumber);
256 var authorization = metadata.getHeader("Proxy-Authorization");
257 var authPrefix = authorization.substring(0, NTLM_PREFIX_LEN);
258 Assert.equal(NTLM_TYPE1_PREFIX, authPrefix, "Expecting a Type 1 message");
261 response.seizePower();
264 Assert.ok(false, "unexpected exception" + e);
270 var httpserver = null;
272 httpserver = new HttpServer();
273 httpserver.start(-1);
275 Services.prefs.setCharPref("network.proxy.http", "localhost");
276 Services.prefs.setIntPref(
277 "network.proxy.http_port",
278 httpserver.identity.primaryPort
280 Services.prefs.setCharPref("network.proxy.no_proxies_on", "");
281 Services.prefs.setIntPref("network.proxy.type", 1);
282 Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
284 registerCleanupFunction(async () => {
285 Services.prefs.clearUserPref("network.proxy.http");
286 Services.prefs.clearUserPref("network.proxy.http_port");
287 Services.prefs.clearUserPref("network.proxy.no_proxies_on");
288 Services.prefs.clearUserPref("network.proxy.type");
289 Services.prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
291 await httpserver.stop();
296 var expectedRequests = 0; // Number of HTTP requests that are expected
297 var requestsMade = 0; // The number of requests that were made
298 var expectedResponse = 0; // The response code
299 // Note that any test failures in the HTTP handler
300 // will manifest as a 500 response code
304 // path - path component of the URL
305 // handler - http handler function for the httpserver
306 // requests - expected number oh http requests
307 // response - expected http response code
308 // clearCache - clear the authentication cache before running the test
309 function setupTest(path, handler, requests, response, clearCache) {
311 expectedRequests = requests;
312 expectedResponse = response;
314 // clear the auth cache if requested
316 dump("Clearing auth cache");
317 Cc["@mozilla.org/network/http-auth-manager;1"]
318 .getService(Ci.nsIHttpAuthManager)
322 return new Promise(resolve => {
323 var chan = makeChan(URL + path, URL);
324 httpserver.registerPathHandler(path, handler);
325 chan.notificationCallbacks = new Requestor();
326 chan.asyncOpen(new TestListener(resolve));
330 let ntlmTypeOneCount = 0; // The number of NTLM type one messages received
331 let exptTypeOneCount = 0; // The number of NTLM type one messages that should be received
332 let ntlmTypeTwoCount = 0; // The number of NTLM type two messages received
333 let exptTypeTwoCount = 0; // The number of NTLM type two messages that should received
336 // Successful proxy auth.
337 async function test_happy_path() {
338 dump("RUNNING TEST: test_happy_path");
339 await setupTest("/auth", successfulAuth, 3, 200, 1);
342 // Failed proxy authentication
343 async function test_failed_auth() {
344 dump("RUNNING TEST:failed auth ");
345 await setupTest("/auth", failedAuth, 4, 407, 1);
348 // Test connection reset, after successful auth
349 async function test_connection_reset() {
350 dump("RUNNING TEST:connection reset ");
351 ntlmTypeOneCount = 0;
352 ntlmTypeTwoCount = 0;
353 exptTypeOneCount = 1;
354 exptTypeTwoCount = 1;
355 await setupTest("/auth", connectionReset, 3, 500, 1);
358 // Test connection reset after sending a negotiate.
359 // Client should retry request using the same connection
360 async function test_connection_reset02() {
361 dump("RUNNING TEST:connection reset ");
362 ntlmTypeOneCount = 0;
363 ntlmTypeTwoCount = 0;
364 let maxRetryAttempt = 5;
365 exptTypeOneCount = maxRetryAttempt;
366 exptTypeTwoCount = 0;
368 Services.prefs.setIntPref(
369 "network.http.request.max-attempts",
373 await setupTest("/auth", connectionReset02, maxRetryAttempt + 1, 500, 1);
377 { pref_set: [["network.auth.use_redirect_for_retries", false]] },
381 { pref_set: [["network.auth.use_redirect_for_retries", false]] },
385 { pref_set: [["network.auth.use_redirect_for_retries", false]] },
386 test_connection_reset
389 { pref_set: [["network.auth.use_redirect_for_retries", false]] },
390 test_connection_reset02
394 { pref_set: [["network.auth.use_redirect_for_retries", true]] },
398 { pref_set: [["network.auth.use_redirect_for_retries", true]] },
402 { pref_set: [["network.auth.use_redirect_for_retries", true]] },
403 test_connection_reset
406 { pref_set: [["network.auth.use_redirect_for_retries", true]] },
407 test_connection_reset02