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:
Michael Kaply 2019-05-15 01:22:39 +00:00
Родитель 67fb859dcb
Коммит a98b93897c
14 изменённых файлов: 683 добавлений и 65 удалений

Просмотреть файл

@ -580,6 +580,16 @@ var gXPInstallObserver = {
args = [brandShortName, Services.appinfo.version, install.name]; 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 // Add Learn More link when refusing to install an unsigned add-on
if (install.error == AddonManager.ERROR_SIGNEDSTATE_REQUIRED) { if (install.error == AddonManager.ERROR_SIGNEDSTATE_REQUIRED) {
options.learnMoreURL = Services.urlFormatter.formatURLPref("app.support.baseURL") + "unsigned-addons"; options.learnMoreURL = Services.urlFormatter.formatURLPref("app.support.baseURL") + "unsigned-addons";

Просмотреть файл

@ -542,7 +542,7 @@ var Policies = {
await addon.uninstall(); await addon.uninstall();
} catch (e) { } catch (e) {
// This can fail for add-ons that can't be uninstalled. // 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 () => { runOncePerModification("extensionsInstall", JSON.stringify(param.Install), async () => {
await uninstallingPromise; await uninstallingPromise;
for (let location of param.Install) { for (let location of param.Install) {
let url; let uri;
if (location.includes("://")) { try {
// Assume location is an URI uri = Services.io.newURI(location);
url = location; } catch (e) {
} else { // If it's not a URL, it's probably a file path.
// Assume location is 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 { try {
xpiFile.initWithPath(location); let xpiFile = new FileUtils.File(location);
} catch (e) { uri = Services.io.newFileURI(xpiFile);
} catch (ex) {
log.error(`Invalid extension path location - ${location}`); 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; return;
} }
let listener = { }
/* eslint-disable-next-line no-shadow */ installAddonFromURL(uri.spec);
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();
});
} }
}); });
} }
if ("Locked" in param) { if ("Locked" in param) {
for (let ID of param.Locked) { 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": { "ExtensionSettings": {
onBeforeAddons(manager, param) { 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; 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; let gChromeURLSBlocked = false;
// If any about page is blocked, we block the loading of all // If any about page is blocked, we block the loading of all

Просмотреть файл

@ -323,10 +323,44 @@
"ExtensionSettings": { "ExtensionSettings": {
"type": "object", "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": { "patternProperties": {
"^.*$": { "^.*$": {
"type": "object", "type": "object",
"properties": { "properties": {
"installation_mode": {
"type": "string",
"enum": ["allowed", "blocked", "force_installed", "normal_installed"]
},
"install_url": {
"type": "string"
},
"blocked_install_message": { "blocked_install_message": {
"type": "string" "type": "string"
} }

Просмотреть файл

@ -9,6 +9,7 @@ support-files =
policy_websitefilter_exception.html policy_websitefilter_exception.html
../../../../../toolkit/components/antitracking/test/browser/page.html ../../../../../toolkit/components/antitracking/test/browser/page.html
../../../../../toolkit/components/antitracking/test/browser/subResources.sjs ../../../../../toolkit/components/antitracking/test/browser/subResources.sjs
extensionsettings.html
[browser_policies_getActivePolicies.js] [browser_policies_getActivePolicies.js]
skip-if = os != 'mac' skip-if = os != 'mac'

Просмотреть файл

@ -2,22 +2,206 @@
* http://creativecommons.org/publicdomain/zero/1.0/ */ * http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict"; "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({ await setupPolicyEngineWithJson({
"policies": { "policies": {
"ExtensionSettings": { "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"); await ContentTask.spawn(tab.linkedBrowser, {}, () => {
is(extensionSettings.blocked_install_message, "Extension1 error message.", "Should have extension specific message."); content.document.getElementById("policytest").click();
extensionSettings = Services.policies.getExtensionSettings("extension2@mozilla.com"); });
is(extensionSettings.blocked_install_message, "Generic error message.", "Should have generic message."); 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_appupdateurl.js]
[test_clear_blocked_cookies.js] [test_clear_blocked_cookies.js]
[test_defaultbrowsercheck.js] [test_defaultbrowsercheck.js]
[test_extensionsettings.js]
[test_macosparser_unflatten.js] [test_macosparser_unflatten.js]
skip-if = os != 'mac' skip-if = os != 'mac'
[test_permissions.js] [test_permissions.js]

Просмотреть файл

@ -90,6 +90,8 @@ policy-EnableTrackingProtection = Enable or disable Content Blocking and optiona
# English or translate them as verbs. # 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-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-ExtensionUpdate = Enable or disable automatic extension updates.
policy-FirefoxHome = Configure Firefox Home. policy-FirefoxHome = Configure Firefox Home.

Просмотреть файл

@ -41,6 +41,13 @@ xpinstallDisabledMessage=Software installation is currently disabled. Click Enab
xpinstallDisabledButton=Enable xpinstallDisabledButton=Enable
xpinstallDisabledButton.accesskey=n 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) # LOCALIZATION NOTE (webextPerms.header)
# This string is used as a header in the webextension permissions dialog, # This string is used as a header in the webextension permissions dialog,
# %S is replaced with the localized name of the extension being installed. # %S is replaced with the localized name of the extension being installed.

Просмотреть файл

@ -300,23 +300,61 @@ EnterprisePoliciesManager.prototype = {
setExtensionSettings(extensionSettings) { setExtensionSettings(extensionSettings) {
ExtensionSettings = extensionSettings; ExtensionSettings = extensionSettings;
if ("*" in extensionSettings &&
"install_sources" in extensionSettings["*"]) {
InstallSources = new MatchPatternSet(extensionSettings["*"].install_sources);
}
}, },
getExtensionSettings(extensionID) { getExtensionSettings(extensionID) {
let settings = null; let settings = null;
if (extensionID in ExtensionSettings) { if (ExtensionSettings) {
settings = ExtensionSettings[extensionID]; if (extensionID in ExtensionSettings) {
} else if ("*" in ExtensionSettings) { settings = ExtensionSettings[extensionID];
settings = ExtensionSettings["*"]; } else if ("*" in ExtensionSettings) {
settings = ExtensionSettings["*"];
}
} }
return settings; 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 DisallowedFeatures = {};
let SupportMenu = null; let SupportMenu = null;
let ExtensionPolicies = null; let ExtensionPolicies = null;
let ExtensionSettings = null; let ExtensionSettings = null;
let InstallSources = null;
/** /**
* areEnterpriseOnlyPoliciesAllowed * areEnterpriseOnlyPoliciesAllowed

Просмотреть файл

@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl" #include "nsISupports.idl"
#include "nsIURI.idl"
[scriptable, uuid(6a568972-cc91-4bf5-963e-3768f3319b8a)] [scriptable, uuid(6a568972-cc91-4bf5-963e-3768f3319b8a)]
interface nsIEnterprisePolicies : nsISupports interface nsIEnterprisePolicies : nsISupports
@ -48,4 +49,20 @@ interface nsIEnterprisePolicies : nsISupports
* @returns A JS object that settings or null if unavailable. * @returns A JS object that settings or null if unavailable.
*/ */
jsval getExtensionSettings(in ACString extensionID); 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", throw Components.Exception("aInstallingPrincipal must be a nsIPrincipal",
Cr.NS_ERROR_INVALID_ARG); Cr.NS_ERROR_INVALID_ARG);
if (this.isInstallAllowedByPolicy(aInstallingPrincipal, null, true /* explicit */)) {
return true;
}
let providers = [...this.providers]; let providers = [...this.providers];
for (let provider of providers) { for (let provider of providers) {
if (callProvider(provider, "supportsMimetype", false, aMimetype) && if (callProvider(provider, "supportsMimetype", false, aMimetype) &&
@ -1672,6 +1676,40 @@ var AddonManagerInternal = {
return false; 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) { installNotifyObservers(aTopic, aBrowser, aUri, aInstall, aInstallFn) {
let info = { let info = {
wrappedJSObject: { wrappedJSObject: {
@ -1809,7 +1847,9 @@ var AddonManagerInternal = {
this.installNotifyObservers("addon-install-disabled", topBrowser, this.installNotifyObservers("addon-install-disabled", topBrowser,
aInstallingPrincipal.URI, aInstall); aInstallingPrincipal.URI, aInstall);
return; 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(); aInstall.cancel();
this.installNotifyObservers("addon-install-origin-blocked", topBrowser, this.installNotifyObservers("addon-install-origin-blocked", topBrowser,
@ -1855,6 +1895,13 @@ var AddonManagerInternal = {
* The AddonInstall to be installed * The AddonInstall to be installed
*/ */
installAddonFromAOM(browser, uri, install) { installAddonFromAOM(browser, uri, install) {
if (!this.isInstallAllowedByPolicy(null, install)) {
install.cancel();
this.installNotifyObservers("addon-install-origin-blocked", browser,
install.sourceURI, install);
return;
}
if (!gStarted) if (!gStarted)
throw Components.Exception("AddonManager is not initialized", throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED); Cr.NS_ERROR_NOT_INITIALIZED);

Просмотреть файл

@ -659,10 +659,13 @@ class AddonInternal {
permissions |= AddonManager.PERM_CAN_CHANGE_PRIVATEBROWSING_ACCESS; permissions |= AddonManager.PERM_CAN_CHANGE_PRIVATEBROWSING_ACCESS;
} }
if (Services.policies && if (Services.policies) {
!Services.policies.isAllowed(`modify-extension:${this.id}`)) { if (!Services.policies.isAllowed(`uninstall-extension:${this.id}`)) {
permissions &= ~AddonManager.PERM_CAN_UNINSTALL; permissions &= ~AddonManager.PERM_CAN_UNINSTALL;
permissions &= ~AddonManager.PERM_CAN_DISABLE; }
if (!Services.policies.isAllowed(`disable-extension:${this.id}`)) {
permissions &= ~AddonManager.PERM_CAN_DISABLE;
}
} }
return permissions; 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; return true;
}, },