Bug 1788086 - Remove temporary server when account setup is finished. r=leftmostcat
Modernize verifyConfig. Hook up unload (cancel) to remove the temporary server if we got that far. Differential Revision: https://phabricator.services.mozilla.com/D174546 --HG-- rename : mail/components/accountcreation/verifyConfig.jsm => mail/components/accountcreation/ConfigVerifier.jsm
This commit is contained in:
Родитель
f4af19242e
Коммит
de4f22e8fe
|
@ -0,0 +1,374 @@
|
|||
/* 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 = ["ConfigVerifier"];
|
||||
|
||||
const { MailServices } = ChromeUtils.import(
|
||||
"resource:///modules/MailServices.jsm"
|
||||
);
|
||||
const { OAuth2Providers } = ChromeUtils.import(
|
||||
"resource:///modules/OAuth2Providers.jsm"
|
||||
);
|
||||
const { AccountCreationUtils } = ChromeUtils.import(
|
||||
"resource:///modules/accountcreation/AccountCreationUtils.jsm"
|
||||
);
|
||||
|
||||
/**
|
||||
* @implements {nsIUrlListener}
|
||||
* @implements {nsIInterfaceRequestor}
|
||||
*/
|
||||
class ConfigVerifier {
|
||||
QueryInterface = ChromeUtils.generateQI([
|
||||
"nsIInterfaceRequestor",
|
||||
"nsIUrlListener",
|
||||
]);
|
||||
|
||||
// @see {nsIInterfaceRequestor}
|
||||
getInterface(iid) {
|
||||
return this.QueryInterface(iid);
|
||||
}
|
||||
|
||||
constructor(msgWindow) {
|
||||
this.msgWindow = msgWindow;
|
||||
this._log = console.createInstance({
|
||||
prefix: "mail.setup",
|
||||
maxLogLevel: "Warn",
|
||||
maxLogLevelPref: "mail.setup.loglevel",
|
||||
});
|
||||
}
|
||||
|
||||
// @see {nsIUrlListener}
|
||||
OnStartRunningUrl(url) {
|
||||
this._log.debug(`Starting to verify configuration;
|
||||
email as username=${this.config.incoming.username !=
|
||||
this.config.identity.emailAddress}
|
||||
savedUsername=${this.config.usernameSaved ? "true" : "false"},
|
||||
authMethod=${this.server.authMethod}`);
|
||||
}
|
||||
|
||||
// @see {nsIUrlListener}
|
||||
OnStopRunningUrl(url, status) {
|
||||
if (Components.isSuccessCode(status)) {
|
||||
this._log.debug(`Configuration verified successfully!`);
|
||||
this.cleanup();
|
||||
this.successCallback(this.config);
|
||||
return;
|
||||
}
|
||||
|
||||
this._log.debug(`Verifying configuration failed; status=${status}`);
|
||||
|
||||
let certError = false;
|
||||
try {
|
||||
let nssErrorsService = Cc["@mozilla.org/nss_errors_service;1"].getService(
|
||||
Ci.nsINSSErrorsService
|
||||
);
|
||||
let errorClass = nssErrorsService.getErrorClass(status);
|
||||
if (errorClass == Ci.nsINSSErrorsService.ERROR_CLASS_BAD_CERT) {
|
||||
certError = true;
|
||||
}
|
||||
} catch (e) {
|
||||
// It's not an NSS error.
|
||||
}
|
||||
|
||||
if (certError) {
|
||||
let mailNewsUrl = url.QueryInterface(Ci.nsIMsgMailNewsUrl);
|
||||
let secInfo = mailNewsUrl.failedSecInfo;
|
||||
this.informUserOfCertError(secInfo, url.asciiHostPort);
|
||||
} else if (this.alter) {
|
||||
// Try other variations.
|
||||
this.server.closeCachedConnections();
|
||||
this.tryNextLogon(url);
|
||||
} else {
|
||||
// Logon failed, and we aren't supposed to try other variations.
|
||||
this._failed(url);
|
||||
}
|
||||
}
|
||||
|
||||
tryNextLogon(aPreviousUrl) {
|
||||
this._log.debug("Trying next logon variation");
|
||||
// check if we tried full email address as username
|
||||
if (this.config.incoming.username != this.config.identity.emailAddress) {
|
||||
this._log.debug("Changing username to email address.");
|
||||
this.config.usernameSaved = this.config.incoming.username;
|
||||
this.config.incoming.username = this.config.identity.emailAddress;
|
||||
this.config.outgoing.username = this.config.identity.emailAddress;
|
||||
this.server.username = this.config.incoming.username;
|
||||
this.server.password = this.config.incoming.password;
|
||||
this.verifyLogon();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.config.usernameSaved) {
|
||||
this._log.debug("Re-setting username.");
|
||||
// If we tried the full email address as the username, then let's go
|
||||
// back to trying just the username before trying the other cases.
|
||||
this.config.incoming.username = this.config.usernameSaved;
|
||||
this.config.outgoing.username = this.config.usernameSaved;
|
||||
this.config.usernameSaved = null;
|
||||
this.server.username = this.config.incoming.username;
|
||||
this.server.password = this.config.incoming.password;
|
||||
}
|
||||
|
||||
// sec auth seems to have failed, and we've tried both
|
||||
// varieties of user name, sadly.
|
||||
// So fall back to non-secure auth, and
|
||||
// again try the user name and email address as username
|
||||
if (this.server.socketType == Ci.nsMsgSocketType.SSL) {
|
||||
this._log.debug("Using SSL");
|
||||
} else if (this.server.socketType == Ci.nsMsgSocketType.alwaysSTARTTLS) {
|
||||
this._log.debug("Using STARTTLS");
|
||||
}
|
||||
if (
|
||||
this.config.incoming.authAlternatives &&
|
||||
this.config.incoming.authAlternatives.length
|
||||
) {
|
||||
// We may be dropping back to insecure auth methods here,
|
||||
// which is not good. But then again, we already warned the user,
|
||||
// if it is a config without SSL.
|
||||
|
||||
let brokenAuth = this.config.incoming.auth;
|
||||
// take the next best method (compare chooseBestAuthMethod() in guess)
|
||||
this.config.incoming.auth = this.config.incoming.authAlternatives.shift();
|
||||
this.server.authMethod = this.config.incoming.auth;
|
||||
// Assume that SMTP server has same methods working as incoming.
|
||||
// Broken assumption, but we currently have no SMTP verification.
|
||||
// TODO: implement real SMTP verification
|
||||
if (
|
||||
this.config.outgoing.auth == brokenAuth &&
|
||||
this.config.outgoing.authAlternatives.includes(
|
||||
this.config.incoming.auth
|
||||
)
|
||||
) {
|
||||
this.config.outgoing.auth = this.config.incoming.auth;
|
||||
}
|
||||
this._log.debug(`Trying next auth method: ${this.server.authMethod}`);
|
||||
this.verifyLogon();
|
||||
return;
|
||||
}
|
||||
|
||||
// Tried all variations we can. Give up.
|
||||
this._log.debug("Have tried all variations. Giving up.");
|
||||
this._failed(aPreviousUrl);
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
try {
|
||||
// Avoid pref pollution, clear out server prefs.
|
||||
if (this.server) {
|
||||
MailServices.accounts.removeIncomingServer(this.server, true);
|
||||
this.server = null;
|
||||
}
|
||||
} catch (e) {
|
||||
this._log.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
_failed(url) {
|
||||
this.cleanup();
|
||||
let code = url.errorCode || "login-error-unknown";
|
||||
let msg = url.errorMessage;
|
||||
// *Only* for known (!) username/password errors, show our message.
|
||||
// But there are 1000 other reasons why it could have failed, e.g.
|
||||
// server not reachable, bad auth method, server hiccups, or even
|
||||
// custom server messages that tell the user to do something,
|
||||
// so show the backend error message, unless we are certain
|
||||
// that it's a wrong username or password.
|
||||
if (
|
||||
!msg || // Normal IMAP login error sets no error msg
|
||||
code == "pop3UsernameFailure" ||
|
||||
code == "pop3PasswordFailed" ||
|
||||
code == "imapOAuth2Error"
|
||||
) {
|
||||
msg = AccountCreationUtils.getStringBundle(
|
||||
"chrome://messenger/locale/accountCreationModel.properties"
|
||||
).GetStringFromName("cannot_login.error");
|
||||
}
|
||||
this.errorCallback(new Error(msg));
|
||||
}
|
||||
|
||||
/**
|
||||
* Inform users that we got a certificate error for the specified location.
|
||||
* Allow them to add an exception for it.
|
||||
*
|
||||
* @param {nsITransportSecurityInfo} secInfo
|
||||
* @param {string} location - "host:port" that had the problem.
|
||||
*/
|
||||
informUserOfCertError(secInfo, location) {
|
||||
this._log.debug(`Informing user about cert error for ${location}`);
|
||||
let params = {
|
||||
exceptionAdded: false,
|
||||
securityInfo: secInfo,
|
||||
prefetchCert: true,
|
||||
location,
|
||||
};
|
||||
Services.wm
|
||||
.getMostRecentWindow("mail:3pane")
|
||||
.browsingContext.topChromeWindow.openDialog(
|
||||
"chrome://pippki/content/exceptionDialog.xhtml",
|
||||
"exceptionDialog",
|
||||
"chrome,centerscreen,modal",
|
||||
params
|
||||
);
|
||||
if (!params.exceptionAdded) {
|
||||
this._log.debug(`Did not accept exception for ${location}`);
|
||||
this.cleanup();
|
||||
let errorMsg = AccountCreationUtils.getStringBundle(
|
||||
"chrome://messenger/locale/accountCreationModel.properties"
|
||||
).GetStringFromName("cannot_login.error");
|
||||
this.errorCallback(new Error(errorMsg));
|
||||
} else {
|
||||
this._log.debug(`Accept exception for ${location} - will retry logon.`);
|
||||
// Retry the logon now that we've added the cert exception.
|
||||
this.verifyLogon();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This checks a given config, by trying a real connection and login,
|
||||
* with username and password.
|
||||
*
|
||||
* @param {AccountConfig} config - The guessed account config.
|
||||
* username, password, realname, emailaddress etc. are not filled out,
|
||||
* but placeholders to be filled out via replaceVariables().
|
||||
* @param alter {boolean} - Try other usernames and login schemes, until
|
||||
* login works. Warning: Modifies |config|.
|
||||
* @returns {Promise<AccountConfig>} the successful configuration.
|
||||
* @throws {Error} when we could guess not the config, either
|
||||
* because we have not found anything or because there was an error
|
||||
* (e.g. no network connection).
|
||||
* The ex.message will contain a user-presentable message.
|
||||
*/
|
||||
async verifyConfig(config, alter) {
|
||||
this.alter = alter;
|
||||
return new Promise((resolve, reject) => {
|
||||
this.config = config;
|
||||
this.successCallback = resolve;
|
||||
this.errorCallback = reject;
|
||||
if (
|
||||
MailServices.accounts.findServer(
|
||||
config.incoming.username,
|
||||
config.incoming.hostname,
|
||||
config.incoming.type,
|
||||
config.incoming.port
|
||||
)
|
||||
) {
|
||||
reject(new Error("Incoming server exists"));
|
||||
return;
|
||||
}
|
||||
|
||||
// incoming server
|
||||
if (!this.server) {
|
||||
this.server = MailServices.accounts.createIncomingServer(
|
||||
config.incoming.username,
|
||||
config.incoming.hostname,
|
||||
config.incoming.type
|
||||
);
|
||||
}
|
||||
this.server.port = config.incoming.port;
|
||||
this.server.password = config.incoming.password;
|
||||
this.server.socketType = config.incoming.socketType;
|
||||
|
||||
this._log.info(
|
||||
"Setting incoming server authMethod to " + config.incoming.auth
|
||||
);
|
||||
this.server.authMethod = config.incoming.auth;
|
||||
|
||||
try {
|
||||
// Lookup OAuth2 issuer if needed.
|
||||
// -- Incoming.
|
||||
if (
|
||||
config.incoming.auth == Ci.nsMsgAuthMethod.OAuth2 &&
|
||||
(!config.incoming.oauthSettings ||
|
||||
!config.incoming.oauthSettings.issuer ||
|
||||
!config.incoming.oauthSettings.scope)
|
||||
) {
|
||||
let details = OAuth2Providers.getHostnameDetails(
|
||||
config.incoming.hostname
|
||||
);
|
||||
if (!details) {
|
||||
reject(
|
||||
new Error(
|
||||
`Could not get OAuth2 details for hostname=${config.incoming.hostname}.`
|
||||
)
|
||||
);
|
||||
}
|
||||
config.incoming.oauthSettings = {
|
||||
issuer: details[0],
|
||||
scope: details[1],
|
||||
};
|
||||
}
|
||||
// -- Outgoing.
|
||||
if (
|
||||
config.outgoing.auth == Ci.nsMsgAuthMethod.OAuth2 &&
|
||||
(!config.outgoing.oauthSettings ||
|
||||
!config.outgoing.oauthSettings.issuer ||
|
||||
!config.outgoing.oauthSettings.scope)
|
||||
) {
|
||||
let details = OAuth2Providers.getHostnameDetails(
|
||||
config.outgoing.hostname
|
||||
);
|
||||
if (!details) {
|
||||
reject(
|
||||
new Error(
|
||||
`Could not get OAuth2 details for hostname=${config.outgoing.hostname}.`
|
||||
)
|
||||
);
|
||||
}
|
||||
config.outgoing.oauthSettings = {
|
||||
issuer: details[0],
|
||||
scope: details[1],
|
||||
};
|
||||
}
|
||||
if (config.incoming.owaURL) {
|
||||
this.server.setUnicharValue("owa_url", config.incoming.owaURL);
|
||||
}
|
||||
if (config.incoming.ewsURL) {
|
||||
this.server.setUnicharValue("ews_url", config.incoming.ewsURL);
|
||||
}
|
||||
if (config.incoming.easURL) {
|
||||
this.server.setUnicharValue("eas_url", config.incoming.easURL);
|
||||
}
|
||||
|
||||
if (
|
||||
this.server.password ||
|
||||
this.server.authMethod == Ci.nsMsgAuthMethod.OAuth2
|
||||
) {
|
||||
this.verifyLogon();
|
||||
} else {
|
||||
// Avoid pref pollution, clear out server prefs.
|
||||
MailServices.accounts.removeIncomingServer(this.server, true);
|
||||
resolve(config);
|
||||
}
|
||||
} catch (e) {
|
||||
this._log.info("verifyConfig failed: " + e);
|
||||
// Avoid pref pollution, clear out server prefs.
|
||||
MailServices.accounts.removeIncomingServer(this.server, true);
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the provided credentials can log in to the incoming server.
|
||||
*/
|
||||
verifyLogon() {
|
||||
this._log.info("verifyLogon for server at " + this.server.hostName);
|
||||
// Save away the old callbacks.
|
||||
let saveCallbacks = this.msgWindow.notificationCallbacks;
|
||||
// Set our own callbacks - this works because verifyLogon will
|
||||
// synchronously create the transport and use the notification callbacks.
|
||||
// Our listener listens both for the url and cert errors.
|
||||
this.msgWindow.notificationCallbacks = this;
|
||||
// try to work around bug where backend is clearing password.
|
||||
try {
|
||||
this.server.password = this.config.incoming.password;
|
||||
let uri = this.server.verifyLogon(this, this.msgWindow);
|
||||
// clear msgWindow so url won't prompt for passwords.
|
||||
uri.QueryInterface(Ci.nsIMsgMailNewsUrl).msgWindow = null;
|
||||
} finally {
|
||||
// restore them
|
||||
this.msgWindow.notificationCallbacks = saveCallbacks;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,12 +20,12 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
|||
AddonManager: "resource://gre/modules/AddonManager.jsm",
|
||||
cal: "resource:///modules/calendar/calUtils.jsm",
|
||||
CardDAVUtils: "resource:///modules/CardDAVUtils.jsm",
|
||||
ConfigVerifier: "resource:///modules/accountcreation/ConfigVerifier.jsm",
|
||||
CreateInBackend: "resource:///modules/accountcreation/CreateInBackend.jsm",
|
||||
FetchConfig: "resource:///modules/accountcreation/FetchConfig.jsm",
|
||||
GuessConfig: "resource:///modules/accountcreation/GuessConfig.jsm",
|
||||
OAuth2Providers: "resource:///modules/OAuth2Providers.jsm",
|
||||
Sanitizer: "resource:///modules/accountcreation/Sanitizer.jsm",
|
||||
verifyConfig: "resource:///modules/accountcreation/verifyConfig.jsm",
|
||||
UIDensity: "resource:///modules/UIDensity.jsm",
|
||||
UIFontSize: "resource:///modules/UIFontSize.jsm",
|
||||
});
|
||||
|
@ -66,29 +66,6 @@ var {
|
|||
* - If user clicks OK, create the account
|
||||
*/
|
||||
|
||||
/**
|
||||
TODO for bug 549045:
|
||||
|
||||
- autodetect protocol
|
||||
Bugs
|
||||
- SSL cert errors
|
||||
- invalid cert (hostname mismatch) doesn't trigger warning dialog as it should
|
||||
- accept self-signed cert (e.g. imap.mail.ru) doesn't work
|
||||
(works without my patch),
|
||||
verifyConfig.js line 124 has no inServer, for whatever reason,
|
||||
although I didn't change verifyConfig.js at all
|
||||
(the change you see in that file is irrelevant: that was an attempt to fix
|
||||
the bug and clean up the code).
|
||||
Things to test (works for me):
|
||||
- state transitions, buttons enable, status msgs
|
||||
- stop button
|
||||
- showes up again after stopping detection and restarting it
|
||||
- when stopping [retest]: buttons proper?
|
||||
- enter nonsense domain. guess fails, (so automatically) manual,
|
||||
change domain to real one (not in DB), guess succeeds.
|
||||
former bug: goes to manual first shortly, then to result
|
||||
*/
|
||||
|
||||
// Keep track of the prefers-reduce-motion media query for JS based animations.
|
||||
var gReducedMotion;
|
||||
|
||||
|
@ -2257,17 +2234,21 @@ var gAccountSetup = {
|
|||
: this._currentConfig.source;
|
||||
|
||||
let self = this;
|
||||
// logic function defined in verifyConfig.js
|
||||
verifyConfig(
|
||||
configFilledIn,
|
||||
// guess login config?
|
||||
configFilledIn.source != AccountConfig.kSourceXML,
|
||||
// TODO Instead, the following line would be correct, but I cannot use it,
|
||||
// because some other code doesn't adhere to the expectations/specs.
|
||||
// Find out what it was and fix it.
|
||||
// concreteConfig.source == AccountConfig.kSourceGuess,
|
||||
this._msgWindow,
|
||||
function(successfulConfig) {
|
||||
let verifier = new ConfigVerifier(this._msgWindow);
|
||||
window.addEventListener("unload", event => {
|
||||
verifier.cleanup();
|
||||
});
|
||||
verifier
|
||||
.verifyConfig(
|
||||
configFilledIn,
|
||||
// guess login config?
|
||||
configFilledIn.source != AccountConfig.kSourceXML
|
||||
// TODO Instead, the following line would be correct, but I cannot use it,
|
||||
// because some other code doesn't adhere to the expectations/specs.
|
||||
// Find out what it was and fix it.
|
||||
// concreteConfig.source == AccountConfig.kSourceGuess,
|
||||
)
|
||||
.then(successfulConfig => {
|
||||
// success
|
||||
self.stopLoadingState(
|
||||
successfulConfig.incoming.password
|
||||
|
@ -2275,8 +2256,7 @@ var gAccountSetup = {
|
|||
: null
|
||||
);
|
||||
|
||||
// the auth might have changed, so we
|
||||
// should back-port it to the current config.
|
||||
// The auth might have changed, so we should update the current config.
|
||||
self._currentConfig.incoming.auth = successfulConfig.incoming.auth;
|
||||
self._currentConfig.outgoing.auth = successfulConfig.outgoing.auth;
|
||||
self._currentConfig.incoming.username =
|
||||
|
@ -2301,8 +2281,8 @@ var gAccountSetup = {
|
|||
telemetryKey,
|
||||
1
|
||||
);
|
||||
},
|
||||
function(e) {
|
||||
})
|
||||
.catch(e => {
|
||||
// failed
|
||||
// Could be a wrong password, but there are 1000 other
|
||||
// reasons why this failed. Only the backend knows.
|
||||
|
@ -2333,10 +2313,12 @@ var gAccountSetup = {
|
|||
telemetryKey,
|
||||
1
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {AccountConfig} concreteConfig - The config to use.
|
||||
*/
|
||||
finish(concreteConfig) {
|
||||
gAccountSetupLogger.debug("creating account in backend");
|
||||
let newAccount = CreateInBackend.createAccountInBackend(concreteConfig);
|
||||
|
|
|
@ -8,6 +8,7 @@ JAR_MANIFESTS += ["jar.mn"]
|
|||
EXTRA_JS_MODULES.accountcreation += [
|
||||
"AccountConfig.jsm",
|
||||
"AccountCreationUtils.jsm",
|
||||
"ConfigVerifier.jsm",
|
||||
"CreateInBackend.jsm",
|
||||
"ExchangeAutoDiscover.jsm",
|
||||
"FetchConfig.jsm",
|
||||
|
@ -15,7 +16,6 @@ EXTRA_JS_MODULES.accountcreation += [
|
|||
"GuessConfig.jsm",
|
||||
"readFromXML.jsm",
|
||||
"Sanitizer.jsm",
|
||||
"verifyConfig.jsm",
|
||||
]
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += [
|
||||
|
|
|
@ -1,439 +0,0 @@
|
|||
/* 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 = ["verifyConfig"];
|
||||
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
lazy,
|
||||
"AccountConfig",
|
||||
"resource:///modules/accountcreation/AccountConfig.jsm"
|
||||
);
|
||||
const { AccountCreationUtils } = ChromeUtils.import(
|
||||
"resource:///modules/accountcreation/AccountCreationUtils.jsm"
|
||||
);
|
||||
|
||||
const { MailServices } = ChromeUtils.import(
|
||||
"resource:///modules/MailServices.jsm"
|
||||
);
|
||||
const { OAuth2Providers } = ChromeUtils.import(
|
||||
"resource:///modules/OAuth2Providers.jsm"
|
||||
);
|
||||
|
||||
const {
|
||||
assert,
|
||||
ddump,
|
||||
Exception,
|
||||
gAccountSetupLogger,
|
||||
getStringBundle,
|
||||
} = AccountCreationUtils;
|
||||
|
||||
/**
|
||||
* This checks a given config, by trying a real connection and login,
|
||||
* with username and password.
|
||||
*
|
||||
* TODO
|
||||
* - give specific errors, bug 555448
|
||||
* - return a working |Abortable| to allow cancel
|
||||
*
|
||||
* @param accountConfig {AccountConfig} The guessed account config.
|
||||
* username, password, realname, emailaddress etc. are not filled out,
|
||||
* but placeholders to be filled out via replaceVariables().
|
||||
* @param alter {boolean}
|
||||
* Try other usernames and login schemes, until login works.
|
||||
* Warning: Modifies |accountConfig|.
|
||||
*
|
||||
* This function is async.
|
||||
* @param successCallback function(accountConfig)
|
||||
* Called when we could guess the config.
|
||||
* For accountConfig, see below.
|
||||
* @param errorCallback function(ex)
|
||||
* Called when we could guess not the config, either
|
||||
* because we have not found anything or
|
||||
* because there was an error (e.g. no network connection).
|
||||
* The ex.message will contain a user-presentable message.
|
||||
*/
|
||||
function verifyConfig(
|
||||
config,
|
||||
alter,
|
||||
msgWindow,
|
||||
successCallback,
|
||||
errorCallback
|
||||
) {
|
||||
ddump("verify config:\n" + config);
|
||||
assert(
|
||||
config instanceof lazy.AccountConfig,
|
||||
"BUG: Arg 'config' needs to be an AccountConfig object"
|
||||
);
|
||||
assert(typeof alter == "boolean");
|
||||
assert(typeof successCallback == "function");
|
||||
assert(typeof errorCallback == "function");
|
||||
|
||||
if (
|
||||
MailServices.accounts.findServer(
|
||||
config.incoming.username,
|
||||
config.incoming.hostname,
|
||||
config.incoming.type,
|
||||
config.incoming.port
|
||||
)
|
||||
) {
|
||||
errorCallback("Incoming server exists");
|
||||
return;
|
||||
}
|
||||
|
||||
// incoming server
|
||||
let inServer = MailServices.accounts.createIncomingServer(
|
||||
config.incoming.username,
|
||||
config.incoming.hostname,
|
||||
config.incoming.type
|
||||
);
|
||||
inServer.port = config.incoming.port;
|
||||
inServer.password = config.incoming.password;
|
||||
inServer.socketType = config.incoming.socketType;
|
||||
|
||||
gAccountSetupLogger.info(
|
||||
"Setting incoming server authMethod to " + config.incoming.auth
|
||||
);
|
||||
inServer.authMethod = config.incoming.auth;
|
||||
|
||||
try {
|
||||
// Lookup OAuth2 issuer if needed.
|
||||
// -- Incoming.
|
||||
if (
|
||||
config.incoming.auth == Ci.nsMsgAuthMethod.OAuth2 &&
|
||||
(!config.incoming.oauthSettings ||
|
||||
!config.incoming.oauthSettings.issuer ||
|
||||
!config.incoming.oauthSettings.scope)
|
||||
) {
|
||||
let details = OAuth2Providers.getHostnameDetails(
|
||||
config.incoming.hostname
|
||||
);
|
||||
if (!details) {
|
||||
throw new Error(
|
||||
`Could not get OAuth2 details for hostname=${config.incoming.hostname}.`
|
||||
);
|
||||
}
|
||||
config.incoming.oauthSettings = { issuer: details[0], scope: details[1] };
|
||||
}
|
||||
// -- Outgoing.
|
||||
if (
|
||||
config.outgoing.auth == Ci.nsMsgAuthMethod.OAuth2 &&
|
||||
(!config.outgoing.oauthSettings ||
|
||||
!config.outgoing.oauthSettings.issuer ||
|
||||
!config.outgoing.oauthSettings.scope)
|
||||
) {
|
||||
let details = OAuth2Providers.getHostnameDetails(
|
||||
config.outgoing.hostname
|
||||
);
|
||||
if (!details) {
|
||||
throw new Error(
|
||||
`Could not get OAuth2 details for hostname=${config.outgoing.hostname}.`
|
||||
);
|
||||
}
|
||||
config.outgoing.oauthSettings = { issuer: details[0], scope: details[1] };
|
||||
}
|
||||
if (config.incoming.owaURL) {
|
||||
inServer.setUnicharValue("owa_url", config.incoming.owaURL);
|
||||
}
|
||||
if (config.incoming.ewsURL) {
|
||||
inServer.setUnicharValue("ews_url", config.incoming.ewsURL);
|
||||
}
|
||||
if (config.incoming.easURL) {
|
||||
inServer.setUnicharValue("eas_url", config.incoming.easURL);
|
||||
}
|
||||
|
||||
if (inServer.password || inServer.authMethod == Ci.nsMsgAuthMethod.OAuth2) {
|
||||
verifyLogon(
|
||||
config,
|
||||
inServer,
|
||||
alter,
|
||||
msgWindow,
|
||||
successCallback,
|
||||
errorCallback
|
||||
);
|
||||
} else {
|
||||
// Avoid pref pollution, clear out server prefs.
|
||||
MailServices.accounts.removeIncomingServer(inServer, true);
|
||||
successCallback(config);
|
||||
}
|
||||
} catch (e) {
|
||||
gAccountSetupLogger.info("verifyConfig failed: " + e);
|
||||
// Avoid pref pollution, clear out server prefs.
|
||||
MailServices.accounts.removeIncomingServer(inServer, true);
|
||||
errorCallback(e);
|
||||
}
|
||||
}
|
||||
|
||||
function verifyLogon(
|
||||
config,
|
||||
inServer,
|
||||
alter,
|
||||
msgWindow,
|
||||
successCallback,
|
||||
errorCallback
|
||||
) {
|
||||
gAccountSetupLogger.info("verifyLogon for server at " + inServer.hostName);
|
||||
// hack - save away the old callbacks.
|
||||
let saveCallbacks = msgWindow.notificationCallbacks;
|
||||
// set our own callbacks - this works because verifyLogon will
|
||||
// synchronously create the transport and use the notification callbacks.
|
||||
let listener = new urlListener(
|
||||
config,
|
||||
inServer,
|
||||
alter,
|
||||
msgWindow,
|
||||
successCallback,
|
||||
errorCallback
|
||||
);
|
||||
// our listener listens both for the url and cert errors.
|
||||
msgWindow.notificationCallbacks = listener;
|
||||
// try to work around bug where backend is clearing password.
|
||||
try {
|
||||
inServer.password = config.incoming.password;
|
||||
let uri = inServer.verifyLogon(listener, msgWindow);
|
||||
// clear msgWindow so url won't prompt for passwords.
|
||||
uri.QueryInterface(Ci.nsIMsgMailNewsUrl).msgWindow = null;
|
||||
} finally {
|
||||
// restore them
|
||||
msgWindow.notificationCallbacks = saveCallbacks;
|
||||
}
|
||||
}
|
||||
|
||||
function urlListener(
|
||||
config,
|
||||
server,
|
||||
alter,
|
||||
msgWindow,
|
||||
successCallback,
|
||||
errorCallback
|
||||
) {
|
||||
this.mConfig = config;
|
||||
this.mServer = server;
|
||||
this.mAlter = alter;
|
||||
this.mSuccessCallback = successCallback;
|
||||
this.mErrorCallback = errorCallback;
|
||||
this.mMsgWindow = msgWindow;
|
||||
this.mCertError = false;
|
||||
this._log = gAccountSetupLogger;
|
||||
}
|
||||
urlListener.prototype = {
|
||||
OnStartRunningUrl(aUrl) {
|
||||
this._log.debug(`Starting to verify configuration;
|
||||
email as username=${this.mConfig.incoming.username !=
|
||||
this.mConfig.identity.emailAddress}
|
||||
savedUsername=${this.mConfig.usernameSaved ? "true" : "false"},
|
||||
authMethod=${this.mServer.authMethod}`);
|
||||
},
|
||||
|
||||
OnStopRunningUrl(aUrl, aExitCode) {
|
||||
if (Components.isSuccessCode(aExitCode)) {
|
||||
this._log.debug(`Configuration verified successfully!`);
|
||||
this._cleanup();
|
||||
this.mSuccessCallback(this.mConfig);
|
||||
return;
|
||||
}
|
||||
|
||||
this._log.debug(`Verifying configuration failed; status=${aExitCode}`);
|
||||
|
||||
try {
|
||||
let nssErrorsService = Cc["@mozilla.org/nss_errors_service;1"].getService(
|
||||
Ci.nsINSSErrorsService
|
||||
);
|
||||
let errorClass = nssErrorsService.getErrorClass(aExitCode);
|
||||
if (errorClass == Ci.nsINSSErrorsService.ERROR_CLASS_BAD_CERT) {
|
||||
this.mCertError = true;
|
||||
}
|
||||
} catch (e) {
|
||||
// It's not an NSS error.
|
||||
}
|
||||
|
||||
if (this.mCertError) {
|
||||
let mailNewsUrl = aUrl.QueryInterface(Ci.nsIMsgMailNewsUrl);
|
||||
let secInfo = mailNewsUrl.failedSecInfo;
|
||||
this.informUserOfCertError(secInfo, aUrl.asciiHostPort);
|
||||
} else if (this.mAlter) {
|
||||
// Try other variations.
|
||||
this.mServer.closeCachedConnections();
|
||||
this.tryNextLogon(aUrl);
|
||||
} else {
|
||||
// Logon failed, and we aren't supposed to try other variations.
|
||||
this._failed(aUrl);
|
||||
}
|
||||
},
|
||||
|
||||
tryNextLogon(aPreviousUrl) {
|
||||
this._log.debug("Trying next logon variation");
|
||||
// check if we tried full email address as username
|
||||
if (this.mConfig.incoming.username != this.mConfig.identity.emailAddress) {
|
||||
this._log.debug("Changing username to email address.");
|
||||
this.mConfig.usernameSaved = this.mConfig.incoming.username;
|
||||
this.mConfig.incoming.username = this.mConfig.identity.emailAddress;
|
||||
this.mConfig.outgoing.username = this.mConfig.identity.emailAddress;
|
||||
this.mServer.username = this.mConfig.incoming.username;
|
||||
this.mServer.password = this.mConfig.incoming.password;
|
||||
verifyLogon(
|
||||
this.mConfig,
|
||||
this.mServer,
|
||||
this.mAlter,
|
||||
this.mMsgWindow,
|
||||
this.mSuccessCallback,
|
||||
this.mErrorCallback
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.mConfig.usernameSaved) {
|
||||
this._log.debug("Re-setting username.");
|
||||
// If we tried the full email address as the username, then let's go
|
||||
// back to trying just the username before trying the other cases.
|
||||
this.mConfig.incoming.username = this.mConfig.usernameSaved;
|
||||
this.mConfig.outgoing.username = this.mConfig.usernameSaved;
|
||||
this.mConfig.usernameSaved = null;
|
||||
this.mServer.username = this.mConfig.incoming.username;
|
||||
this.mServer.password = this.mConfig.incoming.password;
|
||||
}
|
||||
|
||||
// sec auth seems to have failed, and we've tried both
|
||||
// varieties of user name, sadly.
|
||||
// So fall back to non-secure auth, and
|
||||
// again try the user name and email address as username
|
||||
assert(this.mConfig.incoming.auth == this.mServer.authMethod);
|
||||
if (this.mServer.socketType == Ci.nsMsgSocketType.SSL) {
|
||||
this._log.debug("Using SSL");
|
||||
} else if (this.mServer.socketType == Ci.nsMsgSocketType.alwaysSTARTTLS) {
|
||||
this._log.debug("Using STARTTLS");
|
||||
}
|
||||
if (
|
||||
this.mConfig.incoming.authAlternatives &&
|
||||
this.mConfig.incoming.authAlternatives.length
|
||||
) {
|
||||
// We may be dropping back to insecure auth methods here,
|
||||
// which is not good. But then again, we already warned the user,
|
||||
// if it is a config without SSL.
|
||||
|
||||
let brokenAuth = this.mConfig.incoming.auth;
|
||||
// take the next best method (compare chooseBestAuthMethod() in guess)
|
||||
this.mConfig.incoming.auth = this.mConfig.incoming.authAlternatives.shift();
|
||||
this.mServer.authMethod = this.mConfig.incoming.auth;
|
||||
// Assume that SMTP server has same methods working as incoming.
|
||||
// Broken assumption, but we currently have no SMTP verification.
|
||||
// TODO implement real SMTP verification
|
||||
if (
|
||||
this.mConfig.outgoing.auth == brokenAuth &&
|
||||
this.mConfig.outgoing.authAlternatives.includes(
|
||||
this.mConfig.incoming.auth
|
||||
)
|
||||
) {
|
||||
this.mConfig.outgoing.auth = this.mConfig.incoming.auth;
|
||||
}
|
||||
this._log.debug(`Trying next auth method: ${this.mServer.authMethod}`);
|
||||
verifyLogon(
|
||||
this.mConfig,
|
||||
this.mServer,
|
||||
this.mAlter,
|
||||
this.mMsgWindow,
|
||||
this.mSuccessCallback,
|
||||
this.mErrorCallback
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Tried all variations we can. Give up.
|
||||
this._log.debug("Have tried all variations. Giving up.");
|
||||
this._failed(aPreviousUrl);
|
||||
},
|
||||
|
||||
_cleanup() {
|
||||
try {
|
||||
// Avoid pref pollution, clear out server prefs.
|
||||
if (this.mServer) {
|
||||
MailServices.accounts.removeIncomingServer(this.mServer, true);
|
||||
this.mServer = null;
|
||||
}
|
||||
} catch (e) {
|
||||
this._log.error(e);
|
||||
}
|
||||
},
|
||||
|
||||
_failed(aUrl) {
|
||||
this._cleanup();
|
||||
var code = aUrl.errorCode || "login-error-unknown";
|
||||
var msg = aUrl.errorMessage;
|
||||
// *Only* for known (!) username/password errors, show our message.
|
||||
// But there are 1000 other reasons why it could have failed, e.g.
|
||||
// server not reachable, bad auth method, server hiccups, or even
|
||||
// custom server messages that tell the user to do something,
|
||||
// so show the backend error message, unless we are certain
|
||||
// that it's a wrong username or password.
|
||||
if (
|
||||
!msg || // Normal IMAP login error sets no error msg
|
||||
code == "pop3UsernameFailure" ||
|
||||
code == "pop3PasswordFailed" ||
|
||||
code == "imapOAuth2Error"
|
||||
) {
|
||||
msg = getStringBundle(
|
||||
"chrome://messenger/locale/accountCreationModel.properties"
|
||||
).GetStringFromName("cannot_login.error");
|
||||
}
|
||||
var ex = new Exception(msg);
|
||||
ex.code = code;
|
||||
this.mErrorCallback(ex);
|
||||
},
|
||||
|
||||
/**
|
||||
* Inform users that we got a certificate error for the specified location.
|
||||
* Allow them to add an exception for it.
|
||||
*
|
||||
* @param {nsITransportSecurityInfo} secInfo
|
||||
* @param {string} location - "host:port" that had the problem.
|
||||
*/
|
||||
informUserOfCertError(secInfo, location) {
|
||||
this._log.debug(`Informing user about cert error for ${location}`);
|
||||
let params = {
|
||||
exceptionAdded: false,
|
||||
securityInfo: secInfo,
|
||||
prefetchCert: true,
|
||||
location,
|
||||
};
|
||||
Services.wm
|
||||
.getMostRecentWindow("mail:3pane")
|
||||
.browsingContext.topChromeWindow.openDialog(
|
||||
"chrome://pippki/content/exceptionDialog.xhtml",
|
||||
"exceptionDialog",
|
||||
"chrome,centerscreen,modal",
|
||||
params
|
||||
);
|
||||
if (!params.exceptionAdded) {
|
||||
this._log.debug(`Did not accept exception for ${location}`);
|
||||
this._cleanup();
|
||||
let errorMsg = getStringBundle(
|
||||
"chrome://messenger/locale/accountCreationModel.properties"
|
||||
).GetStringFromName("cannot_login.error");
|
||||
this.mErrorCallback(new Exception(errorMsg));
|
||||
} else {
|
||||
this._log.debug(`Accept exception for ${location} - will retry logon.`);
|
||||
// Retry the logon now that we've added the cert exception.
|
||||
verifyLogon(
|
||||
this.mConfig,
|
||||
this.mServer,
|
||||
this.mAlter,
|
||||
this.mMsgWindow,
|
||||
this.mSuccessCallback,
|
||||
this.mErrorCallback
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
// nsIInterfaceRequestor
|
||||
getInterface(iid) {
|
||||
return this.QueryInterface(iid);
|
||||
},
|
||||
|
||||
// nsISupports
|
||||
QueryInterface: ChromeUtils.generateQI([
|
||||
"nsIInterfaceRequestor",
|
||||
"nsIUrlListener",
|
||||
]),
|
||||
};
|
|
@ -280,13 +280,13 @@
|
|||
"resource:///modules/WindowsJumpLists.jsm": "comm/mail/modules/WindowsJumpLists.jsm",
|
||||
"resource:///modules/accountcreation/AccountConfig.jsm": "comm/mail/components/accountcreation/AccountConfig.jsm",
|
||||
"resource:///modules/accountcreation/AccountCreationUtils.jsm": "comm/mail/components/accountcreation/AccountCreationUtils.jsm",
|
||||
"resource:///modules/accountcreation/ConfigVerifier.jsm": "comm/mail/components/accountcreation/ConfigVerifier.jsm",
|
||||
"resource:///modules/accountcreation/CreateInBackend.jsm": "comm/mail/components/accountcreation/CreateInBackend.jsm",
|
||||
"resource:///modules/accountcreation/ExchangeAutoDiscover.jsm": "comm/mail/components/accountcreation/ExchangeAutoDiscover.jsm",
|
||||
"resource:///modules/accountcreation/FetchConfig.jsm": "comm/mail/components/accountcreation/FetchConfig.jsm",
|
||||
"resource:///modules/accountcreation/FetchHTTP.jsm": "comm/mail/components/accountcreation/FetchHTTP.jsm",
|
||||
"resource:///modules/accountcreation/GuessConfig.jsm": "comm/mail/components/accountcreation/GuessConfig.jsm",
|
||||
"resource:///modules/accountcreation/readFromXML.jsm": "comm/mail/components/accountcreation/readFromXML.jsm",
|
||||
"resource:///modules/accountcreation/verifyConfig.jsm": "comm/mail/components/accountcreation/verifyConfig.jsm",
|
||||
"resource:///modules/activity/activityModules.jsm": "comm/mail/components/activity/modules/activityModules.jsm",
|
||||
"resource:///modules/activity/alertHook.jsm": "comm/mail/components/activity/modules/alertHook.jsm",
|
||||
"resource:///modules/activity/autosync.jsm": "comm/mail/components/activity/modules/autosync.jsm",
|
||||
|
|
Загрузка…
Ссылка в новой задаче