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() {
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
) {
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 () {
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