Bug 1280235 Part 2 Allow webextensions apis to be limited to "Mozilla Extensions" signed extensions draft
authorAndrew Swan <aswan@mozilla.com>
Fri, 13 Oct 2017 22:28:26 -0700
changeset 683630 7d92ed5687619dd7058345ae95907d1dc870f3b3
parent 681018 a0a7e69023a7ec62b9fb08b1828fdd37eebe5320
child 736681 a2ad5090924d9a9e25a7cb7e1336fc28740c6f3a
push id85413
push useraswan@mozilla.com
push dateThu, 19 Oct 2017 22:57:13 +0000
bugs1280235
milestone58.0a1
Bug 1280235 Part 2 Allow webextensions apis to be limited to "Mozilla Extensions" signed extensions MozReview-Commit-ID: DciT0fWTgef
toolkit/components/extensions/Extension.jsm
toolkit/components/extensions/schemas/manifest.json
toolkit/components/extensions/test/xpcshell/test_ext_permissions.js
toolkit/components/extensions/test/xpcshell/test_ext_schemas_privileged.js
toolkit/components/extensions/test/xpcshell/xpcshell.ini
toolkit/mozapps/extensions/internal/XPIProvider.jsm
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -1208,18 +1208,34 @@ this.Extension = class extends Extension
         this.localeData.messages.set(locale, result);
       });
   }
 
   get manifestCacheKey() {
     return [this.id, this.version, Services.locale.getAppLocaleAsLangTag()];
   }
 
+  async _parseManifest() {
+    let manifest = await super.parseManifest();
+    if (manifest && manifest.permissions.has("mozillaAddons") &&
+        this.addonData.signedState !== AddonManager.SIGNEDSTATE_PRIVILEGED) {
+      Cu.reportError(`Stripping mozillaAddons permission from ${this.id}`);
+      manifest.permissions.delete("mozillaAddons");
+      let i = manifest.manifest.permissions.indexOf("mozillaAddons");
+      if (i >= 0) {
+        manifest.manifest.permissions.splice(i, 1);
+      } else {
+        throw new Error("Could not find mozilaAddons in original permissions array");
+      }
+    }
+    return manifest;
+  }
+
   parseManifest() {
-    return StartupCache.manifests.get(this.manifestCacheKey, () => super.parseManifest());
+    return StartupCache.manifests.get(this.manifestCacheKey, () => this._parseManifest());
   }
 
   async cachePermissions() {
     let manifestData = await this.parseManifest();
 
     manifestData.originPermissions = this.whiteListedHosts.patterns.map(pat => pat.pattern);
     manifestData.permissions = this.permissions;
     return StartupCache.manifests.set(this.manifestCacheKey, manifestData);
--- a/toolkit/components/extensions/schemas/manifest.json
+++ b/toolkit/components/extensions/schemas/manifest.json
@@ -392,16 +392,17 @@
       {
         "id": "Permission",
         "choices": [
           { "$ref": "OptionalPermission" },
           {
             "type": "string",
             "enum": [
               "alarms",
+              "mozillaAddons",
               "storage",
               "unlimitedStorage"
             ]
           }
         ]
       },
       {
         "id": "PermissionOrOrigin",
--- a/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js
@@ -403,16 +403,17 @@ const GRANTED_WITHOUT_USER_PROMPT = [
   "alarms",
   "contextMenus",
   "contextualIdentities",
   "cookies",
   "geckoProfiler",
   "identity",
   "idle",
   "menus",
+  "mozillaAddons",
   "storage",
   "theme",
   "webRequest",
   "webRequestBlocking",
 ];
 
 add_task(function test_permissions_have_localization_strings() {
   const ns = Schemas.getNamespace("manifest");
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_schemas_privileged.js
@@ -0,0 +1,79 @@
+"use strict";
+
+Components.utils.import("resource://gre/modules/ExtensionCommon.jsm");
+const {ExtensionAPI} = ExtensionCommon;
+
+Components.utils.importGlobalProperties(["Blob", "URL"]);
+
+AddonTestUtils.init(this);
+AddonTestUtils.overrideCertDB();
+
+add_task(async function() {
+  const schema = [
+    {
+      namespace: "privileged",
+      permissions: ["mozillaAddons"],
+      properties: {
+        "test": {
+          "type": "any",
+        },
+      },
+    },
+  ];
+
+  class API extends ExtensionAPI {
+    getAPI(context) {
+      return {
+        privileged: {
+          test: "hello",
+        },
+      };
+    }
+  }
+
+  const modules = {
+    privileged: {
+      url: URL.createObjectURL(new Blob([API.toString()])),
+      schema: `data:,${JSON.stringify(schema)}`,
+      scopes: ["addon_parent"],
+      paths: [["privileged"]],
+    },
+  };
+
+  const catMan = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
+  catMan.addCategoryEntry("webextension-modules", "test-privileged",
+                          `data:,${JSON.stringify(modules)}`, false, false);
+
+  AddonTestUtils.createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
+  await AddonTestUtils.promiseStartupManager();
+
+  // Try accessing the privileged namespace.
+  async function testOnce() {
+    let extension = ExtensionTestUtils.loadExtension({
+      manifest: {
+        applications: {gecko: {id: "privilegedapi@tests.mozilla.org"}},
+        permissions: ["mozillaAddons"],
+      },
+      background() {
+        browser.test.sendMessage("result", browser.privileged instanceof Object);
+      },
+      useAddonManager: "permanent",
+    });
+
+    await extension.startup();
+    let result = await extension.awaitMessage("result");
+    await extension.unload();
+    return result;
+  }
+
+  AddonTestUtils.usePrivilegedSignatures = false;
+  let result = await testOnce();
+  equal(result, false, "Privileged namespace should not be accessible to a regular webextension");
+
+  AddonTestUtils.usePrivilegedSignatures = true;
+  result = await testOnce();
+  equal(result, true, "Privileged namespace should be accessible to a webextension signed with Mozilla Extensions");
+
+  await AddonTestUtils.promiseShutdownManager();
+  catMan.deleteCategoryEntry("webextension-modules", "test-privileged", false);
+});
--- a/toolkit/components/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell.ini
@@ -38,16 +38,17 @@ tags = webextensions in-process-webexten
 [test_ext_manifest_incognito.js]
 [test_ext_manifest_minimum_chrome_version.js]
 [test_ext_manifest_minimum_opera_version.js]
 [test_ext_manifest_themes.js]
 [test_ext_schemas.js]
 [test_ext_schemas_async.js]
 [test_ext_schemas_allowed_contexts.js]
 [test_ext_schemas_interactive.js]
+[test_ext_schemas_privileged.js]
 [test_ext_schemas_revoke.js]
 [test_ext_themes_supported_properties.js]
 [test_ext_unknown_permissions.js]
 [test_locale_converter.js]
 [test_locale_data.js]
 
 [test_ext_runtime_sendMessage_args.js]
 
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -4455,17 +4455,18 @@ this.XPIProvider = {
           }
         }
       }
 
       let params = {
         id: aAddon.id,
         version: aAddon.version,
         installPath: aFile.clone(),
-        resourceURI: getURIForResourceInFile(aFile, "")
+        resourceURI: getURIForResourceInFile(aFile, ""),
+        signedState: aAddon.signedState,
       };
 
       if (aMethod == "startup" && aAddon.startupData) {
         params.startupData = aAddon.startupData;
       }
 
       if (aExtraParams) {
         for (let key in aExtraParams) {