Bug 1372033 - Added option to permanently disallow addon installation for a website. r=johannh,aswan
authorPaul Zuehlcke <pzuhlcke@mozilla.com>
Thu, 16 May 2019 10:20:07 +0000
changeset 474225 cb21c0b524ff08b12769387a1f8d3fa42258cb22
parent 474224 39446af6b4ad5d790bee4685c05952df3bd30c2f
child 474226 578cc4c154efb9d1d54da26e2f2818df88b3971e
push id36027
push usershindli@mozilla.com
push dateFri, 17 May 2019 16:24:38 +0000
treeherdermozilla-central@c94c54aff466 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjohannh, aswan
bugs1372033
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1372033 - Added option to permanently disallow addon installation for a website. r=johannh,aswan Differential Revision: https://phabricator.services.mozilla.com/D28867
browser/base/content/browser-addons.js
browser/base/content/browser.xul
browser/locales/en-US/chrome/browser/browser.dtd
browser/locales/en-US/chrome/browser/browser.properties
browser/modules/SitePermissions.jsm
browser/modules/test/unit/test_SitePermissions.js
browser/themes/shared/notification-icons.inc.css
toolkit/mozapps/extensions/AddonManager.jsm
toolkit/mozapps/extensions/test/xpinstall/browser_doorhanger_installs.js
--- a/browser/base/content/browser-addons.js
+++ b/browser/base/content/browser-addons.js
@@ -475,32 +475,44 @@ var gXPInstallObserver = {
       action = {
         label: gNavigatorBundle.getString("xpinstallPromptMessage.install"),
         accessKey: gNavigatorBundle.getString("xpinstallPromptMessage.install.accesskey"),
         callback() {
           secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_ADDON_ASKING_PREVENTED_CLICK_THROUGH);
           installInfo.install();
         },
       };
