From 06a9b129d0534a06efa86dc1955fe977654f72ba Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 23 Aug 2023 11:55:05 +0200 Subject: [PATCH] Restore OIDC support. --- docs/oidc.md | 4 ++ .../ConfirmAccountProviderPresenter.kt | 45 +++++++++++++- .../login/impl/util/LoginConstants.kt | 2 +- .../ConfirmAccountProviderPresenterTest.kt | 59 ++++++++++--------- .../logout/api/LogoutPreferenceScreen.kt | 4 +- .../logout/api/LogoutPreferenceState.kt | 2 +- .../impl/DefaultLogoutPreferencePresenter.kt | 4 +- .../impl/root/PreferencesRootNode.kt | 15 ++++- .../impl/root/PreferencesRootView.kt | 3 + .../libraries/matrix/api/MatrixClient.kt | 7 ++- .../api/auth/AuthenticationException.kt | 3 +- .../libraries/matrix/impl/RustMatrixClient.kt | 29 +++++---- .../matrix/impl/RustMatrixClientFactory.kt | 1 + .../impl/auth/AuthenticationException.kt | 7 +-- .../matrix/impl/auth/HomeserverDetails.kt | 2 +- .../libraries/matrix/impl/auth/OidcConfig.kt | 18 +++--- .../auth/RustMatrixAuthenticationService.kt | 35 ++++------- .../libraries/matrix/test/FakeMatrixClient.kt | 3 +- .../sessionstorage/api/SessionData.kt | 1 + .../sessionstorage/impl/SessionDataMapper.kt | 2 + .../libraries/matrix/session/SessionData.sq | 3 +- .../impl/src/main/sqldelight/migrations/2.sqm | 1 + .../impl/DatabaseSessionStoreTests.kt | 1 + 23 files changed, 164 insertions(+), 87 deletions(-) create mode 100644 libraries/session-storage/impl/src/main/sqldelight/migrations/2.sqm diff --git a/docs/oidc.md b/docs/oidc.md index 5f9e70268d..0e4ad44852 100644 --- a/docs/oidc.md +++ b/docs/oidc.md @@ -45,3 +45,7 @@ state: ex6mNJVFZ5jn9wL8 Oidc client example: https://github.com/matrix-org/matrix-rust-sdk/blob/39ad8a46801fb4317a777ebf895822b3675b709c/examples/oidc_cli/src/main.rs Oidc sdk doc: https://github.com/matrix-org/matrix-rust-sdk/blob/39ad8a46801fb4317a777ebf895822b3675b709c/crates/matrix-sdk/src/oidc.rs + + +Test server: +synapse-oidc.lab.element.dev diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt index 1a021ad605..61ef4d724b 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt @@ -17,6 +17,7 @@ package io.element.android.features.login.impl.screens.confirmaccountprovider import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -26,8 +27,11 @@ import androidx.compose.runtime.rememberCoroutineScope import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import io.element.android.features.login.api.oidc.OidcAction +import io.element.android.features.login.impl.DefaultLoginUserStory import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource import io.element.android.features.login.impl.error.ChangeServerError +import io.element.android.features.login.impl.oidc.customtab.DefaultOidcActionFlow import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState @@ -40,7 +44,9 @@ import java.net.URL class ConfirmAccountProviderPresenter @AssistedInject constructor( @Assisted private val params: Params, private val accountProviderDataSource: AccountProviderDataSource, - private val authenticationService: MatrixAuthenticationService + private val authenticationService: MatrixAuthenticationService, + private val defaultOidcActionFlow: DefaultOidcActionFlow, + private val defaultLoginUserStory: DefaultLoginUserStory, ) : Presenter { data class Params( @@ -61,6 +67,14 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor( mutableStateOf(Async.Uninitialized) } + LaunchedEffect(Unit) { + launch { + defaultOidcActionFlow.collect { + onOidcAction(it, loginFlowAction) + } + } + } + fun handleEvents(event: ConfirmAccountProviderEvents) { when (event) { ConfirmAccountProviderEvents.Continue -> { @@ -97,4 +111,33 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor( }.getOrThrow() }.runCatchingUpdatingState(loginFlowAction, errorTransform = ChangeServerError::from) } + + private suspend fun onOidcAction( + oidcAction: OidcAction?, + loginFlowAction: MutableState>, + ) { + oidcAction ?: return + loginFlowAction.value = Async.Loading() + when (oidcAction) { + OidcAction.GoBack -> { + authenticationService.cancelOidcLogin() + .onSuccess { + loginFlowAction.value = Async.Uninitialized + } + .onFailure { failure -> + loginFlowAction.value = Async.Failure(failure) + } + } + is OidcAction.Success -> { + authenticationService.loginWithOidc(oidcAction.url) + .onSuccess { _ -> + defaultLoginUserStory.setLoginFlowIsDone(true) + } + .onFailure { failure -> + loginFlowAction.value = Async.Failure(failure) + } + } + } + defaultOidcActionFlow.reset() + } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/util/LoginConstants.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/util/LoginConstants.kt index e8bcea990e..152b1a094c 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/util/LoginConstants.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/util/LoginConstants.kt @@ -21,7 +21,7 @@ import io.element.android.features.login.impl.accountprovider.AccountProvider object LoginConstants { const val MATRIX_ORG_URL = "matrix.org" - const val DEFAULT_HOMESERVER_URL = "matrix.org" // TODO Oidc "synapse-oidc.lab.element.dev" + const val DEFAULT_HOMESERVER_URL = "matrix.org" const val SLIDING_SYNC_READ_MORE_URL = "https://github.com/matrix-org/sliding-sync/blob/main/docs/Landing.md" } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt index 76a3ad3d22..73f98a2667 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt @@ -20,9 +20,12 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat +import io.element.android.features.login.impl.DefaultLoginUserStory import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource +import io.element.android.features.login.impl.oidc.customtab.DefaultOidcActionFlow import io.element.android.features.login.impl.util.defaultAccountProvider import io.element.android.libraries.architecture.Async +import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.test.A_HOMESERVER import io.element.android.libraries.matrix.test.A_HOMESERVER_OIDC import io.element.android.libraries.matrix.test.A_THROWABLE @@ -33,11 +36,7 @@ import org.junit.Test class ConfirmAccountProviderPresenterTest { @Test fun `present - initial test`() = runTest { - val presenter = ConfirmAccountProviderPresenter( - ConfirmAccountProviderPresenter.Params(isAccountCreation = false), - AccountProviderDataSource(), - FakeAuthenticationService(), - ) + val presenter = createConfirmAccountProviderPresenter() moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -51,13 +50,11 @@ class ConfirmAccountProviderPresenterTest { @Test fun `present - continue password login`() = runTest { - val authServer = FakeAuthenticationService() - val presenter = ConfirmAccountProviderPresenter( - ConfirmAccountProviderPresenter.Params(isAccountCreation = false), - AccountProviderDataSource(), - authServer, + val authenticationService = FakeAuthenticationService() + val presenter = createConfirmAccountProviderPresenter( + matrixAuthenticationService = authenticationService, ) - authServer.givenHomeserver(A_HOMESERVER) + authenticationService.givenHomeserver(A_HOMESERVER) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -75,13 +72,11 @@ class ConfirmAccountProviderPresenterTest { @Test fun `present - continue oidc`() = runTest { - val authServer = FakeAuthenticationService() - val presenter = ConfirmAccountProviderPresenter( - ConfirmAccountProviderPresenter.Params(isAccountCreation = false), - AccountProviderDataSource(), - authServer, + val authenticationService = FakeAuthenticationService() + val presenter = createConfirmAccountProviderPresenter( + matrixAuthenticationService = authenticationService, ) - authServer.givenHomeserver(A_HOMESERVER_OIDC) + authenticationService.givenHomeserver(A_HOMESERVER_OIDC) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -99,17 +94,15 @@ class ConfirmAccountProviderPresenterTest { @Test fun `present - submit fails`() = runTest { - val authServer = FakeAuthenticationService() - val presenter = ConfirmAccountProviderPresenter( - ConfirmAccountProviderPresenter.Params(isAccountCreation = false), - AccountProviderDataSource(), - authServer, + val authenticationService = FakeAuthenticationService() + val presenter = createConfirmAccountProviderPresenter( + matrixAuthenticationService = authenticationService, ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() - authServer.givenChangeServerError(Throwable()) + authenticationService.givenChangeServerError(Throwable()) initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue) skipItems(1) // Loading val failureState = awaitItem() @@ -121,10 +114,8 @@ class ConfirmAccountProviderPresenterTest { @Test fun `present - clear error`() = runTest { val authenticationService = FakeAuthenticationService() - val presenter = ConfirmAccountProviderPresenter( - ConfirmAccountProviderPresenter.Params(isAccountCreation = false), - AccountProviderDataSource(), - authenticationService, + val presenter = createConfirmAccountProviderPresenter( + matrixAuthenticationService = authenticationService, ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -147,4 +138,18 @@ class ConfirmAccountProviderPresenterTest { assertThat(clearedState.loginFlow).isEqualTo(Async.Uninitialized) } } + + private fun createConfirmAccountProviderPresenter( + params: ConfirmAccountProviderPresenter.Params = ConfirmAccountProviderPresenter.Params(isAccountCreation = false), + accountProviderDataSource: AccountProviderDataSource = AccountProviderDataSource(), + matrixAuthenticationService: MatrixAuthenticationService = FakeAuthenticationService(), + defaultOidcActionFlow: DefaultOidcActionFlow = DefaultOidcActionFlow(), + defaultLoginUserStory: DefaultLoginUserStory = DefaultLoginUserStory(), + ) = ConfirmAccountProviderPresenter( + params = params, + accountProviderDataSource = accountProviderDataSource, + authenticationService = matrixAuthenticationService, + defaultOidcActionFlow = defaultOidcActionFlow, + defaultLoginUserStory = defaultLoginUserStory, + ) } diff --git a/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/LogoutPreferenceScreen.kt b/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/LogoutPreferenceScreen.kt index 60844f4477..f9f23ac9c4 100644 --- a/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/LogoutPreferenceScreen.kt +++ b/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/LogoutPreferenceScreen.kt @@ -34,12 +34,12 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewLight @Composable fun LogoutPreferenceView( state: LogoutPreferenceState, - onSuccessLogout: () -> Unit = {} + onSuccessLogout: (String?) -> Unit = {} ) { val eventSink = state.eventSink if (state.logoutAction is Async.Success) { LaunchedEffect(state.logoutAction) { - onSuccessLogout() + onSuccessLogout(state.logoutAction.data) } return } diff --git a/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/LogoutPreferenceState.kt b/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/LogoutPreferenceState.kt index e5fd05ba8e..95550ef4c8 100644 --- a/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/LogoutPreferenceState.kt +++ b/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/LogoutPreferenceState.kt @@ -19,6 +19,6 @@ package io.element.android.features.logout.api import io.element.android.libraries.architecture.Async data class LogoutPreferenceState( - val logoutAction: Async, + val logoutAction: Async, val eventSink: (LogoutPreferenceEvents) -> Unit, ) diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/DefaultLogoutPreferencePresenter.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/DefaultLogoutPreferencePresenter.kt index e957755b98..2fece4449b 100644 --- a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/DefaultLogoutPreferencePresenter.kt +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/DefaultLogoutPreferencePresenter.kt @@ -40,7 +40,7 @@ class DefaultLogoutPreferencePresenter @Inject constructor(private val matrixCli @Composable override fun present(): LogoutPreferenceState { val localCoroutineScope = rememberCoroutineScope() - val logoutAction: MutableState> = remember { + val logoutAction: MutableState> = remember { mutableStateOf(Async.Uninitialized) } @@ -56,7 +56,7 @@ class DefaultLogoutPreferencePresenter @Inject constructor(private val matrixCli ) } - private fun CoroutineScope.logout(logoutAction: MutableState>) = launch { + private fun CoroutineScope.logout(logoutAction: MutableState>) = launch { suspend { matrixClient.logout() }.runCatchingUpdatingState(logoutAction) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt index a564927101..3f897caf03 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt @@ -16,8 +16,10 @@ package io.element.android.features.preferences.impl.root +import android.app.Activity import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin @@ -25,7 +27,9 @@ import com.bumble.appyx.core.plugin.plugins import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab import io.element.android.libraries.di.SessionScope +import timber.log.Timber @ContributesNode(SessionScope::class) class PreferencesRootNode @AssistedInject constructor( @@ -65,6 +69,7 @@ class PreferencesRootNode @AssistedInject constructor( @Composable override fun View(modifier: Modifier) { val state = presenter.present() + val activity = LocalContext.current as Activity PreferencesRootView( state = state, modifier = modifier, @@ -73,7 +78,15 @@ class PreferencesRootNode @AssistedInject constructor( onOpenAnalytics = this::onOpenAnalytics, onOpenAbout = this::onOpenAbout, onVerifyClicked = this::onVerifyClicked, - onOpenDeveloperSettings = this::onOpenDeveloperSettings + onOpenDeveloperSettings = this::onOpenDeveloperSettings, + onSuccessLogout = { onSuccessLogout(activity, it) } ) } + + private fun onSuccessLogout(activity: Activity, url: String?) { + Timber.d("Success logout with result url: $url") + url?.let { + activity.openUrlInChromeCustomTab(null, false, it) + } + } } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt index 4b589ad2b0..1cb3a37c77 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt @@ -55,6 +55,7 @@ fun PreferencesRootView( onOpenRageShake: () -> Unit, onOpenAbout: () -> Unit, onOpenDeveloperSettings: () -> Unit, + onSuccessLogout: (String?) -> Unit, modifier: Modifier = Modifier, ) { val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage) @@ -98,6 +99,7 @@ fun PreferencesRootView( HorizontalDivider() LogoutPreferenceView( state = state.logoutState, + onSuccessLogout = onSuccessLogout, ) Text( modifier = Modifier @@ -140,5 +142,6 @@ private fun ContentToPreview(matrixUser: MatrixUser) { onOpenDeveloperSettings = {}, onOpenAbout = {}, onVerifyClicked = {}, + onSuccessLogout = {}, ) } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt index 67c0625a91..705cbbc620 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt @@ -55,7 +55,12 @@ interface MatrixClient : Closeable { * Will close the client and delete the cache data. */ suspend fun clearCache() - suspend fun logout() + + /** + * Logout the user. + * Returns an optional URL. When the URL is there, it should be presented to the user after logout for RP initiated logout on their account page. + */ + suspend fun logout(): String? suspend fun loadUserDisplayName(): Result suspend fun loadUserAvatarURLString(): Result suspend fun uploadMedia(mimeType: String, data: ByteArray, progressCallback: ProgressCallback?): Result diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/AuthenticationException.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/AuthenticationException.kt index 48712b7ddf..9620dc6d7f 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/AuthenticationException.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/AuthenticationException.kt @@ -22,6 +22,5 @@ sealed class AuthenticationException(message: String) : Exception(message) { class SlidingSyncNotAvailable(message: String) : AuthenticationException(message) class SessionMissing(message: String) : AuthenticationException(message) class Generic(message: String) : AuthenticationException(message) - // TODO Oidc - // class OidcError(type: String, message: String) : AuthenticationException(message) + data class OidcError(val type: String, override val message: String) : AuthenticationException(message) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index 8f5cfa496b..e7b1ad4d5e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -119,6 +119,11 @@ class RustMatrixClient constructor( Timber.v("didReceiveAuthError -> already cleaning up") } } + + override fun didRefreshTokens() { + Timber.w("didRefreshTokens()") + // TODO handle refresh token + } } private val rustRoomListService: RoomListService = @@ -287,19 +292,23 @@ class RustMatrixClient constructor( baseDirectory.deleteSessionDirectory(userID = sessionId.value, deleteCryptoDb = false) } - override suspend fun logout() = doLogout(doRequest = true) + override suspend fun logout(): String? = doLogout(doRequest = true) - private suspend fun doLogout(doRequest: Boolean) = withContext(sessionDispatcher) { - if (doRequest) { - try { - client.logout() - } catch (failure: Throwable) { - Timber.e(failure, "Fail to call logout on HS. Still delete local files.") + private suspend fun doLogout(doRequest: Boolean): String? { + var result: String? = null + withContext(sessionDispatcher) { + if (doRequest) { + try { + result = client.logout() + } catch (failure: Throwable) { + Timber.e(failure, "Fail to call logout on HS. Still delete local files.") + } } + close() + baseDirectory.deleteSessionDirectory(userID = sessionId.value, deleteCryptoDb = true) + sessionStore.removeSession(sessionId.value) } - close() - baseDirectory.deleteSessionDirectory(userID = sessionId.value, deleteCryptoDb = true) - sessionStore.removeSession(sessionId.value) + return result } override suspend fun loadUserDisplayName(): Result = withContext(sessionDispatcher) { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt index 931133c266..bb80032230 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt @@ -75,4 +75,5 @@ private fun SessionData.toSession() = Session( deviceId = deviceId, homeserverUrl = homeserverUrl, slidingSyncProxy = slidingSyncProxy, + oidcData = oidcData, ) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationException.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationException.kt index f0feb2857d..8f7f63e503 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationException.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationException.kt @@ -26,15 +26,12 @@ fun Throwable.mapAuthenticationException(): AuthenticationException { is RustAuthenticationException.InvalidServerName -> AuthenticationException.InvalidServerName(this.message!!) is RustAuthenticationException.SessionMissing -> AuthenticationException.SessionMissing(this.message!!) is RustAuthenticationException.SlidingSyncNotAvailable -> AuthenticationException.SlidingSyncNotAvailable(this.message!!) - - /* TODO Oidc is RustAuthenticationException.OidcException -> AuthenticationException.OidcError("OidcException", message!!) is RustAuthenticationException.OidcMetadataInvalid -> AuthenticationException.OidcError("OidcMetadataInvalid", message!!) is RustAuthenticationException.OidcMetadataMissing -> AuthenticationException.OidcError("OidcMetadataMissing", message!!) - is RustAuthenticationException.OidcNotStarted -> AuthenticationException.OidcError("OidcNotStarted", message!!) is RustAuthenticationException.OidcNotSupported -> AuthenticationException.OidcError("OidcNotSupported", message!!) - */ - + is RustAuthenticationException.OidcCancelled -> AuthenticationException.OidcError("OidcCancelled", message!!) + is RustAuthenticationException.OidcCallbackUrlInvalid -> AuthenticationException.OidcError("OidcCallbackUrlInvalid", message!!) else -> AuthenticationException.Generic(this.message ?: "Unknown error") } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/HomeserverDetails.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/HomeserverDetails.kt index a3d277c6da..f1d3e34bf8 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/HomeserverDetails.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/HomeserverDetails.kt @@ -23,6 +23,6 @@ fun HomeserverLoginDetails.map(): MatrixHomeServerDetails = use { MatrixHomeServerDetails( url = url(), supportsPasswordLogin = supportsPasswordLogin(), - supportsOidcLogin = false // TODO Oidc supportsOidcLogin(), + supportsOidcLogin = supportsOidcLogin(), ) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfig.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfig.kt index b5115ffad4..401fa0ce83 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfig.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfig.kt @@ -16,17 +16,19 @@ package io.element.android.libraries.matrix.impl.auth -// TODO Oidc -// import io.element.android.libraries.matrix.api.auth.OidcConfig -// import org.matrix.rustcomponents.sdk.OidcClientMetadata +import io.element.android.libraries.matrix.api.auth.OidcConfig +import org.matrix.rustcomponents.sdk.OidcConfiguration -/* -val oidcClientMetadata: OidcClientMetadata = OidcClientMetadata( +val oidcConfiguration: OidcConfiguration = OidcConfiguration( clientName = "Element", redirectUri = OidcConfig.redirectUri, clientUri = "https://element.io", tosUri = "https://element.io/user-terms-of-service", - policyUri = "https://element.io/privacy" + policyUri = "https://element.io/privacy", + /** + * Some homeservers/auth issuers don't support dynamic client registration, and have to be registered manually + */ + staticRegistrations = mapOf( + "https://id.thirdroom.io/realms/thirdroom" to "elementx", + ), ) - */ - diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt index ba06891013..fe6bf7dea9 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt @@ -16,8 +16,6 @@ package io.element.android.libraries.matrix.impl.auth -// TODO Oidc -// import org.matrix.rustcomponents.sdk.OidcAuthenticationUrl import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.extensions.mapFailure @@ -37,6 +35,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.withContext +import org.matrix.rustcomponents.sdk.OidcAuthenticationData import org.matrix.rustcomponents.sdk.Session import org.matrix.rustcomponents.sdk.use import java.io.File @@ -57,9 +56,8 @@ class RustMatrixAuthenticationService @Inject constructor( private val authService: RustAuthenticationService = RustAuthenticationService( basePath = baseDirectory.absolutePath, passphrase = null, - // TODO Oidc - // oidcClientMetadata = oidcClientMetadata, userAgent = userAgentProvider.provide(), + oidcConfiguration = oidcConfiguration, customSlidingSyncProxy = null, ) private var currentHomeserver = MutableStateFlow(null) @@ -112,60 +110,50 @@ class RustMatrixAuthenticationService @Inject constructor( } } - // TODO Oidc - // private var pendingUrlForOidcLogin: OidcAuthenticationUrl? = null + private var pendingOidcAuthenticationData: OidcAuthenticationData? = null override suspend fun getOidcUrl(): Result { - TODO("Oidc") - /* return withContext(coroutineDispatchers.io) { runCatching { - val urlForOidcLogin = authService.urlForOidcLogin() - val url = urlForOidcLogin.loginUrl() - pendingUrlForOidcLogin = urlForOidcLogin + val oidcAuthenticationData = authService.urlForOidcLogin() + val url = oidcAuthenticationData.loginUrl() + pendingOidcAuthenticationData = oidcAuthenticationData OidcDetails(url) }.mapFailure { failure -> failure.mapAuthenticationException() } } - */ } override suspend fun cancelOidcLogin(): Result { - TODO("Oidc") - /* return withContext(coroutineDispatchers.io) { runCatching { - pendingUrlForOidcLogin?.close() - pendingUrlForOidcLogin = null + pendingOidcAuthenticationData?.close() + pendingOidcAuthenticationData = null }.mapFailure { failure -> failure.mapAuthenticationException() } } - */ } /** * callbackUrl should be the uriRedirect from OidcClientMetadata (with all the parameters). */ override suspend fun loginWithOidc(callbackUrl: String): Result { - TODO("Oidc") - /* return withContext(coroutineDispatchers.io) { runCatching { - val urlForOidcLogin = pendingUrlForOidcLogin ?: error("You need to call `getOidcUrl()` first") + val urlForOidcLogin = pendingOidcAuthenticationData ?: error("You need to call `getOidcUrl()` first") val client = authService.loginWithOidcCallback(urlForOidcLogin, callbackUrl) val sessionData = client.use { it.session().toSessionData() } - pendingUrlForOidcLogin = null + pendingOidcAuthenticationData?.close() + pendingOidcAuthenticationData = null sessionStore.storeData(sessionData) SessionId(sessionData.userId) }.mapFailure { failure -> failure.mapAuthenticationException() } } - */ } - } private fun Session.toSessionData() = SessionData( @@ -174,6 +162,7 @@ private fun Session.toSessionData() = SessionData( accessToken = accessToken, refreshToken = refreshToken, homeserverUrl = homeserverUrl, + oidcData = oidcData, slidingSyncProxy = slidingSyncProxy, loginTimestamp = Date(), ) diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt index 1229836e30..1734aec718 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt @@ -109,9 +109,10 @@ class FakeMatrixClient( override suspend fun clearCache() { } - override suspend fun logout() { + override suspend fun logout(): String? { delay(100) logoutFailure?.let { throw it } + return null } override fun close() = Unit diff --git a/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionData.kt b/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionData.kt index cc106f960a..e14c3feeab 100644 --- a/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionData.kt +++ b/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionData.kt @@ -24,6 +24,7 @@ data class SessionData( val accessToken: String, val refreshToken: String?, val homeserverUrl: String, + val oidcData: String?, val slidingSyncProxy: String?, val loginTimestamp: Date?, ) diff --git a/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/SessionDataMapper.kt b/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/SessionDataMapper.kt index dbb42a8451..d0c89d9896 100644 --- a/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/SessionDataMapper.kt +++ b/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/SessionDataMapper.kt @@ -27,6 +27,7 @@ internal fun SessionData.toDbModel(): DbSessionData { accessToken = accessToken, refreshToken = refreshToken, homeserverUrl = homeserverUrl, + oidcData = oidcData, slidingSyncProxy = slidingSyncProxy, loginTimestamp = loginTimestamp?.time, ) @@ -39,6 +40,7 @@ internal fun DbSessionData.toApiModel(): SessionData { accessToken = accessToken, refreshToken = refreshToken, homeserverUrl = homeserverUrl, + oidcData = oidcData, slidingSyncProxy = slidingSyncProxy, loginTimestamp = loginTimestamp?.let { Date(it) } ) diff --git a/libraries/session-storage/impl/src/main/sqldelight/io/element/android/libraries/matrix/session/SessionData.sq b/libraries/session-storage/impl/src/main/sqldelight/io/element/android/libraries/matrix/session/SessionData.sq index c3123f2ffb..f1dfc51b71 100644 --- a/libraries/session-storage/impl/src/main/sqldelight/io/element/android/libraries/matrix/session/SessionData.sq +++ b/libraries/session-storage/impl/src/main/sqldelight/io/element/android/libraries/matrix/session/SessionData.sq @@ -5,7 +5,8 @@ CREATE TABLE SessionData ( refreshToken TEXT, homeserverUrl TEXT NOT NULL, slidingSyncProxy TEXT, - loginTimestamp INTEGER + loginTimestamp INTEGER, + oidcData TEXT ); diff --git a/libraries/session-storage/impl/src/main/sqldelight/migrations/2.sqm b/libraries/session-storage/impl/src/main/sqldelight/migrations/2.sqm new file mode 100644 index 0000000000..9fc7f2fdaa --- /dev/null +++ b/libraries/session-storage/impl/src/main/sqldelight/migrations/2.sqm @@ -0,0 +1 @@ +ALTER TABLE SessionData ADD COLUMN oidcData TEXT; diff --git a/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTests.kt b/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTests.kt index fc24c5a011..57c911c0b2 100644 --- a/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTests.kt +++ b/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTests.kt @@ -37,6 +37,7 @@ class DatabaseSessionStoreTests { homeserverUrl = "homeserverUrl", slidingSyncProxy = null, loginTimestamp = null, + oidcData = "aOidcData", ) @Before