1 // Unit tests for a NTLM authenticated proxy, proxying for a NTLM authenticated
4 // Currently the tests do not determine whether the Authentication dialogs have
10 const { HttpServer
} = ChromeUtils
.importESModule(
11 "resource://testing-common/httpd.sys.mjs"
14 ChromeUtils
.defineLazyGetter(this, "URL", function () {
15 return "http://localhost:" + httpserver
.identity
.primaryPort
;
18 function AuthPrompt() {}
20 AuthPrompt
.prototype = {
24 QueryInterface
: ChromeUtils
.generateQI(["nsIAuthPrompt2"]),
26 promptAuth
: function ap_promptAuth(channel
, level
, authInfo
) {
27 authInfo
.username
= this.user
;
28 authInfo
.password
= this.pass
;
33 asyncPromptAuth
: function ap_async() {
34 throw Components
.Exception("", Cr
.NS_ERROR_NOT_IMPLEMENTED
);
38 function Requestor() {}
40 Requestor
.prototype = {
41 QueryInterface
: ChromeUtils
.generateQI(["nsIInterfaceRequestor"]),
43 getInterface
: function requestor_gi(iid
) {
44 if (iid
.equals(Ci
.nsIAuthPrompt2
)) {
45 // Allow the prompt to store state by caching it here
47 this.prompt
= new AuthPrompt();
52 throw Components
.Exception("", Cr
.NS_ERROR_NO_INTERFACE
);
58 function makeChan(url
, loadingUrl
) {
59 var principal
= Services
.scriptSecurityManager
.createContentPrincipal(
60 Services
.io
.newURI(loadingUrl
),
63 return NetUtil
.newChannel({
65 loadingPrincipal
: principal
,
66 securityFlags
: Ci
.nsILoadInfo
.SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT
,
67 contentPolicyType
: Ci
.nsIContentPolicy
.TYPE_OTHER
,
71 function TestListener(resolve
) {
72 this.resolve
= resolve
;
74 TestListener
.prototype.onStartRequest = function (request
) {
75 // Need to do the instanceof to allow request.responseStatus
77 if (!(request
instanceof Ci
.nsIHttpChannel
)) {
78 dump("Expecting an HTTP channel");
81 Assert
.equal(expectedResponse
, request
.responseStatus
, "HTTP Status code");
83 TestListener
.prototype.onStopRequest = function () {
84 Assert
.equal(expectedRequests
, requestsMade
, "Number of requests made ");
88 TestListener
.prototype.onDataAvaiable = function (
95 read_stream(stream
, count
);
98 // NTLM Messages, for the received type 1 and 3 messages only check that they
99 // are of the expected type.
100 const NTLM_TYPE1_PREFIX
= "NTLM TlRMTVNTUAABAAAA";
101 const NTLM_TYPE2_PREFIX
= "NTLM TlRMTVNTUAACAAAA";
102 const NTLM_TYPE3_PREFIX
= "NTLM TlRMTVNTUAADAAAA";
103 const NTLM_PREFIX_LEN
= 21;
105 const NTLM_CHALLENGE
=
107 "DAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAAR" +
108 "ABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUg" +
109 "BWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwB" +
110 "lAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA=";
112 const PROXY_CHALLENGE
=
114 "DgAOADgAAAAFgooCqLNOPe2aZOAAAAAAAAAAAFAAUABGAAAA" +
115 "BgEAAAAAAA9HAFcATAAtAE0ATwBaAAIADgBHAFcATAAtAE0A" +
116 "TwBaAAEADgBHAFcATAAtAE0ATwBaAAQAAgAAAAMAEgBsAG8A" +
117 "YwBhAGwAaABvAHMAdAAHAAgAOKEwGEZL0gEAAAAA";
119 // Proxy and Web server responses for the happy path scenario.
120 // i.e. successful proxy auth and successful web server auth
122 function authHandler(metadata
, response
) {
125 switch (requestsMade
) {
127 // Proxy - First request to the Proxy resppond with a 407 to start auth
128 response
.setStatusLine(metadata
.httpVersion
, 407, "Unauthorized");
129 response
.setHeader("Proxy-Authenticate", "NTLM", false);
132 // Proxy - Expecting a type 1 negotiate message from the client
133 authorization
= metadata
.getHeader("Proxy-Authorization");
134 authPrefix
= authorization
.substring(0, NTLM_PREFIX_LEN
);
135 Assert
.equal(NTLM_TYPE1_PREFIX
, authPrefix
, "Expecting a Type 1 message");
136 response
.setStatusLine(metadata
.httpVersion
, 407, "Unauthorized");
137 response
.setHeader("Proxy-Authenticate", PROXY_CHALLENGE
, false);
140 // Proxy - Expecting a type 3 Authenticate message from the client
141 // Will respond with a 401 to start web server auth sequence
142 authorization
= metadata
.getHeader("Proxy-Authorization");
143 authPrefix
= authorization
.substring(0, NTLM_PREFIX_LEN
);
144 Assert
.equal(NTLM_TYPE3_PREFIX
, authPrefix
, "Expecting a Type 3 message");
145 response
.setStatusLine(metadata
.httpVersion
, 401, "Unauthorized");
146 response
.setHeader("WWW-Authenticate", "NTLM", false);
149 // Web Server - Expecting a type 1 negotiate message from the client
150 authorization
= metadata
.getHeader("Authorization");
151 authPrefix
= authorization
.substring(0, NTLM_PREFIX_LEN
);
152 Assert
.equal(NTLM_TYPE1_PREFIX
, authPrefix
, "Expecting a Type 1 message");
153 response
.setStatusLine(metadata
.httpVersion
, 401, "Unauthorized");
154 response
.setHeader("WWW-Authenticate", NTLM_CHALLENGE
, false);
157 // Web Server - Expecting a type 3 Authenticate message from the client
158 authorization
= metadata
.getHeader("Authorization");
159 authPrefix
= authorization
.substring(0, NTLM_PREFIX_LEN
);
160 Assert
.equal(NTLM_TYPE3_PREFIX
, authPrefix
, "Expecting a Type 3 message");
161 response
.setStatusLine(metadata
.httpVersion
, 200, "Successful");
164 // We should be authenticated and further requests are permitted
165 authorization
= metadata
.getHeader("Authorization");
166 authorization
= metadata
.getHeader("Proxy-Authorization");
167 Assert
.isnull(authorization
);
168 response
.setStatusLine(metadata
.httpVersion
, 200, "Successful");
173 // Proxy responses simulating an invalid proxy password
174 // Note: that the connection should not be reused after the
177 function authHandlerInvalidProxyPassword(metadata
, response
) {
180 switch (requestsMade
) {
182 // Proxy - First request respond with a 407 to initiate auth sequence
183 response
.setStatusLine(metadata
.httpVersion
, 407, "Unauthorized");
184 response
.setHeader("Proxy-Authenticate", "NTLM", false);
187 // Proxy - Expecting a type 1 negotiate message from the client
188 authorization
= metadata
.getHeader("Proxy-Authorization");
189 authPrefix
= authorization
.substring(0, NTLM_PREFIX_LEN
);
190 Assert
.equal(NTLM_TYPE1_PREFIX
, authPrefix
, "Expecting a Type 1 message");
191 response
.setStatusLine(metadata
.httpVersion
, 407, "Unauthorized");
192 response
.setHeader("Proxy-Authenticate", PROXY_CHALLENGE
, false);
195 // Proxy - Expecting a type 3 Authenticate message from the client
196 // Respond with a 407 to indicate invalid credentials
198 authorization
= metadata
.getHeader("Proxy-Authorization");
199 authPrefix
= authorization
.substring(0, NTLM_PREFIX_LEN
);
200 Assert
.equal(NTLM_TYPE3_PREFIX
, authPrefix
, "Expecting a Type 3 message");
201 response
.setStatusLine(metadata
.httpVersion
, 407, "Unauthorized");
202 response
.setHeader("Proxy-Authenticate", "NTLM", false);
205 // Strictly speaking the connection should not be reused at this point
206 // and reaching here should be an error, but have commented out for now
207 //dump( "ERROR: NTLM Proxy Authentication, connection should not be reused");
209 response
.setStatusLine(metadata
.httpVersion
, 407, "Unauthorized");
214 // Proxy and web server responses simulating a successful Proxy auth
215 // and a failed web server auth
216 // Note: the connection should not be reused once the password failure is
218 function authHandlerInvalidWebPassword(metadata
, response
) {
221 switch (requestsMade
) {
223 // Proxy - First request return a 407 to start Proxy auth
224 response
.setStatusLine(metadata
.httpVersion
, 407, "Unauthorized");
225 response
.setHeader("Proxy-Authenticate", "NTLM", false);
228 // Proxy - Expecting a type 1 negotiate message from the client
229 authorization
= metadata
.getHeader("Proxy-Authorization");
230 authPrefix
= authorization
.substring(0, NTLM_PREFIX_LEN
);
231 Assert
.equal(NTLM_TYPE1_PREFIX
, authPrefix
, "Expecting a Type 1 message");
232 response
.setStatusLine(metadata
.httpVersion
, 407, "Unauthorized");
233 response
.setHeader("Proxy-Authenticate", NTLM_CHALLENGE
, false);
236 // Proxy - Expecting a type 3 Authenticate message from the client
237 // Responds with a 401 to start web server auth
238 authorization
= metadata
.getHeader("Proxy-Authorization");
239 authPrefix
= authorization
.substring(0, NTLM_PREFIX_LEN
);
240 Assert
.equal(NTLM_TYPE3_PREFIX
, authPrefix
, "Expecting a Type 3 message");
241 response
.setStatusLine(metadata
.httpVersion
, 401, "Unauthorized");
242 response
.setHeader("WWW-Authenticate", "NTLM", false);
245 // Web Server - Expecting a type 1 negotiate message from the client
246 authorization
= metadata
.getHeader("Authorization");
247 authPrefix
= authorization
.substring(0, NTLM_PREFIX_LEN
);
248 Assert
.equal(NTLM_TYPE1_PREFIX
, authPrefix
, "Expecting a Type 1 message");
249 response
.setStatusLine(metadata
.httpVersion
, 401, "Unauthorized");
250 response
.setHeader("WWW-Authenticate", NTLM_CHALLENGE
, false);
253 // Web Server - Expecting a type 3 Authenticate message from the client
254 // Respond with a 401 to restart the auth sequence.
255 authorization
= metadata
.getHeader("Authorization");
256 authPrefix
= authorization
.substring(0, NTLM_PREFIX_LEN
);
257 Assert
.equal(NTLM_TYPE3_PREFIX
, authPrefix
, "Expecting a Type 1 message");
258 response
.setStatusLine(metadata
.httpVersion
, 401, "Unauthorized");
261 // We should not get called past step 4
264 "ERROR: NTLM Auth failed connection should not be reused"
270 // Tests to run test_bad_proxy_pass and test_bad_web_pass are split into two stages
271 // so that once we determine how detect password dialog displays we can check
272 // that the are displayed correctly, i.e. proxy password should not be prompted
273 // for when retrying the web server password
275 var httpserver
= null;
277 httpserver
= new HttpServer();
278 httpserver
.start(-1);
280 Services
.prefs
.setCharPref("network.proxy.http", "localhost");
281 Services
.prefs
.setIntPref(
282 "network.proxy.http_port",
283 httpserver
.identity
.primaryPort
285 Services
.prefs
.setCharPref("network.proxy.no_proxies_on", "");
286 Services
.prefs
.setIntPref("network.proxy.type", 1);
287 Services
.prefs
.setBoolPref("network.proxy.allow_hijacking_localhost", true);
289 registerCleanupFunction(async () => {
290 Services
.prefs
.clearUserPref("network.proxy.http");
291 Services
.prefs
.clearUserPref("network.proxy.http_port");
292 Services
.prefs
.clearUserPref("network.proxy.no_proxies_on");
293 Services
.prefs
.clearUserPref("network.proxy.type");
294 Services
.prefs
.clearUserPref("network.proxy.allow_hijacking_localhost");
296 await httpserver
.stop();
301 var expectedRequests
= 0; // Number of HTTP requests that are expected
302 var requestsMade
= 0; // The number of requests that were made
303 var expectedResponse
= 0; // The response code
304 // Note that any test failures in the HTTP handler
305 // will manifest as a 500 response code
309 // path - path component of the URL
310 // handler - http handler function for the httpserver
311 // requests - expected number oh http requests
312 // response - expected http response code
313 // clearCache - clear the authentication cache before running the test
314 function setupTest(path
, handler
, requests
, response
, clearCache
) {
316 expectedRequests
= requests
;
317 expectedResponse
= response
;
319 // clear the auth cache if requested
321 dump("Clearing auth cache");
322 Cc
["@mozilla.org/network/http-auth-manager;1"]
323 .getService(Ci
.nsIHttpAuthManager
)
327 return new Promise(resolve
=> {
328 var chan
= makeChan(URL
+ path
, URL
);
329 httpserver
.registerPathHandler(path
, handler
);
330 chan
.notificationCallbacks
= new Requestor();
331 chan
.asyncOpen(new TestListener(resolve
));
336 // Succesful proxy and web server auth.
337 add_task(async
function test_happy_path() {
338 dump("RUNNING TEST: test_happy_path");
339 await
setupTest("/auth", authHandler
, 5, 200, 1);
342 // Failed proxy authentication
343 add_task(async
function test_bad_proxy_pass_stage01() {
344 dump("RUNNING TEST: test_bad_proxy_pass_stage01");
345 await
setupTest("/auth", authHandlerInvalidProxyPassword
, 4, 407, 1);
347 // Successful logon after failed proxy auth
348 add_task(async
function test_bad_proxy_pass_stage02() {
349 dump("RUNNING TEST: test_bad_proxy_pass_stage02");
350 await
setupTest("/auth", authHandler
, 5, 200, 0);
353 // successful proxy logon, unsuccessful web server sign on
354 add_task(async
function test_bad_web_pass_stage01() {
355 dump("RUNNING TEST: test_bad_web_pass_stage01");
356 await
setupTest("/auth", authHandlerInvalidWebPassword
, 5, 401, 1);
358 // successful logon after failed web server auth.
359 add_task(async
function test_bad_web_pass_stage02() {
360 dump("RUNNING TEST: test_bad_web_pass_stage02");
361 await
setupTest("/auth", authHandler
, 5, 200, 0);