Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MissingCapabilityException #7381

Merged
merged 1 commit into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.fsck.k9.mail

class MissingCapabilityException(
val capabilityName: String,
) : MessagingException("Missing capability: $capabilityName", true)
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.fsck.k9.mail.CertificateValidationException
import com.fsck.k9.mail.ConnectionSecurity
import com.fsck.k9.mail.K9MailLib
import com.fsck.k9.mail.MessagingException
import com.fsck.k9.mail.MissingCapabilityException
import com.fsck.k9.mail.NetworkTimeouts.SOCKET_CONNECT_TIMEOUT
import com.fsck.k9.mail.NetworkTimeouts.SOCKET_READ_TIMEOUT
import com.fsck.k9.mail.filter.Base64
Expand Down Expand Up @@ -260,14 +261,7 @@ internal class RealImapConnection(

private fun upgradeToTls() {
if (!hasCapability(Capabilities.STARTTLS)) {
/*
* This exception triggers a "Certificate error"
* notification that takes the user to the incoming
* server settings for review. This might be needed if
* the account was configured with an obsolete
* "STARTTLS (if available)" setting.
*/
throw CertificateValidationException("STARTTLS connection security not available")
throw MissingCapabilityException(Capabilities.STARTTLS)
}

startTls()
Expand Down Expand Up @@ -298,20 +292,20 @@ internal class RealImapConnection(
if (oauthTokenProvider == null) {
throw MessagingException("No OAuthToken Provider available.")
} else if (!hasCapability(Capabilities.SASL_IR)) {
throw MessagingException("SASL-IR capability is missing.")
throw MissingCapabilityException(Capabilities.SASL_IR)
} else if (hasCapability(Capabilities.AUTH_OAUTHBEARER)) {
authWithOAuthToken(OAuthMethod.OAUTHBEARER)
} else if (hasCapability(Capabilities.AUTH_XOAUTH2)) {
authWithOAuthToken(OAuthMethod.XOAUTH2)
} else {
throw MessagingException("Server doesn't support SASL OAUTHBEARER or XOAUTH2.")
throw MissingCapabilityException(Capabilities.AUTH_OAUTHBEARER)
}
}
AuthType.CRAM_MD5 -> {
if (hasCapability(Capabilities.AUTH_CRAM_MD5)) {
authCramMD5()
} else {
throw MessagingException("Server doesn't support encrypted passwords using CRAM-MD5.")
throw MissingCapabilityException(Capabilities.AUTH_CRAM_MD5)
}
}
AuthType.PLAIN -> {
Expand All @@ -320,17 +314,14 @@ internal class RealImapConnection(
} else if (!hasCapability(Capabilities.LOGINDISABLED)) {
login()
} else {
throw MessagingException(
"Server doesn't support unencrypted passwords using AUTH=PLAIN and LOGIN is disabled.",
)
throw MissingCapabilityException(Capabilities.AUTH_PLAIN)
}
}
AuthType.EXTERNAL -> {
if (hasCapability(Capabilities.AUTH_EXTERNAL)) {
saslAuthExternal()
} else {
// Provide notification to user of a problem authenticating using client certificates
throw CertificateValidationException(CertificateValidationException.Reason.MissingCapability)
throw MissingCapabilityException(Capabilities.AUTH_EXTERNAL)
}
}
else -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import com.fsck.k9.mail.AuthenticationFailedException
import com.fsck.k9.mail.CertificateValidationException
import com.fsck.k9.mail.ConnectionSecurity
import com.fsck.k9.mail.K9MailLib
import com.fsck.k9.mail.MessagingException
import com.fsck.k9.mail.MissingCapabilityException
import com.fsck.k9.mail.SystemOutLogger
import com.fsck.k9.mail.XOAuth2ChallengeParserTest
import com.fsck.k9.mail.helpers.TestTrustedSocketFactory
Expand Down Expand Up @@ -143,8 +143,8 @@ class RealImapConnectionTest {

assertFailure {
imapConnection.open()
}.isInstanceOf<MessagingException>()
.hasMessage("Server doesn't support unencrypted passwords using AUTH=PLAIN and LOGIN is disabled.")
}.isInstanceOf<MissingCapabilityException>()
.prop(MissingCapabilityException::capabilityName).isEqualTo("AUTH=PLAIN")

server.verifyConnectionClosed()
server.verifyInteractionCompleted()
Expand Down Expand Up @@ -302,8 +302,8 @@ class RealImapConnectionTest {

assertFailure {
imapConnection.open()
}.isInstanceOf<MessagingException>()
.hasMessage("Server doesn't support encrypted passwords using CRAM-MD5.")
}.isInstanceOf<MissingCapabilityException>()
.prop(MissingCapabilityException::capabilityName).isEqualTo("AUTH=CRAM-MD5")

server.verifyConnectionClosed()
server.verifyInteractionCompleted()
Expand Down Expand Up @@ -375,6 +375,38 @@ class RealImapConnectionTest {
server.verifyInteractionCompleted()
}

@Test
fun `open() AUTH OAUTHBEARER with SASL-IR capability missing`() {
val server = MockImapServer().apply {
preAuthenticationDialog(capabilities = "AUTH=OAUTHBEARER")
}
val imapConnection = startServerAndCreateImapConnection(server, authType = AuthType.XOAUTH2)

assertFailure {
imapConnection.open()
}.isInstanceOf<MissingCapabilityException>()
.prop(MissingCapabilityException::capabilityName).isEqualTo("SASL-IR")

server.verifyConnectionClosed()
server.verifyInteractionCompleted()
}

@Test
fun `open() AUTH OAUTHBEARER with AUTH=OAUTHBEARER capability missing`() {
val server = MockImapServer().apply {
preAuthenticationDialog(capabilities = "SASL-IR")
}
val imapConnection = startServerAndCreateImapConnection(server, authType = AuthType.XOAUTH2)

assertFailure {
imapConnection.open()
}.isInstanceOf<MissingCapabilityException>()
.prop(MissingCapabilityException::capabilityName).isEqualTo("AUTH=OAUTHBEARER")

server.verifyConnectionClosed()
server.verifyInteractionCompleted()
}

@Test
fun `open() AUTH XOAUTH2 throws exception on 401 response`() {
val server = MockImapServer().apply {
Expand Down Expand Up @@ -539,9 +571,8 @@ class RealImapConnectionTest {

assertFailure {
imapConnection.open()
}.isInstanceOf<CertificateValidationException>()
.prop(CertificateValidationException::getReason)
.isEqualTo(CertificateValidationException.Reason.MissingCapability)
}.isInstanceOf<MissingCapabilityException>()
.prop(MissingCapabilityException::capabilityName).isEqualTo("AUTH=EXTERNAL")

server.verifyConnectionClosed()
server.verifyInteractionCompleted()
Expand Down Expand Up @@ -686,11 +717,10 @@ class RealImapConnectionTest {
connectionSecurity = ConnectionSecurity.STARTTLS_REQUIRED,
)

// FIXME: CertificateValidationException seems wrong
assertFailure {
imapConnection.open()
}.isInstanceOf<CertificateValidationException>()
.hasMessage("STARTTLS connection security not available")
}.isInstanceOf<MissingCapabilityException>()
.prop(MissingCapabilityException::capabilityName).isEqualTo("STARTTLS")

server.verifyConnectionClosed()
server.verifyInteractionCompleted()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,26 @@
import com.fsck.k9.mail.ConnectionSecurity;
import com.fsck.k9.mail.K9MailLib;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.MissingCapabilityException;
import com.fsck.k9.mail.filter.Base64;
import com.fsck.k9.mail.filter.Hex;
import com.fsck.k9.mail.ssl.TrustedSocketFactory;
import javax.net.ssl.SSLException;

import static com.fsck.k9.mail.CertificateValidationException.Reason.MissingCapability;
import static com.fsck.k9.mail.K9MailLib.DEBUG_PROTOCOL_POP3;
import static com.fsck.k9.mail.NetworkTimeouts.SOCKET_CONNECT_TIMEOUT;
import static com.fsck.k9.mail.NetworkTimeouts.SOCKET_READ_TIMEOUT;
import static com.fsck.k9.mail.store.pop3.Pop3Commands.*;
import static com.fsck.k9.mail.store.pop3.Pop3Commands.AUTH_CRAM_MD5_CAPABILITY;
import static com.fsck.k9.mail.store.pop3.Pop3Commands.AUTH_EXTERNAL_CAPABILITY;
import static com.fsck.k9.mail.store.pop3.Pop3Commands.AUTH_PLAIN_CAPABILITY;
import static com.fsck.k9.mail.store.pop3.Pop3Commands.CAPA_COMMAND;
import static com.fsck.k9.mail.store.pop3.Pop3Commands.PASS_COMMAND;
import static com.fsck.k9.mail.store.pop3.Pop3Commands.SASL_CAPABILITY;
import static com.fsck.k9.mail.store.pop3.Pop3Commands.STLS_CAPABILITY;
import static com.fsck.k9.mail.store.pop3.Pop3Commands.STLS_COMMAND;
import static com.fsck.k9.mail.store.pop3.Pop3Commands.TOP_CAPABILITY;
import static com.fsck.k9.mail.store.pop3.Pop3Commands.UIDL_CAPABILITY;
import static com.fsck.k9.mail.store.pop3.Pop3Commands.USER_COMMAND;


class Pop3Connection {
Expand Down Expand Up @@ -159,8 +169,7 @@ private void performStartTlsUpgrade(TrustedSocketFactory trustedSocketFactory,
}
capabilities = getCapabilities();
} else {
throw new CertificateValidationException(
"STARTTLS connection security not available");
throw new MissingCapabilityException(STLS_CAPABILITY);
}

}
Expand Down Expand Up @@ -188,8 +197,7 @@ private void performAuthentication(AuthType authType, String serverGreeting)
if (capabilities.external) {
authExternal();
} else {
// Provide notification to user of a problem authenticating using client certificates
throw new CertificateValidationException(MissingCapability);
throw new MissingCapabilityException(SASL_CAPABILITY + " " + AUTH_EXTERNAL_CAPABILITY);
}
break;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import com.fsck.k9.mail.ConnectionSecurity.NONE
import com.fsck.k9.mail.ConnectionSecurity.SSL_TLS_REQUIRED
import com.fsck.k9.mail.ConnectionSecurity.STARTTLS_REQUIRED
import com.fsck.k9.mail.MessagingException
import com.fsck.k9.mail.MissingCapabilityException
import com.fsck.k9.mail.helpers.TestTrustedSocketFactory
import com.fsck.k9.mail.ssl.TrustedSocketFactory
import java.io.IOException
Expand Down Expand Up @@ -78,14 +79,17 @@ class Pop3ConnectionTest {
createAndOpenPop3Connection(settings, mockSocketFactory)
}

@Test(expected = CertificateValidationException::class)
fun `open() with STLS capability unavailable should throw CertificateValidationException`() {
@Test
fun `open() with STLS capability unavailable should throw`() {
val server = startServer {
setupServerWithAuthenticationMethods("PLAIN")
}
val settings = server.createSettings(connectionSecurity = STARTTLS_REQUIRED)

createAndOpenPop3Connection(settings)
assertFailure {
createAndOpenPop3Connection(settings)
}.isInstanceOf<MissingCapabilityException>()
.prop(MissingCapabilityException::capabilityName).isEqualTo("STLS")
}

@Test(expected = Pop3ErrorResponse::class)
Expand Down Expand Up @@ -300,9 +304,8 @@ class Pop3ConnectionTest {

assertFailure {
createAndOpenPop3Connection(settings)
}.isInstanceOf<CertificateValidationException>()
.prop(CertificateValidationException::getReason)
.isEqualTo(CertificateValidationException.Reason.MissingCapability)
}.isInstanceOf<MissingCapabilityException>()
.prop(MissingCapabilityException::capabilityName).isEqualTo("SASL EXTERNAL")

server.verifyConnectionStillOpen()
server.verifyInteractionCompleted()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.fsck.k9.mail.K9MailLib
import com.fsck.k9.mail.Message
import com.fsck.k9.mail.Message.RecipientType
import com.fsck.k9.mail.MessagingException
import com.fsck.k9.mail.MissingCapabilityException
import com.fsck.k9.mail.NetworkTimeouts.SOCKET_CONNECT_TIMEOUT
import com.fsck.k9.mail.NetworkTimeouts.SOCKET_READ_TIMEOUT
import com.fsck.k9.mail.ServerSettings
Expand Down Expand Up @@ -125,10 +126,7 @@ class SmtpTransport(
extensions = sendHello(helloName)
secureConnection = true
} else {
// This exception triggers a "Certificate error" notification that takes the user to the incoming
// server settings for review. This might be needed if the account was configured with an obsolete
// "STARTTLS (if available)" setting.
throw CertificateValidationException("STARTTLS connection security not available")
throw MissingCapabilityException("STARTTLS")
}
}

Expand Down Expand Up @@ -161,14 +159,14 @@ class SmtpTransport(
} else if (authLoginSupported) {
saslAuthLogin()
} else {
throw MessagingException("Authentication methods SASL PLAIN and LOGIN are unavailable.")
throw MissingCapabilityException("AUTH PLAIN")
}
}
AuthType.CRAM_MD5 -> {
if (authCramMD5Supported) {
saslAuthCramMD5()
} else {
throw MessagingException("Authentication method CRAM-MD5 is unavailable.")
throw MissingCapabilityException("AUTH CRAM-MD5")
}
}
AuthType.XOAUTH2 -> {
Expand All @@ -179,20 +177,14 @@ class SmtpTransport(
} else if (authXoauth2Supported) {
saslOAuth(OAuthMethod.XOAUTH2)
} else {
throw MessagingException("Server doesn't support SASL OAUTHBEARER or XOAUTH2.")
throw MissingCapabilityException("AUTH OAUTHBEARER")
}
}
AuthType.EXTERNAL -> {
if (authExternalSupported) {
saslAuthExternal()
} else {
// Some SMTP servers are known to provide no error indication when a client certificate
// fails to validate, other than to not offer the AUTH EXTERNAL capability.
// So, we treat it is an error to not offer AUTH EXTERNAL when using client certificates.
// That way, the user can be notified of a problem during account setup.
throw CertificateValidationException(
CertificateValidationException.Reason.MissingCapability,
)
throw MissingCapabilityException("AUTH EXTERNAL")
}
}
AuthType.AUTOMATIC -> {
Expand All @@ -205,7 +197,7 @@ class SmtpTransport(
} else if (authCramMD5Supported) {
saslAuthCramMD5()
} else {
throw MessagingException("No supported authentication methods available.")
throw MissingCapabilityException("AUTH PLAIN")
}
} else {
if (authCramMD5Supported) {
Expand Down
Loading