Bug 1073359 - reinstate PermissionUtils and XPIProvider which were removed in bug 1050080. r=MattN
authorMark Hammond <mhammond@skippinet.com.au>
Fri, 26 Sep 2014 15:54:34 +1000
changeset 207325 cceb2866da0bc5259e1a1eb9033520b8d6ce5c4a
parent 207324 a63ecf76aaa63477f6572a44e80ceb3d312c8219
child 207421 68f76e3c578795b68cbb39da03859db13607bf79
push id9000
push usermhammond@skippinet.com.au
push dateFri, 26 Sep 2014 05:55:57 +0000
treeherderfx-team@cceb2866da0b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMattN
bugs1073359, 1050080
milestone35.0a1
Bug 1073359 - reinstate PermissionUtils and XPIProvider which were removed in bug 1050080. r=MattN
toolkit/modules/PermissionsUtils.jsm
toolkit/modules/moz.build
toolkit/modules/tests/xpcshell/test_PermissionsUtils.js
toolkit/modules/tests/xpcshell/xpcshell.ini
toolkit/mozapps/extensions/internal/XPIProvider.jsm
toolkit/mozapps/extensions/test/xpcshell/test_permissions.js
toolkit/mozapps/extensions/test/xpcshell/test_permissions_prefs.js
toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/PermissionsUtils.jsm
@@ -0,0 +1,87 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+this.EXPORTED_SYMBOLS = ["PermissionsUtils"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+
+let gImportedPrefBranches = new Set();
+
+function importPrefBranch(aPrefBranch, aPermission, aAction) {
+  let list = Services.prefs.getChildList(aPrefBranch, {});
+
+  for (let pref of list) {
+    let hosts = "";
+    try {
+      hosts = Services.prefs.getCharPref(pref);
+    } catch (e) {}
+
+    if (!hosts)
+      continue;
+
+    hosts = hosts.split(",");
+
+    for (let host of hosts) {
+      let uri = null;
+      try {
+        uri = Services.io.newURI("http://" + host, null, null);
+      } catch (e) {
+        try {
+          uri = Services.io.newURI(host, null, null);
+        } catch (e2) {}
+      }
+
+      try {
+        Services.perms.add(uri, aPermission, aAction);
+      } catch (e) {}
+    }
+
+    Services.prefs.setCharPref(pref, "");
+  }
+}
+
+
+this.PermissionsUtils = {
+  /**
+   * Import permissions from perferences to the Permissions Manager. After being
+   * imported, all processed permissions will be set to an empty string.
+   * Perferences are only processed once during the application's
+   * lifetime - it's safe to call this multiple times without worrying about
+   * doing unnecessary work, as the preferences branch will only be processed
+   * the first time.
+   *
+   * @param aPrefBranch  Preferences branch to import from. The preferences
+   *                     under this branch can specify whitelist (ALLOW_ACTION)
+   *                     or blacklist (DENY_ACTION) additions using perference
+   *                     names of the form:
+   *                     * <BRANCH>.whitelist.add.<ID>
+   *                     * <BRANCH>.blacklist.add.<ID>
+   *                     Where <ID> can be any valid preference name.
+   *                     The value is expected to be a comma separated list of
+   *                     host named. eg:
+   *                     * something.example.com
+   *                     * foo.exmaple.com,bar.example.com
+   *
+   * @param aPermission Permission name to be passsed to the Permissions
+   *                    Manager.
+   */
+  importFromPrefs: function(aPrefBranch, aPermission) {
+    if (!aPrefBranch.endsWith("."))
+      aPrefBranch += ".";
+
+    // Ensure we only import this pref branch once.
+    if (gImportedPrefBranches.has(aPrefBranch))
+     return;
+
+    importPrefBranch(aPrefBranch + "whitelist.add", aPermission,
+                     Services.perms.ALLOW_ACTION);
+    importPrefBranch(aPrefBranch + "blacklist.add", aPermission,
+                     Services.perms.DENY_ACTION);
+
+    gImportedPrefBranches.add(aPrefBranch);
+  }
+};
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -25,16 +25,17 @@ EXTRA_JS_MODULES += [
     'Finder.jsm',
     'Geometry.jsm',
     'Http.jsm',
     'InlineSpellChecker.jsm',
     'LoadContextInfo.jsm',
     'Log.jsm',
     'NewTabUtils.jsm',
     'PageMenu.jsm',
+    'PermissionsUtils.jsm',
     'PopupNotifications.jsm',
     'Preferences.jsm',
     'PrivateBrowsingUtils.jsm',
     'Promise-backend.js',
     'Promise.jsm',
     'PropertyListUtils.jsm',
     'RemoteController.jsm',
     'RemoteFinder.jsm',
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/tests/xpcshell/test_PermissionsUtils.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Tests that PerrmissionsUtils.jsm works as expected, including:
+// * PermissionsUtils.importfromPrefs()
+//      <ROOT>.[whitelist|blacklist].add preferences are emptied when
+//       converted into permissions on startup.
+
+
+const PREF_ROOT = "testpermissions.";
+const TEST_PERM = "test-permission";
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/PermissionsUtils.jsm");
+
+function run_test() {
+  test_importfromPrefs();
+}
+
+
+function test_importfromPrefs() {
+  // Create own preferences to test
+  Services.prefs.setCharPref(PREF_ROOT + "whitelist.add.EMPTY", "");
+  Services.prefs.setCharPref(PREF_ROOT + "whitelist.add.EMPTY2", ",");
+  Services.prefs.setCharPref(PREF_ROOT + "whitelist.add.TEST", "whitelist.example.com");
+  Services.prefs.setCharPref(PREF_ROOT + "whitelist.add.TEST2", "whitelist2-1.example.com,whitelist2-2.example.com,about:home");
+  Services.prefs.setCharPref(PREF_ROOT + "blacklist.add.EMPTY", "");
+  Services.prefs.setCharPref(PREF_ROOT + "blacklist.add.TEST", "blacklist.example.com,");
+  Services.prefs.setCharPref(PREF_ROOT + "blacklist.add.TEST2", ",blacklist2-1.example.com,blacklist2-2.example.com,about:mozilla");
+
+  // Check they are unknown in the permission manager prior to importing.
+  let whitelisted = ["http://whitelist.example.com",
+                     "http://whitelist2-1.example.com",
+                     "http://whitelist2-2.example.com",
+                     "about:home"];
+  let blacklisted = ["http://blacklist.example.com",
+                     "http://blacklist2-1.example.com",
+                     "http://blacklist2-2.example.com",
+                     "about:mozilla"];
+  let unknown = whitelisted.concat(blacklisted);
+  for (let url of unknown) {
+    let uri = Services.io.newURI(url, null, null);
+    do_check_eq(Services.perms.testPermission(uri, TEST_PERM), Services.perms.UNKNOWN_ACTION);
+  }
+
+  // Import them
+  PermissionsUtils.importFromPrefs(PREF_ROOT, TEST_PERM);
+
+  // Get list of preferences to check
+  let preferences = Services.prefs.getChildList(PREF_ROOT, {});
+
+  // Check preferences were emptied
+  for (let pref of preferences) {
+    do_check_eq(Services.prefs.getCharPref(pref), "");
+  }
+
+  // Check they were imported into the permissions manager
+  for (let url of whitelisted) {
+    let uri = Services.io.newURI(url, null, null);
+    do_check_eq(Services.perms.testPermission(uri, TEST_PERM), Services.perms.ALLOW_ACTION);
+  }
+  for (let url of blacklisted) {
+    let uri = Services.io.newURI(url, null, null);
+    do_check_eq(Services.perms.testPermission(uri, TEST_PERM), Services.perms.DENY_ACTION);
+  }
+}
--- a/toolkit/modules/tests/xpcshell/xpcshell.ini
+++ b/toolkit/modules/tests/xpcshell/xpcshell.ini
@@ -13,16 +13,17 @@ support-files =
 [test_DirectoryLinksProvider.js]
 [test_FileUtils.js]
 [test_GMPInstallManager.js]
 # GMPInstallManager is not shipped on Android
 skip-if = os == 'android'
 [test_Http.js]
 [test_Log.js]
 [test_NewTabUtils.js]
+[test_PermissionsUtils.js]
 [test_Preferences.js]
 [test_Promise.js]
 [test_propertyListsUtils.js]
 [test_readCertPrefs.js]
 [test_Services.js]
 [test_sqlite.js]
 [test_sqlite_shutdown.js]
 [test_task.js]
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -23,16 +23,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
                                   "resource://gre/modules/LightweightThemeManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ZipUtils",
                                   "resource://gre/modules/ZipUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PermissionsUtils",
+                                  "resource://gre/modules/PermissionsUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
                                   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserToolboxProcess",
                                   "resource:///modules/devtools/ToolboxProcess.jsm");
@@ -71,16 +73,17 @@ const PREF_EM_ENABLED_ADDONS          = 
 const PREF_EM_EXTENSION_FORMAT        = "extensions.";
 const PREF_EM_ENABLED_SCOPES          = "extensions.enabledScopes";
 const PREF_EM_AUTO_DISABLED_SCOPES    = "extensions.autoDisableScopes";
 const PREF_EM_SHOW_MISMATCH_UI        = "extensions.showMismatchUI";
 const PREF_XPI_ENABLED                = "xpinstall.enabled";
 const PREF_XPI_WHITELIST_REQUIRED     = "xpinstall.whitelist.required";
 const PREF_XPI_DIRECT_WHITELISTED     = "xpinstall.whitelist.directRequest";
 const PREF_XPI_FILE_WHITELISTED       = "xpinstall.whitelist.fileRequest";
+const PREF_XPI_PERMISSIONS_BRANCH     = "xpinstall.";
 const PREF_XPI_UNPACK                 = "extensions.alwaysUnpack";
 const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts";
 const PREF_INSTALL_DISTRO_ADDONS      = "extensions.installDistroAddons";
 const PREF_BRANCH_INSTALLED_ADDON     = "extensions.installedDistroAddon.";
 const PREF_SHOWN_SELECTION_UI         = "extensions.shownSelectionUI";
 
 const PREF_EM_MIN_COMPAT_APP_VERSION      = "extensions.minCompatibleAppVersion";
 const PREF_EM_MIN_COMPAT_PLATFORM_VERSION = "extensions.minCompatiblePlatformVersion";
@@ -3456,16 +3459,25 @@ this.XPIProvider = {
 
     // Clear out any cached migration data.
     XPIDatabase.migrateData = null;
 
     return changed;
   },
 
   /**
+   * Imports the xpinstall permissions from preferences into the permissions
+   * manager for the user to change later.
+   */
+  importPermissions: function XPI_importPermissions() {
+    PermissionsUtils.importFromPrefs(PREF_XPI_PERMISSIONS_BRANCH,
+                                     XPI_PERMISSION);
+  },
+
+  /**
    * Checks for any changes that have occurred since the last time the
    * application was launched.
    *
    * @param  aAppChanged
    *         A tri-state value. Undefined means the current profile was created
    *         for this session, true means the profile already existed but was
    *         last used with an application with a different version number,
    *         false means that the profile was last used by this version of the
@@ -3705,16 +3717,18 @@ this.XPIProvider = {
     if (!aUri)
       return this.isDirectRequestWhitelisted();
 
     // Local referrers can be whitelisted.
     if (this.isFileRequestWhitelisted() &&
         (aUri.schemeIs("chrome") || aUri.schemeIs("file")))
       return true;
 
+    this.importPermissions();
+
     let permission = Services.perms.testPermission(aUri, XPI_PERMISSION);
     if (permission == Ci.nsIPermissionManager.DENY_ACTION)
       return false;
 
     let requireWhitelist = Preferences.get(PREF_XPI_WHITELIST_REQUIRED, true);
     if (requireWhitelist && (permission != Ci.nsIPermissionManager.ALLOW_ACTION))
       return false;
 
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_permissions.js
@@ -0,0 +1,76 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+// Checks that permissions set in preferences are correctly imported but can
+// be removed by the user.
+
+const XPI_MIMETYPE = "application/x-xpinstall";
+
+function run_test() {
+  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2", "2");
+
+  Services.prefs.setCharPref("xpinstall.whitelist.add", "test1.com,test2.com");
+  Services.prefs.setCharPref("xpinstall.whitelist.add.36", "test3.com,www.test4.com");
+  Services.prefs.setCharPref("xpinstall.whitelist.add.test5", "test5.com");
+
+  Services.perms.add(NetUtil.newURI("http://www.test9.com"), "install",
+                     AM_Ci.nsIPermissionManager.ALLOW_ACTION);
+
+  startupManager();
+
+  do_check_true(AddonManager.isInstallAllowed(XPI_MIMETYPE,
+                                              NetUtil.newURI("http://test1.com")));
+  do_check_true(AddonManager.isInstallAllowed(XPI_MIMETYPE,
+                                              NetUtil.newURI("https://www.test2.com")));
+  do_check_true(AddonManager.isInstallAllowed(XPI_MIMETYPE,
+                                              NetUtil.newURI("https://test3.com")));
+  do_check_false(AddonManager.isInstallAllowed(XPI_MIMETYPE,
+                                               NetUtil.newURI("https://test4.com")));
+  do_check_true(AddonManager.isInstallAllowed(XPI_MIMETYPE,
+                                              NetUtil.newURI("https://www.test4.com")));
+  do_check_true(AddonManager.isInstallAllowed(XPI_MIMETYPE,
+                                              NetUtil.newURI("http://www.test5.com")));
+
+  do_check_false(AddonManager.isInstallAllowed(XPI_MIMETYPE,
+                                               NetUtil.newURI("http://www.test6.com")));
+  do_check_false(AddonManager.isInstallAllowed(XPI_MIMETYPE,
+                                               NetUtil.newURI("http://test7.com")));
+  do_check_false(AddonManager.isInstallAllowed(XPI_MIMETYPE,
+                                               NetUtil.newURI("http://www.test8.com")));
+
+  // This should remain unaffected
+  do_check_true(AddonManager.isInstallAllowed(XPI_MIMETYPE,
+                                              NetUtil.newURI("http://www.test9.com")));
+  do_check_true(AddonManager.isInstallAllowed(XPI_MIMETYPE,
+                                              NetUtil.newURI("https://www.test9.com")));
+
+  Services.perms.removeAll();
+
+  do_check_false(AddonManager.isInstallAllowed(XPI_MIMETYPE,
+                                               NetUtil.newURI("http://test1.com")));
+  do_check_false(AddonManager.isInstallAllowed(XPI_MIMETYPE,
+                                               NetUtil.newURI("https://www.test2.com")));
+  do_check_false(AddonManager.isInstallAllowed(XPI_MIMETYPE,
+                                               NetUtil.newURI("https://test3.com")));
+  do_check_false(AddonManager.isInstallAllowed(XPI_MIMETYPE,
+                                               NetUtil.newURI("https://www.test4.com")));
+  do_check_false(AddonManager.isInstallAllowed(XPI_MIMETYPE,
+                                               NetUtil.newURI("http://www.test5.com")));
+
+  // Upgrade the application and verify that the permissions are still not there
+  restartManager("2");
+
+  do_check_false(AddonManager.isInstallAllowed(XPI_MIMETYPE,
+                                               NetUtil.newURI("http://test1.com")));
+  do_check_false(AddonManager.isInstallAllowed(XPI_MIMETYPE,
+                                               NetUtil.newURI("https://www.test2.com")));
+  do_check_false(AddonManager.isInstallAllowed(XPI_MIMETYPE,
+                                               NetUtil.newURI("https://test3.com")));
+  do_check_false(AddonManager.isInstallAllowed(XPI_MIMETYPE,
+                                               NetUtil.newURI("https://www.test4.com")));
+  do_check_false(AddonManager.isInstallAllowed(XPI_MIMETYPE,
+                                               NetUtil.newURI("http://www.test5.com")));
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_permissions_prefs.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Tests that xpinstall.[whitelist|blacklist].add preferences are emptied when
+// converted into permissions.
+
+const PREF_XPI_WHITELIST_PERMISSIONS  = "xpinstall.whitelist.add";
+const PREF_XPI_BLACKLIST_PERMISSIONS  = "xpinstall.blacklist.add";
+
+function do_check_permission_prefs(preferences) {
+  // Check preferences were emptied
+  for (let pref of preferences) {
+    try {
+      do_check_eq(Services.prefs.getCharPref(pref), "");
+    }
+    catch (e) {
+      // Successfully emptied
+    }
+  }
+}
+
+function clear_imported_preferences_cache() {
+  let scope = Components.utils.import("resource://gre/modules/PermissionsUtils.jsm", {});
+  scope.gImportedPrefBranches.clear();
+}
+
+function run_test() {
+  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");
+
+  // Create own preferences to test
+  Services.prefs.setCharPref("xpinstall.whitelist.add.EMPTY", "");
+  Services.prefs.setCharPref("xpinstall.whitelist.add.TEST", "whitelist.example.com");
+  Services.prefs.setCharPref("xpinstall.blacklist.add.EMPTY", "");
+  Services.prefs.setCharPref("xpinstall.blacklist.add.TEST", "blacklist.example.com");
+
+  // Get list of preferences to check
+  var whitelistPreferences = Services.prefs.getChildList(PREF_XPI_WHITELIST_PERMISSIONS, {});
+  var blacklistPreferences = Services.prefs.getChildList(PREF_XPI_BLACKLIST_PERMISSIONS, {});
+  var preferences = whitelistPreferences.concat(blacklistPreferences);
+
+  startupManager();
+
+  // Permissions are imported lazily - act as thought we're checking an install,
+  // to trigger on-deman importing of the permissions.
+  let url = Services.io.newURI("http://example.com/file.xpi", null, null);
+  AddonManager.isInstallAllowed("application/x-xpinstall", url);
+  do_check_permission_prefs(preferences);
+
+
+  // Import can also be triggerred by an observer notification by any other area
+  // of code, such as a permissions management UI.
+
+  // First, request to flush all permissions
+  clear_imported_preferences_cache();
+  Services.prefs.setCharPref("xpinstall.whitelist.add.TEST2", "whitelist2.example.com");
+  Services.obs.notifyObservers(null, "flush-pending-permissions", "install");
+  do_check_permission_prefs(preferences);
+
+  // Then, request to flush just install permissions
+  clear_imported_preferences_cache();
+  Services.prefs.setCharPref("xpinstall.whitelist.add.TEST3", "whitelist3.example.com");
+  Services.obs.notifyObservers(null, "flush-pending-permissions", "");
+  do_check_permission_prefs(preferences);
+
+  // And a request to flush some other permissions sholdn't flush install permissions
+  clear_imported_preferences_cache();
+  Services.prefs.setCharPref("xpinstall.whitelist.add.TEST4", "whitelist4.example.com");
+  Services.obs.notifyObservers(null, "flush-pending-permissions", "lolcats");
+  do_check_eq(Services.prefs.getCharPref("xpinstall.whitelist.add.TEST4"), "whitelist4.example.com");
+}
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
@@ -214,16 +214,18 @@ skip-if = os == "android"
 [test_migrate4.js]
 # Times out during parallel runs on desktop
 requesttimeoutfactor = 2
 [test_migrate5.js]
 [test_migrateAddonRepository.js]
 [test_migrate_max_version.js]
 [test_no_addons.js]
 [test_onPropertyChanged_appDisabled.js]
+[test_permissions.js]
+[test_permissions_prefs.js]
 [test_plugins.js]
 skip-if = buildapp == "mulet"
 [test_pluginchange.js]
 # PluginProvider.jsm is not shipped on Android
 skip-if = os == "android"
 [test_pluginBlocklistCtp.js]
 # Bug 676992: test consistently fails on Android
 fail-if = buildapp == "mulet" || os == "android"