Try autodiscover with bare name if we get 401 with address
* Some autodiscover servers appear to require the bare user name
for authentication rather than the user's email address. This
is apparently common for complex organizations maintaining a
group of email domains
* If we get a 401 when trying to connect to an autodiscover server
using the email address, we try again using just the bare name
Bug: 2682833
Change-Id: Ia07ca336e189069d4f3539e2245b3d53c82e3324
diff --git a/src/com/android/exchange/EasSyncService.java b/src/com/android/exchange/EasSyncService.java
index a01bdc2..aa0d5b4 100644
--- a/src/com/android/exchange/EasSyncService.java
+++ b/src/com/android/exchange/EasSyncService.java
@@ -467,15 +467,17 @@
/**
* Send the POST command to the autodiscover server, handling a redirect, if necessary, and
- * return the HttpResponse
+ * return the HttpResponse. If we get a 401 (unauthorized) error and we're using the
+ * full email address, try the bare user name instead (e.g. foo instead of foo@bar.com)
*
* @param client the HttpClient to be used for the request
* @param post the HttpPost we're going to send
+ * @param canRetry whether we can retry using the bare name on an authentication failure (401)
* @return an HttpResponse from the original or redirect server
* @throws IOException on any IOException within the HttpClient code
* @throws MessagingException
*/
- private HttpResponse postAutodiscover(HttpClient client, HttpPost post)
+ private HttpResponse postAutodiscover(HttpClient client, HttpPost post, boolean canRetry)
throws IOException, MessagingException {
userLog("Posting autodiscover to: " + post.getURI());
HttpResponse resp = executePostWithTimeout(client, post, COMMAND_TIMEOUT);
@@ -487,10 +489,21 @@
userLog("Posting autodiscover to redirect: " + post.getURI());
return executePostWithTimeout(client, post, COMMAND_TIMEOUT);
}
+ // 401 (Unauthorized) is for true auth errors when used in Autodiscover
} else if (code == HttpStatus.SC_UNAUTHORIZED) {
- // 401 (Unauthorized) is for true auth errors when used in Autodiscover
- // 403 (and others) we'll just punt on
+ if (canRetry && mUserName.contains("@")) {
+ // Try again using the bare user name
+ int atSignIndex = mUserName.indexOf('@');
+ mUserName = mUserName.substring(0, atSignIndex);
+ cacheAuthAndCmdString();
+ userLog("401 received; trying username: ", mUserName);
+ // Recreate the basic authentication string and reset the header
+ post.removeHeaders("Authorization");
+ post.setHeader("Authorization", mAuthString);
+ return postAutodiscover(client, post, false);
+ }
throw new MessagingException(MessagingException.AUTHENTICATION_FAILED);
+ // 403 (and others) we'll just punt on
} else if (code != HttpStatus.SC_OK) {
// We'll try the next address if this doesn't work
userLog("Code: " + code + ", throwing IOException");
@@ -533,8 +546,8 @@
// Initialize the user name and password
mUserName = userName;
mPassword = password;
- // Make sure the authentication string is created (mAuthString)
- makeUriString("foo", null);
+ // Make sure the authentication string is recreated and cached
+ cacheAuthAndCmdString();
// Split out the domain name
int amp = userName.indexOf('@');
@@ -546,6 +559,9 @@
// There are up to four attempts here; the two URLs that we're supposed to try per the
// specification, and up to one redirect for each (handled in postAutodiscover)
+ // Note: The expectation is that, of these four attempts, only a single server will
+ // actually be identified as the autodiscover server. For the identified server,
+ // we may also try a 2nd connection with a different format (bare name).
// Try the domain first and see if we can get a response
HttpPost post = new HttpPost("https://" + domain + AUTO_DISCOVER_PAGE);
@@ -555,14 +571,14 @@
HttpClient client = getHttpClient(COMMAND_TIMEOUT);
HttpResponse resp;
try {
- resp = postAutodiscover(client, post);
+ resp = postAutodiscover(client, post, true /*canRetry*/);
} catch (IOException e1) {
userLog("IOException in autodiscover; trying alternate address");
// We catch the IOException here because we have an alternate address to try
post.setURI(URI.create("https://autodiscover." + domain + AUTO_DISCOVER_PAGE));
// If we fail here, we're out of options, so we let the outer try catch the
// IOException and return null
- resp = postAutodiscover(client, post);
+ resp = postAutodiscover(client, post, true /*canRetry*/);
}
// Get the "final" code; if it's not 200, just return null
@@ -588,9 +604,12 @@
hostAuth = new HostAuth();
parseAutodiscover(parser, hostAuth);
// On success, we'll have a server address and login
- if (hostAuth.mAddress != null && hostAuth.mLogin != null) {
+ if (hostAuth.mAddress != null) {
// Fill in the rest of the HostAuth
- hostAuth.mPassword = password;
+ // We use the user name and password that were successful during
+ // the autodiscover process
+ hostAuth.mLogin = mUserName;
+ hostAuth.mPassword = mPassword;
hostAuth.mPort = 443;
hostAuth.mProtocol = "eas";
hostAuth.mFlags =
@@ -699,8 +718,7 @@
String name = parser.getName();
if (name.equals("EMailAddress")) {
String addr = parser.nextText();
- hostAuth.mLogin = addr;
- userLog("Autodiscover, login: " + addr);
+ userLog("Autodiscover, email: " + addr);
} else if (name.equals("DisplayName")) {
String dn = parser.nextText();
userLog("Autodiscover, user: " + dn);
@@ -1041,15 +1059,24 @@
}
}
+ /**
+ * Using mUserName and mPassword, create and cache mAuthString and mCacheString, which are used
+ * in all HttpPost commands. This should be called if these strings are null, or if mUserName
+ * and/or mPassword are changed
+ */
@SuppressWarnings("deprecation")
+ private void cacheAuthAndCmdString() {
+ String safeUserName = URLEncoder.encode(mUserName);
+ String cs = mUserName + ':' + mPassword;
+ mAuthString = "Basic " + Base64.encodeToString(cs.getBytes(), Base64.NO_WRAP);
+ mCmdString = "&User=" + safeUserName + "&DeviceId=" + mDeviceId +
+ "&DeviceType=" + mDeviceType;
+ }
+
private String makeUriString(String cmd, String extra) throws IOException {
// Cache the authentication string and the command string
- String safeUserName = URLEncoder.encode(mUserName);
- if (mAuthString == null) {
- String cs = mUserName + ':' + mPassword;
- mAuthString = "Basic " + Base64.encodeToString(cs.getBytes(), Base64.NO_WRAP);
- mCmdString = "&User=" + safeUserName + "&DeviceId=" + mDeviceId + "&DeviceType="
- + mDeviceType;
+ if (mAuthString == null || mCmdString == null) {
+ cacheAuthAndCmdString();
}
String us = (mSsl ? (mTrustSsl ? "httpts" : "https") : "http") + "://" + mHostAddress +
"/Microsoft-Server-ActiveSync";