Bug 1857386 [wpt PR 42383] - Update wpt metadata, a=testonly
[gecko.git] / netwerk / test / unit / test_ntlm_proxy_auth.js
blobb6cb27890b88ca319bb82d4cad7bc280f469b513
1 // Unit tests for a NTLM authenticated proxy
2 //
3 // Currently the tests do not determine whether the Authentication dialogs have
4 // been displayed.
5 //
7 "use strict";
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;
15 });
17 function AuthPrompt() {}
19 AuthPrompt.prototype = {
20   user: "guest",
21   pass: "guest",
23   QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt2"]),
25   promptAuth: function ap_promptAuth(channel, level, authInfo) {
26     authInfo.username = this.user;
27     authInfo.password = this.pass;
29     return true;
30   },
32   asyncPromptAuth: function ap_async(chan, cb, ctx, lvl, info) {
33     throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
34   },
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
45       if (!this.prompt) {
46         this.prompt = new AuthPrompt();
47       }
48       return this.prompt;
49     }
51     throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
52   },
54   prompt: null,
57 function makeChan(url, loadingUrl) {
58   var principal = Services.scriptSecurityManager.createContentPrincipal(
59     Services.io.newURI(loadingUrl),
60     {}
61   );
62   return NetUtil.newChannel({
63     uri: url,
64     loadingPrincipal: principal,
65     securityFlags: Ci.nsILoadInfo.SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT,
66     contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
67   });
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
75   // to be read.
76   if (!(request instanceof Ci.nsIHttpChannel)) {
77     dump("Expecting an HTTP channel");
78   }
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 ");
84   Assert.equal(
85     exptTypeOneCount,
86     ntlmTypeOneCount,
87     "Number of type one messages received"
88   );
89   Assert.equal(
90     exptTypeTwoCount,
91     ntlmTypeTwoCount,
92     "Number of type two messages received"
93   );
95   this.resolve();
97 TestListener.prototype.onDataAvaiable = function (
98   request,
99   context,
100   stream,
101   offset,
102   count
103 ) {
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 =
115   NTLM_TYPE2_PREFIX +
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) {
125   let authorization;
126   let authPrefix;
127   switch (requestsMade) {
128     case 0:
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);
132       break;
133     case 1:
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);
140       break;
141     case 2:
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");
148       break;
149     default:
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");
154   }
155   requestsMade++;
158 // Proxy responses simulating an invalid proxy password
159 // Note: that the connection should not be reused after the
160 //       proxy auth fails.
162 function failedAuth(metadata, response) {
163   let authorization;
164   let authPrefix;
165   switch (requestsMade) {
166     case 0:
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);
170       break;
171     case 1:
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);
178       break;
179     case 2:
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);
187       break;
188     default:
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");
192       // assert.fail();
193       response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
194   }
195   requestsMade++;
198 // Simulate a connection reset once the connection has been authenticated
199 // Detects bug 486508
201 function connectionReset(metadata, response) {
202   let authorization;
203   let authPrefix;
204   switch (requestsMade) {
205     case 0:
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);
209       break;
210     case 1:
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");
215       ntlmTypeOneCount++;
216       response.setStatusLine(metadata.httpVersion, 407, "Unauthorized");
217       response.setHeader("Proxy-Authenticate", PROXY_CHALLENGE, false);
218       break;
219     case 2:
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");
223       ntlmTypeTwoCount++;
224       try {
225         response.seizePower();
226         response.finish();
227       } catch (e) {
228         Assert.ok(false, "unexpected exception" + e);
229       }
230       break;
231     default:
232       // Should not get any further requests on this channel
233       dump("ERROR: NTLM Proxy Authentication, connection should not be reused");
234       Assert.ok(false);
235   }
236   requestsMade++;
240 // Reset the connection after a negotiate message has been received
242 function connectionReset02(metadata, response) {
243   var connectionNumber = httpserver.connectionNumber;
244   switch (requestsMade) {
245     case 0:
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);
250       break;
251     case 1:
252     // eslint-disable-next-line no-fallthrough
253     default:
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");
259       ntlmTypeOneCount++;
260       try {
261         response.seizePower();
262         response.finish();
263       } catch (e) {
264         Assert.ok(false, "unexpected exception" + e);
265       }
266   }
267   requestsMade++;
270 var httpserver = null;
271 function setup() {
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
279   );
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();
292   });
294 setup();
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
302 // Common test setup
303 // Parameters:
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) {
310   requestsMade = 0;
311   expectedRequests = requests;
312   expectedResponse = response;
314   // clear the auth cache if requested
315   if (clearCache) {
316     dump("Clearing auth cache");
317     Cc["@mozilla.org/network/http-auth-manager;1"]
318       .getService(Ci.nsIHttpAuthManager)
319       .clearAll();
320   }
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));
327   });
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
335 // Happy code path
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",
370     maxRetryAttempt
371   );
373   await setupTest("/auth", connectionReset02, maxRetryAttempt + 1, 500, 1);
376 add_task(
377   { pref_set: [["network.auth.use_redirect_for_retries", false]] },
378   test_happy_path
380 add_task(
381   { pref_set: [["network.auth.use_redirect_for_retries", false]] },
382   test_failed_auth
384 add_task(
385   { pref_set: [["network.auth.use_redirect_for_retries", false]] },
386   test_connection_reset
388 add_task(
389   { pref_set: [["network.auth.use_redirect_for_retries", false]] },
390   test_connection_reset02
393 add_task(
394   { pref_set: [["network.auth.use_redirect_for_retries", true]] },
395   test_happy_path
397 add_task(
398   { pref_set: [["network.auth.use_redirect_for_retries", true]] },
399   test_failed_auth
401 add_task(
402   { pref_set: [["network.auth.use_redirect_for_retries", true]] },
403   test_connection_reset
405 add_task(
406   { pref_set: [["network.auth.use_redirect_for_retries", true]] },
407   test_connection_reset02