tor-browser

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

test_detected_requests_telemetry.js (10158B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/
      3 */
      4 
      5 const { AddonManager } = ChromeUtils.importESModule(
      6  "resource://gre/modules/AddonManager.sys.mjs"
      7 );
      8 const { AddonTestUtils } = ChromeUtils.importESModule(
      9  "resource://testing-common/AddonTestUtils.sys.mjs"
     10 );
     11 const { ExtensionTestUtils } = ChromeUtils.importESModule(
     12  "resource://testing-common/ExtensionXPCShellUtils.sys.mjs"
     13 );
     14 
     15 ChromeUtils.defineESModuleGetters(this, {
     16  RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
     17 });
     18 
     19 AddonTestUtils.init(this);
     20 AddonTestUtils.overrideCertDB();
     21 AddonTestUtils.createAppInfo(
     22  "xpcshell@tests.mozilla.org",
     23  "XPCShell",
     24  "139",
     25  "139"
     26 );
     27 ExtensionTestUtils.init(this);
     28 
     29 const BUILTIN_ADDON_ID = "data-leak-blocker@mozilla.com";
     30 const RS_COLLECTION = "addons-data-leak-blocker-domains";
     31 const GLEAN_REGISTERED_PING = "dataLeakBlocker";
     32 const GLEAN_REGISTERED_CATEGORY = "dataLeakBlocker";
     33 const GLEAN_REGISTERED_METRIC = "reportV1";
     34 
     35 const IS_GLEAN_DATA_LEAK_BLOCKER_BUILTIN = GLEAN_REGISTERED_CATEGORY in Glean;
     36 
     37 const server = AddonTestUtils.createHttpServer({
     38  hosts: ["expected.example.org", "unexpected.example.org"],
     39 });
     40 server.registerPathHandler("/test", (req, res) => {
     41  info(`Test HTTP server for domain "${req.host}" got ${req.method} request\n`);
     42  res.setStatusLine(req.httpVersion, 200, "OK");
     43  // Needed by the MV3 extension (due to the MV3 content script being inherited
     44  // from the webpage instead of being overridden by a fetch instance that belongs
     45  // to the extension ExpandedPrincipal as for MV2 content scripts).
     46  res.setHeader("Access-Control-Allow-Origin", "http://unexpected.example.org");
     47  res.write("OK");
     48 });
     49 
     50 add_setup(async () => {
     51  do_get_profile();
     52 
     53  // Disable loading artifacts build jogfile for this test
     54  // (workaround Bug 1983674).
     55  Services.prefs.setBoolPref("telemetry.fog.artifact_build", false);
     56 
     57  Services.fog.initializeFOG();
     58 
     59  // Enable scope application and override the built_in_addons.json
     60  // resource to ensure the add-on built-in into the Firefox Desktop
     61  // omni jar is going to be loaded.
     62  Services.prefs.setIntPref(
     63    "extensions.enabledScopes",
     64    AddonManager.SCOPE_PROFILE | AddonManager.SCOPE_APPLICATION
     65  );
     66 
     67  // Enable more verbose logging for the logs emitted by this extension.
     68  Services.prefs.setBoolPref(`extensions.${BUILTIN_ADDON_ID}.testing`, true);
     69 
     70  // Reduce timeout on the add-on's DeferredTask that submits the custom
     71  // ping.
     72  Services.prefs.setIntPref(
     73    `extensions.${BUILTIN_ADDON_ID}.gleanSubmitTaskTimeout`,
     74    500
     75  );
     76 
     77  const builtinsConfig = await fetch(
     78    "chrome://browser/content/built_in_addons.json"
     79  ).then(res => res.json());
     80 
     81  await AddonTestUtils.overrideBuiltIns({
     82    system: [],
     83    builtins: builtinsConfig.builtins.filter(
     84      entry => entry.addon_id === BUILTIN_ADDON_ID
     85    ),
     86  });
     87  await AddonTestUtils.promiseStartupManager({
     88    lateStartup: false,
     89  });
     90 
     91  // Sanity check.
     92  const builtinAddon = await AddonManager.getAddonByID(BUILTIN_ADDON_ID);
     93  ok(builtinAddon, "Got AddonWrapper instance for the builtin add-on");
     94  ok(builtinAddon.isActive, "Expect builtin add-on to be active");
     95 
     96  if (!IS_GLEAN_DATA_LEAK_BLOCKER_BUILTIN) {
     97    Assert.ok(
     98      !(GLEAN_REGISTERED_PING in GleanPings),
     99      "Expect runtime registered ping to be registered"
    100    );
    101    Assert.ok(
    102      !(
    103        GLEAN_REGISTERED_CATEGORY in Glean &&
    104        GLEAN_REGISTERED_METRIC in Glean[GLEAN_REGISTERED_CATEGORY]
    105      ),
    106      "Expect runtime registered metric to be registered"
    107    );
    108  }
    109 
    110  await AddonTestUtils.notifyLateStartup();
    111 
    112  Assert.ok(
    113    GLEAN_REGISTERED_PING in GleanPings,
    114    "Expect runtime registered ping to be registered"
    115  );
    116  Assert.ok(
    117    GLEAN_REGISTERED_CATEGORY in Glean &&
    118      GLEAN_REGISTERED_METRIC in Glean[GLEAN_REGISTERED_CATEGORY],
    119    "Expect runtime registered metric to be registered"
    120  );
    121 });
    122 
    123 async function test_extension_fetch({
    124  addon_id,
    125  manifest_version = 2,
    126  expectedGleanEvents,
    127 }) {
    128  const extension = ExtensionTestUtils.loadExtension({
    129    useAddonManager: "permanent",
    130    manifest: {
    131      manifest_version,
    132      browser_specific_settings: {
    133        gecko: { id: addon_id },
    134      },
    135      content_scripts: [
    136        {
    137          matches: ["*://*.example.org/*"],
    138          js: ["content_script.js"],
    139        },
    140      ],
    141      ...(manifest_version >= 3
    142        ? {
    143            host_permissions: ["<all_urls>"],
    144            // Prevent http request to be auto-upgraded to https
    145            content_security_policy: {
    146              extension_pages: "script-src 'self';",
    147            },
    148          }
    149        : { permissions: ["<all_urls>"] }),
    150    },
    151    background() {
    152      const { browser } = this;
    153      browser.test.onMessage.addListener(async (msg, ...args) => {
    154        if (msg === "extpage-call-fetch") {
    155          const [{ url, fetchOptions }] = args;
    156          let fetchError;
    157          await fetch(url, fetchOptions).catch(err => (fetchError = err));
    158          browser.test.sendMessage(`${msg}:done`, String(fetchError));
    159        }
    160      });
    161    },
    162    files: {
    163      "content_script.js": function () {
    164        const { browser } = this;
    165        browser.test.onMessage.addListener(async (msg, ...args) => {
    166          if (msg === "contentscript-call-fetch") {
    167            const [{ url, fetchOptions }] = args;
    168            let fetchError;
    169            await fetch(url, fetchOptions).catch(err => (fetchError = err));
    170            browser.test.sendMessage(`${msg}:done`, String(fetchError));
    171          }
    172        });
    173        browser.test.sendMessage("contentscript:ready");
    174      },
    175    },
    176  });
    177 
    178  await extension.startup();
    179 
    180  const page = await ExtensionTestUtils.loadContentPage(
    181    "http://unexpected.example.org/test"
    182  );
    183  const pingSubmitDeferred = Promise.withResolvers();
    184  const allGleanEvents = [];
    185  const pingSubmitCallback = () => {
    186    info("dataAbuseDetection Glean ping submit callback called");
    187    const gleanEvents = Glean[GLEAN_REGISTERED_CATEGORY][
    188      GLEAN_REGISTERED_METRIC
    189    ].testGetValue()?.map(event => event.extra);
    190    allGleanEvents.push(...(gleanEvents ?? []));
    191    if (allGleanEvents.length < expectedGleanEvents.length) {
    192      info(
    193        `Wait for next Glean ping submit for the missing events (${JSON.stringify(
    194          {
    195            expected: expectedGleanEvents.length,
    196            actual: allGleanEvents.length,
    197          }
    198        )})`
    199      );
    200      GleanPings[GLEAN_REGISTERED_PING].testBeforeNextSubmit(
    201        pingSubmitCallback
    202      );
    203      return;
    204    }
    205    pingSubmitDeferred.resolve();
    206  };
    207  GleanPings[GLEAN_REGISTERED_PING].testBeforeNextSubmit(pingSubmitCallback);
    208 
    209  const rsClient = RemoteSettings(RS_COLLECTION);
    210  const rsData = [
    211    {
    212      id: "monitored-domains-set1",
    213      domains: ["expected.example.org"],
    214    },
    215  ];
    216  await rsClient.db.importChanges({}, Date.now(), rsData, {
    217    clear: true,
    218  });
    219  await rsClient.emit("sync", { data: {} });
    220 
    221  extension.sendMessage("extpage-call-fetch", {
    222    url: "http://unexpected.example.org/test#extpage",
    223  });
    224  Assert.equal(
    225    await extension.awaitMessage("extpage-call-fetch:done"),
    226    "undefined",
    227    "Expect non monitored domains to not be blocked"
    228  );
    229  extension.sendMessage("extpage-call-fetch", {
    230    url: "http://expected.example.org/test#extpage",
    231  });
    232  Assert.equal(
    233    await extension.awaitMessage("extpage-call-fetch:done"),
    234    "TypeError: NetworkError when attempting to fetch resource.",
    235    "Expect monitored domains to be blocked"
    236  );
    237 
    238  // Expect mv2 content script request to be attributed to the
    239  // addon (and blocked), while mv3 content script are expected
    240  // to not be attributed to addons (and not blocked).
    241  const expectContentScriptRequestBlocked = manifest_version < 3;
    242 
    243  await extension.awaitMessage("contentscript:ready");
    244 
    245  extension.sendMessage("contentscript-call-fetch", {
    246    url: "http://unexpected.example.org/test#extcs",
    247    fetchOptions: { method: "POST" },
    248  });
    249  Assert.equal(
    250    await extension.awaitMessage("contentscript-call-fetch:done"),
    251    "undefined",
    252    "Expect non monitored domains to not be blocked"
    253  );
    254  extension.sendMessage("contentscript-call-fetch", {
    255    url: "http://expected.example.org/test#extcs",
    256    fetchOptions: { method: "POST" },
    257  });
    258  Assert.equal(
    259    await extension.awaitMessage("contentscript-call-fetch:done"),
    260    expectContentScriptRequestBlocked
    261      ? "TypeError: NetworkError when attempting to fetch resource."
    262      : "undefined",
    263    expectContentScriptRequestBlocked
    264      ? "Expect monitored domains to be blocked on mv2 fetch request"
    265      : "Expect monitored domains to not be blocked on mv3 fetch request or exempted add-ons"
    266  );
    267 
    268  await extension.unload();
    269  await page.close();
    270 
    271  info("Wait for the Glean ping to be submitted");
    272 
    273  await pingSubmitDeferred.promise;
    274 
    275  Assert.deepEqual(
    276    allGleanEvents,
    277    expectedGleanEvents,
    278    "Got the expected telemetry events"
    279  );
    280 }
    281 
    282 add_task(async function test_mv2_extension() {
    283  const addon_id = "ext-mv2@test";
    284  await test_extension_fetch({
    285    addon_id,
    286    manifest_version: 2,
    287    expectedGleanEvents: [
    288      {
    289        addon_id,
    290        method: "GET",
    291        blocked: "true",
    292        content_policy_type: `${Ci.nsIContentPolicy.TYPE_FETCH}`,
    293        is_addon_triggering: "true",
    294        is_addon_loading: "false",
    295        is_content_script: "false",
    296      },
    297      {
    298        addon_id,
    299        blocked: "true",
    300        method: "POST",
    301        content_policy_type: `${Ci.nsIContentPolicy.TYPE_FETCH}`,
    302        is_addon_triggering: "true",
    303        is_addon_loading: "false",
    304        is_content_script: "true",
    305      },
    306    ],
    307  });
    308 });
    309 
    310 add_task(async function test_mv3_extension() {
    311  const addon_id = "ext-mv3@test";
    312  await test_extension_fetch({
    313    addon_id,
    314    manifest_version: 3,
    315    expectedGleanEvents: [
    316      {
    317        method: "GET",
    318        blocked: "true",
    319        content_policy_type: `${Ci.nsIContentPolicy.TYPE_FETCH}`,
    320        is_addon_triggering: "true",
    321        is_addon_loading: "false",
    322        is_content_script: "false",
    323        addon_id,
    324      },
    325    ],
    326  });
    327 });