Bug 901329 - Thunderbird doesn't start when using master password and Google CalDAV OAuth2 authentication. r=mmecca, a=Fallen
authorPhilipp Kewisch <mozilla@kewis.ch>
Tue, 01 Oct 2013 12:32:15 +0200
changeset 16307 e60119b64401d11921c0ade8ef691b9236c42946
parent 16306 554b3201dd7b609bda96e66fbdd7cfd7aeac0baa
child 16308 103749fcdfcafbb85ccd5219713d12a2d0d66604
push id1007
push userryanvm@gmail.com
push dateTue, 08 Oct 2013 13:04:23 +0000
treeherdercomm-beta@018ae8d19d04 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmmecca, Fallen
bugs901329
Bug 901329 - Thunderbird doesn't start when using master password and Google CalDAV OAuth2 authentication. r=mmecca, a=Fallen
calendar/base/modules/OAuth2.jsm
calendar/base/modules/calAuthUtils.jsm
calendar/providers/caldav/calDavCalendar.js
--- a/calendar/base/modules/OAuth2.jsm
+++ b/calendar/base/modules/OAuth2.jsm
@@ -83,18 +83,22 @@ OAuth2.prototype = {
 
         this._browserRequest = {
             promptText: "auth prompt",
             account: this,
             url: this.authURI + "?" + params,
             _active: true,
             iconURI: "",
             cancelled: function() {
-                if (!this._active)
+                if (!this._active) {
                     return;
+                }
+
+                this.account.finishAuthorizationRequest();
+                this.account.onAuthorizationFailed();
             },
 
             loaded: function (aWindow, aWebProgress) {
                 if (!this._active) {
                     return;
                 }
 
                 this._listener = {
@@ -145,22 +149,28 @@ OAuth2.prototype = {
       if (!("_browserRequest" in this))
         return;
 
       this._browserRequest._active = false;
       if ("_listener" in this._browserRequest)
         this._browserRequest._listener._cleanUp();
       delete this._browserRequest;
     },
+
     onAuthorizationReceived: function(aData) {
       this.log.info("authorization received" + aData);
       let results = parseURLData(aData);
       this.requestAccessToken(results.code, OAuth2.CODE_AUTHORIZATION);
     },
 
+    onAuthorizationFailed: function() {
+        this.connecting = false;
+        this.connectFailureCallback();
+    },
+
     requestAccessToken: function requestAccessToken(aCode, aType) {
 
         let params = [
             ["client_id", this.consumerKey],
             ["client_secret", this.consumerSecret],
             ["grant_type", aType],
         ];
 
--- a/calendar/base/modules/calAuthUtils.jsm
+++ b/calendar/base/modules/calAuthUtils.jsm
@@ -90,17 +90,19 @@ cal.auth = {
                                          .createInstance(Components.interfaces.nsILoginInfo);
             newLoginInfo.init(aHostName, null, aRealm, aUsername, aPassword, "", "");
             if (logins.length > 0) {
                 Services.logins.modifyLogin(logins[0], newLoginInfo);
             } else {
                 Services.logins.addLogin(newLoginInfo);
             }
         } catch (exc) {
-            cal.ASSERT(false, exc);
+            // Only show the message if its not an abort, which can happen if
+            // the user canceled the master password dialog
+            cal.ASSERT(exc.result == Components.results.NS_ERROR_ABORT, exc);
         }
     },
 
     /**
      * Helper to retrieve an entry from the password manager.
      *
      * @param in  aUsername     The username to search
      * @param out aPassword     The corresponding password
--- a/calendar/providers/caldav/calDavCalendar.js
+++ b/calendar/providers/caldav/calDavCalendar.js
@@ -1,14 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/Timer.jsm");
 
 Components.utils.import("resource://calendar/modules/calUtils.jsm");
 Components.utils.import("resource://calendar/modules/calXMLUtils.jsm");
 Components.utils.import("resource://calendar/modules/calIteratorUtils.jsm");
 Components.utils.import("resource://calendar/modules/calProviderUtils.jsm");
 Components.utils.import("resource://calendar/modules/calAuthUtils.jsm");
 Components.utils.import("resource://calendar/modules/OAuth2.jsm");
 
@@ -1545,48 +1546,89 @@ calDavCalendar.prototype = {
         function authSuccess() {
             self.checkDavResourceType(aChangeLogListener);
         }
         function authFailed() {
             self.setProperty("disabled", "true");
             self.setProperty("auto-enabled", "true");
             self.completeCheckServerInfo(aChangeLogListener, Components.results.NS_ERROR_FAILURE);
         }
+        function connect() {
+            // Use the async prompter to avoid multiple master password prompts
+            let promptlistener = {
+                onPromptStart: function() {
+                    // Usually this function should be synchronous. The OAuth
+                    // connection itself is asynchronous, but if a master
+                    // password is prompted it will block on that.
+                    this.onPromptAuthAvailable();
+                    return true;
+                },
+
+                onPromptAuthAvailable: function() {
+                    self.oauth.connect(authSuccess, authFailed, true);
+                },
+                onPromptCanceled: authFailed
+            };
+            let asyncprompter = Components.classes["@mozilla.org/messenger/msgAsyncPrompter;1"]
+                                          .getService(Components.interfaces.nsIMsgAsyncPrompter);
+            asyncprompter.queueAsyncAuthPrompt(self.uri.spec, false, promptlistener);
+        }
         if (this.mUri.host == "apidata.googleusercontent.com") {
             if (!this.oauth) {
                 this.oauth = new OAuth2(OAUTH_BASE_URI, OAUTH_SCOPE,
                                         OAUTH_CLIENT_ID, OAUTH_HASH);
                 let sessionId = this.id;
                 let pwMgrId = "Google CalDAV v2";
 
                 Object.defineProperty(this.oauth, "refreshToken", {
                     get: function getRefreshToken() {
                         if (!this.mRefreshToken) {
                             var pass = { value: null };
-                            cal.auth.passwordManagerGet(sessionId, pass, sessionId, pwMgrId);
+                            try {
+                                cal.auth.passwordManagerGet(sessionId, pass, sessionId, pwMgrId);
+                            } catch (e if e.result == Components.results.NS_ERROR_ABORT) {
+                                // User might have cancelled the master password prompt, thats ok
+                            }
                             this.mRefreshToken = pass.value;
                         }
                         return this.mRefreshToken;
                     },
                     set: function setRefreshToken(val) {
-                        if (!val) {
-                            cal.auth.passwordManagerRemove(sessionId, sessionId, pwMgrId);
-                        } else {
-                            cal.auth.passwordManagerSave(sessionId, val, sessionId, pwMgrId);
+                        try {
+                            if (!val) {
+                                cal.auth.passwordManagerRemove(sessionId, sessionId, pwMgrId);
+                            } else {
+                                cal.auth.passwordManagerSave(sessionId, val, sessionId, pwMgrId);
+                            }
+                        } catch (e if e.result == Components.results.NS_ERROR_ABORT) {
+                            // User might have cancelled the master password prompt, thats ok
                         }
                         return (this.mRefreshToken = val);
                     },
                     enumerable: true
                 });
             }
 
             if (this.oauth.accessToken) {
                 authSuccess();
             } else {
-                this.oauth.connect(authSuccess, authFailed, true);
+                // bug 901329: If the calendar window isn't loaded yet the
+                // master password prompt will show just the buttons and
+                // possibly hang. If we postpone until the window is loaded,
+                // all is well.
+                function postpone() {
+                    let win = cal.getCalendarWindow();
+                    if (!win || win.document.readyState != "complete") {
+                        setTimeout(postpone, 0);
+                    } else {
+                        connect();
+                    }
+                }
+
+                setTimeout(postpone, 0);
             }
         } else {
             authSuccess();
         }
     },
 
     /**
      * Checks that the calendar URI exists and is a CalDAV calendar.