Bug 1298939: Don't initialize APIs that the extension does not have permissions for. r=rpl
authorKris Maglione <maglione.k@gmail.com>
Thu, 01 Sep 2016 13:04:54 -0700
changeset 312350 c4d0a934472960be184bee8048a99c9c8c1d65a1
parent 312349 58b07057fe252182816957e68fa23267d43dcadd
child 312351 3be5f41dcbd78ecfc12706d5de83cc3a429f9a42
push id20447
push userkwierso@gmail.com
push dateFri, 02 Sep 2016 20:36:44 +0000
treeherderfx-team@969397f22187 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrpl
bugs1298939
milestone51.0a1
Bug 1298939: Don't initialize APIs that the extension does not have permissions for. r=rpl MozReview-Commit-ID: Y0MTBL1z2O
toolkit/components/extensions/Extension.jsm
toolkit/components/extensions/ExtensionUtils.jsm
toolkit/components/extensions/Schemas.jsm
toolkit/components/extensions/test/xpcshell/test_ext_api_permissions.js
toolkit/components/extensions/test/xpcshell/xpcshell.ini
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -568,16 +568,19 @@ GlobalManager = {
 
   injectInObject(context, isChromeCompat, dest) {
     let apis = {
       extensionTypes: {},
     };
     Management.generateAPIs(context, apis);
     SchemaAPIManager.generateAPIs(context, context.extension.apis, apis);
 
+    // For testing only.
+    context._unwrappedAPIs = apis;
+
     let schemaWrapper = {
       isChromeCompat,
 
       get principal() {
         return context.principal;
       },
 
       get cloneScope() {
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -27,16 +27,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "Locale",
                                   "resource://gre/modules/Locale.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel",
                                   "resource://gre/modules/MessageChannel.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
                                   "resource://gre/modules/Preferences.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
                                   "resource://gre/modules/PromiseUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
+                                  "resource://gre/modules/Schemas.jsm");
 
 function getConsole() {
   return new ConsoleAPI({
     maxLogLevelPref: "extensions.webextensions.log.level",
     prefix: "WebExtensions",
   });
 }
 
@@ -1892,18 +1894,20 @@ class SchemaAPIManager extends EventEmit
           copy(dest[prop], source[prop]);
         } else {
           Object.defineProperty(dest, prop, desc);
         }
       }
     }
 
     for (let api of apis) {
-      api = api.getAPI(context);
-      copy(obj, api);
+      if (Schemas.checkPermissions(api.namespace, context.extension)) {
+        api = api.getAPI(context);
+        copy(obj, api);
+      }
     }
   }
 }
 
 /**
  * Convert any of several different representations of a date/time to a Date object.
  * Accepts several formats:
  * a Date object, an ISO8601 string, or a number of milliseconds since the epoch as
--- a/toolkit/components/extensions/Schemas.jsm
+++ b/toolkit/components/extensions/Schemas.jsm
@@ -1877,16 +1877,38 @@ this.Schemas = {
     data["Extension:Schemas"] = this.schemaJSON;
 
     Services.ppmm.broadcastAsyncMessage("Schema:Delete", {url});
 
     this.flushSchemas();
   },
 
   /**
+   * Checks whether a given object has the necessary permissions to
+   * expose the given namespace.
+   *
+   * @param {string} namespace
+   *        The top-level namespace to check permissions for.
+   * @param {object} wrapperFuncs
+   *        Wrapper functions for the given context.
+   * @param {function} wrapperFuncs.hasPermission
+   *        A function which, when given a string argument, returns true
+   *        if the context has the given permission.
+   * @returns {boolean}
+   *        True if the context has permission for the given namespace.
+   */
+  checkPermissions(namespace, wrapperFuncs) {
+    let ns = this.namespaces.get(namespace);
+    if (ns && ns.permissions) {
+      return ns.permissions.some(perm => wrapperFuncs.hasPermission(perm));
+    }
+    return true;
+  },
+
+  /**
    * 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
    *     interface, which runs the actual functionality of the generated API.
    */
   inject(dest, wrapperFuncs) {
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_api_permissions.js
@@ -0,0 +1,41 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* test_storage_api_without_permissions() {
+  let extension = ExtensionTestUtils.loadExtension({
+    background() {},
+
+    manifest: {
+      permissions: [],
+    },
+  });
+
+  yield extension.startup();
+
+  let context = Array.from(extension.extension.views)[0];
+
+  ok(!("storage" in context._unwrappedAPIs),
+     "The storage API should not be initialized");
+
+  yield extension.unload();
+});
+
+add_task(function* test_storage_api_with_permissions() {
+  let extension = ExtensionTestUtils.loadExtension({
+    background() {},
+
+    manifest: {
+      permissions: ["storage"],
+    },
+  });
+
+  yield extension.startup();
+
+  let context = Array.from(extension.extension.views)[0];
+
+  equal(typeof context._unwrappedAPIs.storage, "object",
+        "The storage API should be initialized");
+
+  yield extension.unload();
+});
--- a/toolkit/components/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell.ini
@@ -9,16 +9,17 @@ tags = webextensions
 
 [test_csp_custom_policies.js]
 [test_csp_validator.js]
 [test_ext_alarms.js]
 [test_ext_alarms_does_not_fire.js]
 [test_ext_alarms_periodic.js]
 [test_ext_alarms_replaces.js]
 [test_ext_apimanager.js]
+[test_ext_api_permissions.js]
 [test_ext_background_generated_load_events.js]
 [test_ext_background_generated_reload.js]
 [test_ext_background_global_history.js]
 skip-if = os == "android" # Android does not use Places for history.
 [test_ext_background_runtime_connect_params.js]
 [test_ext_background_sub_windows.js]
 [test_ext_background_window_properties.js]
 skip-if = os == "android"