summaryrefslogtreecommitdiffstats
path: root/comm/mail/components/accountcreation/AccountConfig.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/components/accountcreation/AccountConfig.jsm')
-rw-r--r--comm/mail/components/accountcreation/AccountConfig.jsm463
1 files changed, 463 insertions, 0 deletions
diff --git a/comm/mail/components/accountcreation/AccountConfig.jsm b/comm/mail/components/accountcreation/AccountConfig.jsm
new file mode 100644
index 0000000000..59b9604725
--- /dev/null
+++ b/comm/mail/components/accountcreation/AccountConfig.jsm
@@ -0,0 +1,463 @@
+/* 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/. */
+
+/**
+ * This file creates the class AccountConfig, which is a JS object that holds
+ * a configuration for a certain account. It is *not* created in the backend
+ * yet (use aw-createAccount.js for that), and it may be incomplete.
+ *
+ * Several AccountConfig objects may co-exist, e.g. for autoconfig.
+ * One AccountConfig object is used to prefill and read the widgets
+ * in the Wizard UI.
+ * When we autoconfigure, we autoconfig writes the values into a
+ * new object and returns that, and the caller can copy these
+ * values into the object used by the UI.
+ *
+ * See also
+ * <https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat>
+ * for values stored.
+ */
+
+const EXPORTED_SYMBOLS = ["AccountConfig"];
+
+const lazy = {};
+
+ChromeUtils.defineModuleGetter(
+ lazy,
+ "AccountCreationUtils",
+ "resource:///modules/accountcreation/AccountCreationUtils.jsm"
+);
+ChromeUtils.defineModuleGetter(
+ lazy,
+ "Sanitizer",
+ "resource:///modules/accountcreation/Sanitizer.jsm"
+);
+
+function AccountConfig() {
+ this.incoming = this.createNewIncoming();
+ this.incomingAlternatives = [];
+ this.outgoing = this.createNewOutgoing();
+ this.outgoingAlternatives = [];
+ this.identity = {
+ // displayed real name of user
+ realname: "%REALNAME%",
+ // email address of user, as shown in From of outgoing mails
+ emailAddress: "%EMAILADDRESS%",
+ };
+ this.inputFields = [];
+ this.domains = [];
+}
+AccountConfig.prototype = {
+ // @see createNewIncoming()
+ incoming: null,
+ // @see createNewOutgoing()
+ outgoing: null,
+ /**
+ * Other servers which can be used instead of |incoming|,
+ * in order of decreasing preference.
+ * (|incoming| itself should not be included here.)
+ * { Array of incoming/createNewIncoming() }
+ */
+ incomingAlternatives: null,
+ outgoingAlternatives: null,
+ // just an internal string to refer to this. Do not show to user.
+ id: null,
+ // who created the config.
+ // { one of kSource* }
+ source: null,
+ /**
+ * Used for telemetry purposes.
+ * - for kSourceXML, subSource is one of xml-from-{disk, db, isp-https, isp-http}.
+ * - for kSourceExchange, subSource is one of exchange-from-urlN[-guess].
+ */
+ subSource: null,
+ displayName: null,
+ // { Array of { varname (value without %), displayName, exampleValue } }
+ inputFields: null,
+ // email address domains for which this config is applicable
+ // { Array of Strings }
+ domains: null,
+
+ /**
+ * Factory function for incoming and incomingAlternatives
+ */
+ createNewIncoming() {
+ return {
+ // { String-enum: "pop3", "imap", "nntp", "exchange" }
+ type: null,
+ hostname: null,
+ // { Integer }
+ port: null,
+ // May be a placeholder (starts and ends with %). { String }
+ username: null,
+ password: null,
+ // {nsMsgSocketType} @see MailNewsTypes2.idl. -1 means not inited
+ socketType: -1,
+ /**
+ * true when the cert is invalid (and thus SSL useless), because it's
+ * 1) not from an accepted CA (including self-signed certs)
+ * 2) for a different hostname or
+ * 3) expired.
+ * May go back to false when user explicitly accepted the cert.
+ */
+ badCert: false,
+ /**
+ * How to log in to the server: plaintext or encrypted pw, GSSAPI etc.
+ * Defined by Ci.nsMsgAuthMethod
+ * Same as server pref "authMethod".
+ */
+ auth: 0,
+ /**
+ * Other auth methods that we think the server supports.
+ * They are ordered by descreasing preference.
+ * (|auth| itself is not included in |authAlternatives|)
+ * {Array of Ci.nsMsgAuthMethod} (same as .auth)
+ */
+ authAlternatives: null,
+ // in minutes { Integer }
+ checkInterval: 10,
+ loginAtStartup: true,
+ // POP3 only:
+ // Not yet implemented. { Boolean }
+ useGlobalInbox: false,
+ leaveMessagesOnServer: true,
+ daysToLeaveMessagesOnServer: 14,
+ deleteByAgeFromServer: true,
+ // When user hits delete, delete from local store and from server
+ deleteOnServerWhenLocalDelete: true,
+ downloadOnBiff: true,
+ // Override `addThisServer` for a specific incoming server
+ useGlobalPreferredServer: false,
+
+ // OAuth2 configuration, if needed.
+ oauthSettings: null,
+
+ // for Microsoft Exchange servers. Optional.
+ owaURL: null,
+ ewsURL: null,
+ easURL: null,
+ // for when an addon overrides the account type. Optional.
+ addonAccountType: null,
+ };
+ },
+ /**
+ * Factory function for outgoing and outgoingAlternatives
+ */
+ createNewOutgoing() {
+ return {
+ type: "smtp",
+ hostname: null,
+ port: null, // see incoming
+ username: null, // see incoming. may be null, if auth is 0.
+ password: null, // see incoming. may be null, if auth is 0.
+ socketType: -1, // see incoming
+ badCert: false, // see incoming
+ auth: 0, // see incoming
+ authAlternatives: null, // see incoming
+ addThisServer: true, // if we already have an SMTP server, add this
+ // if we already have an SMTP server, use it.
+ useGlobalPreferredServer: false,
+ // we should reuse an already configured SMTP server.
+ // nsISmtpServer.key
+ existingServerKey: null,
+ // user display value for existingServerKey
+ existingServerLabel: null,
+
+ // OAuth2 configuration, if needed.
+ oauthSettings: null,
+ };
+ },
+
+ /**
+ * The configuration needs an addon to handle the account type.
+ * The addon needs to be installed before the account can be created
+ * in the backend.
+ * You can choose one, if there are several addons in the list.
+ * (Optional)
+ *
+ * Array of:
+ * {
+ * id: "owl@example.com" {string},
+ *
+ * // already localized string
+ * name: "Owl" {string},
+ *
+ * // already localized string
+ * description: "A third party addon that allows you to connect to Exchange servers" {string}
+ *
+ * // Minimal version of the addon. Needed in case the addon is already installed,
+ * // to verify that the installed version is sufficient.
+ * // The XPI URL below must satisfy this.
+ * // Must satisfy <https://developer.mozilla.org/en-US/docs/Mozilla/Toolkit_version_format>
+ * minVersion: "0.2" {string}
+ *
+ * xpiURL: "https://live.thunderbird.net/autoconfig/owl.xpi" {URL},
+ * websiteURL: "https://www.beonex.com/owl/" {URL},
+ * icon32: "https://www.beonex.com/owl/owl-32x32.png" {URL},
+ *
+ * useType : {
+ * // Type shown as radio button to user in the config result.
+ * // Users won't understand OWA vs. EWS vs. EAS etc., so this is an abstraction
+ * // from the end user perspective.
+ * generalType: "exchange" {string},
+ *
+ * // Protocol
+ * // Independent of the addon
+ * protocolType: "owa" {string},
+ *
+ * // Account type in the Thunderbird backend.
+ * // What nsIMsgAccount.type will be set to when creating the account.
+ * // This is specific to the addon.
+ * addonAccountType: "owl-owa" {string},
+ * }
+ * }
+ */
+ addons: null,
+
+ /**
+ * Returns a deep copy of this object,
+ * i.e. modifying the copy will not affect the original object.
+ */
+ copy() {
+ // Workaround: deepCopy() fails to preserve base obj (instanceof)
+ let result = new AccountConfig();
+ for (let prop in this) {
+ result[prop] = lazy.AccountCreationUtils.deepCopy(this[prop]);
+ }
+
+ return result;
+ },
+
+ isComplete() {
+ return (
+ !!this.incoming.hostname &&
+ !!this.incoming.port &&
+ this.incoming.socketType != -1 &&
+ !!this.incoming.auth &&
+ !!this.incoming.username &&
+ (!!this.outgoing.existingServerKey ||
+ this.outgoing.useGlobalPreferredServer ||
+ (!!this.outgoing.hostname &&
+ !!this.outgoing.port &&
+ this.outgoing.socketType != -1 &&
+ !!this.outgoing.auth &&
+ !!this.outgoing.username))
+ );
+ },
+
+ toString() {
+ function sslToString(socketType) {
+ switch (socketType) {
+ case 0:
+ return "plain";
+ case 2:
+ return "STARTTLS";
+ case 3:
+ return "SSL";
+ default:
+ return "invalid";
+ }
+ }
+
+ function authToString(authMethod) {
+ switch (authMethod) {
+ case 0:
+ return "undefined";
+ case 1:
+ return "none";
+ case 2:
+ return "old plain";
+ case 3:
+ return "plain";
+ case 4:
+ return "encrypted";
+ case 5:
+ return "Kerberos";
+ case 6:
+ return "NTLM";
+ case 7:
+ return "external/SSL";
+ case 8:
+ return "any secure";
+ case 10:
+ return "OAuth2";
+ default:
+ return "invalid";
+ }
+ }
+
+ function passwordToString(password) {
+ return password ? "set" : "not set";
+ }
+
+ function configToString(config) {
+ return (
+ config.type +
+ ", " +
+ config.hostname +
+ ":" +
+ config.port +
+ ", " +
+ sslToString(config.socketType) +
+ ", auth: " +
+ authToString(config.auth) +
+ ", username: " +
+ (config.username || "(undefined)") +
+ ", password: " +
+ passwordToString(config.password)
+ );
+ }
+
+ let result = "Incoming: " + configToString(this.incoming) + "\nOutgoing: ";
+ if (
+ this.outgoing.useGlobalPreferredServer ||
+ this.incoming.useGlobalPreferredServer
+ ) {
+ result += "Use global server";
+ } else if (this.outgoing.existingServerKey) {
+ result += "Use existing server " + this.outgoing.existingServerKey;
+ } else {
+ result += configToString(this.outgoing);
+ }
+ for (let config of this.incomingAlternatives) {
+ result += "\nIncoming alt: " + configToString(config);
+ }
+ for (let config of this.outgoingAlternatives) {
+ result += "\nOutgoing alt: " + configToString(config);
+ }
+ return result;
+ },
+
+ /**
+ * Sort the config alternatives such that exchange is the last of the
+ * alternatives.
+ */
+ preferStandardProtocols() {
+ let alternatives = this.incomingAlternatives;
+ // Add default incoming as one alternative.
+ alternatives.unshift(this.incoming);
+ alternatives.sort((a, b) => {
+ if (a.type == "exchange") {
+ return 1;
+ }
+ if (b.type == "exchange") {
+ return -1;
+ }
+ return 0;
+ });
+ this.incomingAlternatives = alternatives;
+ this.incoming = alternatives.shift();
+ },
+};
+
+// enum consts
+
+// .source
+AccountConfig.kSourceUser = "user"; // user manually entered the config
+AccountConfig.kSourceXML = "xml"; // config from XML from ISP or Mozilla DB
+AccountConfig.kSourceGuess = "guess"; // guessConfig()
+AccountConfig.kSourceExchange = "exchange"; // from Microsoft Exchange AutoDiscover
+
+/**
+ * Some fields on the account config accept placeholders (when coming from XML).
+ *
+ * These are the predefined ones
+ * %EMAILADDRESS% (full email address of the user, usually entered by user)
+ * %EMAILLOCALPART% (email address, part before @)
+ * %EMAILDOMAIN% (email address, part after @)
+ * %REALNAME%
+ * as well as those defined in account.inputFields.*.varname, with % added
+ * before and after.
+ *
+ * These must replaced with real values, supplied by the user or app,
+ * before the account is created. This is done here. You call this function once
+ * you have all the data - gathered the standard vars mentioned above as well as
+ * all listed in account.inputFields, and pass them in here. This function will
+ * insert them in the fields, returning a fully filled-out account ready to be
+ * created.
+ *
+ * @param account {AccountConfig}
+ * The account data to be modified. It may or may not contain placeholders.
+ * After this function, it should not contain placeholders anymore.
+ * This object will be modified in-place.
+ *
+ * @param emailfull {String}
+ * Full email address of this account, e.g. "joe@example.com".
+ * Empty of incomplete email addresses will/may be rejected.
+ *
+ * @param realname {String}
+ * Real name of user, as will appear in From of outgoing messages
+ *
+ * @param password {String}
+ * The password for the incoming server and (if necessary) the outgoing server
+ */
+AccountConfig.replaceVariables = function (
+ account,
+ realname,
+ emailfull,
+ password
+) {
+ lazy.Sanitizer.nonemptystring(emailfull);
+ let emailsplit = emailfull.split("@");
+ lazy.AccountCreationUtils.assert(
+ emailsplit.length == 2,
+ "email address not in expected format: must contain exactly one @"
+ );
+ let emaillocal = lazy.Sanitizer.nonemptystring(emailsplit[0]);
+ let emaildomain = lazy.Sanitizer.hostname(emailsplit[1]);
+ lazy.Sanitizer.label(realname);
+ lazy.Sanitizer.nonemptystring(realname);
+
+ let otherVariables = {};
+ otherVariables.EMAILADDRESS = emailfull;
+ otherVariables.EMAILLOCALPART = emaillocal;
+ otherVariables.EMAILDOMAIN = emaildomain;
+ otherVariables.REALNAME = realname;
+
+ if (password) {
+ account.incoming.password = password;
+ account.outgoing.password = password; // set member only if auth required?
+ }
+ account.incoming.username = _replaceVariable(
+ account.incoming.username,
+ otherVariables
+ );
+ account.outgoing.username = _replaceVariable(
+ account.outgoing.username,
+ otherVariables
+ );
+ account.incoming.hostname = _replaceVariable(
+ account.incoming.hostname,
+ otherVariables
+ );
+ if (account.outgoing.hostname) {
+ // will be null if user picked existing server.
+ account.outgoing.hostname = _replaceVariable(
+ account.outgoing.hostname,
+ otherVariables
+ );
+ }
+ account.identity.realname = _replaceVariable(
+ account.identity.realname,
+ otherVariables
+ );
+ account.identity.emailAddress = _replaceVariable(
+ account.identity.emailAddress,
+ otherVariables
+ );
+ account.displayName = _replaceVariable(account.displayName, otherVariables);
+};
+
+function _replaceVariable(variable, values) {
+ let str = variable;
+ if (typeof str != "string") {
+ return str;
+ }
+
+ for (let varname in values) {
+ str = str.replace("%" + varname + "%", values[varname]);
+ }
+
+ return str;
+}