diff options
Diffstat (limited to 'comm/mailnews/base/src/MailAuthenticator.jsm')
-rw-r--r-- | comm/mailnews/base/src/MailAuthenticator.jsm | 468 |
1 files changed, 468 insertions, 0 deletions
diff --git a/comm/mailnews/base/src/MailAuthenticator.jsm b/comm/mailnews/base/src/MailAuthenticator.jsm new file mode 100644 index 0000000000..cf52a88f17 --- /dev/null +++ b/comm/mailnews/base/src/MailAuthenticator.jsm @@ -0,0 +1,468 @@ +/* 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/. */ + +const EXPORTED_SYMBOLS = [ + "SmtpAuthenticator", + "NntpAuthenticator", + "Pop3Authenticator", + "ImapAuthenticator", +]; + +var { MailCryptoUtils } = ChromeUtils.import( + "resource:///modules/MailCryptoUtils.jsm" +); +var { MailStringUtils } = ChromeUtils.import( + "resource:///modules/MailStringUtils.jsm" +); + +/** + * A base class for interfaces when authenticating a mail connection. + */ +class MailAuthenticator { + /** + * Get the hostname for a connection. + * + * @returns {string} + */ + get hostname() { + throw Components.Exception( + "hostname getter not implemented", + Cr.NS_ERROR_NOT_IMPLEMENTED + ); + } + + /** + * Get the username for a connection. + * + * @returns {string} + */ + get username() { + throw Components.Exception( + "username getter not implemented", + Cr.NS_ERROR_NOT_IMPLEMENTED + ); + } + + /** + * Forget cached password. + */ + forgetPassword() { + throw Components.Exception( + "forgetPassword not implemented", + Cr.NS_ERROR_NOT_IMPLEMENTED + ); + } + + /** + * Get the password for a connection. + * + * @returns {string} + */ + getPassword() { + throw Components.Exception( + "getPassword not implemented", + Cr.NS_ERROR_NOT_IMPLEMENTED + ); + } + + /** + * Get the CRAM-MD5 auth token for a connection. + * + * @param {string} password - The password, used as HMAC-MD5 secret. + * @param {string} challenge - The base64 encoded server challenge. + * @returns {string} + */ + getCramMd5Token(password, challenge) { + // Hash the challenge. + let signature = MailCryptoUtils.hmacMd5( + new TextEncoder().encode(password), + new TextEncoder().encode(atob(challenge)) + ); + // Get the hex form of the signature. + let hex = [...signature].map(x => x.toString(16).padStart(2, "0")).join(""); + return btoa(`${this.username} ${hex}`); + } + + /** + * Get the OAuth token for a connection. + * + * @returns {string} + */ + async getOAuthToken() { + throw Components.Exception( + "getOAuthToken not implemented", + Cr.NS_ERROR_NOT_IMPLEMENTED + ); + } + + /** + * Init a nsIMailAuthModule instance for GSSAPI auth. + * + * @param {('smtp'|'imap')} protocol - The protocol name. + */ + initGssapiAuth(protocol) { + this._authModule = Cc["@mozilla.org/mail/auth-module;1"].createInstance( + Ci.nsIMailAuthModule + ); + this._authModule.init( + "sasl-gssapi", // Auth module type + `${protocol}@${this.hostname}`, + 0, // nsIAuthModule::REQ_DEFAULT + null, // domain + this.username, + null // password + ); + } + + /** + * Get the next token in a sequence of GSSAPI auth steps. + * + * @param {string} inToken - A base64 encoded string, usually server challenge. + * @returns {string} + */ + getNextGssapiToken(inToken) { + return this._authModule.getNextToken(inToken); + } + + /** + * Init a nsIMailAuthModule instance for NTLM auth. + */ + initNtlmAuth() { + this._authModule = Cc["@mozilla.org/mail/auth-module;1"].createInstance( + Ci.nsIMailAuthModule + ); + this._authModule.init( + "ntlm", // Auth module type + null, // Service name + 0, // nsIAuthModule::REQ_DEFAULT + null, // domain + this.username, + this.getPassword() + ); + } + + /** + * Get the next token in a sequence of NTLM auth steps. + * + * @param {string} inToken - A base64 encoded string, usually server challenge. + * @returns {string} + */ + getNextNtlmToken(inToken) { + return this._authModule.getNextToken(inToken); + } + + /** + * Show a dialog for authentication failure. + * + * @returns {number} - 0: Retry; 1: Cancel; 2: New password. + */ + promptAuthFailed() { + throw Components.Exception( + "promptAuthFailed not implemented", + Cr.NS_ERROR_NOT_IMPLEMENTED + ); + } + + /** + * Show a dialog for authentication failure. + * + * @param {nsIMsgWindow} msgWindow - The associated msg window. + * @param {string} accountname - A user defined account name or the server hostname. + * @returns {number} 0: Retry; 1: Cancel; 2: New password. + */ + _promptAuthFailed(msgWindow, accountname) { + let bundle = Services.strings.createBundle( + "chrome://messenger/locale/messenger.properties" + ); + let message = bundle.formatStringFromName("mailServerLoginFailed2", [ + this.hostname, + this.username, + ]); + + let title = bundle.formatStringFromName( + "mailServerLoginFailedTitleWithAccount", + [accountname] + ); + + let retryButtonLabel = bundle.GetStringFromName( + "mailServerLoginFailedRetryButton" + ); + let newPasswordButtonLabel = bundle.GetStringFromName( + "mailServerLoginFailedEnterNewPasswordButton" + ); + let buttonFlags = + Ci.nsIPrompt.BUTTON_POS_0 * Ci.nsIPrompt.BUTTON_TITLE_IS_STRING + + Ci.nsIPrompt.BUTTON_POS_1 * Ci.nsIPrompt.BUTTON_TITLE_CANCEL + + Ci.nsIPrompt.BUTTON_POS_2 * Ci.nsIPrompt.BUTTON_TITLE_IS_STRING; + let dummyValue = { value: false }; + + return Services.prompt.confirmEx( + msgWindow?.domWindow, + title, + message, + buttonFlags, + retryButtonLabel, + null, + newPasswordButtonLabel, + null, + dummyValue + ); + } +} + +/** + * Collection of helper functions for authenticating an SMTP connection. + * + * @augments {MailAuthenticator} + */ +class SmtpAuthenticator extends MailAuthenticator { + /** + * @param {nsISmtpServer} server - The associated server instance. + */ + constructor(server) { + super(); + this._server = server; + } + + get hostname() { + return this._server.hostname; + } + + get username() { + return this._server.username; + } + + forgetPassword() { + this._server.forgetPassword(); + } + + getPassword() { + if (this._server.password) { + return this._server.password; + } + let composeBundle = Services.strings.createBundle( + "chrome://messenger/locale/messengercompose/composeMsgs.properties" + ); + let username = this._server.username; + let promptString; + if (username) { + promptString = composeBundle.formatStringFromName( + "smtpEnterPasswordPromptWithUsername", + [this._server.hostname, username] + ); + } else { + promptString = composeBundle.formatStringFromName( + "smtpEnterPasswordPrompt", + [this._server.hostname] + ); + } + let promptTitle = composeBundle.formatStringFromName( + "smtpEnterPasswordPromptTitleWithHostname", + [this._server.hostname] + ); + return this._server.getPasswordWithUI(promptString, promptTitle); + } + + /** + * Get the ByteString form of the current password. + * + * @returns {string} + */ + getByteStringPassword() { + return MailStringUtils.stringToByteString(this.getPassword()); + } + + /** + * Get the PLAIN auth token for a connection. + * + * @returns {string} + */ + getPlainToken() { + // According to rfc4616#section-2, password should be UTF-8 BinaryString + // before base64 encoded. + return btoa("\0" + this.username + "\0" + this.getByteStringPassword()); + } + + async getOAuthToken() { + let oauth2Module = Cc["@mozilla.org/mail/oauth2-module;1"].createInstance( + Ci.msgIOAuth2Module + ); + if (!oauth2Module.initFromSmtp(this._server)) { + return Promise.reject(`initFromSmtp failed, hostname: ${this.hostname}`); + } + return new Promise((resolve, reject) => { + oauth2Module.connect(true, { + onSuccess: token => { + resolve(token); + }, + onFailure: e => { + reject(e); + }, + }); + }); + } + + promptAuthFailed() { + return this._promptAuthFailed( + null, + this._server.description || this.hostname + ); + } +} + +/** + * Collection of helper functions for authenticating an incoming server. + * + * @augments {MailAuthenticator} + */ +class IncomingServerAuthenticator extends MailAuthenticator { + /** + * @param {nsIMsgIncomingServer} server - The associated server instance. + */ + constructor(server) { + super(); + this._server = server; + } + + get hostname() { + return this._server.hostName; + } + + get username() { + return this._server.username; + } + + forgetPassword() { + this._server.forgetPassword(); + } + + /** + * Get the ByteString form of the current password. + * + * @returns {string} + */ + async getByteStringPassword() { + return MailStringUtils.stringToByteString(await this.getPassword()); + } + + /** + * Get the PLAIN auth token for a connection. + * + * @returns {string} + */ + async getPlainToken() { + // According to rfc4616#section-2, password should be UTF-8 BinaryString + // before base64 encoded. + return btoa( + "\0" + this.username + "\0" + (await this.getByteStringPassword()) + ); + } + + async getOAuthToken() { + let oauth2Module = Cc["@mozilla.org/mail/oauth2-module;1"].createInstance( + Ci.msgIOAuth2Module + ); + if (!oauth2Module.initFromMail(this._server)) { + return Promise.reject(`initFromMail failed, hostname: ${this.hostname}`); + } + return new Promise((resolve, reject) => { + oauth2Module.connect(true, { + onSuccess: token => { + resolve(token); + }, + onFailure: e => { + reject(e); + }, + }); + }); + } +} + +/** + * Collection of helper functions for authenticating a NNTP connection. + * + * @augments {IncomingServerAuthenticator} + */ +class NntpAuthenticator extends IncomingServerAuthenticator { + /** + * @returns {string} - NNTP server has no userName pref, need to pass it in. + */ + get username() { + return this._username; + } + + set username(value) { + this._username = value; + } + + promptAuthFailed() { + return this._promptAuthFailed(null, this._server.prettyName); + } +} + +/** + * Collection of helper functions for authenticating a POP connection. + * + * @augments {IncomingServerAuthenticator} + */ +class Pop3Authenticator extends IncomingServerAuthenticator { + async getPassword() { + if (this._server.password) { + return this._server.password; + } + let composeBundle = Services.strings.createBundle( + "chrome://messenger/locale/localMsgs.properties" + ); + let params = [this._server.username, this._server.hostName]; + let promptString = composeBundle.formatStringFromName( + "pop3EnterPasswordPrompt", + params + ); + let promptTitle = composeBundle.formatStringFromName( + "pop3EnterPasswordPromptTitleWithUsername", + [this._server.hostName] + ); + return this._server.wrappedJSObject.getPasswordWithUIAsync( + promptString, + promptTitle + ); + } + + promptAuthFailed() { + return this._promptAuthFailed(null, this._server.prettyName); + } +} + +/** + * Collection of helper functions for authenticating an IMAP connection. + * + * @augments {IncomingServerAuthenticator} + */ +class ImapAuthenticator extends IncomingServerAuthenticator { + async getPassword() { + if (this._server.password) { + return this._server.password; + } + let composeBundle = Services.strings.createBundle( + "chrome://messenger/locale/imapMsgs.properties" + ); + let params = [this._server.username, this._server.hostName]; + let promptString = composeBundle.formatStringFromName( + "imapEnterServerPasswordPrompt", + params + ); + let promptTitle = composeBundle.formatStringFromName( + "imapEnterPasswordPromptTitleWithUsername", + [this._server.hostName] + ); + return this._server.wrappedJSObject.getPasswordWithUIAsync( + promptString, + promptTitle + ); + } + + promptAuthFailed() { + return this._promptAuthFailed(null, this._server.prettyName); + } +} |