tor-browser

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

browser_loginStatus.js (15883B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 XPCOMUtils.defineLazyServiceGetter(
      8  this,
      9  "IdentityCredentialStorageService",
     10  "@mozilla.org/browser/identity-credential-storage-service;1",
     11  Ci.nsIIdentityCredentialStorageService
     12 );
     13 
     14 const TEST_URL = "https://example.com/";
     15 const TEST_XORIGIN_URL = "https://example.net/";
     16 const TEST_LOGIN_STATUS_BASE =
     17  TEST_URL +
     18  "browser/dom/credentialmanagement/identity/tests/browser/server_loginStatus.sjs?status=";
     19 const TEST_LOGIN_STATUS_XORIGIN_BASE =
     20  TEST_XORIGIN_URL +
     21  "browser/dom/credentialmanagement/identity/tests/browser/server_loginStatus.sjs?status=";
     22 
     23 /**
     24 * Perform a test with a function that should change a page's login status.
     25 *
     26 * This function opens a new foreground tab, then calls stepFn with two arguments:
     27 * the Browser of the new tab and the value that should be set as the login status.
     28 * This repeats for various values of the header, making sure the status is correct
     29 * after each instance.
     30 *
     31 * @param {Function(Browser, string) => Promise} stepFn - The function to update login status,
     32 * @param {string} - An optional description describing the test case, used in assertion descriptions.
     33 */
     34 async function login_logout_sequence(stepFn, desc = "") {
     35  // Open a test page, get its principal
     36  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
     37  const principal = gBrowser.contentPrincipal;
     38 
     39  // Make sure we don't have a starting permission
     40  let permission = Services.perms.testPermissionFromPrincipal(
     41    principal,
     42    "self-reported-logged-in"
     43  );
     44  Assert.equal(
     45    permission,
     46    Services.perms.UNKNOWN_ACTION,
     47    "Permission correctly not initialized in test of " + desc
     48  );
     49 
     50  // Try using a bad value for the argument
     51  await stepFn(tab.linkedBrowser, "should-reject");
     52  permission = Services.perms.testPermissionFromPrincipal(
     53    principal,
     54    "self-reported-logged-in"
     55  );
     56  Assert.equal(
     57    permission,
     58    Services.perms.UNKNOWN_ACTION,
     59    "Permission not altered by bad enum value in test of " + desc
     60  );
     61 
     62  // Try logging in
     63  await stepFn(tab.linkedBrowser, "logged-in");
     64  permission = Services.perms.testPermissionFromPrincipal(
     65    principal,
     66    "self-reported-logged-in"
     67  );
     68  Assert.equal(
     69    permission,
     70    Services.perms.ALLOW_ACTION,
     71    "Permission stored correcty for `logged-in` in test of " + desc
     72  );
     73 
     74  // Try logging out
     75  await stepFn(tab.linkedBrowser, "logged-out");
     76  permission = Services.perms.testPermissionFromPrincipal(
     77    principal,
     78    "self-reported-logged-in"
     79  );
     80  Assert.equal(
     81    permission,
     82    Services.perms.DENY_ACTION,
     83    "Permission stored correcty for `logged-out` in test of " + desc
     84  );
     85 
     86  // Try using a bad value for the argument, after it's already been set
     87  await stepFn(tab.linkedBrowser, "should-reject");
     88  permission = Services.perms.testPermissionFromPrincipal(
     89    principal,
     90    "self-reported-logged-in"
     91  );
     92  Assert.equal(
     93    permission,
     94    Services.perms.DENY_ACTION,
     95    "Permission not altered by bad enum value in test of " + desc
     96  );
     97 
     98  // Try logging in again
     99  await stepFn(tab.linkedBrowser, "logged-in");
    100  permission = Services.perms.testPermissionFromPrincipal(
    101    principal,
    102    "self-reported-logged-in"
    103  );
    104  Assert.equal(
    105    permission,
    106    Services.perms.ALLOW_ACTION,
    107    "Permission stored correcty for `logged-in` in test of " + desc
    108  );
    109 
    110  // Make sure we don't have any extra permissinons laying about
    111  let permissions = Services.perms.getAllByTypes(["self-reported-logged-in"]);
    112  Assert.equal(
    113    permissions.length,
    114    1,
    115    "One permission must be left after all modifications in test of " + desc
    116  );
    117 
    118  // Clear the permission
    119  Services.perms.removeByType("self-reported-logged-in");
    120 
    121  // Close tabs.
    122  await BrowserTestUtils.removeTab(tab);
    123 }
    124 
    125 /**
    126 * Perform a test with a function that should NOT change a page's login status.
    127 *
    128 * This function opens a new foreground tab, then calls stepFn with two arguments:
    129 * the Browser of the new tab and the value that should be set as the login status.
    130 * Then it makes sure that no permission has been set for the login status.
    131 *
    132 * @param {Function(Browser, string) => Promise} stepFn - The function to update login status,
    133 * @param {string} - An optional description describing the test case, used in assertion descriptions.
    134 */
    135 async function login_doesnt_work(stepFn, desc = "") {
    136  // Open a test page, get its principal
    137  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
    138  const principal = gBrowser.contentPrincipal;
    139 
    140  // Make sure we don't have a starting permission
    141  let permission = Services.perms.testPermissionFromPrincipal(
    142    principal,
    143    "self-reported-logged-in"
    144  );
    145  Assert.equal(
    146    permission,
    147    Services.perms.UNKNOWN_ACTION,
    148    "Permission correctly not initialized in test of " + desc
    149  );
    150 
    151  // Try logging in
    152  await stepFn(tab.linkedBrowser, "logged-in");
    153  permission = Services.perms.testPermissionFromPrincipal(
    154    principal,
    155    "self-reported-logged-in"
    156  );
    157  Assert.equal(
    158    permission,
    159    Services.perms.UNKNOWN_ACTION,
    160    "Permission not set for `logged-in` in test of " + desc
    161  );
    162 
    163  // Make sure we don't have any extra permissinons laying about
    164  let permissions = Services.perms.getAllByTypes(["self-reported-logged-in"]);
    165  Assert.equal(permissions.length, 0, "No permission set in test of " + desc);
    166 
    167  // Clear the permission
    168  Services.perms.removeByType("self-reported-logged-in");
    169 
    170  // Close tabs.
    171  await BrowserTestUtils.removeTab(tab);
    172 }
    173 
    174 // Function that we can use to set the status and return with a string for the different possible outcomes
    175 async function setStatusInContent(status) {
    176  try {
    177    let result = await content.navigator.login.setStatus(status);
    178    if (result === undefined) {
    179      return "resolved with undefined";
    180    }
    181    return "resolved with defined";
    182  } catch (err) {
    183    if (err.name == "TypeError") {
    184      return "rejected with TypeError";
    185    }
    186    if (err.name == "SecurityError") {
    187      return "rejected with SecurityError";
    188    }
    189    return "rejected with other error";
    190  }
    191 }
    192 
    193 add_task(async function test_logiStatus_js() {
    194  let setLoginStatusInJavascript = async function (browser, value) {
    195    let loginResult = await SpecialPowers.spawn(
    196      browser,
    197      [value],
    198      setStatusInContent
    199    );
    200    if (value == "logged-in" || value == "logged-out") {
    201      Assert.equal(
    202        "resolved with undefined",
    203        loginResult,
    204        "Successful call resolves with `undefined`"
    205      );
    206    } else {
    207      Assert.equal(
    208        loginResult,
    209        "rejected with TypeError",
    210        "Unsuccessful JS call rejects with TypeError"
    211      );
    212    }
    213  };
    214 
    215  await login_logout_sequence(setLoginStatusInJavascript, "javascript API");
    216 });
    217 
    218 add_task(async function test_loginStatus_js_frame() {
    219  let setLoginStatusInSubframeJavascript = async function (browser, value) {
    220    const iframeBC = await SpecialPowers.spawn(
    221      browser,
    222      [TEST_URL],
    223      async url => {
    224        const iframe = content.document.createElement("iframe");
    225        await new Promise(resolve => {
    226          iframe.addEventListener("load", resolve, { once: true });
    227          iframe.src = url;
    228          content.document.body.appendChild(iframe);
    229        });
    230 
    231        return iframe.browsingContext;
    232      }
    233    );
    234    let loginResult = await SpecialPowers.spawn(
    235      iframeBC,
    236      [value],
    237      setStatusInContent
    238    );
    239    if (value == "logged-in" || value == "logged-out") {
    240      Assert.equal(
    241        "resolved with undefined",
    242        loginResult,
    243        "Successful call resolves with `undefined`"
    244      );
    245    } else {
    246      Assert.equal(
    247        loginResult,
    248        "rejected with TypeError",
    249        "Unsuccessful JS call rejects with TypeError"
    250      );
    251    }
    252  };
    253 
    254  await login_logout_sequence(
    255    setLoginStatusInSubframeJavascript,
    256    "javascript API"
    257  );
    258 });
    259 
    260 add_task(async function test_loginStatus_js_xorigin_frame() {
    261  let setLoginStatusInSubframeJavascript = async function (browser, value) {
    262    const iframeBC = await SpecialPowers.spawn(
    263      browser,
    264      [TEST_XORIGIN_URL],
    265      async url => {
    266        const iframe = content.document.createElement("iframe");
    267        await new Promise(resolve => {
    268          iframe.addEventListener("load", resolve, { once: true });
    269          iframe.src = url;
    270          content.document.body.appendChild(iframe);
    271        });
    272 
    273        return iframe.browsingContext;
    274      }
    275    );
    276    let loginResult = await SpecialPowers.spawn(
    277      iframeBC,
    278      [value],
    279      setStatusInContent
    280    );
    281    if (value == "logged-in" || value == "logged-out") {
    282      Assert.equal(
    283        loginResult,
    284        "rejected with SecurityError",
    285        "Cross origin JS call with correct enum rejects with SecurityError"
    286      );
    287    } else {
    288      Assert.equal(
    289        loginResult,
    290        "rejected with TypeError",
    291        "Unsuccessful JS call rejects with TypeError"
    292      );
    293    }
    294  };
    295 
    296  await login_doesnt_work(setLoginStatusInSubframeJavascript, "javascript API");
    297 });
    298 
    299 add_task(async function test_loginStatus_js_xorigin_ancestor_frame() {
    300  let setLoginStatusInSubframeJavascript = async function (browser, value) {
    301    const iframeBC = await SpecialPowers.spawn(
    302      browser,
    303      [TEST_XORIGIN_URL],
    304      async url => {
    305        const iframe = content.document.createElement("iframe");
    306        await new Promise(resolve => {
    307          iframe.addEventListener("load", resolve, { once: true });
    308          iframe.src = url;
    309          content.document.body.appendChild(iframe);
    310        });
    311 
    312        return iframe.browsingContext;
    313      }
    314    );
    315    const innerIframeBC = await SpecialPowers.spawn(
    316      iframeBC,
    317      [TEST_URL],
    318      async url => {
    319        const iframe = content.document.createElement("iframe");
    320        await new Promise(resolve => {
    321          iframe.addEventListener("load", resolve, { once: true });
    322          iframe.src = url;
    323          content.document.body.appendChild(iframe);
    324        });
    325 
    326        return iframe.browsingContext;
    327      }
    328    );
    329    let loginResult = await SpecialPowers.spawn(
    330      innerIframeBC,
    331      [value],
    332      setStatusInContent
    333    );
    334    if (value == "logged-in" || value == "logged-out") {
    335      Assert.equal(
    336        loginResult,
    337        "rejected with SecurityError",
    338        "Cross origin JS call with correct enum rejects with SecurityError"
    339      );
    340    } else {
    341      Assert.equal(
    342        loginResult,
    343        "rejected with TypeError",
    344        "Unsuccessful JS call rejects with TypeError"
    345      );
    346    }
    347  };
    348 
    349  await login_doesnt_work(setLoginStatusInSubframeJavascript, "javascript API");
    350 });
    351 
    352 add_task(async function test_login_logout_document_headers() {
    353  let setLoginStatusInDocumentHeader = async function (browser, value) {
    354    let loaded = BrowserTestUtils.browserLoaded(browser, false, TEST_URL);
    355    BrowserTestUtils.startLoadingURIString(
    356      browser,
    357      TEST_LOGIN_STATUS_BASE + value
    358    );
    359    await loaded;
    360  };
    361 
    362  await login_logout_sequence(
    363    setLoginStatusInDocumentHeader,
    364    "document redirect"
    365  );
    366 });
    367 
    368 add_task(async function test_loginStatus_subresource_headers() {
    369  let setLoginStatusViaHeader = async function (browser, value) {
    370    await SpecialPowers.spawn(
    371      browser,
    372      [TEST_LOGIN_STATUS_BASE + value],
    373      async function (url) {
    374        await content.fetch(url);
    375      }
    376    );
    377  };
    378 
    379  await login_logout_sequence(setLoginStatusViaHeader, "subresource header");
    380 });
    381 
    382 add_task(async function test_loginStatus_xorigin_subresource_headers() {
    383  let setLoginStatusViaHeader = async function (browser, value) {
    384    await SpecialPowers.spawn(
    385      browser,
    386      [TEST_LOGIN_STATUS_XORIGIN_BASE + value],
    387      async function (url) {
    388        await content.fetch(url, { mode: "no-cors" });
    389      }
    390    );
    391  };
    392  await login_doesnt_work(
    393    setLoginStatusViaHeader,
    394    "xorigin subresource header"
    395  );
    396 });
    397 
    398 add_task(
    399  async function test_loginStatus_xorigin_subdocument_subresource_headers() {
    400    let setLoginStatusViaSubdocumentSubresource = async function (
    401      browser,
    402      value
    403    ) {
    404      const iframeBC = await SpecialPowers.spawn(
    405        browser,
    406        [TEST_XORIGIN_URL],
    407        async url => {
    408          const iframe = content.document.createElement("iframe");
    409          await new Promise(resolve => {
    410            iframe.addEventListener("load", resolve, { once: true });
    411            iframe.src = url;
    412            content.document.body.appendChild(iframe);
    413          });
    414 
    415          return iframe.browsingContext;
    416        }
    417      );
    418      await SpecialPowers.spawn(
    419        iframeBC,
    420        [TEST_LOGIN_STATUS_BASE + value],
    421        async function (url) {
    422          await content.fetch(url, { mode: "no-cors" });
    423        }
    424      );
    425    };
    426    await login_doesnt_work(
    427      setLoginStatusViaSubdocumentSubresource,
    428      "xorigin subresource header in xorigin frame"
    429    );
    430  }
    431 );
    432 
    433 add_task(
    434  async function test_loginStatus_xorigin_subdocument_xorigin_subresource_headers() {
    435    let setLoginStatusViaSubdocumentSubresource = async function (
    436      browser,
    437      value
    438    ) {
    439      const iframeBC = await SpecialPowers.spawn(
    440        browser,
    441        [TEST_XORIGIN_URL],
    442        async url => {
    443          const iframe = content.document.createElement("iframe");
    444          await new Promise(resolve => {
    445            iframe.addEventListener("load", resolve, { once: true });
    446            iframe.src = url;
    447            content.document.body.appendChild(iframe);
    448          });
    449 
    450          return iframe.browsingContext;
    451        }
    452      );
    453      await SpecialPowers.spawn(
    454        iframeBC,
    455        [TEST_LOGIN_STATUS_XORIGIN_BASE + value],
    456        async function (url) {
    457          await content.fetch(url, { mode: "no-cors" });
    458        }
    459      );
    460    };
    461    await login_doesnt_work(
    462      setLoginStatusViaSubdocumentSubresource,
    463      "xorigin subresource header in xorigin frame"
    464    );
    465  }
    466 );
    467 
    468 add_task(async function test_loginStatus_subdocument_subresource_headers() {
    469  let setLoginStatusViaSubdocumentSubresource = async function (
    470    browser,
    471    value
    472  ) {
    473    const iframeBC = await SpecialPowers.spawn(
    474      browser,
    475      [TEST_URL],
    476      async url => {
    477        const iframe = content.document.createElement("iframe");
    478        await new Promise(resolve => {
    479          iframe.addEventListener("load", resolve, { once: true });
    480          iframe.src = url;
    481          content.document.body.appendChild(iframe);
    482        });
    483 
    484        return iframe.browsingContext;
    485      }
    486    );
    487    await SpecialPowers.spawn(
    488      iframeBC,
    489      [TEST_LOGIN_STATUS_BASE + value],
    490      async function (url) {
    491        await content.fetch(url, { mode: "no-cors" });
    492      }
    493    );
    494  };
    495  await login_logout_sequence(
    496    setLoginStatusViaSubdocumentSubresource,
    497    "subresource header in frame"
    498  );
    499 });
    500 
    501 add_task(async function test_loginStatus_js_disconnected_doc_crash() {
    502  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
    503  let complete = await SpecialPowers.spawn(
    504    tab.linkedBrowser,
    505    [],
    506    async function () {
    507      const doc1 = new content.Document();
    508      const iframe = content.document.createElementNS(
    509        "http://www.w3.org/1999/xhtml",
    510        "iframe"
    511      );
    512      content.document.documentElement.appendChild(iframe);
    513      const cw = iframe.contentWindow;
    514      const element = content.document.querySelector("*");
    515      doc1.adoptNode(element);
    516 
    517      try {
    518        await cw.navigator.login.setStatus("logged-out");
    519      } catch (_) {}
    520      try {
    521        await cw.navigator.login.setStatus("logged-in");
    522      } catch (_) {}
    523      return true;
    524    }
    525  );
    526  Assert.ok(complete, "Did not crash");
    527  await BrowserTestUtils.removeTab(tab);
    528 });