From 0ae473e27f6a20b6599616a72140b429775efca7 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Fri, 2 Sep 2022 00:13:14 -0500 Subject: [PATCH] Use http client factory for caldav and etesync --- .../org/tasks/caldav/CaldavClientProvider.kt | 62 ++++++------------- .../tasks/etebase/EtebaseClientProvider.kt | 32 ++-------- .../java/org/tasks/http/HttpClientFactory.kt | 62 ++++++++++--------- .../org/tasks/injection/ApplicationModule.kt | 6 ++ .../org/tasks/location/GeocoderNominatim.kt | 2 +- .../org/tasks/location/PlaceSearchGoogle.kt | 3 +- 6 files changed, 66 insertions(+), 101 deletions(-) diff --git a/app/src/main/java/org/tasks/caldav/CaldavClientProvider.kt b/app/src/main/java/org/tasks/caldav/CaldavClientProvider.kt index 644482711..c5ba8cc30 100644 --- a/app/src/main/java/org/tasks/caldav/CaldavClientProvider.kt +++ b/app/src/main/java/org/tasks/caldav/CaldavClientProvider.kt @@ -1,33 +1,25 @@ package org.tasks.caldav import android.content.Context -import at.bitfire.cert4android.CustomCertManager import at.bitfire.dav4jvm.BasicDigestAuthHandler import dagger.hilt.android.qualifiers.ApplicationContext -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext import okhttp3.Authenticator import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.Interceptor import okhttp3.OkHttpClient -import okhttp3.internal.tls.OkHostnameVerifier -import org.tasks.DebugNetworkInterceptor import org.tasks.R import org.tasks.billing.Inventory import org.tasks.data.CaldavAccount -import org.tasks.http.UserAgentInterceptor -import org.tasks.preferences.Preferences +import org.tasks.http.HttpClientFactory import org.tasks.security.KeyStoreEncryption import java.util.concurrent.TimeUnit import javax.inject.Inject -import javax.net.ssl.SSLContext class CaldavClientProvider @Inject constructor( @ApplicationContext private val context: Context, private val encryption: KeyStoreEncryption, - private val preferences: Preferences, - private val interceptor: DebugNetworkInterceptor, - private val inventory: Inventory + private val inventory: Inventory, + private val httpClientFactory: HttpClientFactory, ) { private val tasksUrl = context.getString(R.string.tasks_caldav_url) @@ -37,11 +29,12 @@ class CaldavClientProvider @Inject constructor( password: String? = null ): CaldavClient { val auth = getAuthInterceptor(username, password, url) - val customCertManager = newCertManager() - customCertManager.appInForeground = true return CaldavClient( this, - createHttpClient(auth, customCertManager), + createHttpClient( + auth = auth, + foreground = true + ), url?.toHttpUrlOrNull() ) } @@ -59,8 +52,7 @@ class CaldavClientProvider @Inject constructor( account.getPassword(encryption), account.url ) - val customCertManager = newCertManager() - val client = createHttpClient(auth, customCertManager) + val client = createHttpClient(auth) return if (account.isTasksOrg) { TasksClient(this, client, url?.toHttpUrlOrNull()) } else { @@ -68,10 +60,6 @@ class CaldavClientProvider @Inject constructor( } } - private suspend fun newCertManager() = withContext(Dispatchers.Default) { - CustomCertManager(context) - } - private fun getAuthInterceptor( username: String?, password: String?, @@ -82,31 +70,21 @@ class CaldavClientProvider @Inject constructor( else -> BasicDigestAuthHandler(null, username, password) } - private fun createHttpClient(auth: Interceptor?, customCertManager: CustomCertManager): OkHttpClient { - val hostnameVerifier = customCertManager.hostnameVerifier(OkHostnameVerifier) - val sslContext = SSLContext.getInstance("TLS") - sslContext.init(null, arrayOf(customCertManager), null) - val builder = OkHttpClient() - .newBuilder() - .cookieJar(MemoryCookieStore()) - .followRedirects(false) - .followSslRedirects(true) - .sslSocketFactory(sslContext.socketFactory, customCertManager) - .hostnameVerifier(hostnameVerifier) + private suspend fun createHttpClient( + auth: Interceptor?, + foreground: Boolean = false, + ): OkHttpClient { + return httpClientFactory.newClient(foreground = foreground) { builder -> + builder .connectTimeout(15, TimeUnit.SECONDS) .writeTimeout(30, TimeUnit.SECONDS) .readTimeout(120, TimeUnit.SECONDS) - .addNetworkInterceptor(UserAgentInterceptor) - auth?.let { - builder.addNetworkInterceptor(it) - if (it is Authenticator) { - builder.authenticator(it) - } + auth?.let { + builder.addNetworkInterceptor(it) + if (it is Authenticator) { + builder.authenticator(it) + } + } } - if (preferences.isFlipperEnabled) { - interceptor.apply(builder) - } - - return builder.build() } } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/etebase/EtebaseClientProvider.kt b/app/src/main/java/org/tasks/etebase/EtebaseClientProvider.kt index 87d721ed5..56c4aeabe 100644 --- a/app/src/main/java/org/tasks/etebase/EtebaseClientProvider.kt +++ b/app/src/main/java/org/tasks/etebase/EtebaseClientProvider.kt @@ -1,33 +1,26 @@ package org.tasks.etebase import android.content.Context -import at.bitfire.cert4android.CustomCertManager import com.etebase.client.Account import com.etebase.client.Client import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import okhttp3.OkHttpClient -import okhttp3.internal.tls.OkHostnameVerifier -import org.tasks.DebugNetworkInterceptor -import org.tasks.caldav.MemoryCookieStore import org.tasks.data.CaldavAccount import org.tasks.data.CaldavDao -import org.tasks.http.UserAgentInterceptor -import org.tasks.preferences.Preferences +import org.tasks.http.HttpClientFactory import org.tasks.security.KeyStoreEncryption import java.security.KeyManagementException import java.security.NoSuchAlgorithmException import java.util.concurrent.TimeUnit import javax.inject.Inject -import javax.net.ssl.SSLContext class EtebaseClientProvider @Inject constructor( @ApplicationContext private val context: Context, private val encryption: KeyStoreEncryption, - private val preferences: Preferences, - private val interceptor: DebugNetworkInterceptor, - private val caldavDao: CaldavDao + private val caldavDao: CaldavDao, + private val httpClientFactory: HttpClientFactory, ) { @Throws(NoSuchAlgorithmException::class, KeyManagementException::class) suspend fun forAccount(account: CaldavAccount): EtebaseClient = forUrl( @@ -47,26 +40,11 @@ class EtebaseClientProvider @Inject constructor( } private suspend fun createHttpClient(foreground: Boolean): OkHttpClient { - val customCertManager = withContext(Dispatchers.Default) { - CustomCertManager(context, foreground) - } - val hostnameVerifier = customCertManager.hostnameVerifier(OkHostnameVerifier) - val sslContext = SSLContext.getInstance("TLS") - sslContext.init(null, arrayOf(customCertManager), null) - val builder = OkHttpClient() - .newBuilder() - .addNetworkInterceptor(UserAgentInterceptor) - .cookieJar(MemoryCookieStore()) - .followRedirects(false) - .followSslRedirects(true) - .sslSocketFactory(sslContext.socketFactory, customCertManager) - .hostnameVerifier(hostnameVerifier) + return httpClientFactory.newClient(foreground = foreground) { builder -> + builder .connectTimeout(15, TimeUnit.SECONDS) .writeTimeout(30, TimeUnit.SECONDS) .readTimeout(120, TimeUnit.SECONDS) - if (preferences.isFlipperEnabled) { - interceptor.apply(builder) } - return builder.build() } } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/http/HttpClientFactory.kt b/app/src/main/java/org/tasks/http/HttpClientFactory.kt index 823e610b8..17e8a6da6 100644 --- a/app/src/main/java/org/tasks/http/HttpClientFactory.kt +++ b/app/src/main/java/org/tasks/http/HttpClientFactory.kt @@ -6,6 +6,7 @@ import at.bitfire.dav4jvm.BasicDigestAuthHandler import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import okhttp3.CookieJar import okhttp3.OkHttpClient import okhttp3.internal.tls.OkHostnameVerifier import org.tasks.DebugNetworkInterceptor @@ -19,45 +20,48 @@ class HttpClientFactory @Inject constructor( private val preferences: Preferences, private val interceptor: DebugNetworkInterceptor, private val encryption: KeyStoreEncryption, + private val cookieJar: CookieJar, ) { - - suspend fun newCertManager() = withContext(Dispatchers.Default) { - CustomCertManager(context) + suspend fun newClient( + foreground: Boolean = false, + username: String? = null, + encryptedPassword: String? = null + ): OkHttpClient { + val decrypted = encryptedPassword?.let { encryption.decrypt(it) } + return newClient(foreground = foreground) { builder -> + if (!username.isNullOrBlank() && !decrypted.isNullOrBlank()) { + val auth = BasicDigestAuthHandler(null, username, decrypted) + builder.addNetworkInterceptor(auth) + builder.authenticator(auth) + } + } } - suspend fun newBuilder( - foreground: Boolean = false, - username: String? = null, - encryptedPassword: String? = null - ): OkHttpClient.Builder = newBuilder( - newCertManager(), - foreground = foreground, - username = username, - password = encryptedPassword?.let { encryption.decrypt(it) } - ) - - fun newBuilder( - customCertManager: CustomCertManager, - foreground: Boolean = false, - username: String? = null, - password: String? = null - ): OkHttpClient.Builder { + suspend fun newClient( + foreground: Boolean = false, + block: (OkHttpClient.Builder) -> Unit = {} + ): OkHttpClient { + val customCertManager = withContext(Dispatchers.Default) { + CustomCertManager(context) + } customCertManager.appInForeground = foreground val hostnameVerifier = customCertManager.hostnameVerifier(OkHostnameVerifier) val sslContext = SSLContext.getInstance("TLS") sslContext.init(null, arrayOf(customCertManager), null) val builder = OkHttpClient() - .newBuilder() - .sslSocketFactory(sslContext.socketFactory, customCertManager) - .hostnameVerifier(hostnameVerifier) + .newBuilder() + .followRedirects(false) + .followSslRedirects(true) + .sslSocketFactory(sslContext.socketFactory, customCertManager) + .hostnameVerifier(hostnameVerifier) + .addInterceptor(UserAgentInterceptor) + .cookieJar(cookieJar) + + block(builder) + if (preferences.isFlipperEnabled) { interceptor.apply(builder) } - if (!username.isNullOrBlank() && !password.isNullOrBlank()) { - val auth = BasicDigestAuthHandler(null, username, password) - builder.addNetworkInterceptor(auth) - builder.authenticator(auth) - } - return builder + return builder.build() } } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/injection/ApplicationModule.kt b/app/src/main/java/org/tasks/injection/ApplicationModule.kt index 67f274c72..634d0a014 100644 --- a/app/src/main/java/org/tasks/injection/ApplicationModule.kt +++ b/app/src/main/java/org/tasks/injection/ApplicationModule.kt @@ -12,10 +12,12 @@ import dagger.hilt.components.SingletonComponent import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob +import okhttp3.CookieJar import org.tasks.analytics.Firebase import org.tasks.billing.BillingClient import org.tasks.billing.BillingClientImpl import org.tasks.billing.Inventory +import org.tasks.caldav.MemoryCookieStore import org.tasks.data.* import org.tasks.jobs.WorkManager import org.tasks.notifications.NotificationDao @@ -121,4 +123,8 @@ class ApplicationModule { @Provides fun providesNotificationManager(@ApplicationContext context: Context) = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + + @Provides + @Singleton + fun cookieJar(): CookieJar = MemoryCookieStore() } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/location/GeocoderNominatim.kt b/app/src/main/java/org/tasks/location/GeocoderNominatim.kt index 0d89919f5..b3205020c 100644 --- a/app/src/main/java/org/tasks/location/GeocoderNominatim.kt +++ b/app/src/main/java/org/tasks/location/GeocoderNominatim.kt @@ -25,7 +25,7 @@ class GeocoderNominatim @Inject constructor( override suspend fun reverseGeocode(mapPosition: MapPosition): Place? = withContext(Dispatchers.IO) { - val client = httpClientFactory.newBuilder(foreground = true).build() + val client = httpClientFactory.newClient(foreground = true) val url = "$url/reverse?format=geocodejson&lat=${mapPosition.latitude}&lon=${mapPosition.longitude}" val response = client.newCall( Request.Builder().get().url(url).addHeader(USER_AGENT, UA_VALUE).build() diff --git a/app/src/main/java/org/tasks/location/PlaceSearchGoogle.kt b/app/src/main/java/org/tasks/location/PlaceSearchGoogle.kt index 08e6ebbfa..4e971fbba 100644 --- a/app/src/main/java/org/tasks/location/PlaceSearchGoogle.kt +++ b/app/src/main/java/org/tasks/location/PlaceSearchGoogle.kt @@ -68,12 +68,11 @@ class PlaceSearchGoogle @Inject constructor( context.getString(R.string.tasks_org_account_required) ) val client = httpClientFactory - .newBuilder( + .newClient( foreground = true, username = account.username, encryptedPassword = account.password ) - .build() val response = client.newCall(Request.Builder().get().url(url).build()).execute() if (response.isSuccessful) { response.body?.string()?.toJson()?.apply { checkResult(this) }