-      let secondaryAction = {
+      let dontAllowAction = {
         label: gNavigatorBundle.getString("xpinstallPromptMessage.dontAllow"),
         accessKey: gNavigatorBundle.getString("xpinstallPromptMessage.dontAllow.accesskey"),
         callback: () => {
           for (let install of installInfo.installs) {
             if (install.state != AddonManager.STATE_CANCELLED) {
               install.cancel();
             }
           }
         },
       };
+      let neverAllowAction = {
+        label: gNavigatorBundle.getString("xpinstallPromptMessage.neverAllow"),
+        accessKey: gNavigatorBundle.getString("xpinstallPromptMessage.neverAllow.accesskey"),
+        callback: () => {
+          SitePermissions.set(browser.currentURI, "install", SitePermissions.BLOCK);
+          for (let install of installInfo.installs) {
+            if (install.state != AddonManager.STATE_CANCELLED) {
+              install.cancel();
+            }
+          }
+        },
+      };
 
       secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_ADDON_ASKING_PREVENTED);
       let popup = PopupNotifications.show(browser, notificationID,
                                           messageString, anchorID,
-                                          action, [secondaryAction], options);
+                                          action, [dontAllowAction, neverAllowAction], options);
       removeNotificationOnEnd(popup, installInfo.installs);
       break; }
     case "addon-install-started": {
       let needsDownload = function needsDownload(aInstall) {
         return aInstall.state != AddonManager.STATE_DOWNLOADED;
       };
       // If all installs have already been downloaded then there is no need to
       // show the download progress
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -883,16 +883,18 @@
                   <image data-permission-id="autoplay-media" class="blocked-permission-icon autoplay-media-icon" role="button"
                          tooltiptext="&urlbar.autoplayMediaBlocked.tooltip;"/>
                   <image data-permission-id="canvas" class="blocked-permission-icon canvas-icon" role="button"
                          tooltiptext="&urlbar.canvasBlocked.tooltip;"/>
                   <image data-permission-id="plugin:flash" class="blocked-permission-icon plugin-icon" role="button"
                          tooltiptext="&urlbar.flashPluginBlocked.tooltip;"/>
                   <image data-permission-id="midi" class="blocked-permission-icon midi-icon" role="button"
                          tooltiptext="&urlbar.midiBlocked.tooltip;"/>
+                  <image data-permission-id="install" class="blocked-permission-icon install-icon" role="button"
+                         tooltiptext="&urlbar.installBlocked.tooltip;"/>
                 </box>
                 <box id="notification-popup-box"
                      hidden="true"
                      onmouseover="document.getElementById('identity-box').classList.add('no-hover');"
                      onmouseout="document.getElementById('identity-box').classList.remove('no-hover');"
                      align="center">
                   <image id="default-notification-icon" class="notification-anchor-icon" role="button"
                          tooltiptext="&urlbar.defaultNotificationAnchor.tooltip;"/>
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -243,16 +243,17 @@ These should match what Safari and other
 <!ENTITY urlbar.geolocationBlocked.tooltip       "You have blocked location information for this website.">
 <!ENTITY urlbar.webNotificationsBlocked.tooltip  "You have blocked notifications for this website.">
 <!ENTITY urlbar.persistentStorageBlocked.tooltip "You have blocked persistent storage for this website.">
 <!ENTITY urlbar.popupBlocked.tooltip             "You have blocked pop-ups for this website.">
 <!ENTITY urlbar.autoplayMediaBlocked.tooltip     "You have blocked autoplay media with sound for this website.">
 <!ENTITY urlbar.canvasBlocked.tooltip            "You have blocked canvas data extraction for this website.">
 <!ENTITY urlbar.flashPluginBlocked.tooltip       "You have blocked this website from using the Adobe Flash plugin.">
 <!ENTITY urlbar.midiBlocked.tooltip              "You have blocked MIDI access for this website.">
+<!ENTITY urlbar.installBlocked.tooltip           "You have blocked add-on installation for this website.">
 
 <!ENTITY urlbar.openHistoryPopup.tooltip                "Show history">
 
 <!ENTITY searchItem.title             "Search">
 
 <!-- Toolbar items -->
 <!ENTITY homeButton.label             "Home">
 <!ENTITY homeButton.defaultPage.tooltip "&brandShortName; Home Page">
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -25,16 +25,18 @@ xpinstallPromptMessage=%S prevented this
 # The string contains the hostname of the site the add-on is being installed from.
 xpinstallPromptMessage.header=Allow %S to install an add-on?
 xpinstallPromptMessage.message=You are attempting to install an add-on from %S. Make sure you trust this site before continuing.
 xpinstallPromptMessage.header.unknown=Allow an unknown site to install an add-on?
 xpinstallPromptMessage.message.unknown=You are attempting to install an add-on from an unknown site. Make sure you trust this site before continuing.
 xpinstallPromptMessage.learnMore=Learn more about installing add-ons safely
 xpinstallPromptMessage.dontAllow=Don’t Allow
 xpinstallPromptMessage.dontAllow.accesskey=D
+xpinstallPromptMessage.neverAllow=Never Allow
+xpinstallPromptMessage.neverAllow.accesskey=N
 # Accessibility Note:
 # Be sure you do not choose an accesskey that is used elsewhere in the active context (e.g. main menu bar, submenu of the warning popup button)
 # See https://website-archive.mozilla.org/www.mozilla.org/access/access/keyboard/ for details
 xpinstallPromptMessage.install=Continue to Installation
 xpinstallPromptMessage.install.accesskey=C
 
 xpinstallDisabledMessageLocked=Software installation has been disabled by your system administrator.
 xpinstallDisabledMessage=Software installation is currently disabled. Click Enable and try again.
--- a/browser/modules/SitePermissions.jsm
+++ b/browser/modules/SitePermissions.jsm
@@ -294,21 +294,16 @@ var SitePermissions = {
     }
 
     let permissions = Services.perms.getAllForPrincipal(principal);
     while (permissions.hasMoreElements()) {
       let permission = permissions.getNext();
 
       // filter out unknown permissions
       if (gPermissionObject[permission.type]) {
-        // XXX Bug 1303108 - Control Center should only show non-default permissions
-        if (permission.type == "install") {
-          continue;
-        }
-
         // Hide canvas permission when privacy.resistFingerprinting is false.
         if ((permission.type == "canvas") && !this.resistFingerprinting) {
           continue;
         }
 
         let scope = this.SCOPE_PERSISTENT;
         if (permission.expireType == Services.perms.EXPIRE_SESSION) {
           scope = this.SCOPE_SESSION;
@@ -914,19 +909,18 @@ var gPermissionObject = {
                SitePermissions.BLOCK : SitePermissions.ALLOW;
     },
     states: [ SitePermissions.ALLOW, SitePermissions.BLOCK ],
   },
 
   "install": {
     getDefault() {
       return Services.prefs.getBoolPref("xpinstall.whitelist.required") ?
-               SitePermissions.BLOCK : SitePermissions.ALLOW;
+               SitePermissions.UNKNOWN : SitePermissions.ALLOW;
     },
-    states: [ SitePermissions.ALLOW, SitePermissions.BLOCK ],
   },
 
   "geo": {
     exactHostMatch: true,
   },
 
   "focus-tab-by-prompt": {
     exactHostMatch: true,
--- a/browser/modules/test/unit/test_SitePermissions.js
+++ b/browser/modules/test/unit/test_SitePermissions.js
@@ -74,21 +74,16 @@ add_task(async function testGetAllByURI(
       { id: "camera", state: SitePermissions.ALLOW, scope: SitePermissions.SCOPE_PERSISTENT },
       { id: "desktop-notification", state: SitePermissions.BLOCK, scope: SitePermissions.SCOPE_PERSISTENT },
   ]);
 
   SitePermissions.remove(uri, "camera");
   SitePermissions.remove(uri, "desktop-notification");
   Assert.deepEqual(SitePermissions.getAllByURI(uri), []);
 
-  // XXX Bug 1303108 - Control Center should only show non-default permissions
-  SitePermissions.set(uri, "addon", SitePermissions.BLOCK);
-  Assert.deepEqual(SitePermissions.getAllByURI(uri), []);
-  SitePermissions.remove(uri, "addon");
-
   Assert.equal(Services.prefs.getIntPref("permissions.default.shortcuts"), 0);
   SitePermissions.set(uri, "shortcuts", SitePermissions.BLOCK);
 
   // Customized preference should have been enabled, but the default should not.
   Assert.equal(Services.prefs.getIntPref("permissions.default.shortcuts"), 0);
   Assert.deepEqual(SitePermissions.getAllByURI(uri), [
       { id: "shortcuts", state: SitePermissions.BLOCK, scope: SitePermissions.SCOPE_PERSISTENT },
   ]);
--- a/browser/themes/shared/notification-icons.inc.css
+++ b/browser/themes/shared/notification-icons.inc.css
@@ -245,16 +245,17 @@ html|*#webRTC-previewVideo {
 }
 
 /* INSTALL ADDONS */
 
 .install-icon {
   list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric-16.svg);
 }
 
+.install-icon.blocked-permission-icon,
 .popup-notification-icon[popupid="xpinstall-disabled"],
 .popup-notification-icon[popupid="addon-install-blocked"],
 .popup-notification-icon[popupid="addon-install-origin-blocked"] {
   list-style-image: url(chrome://browser/skin/addons/addon-install-blocked.svg);
 }
 
 .popup-notification-icon[popupid="addon-progress"] {
   list-style-image: url(chrome://browser/skin/addons/addon-install-downloading.svg);
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -1862,22 +1862,31 @@ var AddonManagerInternal = {
       // install in that case.
       new BrowserListener(aBrowser, aInstallingPrincipal, aInstall);
 
       let startInstall = (source) => {
         AddonManagerInternal.setupPromptHandler(aBrowser, aInstallingPrincipal.URI, aInstall, true, source);
 
         AddonManagerInternal.startInstall(aBrowser, aInstallingPrincipal.URI, aInstall);
       };
-      if (!this.isInstallAllowed(aMimetype, aInstallingPrincipal)) {
+
+      let installAllowed = this.isInstallAllowed(aMimetype, aInstallingPrincipal);
+      let installPerm = Services.perms.testPermissionFromPrincipal(aInstallingPrincipal, "install");
+
+      if (installAllowed) {
+        startInstall("AMO");
+      } else if (installPerm === Ci.nsIPermissionManager.DENY_ACTION) {
+        // Block without prompt
+        aInstall.cancel();
+        this.installNotifyObservers("addon-install-blocked-silent", topBrowser, aInstallingPrincipal.URI, aInstall);
+      } else {
+        // Block with prompt
         this.installNotifyObservers("addon-install-blocked", topBrowser,
-                                    aInstallingPrincipal.URI, aInstall,
-                                    () => startInstall("other"));
-      } else {
-        startInstall("AMO");
+          aInstallingPrincipal.URI, aInstall,
+          () => startInstall("other"));
       }
     } catch (e) {
       // In the event that the weblistener throws during instantiation or when
       // calling onWebInstallBlocked or onWebInstallRequested the
       // install should get cancelled.
       logger.warn("Failure calling web installer", e);
       aInstall.cancel();
     }
--- a/toolkit/mozapps/extensions/test/xpinstall/browser_doorhanger_installs.js
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_doorhanger_installs.js
@@ -296,16 +296,67 @@ async function test_blockedInstall() {
   is(installs.length, 0, "Should be no pending installs");
 
   let addon = await AddonManager.getAddonByID("amosigned-xpi@tests.mozilla.org");
   addon.uninstall();
 
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 },
 
+async function test_permaBlockInstall() {
+  let notificationPromise = waitForNotification("addon-install-blocked");
+  let triggers = encodeURIComponent(JSON.stringify({
+    "XPI": "amosigned.xpi",
+  }));
+  let target = TESTROOT + "installtrigger.html?" + triggers;
+
+  BrowserTestUtils.openNewForegroundTab(gBrowser, target);
+  let notification = (await notificationPromise).firstElementChild;
+  let neverAllowBtn = notification.menupopup.firstElementChild;
+
+  neverAllowBtn.click();
+
+  await TestUtils.waitForCondition(() => !PopupNotifications.isPanelOpen, "Waiting for notification to close");
+
+  let installs = await AddonManager.getAllInstalls();
+  is(installs.length, 0, "Should be no pending installs");
+
+  let installPerm = Services.perms.testPermission(gBrowser.currentURI, "install");
+  is(installPerm, Ci.nsIPermissionManager.DENY_ACTION, "Addon installation should be blocked for site");
+
+  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+  SitePermissions.remove(NetUtil.newURI(target), "install");
+},
+
+async function test_permaBlockedInstallNoPrompt() {
+  let triggers = encodeURIComponent(JSON.stringify({
+    "XPI": "amosigned.xpi",
+  }));
+  let target = TESTROOT + "installtrigger.html?" + triggers;
+
+  SitePermissions.set(NetUtil.newURI(target), "install", SitePermissions.BLOCK);
+  await BrowserTestUtils.openNewForegroundTab(gBrowser, target);
+
+  let panelOpened;
+  try {
+    panelOpened = await TestUtils.waitForCondition(() => PopupNotifications.isPanelOpen, 100, 10);
+  } catch (ex) {
+    panelOpened = false;
+  }
+  is(panelOpened, false, "Addon prompt should not open");
+
+  let installs = await AddonManager.getAllInstalls();
+  is(installs.length, 0, "Should be no pending installs");
+
+  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+  SitePermissions.remove(NetUtil.newURI(target), "install");
+},
+
 async function test_whitelistedInstall() {
   Services.prefs.setBoolPref("extensions.allowPrivateBrowsingByDefault", false);
   let originalTab = gBrowser.selectedTab;
   let tab;
   gBrowser.selectedTab = originalTab;
   let pm = Services.perms;
   pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);