Backed out changeset b71c8c052463 (bug 1943846) for causing mass failures. CLOSED...
[gecko.git] / netwerk / test / unit / test_ntlm_proxy_auth.js
blob194050c0279b626093f7e052469171b99cc9f942
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;
32 asyncPromptAuth: function ap_async() {
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
45 if (!this.prompt) {
46 this.prompt = new AuthPrompt();
48 return this.prompt;
51 throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
54 prompt: null,
57 function makeChan(url, loadingUrl) {
58 var principal = Services.scriptSecurityManager.createContentPrincipal(
59 Services.io.newURI(loadingUrl),
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) {
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");
80 Assert.equal(expectedResponse, request.responseStatus, "HTTP Status code");
82 TestListener.prototype.onStopRequest = function () {
83 Assert.equal(expectedRequests, requestsMade, "Number of requests made ");
84 Assert.equal(
85 exptTypeOneCount,
86 ntlmTypeOneCount,
87 "Number of type one messages received"
89 Assert.equal(
90 exptTypeTwoCount,
91 ntlmTypeTwoCount,
92 "Number of type two messages received"
95 this.resolve();
97 TestListener.prototype.onDataAvaiable = function (
98 request,
99 context,
100 stream,
101 offset,
102 count
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");
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");
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);
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);
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);
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
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();
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();
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
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
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