Bug 1280235 Part 2 Allow webextensions apis to be limited to "Mozilla Extensions" signed extensions r=kmag
authorAndrew Swan <aswan@mozilla.com>
Fri, 13 Oct 2017 22:28:26 -0700
changeset 387287 d4961b5813af00baad4cd63517f52b55d275fb62
parent 387286 71ea8cf2a217d5bccdc422b5462afa870e4699cd
child 387288 70d57430836659b95d6c202cc9edb9dc4ac3f767
push id53712
push useraswan@mozilla.com
push dateFri, 20 Oct 2017 01:54:30 +0000
treeherderautoland@d4961b5813af [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskmag
bugs1280235
milestone58.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1280235 Part 2 Allow webextensions apis to be limited to "Mozilla Extensions" signed extensions r=kmag 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
@@ -4352,17 +4352,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) {