From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../test_blocklist_statechange_telemetry.js | 411 +++++++++++++++++++++ 1 file changed, 411 insertions(+) create mode 100644 toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_statechange_telemetry.js (limited to 'toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_statechange_telemetry.js') diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_statechange_telemetry.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_statechange_telemetry.js new file mode 100644 index 0000000000..ccaa1868fe --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_statechange_telemetry.js @@ -0,0 +1,411 @@ +// Verifies that changes to blocklistState are correctly reported to telemetry. + +"use strict"; + +Services.prefs.setBoolPref("extensions.blocklist.useMLBF", true); + +// Set min version to 42 because the updater defaults to min version 42. +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "42.0", "42.0"); + +// Use unprivileged signatures because the MLBF-based blocklist does not +// apply to add-ons with a privileged signature. +AddonTestUtils.usePrivilegedSignatures = false; + +const { Downloader } = ChromeUtils.importESModule( + "resource://services-settings/Attachments.sys.mjs" +); + +const { TelemetryController } = ChromeUtils.importESModule( + "resource://gre/modules/TelemetryController.sys.mjs" +); +const { TelemetryTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TelemetryTestUtils.sys.mjs" +); + +const ExtensionBlocklistMLBF = getExtensionBlocklistMLBF(); + +const EXT_ID = "maybeblockme@tests.mozilla.org"; + +// The addon blocked by the bloom filter (referenced by MLBF_RECORD). +const EXT_BLOCKED_ID = "@blocked"; +const EXT_BLOCKED_VERSION = "1"; +const EXT_BLOCKED_SIGN_TIME = 12345; // Before MLBF_RECORD.generation_time. + +// To serve updates. +const server = AddonTestUtils.createHttpServer(); +const SERVER_BASE_URL = `http://127.0.0.1:${server.identity.primaryPort}`; +const SERVER_UPDATE_PATH = "/update.json"; +const SERVER_UPDATE_URL = `${SERVER_BASE_URL}${SERVER_UPDATE_PATH}`; +// update is served via `server` over insecure http. +Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false); + +async function assertEventDetails(expectedExtras) { + if (!IS_ANDROID_BUILD) { + const expectedEvents = expectedExtras.map(expectedExtra => { + let { object, value, ...extra } = expectedExtra; + return ["blocklist", "addonBlockChange", object, value, extra]; + }); + await TelemetryTestUtils.assertEvents(expectedEvents, { + category: "blocklist", + method: "addonBlockChange", + }); + } else { + info( + `Skip assertions on collected samples for addonBlockChange on android builds` + ); + } + assertGleanEventDetails(expectedExtras); +} +async function assertGleanEventDetails(expectedExtras) { + const snapshot = testGetValue(Glean.blocklist.addonBlockChange); + if (expectedExtras.length === 0) { + Assert.deepEqual(undefined, snapshot, "Expected zero addonBlockChange"); + return; + } + Assert.equal( + expectedExtras.length, + snapshot?.length, + "Number of addonBlockChange records" + ); + for (let i of expectedExtras.keys()) { + let actual = snapshot[i].extra; + // Glean uses snake_case instead of camelCase. + let { blocklistState, ...expected } = expectedExtras[i]; + expected.blocklist_state = blocklistState; + Assert.deepEqual(expected, actual, `Expected addonBlockChange (${i})`); + } +} + +// Stage an update on the update server. The add-on must have been created +// with update_url set to SERVER_UPDATE_URL. +function setupAddonUpdate(addonId, addonVersion) { + let updateXpi = AddonTestUtils.createTempWebExtensionFile({ + manifest: { + version: addonVersion, + browser_specific_settings: { + gecko: { id: addonId, update_url: SERVER_UPDATE_URL }, + }, + }, + }); + let updateXpiPath = `/update-${addonId}-${addonVersion}.xpi`; + server.registerFile(updateXpiPath, updateXpi); + AddonTestUtils.registerJSON(server, SERVER_UPDATE_PATH, { + addons: { + [addonId]: { + updates: [ + { + version: addonVersion, + update_link: `${SERVER_BASE_URL}${updateXpiPath}`, + }, + ], + }, + }, + }); +} + +async function tryAddonInstall(addonId, addonVersion) { + let xpiFile = AddonTestUtils.createTempWebExtensionFile({ + manifest: { + version: addonVersion, + browser_specific_settings: { + gecko: { id: addonId, update_url: SERVER_UPDATE_URL }, + }, + }, + }); + const install = await promiseInstallFile(xpiFile, true); + // Passing true to promiseInstallFile means that the xpi may not be installed + // if blocked by the blocklist. In that case, |install| may be null. + return install?.addon; +} + +add_task(async function setup() { + if (!IS_ANDROID_BUILD) { + // FOG needs a profile directory to put its data in. + do_get_profile(); + // FOG needs to be initialized in order for data to flow. + Services.fog.initializeFOG(); + } + await TelemetryController.testSetup(); + + // Disable the packaged record and attachment to make sure that the test + // will not fall back to the packaged attachments. + Downloader._RESOURCE_BASE_URL = "invalid://bogus"; + + await promiseStartupManager(); +}); + +add_task(async function install_update_not_blocked_is_no_events() { + resetBlocklistTelemetry(); + // Install an add-on that is not blocked. Then update to the next version. + let addon = await tryAddonInstall(EXT_ID, "0.1"); + + // Version "1" not blocked yet, but will be in the next test task. + setupAddonUpdate(EXT_ID, "1"); + let update = await AddonTestUtils.promiseFindAddonUpdates(addon); + await promiseCompleteInstall(update.updateAvailable); + addon = await AddonManager.getAddonByID(EXT_ID); + equal(addon.version, "1", "Add-on was updated"); + + await assertEventDetails([]); +}); + +add_task(async function blocklist_update_events() { + resetBlocklistTelemetry(); + const EXT_HOURS_SINCE_INSTALL = 4321; + const addon = await AddonManager.getAddonByID(EXT_ID); + addon.__AddonInternal__.installDate = + addon.installDate.getTime() - 3600000 * EXT_HOURS_SINCE_INSTALL; + + await AddonTestUtils.loadBlocklistRawData({ + extensionsMLBF: [ + { stash: { blocked: [`${EXT_ID}:1`], unblocked: [] }, stash_time: 123 }, + { stash: { blocked: [`${EXT_ID}:2`], unblocked: [] }, stash_time: 456 }, + ], + }); + + await assertEventDetails([ + { + object: "blocklist_update", + value: EXT_ID, + blocklistState: "2", // Ci.nsIBlocklistService.STATE_BLOCKED + addon_version: "1", + signed_date: "0", + hours_since: `${EXT_HOURS_SINCE_INSTALL}`, + mlbf_last_time: "456", + mlbf_generation: "0", + mlbf_source: "unknown", + }, + ]); +}); + +add_task(async function update_check_blocked_by_stash() { + resetBlocklistTelemetry(); + setupAddonUpdate(EXT_ID, "2"); + let addon = await AddonManager.getAddonByID(EXT_ID); + let update = await AddonTestUtils.promiseFindAddonUpdates(addon); + // Blocks in stashes are immediately enforced by update checks. + // Blocks stored in MLBFs are only enforced after the package is downloaded, + // and that scenario is covered by update_check_blocked_by_stash elsewhere. + equal(update.updateAvailable, false, "Update was blocked by stash"); + + await assertEventDetails([ + { + object: "addon_update_check", + value: EXT_ID, + blocklistState: "2", // Ci.nsIBlocklistService.STATE_BLOCKED + addon_version: "2", + signed_date: "0", + hours_since: "-1", + mlbf_last_time: "456", + mlbf_generation: "0", + mlbf_source: "unknown", + }, + ]); +}); + +// Any attempt to re-install a blocked add-on should trigger a telemetry +// event, even though the blocklistState did not change. +add_task(async function reinstall_blocked_addon() { + resetBlocklistTelemetry(); + let blockedAddon = await AddonManager.getAddonByID(EXT_ID); + equal( + blockedAddon.blocklistState, + Ci.nsIBlocklistService.STATE_BLOCKED, + "Addon was initially blocked" + ); + + let addon = await tryAddonInstall(EXT_ID, "2"); + ok(!addon, "Add-on install should be blocked by a stash"); + + await assertEventDetails([ + { + // Note: installs of existing versions are observed as "addon_install". + // Only updates after update checks are tagged as "addon_update". + object: "addon_install", + value: EXT_ID, + blocklistState: "2", // Ci.nsIBlocklistService.STATE_BLOCKED + addon_version: "2", + signed_date: "0", + hours_since: "-1", + mlbf_last_time: "456", + mlbf_generation: "0", + mlbf_source: "unknown", + }, + ]); +}); + +// For comparison with the next test task (database_modified), verify that a +// regular restart without database modifications does not trigger events. +add_task(async function regular_restart_no_event() { + resetBlocklistTelemetry(); + // Version different/higher than the 42.0 that was passed to createAppInfo at + // the start of this test file to force a database rebuild. + await promiseRestartManager("90.0"); + await assertEventDetails([]); + + await promiseRestartManager(); + await assertEventDetails([]); +}); + +add_task(async function database_modified() { + resetBlocklistTelemetry(); + const EXT_HOURS_SINCE_INSTALL = 3; + await promiseShutdownManager(); + + // Modify the addon database: blocked->not blocked + decrease installDate. + let addonDB = await IOUtils.readJSON(gExtensionsJSON.path); + let rawAddon = addonDB.addons[0]; + equal(rawAddon.id, EXT_ID, "Expected entry in addonDB"); + equal(rawAddon.blocklistState, 2, "Expected STATE_BLOCKED"); + rawAddon.blocklistState = 0; // STATE_NOT_BLOCKED + rawAddon.installDate = Date.now() - 3600000 * EXT_HOURS_SINCE_INSTALL; + await IOUtils.writeJSON(gExtensionsJSON.path, addonDB); + + // Bump version to force database rebuild. + await promiseStartupManager("91.0"); + // Shut down because the database reconcilation blocks shutdown, and we want + // to be certain that the process has finished before checking the events. + await promiseShutdownManager(); + await assertEventDetails([ + { + object: "addon_db_modified", + value: EXT_ID, + blocklistState: "2", // Ci.nsIBlocklistService.STATE_BLOCKED + addon_version: "1", + signed_date: "0", + hours_since: `${EXT_HOURS_SINCE_INSTALL}`, + mlbf_last_time: "456", + mlbf_generation: "0", + mlbf_source: "unknown", + }, + ]); + + resetBlocklistTelemetry(); + await promiseStartupManager(); + await assertEventDetails([]); +}); + +add_task(async function install_replaces_blocked_addon() { + resetBlocklistTelemetry(); + let addon = await tryAddonInstall(EXT_ID, "3"); + ok(addon, "Update supersedes blocked add-on"); + + await assertEventDetails([ + { + object: "addon_install", + value: EXT_ID, + blocklistState: "0", // Ci.nsIBlocklistService.STATE_NOT_BLOCKED + addon_version: "3", + signed_date: "0", + hours_since: "-1", + mlbf_last_time: "456", + mlbf_generation: "0", + mlbf_source: "unknown", + }, + ]); +}); + +add_task(async function install_blocked_by_mlbf() { + resetBlocklistTelemetry(); + await ExtensionBlocklistMLBF._client.db.saveAttachment( + ExtensionBlocklistMLBF.RS_ATTACHMENT_ID, + { record: MLBF_RECORD, blob: await load_mlbf_record_as_blob() } + ); + await AddonTestUtils.loadBlocklistRawData({ + extensionsMLBF: [MLBF_RECORD], + }); + + AddonTestUtils.certSignatureDate = EXT_BLOCKED_SIGN_TIME; + let addon = await tryAddonInstall(EXT_BLOCKED_ID, EXT_BLOCKED_VERSION); + AddonTestUtils.certSignatureDate = null; + + ok(!addon, "Add-on install should be blocked by the MLBF"); + + await assertEventDetails([ + { + object: "addon_install", + value: EXT_BLOCKED_ID, + blocklistState: "2", // Ci.nsIBlocklistService.STATE_BLOCKED + addon_version: EXT_BLOCKED_VERSION, + signed_date: `${EXT_BLOCKED_SIGN_TIME}`, + hours_since: "-1", + // When there is no stash at all, the MLBF's generation_time is used. + mlbf_last_time: `${MLBF_RECORD.generation_time}`, + mlbf_generation: `${MLBF_RECORD.generation_time}`, + mlbf_source: "cache_match", + }, + ]); +}); + +// A limitation of the MLBF-based blocklist is that it needs the add-on package +// in order to check its signature date. +// This part of the test verifies that installation of the add-on is blocked, +// despite the update check tentatively accepting the package. +// See https://bugzilla.mozilla.org/show_bug.cgi?id=1649896 for rationale. +add_task(async function update_check_blocked_by_mlbf() { + resetBlocklistTelemetry(); + // Install a version that we can update, lower than EXT_BLOCKED_VERSION. + let addon = await tryAddonInstall(EXT_BLOCKED_ID, "0.1"); + + setupAddonUpdate(EXT_BLOCKED_ID, EXT_BLOCKED_VERSION); + AddonTestUtils.certSignatureDate = EXT_BLOCKED_SIGN_TIME; + let update = await AddonTestUtils.promiseFindAddonUpdates(addon); + ok(update.updateAvailable, "Update was not blocked by stash"); + + await promiseCompleteInstall(update.updateAvailable); + AddonTestUtils.certSignatureDate = null; + + addon = await AddonManager.getAddonByID(EXT_BLOCKED_ID); + equal(addon.version, EXT_BLOCKED_VERSION, "Add-on was updated"); + equal( + addon.blocklistState, + Ci.nsIBlocklistService.STATE_BLOCKED, + "Add-on is blocked" + ); + equal(addon.appDisabled, true, "Add-on was disabled because of the block"); + + await assertEventDetails([ + { + object: "addon_update", + value: EXT_BLOCKED_ID, + blocklistState: "2", // Ci.nsIBlocklistService.STATE_BLOCKED + addon_version: EXT_BLOCKED_VERSION, + signed_date: `${EXT_BLOCKED_SIGN_TIME}`, + hours_since: "-1", + mlbf_last_time: `${MLBF_RECORD.generation_time}`, + mlbf_generation: `${MLBF_RECORD.generation_time}`, + mlbf_source: "cache_match", + }, + ]); +}); + +add_task(async function update_blocked_to_unblocked() { + resetBlocklistTelemetry(); + // was blocked in update_check_blocked_by_mlbf. + let blockedAddon = await AddonManager.getAddonByID(EXT_BLOCKED_ID); + + // 3 is higher than EXT_BLOCKED_VERSION. + setupAddonUpdate(EXT_BLOCKED_ID, "3"); + AddonTestUtils.certSignatureDate = EXT_BLOCKED_SIGN_TIME; + let update = await AddonTestUtils.promiseFindAddonUpdates(blockedAddon); + ok(update.updateAvailable, "Found an update"); + + await promiseCompleteInstall(update.updateAvailable); + AddonTestUtils.certSignatureDate = null; + + let addon = await AddonManager.getAddonByID(EXT_BLOCKED_ID); + equal(addon.appDisabled, false, "Add-on was re-enabled after unblock"); + await assertEventDetails([ + { + object: "addon_update", + value: EXT_BLOCKED_ID, + blocklistState: "0", // Ci.nsIBlocklistService.STATE_NOT_BLOCKED + addon_version: "3", + signed_date: `${EXT_BLOCKED_SIGN_TIME}`, + hours_since: "-1", + mlbf_last_time: `${MLBF_RECORD.generation_time}`, + mlbf_generation: `${MLBF_RECORD.generation_time}`, + mlbf_source: "cache_match", + }, + ]); +}); -- cgit v1.2.3