Bug 1394553 - Add support for `manifest:keys` used as permissions
MozReview-Commit-ID: K4Mg6Ls0Wfl
--- a/browser/modules/ExtensionsUI.jsm
+++ b/browser/modules/ExtensionsUI.jsm
@@ -305,17 +305,17 @@ this.ExtensionsUI = {
}
format(wildcards, "webextPerms.hostDescription.wildcard",
"webextPerms.hostDescription.tooManyWildcards");
format(sites, "webextPerms.hostDescription.oneSite",
"webextPerms.hostDescription.tooManySites");
}
- let permissionKey = perm => `webextPerms.description.${perm}`;
+ let permissionKey = perm => `webextPerms.description.${perm.replace(":", ".")}`;
// Next, show the native messaging permission if it is present.
const NATIVE_MSG_PERM = "nativeMessaging";
if (perms.permissions.includes(NATIVE_MSG_PERM)) {
let brandBundle = Services.strings.createBundle(BRAND_PROPERTIES);
let appName = brandBundle.GetStringFromName("brandShortName");
result.msgs.push(bundle.formatStringFromName(permissionKey(NATIVE_MSG_PERM), [appName], 1));
}
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -544,16 +544,25 @@ this.ExtensionData = class {
this.id = id;
}
let apiNames = new Set();
let dependencies = new Set();
let originPermissions = new Set();
let permissions = new Set();
+ // Add in `manifest:keys` used as permissions.
+ let knownPermissions = Schemas.getAllPermissions();
+ for (let key of Object.keys(manifest)) {
+ let perm = `manifest:${key}`;
+ if (this.manifest[key] && knownPermissions.has(perm)) {
+ permissions.add(perm);
+ }
+ }
+
for (let perm of manifest.permissions) {
if (perm === "geckoProfiler") {
const acceptedExtensions = Services.prefs.getStringPref("extensions.geckoProfiler.acceptedExtensionIds", "");
if (!acceptedExtensions.split(",").includes(id)) {
this.manifestError("Only whitelisted extensions are allowed to access the geckoProfiler.");
continue;
}
}
@@ -1374,21 +1383,16 @@ this.Extension = class extends Extension
observe(subject, topic, data) {
if (topic === "xpcom-shutdown") {
this.cleanupGeneratedFile();
}
}
hasPermission(perm, includeOptional = false) {
- let manifest_ = "manifest:";
- if (perm.startsWith(manifest_)) {
- return this.manifest[perm.substr(manifest_.length)] != null;
- }
-
if (this.permissions.has(perm)) {
return true;
}
if (includeOptional && this.manifest.optional_permissions.includes(perm)) {
return true;
}
--- a/toolkit/components/extensions/Schemas.jsm
+++ b/toolkit/components/extensions/Schemas.jsm
@@ -2860,16 +2860,43 @@ this.Schemas = {
let ns = this.getNamespace(namespace);
if (ns && ns.permissions) {
return ns.permissions.some(perm => wrapperFuncs.hasPermission(perm));
}
return true;
},
+ /**
+ * @returns {Set<string>} all known permissions from the schemas,
+ * including Permission, OptionalPermission and `manifest:key` values.
+ */
+ getAllPermissions() {
+ if (!this.initialized) {
+ this.init();
+ }
+
+ const ns = this.getNamespace("manifest");
+ const permissions = ns.get("Permission").choices;
+ const optional = ns.get("OptionalPermission").choices;
+
+ const all = [];
+
+ for (const choice of permissions.concat(optional)) {
+ all.push(...(choice.enumeration || []));
+ }
+
+ for (const ns of this.rootNamespace.values()) {
+ all.push(...(ns.permissions || []));
+ }
+
+ // Sort and uniquify.
+ return new Set(all.sort());
+ },
+
exportLazyGetter,
/**
* Inject registered extension APIs into `dest`.
*
* @param {object} dest The root namespace for the APIs.
* This object is usually exposed to extensions as "chrome" or "browser".
* @param {object} wrapperFuncs An implementation of the InjectionContext
--- a/toolkit/components/extensions/ext-permissions.js
+++ b/toolkit/components/extensions/ext-permissions.js
@@ -60,16 +60,17 @@ this.permissions = class extends Extensi
}
await ExtensionPermissions.add(context.extension, perms);
return true;
},
async getAll() {
let perms = context.extension.userPermissions;
+ perms.permissions = perms.permissions.filter(key => !key.startsWith("manifest:"));
delete perms.apis;
return perms;
},
async contains(permissions) {
for (let perm of permissions.permissions) {
if (!context.extension.hasPermission(perm)) {
return false;
--- a/toolkit/components/extensions/test/xpcshell/test_ext_unknown_permissions.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_unknown_permissions.js
@@ -17,17 +17,17 @@ add_task(async function test_unknown_per
});
let {messages} = await promiseConsoleOutput(
() => extension.startup());
const {WebExtensionPolicy} = Cu.import("resource://gre/modules/Extension.jsm", {});
let policy = WebExtensionPolicy.getByID(extension.id);
- Assert.deepEqual(Array.from(policy.permissions).sort(), ["activeTab", "http://*/*"]);
+ Assert.deepEqual(Array.from(policy.permissions).sort(), ["activeTab", "http://*/*", "manifest:optional_permissions"]);
Assert.deepEqual(extension.extension.manifest.optional_permissions, ["https://example.com/"]);
ok(messages.some(message => /Error processing permissions\.1: Value "fooUnknownPermission" must/.test(message)),
'Got expected error for "fooUnknownPermission"');
ok(messages.some(message => /Error processing permissions\.3: Value "chrome:\/\/favicon\/" must/.test(message)),
'Got expected error for "chrome://favicon/"');