mirror of
https://github.com/bitfireAT/davx5-ose.git
synced 2026-08-07 05:44:54 -04:00
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:
1 parent
789e7f3045
commit
4246ed65ac
25 files changed
+190
-162
No files matched your search
+2
-2
@@ -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())
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+12
-2
@@ -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
|
||||
|
||||
Reference in new issue
Block a user