Merge pull request #2435
Request post AUTH capabilities when not provided with them
This commit is contained in:
commit
da3bf8d679
2 changed files with 230 additions and 117 deletions
|
@ -124,9 +124,11 @@ class ImapConnection {
|
|||
|
||||
upgradeToTlsIfNecessary();
|
||||
|
||||
authenticate();
|
||||
List<ImapResponse> responses = authenticate();
|
||||
authSuccess = true;
|
||||
|
||||
extractOrRequestCapabilities(responses);
|
||||
|
||||
enableCompressionIfRequested();
|
||||
|
||||
retrievePathPrefixIfNecessary();
|
||||
|
@ -242,26 +244,35 @@ class ImapConnection {
|
|||
|
||||
private void readInitialResponse() throws IOException {
|
||||
ImapResponse initialResponse = responseParser.readResponse();
|
||||
|
||||
if (K9MailLib.isDebug() && DEBUG_PROTOCOL_IMAP) {
|
||||
Timber.v("%s <<< %s", getLogId(), initialResponse);
|
||||
}
|
||||
|
||||
extractCapabilities(Collections.singletonList(initialResponse));
|
||||
}
|
||||
|
||||
private List<ImapResponse> extractCapabilities(List<ImapResponse> responses) {
|
||||
CapabilityResponse capabilityResponse = CapabilityResponse.parse(responses);
|
||||
|
||||
if (capabilityResponse != null) {
|
||||
Set<String> receivedCapabilities = capabilityResponse.getCapabilities();
|
||||
|
||||
if (K9MailLib.isDebug()) {
|
||||
Timber.d("Saving %s capabilities for %s", receivedCapabilities, getLogId());
|
||||
}
|
||||
|
||||
capabilities = receivedCapabilities;
|
||||
}
|
||||
return responses;
|
||||
}
|
||||
|
||||
private List<ImapResponse> extractOrRequestCapabilities(List<ImapResponse> responses)
|
||||
throws IOException, MessagingException {
|
||||
CapabilityResponse capabilityResponse = CapabilityResponse.parse(responses);
|
||||
if (capabilityResponse != null) {
|
||||
Set<String> receivedCapabilities = capabilityResponse.getCapabilities();
|
||||
Timber.d("Saving %s capabilities for %s", receivedCapabilities, getLogId());
|
||||
capabilities = receivedCapabilities;
|
||||
} else {
|
||||
Timber.i("Did not get capabilities in post-auth banner, requesting CAPABILITY for %s", getLogId());
|
||||
requestCapabilities();
|
||||
}
|
||||
|
||||
return responses;
|
||||
}
|
||||
|
@ -270,11 +281,9 @@ class ImapConnection {
|
|||
if (!capabilities.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (K9MailLib.isDebug()) {
|
||||
Timber.i("Did not get capabilities in banner, requesting CAPABILITY for %s", getLogId());
|
||||
}
|
||||
|
||||
requestCapabilities();
|
||||
}
|
||||
|
||||
|
@ -325,45 +334,40 @@ class ImapConnection {
|
|||
requestCapabilities();
|
||||
}
|
||||
|
||||
@SuppressWarnings("EnumSwitchStatementWhichMissesCases")
|
||||
private void authenticate() throws MessagingException, IOException {
|
||||
private List<ImapResponse> authenticate() throws MessagingException, IOException {
|
||||
switch (settings.getAuthType()) {
|
||||
case XOAUTH2:
|
||||
if (oauthTokenProvider == null) {
|
||||
throw new MessagingException("No OAuthToken Provider available.");
|
||||
} else if (hasCapability(Capabilities.AUTH_XOAUTH2) && hasCapability(Capabilities.SASL_IR)) {
|
||||
authXoauth2withSASLIR();
|
||||
return authXoauth2withSASLIR();
|
||||
} else {
|
||||
throw new MessagingException("Server doesn't support SASL XOAUTH2.");
|
||||
}
|
||||
break;
|
||||
case CRAM_MD5: {
|
||||
if (hasCapability(Capabilities.AUTH_CRAM_MD5)) {
|
||||
authCramMD5();
|
||||
return authCramMD5();
|
||||
} else {
|
||||
throw new MessagingException("Server doesn't support encrypted passwords using CRAM-MD5.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PLAIN: {
|
||||
if (hasCapability(Capabilities.AUTH_PLAIN)) {
|
||||
saslAuthPlainWithLoginFallback();
|
||||
return saslAuthPlainWithLoginFallback();
|
||||
} else if (!hasCapability(Capabilities.LOGINDISABLED)) {
|
||||
login();
|
||||
return login();
|
||||
} else {
|
||||
throw new MessagingException("Server doesn't support unencrypted passwords using AUTH=PLAIN " +
|
||||
"and LOGIN is disabled.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EXTERNAL: {
|
||||
if (hasCapability(Capabilities.AUTH_EXTERNAL)) {
|
||||
saslAuthExternal();
|
||||
return saslAuthExternal();
|
||||
} else {
|
||||
// Provide notification to user of a problem authenticating using client certificates
|
||||
throw new CertificateValidationException(CertificateValidationException.Reason.MissingCapability);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new MessagingException("Unhandled authentication method found in the server settings (bug).");
|
||||
|
@ -371,28 +375,28 @@ class ImapConnection {
|
|||
}
|
||||
}
|
||||
|
||||
private void authXoauth2withSASLIR() throws IOException, MessagingException {
|
||||
private List<ImapResponse> authXoauth2withSASLIR() throws IOException, MessagingException {
|
||||
retryXoauth2WithNewToken = true;
|
||||
try {
|
||||
attemptXOAuth2();
|
||||
return attemptXOAuth2();
|
||||
} catch (NegativeImapResponseException e) {
|
||||
//TODO: Check response code so we don't needlessly invalidate the token.
|
||||
oauthTokenProvider.invalidateToken(settings.getUsername());
|
||||
|
||||
if (!retryXoauth2WithNewToken) {
|
||||
handlePermanentXoauth2Failure(e);
|
||||
throw handlePermanentXoauth2Failure(e);
|
||||
} else {
|
||||
handleTemporaryXoauth2Failure(e);
|
||||
return handleTemporaryXoauth2Failure(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handlePermanentXoauth2Failure(NegativeImapResponseException e) throws AuthenticationFailedException {
|
||||
private AuthenticationFailedException handlePermanentXoauth2Failure(NegativeImapResponseException e) {
|
||||
Timber.v(e, "Permanent failure during XOAUTH2");
|
||||
throw new AuthenticationFailedException(e.getMessage(), e);
|
||||
return new AuthenticationFailedException(e.getMessage(), e);
|
||||
}
|
||||
|
||||
private void handleTemporaryXoauth2Failure(NegativeImapResponseException e) throws IOException, MessagingException {
|
||||
private List<ImapResponse> handleTemporaryXoauth2Failure(NegativeImapResponseException e) throws IOException, MessagingException {
|
||||
//We got a response indicating a retry might suceed after token refresh
|
||||
//We could avoid this if we had a reasonable chance of knowing
|
||||
//if a token was invalid before use (e.g. due to expiry). But we don't
|
||||
|
@ -400,30 +404,28 @@ class ImapConnection {
|
|||
|
||||
Timber.v(e, "Temporary failure - retrying with new token");
|
||||
try {
|
||||
attemptXOAuth2();
|
||||
return attemptXOAuth2();
|
||||
} catch (NegativeImapResponseException e2) {
|
||||
//Okay, we failed on a new token.
|
||||
//Invalidate the token anyway but assume it's permanent.
|
||||
Timber.v(e, "Authentication exception for new token, permanent error assumed");
|
||||
oauthTokenProvider.invalidateToken(settings.getUsername());
|
||||
handlePermanentXoauth2Failure(e2);
|
||||
throw handlePermanentXoauth2Failure(e2);
|
||||
}
|
||||
}
|
||||
|
||||
private void attemptXOAuth2() throws MessagingException, IOException {
|
||||
private List<ImapResponse> attemptXOAuth2() throws MessagingException, IOException {
|
||||
String token = oauthTokenProvider.getToken(settings.getUsername(), OAuth2TokenProvider.OAUTH2_TIMEOUT);
|
||||
String authString = Authentication.computeXoauth(settings.getUsername(), token);
|
||||
String tag = sendSaslIrCommand(Commands.AUTHENTICATE_XOAUTH2, authString, true);
|
||||
|
||||
List<ImapResponse> responses = responseParser.readStatusResponse(tag, Commands.AUTHENTICATE_XOAUTH2, getLogId(),
|
||||
return responseParser.readStatusResponse(tag, Commands.AUTHENTICATE_XOAUTH2, getLogId(),
|
||||
new UntaggedHandler() {
|
||||
@Override
|
||||
public void handleAsyncUntaggedResponse(ImapResponse response) throws IOException {
|
||||
handleXOAuthUntaggedResponse(response);
|
||||
}
|
||||
});
|
||||
|
||||
extractCapabilities(responses);
|
||||
}
|
||||
|
||||
private void handleXOAuthUntaggedResponse(ImapResponse response) throws IOException {
|
||||
|
@ -437,7 +439,7 @@ class ImapConnection {
|
|||
}
|
||||
}
|
||||
|
||||
private void authCramMD5() throws MessagingException, IOException {
|
||||
private List<ImapResponse> authCramMD5() throws MessagingException, IOException {
|
||||
String command = Commands.AUTHENTICATE_CRAM_MD5;
|
||||
String tag = sendCommand(command, false);
|
||||
|
||||
|
@ -455,25 +457,25 @@ class ImapConnection {
|
|||
outputStream.flush();
|
||||
|
||||
try {
|
||||
extractCapabilities(responseParser.readStatusResponse(tag, command, getLogId(), null));
|
||||
return responseParser.readStatusResponse(tag, command, getLogId(), null);
|
||||
} catch (NegativeImapResponseException e) {
|
||||
handleAuthenticationFailure(e);
|
||||
throw handleAuthenticationFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void saslAuthPlainWithLoginFallback() throws IOException, MessagingException {
|
||||
private List<ImapResponse> saslAuthPlainWithLoginFallback() throws IOException, MessagingException {
|
||||
try {
|
||||
saslAuthPlain();
|
||||
return saslAuthPlain();
|
||||
} catch (AuthenticationFailedException e) {
|
||||
if (!isConnected()) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
login();
|
||||
return login();
|
||||
}
|
||||
}
|
||||
|
||||
private void saslAuthPlain() throws IOException, MessagingException {
|
||||
private List<ImapResponse> saslAuthPlain() throws IOException, MessagingException {
|
||||
String command = Commands.AUTHENTICATE_PLAIN;
|
||||
String tag = sendCommand(command, false);
|
||||
|
||||
|
@ -488,13 +490,13 @@ class ImapConnection {
|
|||
outputStream.flush();
|
||||
|
||||
try {
|
||||
extractCapabilities(responseParser.readStatusResponse(tag, command, getLogId(), null));
|
||||
return responseParser.readStatusResponse(tag, command, getLogId(), null);
|
||||
} catch (NegativeImapResponseException e) {
|
||||
handleAuthenticationFailure(e);
|
||||
throw handleAuthenticationFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void login() throws IOException, MessagingException {
|
||||
private List<ImapResponse> login() throws IOException, MessagingException {
|
||||
/*
|
||||
* Use quoted strings which permit spaces and quotes. (Using IMAP
|
||||
* string literals would be better, but some servers are broken
|
||||
|
@ -509,16 +511,16 @@ class ImapConnection {
|
|||
|
||||
try {
|
||||
String command = String.format(Commands.LOGIN + " \"%s\" \"%s\"", username, password);
|
||||
extractCapabilities(executeSimpleCommand(command, true));
|
||||
return executeSimpleCommand(command, true);
|
||||
} catch (NegativeImapResponseException e) {
|
||||
handleAuthenticationFailure(e);
|
||||
throw handleAuthenticationFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void saslAuthExternal() throws IOException, MessagingException {
|
||||
private List<ImapResponse> saslAuthExternal() throws IOException, MessagingException {
|
||||
try {
|
||||
String command = Commands.AUTHENTICATE_EXTERNAL + " " + Base64.encode(settings.getUsername());
|
||||
extractCapabilities(executeSimpleCommand(command, false));
|
||||
return executeSimpleCommand(command, false);
|
||||
} catch (NegativeImapResponseException e) {
|
||||
/*
|
||||
* Provide notification to the user of a problem authenticating
|
||||
|
@ -531,7 +533,7 @@ class ImapConnection {
|
|||
}
|
||||
}
|
||||
|
||||
private void handleAuthenticationFailure(NegativeImapResponseException e) throws MessagingException {
|
||||
private MessagingException handleAuthenticationFailure(NegativeImapResponseException e) {
|
||||
ImapResponse lastResponse = e.getLastResponse();
|
||||
String responseCode = ResponseCodeExtractor.getResponseCode(lastResponse);
|
||||
|
||||
|
@ -541,10 +543,10 @@ class ImapConnection {
|
|||
close();
|
||||
}
|
||||
|
||||
throw new AuthenticationFailedException(e.getMessage());
|
||||
return new AuthenticationFailedException(e.getMessage());
|
||||
} else {
|
||||
close();
|
||||
throw e;
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,9 @@ package com.fsck.k9.mail.store.imap;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.List;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.ConnectivityManager;
|
||||
|
||||
import com.fsck.k9.mail.AuthType;
|
||||
|
@ -23,7 +25,6 @@ import okio.ByteString;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InOrder;
|
||||
import org.robolectric.shadows.ShadowLog;
|
||||
|
||||
import static org.hamcrest.core.StringContains.containsString;
|
||||
|
@ -32,9 +33,7 @@ import static org.junit.Assert.assertFalse;
|
|||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
||||
@RunWith(K9LibRobolectricTestRunner.class)
|
||||
|
@ -45,10 +44,12 @@ public class ImapConnectionTest {
|
|||
private static final String PASSWORD = "123456";
|
||||
private static final int SOCKET_CONNECT_TIMEOUT = 10000;
|
||||
private static final int SOCKET_READ_TIMEOUT = 10000;
|
||||
private static final String XOAUTH_TOKEN = "token";
|
||||
private static final String XOAUTH_ANOTHER_TOKEN = "token2";
|
||||
private static final String XOAUTH_STRING = ByteString.encodeUtf8(
|
||||
"user=" + USERNAME + "\001auth=Bearer token\001\001").base64();
|
||||
"user=" + USERNAME + "\001auth=Bearer " + XOAUTH_TOKEN + "\001\001").base64();
|
||||
private static final String XOAUTH_STRING_RETRY = ByteString.encodeUtf8(
|
||||
"user=" + USERNAME + "\001auth=Bearer token2\001\001").base64();
|
||||
"user=" + USERNAME + "\001auth=Bearer " + XOAUTH_ANOTHER_TOKEN + "\001\001").base64();
|
||||
|
||||
|
||||
private TrustedSocketFactory socketFactory;
|
||||
|
@ -60,7 +61,7 @@ public class ImapConnectionTest {
|
|||
@Before
|
||||
public void setUp() throws Exception {
|
||||
connectivityManager = mock(ConnectivityManager.class);
|
||||
oAuth2TokenProvider = mock(OAuth2TokenProvider.class);
|
||||
oAuth2TokenProvider = createOAuth2TokenProvider();
|
||||
socketFactory = new TestTrustedSocketFactory();
|
||||
|
||||
settings = new SimpleImapSettings();
|
||||
|
@ -75,7 +76,28 @@ public class ImapConnectionTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void open_withCapabilitiesInInitialResponse_shouldNotIssueCapabilitiesCommand() throws Exception {
|
||||
public void open_withNoCapabilitiesInInitialResponse_shouldIssuePreAuthCapabilitiesCommand() throws Exception {
|
||||
settings.setAuthType(AuthType.PLAIN);
|
||||
MockImapServer server = new MockImapServer();
|
||||
server.output("* OK example.org server");
|
||||
server.expect("1 CAPABILITY");
|
||||
server.output("* CAPABILITY IMAP4 IMAP4REV1 AUTH=PLAIN");
|
||||
server.output("1 OK CAPABILITY Completed");
|
||||
server.expect("2 AUTHENTICATE PLAIN");
|
||||
server.output("+");
|
||||
server.expect(ByteString.encodeUtf8("\000" + USERNAME + "\000" + PASSWORD).base64());
|
||||
server.output("2 OK Success");
|
||||
postAuthenticationDialogRequestingCapabilities(server);
|
||||
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
|
||||
|
||||
imapConnection.open();
|
||||
|
||||
server.verifyConnectionStillOpen();
|
||||
server.verifyInteractionCompleted();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void open_withCapabilitiesInInitialResponse_shouldNotIssuePreAuthCapabilitiesCommand() throws Exception {
|
||||
settings.setAuthType(AuthType.PLAIN);
|
||||
MockImapServer server = new MockImapServer();
|
||||
server.output("* OK [CAPABILITY IMAP4 IMAP4REV1 AUTH=PLAIN]");
|
||||
|
@ -83,9 +105,7 @@ public class ImapConnectionTest {
|
|||
server.output("+");
|
||||
server.expect(ByteString.encodeUtf8("\000" + USERNAME + "\000" + PASSWORD).base64());
|
||||
server.output("1 OK Success");
|
||||
server.expect("2 LIST \"\" \"\"");
|
||||
server.output("* LIST () \"/\" foo/bar");
|
||||
server.output("2 OK");
|
||||
postAuthenticationDialogRequestingCapabilities(server, 2);
|
||||
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
|
||||
|
||||
imapConnection.open();
|
||||
|
@ -103,7 +123,7 @@ public class ImapConnectionTest {
|
|||
server.output("+");
|
||||
server.expect(ByteString.encodeUtf8("\000" + USERNAME + "\000" + PASSWORD).base64());
|
||||
server.output("2 OK Success");
|
||||
simplePostAuthenticationDialog(server);
|
||||
postAuthenticationDialogRequestingCapabilities(server);
|
||||
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
|
||||
|
||||
imapConnection.open();
|
||||
|
@ -119,7 +139,7 @@ public class ImapConnectionTest {
|
|||
preAuthenticationDialog(server);
|
||||
server.expect("2 LOGIN \"" + USERNAME + "\" \"" + PASSWORD + "\"");
|
||||
server.output("2 OK LOGIN completed");
|
||||
simplePostAuthenticationDialog(server);
|
||||
postAuthenticationDialogRequestingCapabilities(server);
|
||||
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
|
||||
imapConnection.open();
|
||||
imapConnection.close();
|
||||
|
@ -163,7 +183,7 @@ public class ImapConnectionTest {
|
|||
server.output("2 NO Login Failure");
|
||||
server.expect("3 LOGIN \"" + USERNAME + "\" \"" + PASSWORD + "\"");
|
||||
server.output("3 OK LOGIN completed");
|
||||
simplePostAuthenticationDialog(server, "4");
|
||||
postAuthenticationDialogRequestingCapabilities(server, 4);
|
||||
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
|
||||
|
||||
imapConnection.open();
|
||||
|
@ -254,7 +274,7 @@ public class ImapConnectionTest {
|
|||
preAuthenticationDialog(server);
|
||||
server.expect("2 LOGIN \"" + USERNAME + "\" \"" + PASSWORD + "\"");
|
||||
server.output("2 OK LOGIN completed");
|
||||
simplePostAuthenticationDialog(server);
|
||||
postAuthenticationDialogRequestingCapabilities(server);
|
||||
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
|
||||
|
||||
imapConnection.open();
|
||||
|
@ -272,7 +292,7 @@ public class ImapConnectionTest {
|
|||
server.output("+ " + ByteString.encodeUtf8("<0000.000000000@example.org>").base64());
|
||||
server.expect("dXNlciA2ZjdiOTcyYjk5YTI4NDk4OTRhN2YyMmE3MGRhZDg0OQ==");
|
||||
server.output("2 OK Success");
|
||||
simplePostAuthenticationDialog(server);
|
||||
postAuthenticationDialogRequestingCapabilities(server);
|
||||
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
|
||||
|
||||
imapConnection.open();
|
||||
|
@ -325,12 +345,11 @@ public class ImapConnectionTest {
|
|||
@Test
|
||||
public void open_authXoauthWithSaslIr() throws Exception {
|
||||
settings.setAuthType(AuthType.XOAUTH2);
|
||||
when(oAuth2TokenProvider.getToken("user", OAuth2TokenProvider.OAUTH2_TIMEOUT)).thenReturn("token");
|
||||
MockImapServer server = new MockImapServer();
|
||||
preAuthenticationDialog(server, "SASL-IR AUTH=XOAUTH AUTH=XOAUTH2");
|
||||
server.expect("2 AUTHENTICATE XOAUTH2 " + XOAUTH_STRING);
|
||||
server.output("2 OK Success");
|
||||
simplePostAuthenticationDialog(server);
|
||||
postAuthenticationDialogRequestingCapabilities(server);
|
||||
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
|
||||
|
||||
imapConnection.open();
|
||||
|
@ -342,8 +361,6 @@ public class ImapConnectionTest {
|
|||
@Test
|
||||
public void open_authXoauthWithSaslIrThrowsExeptionOn401Response() throws Exception {
|
||||
settings.setAuthType(AuthType.XOAUTH2);
|
||||
when(oAuth2TokenProvider.getToken("user", OAuth2TokenProvider.OAUTH2_TIMEOUT))
|
||||
.thenReturn("token").thenReturn("token2");
|
||||
MockImapServer server = new MockImapServer();
|
||||
preAuthenticationDialog(server, "SASL-IR AUTH=XOAUTH AUTH=XOAUTH2");
|
||||
server.expect("2 AUTHENTICATE XOAUTH2 " + XOAUTH_STRING);
|
||||
|
@ -356,8 +373,7 @@ public class ImapConnectionTest {
|
|||
imapConnection.open();
|
||||
fail();
|
||||
} catch (AuthenticationFailedException e) {
|
||||
assertEquals(
|
||||
"Command: AUTHENTICATE XOAUTH2; response: #2# [NO, SASL authentication failed]",
|
||||
assertEquals("Command: AUTHENTICATE XOAUTH2; response: #2# [NO, SASL authentication failed]",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
@ -365,8 +381,6 @@ public class ImapConnectionTest {
|
|||
@Test
|
||||
public void open_authXoauthWithSaslIrInvalidatesAndRetriesNewTokenOn400Response() throws Exception {
|
||||
settings.setAuthType(AuthType.XOAUTH2);
|
||||
when(oAuth2TokenProvider.getToken("user", OAuth2TokenProvider.OAUTH2_TIMEOUT))
|
||||
.thenReturn("token").thenReturn("token2");
|
||||
MockImapServer server = new MockImapServer();
|
||||
preAuthenticationDialog(server, "SASL-IR AUTH=XOAUTH AUTH=XOAUTH2");
|
||||
server.expect("2 AUTHENTICATE XOAUTH2 " + XOAUTH_STRING);
|
||||
|
@ -375,24 +389,18 @@ public class ImapConnectionTest {
|
|||
server.output("2 NO SASL authentication failed");
|
||||
server.expect("3 AUTHENTICATE XOAUTH2 " + XOAUTH_STRING_RETRY);
|
||||
server.output("3 OK Success");
|
||||
simplePostAuthenticationDialog(server, "4");
|
||||
postAuthenticationDialogRequestingCapabilities(server, 4);
|
||||
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
|
||||
|
||||
imapConnection.open();
|
||||
|
||||
server.verifyConnectionStillOpen();
|
||||
server.verifyInteractionCompleted();
|
||||
InOrder inOrder = inOrder(oAuth2TokenProvider);
|
||||
inOrder.verify(oAuth2TokenProvider).getToken("user", OAuth2TokenProvider.OAUTH2_TIMEOUT);
|
||||
inOrder.verify(oAuth2TokenProvider).invalidateToken("user");
|
||||
inOrder.verify(oAuth2TokenProvider).getToken("user", OAuth2TokenProvider.OAUTH2_TIMEOUT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void open_authXoauthWithSaslIrInvalidatesAndRetriesNewTokenOnInvalidJsonResponse() throws Exception {
|
||||
settings.setAuthType(AuthType.XOAUTH2);
|
||||
when(oAuth2TokenProvider.getToken("user", OAuth2TokenProvider.OAUTH2_TIMEOUT))
|
||||
.thenReturn("token").thenReturn("token2");
|
||||
MockImapServer server = new MockImapServer();
|
||||
preAuthenticationDialog(server, "SASL-IR AUTH=XOAUTH AUTH=XOAUTH2");
|
||||
server.expect("2 AUTHENTICATE XOAUTH2 " + XOAUTH_STRING);
|
||||
|
@ -401,24 +409,19 @@ public class ImapConnectionTest {
|
|||
server.output("2 NO SASL authentication failed");
|
||||
server.expect("3 AUTHENTICATE XOAUTH2 " + XOAUTH_STRING_RETRY);
|
||||
server.output("3 OK Success");
|
||||
simplePostAuthenticationDialog(server, "4");
|
||||
requestCapabilities(server, 4);
|
||||
simplePostAuthenticationDialog(server, 5);
|
||||
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
|
||||
|
||||
imapConnection.open();
|
||||
|
||||
server.verifyConnectionStillOpen();
|
||||
server.verifyInteractionCompleted();
|
||||
InOrder inOrder = inOrder(oAuth2TokenProvider);
|
||||
inOrder.verify(oAuth2TokenProvider).getToken("user", OAuth2TokenProvider.OAUTH2_TIMEOUT);
|
||||
inOrder.verify(oAuth2TokenProvider).invalidateToken("user");
|
||||
inOrder.verify(oAuth2TokenProvider).getToken("user", OAuth2TokenProvider.OAUTH2_TIMEOUT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void open_authXoauthWithSaslIrInvalidatesAndRetriesNewTokenOnMissingStatusJsonResponse() throws Exception {
|
||||
settings.setAuthType(AuthType.XOAUTH2);
|
||||
when(oAuth2TokenProvider.getToken("user", OAuth2TokenProvider.OAUTH2_TIMEOUT))
|
||||
.thenReturn("token").thenReturn("token2");
|
||||
MockImapServer server = new MockImapServer();
|
||||
preAuthenticationDialog(server, "SASL-IR AUTH=XOAUTH AUTH=XOAUTH2");
|
||||
server.expect("2 AUTHENTICATE XOAUTH2 " + XOAUTH_STRING);
|
||||
|
@ -427,24 +430,19 @@ public class ImapConnectionTest {
|
|||
server.output("2 NO SASL authentication failed");
|
||||
server.expect("3 AUTHENTICATE XOAUTH2 " + XOAUTH_STRING_RETRY);
|
||||
server.output("3 OK Success");
|
||||
simplePostAuthenticationDialog(server, "4");
|
||||
requestCapabilities(server, 4);
|
||||
simplePostAuthenticationDialog(server, 5);
|
||||
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
|
||||
|
||||
imapConnection.open();
|
||||
|
||||
server.verifyConnectionStillOpen();
|
||||
server.verifyInteractionCompleted();
|
||||
InOrder inOrder = inOrder(oAuth2TokenProvider);
|
||||
inOrder.verify(oAuth2TokenProvider).getToken("user", OAuth2TokenProvider.OAUTH2_TIMEOUT);
|
||||
inOrder.verify(oAuth2TokenProvider).invalidateToken("user");
|
||||
inOrder.verify(oAuth2TokenProvider).getToken("user", OAuth2TokenProvider.OAUTH2_TIMEOUT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void open_authXoauthWithSaslIrWithOldTokenThrowsExceptionIfRetryFails() throws Exception {
|
||||
settings.setAuthType(AuthType.XOAUTH2);
|
||||
when(oAuth2TokenProvider.getToken("user", OAuth2TokenProvider.OAUTH2_TIMEOUT))
|
||||
.thenReturn("token").thenReturn("token2");
|
||||
MockImapServer server = new MockImapServer();
|
||||
preAuthenticationDialog(server, "SASL-IR AUTH=XOAUTH AUTH=XOAUTH2");
|
||||
server.expect("2 AUTHENTICATE XOAUTH2 " + XOAUTH_STRING);
|
||||
|
@ -455,15 +453,14 @@ public class ImapConnectionTest {
|
|||
server.output("+ 433ba3a3a");
|
||||
server.expect("");
|
||||
server.output("3 NO SASL authentication failed");
|
||||
simplePostAuthenticationDialog(server);
|
||||
postAuthenticationDialogRequestingCapabilities(server);
|
||||
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
|
||||
|
||||
try {
|
||||
imapConnection.open();
|
||||
fail();
|
||||
} catch (AuthenticationFailedException e) {
|
||||
assertEquals(
|
||||
"Command: AUTHENTICATE XOAUTH2; response: #3# [NO, SASL authentication failed]",
|
||||
assertEquals("Command: AUTHENTICATE XOAUTH2; response: #3# [NO, SASL authentication failed]",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
@ -471,15 +468,15 @@ public class ImapConnectionTest {
|
|||
@Test
|
||||
public void open_authXoauthWithSaslIrParsesCapabilities() throws Exception {
|
||||
settings.setAuthType(AuthType.XOAUTH2);
|
||||
when(oAuth2TokenProvider.getToken("user", OAuth2TokenProvider.OAUTH2_TIMEOUT))
|
||||
.thenReturn("token");
|
||||
MockImapServer server = new MockImapServer();
|
||||
preAuthenticationDialog(server, "SASL-IR AUTH=XOAUTH AUTH=XOAUTH2");
|
||||
server.expect("2 AUTHENTICATE XOAUTH2 " + XOAUTH_STRING);
|
||||
server.output("2 OK [CAPABILITY IMAP4REV1 IDLE XM-GM-EXT-1]");
|
||||
simplePostAuthenticationDialog(server);
|
||||
simplePostAuthenticationDialog(server, 3);
|
||||
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
|
||||
|
||||
imapConnection.open();
|
||||
|
||||
server.verifyConnectionStillOpen();
|
||||
server.verifyInteractionCompleted();
|
||||
assertTrue(imapConnection.hasCapability("XM-GM-EXT-1"));
|
||||
|
@ -492,7 +489,7 @@ public class ImapConnectionTest {
|
|||
preAuthenticationDialog(server, "AUTH=EXTERNAL");
|
||||
server.expect("2 AUTHENTICATE EXTERNAL " + ByteString.encodeUtf8(USERNAME).base64());
|
||||
server.output("2 OK Success");
|
||||
simplePostAuthenticationDialog(server);
|
||||
postAuthenticationDialogRequestingCapabilities(server);
|
||||
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
|
||||
|
||||
imapConnection.open();
|
||||
|
@ -540,6 +537,69 @@ public class ImapConnectionTest {
|
|||
server.verifyInteractionCompleted();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void open_withNoPostAuthCapabilityResponse_shouldIssueCapabilityCommand() throws Exception {
|
||||
settings.setAuthType(AuthType.PLAIN);
|
||||
MockImapServer server = new MockImapServer();
|
||||
preAuthenticationDialog(server, "AUTH=PLAIN");
|
||||
server.expect("2 AUTHENTICATE PLAIN");
|
||||
server.output("+");
|
||||
server.expect(ByteString.encodeUtf8("\000" + USERNAME + "\000" + PASSWORD).base64());
|
||||
server.output("2 OK Success");
|
||||
server.expect("3 CAPABILITY");
|
||||
server.output("* CAPABILITY IDLE");
|
||||
server.output("3 OK CAPABILITY Completed");
|
||||
simplePostAuthenticationDialog(server, 4);
|
||||
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
|
||||
|
||||
imapConnection.open();
|
||||
|
||||
server.verifyConnectionStillOpen();
|
||||
server.verifyInteractionCompleted();
|
||||
assertTrue(imapConnection.isIdleCapable());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void open_withUntaggedPostAuthCapabilityResponse_shouldNotIssueCapabilityCommand() throws Exception {
|
||||
settings.setAuthType(AuthType.PLAIN);
|
||||
MockImapServer server = new MockImapServer();
|
||||
preAuthenticationDialog(server, "AUTH=PLAIN");
|
||||
server.expect("2 AUTHENTICATE PLAIN");
|
||||
server.output("+");
|
||||
server.expect(ByteString.encodeUtf8("\000" + USERNAME + "\000" + PASSWORD).base64());
|
||||
server.output("* CAPABILITY IMAP4rev1 UNSELECT IDLE QUOTA ID XLIST CHILDREN X-GM-EXT-1 UIDPLUS " +
|
||||
"ENABLE MOVE CONDSTORE ESEARCH UTF8=ACCEPT LIST-EXTENDED LIST-STATUS LITERAL- SPECIAL-USE " +
|
||||
"APPENDLIMIT=35651584");
|
||||
server.output("2 OK");
|
||||
simplePostAuthenticationDialog(server, 3);
|
||||
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
|
||||
|
||||
imapConnection.open();
|
||||
|
||||
server.verifyConnectionStillOpen();
|
||||
server.verifyInteractionCompleted();
|
||||
assertTrue(imapConnection.isIdleCapable());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void open_withPostAuthCapabilityResponse_shouldNotIssueCapabilityCommand() throws Exception {
|
||||
settings.setAuthType(AuthType.PLAIN);
|
||||
MockImapServer server = new MockImapServer();
|
||||
preAuthenticationDialog(server, "AUTH=PLAIN");
|
||||
server.expect("2 AUTHENTICATE PLAIN");
|
||||
server.output("+");
|
||||
server.expect(ByteString.encodeUtf8("\000" + USERNAME + "\000" + PASSWORD).base64());
|
||||
server.output("2 OK [CAPABILITY IDLE]");
|
||||
simplePostAuthenticationDialog(server, 3);
|
||||
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
|
||||
|
||||
imapConnection.open();
|
||||
|
||||
server.verifyConnectionStillOpen();
|
||||
server.verifyInteractionCompleted();
|
||||
assertTrue(imapConnection.isIdleCapable());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void open_withNamespaceCapability_shouldIssueNamespaceCommand() throws Exception {
|
||||
MockImapServer server = new MockImapServer();
|
||||
|
@ -597,10 +657,10 @@ public class ImapConnectionTest {
|
|||
server.output("2 OK [CAPABILITY IMAP4REV1 NAMESPACE]");
|
||||
server.startTls();
|
||||
server.expect("3 CAPABILITY");
|
||||
server.output("* CAPABILITY IMAP4 IMAP4REV1 NAMESPACE");
|
||||
server.output("* CAPABILITY IMAP4 IMAP4REV1");
|
||||
server.output("3 OK");
|
||||
server.expect("4 LOGIN \"" + USERNAME + "\" \"" + PASSWORD + "\"");
|
||||
server.output("4 OK LOGIN completed");
|
||||
server.output("4 OK [CAPABILITY NAMESPACE] LOGIN completed");
|
||||
server.expect("5 NAMESPACE");
|
||||
server.output("* NAMESPACE ((\"\" \"/\")) NIL NIL");
|
||||
server.output("5 OK command completed");
|
||||
|
@ -660,7 +720,7 @@ public class ImapConnectionTest {
|
|||
server.expect("3 COMPRESS DEFLATE");
|
||||
server.output("3 OK");
|
||||
server.enableCompression();
|
||||
simplePostAuthenticationDialog(server, "4");
|
||||
simplePostAuthenticationDialog(server, 4);
|
||||
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
|
||||
|
||||
imapConnection.open();
|
||||
|
@ -677,7 +737,7 @@ public class ImapConnectionTest {
|
|||
simplePreAuthAndLoginDialog(server, "COMPRESS=DEFLATE");
|
||||
server.expect("3 COMPRESS DEFLATE");
|
||||
server.output("3 NO");
|
||||
simplePostAuthenticationDialog(server, "4");
|
||||
simplePostAuthenticationDialog(server, 4);
|
||||
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
|
||||
|
||||
imapConnection.open();
|
||||
|
@ -709,7 +769,6 @@ public class ImapConnectionTest {
|
|||
@Test
|
||||
public void open_withIoExceptionDuringListCommand_shouldThrow() throws Exception {
|
||||
settings.setAuthType(AuthType.PLAIN);
|
||||
settings.setUseCompression(true);
|
||||
MockImapServer server = new MockImapServer();
|
||||
simplePreAuthAndLoginDialog(server, "");
|
||||
server.expect("3 LIST \"\" \"\"");
|
||||
|
@ -729,7 +788,6 @@ public class ImapConnectionTest {
|
|||
@Test
|
||||
public void open_withNegativeResponseToListCommand() throws Exception {
|
||||
settings.setAuthType(AuthType.PLAIN);
|
||||
settings.setUseCompression(true);
|
||||
MockImapServer server = new MockImapServer();
|
||||
simplePreAuthAndLoginDialog(server, "");
|
||||
server.expect("3 LIST \"\" \"\"");
|
||||
|
@ -843,6 +901,7 @@ public class ImapConnectionTest {
|
|||
server.verifyInteractionCompleted();
|
||||
}
|
||||
|
||||
|
||||
private ImapConnection createImapConnection(ImapSettings settings, TrustedSocketFactory socketFactory,
|
||||
ConnectivityManager connectivityManager, OAuth2TokenProvider oAuth2TokenProvider) {
|
||||
return new ImapConnection(settings, socketFactory, connectivityManager, oAuth2TokenProvider,
|
||||
|
@ -860,8 +919,9 @@ public class ImapConnectionTest {
|
|||
return simpleOpenWithCapabilities(server, "");
|
||||
}
|
||||
|
||||
private ImapConnection simpleOpenWithCapabilities(MockImapServer server, String capabilities) throws Exception {
|
||||
simpleOpenDialog(server, capabilities);
|
||||
private ImapConnection simpleOpenWithCapabilities(MockImapServer server, String postAuthCapabilities)
|
||||
throws Exception {
|
||||
simpleOpenDialog(server, postAuthCapabilities);
|
||||
|
||||
ImapConnection imapConnection = startServerAndCreateImapConnection(server);
|
||||
imapConnection.open();
|
||||
|
@ -880,27 +940,78 @@ public class ImapConnectionTest {
|
|||
server.output("1 OK CAPABILITY");
|
||||
}
|
||||
|
||||
private void simplePostAuthenticationDialog(MockImapServer server) {
|
||||
simplePostAuthenticationDialog(server, "3");
|
||||
private void postAuthenticationDialogRequestingCapabilities(MockImapServer server) {
|
||||
postAuthenticationDialogRequestingCapabilities(server, 3);
|
||||
}
|
||||
|
||||
private void simplePostAuthenticationDialog(MockImapServer server, String tag) {
|
||||
private void postAuthenticationDialogRequestingCapabilities(MockImapServer server, int tag) {
|
||||
requestCapabilities(server, tag);
|
||||
simplePostAuthenticationDialog(server, tag + 1);
|
||||
}
|
||||
|
||||
private void requestCapabilities(MockImapServer server, int tag) {
|
||||
server.expect(tag + " CAPABILITY");
|
||||
server.output("* CAPABILITY IMAP4 IMAP4REV1 ");
|
||||
server.output(tag + " OK CAPABILITY");
|
||||
}
|
||||
|
||||
private void simplePostAuthenticationDialog(MockImapServer server, int tag) {
|
||||
server.expect(tag + " LIST \"\" \"\"");
|
||||
server.output("* LIST () \"/\" foo/bar");
|
||||
server.output(tag + " OK");
|
||||
}
|
||||
|
||||
private void simpleOpenDialog(MockImapServer server, String capabilities) {
|
||||
simplePreAuthAndLoginDialog(server, capabilities);
|
||||
simplePostAuthenticationDialog(server);
|
||||
private void simpleOpenDialog(MockImapServer server, String postAuthCapabilities) {
|
||||
simplePreAuthAndLoginDialog(server, postAuthCapabilities);
|
||||
simplePostAuthenticationDialog(server, 3);
|
||||
}
|
||||
|
||||
private void simplePreAuthAndLoginDialog(MockImapServer server, String capabilities) {
|
||||
private void simplePreAuthAndLoginDialog(MockImapServer server, String postAuthCapabilities) {
|
||||
settings.setAuthType(AuthType.PLAIN);
|
||||
|
||||
preAuthenticationDialog(server, capabilities);
|
||||
preAuthenticationDialog(server);
|
||||
|
||||
server.expect("2 LOGIN \"" + USERNAME + "\" \"" + PASSWORD + "\"");
|
||||
server.output("2 OK LOGIN completed");
|
||||
server.output("2 OK [CAPABILITY " + postAuthCapabilities + "] LOGIN completed");
|
||||
}
|
||||
|
||||
private OAuth2TokenProvider createOAuth2TokenProvider() throws AuthenticationFailedException {
|
||||
return new OAuth2TokenProvider() {
|
||||
private int invalidationCount = 0;
|
||||
|
||||
@Override
|
||||
public String getToken(String username, long timeoutMillis) throws AuthenticationFailedException {
|
||||
assertEquals(USERNAME, username);
|
||||
assertEquals(OAUTH2_TIMEOUT, timeoutMillis);
|
||||
|
||||
switch (invalidationCount) {
|
||||
case 0: {
|
||||
return XOAUTH_TOKEN;
|
||||
}
|
||||
case 1: {
|
||||
return XOAUTH_ANOTHER_TOKEN;
|
||||
}
|
||||
default: {
|
||||
throw new AssertionError("Ran out of auth tokens. invalidateToken() called too often?");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateToken(String username) {
|
||||
assertEquals(USERNAME, username);
|
||||
invalidationCount++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAccounts() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authorizeApi(String username, Activity activity, OAuth2TokenProviderAuthCallback callback) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue