Bug 1449021 - Policy for install/uninstalling add-ons. r?felipe
MozReview-Commit-ID: HBcbs4hWfEA
--- a/browser/components/enterprisepolicies/Policies.jsm
+++ b/browser/components/enterprisepolicies/Policies.jsm
@@ -8,16 +8,17 @@ ChromeUtils.import("resource://gre/modul
ChromeUtils.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gXulStore",
"@mozilla.org/xul/xulstore;1",
"nsIXULStore");
XPCOMUtils.defineLazyModuleGetters(this, {
BookmarksPolicies: "resource:///modules/policies/BookmarksPolicies.jsm",
ProxyPolicies: "resource:///modules/policies/ProxyPolicies.jsm",
+ AddonManager: "resource://gre/modules/AddonManager.jsm",
});
const PREF_LOGLEVEL = "browser.policies.loglevel";
const BROWSER_DOCUMENT_URL = "chrome://browser/content/browser.xul";
XPCOMUtils.defineLazyGetter(this, "log", () => {
let { ConsoleAPI } = ChromeUtils.import("resource://gre/modules/Console.jsm", {});
return new ConsoleAPI({
@@ -274,16 +275,73 @@ var Policies = {
}
} else {
setAndLockPref("privacy.trackingprotection.enabled", false);
setAndLockPref("privacy.trackingprotection.pbmode.enabled", false);
}
}
},
+ "Extensions": {
+ onBeforeUIStartup(manager, param) {
+ if ("Install" in param) {
+ runOncePerModification("extensionsInstall", JSON.stringify(param.Install), () => {
+ for (let location of param.Install) {
+ let url;
+ if (location.includes("://")) {
+ // Assume location is an URI
+ url = location;
+ } else {
+ // Assume location is a file path
+ let xpiFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ try {
+ xpiFile.initWithPath(location);
+ } catch (e) {
+ log.error(`Invalid extension path location - ${location}`);
+ continue;
+ }
+ url = Services.io.newFileURI(xpiFile).spec;
+ }
+ AddonManager.getInstallForURL(url, (install) => {
+ let listener = {
+ onDownloadFailed: () => {
+ log.error(`Download failed - ${location}`);
+ },
+ onInstallFailed: () => {
+ log.error(`Installation failed - ${location}`);
+ },
+ onInstallEnded: () => {
+ log.debug(`Installation succeeded - ${location}`);
+ }
+ };
+ install.addListener(listener);
+ install.install();
+ }, "application/x-xpinstall");
+ }
+ });
+ }
+ if ("Uninstall" in param) {
+ runOncePerModification("extensionsUninstall", JSON.stringify(param.Uninstall), () => {
+ AddonManager.getAddonsByIDs(param.Uninstall, (addons) => {
+ for (let addon of addons) {
+ if (addon) {
+ addon.uninstall();
+ }
+ }
+ });
+ });
+ }
+ if ("Locked" in param) {
+ for (let ID of param.Locked) {
+ manager.disallowFeature(`modify-extension:${ID}`);
+ }
+ }
+ }
+ },
+
"FlashPlugin": {
onBeforeUIStartup(manager, param) {
addAllowDenyPermissions("plugin:flash", param.Allow, param.Block);
}
},
"Homepage": {
onBeforeUIStartup(manager, param) {
--- a/browser/components/enterprisepolicies/schemas/policies-schema.json
+++ b/browser/components/enterprisepolicies/schemas/policies-schema.json
@@ -217,16 +217,44 @@
},
"Locked": {
"type": "boolean"
}
},
"required": ["Value"]
},
+ "Extensions": {
+ "description": "Install, uninstall or lock extensions. The Install option takes URLs or paths as parameters. The Uninstall and Locked options take extension IDs.",
+ "first_available": "60.0",
+ "enterprise_only": true,
+
+ "type": "object",
+ "properties": {
+ "Install" : {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "Uninstall" : {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "Locked" : {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ },
+
"FlashPlugin": {
"description": "Allow or deny flash plugin usage.",
"first_available": "60.0",
"type": "object",
"properties": {
"Allow": {
"type": "array",
--- a/browser/components/enterprisepolicies/tests/browser/browser.ini
+++ b/browser/components/enterprisepolicies/tests/browser/browser.ini
@@ -1,15 +1,16 @@
[DEFAULT]
support-files =
head.js
config_popups_cookies_addons_flash.json
config_broken_json.json
opensearch.html
opensearchEngine.xml
+ policytest.xpi
[browser_policies_basic_tests.js]
[browser_policies_broken_json.js]
[browser_policies_enterprise_only.js]
[browser_policies_notice_in_aboutpreferences.js]
[browser_policies_popups_cookies_addons_flash.js]
[browser_policies_runOnce_helper.js]
[browser_policies_setAndLockPref_API.js]
@@ -30,11 +31,12 @@ support-files =
[browser_policy_disable_masterpassword.js]
[browser_policy_disable_pdfjs.js]
[browser_policy_disable_pocket.js]
[browser_policy_disable_privatebrowsing.js]
[browser_policy_disable_safemode.js]
[browser_policy_disable_shield.js]
[browser_policy_display_bookmarks.js]
[browser_policy_display_menu.js]
+[browser_policy_extensions.js]
[browser_policy_proxy.js]
[browser_policy_search_engine.js]
[browser_policy_set_homepage.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_extensions.js
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const addonID = "policytest@mozilla.com";
+
+add_task(async function test_addon_install() {
+ await setupPolicyEngineWithJson({
+ "policies": {
+ "Extensions": {
+ "Install": [
+ "http://mochi.test:8888/browser/browser/components/enterprisepolicies/tests/browser/policytest.xpi"
+ ],
+ "Locked": [
+ addonID
+ ]
+ }
+ }
+ });
+ await wait_for_addon_install();
+ let addon = await AddonManager.getAddonByID(addonID);
+ isnot(addon, null, "Addon not installed.");
+});
+
+add_task(async function test_addon_locked() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ await BrowserOpenAddonsMgr("addons://list/extension");
+ // eslint-disable-next-line no-shadow
+ await ContentTask.spawn(tab.linkedBrowser, {addonID}, async function({addonID}) {
+ let list = content.document.getElementById("addon-list");
+ let flashEntry = list.getElementsByAttribute("value", addonID)[0];
+ let disableBtn = content.document.getAnonymousElementByAttribute(flashEntry, "anonid", "disable-btn");
+ let removeBtn = content.document.getAnonymousElementByAttribute(flashEntry, "anonid", "remove-btn");
+ is(removeBtn.hidden, true, "Remove button should be hidden");
+ is(disableBtn.hidden, true, "Disable button should be hidden");
+ });
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_addon_uninstall() {
+ await setupPolicyEngineWithJson({
+ "policies": {
+ "Extensions": {
+ "Uninstall": [
+ addonID
+ ]
+ }
+ }
+ });
+ let addon = await AddonManager.getAddonByID(addonID);
+ is(addon, null, "Addon should be uninstalled.");
+});
+
+function wait_for_addon_install() {
+ return new Promise((resolve, reject) => {
+ AddonManager.addInstallListener({
+ onInstallEnded(install, addon) {
+ if (addon.id == addonID)
+ resolve();
+ },
+ onDownloadFailed: (install) => {
+ reject();
+ },
+ onInstallFailed: (install) => {
+ reject();
+ },
+ });
+ });
+}
new file mode 100644
index 0000000000000000000000000000000000000000..ee2a6289ee5c2abd373f4aefdae1356110c18596
GIT binary patch
literal 305
zc$^FHW@Zs#U|`^2cwSKLv+`YjW+RZd5r}yiWEgT2^D@&?i%ayfiu3bALpT|j6Q}Kp
z0^!mMZU#n{uZ#=~VC}Abd-)C-@U(olX`Lz=df>#7OLH4#BM-WY{R~c8=ybJwy|9Xe
z&7Jb{Yr1R)*m`UNr{3=NdMfgkvu}5r3(t~6eSs^U9Gd!IlFe@Om`pK=^o2LLZU5%q
z{#)?F=uG5ijZ>?ox{a3d7oGnynd`^BsGa}CVvG1*9=`YZLC^-<Ewjp2-((2zW@M6M
n#^n<U1`q%`mf;0R5Zzy_5PzZhDZrbR4Wf;aAs9%X02=}T?uTd9
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -4721,16 +4721,22 @@ AddonInternal.prototype = {
if (this.type != "experiment" &&
!this._installLocation.isLinkedAddon(this.id) && !isSystem) {
permissions |= AddonManager.PERM_CAN_UPGRADE;
}
permissions |= AddonManager.PERM_CAN_UNINSTALL;
}
+ if (Services.policies &&
+ !Services.policies.isAllowed(`modify-extension:${this.id}`)) {
+ permissions &= ~AddonManager.PERM_CAN_UNINSTALL;
+ permissions &= ~AddonManager.PERM_CAN_DISABLE;
+ }
+
return permissions;
},
};
/**
* The AddonWrapper wraps an Addon to provide the data visible to consumers of
* the public API.
*/