зеркало из 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];
|
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;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
Загрузка…
Ссылка в новой задаче