Bug 1592258 - [autoconfig] In emailWizard, default to Exchange protocol for Office365 with enforced MFA or IMAP disabled. r=aleca,jorgk,mkmelin a=mkmelin
authorBen Bucksch <ben.bucksch@beonex.com>
Thu, 02 Jan 2020 12:26:03 +0200
changeset 37657 a45840067d4431441599c06fe7bf787da298dab4
parent 37656 b4316a22736be13136f583faa6c83a563136550e
child 37658 6a0479402139ab109836d93f61bc87e227e2680d
push id396
push userclokep@gmail.com
push dateMon, 06 Jan 2020 23:11:57 +0000
reviewersaleca, jorgk, mkmelin, mkmelin
bugs1592258
Bug 1592258 - [autoconfig] In emailWizard, default to Exchange protocol for Office365 with enforced MFA or IMAP disabled. r=aleca,jorgk,mkmelin a=mkmelin
mail/components/accountcreation/content/emailWizard.js
--- a/mail/components/accountcreation/content/emailWizard.js
+++ b/mail/components/accountcreation/content/emailWizard.js
@@ -784,25 +784,109 @@ EmailConfigWizard.prototype = {
       return;
     }
 
     e("status-area").setAttribute("status", "loading");
     config.addons = [];
     let successCallback = () => {
       this._abortable = null;
       e("status-area").setAttribute("status", "result");
-      this.displayConfigResult(config);
+      // For Office365, do a pre-verification of whether IMAP works. If it fails
+      // due to wrong password, make the user aware. If it fails due to other
+      // reasons (mainly MFA enforced), make Exchange the default.
+      // We do this since the account may have MFA enabled and that can't yet be
+      // used with IMAP/POP.
+      if (
+        config.incoming.hostname == "outlook.office365.com" &&
+        (config.incoming.type == "imap" || config.incoming.type == "pop3") &&
+        config.incomingAlternatives.some(i => i.type == "exchange")
+      ) {
+        this.startSpinner("looking_up_settings_exchange");
+        this._currentConfig = config;
+        let configFilledIn = this.getConcreteConfig();
+        this.checkOffice365Creds(
+          configFilledIn,
+          () => {
+            // Password valid: check whether IMAP works.
+            verifyConfig(
+              configFilledIn,
+              false,
+              this._parentMsgWindow,
+              testedConfig => {
+                // IMAP worked
+                this.stopSpinner("found_settings_exchange");
+                this.displayConfigResult(testedConfig);
+              },
+              ex => {
+                // IMAP failed: make Exchange the default.
+                config.incomingAlternatives.unshift(config.incoming);
+                config.incoming = config.incomingAlternatives.find(
+                  alt => alt.type == "exchange"
+                );
+                config.incomingAlternatives = config.incomingAlternatives.filter(
+                  alt => alt != config.incoming
+                );
+                this.stopSpinner("found_settings_exchange");
+                this.displayConfigResult(config);
+              }
+            );
+          },
+          () => {
+            // Invalid password: show the error and let user correct it.
+            this.onStartOver();
+            _show("status-area");
+            this.showErrorStatus("user_pass_invalid");
+          }
+        );
+      } else {
+        this.displayConfigResult(config);
+      }
     };
     this._abortable = getAddonsList(config, successCallback, e => {
       successCallback();
       this.showErrorMsg(e);
     });
   },
 
   /**
+   * Office365 AutoDiscover gives us specific error codes for
+   * invalid password and MFA enforced, so we can differentiate that.
+   * @param {Function} successCallback - function to be called in case the
+   *   credentials were not explicitedly invalid.
+   * @param {Function} invalidPassword - function to be called in case the
+   *   credentials were explicitely invalid.
+   */
+  checkOffice365Creds(configFilledIn, successCallback, invalidPassword) {
+    let fetch = new FetchHTTP(
+      "https://autodiscover-s.outlook.com/Autodiscover/Autodiscover.xml",
+      {
+        username: configFilledIn.incoming.username,
+        password: configFilledIn.incoming.password,
+        allowAuthPrompt: false,
+        allowCache: false,
+        headers: {
+          Cookie: "",
+        },
+        timeout: 10000,
+      },
+      successCallback,
+      ex => {
+        let err = fetch._request.getResponseHeader("X-AutoDiscovery-Error");
+        gEmailWizardLogger.info("O365 X-AutoDiscovery-Error: " + err);
+        if (err && err.includes(":InvalidCreds:")) {
+          invalidPassword();
+        } else {
+          successCallback();
+        }
+      }
+    );
+    fetch.start();
+  },
+
+  /**
    * [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.
    * 3. verifyConfig() - We can't stop this yet, so irrelevant here currently.
    * Given that these need slightly different actions, this function will be set
    * to a function (i.e. overwritten) by whoever enables the stop button.