Add an AuthStateStorage
parameter to ServerSettingsValidator
Typically we use `Account` to hold the (OAuth 2.0) authorization state. But during account setup we don't have an `Account` instance yet. So we allow a `ServerSettingsValidator` caller to pass an `AuthStateStorage` that we then use with `OAuth2TokenProviderFactory` to create an `OAuth2TokenProvider` instance. When setting up an account we can use an `AuthStateStorage` implementation that will simply hold the state in memory.
This commit is contained in:
parent
3bc0bada31
commit
8e7a5f3541
14 changed files with 264 additions and 53 deletions
|
@ -6,6 +6,7 @@ import com.fsck.k9.BuildConfig
|
|||
import com.fsck.k9.backend.BackendManager
|
||||
import com.fsck.k9.backend.imap.BackendIdleRefreshManager
|
||||
import com.fsck.k9.backend.imap.SystemAlarmManager
|
||||
import com.fsck.k9.mail.oauth.OAuth2TokenProviderFactory
|
||||
import com.fsck.k9.mail.store.imap.IdleRefreshManager
|
||||
import org.koin.core.qualifier.named
|
||||
import org.koin.dsl.module
|
||||
|
@ -34,6 +35,7 @@ val backendsModule = module {
|
|||
single<IdleRefreshManager> { BackendIdleRefreshManager(alarmManager = get()) }
|
||||
single { Pop3BackendFactory(get(), get()) }
|
||||
single(named("ClientIdAppName")) { BuildConfig.CLIENT_ID_APP_NAME }
|
||||
single<OAuth2TokenProviderFactory> { RealOAuth2TokenProviderFactory(context = get()) }
|
||||
|
||||
developmentModuleAdditions()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package com.fsck.k9.backends
|
||||
|
||||
import android.content.Context
|
||||
import com.fsck.k9.mail.oauth.AuthStateStorage
|
||||
import com.fsck.k9.mail.oauth.OAuth2TokenProvider
|
||||
import com.fsck.k9.mail.oauth.OAuth2TokenProviderFactory
|
||||
|
||||
class RealOAuth2TokenProviderFactory(
|
||||
private val context: Context,
|
||||
) : OAuth2TokenProviderFactory {
|
||||
override fun create(authStateStorage: AuthStateStorage): OAuth2TokenProvider {
|
||||
return RealOAuth2TokenProvider(context, authStateStorage)
|
||||
}
|
||||
}
|
|
@ -55,7 +55,7 @@ val featureAccountSetupModule: Module = module {
|
|||
ValidateServerSettings(
|
||||
imapValidator = ImapServerSettingsValidator(
|
||||
trustedSocketFactory = get(),
|
||||
oAuth2TokenProvider = null, // TODO
|
||||
oAuth2TokenProviderFactory = get(),
|
||||
clientIdAppName = "null",
|
||||
),
|
||||
pop3Validator = Pop3ServerSettingsValidator(
|
||||
|
@ -63,7 +63,7 @@ val featureAccountSetupModule: Module = module {
|
|||
),
|
||||
smtpValidator = SmtpServerSettingsValidator(
|
||||
trustedSocketFactory = get(),
|
||||
oAuth2TokenProvider = null, // TODO
|
||||
oAuth2TokenProviderFactory = get(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -17,9 +17,9 @@ internal class ValidateServerSettings(
|
|||
override suspend fun execute(settings: ServerSettings): ServerSettingsValidationResult =
|
||||
withContext(coroutineDispatcher) {
|
||||
return@withContext when (settings.type) {
|
||||
"imap" -> imapValidator.checkServerSettings(settings)
|
||||
"pop3" -> pop3Validator.checkServerSettings(settings)
|
||||
"smtp" -> smtpValidator.checkServerSettings(settings)
|
||||
"imap" -> imapValidator.checkServerSettings(settings, authStateStorage = null)
|
||||
"pop3" -> pop3Validator.checkServerSettings(settings, authStateStorage = null)
|
||||
"smtp" -> smtpValidator.checkServerSettings(settings, authStateStorage = null)
|
||||
else -> {
|
||||
throw IllegalArgumentException("Unsupported server type: ${settings.type}")
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContrac
|
|||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract
|
||||
import app.k9mail.feature.account.setup.ui.outgoing.AccountOutgoingConfigContract
|
||||
import app.k9mail.feature.account.setup.ui.validation.AccountValidationContract
|
||||
import com.fsck.k9.mail.oauth.OAuth2TokenProvider
|
||||
import com.fsck.k9.mail.oauth.OAuth2TokenProviderFactory
|
||||
import com.fsck.k9.mail.ssl.TrustedSocketFactory
|
||||
import okhttp3.OkHttpClient
|
||||
import org.junit.Test
|
||||
|
@ -37,6 +39,14 @@ class AccountSetupModuleKtTest : KoinTest {
|
|||
AccountCreator { _ -> AccountCreatorResult.Success("accountUuid") }
|
||||
}
|
||||
single<OAuthConfigurationFactory> { OAuthConfigurationFactory { emptyMap() } }
|
||||
single<OAuth2TokenProviderFactory> {
|
||||
OAuth2TokenProviderFactory { _ ->
|
||||
object : OAuth2TokenProvider {
|
||||
override fun getToken(timeoutMillis: Long) = TODO()
|
||||
override fun invalidateToken() = TODO()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -15,9 +15,9 @@ class ValidateServerSettingsTest {
|
|||
@Test
|
||||
fun `should check with imap validator when protocol is imap`() = runTest {
|
||||
val testSubject = ValidateServerSettings(
|
||||
imapValidator = { ServerSettingsValidationResult.Success },
|
||||
pop3Validator = { ServerSettingsValidationResult.NetworkError(IOException("Failed POP3")) },
|
||||
smtpValidator = { ServerSettingsValidationResult.NetworkError(IOException("Failed SMTP")) },
|
||||
imapValidator = { _, _ -> ServerSettingsValidationResult.Success },
|
||||
pop3Validator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed POP3")) },
|
||||
smtpValidator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed SMTP")) },
|
||||
)
|
||||
|
||||
val result = testSubject.execute(IMAP_SERVER_SETTINGS)
|
||||
|
@ -29,9 +29,9 @@ class ValidateServerSettingsTest {
|
|||
fun `should check with imap validator when protocol is imap and return failure`() = runTest {
|
||||
val failure = ServerSettingsValidationResult.ServerError("Failed")
|
||||
val testSubject = ValidateServerSettings(
|
||||
imapValidator = { failure },
|
||||
pop3Validator = { ServerSettingsValidationResult.NetworkError(IOException("Failed POP3")) },
|
||||
smtpValidator = { ServerSettingsValidationResult.NetworkError(IOException("Failed SMTP")) },
|
||||
imapValidator = { _, _ -> failure },
|
||||
pop3Validator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed POP3")) },
|
||||
smtpValidator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed SMTP")) },
|
||||
)
|
||||
|
||||
val result = testSubject.execute(IMAP_SERVER_SETTINGS)
|
||||
|
@ -42,9 +42,9 @@ class ValidateServerSettingsTest {
|
|||
@Test
|
||||
fun `should check with pop3 validator when protocol is pop3`() = runTest {
|
||||
val testSubject = ValidateServerSettings(
|
||||
imapValidator = { ServerSettingsValidationResult.NetworkError(IOException("Failed IMAP")) },
|
||||
pop3Validator = { ServerSettingsValidationResult.Success },
|
||||
smtpValidator = { ServerSettingsValidationResult.NetworkError(IOException("Failed SMTP")) },
|
||||
imapValidator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed IMAP")) },
|
||||
pop3Validator = { _, _ -> ServerSettingsValidationResult.Success },
|
||||
smtpValidator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed SMTP")) },
|
||||
)
|
||||
|
||||
val result = testSubject.execute(POP3_SERVER_SETTINGS)
|
||||
|
@ -56,9 +56,9 @@ class ValidateServerSettingsTest {
|
|||
fun `should check with pop3 validator when protocol is pop3 and return failure`() = runTest {
|
||||
val failure = ServerSettingsValidationResult.ServerError("Failed POP3")
|
||||
val testSubject = ValidateServerSettings(
|
||||
imapValidator = { ServerSettingsValidationResult.NetworkError(IOException("Failed IMAP")) },
|
||||
pop3Validator = { failure },
|
||||
smtpValidator = { ServerSettingsValidationResult.NetworkError(IOException("Failed SMTP")) },
|
||||
imapValidator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed IMAP")) },
|
||||
pop3Validator = { _, _ -> failure },
|
||||
smtpValidator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed SMTP")) },
|
||||
)
|
||||
|
||||
val result = testSubject.execute(POP3_SERVER_SETTINGS)
|
||||
|
@ -69,9 +69,9 @@ class ValidateServerSettingsTest {
|
|||
@Test
|
||||
fun `should check with smtp validator when protocol is smtp`() = runTest {
|
||||
val testSubject = ValidateServerSettings(
|
||||
imapValidator = { ServerSettingsValidationResult.NetworkError(IOException("Failed IMAP")) },
|
||||
pop3Validator = { ServerSettingsValidationResult.NetworkError(IOException("Failed POP3")) },
|
||||
smtpValidator = { ServerSettingsValidationResult.Success },
|
||||
imapValidator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed IMAP")) },
|
||||
pop3Validator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed POP3")) },
|
||||
smtpValidator = { _, _ -> ServerSettingsValidationResult.Success },
|
||||
)
|
||||
|
||||
val result = testSubject.execute(SMTP_SERVER_SETTINGS)
|
||||
|
@ -83,9 +83,9 @@ class ValidateServerSettingsTest {
|
|||
fun `should check with smtp validator when protocol is smtp and return failure`() = runTest {
|
||||
val failure = ServerSettingsValidationResult.ServerError("Failed SMTP")
|
||||
val testSubject = ValidateServerSettings(
|
||||
imapValidator = { ServerSettingsValidationResult.NetworkError(IOException("Failed IMAP")) },
|
||||
pop3Validator = { ServerSettingsValidationResult.NetworkError(IOException("Failed POP3")) },
|
||||
smtpValidator = { failure },
|
||||
imapValidator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed IMAP")) },
|
||||
pop3Validator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed POP3")) },
|
||||
smtpValidator = { _, _ -> failure },
|
||||
)
|
||||
|
||||
val result = testSubject.execute(SMTP_SERVER_SETTINGS)
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
package com.fsck.k9.mail.oauth
|
||||
|
||||
/**
|
||||
* Creates an instance of [OAuth2TokenProvider] that uses a given [AuthStateStorage] to retrieve and store the
|
||||
* (implementation-specific) authorization state.
|
||||
*/
|
||||
fun interface OAuth2TokenProviderFactory {
|
||||
fun create(authStateStorage: AuthStateStorage): OAuth2TokenProvider
|
||||
}
|
|
@ -1,10 +1,14 @@
|
|||
package com.fsck.k9.mail.server
|
||||
|
||||
import com.fsck.k9.mail.ServerSettings
|
||||
import com.fsck.k9.mail.oauth.AuthStateStorage
|
||||
|
||||
/**
|
||||
* Validate [ServerSettings] by trying to connect to the server and log in.
|
||||
*/
|
||||
fun interface ServerSettingsValidator {
|
||||
fun checkServerSettings(serverSettings: ServerSettings): ServerSettingsValidationResult
|
||||
fun checkServerSettings(
|
||||
serverSettings: ServerSettings,
|
||||
authStateStorage: AuthStateStorage?,
|
||||
): ServerSettingsValidationResult
|
||||
}
|
||||
|
|
|
@ -4,7 +4,9 @@ import com.fsck.k9.mail.AuthenticationFailedException
|
|||
import com.fsck.k9.mail.CertificateValidationException
|
||||
import com.fsck.k9.mail.MessagingException
|
||||
import com.fsck.k9.mail.ServerSettings
|
||||
import com.fsck.k9.mail.oauth.AuthStateStorage
|
||||
import com.fsck.k9.mail.oauth.OAuth2TokenProvider
|
||||
import com.fsck.k9.mail.oauth.OAuth2TokenProviderFactory
|
||||
import com.fsck.k9.mail.server.ServerSettingsValidationResult
|
||||
import com.fsck.k9.mail.server.ServerSettingsValidator
|
||||
import com.fsck.k9.mail.ssl.TrustedSocketFactory
|
||||
|
@ -12,17 +14,21 @@ import java.io.IOException
|
|||
|
||||
class ImapServerSettingsValidator(
|
||||
private val trustedSocketFactory: TrustedSocketFactory,
|
||||
private val oAuth2TokenProvider: OAuth2TokenProvider?,
|
||||
private val oAuth2TokenProviderFactory: OAuth2TokenProviderFactory?,
|
||||
private val clientIdAppName: String,
|
||||
) : ServerSettingsValidator {
|
||||
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
override fun checkServerSettings(serverSettings: ServerSettings): ServerSettingsValidationResult {
|
||||
override fun checkServerSettings(
|
||||
serverSettings: ServerSettings,
|
||||
authStateStorage: AuthStateStorage?,
|
||||
): ServerSettingsValidationResult {
|
||||
val config = object : ImapStoreConfig {
|
||||
override val logLabel = "check"
|
||||
override fun isSubscribedFoldersOnly() = false
|
||||
override fun clientIdAppName() = clientIdAppName
|
||||
}
|
||||
val oAuth2TokenProvider = createOAuth2TokenProviderOrNull(authStateStorage)
|
||||
val store = RealImapStore(serverSettings, config, trustedSocketFactory, oAuth2TokenProvider)
|
||||
|
||||
return try {
|
||||
|
@ -48,4 +54,10 @@ class ImapServerSettingsValidator(
|
|||
ServerSettingsValidationResult.UnknownError(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createOAuth2TokenProviderOrNull(authStateStorage: AuthStateStorage?): OAuth2TokenProvider? {
|
||||
return authStateStorage?.let {
|
||||
oAuth2TokenProviderFactory?.create(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ import com.fsck.k9.mail.ConnectionSecurity
|
|||
import com.fsck.k9.mail.ServerSettings
|
||||
import com.fsck.k9.mail.helpers.FakeTrustManager
|
||||
import com.fsck.k9.mail.helpers.SimpleTrustedSocketFactory
|
||||
import com.fsck.k9.mail.oauth.AuthStateStorage
|
||||
import com.fsck.k9.mail.oauth.OAuth2TokenProvider
|
||||
import com.fsck.k9.mail.server.ServerSettingsValidationResult
|
||||
import com.fsck.k9.mail.store.imap.mockserver.MockImapServer
|
||||
import java.net.UnknownHostException
|
||||
|
@ -18,19 +20,22 @@ import kotlin.test.Test
|
|||
|
||||
private const val USERNAME = "user"
|
||||
private const val PASSWORD = "password"
|
||||
private const val AUTHORIZATION_STATE = "auth state"
|
||||
private const val AUTHORIZATION_TOKEN = "auth-token"
|
||||
private val CLIENT_CERTIFICATE_ALIAS: String? = null
|
||||
private const val CLIENT_ID = "clientId"
|
||||
|
||||
class ImapServerSettingsValidatorTest {
|
||||
private val fakeTrustManager = FakeTrustManager()
|
||||
private val trustedSocketFactory = SimpleTrustedSocketFactory(fakeTrustManager)
|
||||
private val serverSettingsValidator = ImapServerSettingsValidator(
|
||||
trustedSocketFactory = SimpleTrustedSocketFactory(fakeTrustManager),
|
||||
oAuth2TokenProvider = null,
|
||||
trustedSocketFactory = trustedSocketFactory,
|
||||
oAuth2TokenProviderFactory = null,
|
||||
clientIdAppName = CLIENT_ID,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `valid server settings should return Success`() {
|
||||
fun `valid server settings with password should return Success`() {
|
||||
val server = startServer {
|
||||
output("* OK IMAP4rev1 server ready")
|
||||
expect("1 CAPABILITY")
|
||||
|
@ -64,7 +69,56 @@ class ImapServerSettingsValidatorTest {
|
|||
),
|
||||
)
|
||||
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings)
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings, authStateStorage = null)
|
||||
|
||||
assertThat(result).isInstanceOf<ServerSettingsValidationResult.Success>()
|
||||
server.verifyConnectionClosed()
|
||||
server.verifyInteractionCompleted()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `valid server settings with OAuth should return Success`() {
|
||||
val serverSettingsValidator = ImapServerSettingsValidator(
|
||||
trustedSocketFactory = trustedSocketFactory,
|
||||
oAuth2TokenProviderFactory = { authStateStorage ->
|
||||
assertThat(authStateStorage.getAuthorizationState()).isEqualTo(AUTHORIZATION_STATE)
|
||||
FakeOAuth2TokenProvider()
|
||||
},
|
||||
clientIdAppName = CLIENT_ID,
|
||||
)
|
||||
val server = startServer {
|
||||
output("* OK IMAP4rev1 server ready")
|
||||
expect("1 CAPABILITY")
|
||||
output("* CAPABILITY IMAP4rev1 SASL-IR AUTH=PLAIN AUTH=OAUTHBEARER")
|
||||
output("1 OK CAPABILITY Completed")
|
||||
expect("2 AUTHENTICATE OAUTHBEARER bixhPXVzZXIsAWF1dGg9QmVhcmVyIGF1dGgtdG9rZW4BAQ==")
|
||||
output("2 OK [CAPABILITY IMAP4rev1 SASL-IR AUTH=PLAIN AUTH=OAUTHBEARER NAMESPACE ID] LOGIN completed")
|
||||
expect("3 ID (\"name\" \"$CLIENT_ID\")")
|
||||
output("* ID NIL")
|
||||
output("3 OK ID completed")
|
||||
expect("4 NAMESPACE")
|
||||
output("* NAMESPACE ((\"\" \"/\")) NIL NIL")
|
||||
output("4 OK command completed")
|
||||
}
|
||||
val serverSettings = ServerSettings(
|
||||
type = "imap",
|
||||
host = server.host,
|
||||
port = server.port,
|
||||
connectionSecurity = ConnectionSecurity.NONE,
|
||||
authenticationType = AuthType.XOAUTH2,
|
||||
username = USERNAME,
|
||||
password = PASSWORD,
|
||||
clientCertificateAlias = CLIENT_CERTIFICATE_ALIAS,
|
||||
extra = ImapStoreSettings.createExtra(
|
||||
autoDetectNamespace = true,
|
||||
pathPrefix = null,
|
||||
useCompression = false,
|
||||
sendClientId = true,
|
||||
),
|
||||
)
|
||||
val authStateStorage = FakeAuthStateStorage(authorizationState = AUTHORIZATION_STATE)
|
||||
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings, authStateStorage)
|
||||
|
||||
assertThat(result).isInstanceOf<ServerSettingsValidationResult.Success>()
|
||||
server.verifyConnectionClosed()
|
||||
|
@ -93,7 +147,7 @@ class ImapServerSettingsValidatorTest {
|
|||
clientCertificateAlias = CLIENT_CERTIFICATE_ALIAS,
|
||||
)
|
||||
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings)
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings, authStateStorage = null)
|
||||
|
||||
assertThat(result).isInstanceOf<ServerSettingsValidationResult.AuthenticationError>()
|
||||
.prop(ServerSettingsValidationResult.AuthenticationError::serverMessage).isEqualTo("Authentication failed")
|
||||
|
@ -120,7 +174,7 @@ class ImapServerSettingsValidatorTest {
|
|||
clientCertificateAlias = CLIENT_CERTIFICATE_ALIAS,
|
||||
)
|
||||
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings)
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings, authStateStorage = null)
|
||||
|
||||
assertThat(result).isInstanceOf<ServerSettingsValidationResult.ServerError>()
|
||||
server.verifyConnectionClosed()
|
||||
|
@ -150,7 +204,7 @@ class ImapServerSettingsValidatorTest {
|
|||
clientCertificateAlias = CLIENT_CERTIFICATE_ALIAS,
|
||||
)
|
||||
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings)
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings, authStateStorage = null)
|
||||
|
||||
assertThat(result).isInstanceOf<ServerSettingsValidationResult.CertificateError>()
|
||||
.prop(ServerSettingsValidationResult.CertificateError::certificateChain).hasSize(1)
|
||||
|
@ -171,7 +225,7 @@ class ImapServerSettingsValidatorTest {
|
|||
clientCertificateAlias = CLIENT_CERTIFICATE_ALIAS,
|
||||
)
|
||||
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings)
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings, authStateStorage = null)
|
||||
|
||||
assertThat(result).isInstanceOf<ServerSettingsValidationResult.NetworkError>()
|
||||
.prop(ServerSettingsValidationResult.NetworkError::exception)
|
||||
|
@ -192,7 +246,7 @@ class ImapServerSettingsValidatorTest {
|
|||
)
|
||||
|
||||
assertFailure {
|
||||
serverSettingsValidator.checkServerSettings(serverSettings)
|
||||
serverSettingsValidator.checkServerSettings(serverSettings, authStateStorage = null)
|
||||
}.isInstanceOf<IllegalArgumentException>()
|
||||
}
|
||||
|
||||
|
@ -203,3 +257,25 @@ class ImapServerSettingsValidatorTest {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FakeOAuth2TokenProvider : OAuth2TokenProvider {
|
||||
override fun getToken(timeoutMillis: Long): String {
|
||||
return AUTHORIZATION_TOKEN
|
||||
}
|
||||
|
||||
override fun invalidateToken() {
|
||||
throw UnsupportedOperationException("not implemented")
|
||||
}
|
||||
}
|
||||
|
||||
class FakeAuthStateStorage(
|
||||
private var authorizationState: String? = null,
|
||||
) : AuthStateStorage {
|
||||
override fun getAuthorizationState(): String? {
|
||||
return authorizationState
|
||||
}
|
||||
|
||||
override fun updateAuthorizationState(authorizationState: String?) {
|
||||
this.authorizationState = authorizationState
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import com.fsck.k9.mail.AuthenticationFailedException
|
|||
import com.fsck.k9.mail.CertificateValidationException
|
||||
import com.fsck.k9.mail.MessagingException
|
||||
import com.fsck.k9.mail.ServerSettings
|
||||
import com.fsck.k9.mail.oauth.AuthStateStorage
|
||||
import com.fsck.k9.mail.server.ServerSettingsValidationResult
|
||||
import com.fsck.k9.mail.server.ServerSettingsValidator
|
||||
import com.fsck.k9.mail.ssl.TrustedSocketFactory
|
||||
|
@ -14,7 +15,10 @@ class Pop3ServerSettingsValidator(
|
|||
) : ServerSettingsValidator {
|
||||
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
override fun checkServerSettings(serverSettings: ServerSettings): ServerSettingsValidationResult {
|
||||
override fun checkServerSettings(
|
||||
serverSettings: ServerSettings,
|
||||
authStateStorage: AuthStateStorage?,
|
||||
): ServerSettingsValidationResult {
|
||||
val store = Pop3Store(serverSettings, trustedSocketFactory)
|
||||
|
||||
return try {
|
||||
|
|
|
@ -54,7 +54,7 @@ class Pop3ServerSettingsValidatorTest {
|
|||
clientCertificateAlias = CLIENT_CERTIFICATE_ALIAS,
|
||||
)
|
||||
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings)
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings, authStateStorage = null)
|
||||
|
||||
assertThat(result).isInstanceOf<ServerSettingsValidationResult.Success>()
|
||||
server.verifyConnectionClosed()
|
||||
|
@ -90,7 +90,7 @@ class Pop3ServerSettingsValidatorTest {
|
|||
clientCertificateAlias = CLIENT_CERTIFICATE_ALIAS,
|
||||
)
|
||||
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings)
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings, authStateStorage = null)
|
||||
|
||||
assertThat(result).isInstanceOf<ServerSettingsValidationResult.AuthenticationError>()
|
||||
.prop(ServerSettingsValidationResult.AuthenticationError::serverMessage)
|
||||
|
@ -116,7 +116,7 @@ class Pop3ServerSettingsValidatorTest {
|
|||
clientCertificateAlias = CLIENT_CERTIFICATE_ALIAS,
|
||||
)
|
||||
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings)
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings, authStateStorage = null)
|
||||
|
||||
assertThat(result).isInstanceOf<ServerSettingsValidationResult.ServerError>()
|
||||
.prop(ServerSettingsValidationResult.ServerError::serverMessage)
|
||||
|
@ -151,7 +151,7 @@ class Pop3ServerSettingsValidatorTest {
|
|||
clientCertificateAlias = CLIENT_CERTIFICATE_ALIAS,
|
||||
)
|
||||
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings)
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings, authStateStorage = null)
|
||||
|
||||
assertThat(result).isInstanceOf<ServerSettingsValidationResult.CertificateError>()
|
||||
.prop(ServerSettingsValidationResult.CertificateError::certificateChain).hasSize(1)
|
||||
|
@ -172,7 +172,7 @@ class Pop3ServerSettingsValidatorTest {
|
|||
clientCertificateAlias = CLIENT_CERTIFICATE_ALIAS,
|
||||
)
|
||||
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings)
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings, authStateStorage = null)
|
||||
|
||||
assertThat(result).isInstanceOf<ServerSettingsValidationResult.NetworkError>()
|
||||
.prop(ServerSettingsValidationResult.NetworkError::exception)
|
||||
|
@ -193,7 +193,7 @@ class Pop3ServerSettingsValidatorTest {
|
|||
)
|
||||
|
||||
assertFailure {
|
||||
serverSettingsValidator.checkServerSettings(serverSettings)
|
||||
serverSettingsValidator.checkServerSettings(serverSettings, authStateStorage = null)
|
||||
}.isInstanceOf<IllegalArgumentException>()
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,9 @@ import com.fsck.k9.mail.AuthenticationFailedException
|
|||
import com.fsck.k9.mail.CertificateValidationException
|
||||
import com.fsck.k9.mail.MessagingException
|
||||
import com.fsck.k9.mail.ServerSettings
|
||||
import com.fsck.k9.mail.oauth.AuthStateStorage
|
||||
import com.fsck.k9.mail.oauth.OAuth2TokenProvider
|
||||
import com.fsck.k9.mail.oauth.OAuth2TokenProviderFactory
|
||||
import com.fsck.k9.mail.server.ServerSettingsValidationResult
|
||||
import com.fsck.k9.mail.server.ServerSettingsValidator
|
||||
import com.fsck.k9.mail.ssl.TrustedSocketFactory
|
||||
|
@ -12,11 +14,15 @@ import java.io.IOException
|
|||
|
||||
class SmtpServerSettingsValidator(
|
||||
private val trustedSocketFactory: TrustedSocketFactory,
|
||||
private val oAuth2TokenProvider: OAuth2TokenProvider?,
|
||||
private val oAuth2TokenProviderFactory: OAuth2TokenProviderFactory?,
|
||||
) : ServerSettingsValidator {
|
||||
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
override fun checkServerSettings(serverSettings: ServerSettings): ServerSettingsValidationResult {
|
||||
override fun checkServerSettings(
|
||||
serverSettings: ServerSettings,
|
||||
authStateStorage: AuthStateStorage?,
|
||||
): ServerSettingsValidationResult {
|
||||
val oAuth2TokenProvider = createOAuth2TokenProviderOrNull(authStateStorage)
|
||||
val smtpTransport = SmtpTransport(serverSettings, trustedSocketFactory, oAuth2TokenProvider)
|
||||
|
||||
return try {
|
||||
|
@ -42,4 +48,10 @@ class SmtpServerSettingsValidator(
|
|||
ServerSettingsValidationResult.UnknownError(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createOAuth2TokenProviderOrNull(authStateStorage: AuthStateStorage?): OAuth2TokenProvider? {
|
||||
return authStateStorage?.let {
|
||||
oAuth2TokenProviderFactory?.create(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ import com.fsck.k9.mail.ConnectionSecurity
|
|||
import com.fsck.k9.mail.ServerSettings
|
||||
import com.fsck.k9.mail.helpers.FakeTrustManager
|
||||
import com.fsck.k9.mail.helpers.SimpleTrustedSocketFactory
|
||||
import com.fsck.k9.mail.oauth.AuthStateStorage
|
||||
import com.fsck.k9.mail.oauth.OAuth2TokenProvider
|
||||
import com.fsck.k9.mail.server.ServerSettingsValidationResult
|
||||
import com.fsck.k9.mail.transport.mockServer.MockSmtpServer
|
||||
import java.net.UnknownHostException
|
||||
|
@ -18,17 +20,20 @@ import kotlin.test.Test
|
|||
|
||||
private const val USERNAME = "user"
|
||||
private const val PASSWORD = "password"
|
||||
private const val AUTHORIZATION_STATE = "auth state"
|
||||
private const val AUTHORIZATION_TOKEN = "auth-token"
|
||||
private val CLIENT_CERTIFICATE_ALIAS: String? = null
|
||||
|
||||
class SmtpServerSettingsValidatorTest {
|
||||
private val fakeTrustManager = FakeTrustManager()
|
||||
private val trustedSocketFactory = SimpleTrustedSocketFactory(fakeTrustManager)
|
||||
private val serverSettingsValidator = SmtpServerSettingsValidator(
|
||||
trustedSocketFactory = SimpleTrustedSocketFactory(fakeTrustManager),
|
||||
oAuth2TokenProvider = null,
|
||||
trustedSocketFactory = trustedSocketFactory,
|
||||
oAuth2TokenProviderFactory = null,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `valid server settings should return Success`() {
|
||||
fun `valid server settings with password should return Success`() {
|
||||
val server = MockSmtpServer().apply {
|
||||
output("220 localhost Simple Mail Transfer Service Ready")
|
||||
expect("EHLO [127.0.0.1]")
|
||||
|
@ -53,7 +58,48 @@ class SmtpServerSettingsValidatorTest {
|
|||
clientCertificateAlias = CLIENT_CERTIFICATE_ALIAS,
|
||||
)
|
||||
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings)
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings, authStateStorage = null)
|
||||
|
||||
assertThat(result).isInstanceOf<ServerSettingsValidationResult.Success>()
|
||||
server.verifyConnectionClosed()
|
||||
server.verifyInteractionCompleted()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `valid server settings with OAuth should return Success`() {
|
||||
val serverSettingsValidator = SmtpServerSettingsValidator(
|
||||
trustedSocketFactory = trustedSocketFactory,
|
||||
oAuth2TokenProviderFactory = { authStateStorage ->
|
||||
assertThat(authStateStorage.getAuthorizationState()).isEqualTo(AUTHORIZATION_STATE)
|
||||
FakeOAuth2TokenProvider()
|
||||
},
|
||||
)
|
||||
val server = MockSmtpServer().apply {
|
||||
output("220 localhost Simple Mail Transfer Service Ready")
|
||||
expect("EHLO [127.0.0.1]")
|
||||
output("250-localhost Hello client.localhost")
|
||||
output("250-ENHANCEDSTATUSCODES")
|
||||
output("250-AUTH PLAIN LOGIN OAUTHBEARER")
|
||||
output("250 HELP")
|
||||
expect("AUTH OAUTHBEARER bixhPXVzZXIsAWF1dGg9QmVhcmVyIGF1dGgtdG9rZW4BAQ==")
|
||||
output("235 2.7.0 Authentication successful")
|
||||
expect("QUIT")
|
||||
closeConnection()
|
||||
}
|
||||
server.start()
|
||||
val serverSettings = ServerSettings(
|
||||
type = "smtp",
|
||||
host = server.host,
|
||||
port = server.port,
|
||||
connectionSecurity = ConnectionSecurity.NONE,
|
||||
authenticationType = AuthType.XOAUTH2,
|
||||
username = USERNAME,
|
||||
password = null,
|
||||
clientCertificateAlias = CLIENT_CERTIFICATE_ALIAS,
|
||||
)
|
||||
val authStateStorage = FakeAuthStateStorage(authorizationState = AUTHORIZATION_STATE)
|
||||
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings, authStateStorage)
|
||||
|
||||
assertThat(result).isInstanceOf<ServerSettingsValidationResult.Success>()
|
||||
server.verifyConnectionClosed()
|
||||
|
@ -86,7 +132,7 @@ class SmtpServerSettingsValidatorTest {
|
|||
clientCertificateAlias = CLIENT_CERTIFICATE_ALIAS,
|
||||
)
|
||||
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings)
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings, authStateStorage = null)
|
||||
|
||||
assertThat(result).isInstanceOf<ServerSettingsValidationResult.AuthenticationError>()
|
||||
.prop(ServerSettingsValidationResult.AuthenticationError::serverMessage).isEqualTo("Authentication failed")
|
||||
|
@ -112,7 +158,7 @@ class SmtpServerSettingsValidatorTest {
|
|||
clientCertificateAlias = CLIENT_CERTIFICATE_ALIAS,
|
||||
)
|
||||
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings)
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings, authStateStorage = null)
|
||||
|
||||
assertThat(result).isInstanceOf<ServerSettingsValidationResult.ServerError>()
|
||||
server.verifyConnectionClosed()
|
||||
|
@ -144,7 +190,7 @@ class SmtpServerSettingsValidatorTest {
|
|||
clientCertificateAlias = CLIENT_CERTIFICATE_ALIAS,
|
||||
)
|
||||
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings)
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings, authStateStorage = null)
|
||||
|
||||
assertThat(result).isInstanceOf<ServerSettingsValidationResult.CertificateError>()
|
||||
.prop(ServerSettingsValidationResult.CertificateError::certificateChain).hasSize(1)
|
||||
|
@ -165,7 +211,7 @@ class SmtpServerSettingsValidatorTest {
|
|||
clientCertificateAlias = CLIENT_CERTIFICATE_ALIAS,
|
||||
)
|
||||
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings)
|
||||
val result = serverSettingsValidator.checkServerSettings(serverSettings, authStateStorage = null)
|
||||
|
||||
assertThat(result).isInstanceOf<ServerSettingsValidationResult.NetworkError>()
|
||||
.prop(ServerSettingsValidationResult.NetworkError::exception)
|
||||
|
@ -186,7 +232,29 @@ class SmtpServerSettingsValidatorTest {
|
|||
)
|
||||
|
||||
assertFailure {
|
||||
serverSettingsValidator.checkServerSettings(serverSettings)
|
||||
serverSettingsValidator.checkServerSettings(serverSettings, authStateStorage = null)
|
||||
}.isInstanceOf<IllegalArgumentException>()
|
||||
}
|
||||
}
|
||||
|
||||
class FakeOAuth2TokenProvider : OAuth2TokenProvider {
|
||||
override fun getToken(timeoutMillis: Long): String {
|
||||
return AUTHORIZATION_TOKEN
|
||||
}
|
||||
|
||||
override fun invalidateToken() {
|
||||
throw UnsupportedOperationException("not implemented")
|
||||
}
|
||||
}
|
||||
|
||||
class FakeAuthStateStorage(
|
||||
private var authorizationState: String? = null,
|
||||
) : AuthStateStorage {
|
||||
override fun getAuthorizationState(): String? {
|
||||
return authorizationState
|
||||
}
|
||||
|
||||
override fun updateAuthorizationState(authorizationState: String?) {
|
||||
this.authorizationState = authorizationState
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue