Bug 1889402 - Grant origin permissions during install for mv3 r=robwu

Differential Revision: https://phabricator.services.mozilla.com/D208426
This commit is contained in:
Tomislav Jovanovic 2024-05-03 16:05:15 +00:00
Родитель 60383a2ec2
Коммит 9fd11ac2ef
17 изменённых файлов: 244 добавлений и 54 удалений

Просмотреть файл

@ -16,11 +16,14 @@ const kSideloaded = true;
async function createWebExtension(details) {
let options = {
manifest: {
manifest_version: details.manifest_version ?? 2,
browser_specific_settings: { gecko: { id: details.id } },
name: details.name,
permissions: details.permissions,
host_permissions: details.host_permissions,
},
};
@ -86,9 +89,10 @@ add_task(async function test_sideloading() {
const ID2 = "addon2@tests.mozilla.org";
await createWebExtension({
manifest_version: 3,
id: ID2,
name: "Test 2",
permissions: ["<all_urls>"],
host_permissions: ["<all_urls>"],
});
const ID3 = "addon3@tests.mozilla.org";

Просмотреть файл

@ -20,6 +20,12 @@ const l10n = new Localization(
true
);
add_setup(async function setup() {
await SpecialPowers.pushPrefEnv({
set: [["extensions.originControls.grantByDefault", false]],
});
});
async function makeExtension({
useAddonManager = "temporary",
manifest_version = 3,

Просмотреть файл

@ -44,6 +44,9 @@ add_setup(async function () {
// panel, which could happen when a previous test file resizes the current
// window.
await ensureMaximizedWindow(window);
await SpecialPowers.pushPrefEnv({
set: [["extensions.originControls.grantByDefault", false]],
});
});
add_task(async function test_button_enabled_by_pref() {

Просмотреть файл

@ -5,6 +5,12 @@
loadTestSubscript("head_unified_extensions.js");
add_setup(async () => {
await SpecialPowers.pushPrefEnv({
set: [["extensions.originControls.grantByDefault", false]],
});
});
add_task(async function test_keyboard_navigation_activeScript() {
const extension1 = ExtensionTestUtils.loadExtension({
manifest: {

Просмотреть файл

@ -134,7 +134,7 @@ export var ExtensionsUI = {
let strings = this._buildStrings({
addon,
permissions: addon.userPermissions,
permissions: addon.installPermissions,
type: "sideload",
});

Просмотреть файл

@ -3147,6 +3147,9 @@ pref("extensions.webextensions.restrictedDomains", "accounts-static.cdn.mozilla.
pref("extensions.quarantinedDomains.enabled", true);
pref("extensions.quarantinedDomains.list", "");
// Include origin permissions in the install prompt for MV3 extensions.
pref("extensions.originControls.grantByDefault", true);
// Whether or not the moz-extension resource loads are remoted. For debugging
// purposes only. Setting this to false will break moz-extension URI loading
// unless other process sandboxing and extension remoting prefs are changed.

Просмотреть файл

@ -161,6 +161,13 @@ XPCOMUtils.defineLazyPreferenceGetter(
30 * 1000
);
XPCOMUtils.defineLazyPreferenceGetter(
lazy,
"installIncludesOrigins",
"extensions.originControls.grantByDefault",
false
);
var {
GlobalManager,
IconDetails,
@ -1196,8 +1203,10 @@ export class ExtensionData {
* includes the contents of the "permissions" property as well as other
* capabilities that are derived from manifest fields that users should
* be informed of (e.g., origins where content scripts are injected).
*
* For MV3 extensions with origin controls, this does not include origins.
*/
get manifestPermissions() {
getRequiredPermissions() {
if (this.type !== "extension") {
return null;
}
@ -1240,6 +1249,20 @@ export class ExtensionData {
return Array.from(origins);
}
/**
* Returns additional permissions that extensions is requesting based on its
* manifest. For now, this is host_permissions (and content scripts) in mv3.
*/
getRequestedPermissions() {
if (this.type !== "extension") {
return null;
}
if (this.originControls && lazy.installIncludesOrigins) {
return { permissions: [], origins: this.getManifestOrigins() };
}
return { permissions: [], origins: [] };
}
/**
* Returns optional permissions from the manifest, including host permissions
* if originControls is true.
@ -3729,8 +3752,8 @@ export class Extension extends ExtensionData {
if (
this.originControls &&
this.manifest.granted_host_permissions &&
this.startupReason === "ADDON_INSTALL"
this.startupReason === "ADDON_INSTALL" &&
(this.manifest.granted_host_permissions || lazy.installIncludesOrigins)
) {
let origins = this.getManifestOrigins();
lazy.ExtensionPermissions.add(this.id, { permissions: [], origins });

Просмотреть файл

@ -14,7 +14,10 @@
add_task(async function setup() {
await SpecialPowers.pushPrefEnv({
set: [["extensions.manifestV3.enabled", true]],
set: [
["extensions.manifestV3.enabled", true],
["extensions.originControls.grantByDefault", false],
],
});
});

Просмотреть файл

@ -27,7 +27,10 @@ const makeExtension = ({ manifest: manifestProps, ...otherProps }) => {
add_task(async function setup() {
await SpecialPowers.pushPrefEnv({
set: [["extensions.manifestV3.enabled", true]],
set: [
["extensions.manifestV3.enabled", true],
["extensions.originControls.grantByDefault", false],
],
});
});

Просмотреть файл

@ -1,6 +1,7 @@
"use strict";
Services.prefs.setBoolPref("extensions.manifestV3.enabled", true);
Services.prefs.setBoolPref("extensions.originControls.grantByDefault", false);
const server = createHttpServer({ hosts: ["example.com", "example.net"] });
server.registerDirectory("/data/", do_get_file("data"));

Просмотреть файл

@ -41,7 +41,7 @@ async function getManifestPermissions(extensionData) {
ExtensionTestUtils.failOnSchemaWarnings(false);
await extension.loadManifest();
ExtensionTestUtils.failOnSchemaWarnings(true);
let result = extension.manifestPermissions;
let result = extension.getRequiredPermissions();
if (extension.manifest.manifest_version >= 3) {
// In MV3, host permissions are optional by default.

Просмотреть файл

@ -10,6 +10,11 @@ const { ExtensionPermissions } = ChromeUtils.importESModule(
"resource://gre/modules/ExtensionPermissions.sys.mjs"
);
const WITH_INSTALL_PROMPT = [
["extensions.originControls.grantByDefault", true],
];
const NO_INSTALL_PROMPT = [["extensions.originControls.grantByDefault", false]];
Services.prefs.setBoolPref("extensions.manifestV3.enabled", true);
// ExtensionParent.sys.mjs is being imported lazily because when it is imported Services.appinfo will be
@ -427,12 +432,24 @@ add_task(function test_normal_mv2() {
});
});
add_task(function test_normal_mv3_noInstallPrompt() {
return runWithPrefs(NO_INSTALL_PROMPT, () =>
test_permissions({
manifest_version: 3,
useAddonManager: "permanent",
expectAllGranted: false,
})
);
});
add_task(function test_normal_mv3() {
return test_permissions({
manifest_version: 3,
useAddonManager: "permanent",
expectAllGranted: false,
});
return runWithPrefs(WITH_INSTALL_PROMPT, () =>
test_permissions({
manifest_version: 3,
useAddonManager: "permanent",
expectAllGranted: true,
})
);
});
add_task(function test_granted_for_temporary_mv3() {
@ -444,28 +461,30 @@ add_task(function test_granted_for_temporary_mv3() {
});
});
add_task(async function test_granted_only_for_privileged_mv3() {
try {
// For permanent non-privileged, granted_host_permissions does nothing.
await test_permissions({
manifest_version: 3,
granted_host_permissions: true,
useAddonManager: "permanent",
expectAllGranted: false,
});
add_task(function test_granted_only_for_privileged_mv3() {
return runWithPrefs(NO_INSTALL_PROMPT, async () => {
try {
// For permanent non-privileged, granted_host_permissions does nothing.
await test_permissions({
manifest_version: 3,
granted_host_permissions: true,
useAddonManager: "permanent",
expectAllGranted: false,
});
// Make extensions loaded with addon manager privileged.
AddonTestUtils.usePrivilegedSignatures = true;
// Make extensions loaded with addon manager privileged.
AddonTestUtils.usePrivilegedSignatures = true;
await test_permissions({
manifest_version: 3,
granted_host_permissions: true,
useAddonManager: "permanent",
expectAllGranted: true,
});
} finally {
AddonTestUtils.usePrivilegedSignatures = false;
}
await test_permissions({
manifest_version: 3,
granted_host_permissions: true,
useAddonManager: "permanent",
expectAllGranted: true,
});
} finally {
AddonTestUtils.usePrivilegedSignatures = false;
}
});
});
add_task(async function test_startup() {
@ -540,7 +559,7 @@ add_task(async function test_startup() {
});
// Test that we don't prompt for permissions an extension already has.
async function test_alreadyGranted(manifest_version) {
async function test_alreadyGranted({ manifest_version }) {
const REQUIRED_PERMISSIONS = ["geolocation"];
const REQUIRED_ORIGINS = [
"*://required-host.com/",
@ -671,10 +690,17 @@ async function test_alreadyGranted(manifest_version) {
await extension.unload();
}
add_task(async function test_alreadyGranted_mv2() {
return test_alreadyGranted(2);
return test_alreadyGranted({ manifest_version: 2 });
});
add_task(async function test_alreadyGranted_mv3() {
return test_alreadyGranted(3);
add_task(function test_alreadyGranted_mv3_noInstallPrompt() {
return runWithPrefs(NO_INSTALL_PROMPT, () =>
test_alreadyGranted({ manifest_version: 3 })
);
});
add_task(function test_alreadyGranted_mv3() {
return runWithPrefs(WITH_INSTALL_PROMPT, () =>
test_alreadyGranted({ manifest_version: 3 })
);
});
// IMPORTANT: Do not change this list without review from a Web Extensions peer!
@ -779,7 +805,10 @@ add_task(async function test_optional_all_urls() {
});
// Check when content_script match patterns are treated as optional origins.
async function test_content_script_is_optional(manifest_version) {
async function test_content_script_is_optional({
manifest_version,
expectGranted,
}) {
function background() {
browser.test.onMessage.addListener(async (msg, arg) => {
if (msg == "request") {
@ -816,7 +845,11 @@ async function test_content_script_is_optional(manifest_version) {
extension.sendMessage("getAll");
let initial = await extension.awaitMessage("granted");
deepEqual(initial.origins, [], "Nothing granted on install.");
if (manifest_version < 3 || !expectGranted) {
deepEqual(initial.origins, [], "Nothing granted on install.");
} else {
deepEqual(initial.origins, [CS_ORIGIN], "CS origin granted on install.");
}
await withHandlingUserInput(extension, async () => {
extension.sendMessage("request", {
@ -845,11 +878,32 @@ async function test_content_script_is_optional(manifest_version) {
await extension.unload();
}
add_task(() => test_content_script_is_optional(2));
add_task(() => test_content_script_is_optional(3));
add_task(async function test_content_script_is_optional_mv2() {
await test_content_script_is_optional({ manifest_version: 2 });
});
add_task(function test_content_script_is_optional_mv3_noInstallPrompt() {
return runWithPrefs(NO_INSTALL_PROMPT, () =>
test_content_script_is_optional({
manifest_version: 3,
expectGranted: false,
})
);
});
add_task(function test_content_script_is_optional_mv3() {
return runWithPrefs(WITH_INSTALL_PROMPT, () =>
test_content_script_is_optional({
manifest_version: 3,
expectGranted: true,
})
);
});
// Check that optional permissions are not included in update prompts
async function test_permissions_prompt(manifest_version) {
async function test_permissions_prompt({
manifest_version,
expectInitialGranted,
}) {
function background() {
browser.test.onMessage.addListener(async (msg, arg) => {
if (msg == "request") {
@ -896,7 +950,7 @@ async function test_permissions_prompt(manifest_version) {
equal(result, true, "request() for optional permissions succeeded");
});
if (manifest_version >= 3) {
if (!expectInitialGranted) {
await withHandlingUserInput(extension, async () => {
extension.sendMessage("request", {
origins: ["https://test1.example.com/*"],
@ -964,10 +1018,26 @@ async function test_permissions_prompt(manifest_version) {
await extension.unload();
}
add_task(async function test_permissions_prompt_mv2() {
return test_permissions_prompt(2);
return test_permissions_prompt({
manifest_version: 2,
expectInitialGranted: true,
});
});
add_task(function test_permissions_prompt_mv3_noInstallPrompt() {
return runWithPrefs(NO_INSTALL_PROMPT, () =>
test_permissions_prompt({
manifest_version: 3,
expectInitialGranted: false,
})
);
});
add_task(async function test_permissions_prompt_mv3() {
return test_permissions_prompt(3);
return runWithPrefs(WITH_INSTALL_PROMPT, () =>
test_permissions_prompt({
manifest_version: 3,
expectInitialGranted: true,
})
);
});
// Check that internal permissions can not be set and are not returned by the API.

Просмотреть файл

@ -3278,7 +3278,7 @@ var AddonManagerInternal = {
// the customConfirmationUI preference and responding to the
// "addon-install-confirmation" notification. If the application
// does not implement its own prompt, use the built-in xul dialog.
if (info.addon.userPermissions) {
if (info.addon.installPermissions) {
let subject = {
wrappedJSObject: {
target: browser,
@ -3286,7 +3286,7 @@ var AddonManagerInternal = {
},
};
subject.wrappedJSObject.info.permissions =
info.addon.userPermissions;
info.addon.installPermissions;
Services.obs.notifyObservers(
subject,
"webextension-permission-prompt"

Просмотреть файл

@ -150,13 +150,13 @@ function shouldShowPermissionsPrompt(addon) {
return false;
}
let perms = addon.userPermissions;
let perms = addon.installPermissions;
return perms?.origins.length || perms?.permissions.length;
}
function showPermissionsPrompt(addon) {
return new Promise(resolve => {
const permissions = addon.userPermissions;
const permissions = addon.installPermissions;
const target = getBrowserElement();
const onAddonEnabled = () => {

Просмотреть файл

@ -200,6 +200,7 @@ const PROP_JSON_FIELDS = [
"incognito",
"userPermissions",
"optionalPermissions",
"requestedPermissions",
"sitePermissions",
"siteOrigin",
"icons",
@ -1426,6 +1427,10 @@ AddonWrapper = class {
return addon.location.name == KEY_APP_PROFILE;
}
/**
* Required permissions that extension has access to based on its manifest.
* In mv3 this doesn't include host_permissions.
*/
get userPermissions() {
return addonFor(this).userPermissions;
}
@ -1434,6 +1439,32 @@ AddonWrapper = class {
return addonFor(this).optionalPermissions;
}
/**
* Additional permissions that extension is requesting in its manifest.
* Currently this is host_permissions in MV3.
*/
get requestedPermissions() {
return addonFor(this).requestedPermissions;
}
/**
* A helper that returns all permissions for the install prompt.
*/
get installPermissions() {
let required = this.userPermissions;
if (!required) {
return null;
}
let requested = this.requestedPermissions;
// Currently this can't result in duplicates, but if logic of what goes
// into these lists changes, make sure to check for dupes.
let perms = {
origins: required.origins.concat(requested?.origins ?? []),
permissions: required.permissions.concat(requested?.permissions ?? []),
};
return perms;
}
isCompatibleWith(aAppVersion, aPlatformVersion) {
return addonFor(this).isCompatibleWith(aAppVersion, aPlatformVersion);
}

Просмотреть файл

@ -542,8 +542,9 @@ async function loadManifestFromWebManifest(aPackage, aLocation) {
// WebExtensions don't use iconURLs
addon.iconURL = null;
addon.icons = manifest.icons || {};
addon.userPermissions = extension.manifestPermissions;
addon.userPermissions = extension.getRequiredPermissions();
addon.optionalPermissions = extension.manifestOptionalPermissions;
addon.requestedPermissions = extension.getRequestedPermissions();
addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;
function getLocale(aLocale) {

Просмотреть файл

@ -419,7 +419,11 @@ async function runTest(options) {
}
}
async function testPermissionsView({ manifestV3enabled, manifest_version }) {
async function testPermissionsView({
manifestV3enabled,
manifest_version,
expectGranted,
}) {
await SpecialPowers.pushPrefEnv({
set: [["extensions.manifestV3.enabled", manifestV3enabled]],
});
@ -438,7 +442,16 @@ async function testPermissionsView({ manifestV3enabled, manifest_version }) {
extension: extensions["addon1@mochi.test"],
permissions: ["<all_urls>", "tabs", "webNavigation"],
});
} else {
}
if (manifest_version >= 3 && expectGranted) {
await runTest({
extension: extensions["addon1@mochi.test"],
permissions: ["tabs", "webNavigation"],
optional_permissions: ["<all_urls>"],
optional_enabled: ["<all_urls>"],
});
}
if (manifest_version >= 3 && !expectGranted) {
await runTest({
extension: extensions["addon1@mochi.test"],
permissions: ["tabs", "webNavigation"],
@ -544,8 +557,28 @@ add_task(async function testPermissionsView_MV2_manifestV3enabled() {
await testPermissionsView({ manifestV3enabled: true, manifest_version: 2 });
});
add_task(async function testPermissionsView_MV3_noInstallPrompt() {
await SpecialPowers.pushPrefEnv({
set: [["extensions.originControls.grantByDefault", false]],
});
await testPermissionsView({
manifestV3enabled: true,
manifest_version: 3,
expectGranted: false,
});
await SpecialPowers.popPrefEnv();
});
add_task(async function testPermissionsView_MV3() {
await testPermissionsView({ manifestV3enabled: true, manifest_version: 3 });
await SpecialPowers.pushPrefEnv({
set: [["extensions.originControls.grantByDefault", true]],
});
await testPermissionsView({
manifestV3enabled: true,
manifest_version: 3,
expectGranted: true,
});
await SpecialPowers.popPrefEnv();
});
add_task(async function testPermissionsViewStates() {
@ -623,7 +656,10 @@ add_task(async function testPermissionsViewStates() {
add_task(async function testAllUrlsNotGrantedUnconditionally_MV3() {
await SpecialPowers.pushPrefEnv({
set: [["extensions.manifestV3.enabled", true]],
set: [
["extensions.manifestV3.enabled", true],
["extensions.originControls.grantByDefault", false],
],
});
const extension = ExtensionTestUtils.loadExtension({