Bug 407875 - Unprivileged users are not notified of security updates. r=dtownsend, r=vlad, a=dietrich
authorRobert Strong <robert.bugzilla@gmail.com>
Wed, 18 Nov 2009 21:50:05 -0800
changeset 35033 d8552c8f795f5e2fc2fbe3378c3bf47b0966ad1a
parent 35032 f43b6bc2165bd9fc3bc202bcb6c8c6a133f45287
child 35034 c10ea5e5eab321d4ab24f1d08100d6acaf4448d7
push id10419
push userrstrong@mozilla.com
push dateThu, 19 Nov 2009 05:50:29 +0000
treeherdermozilla-central@d8552c8f795f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdtownsend, vlad, dietrich
bugs407875
milestone1.9.3a1pre
Bug 407875 - Unprivileged users are not notified of security updates. r=dtownsend, r=vlad, a=dietrich
browser/base/content/utilityOverlay.js
other-licenses/branding/firefox/pref/firefox-branding.js
toolkit/mozapps/update/content/updates.js
toolkit/mozapps/update/content/updates.xul
toolkit/mozapps/update/public/nsIUpdateService.idl
toolkit/mozapps/update/src/nsUpdateService.js.in
toolkit/mozapps/update/src/nsUpdateTimerManager.js
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -470,27 +470,27 @@ function buildHelpMenu()
   // Enable/disable the "Report Web Forgery" menu item.  safebrowsing object
   // may not exist in OSX
   if (typeof safebrowsing != "undefined")
     safebrowsing.setReportPhishingMenu();
 
 #ifdef MOZ_UPDATER
   var updates = 
       Components.classes["@mozilla.org/updates/update-service;1"].
-      getService(Components.interfaces.nsIApplicationUpdateService);
+      getService(Components.interfaces.nsIApplicationUpdateService2);
   var um = 
       Components.classes["@mozilla.org/updates/update-manager;1"].
       getService(Components.interfaces.nsIUpdateManager);
 
   // Disable the UI if the update enabled pref has been locked by the 
   // administrator or if we cannot update for some other reason
   var checkForUpdates = document.getElementById("checkForUpdates");
