OAuth: Synchronize access token generation (#1547)

* OAuthInterceptor: synchronize refreshing of access token

* Remove sensitive logging

* [WIP] Logging

* [Google] Support custom client ID on re-authorization

* Statically synchronize acquisition of access token

* [WIP] OAuth: use callbacks for reading/writing AuthState

* Fix DavResourceFinderTest

* Move Credentials class to settings package; KDoc

* Simplify reauthorization
This commit is contained in:
Ricki Hirner authored and GitHub committed 2025-06-27 11:24:02 +02:00
1 parent 789e7f3045
commit 4246ed65ac
25 files changed
+190 -162

No files matched your search

@@ -8,9 +8,9 @@ import android.security.NetworkSecurityPolicy
import at.bitfire.dav4jvm.DavResource
import at.bitfire.dav4jvm.property.carddav.AddressbookHomeSet
import at.bitfire.dav4jvm.property.webdav.ResourceType
import at.bitfire.davdroid.db.Credentials
import at.bitfire.davdroid.network.HttpClient
import at.bitfire.davdroid.servicedetection.DavResourceFinder.Configuration.ServiceInfo
import at.bitfire.davdroid.settings.Credentials
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import okhttp3.mockwebserver.Dispatcher
@@ -72,7 +72,7 @@ class DavResourceFinderTest {
val credentials = Credentials(username = "mock", password = "12345".toCharArray())
client = httpClientBuilder
.authenticate(host = null, credentials = credentials)
.authenticate(host = null, getCredentials = { credentials })
.build()
Assume.assumeTrue(NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted)
@@ -4,7 +4,7 @@
package at.bitfire.davdroid.webdav
import at.bitfire.davdroid.db.Credentials
import at.bitfire.davdroid.settings.Credentials
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import org.junit.Assert.assertEquals
@@ -1,72 +0,0 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.network
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.runBlocking
import net.openid.appauth.AuthState
import net.openid.appauth.AuthorizationException
import net.openid.appauth.AuthorizationService
import okhttp3.Interceptor
import okhttp3.Response
import java.util.logging.Level
import java.util.logging.Logger
/**
* Sends an OAuth Bearer token authorization as described in RFC 6750.
*/
class BearerAuthInterceptor(
private val accessToken: String
): Interceptor {
companion object {
val logger: Logger
get() = Logger.getGlobal()
fun fromAuthState(authService: AuthorizationService, authState: AuthState, callback: AuthStateUpdateCallback? = null): BearerAuthInterceptor? {
return runBlocking {
val accessTokenFuture = CompletableDeferred<String>()
authState.performActionWithFreshTokens(authService) { accessToken: String?, _: String?, ex: AuthorizationException? ->
if (accessToken != null) {
// persist updated AuthState
callback?.onUpdate(authState)
// emit access token
accessTokenFuture.complete(accessToken)
}
else {
logger.log(Level.WARNING, "Couldn't obtain access token", ex)
accessTokenFuture.cancel()
}
}
// return value
try {
BearerAuthInterceptor(accessTokenFuture.await())
} catch (ignored: CancellationException) {
null
}
}
}
}
override fun intercept(chain: Interceptor.Chain): Response {
logger.finer("Authenticating request with access token")
val rq = chain.request().newBuilder()
.header("Authorization", "Bearer $accessToken")
.build()
return chain.proceed(rq)
}
fun interface AuthStateUpdateCallback {
fun onUpdate(authState: AuthState)
}
}
@@ -10,9 +10,9 @@ import androidx.annotation.WorkerThread
import at.bitfire.cert4android.CustomCertManager
import at.bitfire.dav4jvm.BasicDigestAuthHandler
import at.bitfire.dav4jvm.UrlUtils
import at.bitfire.davdroid.db.Credentials
import at.bitfire.davdroid.di.IoDispatcher
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.settings.Credentials
import at.bitfire.davdroid.settings.Settings
import at.bitfire.davdroid.settings.SettingsManager
import at.bitfire.davdroid.ui.ForegroundTracker
@@ -21,7 +21,6 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import net.openid.appauth.AuthState
import net.openid.appauth.AuthorizationService
import okhttp3.Authenticator
import okhttp3.Cache
import okhttp3.ConnectionSpec
@@ -39,17 +38,14 @@ import java.util.concurrent.TimeUnit
import java.util.logging.Level
import java.util.logging.Logger
import javax.inject.Inject
import javax.inject.Provider
import javax.net.ssl.KeyManager
import javax.net.ssl.SSLContext
class HttpClient(
val okHttpClient: OkHttpClient,
private val authorizationService: AuthorizationService? = null
val okHttpClient: OkHttpClient
): AutoCloseable {
override fun close() {
authorizationService?.dispose()
okHttpClient.cache?.close()
}
@@ -66,11 +62,11 @@ class HttpClient(
*/
class Builder @Inject constructor(
private val accountSettingsFactory: AccountSettings.Factory,
private val authorizationServiceProvider: Provider<AuthorizationService>,
@ApplicationContext private val context: Context,
defaultLogger: Logger,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
private val keyManagerFactory: ClientCertKeyManager.Factory,
private val oAuthInterceptorFactory: OAuthInterceptor.Factory,
private val settingsManager: SettingsManager
) {
@@ -97,14 +93,22 @@ class HttpClient(
private var authenticationInterceptor: Interceptor? = null
private var authenticator: Authenticator? = null
private var authorizationService: AuthorizationService? = null
private var certificateAlias: String? = null
fun authenticate(host: String?, credentials: Credentials, authStateCallback: BearerAuthInterceptor.AuthStateUpdateCallback? = null): Builder {
fun authenticate(host: String?, getCredentials: () -> Credentials, updateAuthState: ((AuthState) -> Unit)? = null): Builder {
val credentials = getCredentials()
if (credentials.authState != null) {
// OAuth
val authService = authorizationServiceProvider.get()
authenticationInterceptor = BearerAuthInterceptor.fromAuthState(authService, credentials.authState, authStateCallback)
authorizationService = authService
authenticationInterceptor = oAuthInterceptorFactory.create(
readAuthState = {
// We don't use the "credentials" object from above because it may contain an outdated access token
// when readAuthState is called. Instead, we fetch the up-to-date auth-state.
getCredentials().authState
},
writeAuthState = { authState ->
updateAuthState?.invoke(authState)
}
)
} else if (credentials.username != null && credentials.password != null) {
// basic/digest auth
@@ -164,9 +168,11 @@ class HttpClient(
val accountSettings = accountSettingsFactory.create(account)
authenticate(
host = onlyHost,
credentials = accountSettings.credentials(),
authStateCallback = { authState: AuthState ->
accountSettings.credentials(Credentials(authState = authState))
getCredentials = {
accountSettings.credentials()
},
updateAuthState = { authState ->
accountSettings.updateAuthState(authState)
}
)
return this
@@ -231,10 +237,7 @@ class HttpClient(
okBuilder.addNetworkInterceptor(loggingInterceptor)
}
return HttpClient(
okHttpClient = okBuilder.build(),
authorizationService = authorizationService
)
return HttpClient(okBuilder.build())
}
private fun buildAuthentication(okBuilder: OkHttpClient.Builder) {
@@ -6,7 +6,7 @@ package at.bitfire.davdroid.network
import at.bitfire.dav4jvm.exception.DavException
import at.bitfire.dav4jvm.exception.HttpException
import at.bitfire.davdroid.db.Credentials
import at.bitfire.davdroid.settings.Credentials
import at.bitfire.davdroid.ui.setup.LoginInfo
import at.bitfire.davdroid.util.withTrailingSlash
import at.bitfire.vcard4android.GroupMethod
@@ -9,19 +9,14 @@ import android.content.Intent
import androidx.activity.result.contract.ActivityResultContract
import androidx.core.net.toUri
import at.bitfire.davdroid.BuildConfig
import at.bitfire.davdroid.db.Credentials
import at.bitfire.davdroid.network.OAuthIntegration.redirectUri
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import net.openid.appauth.AuthState
import net.openid.appauth.AuthorizationException
import net.openid.appauth.AuthorizationRequest
import net.openid.appauth.AuthorizationResponse
import net.openid.appauth.AuthorizationService
import net.openid.appauth.AuthorizationServiceConfiguration
import net.openid.appauth.TokenResponse
import java.util.Locale
/**
* Integration with OpenID AppAuth (Android)
@@ -38,42 +33,20 @@ object OAuthIntegration {
* @param authService authorization service
* @param authResponse response from the server (coming over the Intent from the browser / [AuthorizationContract])
*/
suspend fun authenticate(authService: AuthorizationService, authResponse: AuthorizationResponse): Credentials {
suspend fun authenticate(authService: AuthorizationService, authResponse: AuthorizationResponse): AuthState {
val authState = AuthState(authResponse, null) // authorization code must not be stored; exchange it to refresh token
val credentials = CompletableDeferred<Credentials>()
val authStateFuture = CompletableDeferred<AuthState>()
withContext(Dispatchers.IO) {
authService.performTokenRequest(authResponse.createTokenExchangeRequest()) { tokenResponse: TokenResponse?, refreshTokenException: AuthorizationException? ->
if (tokenResponse != null) {
// success, save authState (= refresh token)
authState.update(tokenResponse, refreshTokenException)
credentials.complete(Credentials(authState = authState))
} else if (refreshTokenException != null)
credentials.completeExceptionally(refreshTokenException)
}
authService.performTokenRequest(authResponse.createTokenExchangeRequest()) { tokenResponse: TokenResponse?, refreshTokenException: AuthorizationException? ->
if (tokenResponse != null) {
// success, save authState (= refresh token)
authState.update(tokenResponse, refreshTokenException)
authStateFuture.complete(authState)
} else if (refreshTokenException != null)
authStateFuture.completeExceptionally(refreshTokenException)
}
return credentials.await()
}
/**
* Creates a new authorization request from a known configuration. Typically used to re-authorize
* from a given configuration.
*
* @param authConfig current authorization config that shall be replaced
* @return authorization request, or `null` if the current config doesn't contain a known provider
*/
fun newAuthorizeRequest(authConfig: AuthorizationServiceConfiguration): AuthorizationRequest? {
val authHost = authConfig.authorizationEndpoint.host.toString()
val locale = Locale.getDefault().toLanguageTag()
// If more OAuth providers become added, this should be rewritten so that all providers
// are checked automatically.
return when {
authHost.contains("fastmail.com") -> OAuthFastmail.signIn(null, locale)
authHost.contains("google.com") -> OAuthGoogle.signIn(null, null, locale)
else -> return null
}
return authStateFuture.await()
}
@@ -0,0 +1,106 @@
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.network
import at.bitfire.davdroid.BuildConfig
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import net.openid.appauth.AuthState
import net.openid.appauth.AuthorizationException
import net.openid.appauth.AuthorizationService
import okhttp3.Interceptor
import okhttp3.Response
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CompletionException
import java.util.logging.Level
import java.util.logging.Logger
import javax.inject.Provider
/**
* Sends an OAuth Bearer token authorization as described in RFC 6750.
*
* @param readAuthState callback that fetches an up-to-date authorization state
* @param writeAuthState callback that persists a new authorization state
*/
class OAuthInterceptor @AssistedInject constructor(
@Assisted private val readAuthState: () -> AuthState?,
@Assisted private val writeAuthState: (AuthState) -> Unit,
private val authServiceProvider: Provider<AuthorizationService>,
private val logger: Logger
): Interceptor {
@AssistedFactory
interface Factory {
fun create(readAuthState: () -> AuthState?, writeAuthState: (AuthState) -> Unit): OAuthInterceptor
}
override fun intercept(chain: Interceptor.Chain): Response {
val rq = chain.request().newBuilder()
/** Syntax for the "Authorization" header [RFC 6750 2.1]:
*
* b64token = 1*( ALPHA / DIGIT /
* "-" / "." / "_" / "~" / "+" / "/" ) *"="
* credentials = "Bearer" 1*SP b64token
*/
val accessToken = provideAccessToken()
if (accessToken != null)
rq.header("Authorization", "Bearer $accessToken")
else
logger.severe("No access token available, won't authenticate")
return chain.proceed(rq.build())
}
/**
* Provides a fresh access token for authorization. Uses the current one if it's still valid,
* or requests a new one if necessary.
*
* This method is synchronized / thread-safe so that it can be called for multiple HTTP requests at the same time.
*
* @return access token or `null` if no valid access token is available (usually because of an error during refresh)
*/
fun provideAccessToken(): String? = synchronized(javaClass) {
// if possible, use cached access token
val authState = readAuthState() ?: return null
if (authState.isAuthorized && authState.accessToken != null && !authState.needsTokenRefresh) {
if (BuildConfig.DEBUG) // log sensitive information (refresh/access token) only in debug builds
logger.log(Level.FINEST, "Using cached AuthState", authState.jsonSerializeString())
return authState.accessToken
}
// request fresh access token
logger.fine("Requesting fresh access token")
val accessTokenFuture = CompletableFuture<String>()
val authService = authServiceProvider.get()
try {
authState.performActionWithFreshTokens(authService) { accessToken: String?, _: String?, ex: AuthorizationException? ->
// appauth internally fetches the new token over HttpURLConnection in an AsyncTask
if (BuildConfig.DEBUG)
logger.log(Level.FINEST, "Got new AuthState", authState.jsonSerializeString())
// persist updated AuthState
writeAuthState(authState)
if (ex != null)
accessTokenFuture.completeExceptionally(ex)
else if (accessToken != null)
accessTokenFuture.complete(accessToken)
}
accessTokenFuture.join()
} catch (e: CompletionException) {
logger.log(Level.SEVERE, "Couldn't obtain access token", e.cause)
null
} finally {
authService.dispose()
}
}
}
@@ -19,6 +19,12 @@ import java.net.URL
@InstallIn(SingletonComponent::class)
object OAuthModule {
/**
* Make sure to call [AuthorizationService.dispose] when obtaining an instance.
*
* Creating an instance is expensive (involves CustomTabsManager), so don't create an
* instance if not necessary (use Provider/Lazy).
*/
@Provides
fun authorizationService(@ApplicationContext context: Context): AuthorizationService =
AuthorizationService(context,
@@ -9,7 +9,6 @@ import android.accounts.AccountManager
import android.accounts.OnAccountsUpdateListener
import android.content.Context
import at.bitfire.davdroid.R
import at.bitfire.davdroid.db.Credentials
import at.bitfire.davdroid.db.HomeSet
import at.bitfire.davdroid.db.Service
import at.bitfire.davdroid.db.ServiceType
@@ -18,6 +17,7 @@ import at.bitfire.davdroid.resource.LocalCalendarStore
import at.bitfire.davdroid.servicedetection.DavResourceFinder
import at.bitfire.davdroid.servicedetection.RefreshCollectionsWorker
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.settings.Credentials
import at.bitfire.davdroid.sync.AutomaticSyncManager
import at.bitfire.davdroid.sync.SyncDataType
import at.bitfire.davdroid.sync.TasksAppManager
@@ -27,10 +27,10 @@ import at.bitfire.dav4jvm.property.webdav.DisplayName
import at.bitfire.dav4jvm.property.webdav.HrefListProperty
import at.bitfire.dav4jvm.property.webdav.ResourceType
import at.bitfire.davdroid.db.Collection
import at.bitfire.davdroid.db.Credentials
import at.bitfire.davdroid.log.StringHandler
import at.bitfire.davdroid.network.DnsRecordResolver
import at.bitfire.davdroid.network.HttpClient
import at.bitfire.davdroid.settings.Credentials
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -88,7 +88,7 @@ class DavResourceFinder @AssistedInject constructor(
if (credentials != null)
authenticate(
host = null,
credentials = credentials
getCredentials = { credentials }
)
}
.build()
@@ -11,7 +11,6 @@ import android.os.Looper
import androidx.annotation.WorkerThread
import androidx.core.os.bundleOf
import at.bitfire.davdroid.R
import at.bitfire.davdroid.db.Credentials
import at.bitfire.davdroid.settings.AccountSettings.Companion.CREDENTIALS_LOCK
import at.bitfire.davdroid.settings.AccountSettings.Companion.CREDENTIALS_LOCK_AT_LOGIN_AND_SETTINGS
import at.bitfire.davdroid.settings.migration.AccountSettingsMigration
@@ -125,7 +124,13 @@ class AccountSettings @AssistedInject constructor(
accountManager.setAndVerifyUserData(account, KEY_CERTIFICATE_ALIAS, credentials.certificateAlias)
// OAuth
accountManager.setAndVerifyUserData(account, KEY_AUTH_STATE, credentials.authState?.jsonSerializeString())
credentials.authState?.let { authState ->
updateAuthState(authState)
}
}
fun updateAuthState(authState: AuthState) {
accountManager.setAndVerifyUserData(account, KEY_AUTH_STATE, authState.jsonSerializeString())
}
/**
@@ -2,16 +2,26 @@
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.db
package at.bitfire.davdroid.settings
import net.openid.appauth.AuthState
/**
* Represents credentials that are used to authenticate against a CalDAV/CardDAV/WebDAV server.
*
* Note: [authState] can change from request to request, so make sure that you have an up-to-date
* copy when using it.
*/
data class Credentials(
/** username for Basic / Digest auth */
val username: String? = null,
/** password for Basic / Digest auth */
val password: CharArray? = null,
/** alias of an client certificate that is present on the system */
val certificateAlias: String? = null,
/** OAuth authorization state */
val authState: AuthState? = null
) {
@@ -26,7 +36,7 @@ data class Credentials(
if (certificateAlias != null)
s += "certificateAlias=$certificateAlias"
if (authState != null)
if (authState != null) // contains sensitive information (refresh token, access token)
s += "authState=${authState.jsonSerializeString()}"
return "Credentials(" + s.joinToString(", ") + ")"
@@ -10,11 +10,11 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import at.bitfire.davdroid.R
import at.bitfire.davdroid.db.AppDatabase
import at.bitfire.davdroid.db.Credentials
import at.bitfire.davdroid.db.Service
import at.bitfire.davdroid.di.DefaultDispatcher
import at.bitfire.davdroid.network.OAuthIntegration
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.settings.Credentials
import at.bitfire.davdroid.settings.SettingsManager
import at.bitfire.davdroid.sync.ResyncType
import at.bitfire.davdroid.sync.SyncDataType
@@ -185,20 +185,15 @@ class AccountSettingsModel @AssistedInject constructor(
fun authorizationContract() = OAuthIntegration.AuthorizationContract(authService)
fun newAuthorizationRequest(): AuthorizationRequest? {
val authState = accountSettings.credentials().authState ?: return null
// create new authorization request
val authConfig = authState.authorizationServiceConfiguration ?: return null
return OAuthIntegration.newAuthorizeRequest(authConfig)
}
fun newAuthorizationRequest(): AuthorizationRequest? =
accountSettings.credentials().authState?.lastAuthorizationResponse?.request
fun authenticate(authResponse: AuthorizationResponse) {
CoroutineScope(defaultDispatcher).launch {
try {
// save new credentials
val credentials = OAuthIntegration.authenticate(authService, authResponse)
accountSettings.credentials(credentials)
val authState = OAuthIntegration.authenticate(authService, authResponse)
accountSettings.updateAuthState(authState)
_uiState.update {
it.copy(status = context.getString(R.string.settings_reauthorize_oauth_success))
@@ -57,7 +57,7 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import at.bitfire.davdroid.Constants
import at.bitfire.davdroid.R
import at.bitfire.davdroid.db.Credentials
import at.bitfire.davdroid.settings.Credentials
import at.bitfire.davdroid.ui.AppTheme
import at.bitfire.davdroid.ui.composable.ActionCard
import at.bitfire.davdroid.ui.composable.EditTextInputDialog
@@ -8,7 +8,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import at.bitfire.davdroid.db.Credentials
import at.bitfire.davdroid.settings.Credentials
import at.bitfire.davdroid.util.DavUtils.toURIorNull
import at.bitfire.davdroid.util.trimToNull
import dagger.assisted.Assisted
@@ -8,7 +8,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import at.bitfire.davdroid.db.Credentials
import at.bitfire.davdroid.settings.Credentials
import at.bitfire.davdroid.util.DavUtils.toURIorNull
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -13,6 +13,7 @@ import androidx.lifecycle.viewModelScope
import at.bitfire.davdroid.R
import at.bitfire.davdroid.network.OAuthFastmail
import at.bitfire.davdroid.network.OAuthIntegration
import at.bitfire.davdroid.settings.Credentials
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -85,7 +86,7 @@ class FastmailLoginModel @AssistedInject constructor(
fun authenticate(authResponse: AuthorizationResponse) {
viewModelScope.launch {
try {
val credentials = OAuthIntegration.authenticate(authService, authResponse)
val credentials = Credentials(authState = OAuthIntegration.authenticate(authService, authResponse))
// success, provide login info to continue
uiState = uiState.copy(
@@ -14,6 +14,7 @@ import androidx.lifecycle.viewModelScope
import at.bitfire.davdroid.R
import at.bitfire.davdroid.network.OAuthGoogle
import at.bitfire.davdroid.network.OAuthIntegration
import at.bitfire.davdroid.settings.Credentials
import at.bitfire.davdroid.util.trimToNull
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -93,7 +94,7 @@ class GoogleLoginModel @AssistedInject constructor(
fun authenticate(authResponse: AuthorizationResponse) {
viewModelScope.launch {
try {
val credentials = OAuthIntegration.authenticate(authService, authResponse)
val credentials = Credentials(authState = OAuthIntegration.authenticate(authService, authResponse))
// success, provide login info to continue
uiState = uiState.copy(
@@ -8,7 +8,7 @@ import android.content.Intent
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import at.bitfire.davdroid.db.Credentials
import at.bitfire.davdroid.settings.Credentials
import at.bitfire.davdroid.ui.account.AccountActivity
import dagger.hilt.android.AndroidEntryPoint
import java.net.URI
@@ -4,7 +4,7 @@
package at.bitfire.davdroid.ui.setup
import at.bitfire.davdroid.db.Credentials
import at.bitfire.davdroid.settings.Credentials
import at.bitfire.vcard4android.GroupMethod
import java.net.URI
@@ -8,7 +8,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import at.bitfire.davdroid.db.Credentials
import at.bitfire.davdroid.settings.Credentials
import at.bitfire.davdroid.util.DavUtils.toURIorNull
import at.bitfire.davdroid.util.trimToNull
import dagger.assisted.Assisted
@@ -12,7 +12,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import at.bitfire.davdroid.R
import at.bitfire.davdroid.db.AppDatabase
import at.bitfire.davdroid.db.Credentials
import at.bitfire.davdroid.settings.Credentials
import at.bitfire.davdroid.util.trimToNull
import at.bitfire.davdroid.webdav.WebDavMountRepository
import dagger.hilt.android.lifecycle.HiltViewModel
@@ -9,7 +9,7 @@ import androidx.annotation.StringDef
import androidx.core.content.edit
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import at.bitfire.davdroid.db.Credentials
import at.bitfire.davdroid.settings.Credentials
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
@@ -773,7 +773,7 @@ class DavDocumentsProvider(
)
credentialsStore.getCredentials(mountId)?.let { credentials ->
builder.authenticate(host = null, credentials = credentials)
builder.authenticate(host = null, getCredentials = { credentials })
}
return builder.build()
@@ -10,10 +10,10 @@ import androidx.annotation.VisibleForTesting
import at.bitfire.dav4jvm.DavResource
import at.bitfire.davdroid.R
import at.bitfire.davdroid.db.AppDatabase
import at.bitfire.davdroid.db.Credentials
import at.bitfire.davdroid.db.WebDavMount
import at.bitfire.davdroid.di.IoDispatcher
import at.bitfire.davdroid.network.HttpClient
import at.bitfire.davdroid.settings.Credentials
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.delay
@@ -122,7 +122,7 @@ class WebDavMountRepository @Inject constructor(
if (credentials != null)
builder.authenticate(
host = null,
credentials = credentials
getCredentials = { credentials }
)
var supported = false