Bug 1571772 - Support Exchange as an alternative account type for ISPDB. r=BenB a=jorgk
authorNeil Rashbrook <neil@parkwaycc.co.uk>
Wed, 14 Aug 2019 11:57:24 +0100
changeset 36174 ea35b787e55b464c5617f4f412a57dd78d801660
parent 36173 5477fcc25b929b4d74bc4cb60ed24cccd030bb0e
child 36175 1e92fa2aab3f6481a1e2f28a4cce11370328a2a6
push id392
push userclokep@gmail.com
push dateMon, 02 Sep 2019 20:17:19 +0000
reviewersBenB, jorgk
bugs1571772
Bug 1571772 - Support Exchange as an alternative account type for ISPDB. r=BenB a=jorgk
mail/components/accountcreation/content/accountConfig.js
mail/components/accountcreation/content/emailWizard.js
mail/components/accountcreation/content/exchangeAutoDiscover.js
mail/components/accountcreation/content/readFromXML.js
mail/components/accountcreation/content/util.js
--- a/mail/components/accountcreation/content/accountConfig.js
+++ b/mail/components/accountcreation/content/accountConfig.js
@@ -112,16 +112,18 @@ AccountConfig.prototype = {
       // When user hits delete, delete from local store and from server
       deleteOnServerWhenLocalDelete: true,
       downloadOnBiff: true,
 
       // 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",
--- a/mail/components/accountcreation/content/emailWizard.js
+++ b/mail/components/accountcreation/content/emailWizard.js
@@ -426,16 +426,19 @@ EmailConfigWizard.prototype = {
     this.switchToMode("start");
   },
 
   getConcreteConfig() {
     var result = this._currentConfig.copy();
     replaceVariables(result, this._realname, this._email, this._password);
     result.rememberPassword = e("remember_password").checked &&
                               !!this._password;
+    if (result.incoming.addonAccountType) {
+      result.incoming.type = result.incoming.addonAccountType;
+    }
     return result;
   },
 
   /*
    * This checks if the email address is at least possibly valid, meaning it
    * has an '@' before the last char.
    */
   validateEmailMinimally(emailAddr) {
@@ -699,17 +702,27 @@ EmailConfigWizard.prototype = {
         "BUG: Arg 'config' needs to be an AccountConfig object");
 
     this._haveValidConfigForDomain = this._email.split("@")[1];
 
     if (!this._realname || !this._email) {
       return;
     }
 
-    this.displayConfigResult(config);
+    e("status_area").setAttribute("status", "loading");
+    config.addons = [];
+    let successCallback = () => {
+      this._abortable = null;
+      e("status_area").setAttribute("status", "result");
+      this.displayConfigResult(config);
+    };
+    this._abortable = getAddonsList(config, successCallback, e => {
+      successCallback();
+      this.showErrorMsg(e);
+    });
   },
 
   /**
    * [Stop] button click handler.
    * This allows the user to abort any longer operation, esp. network activity.
    * We currently have 3 such cases here:
    * 1. findConfig(), i.e. fetch config from DB, guessConfig etc.
    * 2. onHalfManualTest(), i.e. the [Retest] button in manual config.
@@ -841,30 +854,28 @@ EmailConfigWizard.prototype = {
    */
   displayConfigResult(config) {
     assert(config instanceof AccountConfig);
     this._currentConfig = config;
     var configFilledIn = this.getConcreteConfig();
 
     // IMAP / POP3 server type radio buttons
     let alternatives = config.incomingAlternatives.filter(alt =>
-        (alt.type == "imap" || alt.type == "pop3" || alt.type == "exchange") &&
-        alt.type != config.incoming.type
-      );
-    let alternative = alternatives[0];
-    if (alternative) {
+        alt.type == "imap" || alt.type == "pop3" || alt.type == "exchange");
+    alternatives.unshift(config.incoming);
+    alternatives = alternatives.unique(alt => alt.type);
+    if (alternatives.length > 1) {
       _show("result_servertype");
       _hide("result_select_imap");
       _hide("result_select_pop3");
       _hide("result_select_exchange");
-      _show("result_select_" + alternative.type);
-      _show("result_select_" + config.incoming.type);
-      e("result_select_" + alternative.type).configIncoming = alternative;
-      e("result_select_" + config.incoming.type).configIncoming =
-          config.incoming;
+      for (let alt of alternatives) {
+        _show("result_select_" + alt.type);
+        e("result_select_" + alt.type).configIncoming = alt;
+      }
       e("result_servertype").value = config.incoming.type;
     } else {
       _hide("result_servertype");
     }
 
     if (config.incoming.type == "exchange") {
       _hide("result_hostnames");
       _show("result_exchange");
@@ -879,23 +890,20 @@ EmailConfigWizard.prototype = {
 
       (async () => {
         for (let addon of config.addons) {
           let installer = new AddonInstaller(addon);
           addon.isInstalled = await installer.isInstalled();
         }
         let installedAddon = config.addons.find(addon => addon.isInstalled);
         if (installedAddon) {
+          config.incoming.addonAccountType = installedAddon.useType.addonAccountType;
           _hide("result_addon_intro");
           _hide("result_addon_install");
           _enable("create_button");
-          this.onCreate = () => { // TODO
-            this._currentConfig.incoming.type = installedAddon.useType.addonAccountType;
-            this.validateAndFinish();
-          };
         } else {
           _hide("status_area");
           _show("result_addon_intro");
           var msg = gStringsBundle.getString("addon-intro");
           if (!config.incomingAlternatives.find(alt => (alt.type == "imap" || alt.type == "pop3"))) {
             msg = gStringsBundle.getString("no-open-protocols") + " " + msg;
           }
           setText("result_addon_intro", msg);
--- a/mail/components/accountcreation/content/exchangeAutoDiscover.js
+++ b/mail/components/accountcreation/content/exchangeAutoDiscover.js
@@ -75,19 +75,17 @@ function fetchConfigFromExchange(domain,
   };
   let call;
   let fetch;
   let fetch3;
 
   let successive = new SuccessiveAbortable();
   let priority = new PriorityOrderAbortable(
     function(xml, call) { // success
-      readAutoDiscoverResponse(xml, successive, username, password, function(config) {
-        successive.current = getAddonsList(config, successCallback, errorCallback);
-      }, errorCallback);
+      readAutoDiscoverResponse(xml, successive, username, password, successCallback, errorCallback);
     },
     errorCallback); // all failed
 
   call = priority.addCall();
   fetch = new FetchHTTP(url1, callArgs,
     call.successCallback(), call.errorCallback());
   fetch.start();
   call.setAbortable(fetch);
@@ -299,31 +297,36 @@ function readAutoDiscoverXML(autoDiscove
 
 /**
  * Ask server which addons can handle this config.
  * @param {AccountConfig} config
  * @param {Function(config {AccountConfig})} successCallback
  * @returns {Abortable}
  */
 function getAddonsList(config, successCallback, errorCallback) {
+  let incoming = [config.incoming, ...config.incomingAlternatives].find(alt => alt.type == "exchange");
+  if (!incoming) {
+    successCallback();
+    return new Abortable();
+  }
   let url = Services.prefs.getCharPref("mailnews.auto_config.addons_url");
   if (!url) {
     errorCallback(new Exception("no URL for addons list configured"));
     return new Abortable();
   }
   let fetch = new FetchHTTP(url, { allowCache: true, timeout: 10000 }, function(json) {
     let addons = readAddonsJSON(json);
     addons = addons.filter(addon => {
       // Find types matching the current config.
       // Pick the first in the list as the preferred one and
       // tell the UI to use that one.
       addon.useType = addon.supportedTypes.find(type =>
-        config.incoming.owaURL && type.protocolType == "owa" ||
-        config.incoming.ewsURL && type.protocolType == "ews" ||
-        config.incoming.easURL && type.protocolType == "eas");
+        incoming.owaURL && type.protocolType == "owa" ||
+        incoming.ewsURL && type.protocolType == "ews" ||
+        incoming.easURL && type.protocolType == "eas");
       return !!addon.useType;
     });
     if (addons.length == 0) {
       errorCallback(new Exception("Config found, but no addons known to handle the config"));
       return;
     }
     config.addons = addons;
     successCallback(config);
--- a/mail/components/accountcreation/content/readFromXML.js
+++ b/mail/components/accountcreation/content/readFromXML.js
@@ -60,17 +60,17 @@ function readFromXML(clientConfigXML) {
     throw exception ? exception : "need proper <domain> in XML";
   exception = null;
 
   // incoming server
   for (let iX of array_or_undef(xml.$incomingServer)) { // input (XML)
     let iO = d.createNewIncoming(); // output (object)
     try {
       // throws if not supported
-      iO.type = sanitize.enum(iX["@type"], ["pop3", "imap", "nntp"]);
+      iO.type = sanitize.enum(iX["@type"], ["pop3", "imap", "nntp", "exchange"]);
       iO.hostname = sanitize.hostname(iX.hostname);
       iO.port = sanitize.integerRange(iX.port, kMinPort, kMaxPort);
       // We need a username even for Kerberos, need it even internally.
       iO.username = sanitize.string(iX.username); // may be a %VARIABLE%
 
       if ("password" in iX) {
         d.rememberPassword = true;
         iO.password = sanitize.string(iX.password);
@@ -101,16 +101,37 @@ function readFromXML(clientConfigXML) {
                 "OAuth2": Ci.nsMsgAuthMethod.OAuth2 });
           break; // take first that we support
         } catch (e) { exception = e; }
       }
       if (!iO.auth)
         throw exception ? exception : "need proper <authentication> in XML";
       exception = null;
 
+      if (iO.type == "exchange") {
+        try {
+          if ("owaURL" in iX) {
+            iO.owaURL = sanitize.url(iX.owaURL);
+          }
+        } catch (e) { logException(e); }
+        try {
+          if ("ewsURL" in iX) {
+            iO.ewsURL = sanitize.url(iX.ewsURL);
+          }
+        } catch (e) { logException(e); }
+        try {
+          if ("easURL" in iX) {
+            iO.easURL = sanitize.url(iX.easURL);
+          }
+        } catch (e) { logException(e); }
+        d.oauthSettings = {
+          issuer: iO.hostname,
+          scope: iO.owaURL || iO.ewsURL || iO.easURL,
+        };
+      }
       // defaults are in accountConfig.js
       if (iO.type == "pop3" && "pop3" in iX) {
         try {
           if ("leaveMessagesOnServer" in iX.pop3)
             iO.leaveMessagesOnServer =
                 sanitize.boolean(iX.pop3.leaveMessagesOnServer);
           if ("daysToLeaveMessagesOnServer" in iX.pop3)
             iO.daysToLeaveMessagesOnServer =
--- a/mail/components/accountcreation/content/util.js
+++ b/mail/components/accountcreation/content/util.js
@@ -96,16 +96,31 @@ function readURLasUTF8(uri) {
  */
 function splitLines(content) {
   content = content.replace("\r\n", "\n");
   content = content.replace("\r", "\n");
   return content.split("\n");
 }
 
 /**
+ * Returns only one element for each result of testFunc.
+ * First element for each result wins.
+ * @param testFunc {Function(arrayElement)}
+ * @returns new array {Array}
+ */
+Array.prototype.unique = function(testFunc) {
+  return this.reduce((found, nextEl) => {
+    if (!found.some(prevEl => testFunc(prevEl) == testFunc(nextEl))) {
+      found.push(nextEl);
+    }
+    return found;
+  }, []);
+};
+
+/**
  * @param bundleURI {String}   chrome URL to properties file
  * @return nsIStringBundle
  */
 function getStringBundle(bundleURI) {
   try {
     return Services.strings.createBundle(bundleURI);
   } catch (e) {
     throw new Exception("Failed to get stringbundle URI <" + bundleURI +