"use strict"; const { ExtensionPermissions } = ChromeUtils.importESModule( "resource://gre/modules/ExtensionPermissions.sys.mjs" ); AddonTestUtils.init(this); AddonTestUtils.overrideCertDB(); AddonTestUtils.createAppInfo( "xpcshell@tests.mozilla.org", "XPCShell", "1", "42" ); add_task(async function setup() { // Bug 1646182: Force ExtensionPermissions to run in rkv mode, the legacy // storage mode will run in xpcshell-legacy-ep.toml await ExtensionPermissions._uninit(); Services.prefs.setBoolPref( "extensions.webextOptionalPermissionPrompts", false ); registerCleanupFunction(() => { Services.prefs.clearUserPref("extensions.webextOptionalPermissionPrompts"); }); await AddonTestUtils.promiseStartupManager(); AddonTestUtils.usePrivilegedSignatures = false; }); async function test_migrated_permission_to_optional({ manifest_version }) { let id = "permission-upgrade@test"; let extensionData = { manifest: { version: "1.0", browser_specific_settings: { gecko: { id } }, permissions: [ "webRequest", "tabs", "http://example.net/*", "http://example.com/*", ], }, useAddonManager: "permanent", }; function checkPermissions() { let policy = WebExtensionPolicy.getByID(id); ok(policy.hasPermission("webRequest"), "addon has webRequest permission"); ok(policy.hasPermission("tabs"), "addon has tabs permission"); ok( policy.canAccessURI(Services.io.newURI("http://example.net/")), "addon has example.net host permission" ); ok( policy.canAccessURI(Services.io.newURI("http://example.com/")), "addon has example.com host permission" ); ok( !policy.canAccessURI(Services.io.newURI("http://other.com/")), "addon does not have other.com host permission" ); } let extension = ExtensionTestUtils.loadExtension(extensionData); await extension.startup(); checkPermissions(); extensionData.manifest.manifest_version = manifest_version; // Move to using optional permission extensionData.manifest.version = "2.0"; extensionData.manifest.permissions = ["tabs"]; // The ExtensionTestCommon.generateFiles() test helper will normalize (move) // host permissions into the `permissions` key for the MV2 test. extensionData.manifest.host_permissions = ["http://example.net/*"]; extensionData.manifest.optional_permissions = [ "webRequest", "http://example.com/*", "http://other.com/*", ]; // Restart the addon manager to flush the AddonInternal instance created // when installing the addon above. See bug 1622117. await AddonTestUtils.promiseRestartManager(); await extension.upgrade(extensionData); equal(extension.version, "2.0", "Expected extension version"); checkPermissions(); await extension.unload(); } add_task(function test_migrated_permission_to_optional_mv2() { return test_migrated_permission_to_optional({ manifest_version: 2 }); }); // Test migration of mv2 (required) to mv3 (optional) host permissions. add_task(function test_migrated_permission_to_optional_mv3() { return test_migrated_permission_to_optional({ manifest_version: 3 }); }); // This tests that settings are removed if a required permission is removed. // We use two settings APIs to make sure the one we keep permission to is not // removed inadvertantly. add_task(async function test_required_permissions_removed() { function cacheIsEnabled() { return ( Services.prefs.getBoolPref("browser.cache.disk.enable") && Services.prefs.getBoolPref("browser.cache.memory.enable") ); } let extData = { background() { if (browser.browserSettings) { browser.browserSettings.cacheEnabled.set({ value: false }); } browser.privacy.services.passwordSavingEnabled.set({ value: false }); }, manifest: { browser_specific_settings: { gecko: { id: "pref-test@test" } }, permissions: ["tabs", "browserSettings", "privacy", "http://test.com/*"], }, useAddonManager: "permanent", }; let extension = ExtensionTestUtils.loadExtension(extData); ok( Services.prefs.getBoolPref("signon.rememberSignons"), "privacy setting intial value as expected" ); await extension.startup(); ok(!cacheIsEnabled(), "setting is set after startup"); extData.manifest.permissions = ["tabs"]; extData.manifest.optional_permissions = ["privacy"]; await extension.upgrade(extData); ok(cacheIsEnabled(), "setting is reset after upgrade"); ok( !Services.prefs.getBoolPref("signon.rememberSignons"), "privacy setting is still set after upgrade" ); await extension.unload(); }); // This separate test task covers ExtensionData.migratePermissions behaviors // under some additional corner cases (e.g. updating an addon that was installed // before support for the data collection permission was introduced) add_task(async function test_migratePermissions_on_missing_data_collection() { const { ExtensionData } = ChromeUtils.importESModule( "resource://gre/modules/Extension.sys.mjs" ); const { ExtensionPermissions } = ChromeUtils.importESModule( "resource://gre/modules/ExtensionPermissions.sys.mjs" ); const { sinon } = ChromeUtils.importESModule( "resource://testing-common/Sinon.sys.mjs" ); let sandbox = sinon.createSandbox(); let removeStub = sandbox.stub(ExtensionPermissions, "remove").resolves(); const emptyPerms = { permissions: [], origins: [] }; const testCases = [ { description: "old permissions missing data_collection property", id: "test@ext", oldPermissions: { ...emptyPerms }, oldOptionalPermissions: { ...emptyPerms }, newPermissions: { ...emptyPerms, data_collection: ["locationInfo"] }, newOptionalPermissions: { ...emptyPerms, data_collection: ["healthInfo"], }, expectedRemovedPermissions: { ...emptyPerms, data_collection: [] }, }, { description: "new permissions missing data_collection property", id: "test@ext", oldPermissions: { ...emptyPerms, data_collection: ["locationInfo"] }, oldOptionalPermissions: { ...emptyPerms, data_collection: ["healthInfo"], }, newPermissions: { ...emptyPerms }, newOptionalPermissions: { ...emptyPerms }, expectedRemovedPermissions: { ...emptyPerms, data_collection: ["locationInfo", "healthInfo"], }, }, ]; for (const testCase of testCases) { removeStub.resetHistory(); await ExtensionData.migratePermissions( testCase.id, testCase.oldPermissions, testCase.oldOptionalPermissions, testCase.newPermissions, testCase.newOptionalPermissions ); Assert.deepEqual( removeStub.firstCall.args, [testCase.id, testCase.expectedRemovedPermissions], "ExtensionPermissions.remove call got the expected arguments" ); ok( removeStub.calledOnce, "Expect ExtensionPermissions to have been called once" ); } sandbox.restore(); }); // This tests that settings are removed if a granted permission is removed. // We use two settings APIs to make sure the one we keep permission to is not // removed inadvertantly. add_task( { pref_set: [["extensions.dataCollectionPermissions.enabled", true]], }, async function test_granted_permissions_removed() { function cacheIsEnabled() { return ( Services.prefs.getBoolPref("browser.cache.disk.enable") && Services.prefs.getBoolPref("browser.cache.memory.enable") ); } let extData = { async background() { browser.test.onMessage.addListener(async (msg, args) => { let result; if (msg === "permissions-request") { await browser.permissions.request({ permissions: args.permissions, origins: [], data_collection: args.data_collection, }); if (browser.browserSettings) { browser.browserSettings.cacheEnabled.set({ value: false }); } browser.privacy.services.passwordSavingEnabled.set({ value: false, }); } else if (msg === "permissions-getAll") { result = await browser.permissions.getAll(); } else { browser.test.fail(`Got unexpected test message ${msg}`); } browser.test.sendMessage(`${msg}:done`, result); }); }, // "tabs" is never granted, it is included to exercise the removal code // that called during the upgrade. manifest: { version: "1.0", browser_specific_settings: { gecko: { id: "pref-test@test", data_collection_permissions: { optional: ["technicalAndInteraction", "locationInfo"], }, }, }, optional_permissions: [ "tabs", "browserSettings", "privacy", "http://test.com/*", ], }, useAddonManager: "permanent", }; let extension = ExtensionTestUtils.loadExtension(extData); ok( Services.prefs.getBoolPref("signon.rememberSignons"), "privacy setting intial value as expected" ); await extension.startup(); Assert.equal( extension.extension.version, "1.0", "Got the expected extension version before upgrade" ); await withHandlingUserInput(extension, async () => { extension.sendMessage("permissions-request", { permissions: ["browserSettings", "privacy"], data_collection: ["technicalAndInteraction", "locationInfo"], }); await extension.awaitMessage("permissions-request:done"); }); ok(!cacheIsEnabled(), "setting is set after startup"); extension.sendMessage("permissions-getAll"); Assert.deepEqual( await extension.awaitMessage("permissions-getAll:done"), { origins: [], permissions: ["browserSettings", "privacy"], data_collection: ["technicalAndInteraction", "locationInfo"], }, "got the expected granted permissions" ); extData.manifest.version = "2.0"; extData.manifest.permissions = ["privacy"]; extData.manifest.browser_specific_settings.gecko.data_collection_permissions.required = ["locationInfo"]; delete extData.manifest.optional_permissions; delete extData.manifest.browser_specific_settings.gecko .data_collection_permissions.optional; await extension.upgrade(extData); Assert.equal( extension.extension.version, "2.0", "Got the expected extension version after upgrade" ); ok(cacheIsEnabled(), "setting is reset after upgrade"); ok( !Services.prefs.getBoolPref("signon.rememberSignons"), "privacy setting is still set after upgrade" ); extension.sendMessage("permissions-getAll"); Assert.deepEqual( await extension.awaitMessage("permissions-getAll:done"), { origins: [], permissions: ["privacy"], data_collection: ["locationInfo"], }, "got the expected granted permissions after upgrade" ); await extension.unload(); } );