tor-browser

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

test_ContextId_RustBackend.js (9628B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 https://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 const { _ContextId } = ChromeUtils.importESModule(
      7  "moz-src:///browser/modules/ContextId.sys.mjs"
      8 );
      9 
     10 const { ObliviousHTTP } = ChromeUtils.importESModule(
     11  "resource://gre/modules/ObliviousHTTP.sys.mjs"
     12 );
     13 
     14 const { sinon } = ChromeUtils.importESModule(
     15  "resource://testing-common/Sinon.sys.mjs"
     16 );
     17 
     18 const CONTEXT_ID_PREF = "browser.contextual-services.contextId";
     19 const CONTEXT_ID_TIMESTAMP_PREF =
     20  "browser.contextual-services.contextId.timestamp-in-seconds";
     21 const CONTEXT_ID_ROTATION_DAYS_PREF =
     22  "browser.contextual-services.contextId.rotation-in-days";
     23 const UUID_REGEX =
     24  /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
     25 const TEST_CONTEXT_ID = "decafbad-0cd1-0cd2-0cd3-decafbad1000";
     26 const TEST_CONTEXT_ID_WITH_BRACES = "{" + TEST_CONTEXT_ID + "}";
     27 const UNIFIED_ADS_ENDPOINT = Services.prefs.getCharPref(
     28  "browser.newtabpage.activity-stream.unifiedAds.endpoint",
     29  ""
     30 );
     31 
     32 do_get_profile();
     33 
     34 /**
     35 * Resolves when the passed in ContextId instance fires the ContextId:Persisted
     36 * event.
     37 *
     38 * @param {_ContextId} instance
     39 *   An instance of the _ContextId class under test.
     40 * @returns {Promise<CustomEvent>}
     41 */
     42 function waitForPersist(instance) {
     43  return new Promise(resolve => {
     44    instance.addEventListener("ContextId:Persisted", resolve, { once: true });
     45  });
     46 }
     47 
     48 /**
     49 * Resolves when the the context-id-deletion-request ping is next sent, as
     50 * well as the MARS deletion request. This helper also checks that those two
     51 * requests properly send the rotatedFromContextId value.
     52 *
     53 * @param {string} rotatedFromContextIed
     54 *   The context ID that was rotated away from.
     55 * @param {Function} taskFn
     56 *   A function that will trigger the requests to be sent. This function might
     57 *   be async.
     58 * @returns {Promise<undefined>}
     59 */
     60 function waitForRotated(rotatedFromContextId, taskFn) {
     61  let sandbox = sinon.createSandbox();
     62  sandbox.stub(ObliviousHTTP, "getOHTTPConfig").resolves({});
     63 
     64  let { promise, resolve } = Promise.withResolvers();
     65 
     66  sandbox
     67    .stub(ObliviousHTTP, "ohttpRequest")
     68    .callsFake((_url, _config, endpoint, options) => {
     69      Assert.equal(
     70        endpoint,
     71        `${UNIFIED_ADS_ENDPOINT}v1/delete_user`,
     72        "Sent to the MARS endpoint"
     73      );
     74      Assert.equal(options.method, "DELETE", "Sent using DELETE");
     75      Assert.deepEqual(
     76        JSON.parse(options.body),
     77        {
     78          context_id: rotatedFromContextId,
     79        },
     80        "Sent the old context_id"
     81      );
     82 
     83      resolve();
     84      return Promise.resolve({
     85        status: 200,
     86        json: async () => [],
     87      });
     88    });
     89 
     90  return GleanPings.contextIdDeletionRequest.testSubmission(async () => {
     91    Assert.equal(
     92      Glean.contextualServices.contextId.testGetValue(),
     93      rotatedFromContextId,
     94      "Sent the right context ID to be deleted."
     95    );
     96    await promise;
     97    sandbox.restore();
     98  }, taskFn);
     99 }
    100 
    101 /**
    102 * Checks that when a taskFn resolves, a context ID rotation has not occurred
    103 * for the instance.
    104 *
    105 * @param {_ContextId} instance
    106 *   The instance of _ContextId under test.
    107 * @param {function} taskFn
    108 *   A function that is being tested to ensure that it does not cause rotation
    109 *   to occur. It can be async.
    110 * @returns {Promise<undefined>}
    111 */
    112 async function doesNotRotate(instance, taskFn) {
    113  let controller = new AbortController();
    114  instance.addEventListener(
    115    "ContextId:Rotated",
    116    () => {
    117      Assert.ok(false, "Saw unexpected rotation.");
    118    },
    119    { signal: controller.signal }
    120  );
    121  await taskFn();
    122  controller.abort();
    123 }
    124 
    125 add_setup(() => {
    126  Services.fog.initializeFOG();
    127 });
    128 
    129 /**
    130 * Test that if there's a pre-existing contextID, we can get it, and that a
    131 * timestamp will be generated for it.
    132 */
    133 add_task(async function test_get_existing() {
    134  // Historically, we've stored the context ID with braces, but our endpoints
    135  // actually would prefer just the raw UUID. The Rust component does the
    136  // work of stripping those off for us automatically. We'll test that by
    137  // starting with a context ID with braces in storage, and ensuring that
    138  // what gets saved and what gets returned does not have braces.
    139  Services.prefs.setCharPref(CONTEXT_ID_PREF, TEST_CONTEXT_ID_WITH_BRACES);
    140  Services.prefs.clearUserPref(CONTEXT_ID_TIMESTAMP_PREF);
    141  Services.prefs.setIntPref(CONTEXT_ID_ROTATION_DAYS_PREF, 0);
    142 
    143  let instance = new _ContextId();
    144  let persisted = waitForPersist(instance);
    145 
    146  Assert.equal(
    147    await instance.request(),
    148    TEST_CONTEXT_ID,
    149    "Should have gotten the stored context ID"
    150  );
    151 
    152  await persisted;
    153  Assert.equal(
    154    typeof Services.prefs.getIntPref(CONTEXT_ID_TIMESTAMP_PREF, 0),
    155    "number",
    156    "We stored a timestamp for the context ID."
    157  );
    158  Assert.equal(
    159    Services.prefs.getCharPref(CONTEXT_ID_PREF),
    160    TEST_CONTEXT_ID,
    161    "We stored a the context ID without braces."
    162  );
    163 
    164  Assert.equal(
    165    await instance.request(),
    166    TEST_CONTEXT_ID,
    167    "Should have gotten the same stored context ID back again."
    168  );
    169 });
    170 
    171 /**
    172 * Test that if there's not a pre-existing contextID, we will generate one, and
    173 * a timestamp will be generated for it.
    174 */
    175 add_task(async function test_generate() {
    176  Services.prefs.clearUserPref(CONTEXT_ID_PREF);
    177  Services.prefs.clearUserPref(CONTEXT_ID_TIMESTAMP_PREF);
    178 
    179  let instance = new _ContextId();
    180  let persisted = waitForPersist(instance);
    181 
    182  const generatedContextID = await instance.request();
    183  await persisted;
    184 
    185  Assert.ok(
    186    UUID_REGEX.test(generatedContextID),
    187    "Should have gotten a UUID generated for the context ID."
    188  );
    189  Assert.equal(
    190    typeof Services.prefs.getIntPref(CONTEXT_ID_TIMESTAMP_PREF, 0),
    191    "number",
    192    "We stored a timestamp for the context ID."
    193  );
    194 
    195  Assert.equal(
    196    await instance.request(),
    197    generatedContextID,
    198    "Should have gotten the same stored context ID back again."
    199  );
    200 });
    201 
    202 /**
    203 * Test that if we have a pre-existing context ID, with an extremely old
    204 * creation date (we'll use a creation date of 1, which is back in the 1970s),
    205 * but a rotation setting of 0, that we don't rotate the context ID.
    206 */
    207 add_task(async function test_no_rotation() {
    208  Services.prefs.setCharPref(CONTEXT_ID_PREF, TEST_CONTEXT_ID);
    209  Services.prefs.setIntPref(CONTEXT_ID_TIMESTAMP_PREF, 1);
    210  Services.prefs.setIntPref(CONTEXT_ID_ROTATION_DAYS_PREF, 0);
    211 
    212  let instance = new _ContextId();
    213  Assert.ok(
    214    !instance.rotationEnabled,
    215    "ContextId should report that rotation is not enabled."
    216  );
    217 
    218  await doesNotRotate(instance, async () => {
    219    Assert.equal(
    220      await instance.request(),
    221      TEST_CONTEXT_ID,
    222      "Should have gotten the stored context ID"
    223    );
    224  });
    225 
    226  // We should be able to synchronously request the context ID in this
    227  // configuration.
    228  Assert.equal(
    229    instance.requestSynchronously(),
    230    TEST_CONTEXT_ID,
    231    "Got the stored context ID back synchronously."
    232  );
    233 });
    234 
    235 /**
    236 * Test that if we have a pre-existing context ID, and if the age associated
    237 * with it is greater than our rotation window, that we'll generate a new
    238 * context ID and update the creation timestamp. We'll use a creation timestamp
    239 * of the original context ID of 1, which is sometime in the 1970s.
    240 */
    241 add_task(async function test_rotation() {
    242  Services.prefs.setCharPref(CONTEXT_ID_PREF, TEST_CONTEXT_ID);
    243  Services.prefs.setIntPref(CONTEXT_ID_TIMESTAMP_PREF, 1);
    244  // Let's say there's a 30 day rotation window.
    245  const ROTATION_DAYS = 30;
    246  Services.prefs.setIntPref(CONTEXT_ID_ROTATION_DAYS_PREF, ROTATION_DAYS);
    247 
    248  let instance = new _ContextId();
    249  Assert.ok(
    250    instance.rotationEnabled,
    251    "ContextId should report that rotation is enabled."
    252  );
    253 
    254  let generatedContextID;
    255 
    256  await waitForRotated(TEST_CONTEXT_ID, async () => {
    257    let persisted = waitForPersist(instance);
    258    generatedContextID = await instance.request();
    259    await persisted;
    260  });
    261 
    262  Assert.ok(
    263    UUID_REGEX.test(generatedContextID),
    264    "Should have gotten a UUID generated for the context ID."
    265  );
    266 
    267  let creationTimestamp = Services.prefs.getIntPref(CONTEXT_ID_TIMESTAMP_PREF);
    268  // We should have bumped the creation timestamp.
    269  Assert.greater(creationTimestamp, 1);
    270 
    271  // We should NOT be able to synchronously request the context ID in this
    272  // configuration.
    273  Assert.throws(() => {
    274    instance.requestSynchronously();
    275  }, /Cannot request context ID synchronously/);
    276 });
    277 
    278 /**
    279 * Test that if we have a pre-existing context ID, we can force rotation even
    280 * if the expiry hasn't come up.
    281 */
    282 add_task(async function test_force_rotation() {
    283  Services.prefs.setCharPref(CONTEXT_ID_PREF, TEST_CONTEXT_ID);
    284  Services.prefs.clearUserPref(CONTEXT_ID_TIMESTAMP_PREF);
    285  // Let's say there's a 30 day rotation window.
    286  const ROTATION_DAYS = 30;
    287  Services.prefs.setIntPref(CONTEXT_ID_ROTATION_DAYS_PREF, ROTATION_DAYS);
    288 
    289  let instance = new _ContextId();
    290  Assert.equal(
    291    await instance.request(),
    292    TEST_CONTEXT_ID,
    293    "Should have gotten the stored context ID"
    294  );
    295 
    296  await waitForRotated(TEST_CONTEXT_ID, async () => {
    297    await instance.forceRotation();
    298  });
    299 
    300  let generatedContextID = await instance.request();
    301 
    302  Assert.notEqual(
    303    generatedContextID,
    304    TEST_CONTEXT_ID,
    305    "The context ID should have been regenerated."
    306  );
    307  Assert.ok(
    308    UUID_REGEX.test(generatedContextID),
    309    "Should have gotten a UUID generated for the context ID."
    310  );
    311 
    312  // We should NOT be able to synchronously request the context ID in this
    313  // configuration.
    314  Assert.throws(() => {
    315    instance.requestSynchronously();
    316  }, /Cannot request context ID synchronously/);
    317 });