Merge pull request #7007 from thundernest/refactor_oauth_setup

Refactor OAuth setup
This commit is contained in:
Wolf-Martell Montwé 2023-06-21 15:35:44 +02:00 committed by GitHub
commit 3699e7d68d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
71 changed files with 364 additions and 160 deletions

View file

@ -9,6 +9,32 @@ android {
applicationId = "net.thunderbird.feature.preview"
versionCode = 1
versionName = "1.0"
buildConfigField(
"String",
"OAUTH_GMAIL_CLIENT_ID",
"\"262622259280-5qb3vtj68d5dtudmaif4g9vd3cpar8r3.apps.googleusercontent.com\"",
)
buildConfigField(
"String",
"OAUTH_YAHOO_CLIENT_ID",
"\"dj0yJmk9ejRCRU1ybmZjQlVBJmQ9WVdrOVVrZEViak4xYmxZbWNHbzlNQT09JnM9Y29uc3VtZXJzZWNyZXQmc3Y9MCZ4PTZj\"",
)
buildConfigField(
"String",
"OAUTH_AOL_CLIENT_ID",
"\"dj0yJmk9cHYydkJkTUxHcXlYJmQ9WVdrOWVHZHhVVXN4VVV3bWNHbzlNQT09JnM9Y29uc3VtZXJzZWNyZXQmc3Y9MCZ4PTdm\"",
)
buildConfigField(
"String",
"OAUTH_MICROSOFT_CLIENT_ID",
"\"e647013a-ada4-4114-b419-e43d250f99c5\"",
)
buildConfigField(
"String",
"OAUTH_MICROSOFT_REDIRECT_URI_ID",
"\"VZF2DYuLYAu4TurFd6usQB2JPts%3D\"",
)
}
packaging {
@ -19,6 +45,17 @@ android {
}
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
signingConfig = signingConfigs.getByName("debug")
}
}
lint {
baseline = file("lint-baseline.xml")
}
@ -26,6 +63,7 @@ android {
dependencies {
implementation(projects.core.ui.compose.designsystem)
implementation(projects.core.common)
implementation(projects.feature.onboarding)
implementation(projects.feature.account.setup)

View file

@ -1,6 +1,8 @@
package app.k9mail.feature.preview
import app.k9mail.core.common.oauth.OAuthConfigurationFactory
import app.k9mail.feature.account.setup.featureAccountSetupModule
import app.k9mail.feature.preview.auth.AppOAuthConfigurationFactory
import okhttp3.OkHttpClient
import org.koin.core.module.Module
import org.koin.dsl.module
@ -12,5 +14,7 @@ val featureModule: Module = module {
OkHttpClient()
}
single<OAuthConfigurationFactory> { AppOAuthConfigurationFactory() }
includes(featureAccountSetupModule)
}

View file

@ -0,0 +1,71 @@
package app.k9mail.feature.preview.auth
import app.k9mail.core.common.oauth.OAuthConfiguration
import app.k9mail.core.common.oauth.OAuthConfigurationFactory
import app.k9mail.feature.preview.BuildConfig
class AppOAuthConfigurationFactory : OAuthConfigurationFactory {
override fun createConfigurations(): Map<List<String>, OAuthConfiguration> {
return mapOf(
createAolConfiguration(),
createGmailConfiguration(),
createMicrosoftConfiguration(),
createYahooConfiguration(),
)
}
private fun createAolConfiguration(): Pair<List<String>, OAuthConfiguration> {
return listOf("imap.aol.com", "smtp.aol.com") to OAuthConfiguration(
clientId = BuildConfig.OAUTH_AOL_CLIENT_ID,
scopes = listOf("mail-w"),
authorizationEndpoint = "https://api.login.aol.com/oauth2/request_auth",
tokenEndpoint = "https://api.login.aol.com/oauth2/get_token",
redirectUri = "${BuildConfig.APPLICATION_ID}://oauth2redirect",
)
}
private fun createGmailConfiguration(): Pair<List<String>, OAuthConfiguration> {
return listOf(
"imap.gmail.com",
"imap.googlemail.com",
"smtp.gmail.com",
"smtp.googlemail.com",
) to OAuthConfiguration(
clientId = BuildConfig.OAUTH_GMAIL_CLIENT_ID,
scopes = listOf("https://mail.google.com/"),
authorizationEndpoint = "https://accounts.google.com/o/oauth2/v2/auth",
tokenEndpoint = "https://oauth2.googleapis.com/token",
redirectUri = "${BuildConfig.APPLICATION_ID}:/oauth2redirect",
)
}
private fun createMicrosoftConfiguration(): Pair<List<String>, OAuthConfiguration> {
return listOf(
"imap.mail.yahoo.com",
"smtp.mail.yahoo.com",
) to OAuthConfiguration(
clientId = BuildConfig.OAUTH_MICROSOFT_CLIENT_ID,
scopes = listOf(
"https://outlook.office.com/IMAP.AccessAsUser.All",
"https://outlook.office.com/SMTP.Send",
"offline_access",
),
authorizationEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
tokenEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/token",
redirectUri = "msauth://${BuildConfig.APPLICATION_ID}://${BuildConfig.OAUTH_MICROSOFT_REDIRECT_URI_ID}",
)
}
private fun createYahooConfiguration(): Pair<List<String>, OAuthConfiguration> {
return listOf(
"imap.mail.yahoo.com",
"smtp.mail.yahoo.com",
) to OAuthConfiguration(
clientId = BuildConfig.OAUTH_YAHOO_CLIENT_ID,
scopes = listOf("mail-w"),
authorizationEndpoint = "https://api.login.yahoo.com/oauth2/request_auth",
tokenEndpoint = "https://api.login.yahoo.com/oauth2/get_token",
redirectUri = "${BuildConfig.APPLICATION_ID}://oauth2redirect",
)
}
}

View file

@ -28,6 +28,7 @@ dependencies {
implementation(libs.mime4j.dom)
testApi(projects.core.testing)
testApi(projects.core.android.testing)
testImplementation(projects.mail.testing)
testImplementation(projects.backend.imap)
testImplementation(projects.mail.protocols.smtp)

View file

@ -1,10 +1,10 @@
package com.fsck.k9.mailstore
import app.k9mail.core.common.mail.Protocols
import com.fsck.k9.Account
import com.fsck.k9.Account.SpecialFolderSelection
import com.fsck.k9.Preferences
import com.fsck.k9.mail.FolderClass
import com.fsck.k9.preferences.Protocols
/**
* Updates special folders in [Account] if they are marked as [SpecialFolderSelection.AUTOMATIC] or if they are marked

View file

@ -1,10 +1,10 @@
package com.fsck.k9.mailstore
import app.k9mail.core.common.mail.Protocols
import com.fsck.k9.Account
import com.fsck.k9.Account.SpecialFolderSelection
import com.fsck.k9.Preferences
import com.fsck.k9.mail.FolderType
import com.fsck.k9.preferences.Protocols
import timber.log.Timber
class SpecialLocalFoldersCreator(

View file

@ -1,22 +0,0 @@
package com.fsck.k9.oauth
class OAuthConfigurationProvider(
private val configurations: Map<List<String>, OAuthConfiguration>,
private val googleConfiguration: OAuthConfiguration,
) {
private val hostnameMapping: Map<String, OAuthConfiguration> = buildMap {
for ((hostnames, configuration) in configurations) {
for (hostname in hostnames) {
put(hostname.lowercase(), configuration)
}
}
}
fun getConfiguration(hostname: String): OAuthConfiguration? {
return hostnameMapping[hostname.lowercase()]
}
fun isGoogle(hostname: String): Boolean {
return getConfiguration(hostname) == googleConfiguration
}
}

View file

@ -1,5 +1,7 @@
package com.fsck.k9.preferences
import app.k9mail.core.common.mail.Protocols
object ServerTypeConverter {
@JvmStatic
fun toServerSettingsType(exportType: String): String = exportType.lowercase()

View file

@ -1,6 +1,6 @@
package com.fsck.k9.setup
import com.fsck.k9.preferences.Protocols
import app.k9mail.core.common.mail.Protocols
class ServerNameSuggester {
fun suggestServerName(serverType: String, domainPart: String): String = when (serverType) {

View file

@ -1,12 +1,12 @@
package com.fsck.k9.autocrypt
import app.k9mail.core.android.testing.RobolectricTest
import assertk.assertThat
import assertk.assertions.isEqualTo
import assertk.assertions.isFalse
import assertk.assertions.isNotNull
import assertk.assertions.isNull
import assertk.assertions.isTrue
import com.fsck.k9.RobolectricTest
import org.junit.Test
class AutocryptDraftStateHeaderParserTest : RobolectricTest() {

View file

@ -5,7 +5,7 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import com.fsck.k9.RobolectricTest;
import app.k9mail.core.android.testing.RobolectricTest;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.BinaryTempFileBody;
import com.fsck.k9.mail.internet.MimeMessage;

View file

@ -33,7 +33,7 @@ import com.fsck.k9.mailstore.SendState;
import com.fsck.k9.mailstore.SpecialLocalFoldersCreator;
import com.fsck.k9.notification.NotificationController;
import com.fsck.k9.notification.NotificationStrategy;
import com.fsck.k9.preferences.Protocols;
import app.k9mail.core.common.mail.Protocols;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

View file

@ -1,10 +1,10 @@
package com.fsck.k9.helper
import app.k9mail.core.android.testing.RobolectricTest
import assertk.assertThat
import assertk.assertions.isEqualTo
import com.fsck.k9.Account
import com.fsck.k9.Identity
import com.fsck.k9.RobolectricTest
import com.fsck.k9.mail.Address
import com.fsck.k9.mail.Message
import com.fsck.k9.mail.Message.RecipientType

View file

@ -1,7 +1,6 @@
package com.fsck.k9.helper;
import com.fsck.k9.RobolectricTest;
import app.k9mail.core.android.testing.RobolectricTest;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.internet.MimeMessage;
@ -10,7 +9,6 @@ import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
public class ListHeadersTest extends RobolectricTest {
private static final String[] TEST_EMAIL_ADDRESSES = new String[] {
"prettyandsimple@example.com",

View file

@ -1,10 +1,10 @@
package com.fsck.k9.helper
import androidx.core.net.toUri
import app.k9mail.core.android.testing.RobolectricTest
import assertk.assertThat
import assertk.assertions.isEqualTo
import assertk.assertions.isNull
import com.fsck.k9.RobolectricTest
import com.fsck.k9.mail.internet.MimeMessage
import org.junit.Test

View file

@ -7,7 +7,7 @@ import java.util.List;
import android.net.Uri;
import com.fsck.k9.RobolectricTest;
import app.k9mail.core.android.testing.RobolectricTest;
import com.fsck.k9.helper.MailTo.CaseInsensitiveParamWrapper;
import com.fsck.k9.mail.Address;
import org.junit.Rule;

View file

@ -4,12 +4,12 @@ import android.graphics.Color
import android.text.SpannableString
import app.k9mail.core.android.common.contact.Contact
import app.k9mail.core.android.common.contact.ContactRepository
import app.k9mail.core.android.testing.RobolectricTest
import app.k9mail.core.common.mail.EmailAddress
import app.k9mail.core.common.mail.toEmailAddressOrThrow
import assertk.assertThat
import assertk.assertions.isEqualTo
import assertk.assertions.isInstanceOf
import com.fsck.k9.RobolectricTest
import com.fsck.k9.helper.MessageHelper.Companion.toFriendly
import com.fsck.k9.mail.Address
import org.junit.Test

View file

@ -1,11 +1,10 @@
package com.fsck.k9.helper;
import java.lang.reflect.Array;
import java.util.ArrayList;
import app.k9mail.core.android.testing.RobolectricTest;
import com.fsck.k9.Account;
import com.fsck.k9.RobolectricTest;
import com.fsck.k9.helper.ReplyToParser.ReplyToAddresses;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Message;

View file

@ -5,7 +5,7 @@ import java.util.Map;
import android.net.Uri;
import com.fsck.k9.RobolectricTest;
import app.k9mail.core.android.testing.RobolectricTest;
import com.fsck.k9.mail.BodyPart;
import com.fsck.k9.mail.Multipart;
import com.fsck.k9.mail.Part;

View file

@ -1,10 +1,10 @@
package com.fsck.k9.message
import app.k9mail.core.android.testing.RobolectricTest
import assertk.assertThat
import assertk.assertions.isGreaterThan
import com.fsck.k9.Account.QuoteStyle
import com.fsck.k9.Identity
import com.fsck.k9.RobolectricTest
import com.fsck.k9.mail.internet.MimeHeaderChecker
import com.fsck.k9.mail.internet.TextBody
import org.junit.Test

View file

@ -1,8 +1,8 @@
package com.fsck.k9.message
import app.k9mail.core.android.testing.RobolectricTest
import assertk.assertThat
import assertk.assertions.contains
import com.fsck.k9.RobolectricTest
import com.fsck.k9.helper.toCrLf
import org.junit.Test

View file

@ -1,6 +1,5 @@
package com.fsck.k9.message;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
@ -13,10 +12,10 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import app.k9mail.core.android.testing.RobolectricTest;
import com.fsck.k9.Account.QuoteStyle;
import com.fsck.k9.CoreResourceProvider;
import com.fsck.k9.Identity;
import com.fsck.k9.RobolectricTest;
import com.fsck.k9.TestCoreResourceProvider;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.BodyPart;

View file

@ -1,11 +1,10 @@
package com.fsck.k9.message.extractors;
import android.content.Context;
import android.net.Uri;
import androidx.annotation.Nullable;
import com.fsck.k9.RobolectricTest;
import app.k9mail.core.android.testing.RobolectricTest;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.internet.MimeBodyPart;
import com.fsck.k9.mail.internet.MimeHeader;

View file

@ -1,9 +1,9 @@
package com.fsck.k9.message.quote
import app.k9mail.core.android.testing.RobolectricTest
import assertk.assertThat
import assertk.assertions.isEqualTo
import com.fsck.k9.Account.QuoteStyle
import com.fsck.k9.RobolectricTest
import com.fsck.k9.TestCoreResourceProvider
import com.fsck.k9.mail.Address
import com.fsck.k9.mail.Message

View file

@ -5,8 +5,8 @@ import android.app.PendingIntent
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.test.core.app.ApplicationProvider
import app.k9mail.core.android.testing.RobolectricTest
import com.fsck.k9.Account
import com.fsck.k9.RobolectricTest
import com.fsck.k9.testing.MockHelper.mockBuilder
import org.junit.Test
import org.mockito.Mockito.verify

View file

@ -5,8 +5,8 @@ import android.app.PendingIntent
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.test.core.app.ApplicationProvider
import app.k9mail.core.android.testing.RobolectricTest
import com.fsck.k9.Account
import com.fsck.k9.RobolectricTest
import com.fsck.k9.testing.MockHelper.mockBuilder
import org.junit.Test
import org.mockito.Mockito.verify

View file

@ -2,8 +2,8 @@ package com.fsck.k9.notification
import androidx.core.app.NotificationCompat
import androidx.test.core.app.ApplicationProvider
import app.k9mail.core.android.testing.RobolectricTest
import com.fsck.k9.Account
import com.fsck.k9.RobolectricTest
import com.fsck.k9.testing.MockHelper.mockBuilder
import org.junit.Test
import org.mockito.Mockito.verify

View file

@ -1,10 +1,10 @@
package com.fsck.k9.notification
import app.k9mail.core.android.common.contact.ContactRepository
import app.k9mail.core.android.testing.RobolectricTest
import assertk.assertThat
import assertk.assertions.isEqualTo
import com.fsck.k9.Account
import com.fsck.k9.RobolectricTest
import com.fsck.k9.controller.MessageReference
import com.fsck.k9.mail.Address
import com.fsck.k9.mail.Message.RecipientType

View file

@ -1,5 +1,6 @@
package com.fsck.k9.notification
import app.k9mail.core.android.testing.RobolectricTest
import assertk.assertThat
import assertk.assertions.containsExactly
import assertk.assertions.hasSize
@ -12,7 +13,6 @@ import assertk.assertions.isNull
import assertk.assertions.isSameAs
import assertk.assertions.isTrue
import com.fsck.k9.Account
import com.fsck.k9.RobolectricTest
import com.fsck.k9.controller.MessageReference
import kotlin.test.assertNotNull
import org.junit.Test

View file

@ -5,8 +5,8 @@ import android.app.PendingIntent
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.test.core.app.ApplicationProvider
import app.k9mail.core.android.testing.RobolectricTest
import com.fsck.k9.Account
import com.fsck.k9.RobolectricTest
import com.fsck.k9.testing.MockHelper.mockBuilder
import org.junit.Test
import org.mockito.ArgumentMatchers.anyLong

View file

@ -5,8 +5,8 @@ import android.app.PendingIntent
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.test.core.app.ApplicationProvider
import app.k9mail.core.android.testing.RobolectricTest
import com.fsck.k9.Account
import com.fsck.k9.RobolectricTest
import com.fsck.k9.mailstore.LocalFolder
import com.fsck.k9.notification.NotificationIds.getFetchingMailNotificationId
import com.fsck.k9.testing.MockHelper.mockBuilder

View file

@ -1,7 +1,7 @@
package com.fsck.k9.setup;
import com.fsck.k9.preferences.Protocols;
import app.k9mail.core.common.mail.Protocols;
import org.junit.Before;
import org.junit.Test;

View file

@ -1,7 +1,8 @@
package com.fsck.k9
import app.k9mail.core.common.oauth.OAuthConfigurationFactory
import app.k9mail.ui.widget.list.messageListWidgetModule
import com.fsck.k9.auth.createOAuthConfigurationProvider
import com.fsck.k9.auth.AppOAuthConfigurationFactory
import com.fsck.k9.backends.backendsModule
import com.fsck.k9.controller.ControllerExtension
import com.fsck.k9.crypto.EncryptionExtractor
@ -29,7 +30,7 @@ private val mainAppModule = module {
single(named("controllerExtensions")) { emptyList<ControllerExtension>() }
single<EncryptionExtractor> { OpenPgpEncryptionExtractor.newInstance() }
single<StoragePersister> { K9StoragePersister(get()) }
single { createOAuthConfigurationProvider() }
single<OAuthConfigurationFactory> { AppOAuthConfigurationFactory() }
}
val appModules = listOf(

View file

@ -0,0 +1,71 @@
package com.fsck.k9.auth
import app.k9mail.core.common.oauth.OAuthConfiguration
import app.k9mail.core.common.oauth.OAuthConfigurationFactory
import com.fsck.k9.BuildConfig
class AppOAuthConfigurationFactory : OAuthConfigurationFactory {
override fun createConfigurations(): Map<List<String>, OAuthConfiguration> {
return mapOf(
createAolConfiguration(),
createGmailConfiguration(),
createMicrosoftConfiguration(),
createYahooConfiguration(),
)
}
private fun createAolConfiguration(): Pair<List<String>, OAuthConfiguration> {
return listOf("imap.aol.com", "smtp.aol.com") to OAuthConfiguration(
clientId = BuildConfig.OAUTH_AOL_CLIENT_ID,
scopes = listOf("mail-w"),
authorizationEndpoint = "https://api.login.aol.com/oauth2/request_auth",
tokenEndpoint = "https://api.login.aol.com/oauth2/get_token",
redirectUri = "${BuildConfig.APPLICATION_ID}://oauth2redirect",
)
}
private fun createGmailConfiguration(): Pair<List<String>, OAuthConfiguration> {
return listOf(
"imap.gmail.com",
"imap.googlemail.com",
"smtp.gmail.com",
"smtp.googlemail.com",
) to OAuthConfiguration(
clientId = BuildConfig.OAUTH_GMAIL_CLIENT_ID,
scopes = listOf("https://mail.google.com/"),
authorizationEndpoint = "https://accounts.google.com/o/oauth2/v2/auth",
tokenEndpoint = "https://oauth2.googleapis.com/token",
redirectUri = "${BuildConfig.APPLICATION_ID}:/oauth2redirect",
)
}
private fun createMicrosoftConfiguration(): Pair<List<String>, OAuthConfiguration> {
return listOf(
"imap.mail.yahoo.com",
"smtp.mail.yahoo.com",
) to OAuthConfiguration(
clientId = BuildConfig.OAUTH_MICROSOFT_CLIENT_ID,
scopes = listOf(
"https://outlook.office.com/IMAP.AccessAsUser.All",
"https://outlook.office.com/SMTP.Send",
"offline_access",
),
authorizationEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
tokenEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/token",
redirectUri = BuildConfig.OAUTH_MICROSOFT_REDIRECT_URI,
)
}
private fun createYahooConfiguration(): Pair<List<String>, OAuthConfiguration> {
return listOf(
"imap.mail.yahoo.com",
"smtp.mail.yahoo.com",
) to OAuthConfiguration(
clientId = BuildConfig.OAUTH_YAHOO_CLIENT_ID,
scopes = listOf("mail-w"),
authorizationEndpoint = "https://api.login.yahoo.com/oauth2/request_auth",
tokenEndpoint = "https://api.login.yahoo.com/oauth2/get_token",
redirectUri = "${BuildConfig.APPLICATION_ID}://oauth2redirect",
)
}
}

View file

@ -1,50 +0,0 @@
package com.fsck.k9.auth
import com.fsck.k9.BuildConfig
import com.fsck.k9.oauth.OAuthConfiguration
import com.fsck.k9.oauth.OAuthConfigurationProvider
fun createOAuthConfigurationProvider(): OAuthConfigurationProvider {
val redirectUriSlash = BuildConfig.APPLICATION_ID + ":/oauth2redirect"
val redirectUriDoubleSlash = BuildConfig.APPLICATION_ID + "://oauth2redirect"
val googleConfig = OAuthConfiguration(
clientId = BuildConfig.OAUTH_GMAIL_CLIENT_ID,
scopes = listOf("https://mail.google.com/"),
authorizationEndpoint = "https://accounts.google.com/o/oauth2/v2/auth",
tokenEndpoint = "https://oauth2.googleapis.com/token",
redirectUri = redirectUriSlash,
)
return OAuthConfigurationProvider(
configurations = mapOf(
listOf("imap.gmail.com", "imap.googlemail.com", "smtp.gmail.com", "smtp.googlemail.com") to googleConfig,
listOf("imap.mail.yahoo.com", "smtp.mail.yahoo.com") to OAuthConfiguration(
clientId = BuildConfig.OAUTH_YAHOO_CLIENT_ID,
scopes = listOf("mail-w"),
authorizationEndpoint = "https://api.login.yahoo.com/oauth2/request_auth",
tokenEndpoint = "https://api.login.yahoo.com/oauth2/get_token",
redirectUri = redirectUriDoubleSlash,
),
listOf("imap.aol.com", "smtp.aol.com") to OAuthConfiguration(
clientId = BuildConfig.OAUTH_AOL_CLIENT_ID,
scopes = listOf("mail-w"),
authorizationEndpoint = "https://api.login.aol.com/oauth2/request_auth",
tokenEndpoint = "https://api.login.aol.com/oauth2/get_token",
redirectUri = redirectUriDoubleSlash,
),
listOf("outlook.office365.com", "smtp.office365.com") to OAuthConfiguration(
clientId = BuildConfig.OAUTH_MICROSOFT_CLIENT_ID,
scopes = listOf(
"https://outlook.office.com/IMAP.AccessAsUser.All",
"https://outlook.office.com/SMTP.Send",
"offline_access",
),
authorizationEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
tokenEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/token",
redirectUri = BuildConfig.OAUTH_MICROSOFT_REDIRECT_URI,
),
),
googleConfiguration = googleConfig,
)
}

View file

@ -1,8 +1,8 @@
package com.fsck.k9.preferences.migrations
import android.database.sqlite.SQLiteDatabase
import app.k9mail.core.common.mail.Protocols
import com.fsck.k9.ServerSettingsSerializer
import com.fsck.k9.preferences.Protocols
/**
* Rewrite 'folderPushMode' value of non-IMAP accounts to 'NONE'.

View file

@ -1,8 +1,8 @@
package com.fsck.k9.storage.migrations
import android.database.sqlite.SQLiteDatabase
import app.k9mail.core.common.mail.Protocols
import com.fsck.k9.mailstore.MigrationsHelper
import com.fsck.k9.preferences.Protocols
internal object MigrationTo65 {
@JvmStatic

View file

@ -3,9 +3,9 @@ package com.fsck.k9.storage.migrations
import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase
import app.k9mail.core.android.common.database.map
import app.k9mail.core.common.mail.Protocols
import com.fsck.k9.Account
import com.fsck.k9.mailstore.MigrationsHelper
import com.fsck.k9.preferences.Protocols
import timber.log.Timber
/**

View file

@ -5,8 +5,8 @@ plugins {
dependencies {
implementation(projects.app.core)
api(libs.junit)
api(libs.robolectric)
api(projects.core.android.testing)
api(libs.koin.core)
api(libs.mockito.core)
api(libs.mockito.kotlin)

View file

@ -1,11 +1,11 @@
package com.fsck.k9.account
import android.content.res.Resources
import app.k9mail.core.common.mail.Protocols
import com.fsck.k9.Account.DeletePolicy
import com.fsck.k9.Preferences
import com.fsck.k9.core.R
import com.fsck.k9.mail.ConnectionSecurity
import com.fsck.k9.preferences.Protocols
/**
* Deals with logic surrounding account creation.

View file

@ -4,13 +4,13 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import app.k9mail.core.common.mail.Protocols
import com.fsck.k9.Account
import com.fsck.k9.Preferences
import com.fsck.k9.helper.EmailHelper.getDomainFromEmailAddress
import com.fsck.k9.mail.ConnectionSecurity
import com.fsck.k9.mail.ServerSettings
import com.fsck.k9.mailstore.SpecialLocalFoldersCreator
import com.fsck.k9.preferences.Protocols
import com.fsck.k9.setup.ServerNameSuggester
import com.fsck.k9.ui.R
import com.fsck.k9.ui.base.K9Activity

View file

@ -38,7 +38,7 @@ import com.fsck.k9.mail.ConnectionSecurity;
import com.fsck.k9.mail.MailServerDirection;
import com.fsck.k9.mail.ServerSettings;
import com.fsck.k9.mail.store.imap.ImapStoreSettings;
import com.fsck.k9.preferences.Protocols;
import app.k9mail.core.common.mail.Protocols;
import com.fsck.k9.ui.R;
import com.fsck.k9.ui.base.extensions.TextInputLayoutHelper;
import com.fsck.k9.view.ClientCertificateSpinner;

View file

@ -28,7 +28,7 @@ import com.fsck.k9.Account;
import com.fsck.k9.DI;
import com.fsck.k9.LocalKeyStoreManager;
import com.fsck.k9.Preferences;
import com.fsck.k9.preferences.Protocols;
import app.k9mail.core.common.mail.Protocols;
import com.fsck.k9.ui.R;
import com.fsck.k9.account.AccountCreator;
import com.fsck.k9.ui.base.K9Activity;

View file

@ -14,9 +14,9 @@ import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.viewModelScope
import app.k9mail.core.common.oauth.OAuthConfiguration
import app.k9mail.core.common.oauth.OAuthConfigurationProvider
import com.fsck.k9.Account
import com.fsck.k9.oauth.OAuthConfiguration
import com.fsck.k9.oauth.OAuthConfigurationProvider
import com.fsck.k9.preferences.AccountManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
@ -72,7 +72,7 @@ class AuthViewModel(
}
fun isUsingGoogle(account: Account): Boolean {
return oAuthConfigurationProvider.isGoogle(account.incomingServerSettings.host!!)
return GoogleOAuthHelper.isGoogle(account.incomingServerSettings.host!!)
}
private fun getOrCreateAuthState(account: Account): AuthState {

View file

@ -0,0 +1,8 @@
package com.fsck.k9.activity.setup
object GoogleOAuthHelper {
fun isGoogle(hostname: String): Boolean {
return hostname.lowercase().endsWith(".gmail.com") ||
hostname.lowercase().endsWith(".googlemail.com")
}
}

View file

@ -7,7 +7,7 @@ import com.fsck.k9.Account.DeletePolicy;
import com.fsck.k9.Preferences;
import com.fsck.k9.RobolectricTest;
import com.fsck.k9.mail.ConnectionSecurity;
import com.fsck.k9.preferences.Protocols;
import app.k9mail.core.common.mail.Protocols;
import org.junit.Before;
import org.junit.Test;

View file

@ -1,12 +1,12 @@
package com.fsck.k9.activity.compose
import android.os.Bundle
import app.k9mail.core.android.testing.RobolectricTest
import assertk.assertThat
import assertk.assertions.isFalse
import assertk.assertions.isSameAs
import assertk.assertions.isTrue
import com.fsck.k9.Identity
import com.fsck.k9.RobolectricTest
import com.fsck.k9.mail.Address
import org.junit.Test
import org.mockito.kotlin.doReturn

View file

@ -1,8 +1,8 @@
package com.fsck.k9.contacts
import app.k9mail.core.android.testing.RobolectricTest
import assertk.assertThat
import assertk.assertions.isEqualTo
import com.fsck.k9.RobolectricTest
import com.fsck.k9.mail.Address
import org.junit.Test

View file

@ -1,9 +1,9 @@
package com.fsck.k9.ui
import app.k9mail.core.android.testing.RobolectricTest
import assertk.assertThat
import assertk.assertions.isEqualTo
import assertk.assertions.size
import com.fsck.k9.RobolectricTest
import com.fsck.k9.core.R
import org.junit.Test
import org.robolectric.RuntimeEnvironment

View file

@ -2,10 +2,10 @@ package com.fsck.k9.ui.helper
import android.os.Build
import android.os.SystemClock
import app.k9mail.core.android.testing.RobolectricTest
import app.k9mail.core.testing.TestClock
import assertk.assertThat
import assertk.assertions.isEqualTo
import com.fsck.k9.RobolectricTest
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.ZoneId

View file

@ -1,8 +1,8 @@
package com.fsck.k9.ui.helper
import app.k9mail.core.android.testing.RobolectricTest
import assertk.assertThat
import assertk.assertions.isEqualTo
import com.fsck.k9.RobolectricTest
import org.junit.Test
import org.robolectric.RuntimeEnvironment
import org.robolectric.annotation.Config

View file

@ -4,6 +4,7 @@ import android.graphics.Color
import android.text.Spannable
import android.text.style.ForegroundColorSpan
import androidx.core.text.getSpans
import app.k9mail.core.android.testing.RobolectricTest
import assertk.assertThat
import assertk.assertions.containsExactly
import assertk.assertions.isEqualTo
@ -12,7 +13,6 @@ import assertk.assertions.isNotNull
import assertk.assertions.isNull
import com.fsck.k9.Account
import com.fsck.k9.Identity
import com.fsck.k9.RobolectricTest
import com.fsck.k9.helper.ContactNameProvider
import com.fsck.k9.mail.Address
import org.junit.Test

View file

@ -11,6 +11,7 @@ import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isGone
import androidx.core.view.isVisible
import app.k9mail.core.android.testing.RobolectricTest
import app.k9mail.core.testing.TestClock
import assertk.Assert
import assertk.assertThat
@ -21,7 +22,6 @@ import com.fsck.k9.Account
import com.fsck.k9.FontSizes
import com.fsck.k9.FontSizes.FONT_DEFAULT
import com.fsck.k9.FontSizes.LARGE
import com.fsck.k9.RobolectricTest
import com.fsck.k9.UiDensity
import com.fsck.k9.contacts.ContactPictureLoader
import com.fsck.k9.mail.Address

View file

@ -4,13 +4,13 @@ import android.graphics.Color
import android.text.Spannable
import android.text.style.ForegroundColorSpan
import androidx.core.text.getSpans
import app.k9mail.core.android.testing.RobolectricTest
import assertk.assertThat
import assertk.assertions.containsExactly
import assertk.assertions.isEqualTo
import assertk.assertions.isInstanceOf
import com.fsck.k9.Account
import com.fsck.k9.Identity
import com.fsck.k9.RobolectricTest
import com.fsck.k9.helper.ContactNameProvider
import com.fsck.k9.mail.Address
import org.junit.Test

View file

@ -1,9 +1,9 @@
package com.fsck.k9.ui.messageview
import app.k9mail.core.android.testing.RobolectricTest
import assertk.assertThat
import assertk.assertions.isEqualTo
import assertk.assertions.isNull
import com.fsck.k9.RobolectricTest
import org.junit.Test
class RecipientLayoutCreatorTest : RobolectricTest() {

View file

@ -1,5 +1,6 @@
package app.k9mail.core.android.common
import app.k9mail.core.android.common.test.externalModule
import org.junit.Test
import org.junit.runner.RunWith
import org.koin.android.ext.koin.androidContext
@ -14,7 +15,10 @@ internal class CoreCommonAndroidModuleKtTest {
@Test
fun `should have a valid di module`() {
koinApplication {
modules(coreCommonAndroidModule)
modules(
externalModule,
coreCommonAndroidModule,
)
androidContext(RuntimeEnvironment.getApplication())
checkModules()
}

View file

@ -1,6 +1,7 @@
package app.k9mail.core.android.common.contact
import app.k9mail.core.android.common.coreCommonAndroidModule
import app.k9mail.core.android.common.test.externalModule
import org.junit.Test
import org.junit.runner.RunWith
import org.koin.android.ext.koin.androidContext
@ -11,13 +12,15 @@ import org.robolectric.RuntimeEnvironment
@RunWith(RobolectricTestRunner::class)
internal class ContactKoinModuleKtTest {
@Test
fun `should have a valid di module`() {
koinApplication {
modules(coreCommonAndroidModule)
modules(contactModule)
modules(
externalModule,
coreCommonAndroidModule,
contactModule,
)
androidContext(RuntimeEnvironment.getApplication())
checkModules()
}

View file

@ -0,0 +1,10 @@
package app.k9mail.core.android.common.test
import app.k9mail.core.common.oauth.OAuthConfigurationFactory
import org.koin.dsl.module
internal val externalModule = module {
single<OAuthConfigurationFactory> {
OAuthConfigurationFactory { emptyMap() }
}
}

View file

@ -0,0 +1,12 @@
plugins {
id(ThunderbirdPlugins.Library.android)
}
android {
namespace = "app.k9mail.core.android.testing"
}
dependencies {
api(libs.junit)
api(libs.robolectric)
}

View file

@ -1,4 +1,4 @@
package com.fsck.k9
package app.k9mail.core.android.testing
import android.app.Application
import org.junit.runner.RunWith
@ -6,9 +6,7 @@ import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
/**
* A Robolectric test that does not create an instance of our [Application] class [K9].
*
* See also [K9RobolectricTest].
* A Robolectric test that does not create an instance of our [Application].
*/
@RunWith(RobolectricTestRunner::class)
@Config(application = EmptyApplication::class)

View file

@ -1,9 +1,17 @@
package app.k9mail.core.common
import app.k9mail.core.common.oauth.InMemoryOAuthConfigurationProvider
import app.k9mail.core.common.oauth.OAuthConfigurationProvider
import kotlinx.datetime.Clock
import org.koin.core.module.Module
import org.koin.dsl.module
val coreCommonModule: Module = module {
single<Clock> { Clock.System }
single<OAuthConfigurationProvider> {
InMemoryOAuthConfigurationProvider(
configurationFactory = get(),
)
}
}

View file

@ -1,4 +1,4 @@
package com.fsck.k9.preferences
package app.k9mail.core.common.mail
object Protocols {
const val IMAP = "imap"

View file

@ -0,0 +1,18 @@
package app.k9mail.core.common.oauth
internal class InMemoryOAuthConfigurationProvider(
private val configurationFactory: OAuthConfigurationFactory,
) : OAuthConfigurationProvider {
private val hostnameMapping: Map<String, OAuthConfiguration> = buildMap {
for ((hostnames, configuration) in configurationFactory.createConfigurations()) {
for (hostname in hostnames) {
put(hostname.lowercase(), configuration)
}
}
}
override fun getConfiguration(hostname: String): OAuthConfiguration? {
return hostnameMapping[hostname.lowercase()]
}
}

View file

@ -1,4 +1,4 @@
package com.fsck.k9.oauth
package app.k9mail.core.common.oauth
data class OAuthConfiguration(
val clientId: String,

View file

@ -0,0 +1,5 @@
package app.k9mail.core.common.oauth
fun interface OAuthConfigurationFactory {
fun createConfigurations(): Map<List<String>, OAuthConfiguration>
}

View file

@ -0,0 +1,6 @@
package app.k9mail.core.common.oauth
interface OAuthConfigurationProvider {
fun getConfiguration(hostname: String): OAuthConfiguration?
}

View file

@ -1,15 +1,32 @@
package app.k9mail.core.common
import app.k9mail.core.common.oauth.OAuthConfigurationFactory
import org.junit.Test
import org.koin.core.annotation.KoinExperimentalAPI
import org.koin.dsl.koinApplication
import org.koin.dsl.module
import org.koin.test.check.checkModules
import org.koin.test.verify.verify
@OptIn(KoinExperimentalAPI::class)
internal class CoreCommonModuleKtTest {
private val externalModule = module {
single<OAuthConfigurationFactory> {
OAuthConfigurationFactory { emptyMap() }
}
}
@Test
fun `should have a valid di module`() {
coreCommonModule.verify(
extraTypes = listOf(
OAuthConfigurationFactory::class,
),
)
koinApplication {
modules(coreCommonModule)
modules(externalModule, coreCommonModule)
checkModules()
}
}

View file

@ -3,13 +3,13 @@ plugins {
}
dependencies {
implementation(projects.app.core)
implementation(projects.core.common)
implementation(projects.mail.common)
implementation(projects.feature.autodiscovery.api)
implementation(libs.timber)
testImplementation(projects.app.testing)
testImplementation(projects.core.android.testing)
testImplementation(projects.backend.imap)
testImplementation(libs.robolectric)
testImplementation(libs.androidx.test.core)

View file

@ -5,11 +5,11 @@ import android.net.Uri
import app.k9mail.autodiscovery.api.ConnectionSettingsDiscovery
import app.k9mail.autodiscovery.api.DiscoveredServerSettings
import app.k9mail.autodiscovery.api.DiscoveryResults
import app.k9mail.core.common.mail.Protocols
import app.k9mail.core.common.oauth.OAuthConfigurationProvider
import com.fsck.k9.helper.EmailHelper
import com.fsck.k9.mail.AuthType
import com.fsck.k9.mail.ConnectionSecurity
import com.fsck.k9.oauth.OAuthConfigurationProvider
import com.fsck.k9.preferences.Protocols
import org.xmlpull.v1.XmlPullParser
import timber.log.Timber

View file

@ -1,15 +1,15 @@
package app.k9mail.autodiscovery.providersxml
import androidx.test.core.app.ApplicationProvider
import app.k9mail.core.android.testing.RobolectricTest
import app.k9mail.core.common.oauth.OAuthConfiguration
import app.k9mail.core.common.oauth.OAuthConfigurationProvider
import assertk.assertThat
import assertk.assertions.isEqualTo
import assertk.assertions.isNotNull
import assertk.assertions.isNull
import com.fsck.k9.RobolectricTest
import com.fsck.k9.mail.AuthType
import com.fsck.k9.mail.ConnectionSecurity
import com.fsck.k9.oauth.OAuthConfiguration
import com.fsck.k9.oauth.OAuthConfigurationProvider
import org.junit.Test
class ProvidersXmlDiscoveryTest : RobolectricTest() {
@ -46,19 +46,22 @@ class ProvidersXmlDiscoveryTest : RobolectricTest() {
}
private fun createOAuthConfigurationProvider(): OAuthConfigurationProvider {
val googleConfig = OAuthConfiguration(
clientId = "irrelevant",
scopes = listOf("irrelevant"),
authorizationEndpoint = "irrelevant",
tokenEndpoint = "irrelevant",
redirectUri = "irrelevant",
)
return OAuthConfigurationProvider(
configurations = mapOf(
listOf("imap.gmail.com", "smtp.gmail.com") to googleConfig,
),
googleConfiguration = googleConfig,
)
return object : OAuthConfigurationProvider {
override fun getConfiguration(hostname: String): OAuthConfiguration? {
return when (hostname) {
"imap.gmail.com" -> oAuthConfiguration
"smtp.gmail.com" -> oAuthConfiguration
else -> null
}
}
}
}
private val oAuthConfiguration = OAuthConfiguration(
clientId = "irrelevant",
scopes = listOf("irrelevant"),
authorizationEndpoint = "irrelevant",
tokenEndpoint = "irrelevant",
redirectUri = "irrelevant",
)
}

View file

@ -52,6 +52,7 @@ include(
":core:common",
":core:testing",
":core:android:common",
":core:android:testing",
":core:ui:compose:common",
":core:ui:compose:designsystem",
":core:ui:compose:theme",