-  var canUpdate = updates.canUpdate;
-  checkForUpdates.setAttribute("disabled", !canUpdate);
-  if (!canUpdate)
+  var canCheckForUpdates = updates.canCheckForUpdates;
+  checkForUpdates.setAttribute("disabled", !canCheckForUpdates);
+  if (!canCheckForUpdates)
     return; 
 
   var strings = document.getElementById("bundle_browser");
   var activeUpdate = um.activeUpdate;
   
   // If there's an active update, substitute its name into the label
   // we show for this item, otherwise display a generic label.
   function getStringWithUpdateName(key) {
--- a/other-licenses/branding/firefox/pref/firefox-branding.js
+++ b/other-licenses/branding/firefox/pref/firefox-branding.js
@@ -3,17 +3,17 @@ pref("startup.homepage_welcome_url","htt
 // Interval: Time between checks for a new version (in seconds)
 // nightly=6 hours, official=24 hours
 pref("app.update.interval", 86400);
 // The time interval between the downloading of mar file chunks in the
 // background (in seconds)
 pref("app.update.download.backgroundInterval", 600);
 // URL user can browse to manually if for some reason all update installation
 // attempts fail.
-pref("app.update.url.manual", "http://%LOCALE%.www.mozilla.com/%LOCALE%/%APP%/");
+pref("app.update.url.manual", "http://www.firefox.com");
 // A default value for the "More information about this update" link
 // supplied in the "An update is available" page of the update wizard. 
 pref("app.update.url.details", "http://%LOCALE%.www.mozilla.com/%LOCALE%/%APP%/releases/");
 
 // Release notes URL
 pref("app.releaseNotesURL", "http://%LOCALE%.www.mozilla.com/%LOCALE%/%APP%/%VERSION%/releasenotes/");
 
 pref("browser.search.param.yahoo-fr", "moz35");
--- a/toolkit/mozapps/update/content/updates.js
+++ b/toolkit/mozapps/update/content/updates.js
@@ -620,16 +620,25 @@ var gIncompatibleCheckPage = {
    * The progress bar for this page
    */
   _pBar: null,
 
   /**
    * Initialize
    */
   onPageShow: function() {
+    var aus = CoC["@mozilla.org/updates/update-service;1"].
+              getService(CoI.nsIApplicationUpdateService2);
+    // Display the manual update page if the user is unable to apply the update
+    if (!aus.canApplyUpdates) {
+      gUpdates.wiz.currentPage.setAttribute("next", "manualUpdate");
+      gUpdates.wiz.advance();
+      return;
+    }
+
     var ai = CoC["@mozilla.org/xre/app-info;1"].getService(CoI.nsIXULAppInfo);
     var vc = CoC["@mozilla.org/xpcom/version-comparator;1"].
              getService(CoI.nsIVersionComparator);
     if (!gUpdates.update.extensionVersion ||
         vc.compare(gUpdates.update.extensionVersion, ai.version) == 0) {
       // Go to the next page
       gUpdates.wiz.advance();
       return;
@@ -736,16 +745,34 @@ var gIncompatibleCheckPage = {
     if (!iid.equals(CoI.nsIAddonUpdateCheckListener) &&
         !iid.equals(CoI.nsISupports))
       throw CoR.NS_ERROR_NO_INTERFACE;
     return this;
   }
 };
 
 /**
+ * The "Unable to Update" page. Provides the user information about why they
+ * were unable to update and a manual download url.
+ */
+var gManualUpdatePage = {
+  onPageShow: function() {
+    var formatter = CoC["@mozilla.org/toolkit/URLFormatterService;1"].
+                    getService(CoI.nsIURLFormatter);
+    var manualURL = formatter.formatURLPref(PREF_UPDATE_MANUAL_URL);
+    var manualUpdateLinkLabel = document.getElementById("manualUpdateLinkLabel");
+    manualUpdateLinkLabel.value = manualURL;
+    manualUpdateLinkLabel.setAttribute("url", manualURL);
+
+    gUpdates.setButtons(null, null, "okButton", true);
+    gUpdates.wiz.getButton("finish").focus();
+  }
+};
+
+/**
  * The "Updates Are Available" page. Provides the user information about the
  * available update.
  */
 var gUpdatesAvailablePage = {
   /**
    * If this page has been previously loaded
    */
   _loaded: false,
--- a/toolkit/mozapps/update/content/updates.xul
+++ b/toolkit/mozapps/update/content/updates.xul
@@ -100,16 +100,28 @@
               next="updatesfound" label="&incompatibleCheck.title;"
               object="gIncompatibleCheckPage"
               onpageshow="gIncompatibleCheckPage.onPageShow();">
     <label>&incompatibleCheck.label;</label>
     <separator class="thin"/>
     <progressmeter id="incompatibleCheckProgress" mode="undetermined" hidden="true"/>
   </wizardpage>
 
+  <wizardpage id="manualUpdate" pageid="manualUpdate"
+              label="&manualUpdate.title;" object="gManualUpdatePage"
+              onpageshow="gManualUpdatePage.onPageShow();">
+    <description>&manualUpdate.desc;</description>
+    <separator class="thin"/>
+    <label>&manualUpdateGetMsg.label;</label>
+    <hbox>
+      <label class="text-link" id="manualUpdateLinkLabel" value=""
+             onclick="openUpdateURL(event);"/>
+    </hbox>
+  </wizardpage>
+
   <wizardpage id="updatesfound" pageid="updatesfound" next="license"
               object="gUpdatesAvailablePage" label=""
               onpageshow="gUpdatesAvailablePage.onPageShow();"
               onextra1="gUpdatesAvailablePage.onExtra1();"
               onextra2="gUpdatesAvailablePage.onExtra2();">
     <description id="updateType"/>
     <separator class="thin"/>
     <label id="updateName" crop="right" value=""/>
--- a/toolkit/mozapps/update/public/nsIUpdateService.idl
+++ b/toolkit/mozapps/update/public/nsIUpdateService.idl
@@ -399,16 +399,39 @@ interface nsIApplicationUpdateService : 
    * Whether or not the Update Service can download and install updates.
    * This is a function of whether or not the current user has access
    * privileges to the install directory.
    */
   readonly attribute boolean canUpdate;
 };
 
 /**
+ * A temporary interface to allow adding new methods without changing existing
+ * interfaces for Gecko 1.9.2. After the 1.9.2 release this interface will be
+ * removed.
+ */
+[scriptable, uuid(e22e4bf1-b18c-40cd-a2be-2d565723d056)]
+interface nsIApplicationUpdateService2 : nsIApplicationUpdateService
+{
+  /**
+   * Whether or not the Update Service can check for updates. This is a function
+   * of whether or not application update is disabled by the application and the
+   * platform the application is running on.
+   */
+  readonly attribute boolean canCheckForUpdates;
+
+  /**
+   * Whether or not the Update Service can download and install updates.
+   * This is a function of whether or not the current user has access
+   * privileges to the install directory.
+   */
+  readonly attribute boolean canApplyUpdates;
+};
+
+/**
  * An interface describing a global application service that maintains a list
  * of updates previously performed as well as the current active update.
  */
 [scriptable, uuid(fede66a9-9f96-4507-a22a-775ee885577e)]
 interface nsIUpdateManager : nsISupports
 {
   /**
    * Gets the update at the specified index
--- a/toolkit/mozapps/update/src/nsUpdateService.js.in
+++ b/toolkit/mozapps/update/src/nsUpdateService.js.in
@@ -185,33 +185,33 @@ XPCOMUtils.defineLazyGetter(this, "gOSVe
     catch (e) {
       // Not all platforms have a secondary widget library, so an error is nothing to worry about.
     }
     osVersion = encodeURIComponent(osVersion);
   }
   return osVersion;
 });
 
-XPCOMUtils.defineLazyGetter(this, "gCanUpdate", function aus_gCanUpdate() {
+XPCOMUtils.defineLazyGetter(this, "gCanApplyUpdates", function aus_gCanApplyUpdates() {
   try {
     const NORMAL_FILE_TYPE = Ci.nsILocalFile.NORMAL_FILE_TYPE;
     var updateTestFile = getUpdateFile([FILE_PERMS_TEST]);
-    LOG("gCanUpdate - testing write access " + updateTestFile.path);
+    LOG("gCanApplyUpdates - testing write access " + updateTestFile.path);
     if (updateTestFile.exists())
       updateTestFile.remove(false);
     updateTestFile.create(NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
     updateTestFile.remove(false);
 #ifdef XP_WIN
 #ifndef WINCE
     var sysInfo = Cc["@mozilla.org/system-info;1"].
                   getService(Ci.nsIPropertyBag2);
 
     // Example windowsVersion:  Windows XP == 5.1
     var windowsVersion = sysInfo.getProperty("version");
-    LOG("gCanUpdate - windowsVersion = " + windowsVersion);
+    LOG("gCanApplyUpdates - windowsVersion = " + windowsVersion);
 
   /**
 #    For Vista, updates can be performed to a location requiring admin
 #    privileges by requesting elevation via the UAC prompt when launching
 #    updater.exe if the appDir is under the Program Files directory
 #    (e.g. C:\Program Files\) and UAC is turned on and  we can elevate
 #    (e.g. user has a split token).
 #
@@ -226,23 +226,23 @@ XPCOMUtils.defineLazyGetter(this, "gCanU
         var fileLocator = Cc["@mozilla.org/file/directory_service;1"].
                           getService(Ci.nsIProperties);
         // KEY_UPDROOT will fail and throw an exception if
         // appDir is not under the Program Files, so we rely on that
         var dir = fileLocator.get(KEY_UPDROOT, Ci.nsIFile);
         // appDir is under Program Files, so check if the user can elevate
         userCanElevate = gApp.QueryInterface(Ci.nsIWinAppHelper).
                          userCanElevate;
-        LOG("gCanUpdate - on Vista, userCanElevate: " + userCanElevate);
+        LOG("gCanApplyUpdates - on Vista, userCanElevate: " + userCanElevate);
       }
       catch (ex) {
         // When the installation directory is not under Program Files,
         // fall through to checking if write access to the 
         // installation directory is available.
-        LOG("gCanUpdate - on Vista, appDir is not under Program Files");
+        LOG("gCanApplyUpdates - on Vista, appDir is not under Program Files");
       }
     }
 
     /**
 #      On Windows, we no longer store the update under the app dir if the app
 #      dir is under C:\Program Files.
 #
 #      If we are on Windows (including Vista, if we can't elevate) we need to
@@ -260,53 +260,60 @@ XPCOMUtils.defineLazyGetter(this, "gCanU
 #      3) UAC is turned on and the user is not an admin 
 #         (e.g. the user does not have a split token)
 #      4) UAC is turned on and the user is already elevated, so they can't be
 #         elevated again
      */
     if (!userCanElevate) {
       // if we're unable to create the test file this will throw an exception.
       var appDirTestFile = FileUtils.getFile(KEY_APPDIR, [FILE_PERMS_TEST]);
-      LOG("gCanUpdate - testing write access " + appDirTestFile.path);
+      LOG("gCanApplyUpdates - testing write access " + appDirTestFile.path);
       if (appDirTestFile.exists())
         appDirTestFile.remove(false)
       appDirTestFile.create(NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
       appDirTestFile.remove(false);
     }
 #endif //WINCE
 #endif //XP_WIN
   }
   catch (e) {
-     LOG("gCanUpdate - unable to update. Exception: " + e);
+     LOG("gCanApplyUpdates - unable to apply updates. Exception: " + e);
     // No write privileges to install directory
     return false;
   }
 
+  LOG("gCanApplyUpdates - able to apply updates");
+  return true;
+});
+
+XPCOMUtils.defineLazyGetter(this, "gCanCheckForUpdates", function aus_gCanCheckForUpdates() {
   // If the administrator has locked the app update functionality
   // OFF - this is not just a user setting, so disable the manual
   // UI too.
   var enabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true);
   if (!enabled && gPref.prefIsLocked(PREF_APP_UPDATE_ENABLED)) {
-    LOG("gCanUpdate - unable to update, disabled by pref");
+    LOG("gCanCheckForUpdates - unable to automatically check for updates, " +
+        "disabled by pref");
     return false;
   }
 
   // If we don't know the binary platform we're updating, we can't update.
   if (!gABI) {
-    LOG("gCanUpdate - unable tp update, unknown ABI");
+    LOG("gCanCheckForUpdates - unable to check for updates, unknown ABI");
     return false;
   }
 
   // If we don't know the OS version we're updating, we can't update.
   if (!gOSVersion) {
-    LOG("gCanUpdate - unable to update, unknown OS version");
+    LOG("gCanCheckForUpdates - unable to check for updates, unknown OS " +
+        "version");
     return false;
   }
 
-  LOG("gCanUpdate - able to update");
+  LOG("gCanCheckForUpdates - able to check for updates");
   return true;
 });
 
 /**
  * Logs a string to the error console.
  * @param   string
  *          The string to write to the error console.
  */
@@ -1074,31 +1081,32 @@ UpdateService.prototype = {
 
       // Prevent leaking the downloader (bug 454964)
       this._downloader = null;
       break;
     }
   },
 
   /**
-   * The following needs to happen during the profile-after-change notification:
+   * The following needs to happen during the post-update-processing
+   * notification from nsUpdateServiceStub.js:
    * 1. post update processing
    * 2. resume of a download that was in progress during a previous session
    * 3. start of a complete update download after the failure to apply a partial
    *    update
    */
 
   /**
    * Perform post-processing on updates lingering in the updates directory
    * from a previous browser session - either report install failures (and
    * optionally attempt to fetch a different version if appropriate) or
    * notify the user of install success.
    */
   _postUpdateProcessing: function AUS__postUpdateProcessing() {
-    if (!gCanUpdate) {
+    if (!this.canUpdate) {
       LOG("UpdateService:_postUpdateProcessing - unable to update");
       return;
     }
 
     var status = readStatusFile(getUpdatesDir());
     /**
      * STATE_NONE status means the update.status file is not present, because
      * either:
@@ -1344,16 +1352,23 @@ UpdateService.prototype = {
      */
     if (update.type == "major") {
       LOG("Checker:_selectAndInstallUpdate - prompting because it is a major " +
           "update");
       this._showPrompt(update);
       return;
     }
 
+    if (!gCanApplyUpdates) {
+      LOG("Checker:_selectAndInstallUpdate - prompting because the user is " +
+          "unable to apply updates");
+      this._showPrompt(update);
+      return;
+    }
+
     if (!getPref("getBoolPref", PREF_APP_UPDATE_AUTO, true)) {
       LOG("Checker:_selectAndInstallUpdate - prompting because silent " +
           "install is disabled");
       this._showPrompt(update);
       return;
     }
 
     if (getPref("getIntPref", PREF_APP_UPDATE_MODE, 1) == 0) {
@@ -1463,17 +1478,17 @@ UpdateService.prototype = {
    */
   onUpdateStarted: function AUS_onUpdateStarted() {
   },
 
   /**
    * See nsIExtensionManager.idl
    */
   onUpdateEnded: function AUS_onUpdateEnded() {
-    if (this._incompatAddonsCount > 0) {
+    if (this._incompatAddonsCount > 0 || !gCanApplyUpdates) {
       LOG("Checker:onUpdateEnded - prompting because there are incompatible " +
           "add-ons");
       this._showPrompt(this._update);
     }
     else {
       LOG("UpdateService:onUpdateEnded - no need to show prompt, just " +
           "download the update");
       var status = this.downloadUpdate(this._update, true);
@@ -1515,17 +1530,31 @@ UpdateService.prototype = {
       this._backgroundChecker = new Checker();
     return this._backgroundChecker;
   },
 
   /**
    * See nsIUpdateService.idl
    */
   get canUpdate() {
-    return gCanUpdate;
+    return gCanCheckForUpdates && gCanApplyUpdates;
+  },
+
+  /**
+   * See nsIUpdateService.idl
+   */
+  get canCheckForUpdates() {
+    return gCanCheckForUpdates;
+  },
+
+  /**
+   * See nsIUpdateService.idl
+   */
+  get canApplyUpdates() {
+    return gCanApplyUpdates;
   },
 
   /**
    * See nsIUpdateService.idl
    */
   addDownloadListener: function AUS_addDownloadListener(listener) {
     if (!this._downloader) {
       LOG("UpdateService:addDownloadListener - no downloader!");
@@ -1594,31 +1623,34 @@ UpdateService.prototype = {
     return this._downloader && this._downloader.isBusy;
   },
 
   // nsIClassInfo
   flags: Ci.nsIClassInfo.SINGLETON,
   implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
   getHelperForLanguage: function(language) null,
   getInterfaces: function AUS_getInterfaces(count) {
-    var interfaces = [Ci.nsIApplicationUpdateService, Ci.nsITimerCallback,
+    var interfaces = [Ci.nsIApplicationUpdateService,
+                      Ci.nsIApplicationUpdateService2,
+                      Ci.nsITimerCallback,
                       Ci.nsIObserver];
     count.value = interfaces.length;
     return interfaces;
   },
 
   classDescription: "Update Service",
   contractID: "@mozilla.org/updates/update-service;1",
   classID: Components.ID("{B3C290A6-3943-4B89-8BBE-C01EB7B3B311}"),
   _xpcom_categories: [{ category: CATEGORY_UPDATE_TIMER,
                         value: "@mozilla.org/updates/update-service;1," +
                                "getService,background-update-timer," +
                                 PREF_APP_UPDATE_INTERVAL + ",86400" }],
   _xpcom_factory: UpdateServiceFactory,
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIApplicationUpdateService,
+                                         Ci.nsIApplicationUpdateService2,
                                          Ci.nsIAddonUpdateCheckListener,
                                          Ci.nsITimerCallback,
                                          Ci.nsIObserver])
 };
 
 /**
  * A service to manage active and past updates.
  * @constructor
@@ -2055,17 +2087,17 @@ Checker.prototype = {
   },
 
   /**
    * Whether or not we are allowed to do update checking.
    */
   _enabled: true,
   get enabled() {
     return getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true) &&
-           gCanUpdate && this._enabled;
+           gCanCheckForUpdates && this._enabled;
   },
 
   /**
    * See nsIUpdateService.idl
    */
   stopChecking: function UC_stopChecking(duration) {
     // Always stop the current check
     if (this._request)
--- a/toolkit/mozapps/update/src/nsUpdateTimerManager.js
+++ b/toolkit/mozapps/update/src/nsUpdateTimerManager.js
@@ -186,17 +186,17 @@ TimerManager.prototype = {
         LOG("TimerManager:notify - update-timer category registered" +
             (cid ? " for " + cid : "") + " without required parameters - " +
              "skipping");
         continue;
       }
 
       let interval = getPref("getIntPref", prefInterval, defaultInterval);
       prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/,
-                                                                      timerID);
+                                                                  timerID);
       if (gPref.prefHasUserValue(prefLastUpdate)) {
         lastUpdateTime = gPref.getIntPref(prefLastUpdate);
       }
       else {
         lastUpdateTime = now + this._fudge;
         gPref.setIntPref(prefLastUpdate, lastUpdateTime);
         continue;
       }