test_auth_proxy.js (12521B)
1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 /** 7 * This tests the automatic login to the proxy with password, 8 * if the password is stored and the browser is restarted. 9 * 10 * <copied from="test_authentication.js"/> 11 */ 12 13 "use strict"; 14 15 const { HttpServer } = ChromeUtils.importESModule( 16 "resource://testing-common/httpd.sys.mjs" 17 ); 18 19 const FLAG_RETURN_FALSE = 1 << 0; 20 const FLAG_WRONG_PASSWORD = 1 << 1; 21 const FLAG_PREVIOUS_FAILED = 1 << 2; 22 23 function AuthPrompt2(proxyFlags, hostFlags) { 24 this.proxyCred.flags = proxyFlags; 25 this.hostCred.flags = hostFlags; 26 } 27 AuthPrompt2.prototype = { 28 proxyCred: { 29 user: "proxy", 30 pass: "guest", 31 realmExpected: "intern", 32 flags: 0, 33 }, 34 hostCred: { user: "host", pass: "guest", realmExpected: "extern", flags: 0 }, 35 36 QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt2"]), 37 38 promptAuth: function ap2_promptAuth(channel, encryptionLevel, authInfo) { 39 try { 40 // never HOST and PROXY set at the same time in prompt 41 Assert.equal( 42 (authInfo.flags & Ci.nsIAuthInformation.AUTH_HOST) != 0, 43 (authInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) == 0 44 ); 45 46 var isProxy = (authInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) != 0; 47 var cred = isProxy ? this.proxyCred : this.hostCred; 48 49 dump( 50 "with flags: " + 51 ((cred.flags & FLAG_WRONG_PASSWORD) != 0 ? "wrong password" : "") + 52 " " + 53 ((cred.flags & FLAG_PREVIOUS_FAILED) != 0 ? "previous failed" : "") + 54 " " + 55 ((cred.flags & FLAG_RETURN_FALSE) != 0 ? "return false" : "") + 56 "\n" 57 ); 58 59 // PROXY properly set by necko (checked using realm) 60 Assert.equal(cred.realmExpected, authInfo.realm); 61 62 // PREVIOUS_FAILED properly set by necko 63 Assert.equal( 64 (cred.flags & FLAG_PREVIOUS_FAILED) != 0, 65 (authInfo.flags & Ci.nsIAuthInformation.PREVIOUS_FAILED) != 0 66 ); 67 68 if (cred.flags & FLAG_RETURN_FALSE) { 69 cred.flags |= FLAG_PREVIOUS_FAILED; 70 cred.flags &= ~FLAG_RETURN_FALSE; 71 return false; 72 } 73 74 authInfo.username = cred.user; 75 if (cred.flags & FLAG_WRONG_PASSWORD) { 76 authInfo.password = cred.pass + ".wrong"; 77 cred.flags |= FLAG_PREVIOUS_FAILED; 78 // Now clear the flag to avoid an infinite loop 79 cred.flags &= ~FLAG_WRONG_PASSWORD; 80 } else { 81 authInfo.password = cred.pass; 82 cred.flags &= ~FLAG_PREVIOUS_FAILED; 83 } 84 } catch (e) { 85 do_throw(e); 86 } 87 return true; 88 }, 89 90 asyncPromptAuth: function ap2_async( 91 channel, 92 callback, 93 context, 94 encryptionLevel, 95 authInfo 96 ) { 97 var me = this; 98 var allOverAndDead = false; 99 executeSoon(function () { 100 try { 101 if (allOverAndDead) { 102 throw new Error("already canceled"); 103 } 104 var ret = me.promptAuth(channel, encryptionLevel, authInfo); 105 if (!ret) { 106 callback.onAuthCancelled(context, true); 107 } else { 108 callback.onAuthAvailable(context, authInfo); 109 } 110 allOverAndDead = true; 111 } catch (e) { 112 do_throw(e); 113 } 114 }); 115 return new Cancelable(function () { 116 if (allOverAndDead) { 117 throw new Error("can't cancel, already ran"); 118 } 119 callback.onAuthAvailable(context, authInfo); 120 allOverAndDead = true; 121 }); 122 }, 123 }; 124 125 function Cancelable(onCancelFunc) { 126 this.onCancelFunc = onCancelFunc; 127 } 128 Cancelable.prototype = { 129 QueryInterface: ChromeUtils.generateQI(["nsICancelable"]), 130 cancel: function cancel() { 131 try { 132 this.onCancelFunc(); 133 } catch (e) { 134 do_throw(e); 135 } 136 }, 137 }; 138 139 function Requestor(proxyFlags, hostFlags) { 140 this.proxyFlags = proxyFlags; 141 this.hostFlags = hostFlags; 142 } 143 Requestor.prototype = { 144 QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]), 145 146 getInterface: function requestor_gi(iid) { 147 if (iid.equals(Ci.nsIAuthPrompt)) { 148 dump("authprompt1 not implemented\n"); 149 throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE); 150 } 151 if (iid.equals(Ci.nsIAuthPrompt2)) { 152 try { 153 // Allow the prompt to store state by caching it here 154 if (!this.prompt2) { 155 this.prompt2 = new AuthPrompt2(this.proxyFlags, this.hostFlags); 156 } 157 return this.prompt2; 158 } catch (e) { 159 do_throw(e); 160 } 161 } 162 throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE); 163 }, 164 165 prompt2: null, 166 }; 167 168 var listener = { 169 expectedCode: -1, // uninitialized 170 171 onStartRequest: function test_onStartR(request) { 172 try { 173 // Proxy auth cancellation return failures to avoid spoofing 174 if ( 175 !Components.isSuccessCode(request.status) && 176 this.expectedCode != 407 177 ) { 178 do_throw("Channel should have a success code!"); 179 } 180 181 if (!(request instanceof Ci.nsIHttpChannel)) { 182 do_throw("Expecting an HTTP channel"); 183 } 184 185 Assert.equal(this.expectedCode, request.responseStatus); 186 // If we expect 200, the request should have succeeded 187 Assert.equal(this.expectedCode == 200, request.requestSucceeded); 188 189 var cookie = ""; 190 try { 191 cookie = request.getRequestHeader("Cookie"); 192 } catch (e) {} 193 Assert.equal(cookie, ""); 194 } catch (e) { 195 do_throw("Unexpected exception: " + e); 196 } 197 198 throw Components.Exception("", Cr.NS_ERROR_ABORT); 199 }, 200 201 onDataAvailable: function test_ODA() { 202 do_throw("Should not get any data!"); 203 }, 204 205 onStopRequest: function test_onStopR(request, status) { 206 Assert.equal(status, Cr.NS_ERROR_ABORT); 207 208 if (current_test < tests.length - 1) { 209 // First, need to clear the auth cache 210 Cc["@mozilla.org/network/http-auth-manager;1"] 211 .getService(Ci.nsIHttpAuthManager) 212 .clearAll(); 213 214 current_test++; 215 tests[current_test](); 216 } else { 217 do_test_pending(); 218 httpserv.stop(do_test_finished); 219 } 220 221 do_test_finished(); 222 }, 223 }; 224 225 function makeChan(url) { 226 if (!url) { 227 url = "http://somesite/"; 228 } 229 230 return NetUtil.newChannel({ 231 uri: url, 232 loadUsingSystemPrincipal: true, 233 }).QueryInterface(Ci.nsIHttpChannel); 234 } 235 236 var current_test = 0; 237 var httpserv = null; 238 239 function run_test() { 240 httpserv = new HttpServer(); 241 httpserv.registerPathHandler("/", proxyAuthHandler); 242 httpserv.identity.add("http", "somesite", 80); 243 httpserv.start(-1); 244 245 Services.prefs.setCharPref("network.proxy.http", "localhost"); 246 Services.prefs.setIntPref( 247 "network.proxy.http_port", 248 httpserv.identity.primaryPort 249 ); 250 Services.prefs.setCharPref("network.proxy.no_proxies_on", ""); 251 Services.prefs.setIntPref("network.proxy.type", 1); 252 253 // Turn off the authentication dialog blocking for this test. 254 Services.prefs.setIntPref("network.auth.subresource-http-auth-allow", 2); 255 Services.prefs.setBoolPref( 256 "network.auth.non-web-content-triggered-resources-http-auth-allow", 257 true 258 ); 259 260 registerCleanupFunction(() => { 261 Services.prefs.clearUserPref("network.proxy.http"); 262 Services.prefs.clearUserPref("network.proxy.http_port"); 263 Services.prefs.clearUserPref("network.proxy.no_proxies_on"); 264 Services.prefs.clearUserPref("network.proxy.type"); 265 Services.prefs.clearUserPref("network.auth.subresource-http-auth-allow"); 266 Services.prefs.clearUserPref( 267 "network.auth.non-web-content-triggered-resources-http-auth-allow" 268 ); 269 }); 270 271 tests[current_test](); 272 } 273 274 function test_proxy_returnfalse() { 275 dump("\ntest: proxy returnfalse\n"); 276 var chan = makeChan(); 277 chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 0); 278 listener.expectedCode = 407; // Proxy Unauthorized 279 chan.asyncOpen(listener); 280 281 do_test_pending(); 282 } 283 284 function test_proxy_wrongpw() { 285 dump("\ntest: proxy wrongpw\n"); 286 var chan = makeChan(); 287 chan.notificationCallbacks = new Requestor(FLAG_WRONG_PASSWORD, 0); 288 listener.expectedCode = 200; // Eventually OK 289 chan.asyncOpen(listener); 290 do_test_pending(); 291 } 292 293 function test_all_ok() { 294 dump("\ntest: all ok\n"); 295 var chan = makeChan(); 296 chan.notificationCallbacks = new Requestor(0, 0); 297 listener.expectedCode = 200; // OK 298 chan.asyncOpen(listener); 299 do_test_pending(); 300 } 301 302 function test_proxy_407_cookie() { 303 var chan = makeChan(); 304 chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 0); 305 chan.setRequestHeader("X-Set-407-Cookie", "1", false); 306 listener.expectedCode = 407; // Proxy Unauthorized 307 chan.asyncOpen(listener); 308 309 do_test_pending(); 310 } 311 312 function test_proxy_200_cookie() { 313 var chan = makeChan(); 314 chan.notificationCallbacks = new Requestor(0, 0); 315 chan.setRequestHeader("X-Set-407-Cookie", "1", false); 316 listener.expectedCode = 200; // OK 317 chan.asyncOpen(listener); 318 do_test_pending(); 319 } 320 321 function test_host_returnfalse() { 322 dump("\ntest: host returnfalse\n"); 323 var chan = makeChan(); 324 chan.notificationCallbacks = new Requestor(0, FLAG_RETURN_FALSE); 325 listener.expectedCode = 401; // Host Unauthorized 326 chan.asyncOpen(listener); 327 328 do_test_pending(); 329 } 330 331 function test_host_wrongpw() { 332 dump("\ntest: host wrongpw\n"); 333 var chan = makeChan(); 334 chan.notificationCallbacks = new Requestor(0, FLAG_WRONG_PASSWORD); 335 listener.expectedCode = 200; // Eventually OK 336 chan.asyncOpen(listener); 337 do_test_pending(); 338 } 339 340 function test_proxy_wrongpw_host_wrongpw() { 341 dump("\ntest: proxy wrongpw, host wrongpw\n"); 342 var chan = makeChan(); 343 chan.notificationCallbacks = new Requestor( 344 FLAG_WRONG_PASSWORD, 345 FLAG_WRONG_PASSWORD 346 ); 347 listener.expectedCode = 200; // OK 348 chan.asyncOpen(listener); 349 do_test_pending(); 350 } 351 352 function test_proxy_wrongpw_host_returnfalse() { 353 dump("\ntest: proxy wrongpw, host return false\n"); 354 var chan = makeChan(); 355 chan.notificationCallbacks = new Requestor( 356 FLAG_WRONG_PASSWORD, 357 FLAG_RETURN_FALSE 358 ); 359 listener.expectedCode = 401; // Host Unauthorized 360 chan.asyncOpen(listener); 361 do_test_pending(); 362 } 363 364 var tests = [ 365 test_proxy_returnfalse, 366 test_proxy_wrongpw, 367 test_all_ok, 368 test_proxy_407_cookie, 369 test_proxy_200_cookie, 370 test_host_returnfalse, 371 test_host_wrongpw, 372 test_proxy_wrongpw_host_wrongpw, 373 test_proxy_wrongpw_host_returnfalse, 374 ]; 375 376 // PATH HANDLERS 377 378 // Proxy 379 function proxyAuthHandler(metadata, response) { 380 try { 381 var realm = "intern"; 382 // btoa("proxy:guest"), but that function is not available here 383 var expectedHeader = "Basic cHJveHk6Z3Vlc3Q="; 384 385 var body; 386 if ( 387 metadata.hasHeader("Proxy-Authorization") && 388 metadata.getHeader("Proxy-Authorization") == expectedHeader 389 ) { 390 dump("proxy password ok\n"); 391 response.setHeader( 392 "Proxy-Authenticate", 393 'Basic realm="' + realm + '"', 394 false 395 ); 396 397 hostAuthHandler(metadata, response); 398 } else { 399 dump("proxy password required\n"); 400 response.setStatusLine( 401 metadata.httpVersion, 402 407, 403 "Unauthorized by HTTP proxy" 404 ); 405 response.setHeader( 406 "Proxy-Authenticate", 407 'Basic realm="' + realm + '"', 408 false 409 ); 410 if (metadata.hasHeader("X-Set-407-Cookie")) { 411 response.setHeader("Set-Cookie", "chewy", false); 412 } 413 body = "failed"; 414 response.bodyOutputStream.write(body, body.length); 415 } 416 } catch (e) { 417 do_throw(e); 418 } 419 } 420 421 // Host /auth 422 function hostAuthHandler(metadata, response) { 423 try { 424 var realm = "extern"; 425 // btoa("host:guest"), but that function is not available here 426 var expectedHeader = "Basic aG9zdDpndWVzdA=="; 427 428 var body; 429 if ( 430 metadata.hasHeader("Authorization") && 431 metadata.getHeader("Authorization") == expectedHeader 432 ) { 433 dump("host password ok\n"); 434 response.setStatusLine( 435 metadata.httpVersion, 436 200, 437 "OK, authorized for host" 438 ); 439 response.setHeader( 440 "WWW-Authenticate", 441 'Basic realm="' + realm + '"', 442 false 443 ); 444 body = "success"; 445 } else { 446 dump("host password required\n"); 447 response.setStatusLine( 448 metadata.httpVersion, 449 401, 450 "Unauthorized by HTTP server host" 451 ); 452 response.setHeader( 453 "WWW-Authenticate", 454 'Basic realm="' + realm + '"', 455 false 456 ); 457 body = "failed"; 458 } 459 response.bodyOutputStream.write(body, body.length); 460 } catch (e) { 461 do_throw(e); 462 } 463 }