tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }