зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1522823 - Policy for whitelist/blacklist addons by ID. r=aswan,flod
Differential Revision: https://phabricator.services.mozilla.com/D27902 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
67fb859dcb
Коммит
a98b93897c
|
@ -580,6 +580,16 @@ var gXPInstallObserver = {
|
|||
args = [brandShortName, Services.appinfo.version, install.name];
|
||||
}
|
||||
|
||||
if (install.addon && !Services.policies.mayInstallAddon(install.addon)) {
|
||||
error = "addonInstallBlockedByPolicy";
|
||||
let extensionSettings = Services.policies.getExtensionSettings(install.addon.id);
|
||||
let message = "";
|
||||
if (extensionSettings && "blocked_install_message" in extensionSettings) {
|
||||
message = " " + extensionSettings.blocked_install_message;
|
||||
}
|
||||
args = [install.name, install.addon.id, message];
|
||||
}
|
||||
|
||||
// Add Learn More link when refusing to install an unsigned add-on
|
||||
if (install.error == AddonManager.ERROR_SIGNEDSTATE_REQUIRED) {
|
||||
options.learnMoreURL = Services.urlFormatter.formatURLPref("app.support.baseURL") + "unsigned-addons";
|
||||
|
|
|
@ -542,7 +542,7 @@ var Policies = {
|
|||
await addon.uninstall();
|
||||
} catch (e) {
|
||||
// This can fail for add-ons that can't be uninstalled.
|
||||
// Just ignore.
|
||||
log.debug(`Add-on ID (${addon.id}) couldn't be uninstalled.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -552,61 +552,29 @@ var Policies = {
|
|||
runOncePerModification("extensionsInstall", JSON.stringify(param.Install), async () => {
|
||||
await uninstallingPromise;
|
||||
for (let location of param.Install) {
|
||||
let url;
|
||||
if (location.includes("://")) {
|
||||
// Assume location is an URI
|
||||
url = location;
|
||||
} else {
|
||||
let uri;
|
||||
try {
|
||||
uri = Services.io.newURI(location);
|
||||
} catch (e) {
|
||||
// If it's not a URL, it's probably a file path.
|
||||
// Assume location is a file path
|
||||
let xpiFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
|
||||
// This is done for legacy support (old API)
|
||||
try {
|
||||
xpiFile.initWithPath(location);
|
||||
} catch (e) {
|
||||
let xpiFile = new FileUtils.File(location);
|
||||
uri = Services.io.newFileURI(xpiFile);
|
||||
} catch (ex) {
|
||||
log.error(`Invalid extension path location - ${location}`);
|
||||
continue;
|
||||
}
|
||||
url = Services.io.newFileURI(xpiFile).spec;
|
||||
}
|
||||
AddonManager.getInstallForURL(url, {
|
||||
telemetryInfo: {source: "enterprise-policy"},
|
||||
}).then(install => {
|
||||
if (install.addon && install.addon.appDisabled) {
|
||||
log.error(`Incompatible add-on - ${location}`);
|
||||
install.cancel();
|
||||
return;
|
||||
}
|
||||
let listener = {
|
||||
/* eslint-disable-next-line no-shadow */
|
||||
onDownloadEnded: (install) => {
|
||||
if (install.addon && install.addon.appDisabled) {
|
||||
log.error(`Incompatible add-on - ${location}`);
|
||||
install.removeListener(listener);
|
||||
install.cancel();
|
||||
}
|
||||
},
|
||||
onDownloadFailed: () => {
|
||||
install.removeListener(listener);
|
||||
log.error(`Download failed - ${location}`);
|
||||
clearRunOnceModification("extensionsInstall");
|
||||
},
|
||||
onInstallFailed: () => {
|
||||
install.removeListener(listener);
|
||||
log.error(`Installation failed - ${location}`);
|
||||
},
|
||||
onInstallEnded: () => {
|
||||
install.removeListener(listener);
|
||||
log.debug(`Installation succeeded - ${location}`);
|
||||
},
|
||||
};
|
||||
install.addListener(listener);
|
||||
install.install();
|
||||
});
|
||||
}
|
||||
installAddonFromURL(uri.spec);
|
||||
}
|
||||
});
|
||||
}
|
||||
if ("Locked" in param) {
|
||||
for (let ID of param.Locked) {
|
||||
manager.disallowFeature(`modify-extension:${ID}`);
|
||||
manager.disallowFeature(`uninstall-extension:${ID}`);
|
||||
manager.disallowFeature(`disable-extension:${ID}`);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -614,7 +582,79 @@ var Policies = {
|
|||
|
||||
"ExtensionSettings": {
|
||||
onBeforeAddons(manager, param) {
|
||||
manager.setExtensionSettings(param);
|
||||
try {
|
||||
manager.setExtensionSettings(param);
|
||||
} catch (e) {
|
||||
log.error("Invalid ExtensionSettings");
|
||||
}
|
||||
},
|
||||
async onBeforeUIStartup(manager, param) {
|
||||
let extensionSettings = param;
|
||||
let blockAllExtensions = false;
|
||||
if ("*" in extensionSettings) {
|
||||
if ("installation_mode" in extensionSettings["*"] &&
|
||||
extensionSettings["*"].installation_mode == "blocked") {
|
||||
blockAllExtensions = true;
|
||||
// Turn off discovery pane in about:addons
|
||||
setAndLockPref("extensions.getAddons.showPane", false);
|
||||
// Block about:debugging
|
||||
blockAboutPage(manager, "about:debugging");
|
||||
}
|
||||
}
|
||||
let {addons} = await AddonManager.getActiveAddons();
|
||||
let allowedExtensions = [];
|
||||
for (let extensionID in extensionSettings) {
|
||||
if (extensionID == "*") {
|
||||
// Ignore global settings
|
||||
continue;
|
||||
}
|
||||
if ("installation_mode" in extensionSettings[extensionID]) {
|
||||
if (extensionSettings[extensionID].installation_mode == "force_installed" ||
|
||||
extensionSettings[extensionID].installation_mode == "normal_installed") {
|
||||
if (!extensionSettings[extensionID].install_url) {
|
||||
throw new Error(`Missing install_url for ${extensionID}`);
|
||||
}
|
||||
if (!addons.find(addon => addon.id == extensionID)) {
|
||||
installAddonFromURL(extensionSettings[extensionID].install_url, extensionID);
|
||||
}
|
||||
manager.disallowFeature(`uninstall-extension:${extensionID}`);
|
||||
if (extensionSettings[extensionID].installation_mode == "force_installed") {
|
||||
manager.disallowFeature(`disable-extension:${extensionID}`);
|
||||
}
|
||||
allowedExtensions.push(extensionID);
|
||||
} else if (extensionSettings[extensionID].installation_mode == "allowed") {
|
||||
allowedExtensions.push(extensionID);
|
||||
} else if (extensionSettings[extensionID].installation_mode == "blocked") {
|
||||
if (addons.find(addon => addon.id == extensionID)) {
|
||||
// Can't use the addon from getActiveAddons since it doesn't have uninstall.
|
||||
let addon = await AddonManager.getAddonByID(extensionID);
|
||||
try {
|
||||
await addon.uninstall();
|
||||
} catch (e) {
|
||||
// This can fail for add-ons that can't be uninstalled.
|
||||
log.debug(`Add-on ID (${addon.id}) couldn't be uninstalled.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (blockAllExtensions) {
|
||||
for (let addon of addons) {
|
||||
if (addon.isSystem || addon.isBuiltin) {
|
||||
continue;
|
||||
}
|
||||
if (!allowedExtensions.includes(addon.id)) {
|
||||
try {
|
||||
// Can't use the addon from getActiveAddons since it doesn't have uninstall.
|
||||
let addonToUninstall = await AddonManager.getAddonByID(addon.id);
|
||||
await addonToUninstall.uninstall();
|
||||
} catch (e) {
|
||||
// This can fail for add-ons that can't be uninstalled.
|
||||
log.debug(`Add-on ID (${addon.id}) couldn't be uninstalled.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -1304,6 +1344,54 @@ function replacePathVariables(path) {
|
|||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* installAddonFromURL
|
||||
*
|
||||
* Helper function that installs an addon from a URL
|
||||
* and verifies that the addon ID matches.
|
||||
*/
|
||||
function installAddonFromURL(url, extensionID) {
|
||||
AddonManager.getInstallForURL(url, {
|
||||
telemetryInfo: {source: "enterprise-policy"},
|
||||
}).then(install => {
|
||||
if (install.addon && install.addon.appDisabled) {
|
||||
log.error(`Incompatible add-on - ${location}`);
|
||||
install.cancel();
|
||||
return;
|
||||
}
|
||||
let listener = {
|
||||
/* eslint-disable-next-line no-shadow */
|
||||
onDownloadEnded: (install) => {
|
||||
if (extensionID && install.addon.id != extensionID) {
|
||||
log.error(`Add-on downloaded from ${url} had unexpected id (got ${install.addon.id} expected ${extensionID})`);
|
||||
install.removeListener(listener);
|
||||
install.cancel();
|
||||
}
|
||||
if (install.addon && install.addon.appDisabled) {
|
||||
log.error(`Incompatible add-on - ${url}`);
|
||||
install.removeListener(listener);
|
||||
install.cancel();
|
||||
}
|
||||
},
|
||||
onDownloadFailed: () => {
|
||||
install.removeListener(listener);
|
||||
log.error(`Download failed - ${url}`);
|
||||
clearRunOnceModification("extensionsInstall");
|
||||
},
|
||||
onInstallFailed: () => {
|
||||
install.removeListener(listener);
|
||||
log.error(`Installation failed - ${url}`);
|
||||
},
|
||||
onInstallEnded: () => {
|
||||
install.removeListener(listener);
|
||||
log.debug(`Installation succeeded - ${url}`);
|
||||
},
|
||||
};
|
||||
install.addListener(listener);
|
||||
install.install();
|
||||
});
|
||||
}
|
||||
|
||||
let gChromeURLSBlocked = false;
|
||||
|
||||
// If any about page is blocked, we block the loading of all
|
||||
|
|
|
@ -323,10 +323,44 @@
|
|||
|
||||
"ExtensionSettings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"*": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"installation_mode": {
|
||||
"type": "string",
|
||||
"enum": ["allowed", "blocked"]
|
||||
},
|
||||
"allowed_types": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": ["extension", "dictionary", "locale", "theme"]
|
||||
}
|
||||
},
|
||||
"blocked_install_message": {
|
||||
"type": "string"
|
||||
},
|
||||
"install_sources": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"patternProperties": {
|
||||
"^.*$": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"installation_mode": {
|
||||
"type": "string",
|
||||
"enum": ["allowed", "blocked", "force_installed", "normal_installed"]
|
||||
},
|
||||
"install_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"blocked_install_message": {
|
||||
"type": "string"
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ support-files =
|
|||
policy_websitefilter_exception.html
|
||||
../../../../../toolkit/components/antitracking/test/browser/page.html
|
||||
../../../../../toolkit/components/antitracking/test/browser/subResources.sjs
|
||||
extensionsettings.html
|
||||
|
||||
[browser_policies_getActivePolicies.js]
|
||||
skip-if = os != 'mac'
|
||||
|
|
|
@ -2,22 +2,206 @@
|
|||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
add_task(async function test_extensionsettings() {
|
||||
const BASE_URL = "http://mochi.test:8888/browser/browser/components/enterprisepolicies/tests/browser/";
|
||||
|
||||
/**
|
||||
* Wait for the given PopupNotification to display
|
||||
*
|
||||
* @param {string} name
|
||||
* The name of the notification to wait for.
|
||||
*
|
||||
* @returns {Promise}
|
||||
* Resolves with the notification window.
|
||||
*/
|
||||
function promisePopupNotificationShown(name) {
|
||||
return new Promise(resolve => {
|
||||
function popupshown() {
|
||||
let notification = PopupNotifications.getNotification(name);
|
||||
if (!notification) { return; }
|
||||
|
||||
ok(notification, `${name} notification shown`);
|
||||
ok(PopupNotifications.isPanelOpen, "notification panel open");
|
||||
|
||||
PopupNotifications.panel.removeEventListener("popupshown", popupshown);
|
||||
resolve(PopupNotifications.panel.firstElementChild);
|
||||
}
|
||||
|
||||
PopupNotifications.panel.addEventListener("popupshown", popupshown);
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function test_install_source_blocked_link() {
|
||||
await setupPolicyEngineWithJson({
|
||||
"policies": {
|
||||
"ExtensionSettings": {
|
||||
"extension1@mozilla.com": {
|
||||
"blocked_install_message": "Extension1 error message.",
|
||||
},
|
||||
"*": {
|
||||
"blocked_install_message": "Generic error message.",
|
||||
"install_sources": ["http://blocks.other.install.sources/*"],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
let popupPromise = promisePopupNotificationShown("addon-install-origin-blocked");
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab({gBrowser,
|
||||
opening: BASE_URL + "extensionsettings.html",
|
||||
waitForStateStop: true});
|
||||
|
||||
let extensionSettings = Services.policies.getExtensionSettings("extension1@mozilla.com");
|
||||
is(extensionSettings.blocked_install_message, "Extension1 error message.", "Should have extension specific message.");
|
||||
extensionSettings = Services.policies.getExtensionSettings("extension2@mozilla.com");
|
||||
is(extensionSettings.blocked_install_message, "Generic error message.", "Should have generic message.");
|
||||
await ContentTask.spawn(tab.linkedBrowser, {}, () => {
|
||||
content.document.getElementById("policytest").click();
|
||||
});
|
||||
await popupPromise;
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(async function test_install_source_blocked_installtrigger() {
|
||||
await setupPolicyEngineWithJson({
|
||||
"policies": {
|
||||
"ExtensionSettings": {
|
||||
"*": {
|
||||
"install_sources": ["http://blocks.other.install.sources/*"],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
let popupPromise = promisePopupNotificationShown("addon-install-origin-blocked");
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab({gBrowser,
|
||||
opening: BASE_URL + "extensionsettings.html",
|
||||
waitForStateStop: true});
|
||||
|
||||
await ContentTask.spawn(tab.linkedBrowser, {}, () => {
|
||||
content.document.getElementById("policytest_installtrigger").click();
|
||||
});
|
||||
await popupPromise;
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(async function test_install_source_blocked_otherdomain() {
|
||||
await setupPolicyEngineWithJson({
|
||||
"policies": {
|
||||
"ExtensionSettings": {
|
||||
"*": {
|
||||
"install_sources": ["http://mochi.test/*"],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
let popupPromise = promisePopupNotificationShown("addon-install-origin-blocked");
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab({gBrowser,
|
||||
opening: BASE_URL + "extensionsettings.html",
|
||||
waitForStateStop: true});
|
||||
|
||||
await ContentTask.spawn(tab.linkedBrowser, {}, () => {
|
||||
content.document.getElementById("policytest_otherdomain").click();
|
||||
});
|
||||
await popupPromise;
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(async function test_install_source_blocked_direct() {
|
||||
await setupPolicyEngineWithJson({
|
||||
"policies": {
|
||||
"ExtensionSettings": {
|
||||
"*": {
|
||||
"install_sources": ["http://blocks.other.install.sources/*"],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
let popupPromise = promisePopupNotificationShown("addon-install-origin-blocked");
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab({gBrowser,
|
||||
opening: BASE_URL + "extensionsettings.html",
|
||||
waitForStateStop: true});
|
||||
|
||||
await ContentTask.spawn(tab.linkedBrowser, {baseUrl: BASE_URL}, async function({baseUrl}) {
|
||||
content.document.location.href = baseUrl + "policytest_v0.1.xpi";
|
||||
});
|
||||
await popupPromise;
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(async function test_install_source_allowed_link() {
|
||||
await setupPolicyEngineWithJson({
|
||||
"policies": {
|
||||
"ExtensionSettings": {
|
||||
"*": {
|
||||
"install_sources": ["http://mochi.test/*"],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
let popupPromise = promisePopupNotificationShown("addon-webext-permissions");
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab({gBrowser,
|
||||
opening: BASE_URL + "extensionsettings.html",
|
||||
waitForStateStop: true});
|
||||
|
||||
await ContentTask.spawn(tab.linkedBrowser, {}, () => {
|
||||
content.document.getElementById("policytest").click();
|
||||
});
|
||||
await popupPromise;
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(async function test_install_source_allowed_installtrigger() {
|
||||
await setupPolicyEngineWithJson({
|
||||
"policies": {
|
||||
"ExtensionSettings": {
|
||||
"*": {
|
||||
"install_sources": ["http://mochi.test/*"],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
let popupPromise = promisePopupNotificationShown("addon-webext-permissions");
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab({gBrowser,
|
||||
opening: BASE_URL + "extensionsettings.html",
|
||||
waitForStateStop: true});
|
||||
|
||||
await ContentTask.spawn(tab.linkedBrowser, {}, () => {
|
||||
content.document.getElementById("policytest_installtrigger").click();
|
||||
});
|
||||
await popupPromise;
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(async function test_install_source_allowed_otherdomain() {
|
||||
await setupPolicyEngineWithJson({
|
||||
"policies": {
|
||||
"ExtensionSettings": {
|
||||
"*": {
|
||||
"install_sources": ["http://mochi.test/*", "http://example.org/*"],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
let popupPromise = promisePopupNotificationShown("addon-webext-permissions");
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab({gBrowser,
|
||||
opening: BASE_URL + "extensionsettings.html",
|
||||
waitForStateStop: true});
|
||||
|
||||
await ContentTask.spawn(tab.linkedBrowser, {}, () => {
|
||||
content.document.getElementById("policytest_otherdomain").click();
|
||||
});
|
||||
await popupPromise;
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(async function test_install_source_allowed_direct() {
|
||||
await setupPolicyEngineWithJson({
|
||||
"policies": {
|
||||
"ExtensionSettings": {
|
||||
"*": {
|
||||
"install_sources": ["http://mochi.test/*"],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
let popupPromise = promisePopupNotificationShown("addon-webext-permissions");
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab({gBrowser,
|
||||
opening: BASE_URL + "extensionsettings.html",
|
||||
waitForStateStop: true});
|
||||
|
||||
await ContentTask.spawn(tab.linkedBrowser, {baseUrl: BASE_URL}, async function({baseUrl}) {
|
||||
content.document.location.href = baseUrl + "policytest_v0.1.xpi";
|
||||
});
|
||||
await popupPromise;
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<script type="text/javascript">
|
||||
function installTrigger(url) {
|
||||
InstallTrigger.install({extension: url});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
<a id="policytest" href="policytest_v0.1.xpi">policytest@mozilla.com</a>
|
||||
</p>
|
||||
<p>
|
||||
<a id="policytest_installtrigger" onclick="installTrigger(this.href);return false;" href="policytest_v0.1.xpi">policytest@mozilla.com</a>
|
||||
</p>
|
||||
<p>
|
||||
<a id="policytest_otherdomain" href="http://example.org:80/browser/browser/components/enterprisepolicies/tests/browser/policytest_v0.1.xpi">policytest@mozilla.com</a>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,155 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
const {AddonTestUtils} = ChromeUtils.import("resource://testing-common/AddonTestUtils.jsm");
|
||||
const {AddonManager} = ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
|
||||
|
||||
AddonTestUtils.init(this);
|
||||
AddonTestUtils.overrideCertDB();
|
||||
AddonTestUtils.createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "48", "48");
|
||||
|
||||
const server = AddonTestUtils.createHttpServer({hosts: ["example.com"]});
|
||||
const BASE_URL = `http://example.com/data`;
|
||||
|
||||
let addonID = "policytest2@mozilla.com";
|
||||
|
||||
add_task(async function setup() {
|
||||
await AddonTestUtils.promiseStartupManager();
|
||||
|
||||
let webExtensionFile = AddonTestUtils.createTempWebExtensionFile({
|
||||
manifest: {
|
||||
applications: {
|
||||
gecko: {
|
||||
id: addonID,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
server.registerFile("/data/policy_test.xpi", webExtensionFile);
|
||||
});
|
||||
|
||||
add_task(async function test_extensionsettings() {
|
||||
await setupPolicyEngineWithJson({
|
||||
"policies": {
|
||||
"ExtensionSettings": {
|
||||
"extension1@mozilla.com": {
|
||||
"blocked_install_message": "Extension1 error message.",
|
||||
},
|
||||
"*": {
|
||||
"blocked_install_message": "Generic error message.",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
let extensionSettings = Services.policies.getExtensionSettings("extension1@mozilla.com");
|
||||
equal(extensionSettings.blocked_install_message, "Extension1 error message.", "Should have extension specific message.");
|
||||
extensionSettings = Services.policies.getExtensionSettings("extension2@mozilla.com");
|
||||
equal(extensionSettings.blocked_install_message, "Generic error message.", "Should have generic message.");
|
||||
});
|
||||
|
||||
add_task(async function test_addon_blocked() {
|
||||
await setupPolicyEngineWithJson({
|
||||
"policies": {
|
||||
"ExtensionSettings": {
|
||||
"policytest2@mozilla.com": {
|
||||
"installation_mode": "blocked",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
let install = await AddonManager.getInstallForURL(BASE_URL + "/policy_test.xpi");
|
||||
await install.install();
|
||||
notEqual(install.addon, null, "Addon should not be null");
|
||||
equal(install.addon.appDisabled, true, "Addon should be disabled");
|
||||
await install.addon.uninstall();
|
||||
});
|
||||
|
||||
add_task(async function test_addon_allowed() {
|
||||
await setupPolicyEngineWithJson({
|
||||
"policies": {
|
||||
"ExtensionSettings": {
|
||||
"policytest2@mozilla.com": {
|
||||
"installation_mode": "allowed",
|
||||
},
|
||||
"*": {
|
||||
"installation_mode": "blocked",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
let install = await AddonManager.getInstallForURL(BASE_URL + "/policy_test.xpi");
|
||||
await install.install();
|
||||
notEqual(install.addon, null, "Addon should not be null");
|
||||
equal(install.addon.appDisabled, false, "Addon should not be disabled");
|
||||
await install.addon.uninstall();
|
||||
});
|
||||
|
||||
add_task(async function test_addon_uninstalled() {
|
||||
let install = await AddonManager.getInstallForURL(BASE_URL + "/policy_test.xpi");
|
||||
await install.install();
|
||||
notEqual(install.addon, null, "Addon should not be null");
|
||||
|
||||
await Promise.all([
|
||||
AddonTestUtils.promiseAddonEvent("onUninstalled"),
|
||||
setupPolicyEngineWithJson({
|
||||
"policies": {
|
||||
"ExtensionSettings": {
|
||||
"*": {
|
||||
"installation_mode": "blocked",
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
let addon = await AddonManager.getAddonByID(addonID);
|
||||
equal(addon, null, "Addon should be null");
|
||||
});
|
||||
|
||||
add_task(async function test_addon_forceinstalled() {
|
||||
await Promise.all([
|
||||
AddonTestUtils.promiseInstallEvent("onInstallEnded"),
|
||||
setupPolicyEngineWithJson({
|
||||
"policies": {
|
||||
"ExtensionSettings": {
|
||||
"policytest2@mozilla.com": {
|
||||
"installation_mode": "force_installed",
|
||||
"install_url": BASE_URL + "/policy_test.xpi",
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
let addon = await AddonManager.getAddonByID(addonID);
|
||||
notEqual(addon, null, "Addon should not be null");
|
||||
equal(addon.appDisabled, false, "Addon should not be disabled");
|
||||
equal(addon.permissions & AddonManager.PERM_CAN_UNINSTALL, 0, "Addon should not be able to be uninstalled.");
|
||||
equal(addon.permissions & AddonManager.PERM_CAN_DISABLE, 0, "Addon should not be able to be disabled.");
|
||||
await addon.uninstall();
|
||||
});
|
||||
|
||||
add_task(async function test_addon_normalinstalled() {
|
||||
await Promise.all([
|
||||
AddonTestUtils.promiseInstallEvent("onInstallEnded"),
|
||||
setupPolicyEngineWithJson({
|
||||
"policies": {
|
||||
"ExtensionSettings": {
|
||||
"policytest2@mozilla.com": {
|
||||
"installation_mode": "normal_installed",
|
||||
"install_url": BASE_URL + "/policy_test.xpi",
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
let addon = await AddonManager.getAddonByID(addonID);
|
||||
notEqual(addon, null, "Addon should not be null");
|
||||
equal(addon.appDisabled, false, "Addon should not be disabled");
|
||||
equal(addon.permissions & AddonManager.PERM_CAN_UNINSTALL, 0, "Addon should not be able to be uninstalled.");
|
||||
notEqual(addon.permissions & AddonManager.PERM_CAN_DISABLE, 0, "Addon should be able to be disabled.");
|
||||
await addon.uninstall();
|
||||
});
|
|
@ -6,6 +6,7 @@ head = head.js
|
|||
[test_appupdateurl.js]
|
||||
[test_clear_blocked_cookies.js]
|
||||
[test_defaultbrowsercheck.js]
|
||||
[test_extensionsettings.js]
|
||||
[test_macosparser_unflatten.js]
|
||||
skip-if = os != 'mac'
|
||||
[test_permissions.js]
|
||||
|
|
|
@ -90,6 +90,8 @@ policy-EnableTrackingProtection = Enable or disable Content Blocking and optiona
|
|||
# English or translate them as verbs.
|
||||
policy-Extensions = Install, uninstall or lock extensions. The Install option takes URLs or paths as parameters. The Uninstall and Locked options take extension IDs.
|
||||
|
||||
policy-ExtensionSettings = Manage all aspects of extension installation.
|
||||
|
||||
policy-ExtensionUpdate = Enable or disable automatic extension updates.
|
||||
|
||||
policy-FirefoxHome = Configure Firefox Home.
|
||||
|
|
|
@ -41,6 +41,13 @@ xpinstallDisabledMessage=Software installation is currently disabled. Click Enab
|
|||
xpinstallDisabledButton=Enable
|
||||
xpinstallDisabledButton.accesskey=n
|
||||
|
||||
# LOCALIZATION NOTE (addonInstallBlockedByPolicy)
|
||||
# This message is shown when the installation of an add-on is blocked by
|
||||
# enterprise policy. %1$S is replaced by the name of the add-on.
|
||||
# %2$S is replaced by the ID of add-on. %3$S is a custom message that
|
||||
# the administration can add to the message.
|
||||
addonInstallBlockedByPolicy=%1$S (%2$S) is blocked by your system administrator.%3$S
|
||||
|
||||
# LOCALIZATION NOTE (webextPerms.header)
|
||||
# This string is used as a header in the webextension permissions dialog,
|
||||
# %S is replaced with the localized name of the extension being installed.
|
||||
|
|
|
@ -300,23 +300,61 @@ EnterprisePoliciesManager.prototype = {
|
|||
|
||||
setExtensionSettings(extensionSettings) {
|
||||
ExtensionSettings = extensionSettings;
|
||||
if ("*" in extensionSettings &&
|
||||
"install_sources" in extensionSettings["*"]) {
|
||||
InstallSources = new MatchPatternSet(extensionSettings["*"].install_sources);
|
||||
}
|
||||
},
|
||||
|
||||
getExtensionSettings(extensionID) {
|
||||
let settings = null;
|
||||
if (extensionID in ExtensionSettings) {
|
||||
settings = ExtensionSettings[extensionID];
|
||||
} else if ("*" in ExtensionSettings) {
|
||||
settings = ExtensionSettings["*"];
|
||||
if (ExtensionSettings) {
|
||||
if (extensionID in ExtensionSettings) {
|
||||
settings = ExtensionSettings[extensionID];
|
||||
} else if ("*" in ExtensionSettings) {
|
||||
settings = ExtensionSettings["*"];
|
||||
}
|
||||
}
|
||||
return settings;
|
||||
},
|
||||
|
||||
mayInstallAddon(addon) {
|
||||
// See https://dev.chromium.org/administrators/policy-list-3/extension-settings-full
|
||||
if (!ExtensionSettings) {
|
||||
return true;
|
||||
}
|
||||
if (addon.id in ExtensionSettings) {
|
||||
if ("installation_mode" in ExtensionSettings[addon.id]) {
|
||||
switch (ExtensionSettings[addon.id].installation_mode) {
|
||||
case "blocked":
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ("*" in ExtensionSettings) {
|
||||
if (ExtensionSettings["*"].installation_mode &&
|
||||
ExtensionSettings["*"].installation_mode == "blocked") {
|
||||
return false;
|
||||
}
|
||||
if ("allowed_types" in ExtensionSettings["*"]) {
|
||||
return ExtensionSettings["*"].allowed_types.includes(addon.type);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
allowedInstallSource(uri) {
|
||||
return InstallSources ? InstallSources.matches(uri) : true;
|
||||
},
|
||||
};
|
||||
|
||||
let DisallowedFeatures = {};
|
||||
let SupportMenu = null;
|
||||
let ExtensionPolicies = null;
|
||||
let ExtensionSettings = null;
|
||||
let InstallSources = null;
|
||||
|
||||
/**
|
||||
* areEnterpriseOnlyPoliciesAllowed
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
#include "nsIURI.idl"
|
||||
|
||||
[scriptable, uuid(6a568972-cc91-4bf5-963e-3768f3319b8a)]
|
||||
interface nsIEnterprisePolicies : nsISupports
|
||||
|
@ -48,4 +49,20 @@ interface nsIEnterprisePolicies : nsISupports
|
|||
* @returns A JS object that settings or null if unavailable.
|
||||
*/
|
||||
jsval getExtensionSettings(in ACString extensionID);
|
||||
|
||||
/**
|
||||
* Uses the whitelist, blacklist and settings to determine if an extension
|
||||
* may be installed.
|
||||
*
|
||||
* @returns A boolean - true of the extension may be installed.
|
||||
*/
|
||||
bool mayInstallAddon(in jsval addon);
|
||||
|
||||
/**
|
||||
* Uses install_sources to determine if an extension can be installed
|
||||
* from the given URI.
|
||||
*
|
||||
* @returns A boolean - true of the extension may be installed.
|
||||
*/
|
||||
bool allowedInstallSource(in nsIURI uri);
|
||||
};
|
||||
|
|
|
@ -1663,6 +1663,10 @@ var AddonManagerInternal = {
|
|||
throw Components.Exception("aInstallingPrincipal must be a nsIPrincipal",
|
||||
Cr.NS_ERROR_INVALID_ARG);
|
||||
|
||||
if (this.isInstallAllowedByPolicy(aInstallingPrincipal, null, true /* explicit */)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let providers = [...this.providers];
|
||||
for (let provider of providers) {
|
||||
if (callProvider(provider, "supportsMimetype", false, aMimetype) &&
|
||||
|
@ -1672,6 +1676,40 @@ var AddonManagerInternal = {
|
|||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks whether a particular source is allowed to install add-ons based
|
||||
* on policy.
|
||||
*
|
||||
* @param aInstallingPrincipal
|
||||
* The nsIPrincipal that initiated the install
|
||||
* @param aInstall
|
||||
* The AddonInstall to be installed
|
||||
* @param explicit
|
||||
* If this is set, we only return true if the source is explicitly
|
||||
* blocked via policy.
|
||||
*
|
||||
* @return boolean
|
||||
* By default, returns true if the source is blocked by policy
|
||||
* or there is no policy.
|
||||
* If explicit is set, only returns true of the source is
|
||||
* blocked by policy, false otherwise. This is needed for
|
||||
* handling inverse cases.
|
||||
*/
|
||||
isInstallAllowedByPolicy(aInstallingPrincipal, aInstall, explicit) {
|
||||
if (Services.policies) {
|
||||
let extensionSettings = Services.policies.getExtensionSettings("*");
|
||||
if (extensionSettings && extensionSettings.install_sources) {
|
||||
if ((!aInstall || Services.policies.allowedInstallSource(aInstall.sourceURI)) &&
|
||||
(!aInstallingPrincipal || !aInstallingPrincipal.URI ||
|
||||
Services.policies.allowedInstallSource(aInstallingPrincipal.URI))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return !explicit;
|
||||
},
|
||||
|
||||
installNotifyObservers(aTopic, aBrowser, aUri, aInstall, aInstallFn) {
|
||||
let info = {
|
||||
wrappedJSObject: {
|
||||
|
@ -1809,7 +1847,9 @@ var AddonManagerInternal = {
|
|||
this.installNotifyObservers("addon-install-disabled", topBrowser,
|
||||
aInstallingPrincipal.URI, aInstall);
|
||||
return;
|
||||
} else if (aInstallingPrincipal.isNullPrincipal || !aBrowser.contentPrincipal || !aInstallingPrincipal.subsumes(aBrowser.contentPrincipal)) {
|
||||
} else if (aInstallingPrincipal.isNullPrincipal || !aBrowser.contentPrincipal ||
|
||||
!aInstallingPrincipal.subsumes(aBrowser.contentPrincipal) ||
|
||||
!this.isInstallAllowedByPolicy(aInstallingPrincipal, aInstall, false /* explicit */)) {
|
||||
aInstall.cancel();
|
||||
|
||||
this.installNotifyObservers("addon-install-origin-blocked", topBrowser,
|
||||
|
@ -1855,6 +1895,13 @@ var AddonManagerInternal = {
|
|||
* The AddonInstall to be installed
|
||||
*/
|
||||
installAddonFromAOM(browser, uri, install) {
|
||||
if (!this.isInstallAllowedByPolicy(null, install)) {
|
||||
install.cancel();
|
||||
|
||||
this.installNotifyObservers("addon-install-origin-blocked", browser,
|
||||
install.sourceURI, install);
|
||||
return;
|
||||
}
|
||||
if (!gStarted)
|
||||
throw Components.Exception("AddonManager is not initialized",
|
||||
Cr.NS_ERROR_NOT_INITIALIZED);
|
||||
|
|
|
@ -659,10 +659,13 @@ class AddonInternal {
|
|||
permissions |= AddonManager.PERM_CAN_CHANGE_PRIVATEBROWSING_ACCESS;
|
||||
}
|
||||
|
||||
if (Services.policies &&
|
||||
!Services.policies.isAllowed(`modify-extension:${this.id}`)) {
|
||||
permissions &= ~AddonManager.PERM_CAN_UNINSTALL;
|
||||
permissions &= ~AddonManager.PERM_CAN_DISABLE;
|
||||
if (Services.policies) {
|
||||
if (!Services.policies.isAllowed(`uninstall-extension:${this.id}`)) {
|
||||
permissions &= ~AddonManager.PERM_CAN_UNINSTALL;
|
||||
}
|
||||
if (!Services.policies.isAllowed(`disable-extension:${this.id}`)) {
|
||||
permissions &= ~AddonManager.PERM_CAN_DISABLE;
|
||||
}
|
||||
}
|
||||
|
||||
return permissions;
|
||||
|
@ -1945,6 +1948,14 @@ this.XPIDatabase = {
|
|||
}
|
||||
}
|
||||
|
||||
if (aAddon.location.isSystem || aAddon.location.isBuiltin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Services.policies && !Services.policies.mayInstallAddon(aAddon)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче