Bug 1394553 - Add support for `manifest:keys` used as permissions draft
authorTomislav Jovanovic <tomica@gmail.com>
Mon, 04 Sep 2017 21:45:37 +0200
changeset 658809 8a96795d9a224998c3d9b10b24810dfa8040e279
parent 657984 a46a5879b8781ae9ea99f37b5d34a891f0f75047
child 658810 4f2c44d832a7325534bb5e9b5eb8c5cc76132c44
push id77877
push userbmo:tomica@gmail.com
push dateMon, 04 Sep 2017 22:27:09 +0000
bugs1394553
milestone57.0a1
Bug 1394553 - Add support for `manifest:keys` used as permissions MozReview-Commit-ID: K4Mg6Ls0Wfl
browser/modules/ExtensionsUI.jsm
toolkit/components/extensions/Extension.jsm
toolkit/components/extensions/Schemas.jsm
toolkit/components/extensions/ext-permissions.js
toolkit/components/extensions/test/xpcshell/test_ext_unknown_permissions.js
--- 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/"');