From 3778b3ed7ee505c00fa7cd3b1af37cbe54de244a Mon Sep 17 00:00:00 2001 From: Andrew Stadler Date: Fri, 14 May 2010 00:05:28 -0700 Subject: [PATCH] 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 --- src/com/android/exchange/EasSyncService.java | 63 ++++++++++++++------ 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/src/com/android/exchange/EasSyncService.java b/src/com/android/exchange/EasSyncService.java index a01bdc290..aa0d5b4a5 100644 --- a/src/com/android/exchange/EasSyncService.java +++ b/src/com/android/exchange/EasSyncService.java @@ -467,15 +467,17 @@ public class EasSyncService extends AbstractSyncService { /** * 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 @@ public class EasSyncService extends AbstractSyncService { 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 @@ public class EasSyncService extends AbstractSyncService { // 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 @@ public class EasSyncService extends AbstractSyncService { // 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 @@ public class EasSyncService extends AbstractSyncService { 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 @@ public class EasSyncService extends AbstractSyncService { 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 @@ public class EasSyncService extends AbstractSyncService { 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 @@ public class EasSyncService extends AbstractSyncService { } } + /** + * 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";