From 0395a076c826ffed7f9a83f8b8f5d49730f773ba Mon Sep 17 00:00:00 2001 From: Gaming4JC Date: Mon, 30 Dec 2019 08:46:47 -0500 Subject: Bug 1176399 - Multiple requests for master password when GMail OAuth2 is enabled. --- mailnews/base/public/nsIMsgAsyncPrompter.idl | 26 +++++++++++++---- mailnews/base/src/msgAsyncPrompter.js | 33 +++++++++++++++++----- mailnews/base/src/msgOAuth2Module.js | 42 ++++++++++++++++++++++------ mailnews/imap/src/nsImapProtocol.cpp | 7 +++++ mailnews/local/src/nsPop3Protocol.cpp | 7 +++++ mailnews/news/src/nsNNTPProtocol.cpp | 7 +++++ 6 files changed, 101 insertions(+), 21 deletions(-) (limited to 'mailnews') diff --git a/mailnews/base/public/nsIMsgAsyncPrompter.idl b/mailnews/base/public/nsIMsgAsyncPrompter.idl index 5a59c4f39..4e1f81d12 100644 --- a/mailnews/base/public/nsIMsgAsyncPrompter.idl +++ b/mailnews/base/public/nsIMsgAsyncPrompter.idl @@ -35,20 +35,36 @@ interface nsIMsgAsyncPrompter : nsISupports { in nsIMsgAsyncPromptListener aCaller); }; +[scriptable, function, uuid(acca94c9-378e-46e3-9a91-6655bf9c91a3)] +interface nsIMsgAsyncPromptCallback : nsISupports { + /** + * Called when an auth result is available. Can be passed as a function. + * + * @param aResult True if there is auth information available following the + * prompt, false otherwise. + */ + void onAuthResult(in boolean aResult); +}; + /** * This is used in combination with nsIMsgAsyncPrompter. */ [scriptable, uuid(fb5307a3-39d0-462e-92c8-c5c288a2612f)] interface nsIMsgAsyncPromptListener : nsISupports { /** - * Called when the listener should do its prompt. The listener - * should not return until the prompt is complete. - * - * @return True if there is auth information available following the prompt, - * false otherwise. + * This method has been deprecated, please use onPromptStartAsync instead. */ boolean onPromptStart(); + /** + * Called when the listener should do its prompt. This can happen + * synchronously or asynchronously, but in any case when done the callback + * method should be called. + * + * @param aCallback The callback to execute when auth prompt has completed. + */ + void onPromptStartAsync(in nsIMsgAsyncPromptCallback aCallback); + /** * Called in the case that the queued prompt was combined with another and * there is now authentication information available. diff --git a/mailnews/base/src/msgAsyncPrompter.js b/mailnews/base/src/msgAsyncPrompter.js index 58b5288e9..ae114683a 100644 --- a/mailnews/base/src/msgAsyncPrompter.js +++ b/mailnews/base/src/msgAsyncPrompter.js @@ -2,6 +2,7 @@ * 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/Deprecated.jsm"); Components.utils.import("resource://gre/modules/Services.jsm"); Components.utils.import("resource://gre/modules/Task.jsm"); Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); @@ -19,28 +20,46 @@ runnablePrompter.prototype = { _asyncPrompter: null, _hashKey: null, + _promiseAuthPrompt: function(listener) { + return new Promise((resolve, reject) => { + try { + listener.onPromptStartAsync({ onAuthResult: resolve }); + } catch (e) { + if (e.result == Components.results.NS_ERROR_XPC_JSOBJECT_HAS_NO_FUNCTION_NAMED) { + // Fall back to onPromptStart, for add-ons compat + Deprecated.warning("onPromptStart has been replaced by onPromptStartAsync", + "https://bugzilla.mozilla.org/show_bug.cgi?id=1176399"); + let ok = listener.onPromptStart(); + resolve(ok); + } else { + reject(e); + } + } + }); + }, + run: Task.async(function *() { yield Services.logins.initializationPromise; this._asyncPrompter._log.debug("Running prompt for " + this._hashKey); let prompter = this._asyncPrompter._pendingPrompts[this._hashKey]; let ok = false; try { - ok = prompter.first.onPromptStart(); - } - catch (ex) { + ok = yield this._promiseAuthPrompt(prompter.first); + } catch (ex) { Components.utils.reportError("runnablePrompter:run: " + ex + "\n"); + prompter.first.onPromptCanceled(); } delete this._asyncPrompter._pendingPrompts[this._hashKey]; for (var consumer of prompter.consumers) { try { - if (ok) + if (ok) { consumer.onPromptAuthAvailable(); - else + } else { consumer.onPromptCanceled(); - } - catch (ex) { + } + } catch (ex) { // Log the error for extension devs and others to pick up. Components.utils.reportError("runnablePrompter:run: consumer.onPrompt* reported an exception: " + ex + "\n"); } diff --git a/mailnews/base/src/msgOAuth2Module.js b/mailnews/base/src/msgOAuth2Module.js index 407ab0519..22d5dc572 100644 --- a/mailnews/base/src/msgOAuth2Module.js +++ b/mailnews/base/src/msgOAuth2Module.js @@ -126,19 +126,43 @@ OAuth2Module.prototype = { } } - // Otherwise, we need a new login, so create one and fill it in. - let login = Cc["@mozilla.org/login-manager/loginInfo;1"] - .createInstance(Ci.nsILoginInfo); - login.init(this._loginUrl, null, this._scope, this._username, token, - '', ''); - loginMgr.addLogin(login); + // Unless the token is null, we need to create and fill in a new login + if (token) { + let login = Cc["@mozilla.org/login-manager/loginInfo;1"] + .createInstance(Ci.nsILoginInfo); + login.init(this._loginUrl, null, this._scope, this._username, token, + '', ''); + loginMgr.addLogin(login); + } return token; }, connect(aWithUI, aListener) { - this._oauth.connect(() => aListener.onSuccess(this._oauth.accessToken), - x => aListener.onFailure(x), - aWithUI, false); + let oauth = this._oauth; + let promptlistener = { + onPromptStartAsync: function(callback) { + oauth.connect(() => { + this.onPromptAuthAvailable(); + callback.onAuthResult(true); + }, (err) => { + this.onPromptCanceled(); + callback.onAuthResult(false); + }, aWithUI, false); + }, + + onPromptAuthAvailable: function() { + aListener.onSuccess(oauth.accessToken); + }, + onPromptCanceled: function() { + aListener.onFailure(Components.results.NS_ERROR_ABORT); + }, + onPromptStart: function() {} + }; + + let asyncprompter = Components.classes["@mozilla.org/messenger/msgAsyncPrompter;1"] + .getService(Components.interfaces.nsIMsgAsyncPrompter); + let promptkey = this._loginUrl + "/" + this._username; + asyncprompter.queueAsyncAuthPrompt(promptkey, false, promptlistener); }, buildXOAuth2String() { diff --git a/mailnews/imap/src/nsImapProtocol.cpp b/mailnews/imap/src/nsImapProtocol.cpp index 4cfa9dab2..c8e3ceb67 100644 --- a/mailnews/imap/src/nsImapProtocol.cpp +++ b/mailnews/imap/src/nsImapProtocol.cpp @@ -8513,6 +8513,13 @@ nsresult nsImapProtocol::GetPassword(nsCString &password, return rv; } +NS_IMETHODIMP nsImapProtocol::OnPromptStartAsync(nsIMsgAsyncPromptCallback *aCallback) +{ + bool result = false; + OnPromptStart(&result); + return aCallback->OnAuthResult(result); +} + // This is called from the UI thread. NS_IMETHODIMP nsImapProtocol::OnPromptStart(bool *aResult) diff --git a/mailnews/local/src/nsPop3Protocol.cpp b/mailnews/local/src/nsPop3Protocol.cpp index 5d9d9145a..de129a494 100644 --- a/mailnews/local/src/nsPop3Protocol.cpp +++ b/mailnews/local/src/nsPop3Protocol.cpp @@ -740,6 +740,13 @@ nsresult nsPop3Protocol::StartGetAsyncPassword(Pop3StatesEnum aNextState) return rv; } +NS_IMETHODIMP nsPop3Protocol::OnPromptStartAsync(nsIMsgAsyncPromptCallback *aCallback) +{ + bool result = false; + OnPromptStart(&result); + return aCallback->OnAuthResult(result); +} + NS_IMETHODIMP nsPop3Protocol::OnPromptStart(bool *aResult) { MOZ_LOG(POP3LOGMODULE, LogLevel::Debug, (POP3LOG("OnPromptStart()"))); diff --git a/mailnews/news/src/nsNNTPProtocol.cpp b/mailnews/news/src/nsNNTPProtocol.cpp index 8ce367faa..035dff6e6 100644 --- a/mailnews/news/src/nsNNTPProtocol.cpp +++ b/mailnews/news/src/nsNNTPProtocol.cpp @@ -2472,6 +2472,13 @@ nsresult nsNNTPProtocol::PasswordResponse() return NS_ERROR_FAILURE; } +NS_IMETHODIMP nsNNTPProtocol::OnPromptStartAsync(nsIMsgAsyncPromptCallback *aCallback) +{ + bool result = false; + OnPromptStart(&result); + return aCallback->OnAuthResult(result); +} + NS_IMETHODIMP nsNNTPProtocol::OnPromptStart(bool *authAvailable) { NS_ENSURE_ARG_POINTER(authAvailable); -- cgit v1.2.3