From 92fe22d9d7733a5296dbc08020ea7cf8631044a0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 22 Aug 2023 20:44:06 +0000 Subject: [PATCH 1/7] Update dependency org.matrix.rustcomponents:sdk-android to v0.1.45 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d79df0df1a..d664e32530 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -146,7 +146,7 @@ jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = { module = "app.cash.molecule:molecule-runtime", version.ref = "molecule" } timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.44" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.45" sqldelight-driver-android = { module = "com.squareup.sqldelight:android-driver", version.ref = "sqldelight" } sqldelight-driver-jvm = { module = "com.squareup.sqldelight:sqlite-driver", version.ref = "sqldelight" } sqldelight-coroutines = { module = "com.squareup.sqldelight:coroutines-extensions", version.ref = "sqldelight" } From 06a9b129d0534a06efa86dc1955fe977654f72ba Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 23 Aug 2023 11:55:05 +0200 Subject: [PATCH 2/7] 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 From 79aa128377f6c43f9bc4737c15eb38b10b1528f1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 23 Aug 2023 12:54:22 +0200 Subject: [PATCH 3/7] Add setting entry point to manage account (OIDC) --- .../messages/impl/src/main/res/values/localazy.xml | 2 ++ .../preferences/impl/root/PreferencesRootNode.kt | 9 ++++++++- .../impl/root/PreferencesRootPresenter.kt | 13 +++++++++++++ .../preferences/impl/root/PreferencesRootState.kt | 1 + .../impl/root/PreferencesRootStateProvider.kt | 1 + .../preferences/impl/root/PreferencesRootView.kt | 10 ++++++++++ .../impl/root/PreferencesRootPresenterTest.kt | 1 + .../android/libraries/matrix/api/MatrixClient.kt | 1 + .../libraries/matrix/impl/RustMatrixClient.kt | 5 +++++ .../libraries/matrix/test/FakeMatrixClient.kt | 4 ++++ .../ui-strings/src/main/res/values/localazy.xml | 11 +++++++++++ 11 files changed, 57 insertions(+), 1 deletion(-) diff --git a/features/messages/impl/src/main/res/values/localazy.xml b/features/messages/impl/src/main/res/values/localazy.xml index c88beff81a..105cb1fc7b 100644 --- a/features/messages/impl/src/main/res/values/localazy.xml +++ b/features/messages/impl/src/main/res/values/localazy.xml @@ -26,11 +26,13 @@ "You can change it in your %1$s." "global settings" "Default setting" + "Remove custom setting" "An error occurred while loading notification settings." "Failed restoring the default mode, please try again." "Failed setting the mode, please try again." "All messages" "Mentions and Keywords only" + "In this room, notify me for" "Show less" "Show more" "Send again" 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 3f897caf03..e90569b40e 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 @@ -66,6 +66,12 @@ class PreferencesRootNode @AssistedInject constructor( plugins().forEach { it.onOpenAbout() } } + private fun onManageAccountClicked(activity: Activity, accountManagementUrl: String?) { + accountManagementUrl?.let { + activity.openUrlInChromeCustomTab(null, false, it) + } + } + @Composable override fun View(modifier: Modifier) { val state = presenter.present() @@ -79,7 +85,8 @@ class PreferencesRootNode @AssistedInject constructor( onOpenAbout = this::onOpenAbout, onVerifyClicked = this::onVerifyClicked, onOpenDeveloperSettings = this::onOpenDeveloperSettings, - onSuccessLogout = { onSuccessLogout(activity, it) } + onSuccessLogout = { onSuccessLogout(activity, it) }, + onManageAccountClicked = { onManageAccountClicked(activity, state.accountManagementUrl) }, ) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt index 0cd2e7f7db..b33940b21e 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt @@ -68,6 +68,14 @@ class PreferencesRootPresenter @Inject constructor( derivedStateOf { sessionVerifiedStatus == SessionVerifiedStatus.NotVerified } } + val accountManagementUrl: MutableState = remember { + mutableStateOf(null) + } + + LaunchedEffect(Unit) { + initAccountManagementUrl(accountManagementUrl) + } + val logoutState = logoutPresenter.present() val showDeveloperSettings = buildType != BuildType.RELEASE return PreferencesRootState( @@ -75,6 +83,7 @@ class PreferencesRootPresenter @Inject constructor( myUser = matrixUser.value, version = versionFormatter.get(), showCompleteVerification = sessionIsNotVerified, + accountManagementUrl = accountManagementUrl.value, showAnalyticsSettings = hasAnalyticsProviders, showDeveloperSettings = showDeveloperSettings, snackbarMessage = snackbarMessage, @@ -84,4 +93,8 @@ class PreferencesRootPresenter @Inject constructor( private fun CoroutineScope.initialLoad(matrixUser: MutableState) = launch { matrixUser.value = matrixClient.getCurrentUser() } + + private fun CoroutineScope.initAccountManagementUrl(accountManagementUrl: MutableState) = launch { + accountManagementUrl.value = matrixClient.getAccountManagementUrl().getOrNull() + } } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt index 540c470815..af3a090630 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt @@ -25,6 +25,7 @@ data class PreferencesRootState( val myUser: MatrixUser?, val version: String, val showCompleteVerification: Boolean, + val accountManagementUrl: String?, val showAnalyticsSettings: Boolean, val showDeveloperSettings: Boolean, val snackbarMessage: SnackbarMessage?, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt index e8c148267f..931a560c1d 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt @@ -25,6 +25,7 @@ fun aPreferencesRootState() = PreferencesRootState( myUser = null, version = "Version 1.1 (1)", showCompleteVerification = true, + accountManagementUrl = "aUrl", showAnalyticsSettings = true, showDeveloperSettings = true, snackbarMessage = SnackbarMessage(CommonStrings.common_verification_complete), 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 1cb3a37c77..c24a2ec875 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 @@ -23,6 +23,7 @@ import androidx.compose.material.icons.outlined.BugReport import androidx.compose.material.icons.outlined.DeveloperMode import androidx.compose.material.icons.outlined.Help import androidx.compose.material.icons.outlined.InsertChart +import androidx.compose.material.icons.outlined.ManageAccounts import androidx.compose.material.icons.outlined.VerifiedUser import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -51,6 +52,7 @@ fun PreferencesRootView( state: PreferencesRootState, onBackPressed: () -> Unit, onVerifyClicked: () -> Unit, + onManageAccountClicked: () -> Unit, onOpenAnalytics: () -> Unit, onOpenRageShake: () -> Unit, onOpenAbout: () -> Unit, @@ -76,6 +78,13 @@ fun PreferencesRootView( ) HorizontalDivider() } + if (state.accountManagementUrl != null) { + PreferenceText( + title = stringResource(id = CommonStrings.screen_settings_oidc_account), + icon = Icons.Outlined.ManageAccounts, + onClick = onManageAccountClicked, + ) + } if (state.showAnalyticsSettings) { PreferenceText( title = stringResource(id = CommonStrings.common_analytics), @@ -143,5 +152,6 @@ private fun ContentToPreview(matrixUser: MatrixUser) { onOpenAbout = {}, onVerifyClicked = {}, onSuccessLogout = {}, + onManageAccountClicked = {}, ) } diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt index 580426fcfa..5d508912a8 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt @@ -64,6 +64,7 @@ class PreferencesRootPresenterTest { ) assertThat(loadedState.showDeveloperSettings).isEqualTo(true) assertThat(loadedState.showAnalyticsSettings).isEqualTo(false) + assertThat(loadedState.accountManagementUrl).isNull() } } } 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 705cbbc620..be64a3371f 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 @@ -63,6 +63,7 @@ interface MatrixClient : Closeable { suspend fun logout(): String? suspend fun loadUserDisplayName(): Result suspend fun loadUserAvatarURLString(): Result + suspend fun getAccountManagementUrl(): Result suspend fun uploadMedia(mimeType: String, data: ByteArray, progressCallback: ProgressCallback?): Result fun roomMembershipObserver(): RoomMembershipObserver 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 e7b1ad4d5e..58d82ec116 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 @@ -311,6 +311,11 @@ class RustMatrixClient constructor( return result } + override suspend fun getAccountManagementUrl(): Result = withContext(sessionDispatcher) { + runCatching { + client.accountUrl() + } + } override suspend fun loadUserDisplayName(): Result = withContext(sessionDispatcher) { runCatching { client.displayName() 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 1734aec718..83f7f3ad79 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 @@ -51,6 +51,7 @@ class FakeMatrixClient( private val pushersService: FakePushersService = FakePushersService(), private val notificationService: FakeNotificationService = FakeNotificationService(), private val syncService: FakeSyncService = FakeSyncService(), + private val accountManagementUrlString: Result = Result.success(null), ) : MatrixClient { private var ignoreUserResult: Result = Result.success(Unit) @@ -125,6 +126,9 @@ class FakeMatrixClient( return userAvatarURLString } + override suspend fun getAccountManagementUrl(): Result { + return accountManagementUrlString + } override suspend fun uploadMedia( mimeType: String, data: ByteArray, diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 3b4c305ffc..e38c4fc6b8 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -180,11 +180,21 @@ "Setting up your account." "Additional settings" "Audio and video calls" + "Configuration mismatch" + "We’ve simplified Notifications Settings to make options easier to find. + +Some custom settings you’ve chosen in the past are not shown here, but they’re still active. + +If you proceed, some of your settings may change." "Direct chats" + "Custom setting per chat" "An error occurred while updating the notification setting." + "All messages" + "Mentions and Keywords only" "On direct chats, notify me for" "On group chats, notify me for" "Enable notifications on this device" + "The configuration has not been corrected, please try again." "Group chats" "Mentions" "All" @@ -196,6 +206,7 @@ "System notifications turned off" "Notifications" "Check if you want to hide all current and future messages from this user" + "Account and devices" "Share location" "Share my location" "Open in Apple Maps" From 3f8e7eaef8941ba5726048f5feb922dec0a674a3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 23 Aug 2023 13:12:11 +0200 Subject: [PATCH 4/7] Add missing tests. --- .../ConfirmAccountProviderPresenterTest.kt | 120 ++++++++++++++++++ .../tests/testutils/WaitingForAssertion.kt | 31 +++++ 2 files changed, 151 insertions(+) create mode 100644 tests/testutils/src/main/kotlin/io/element/android/tests/testutils/WaitingForAssertion.kt 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 73f98a2667..95cd9bf053 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,6 +20,7 @@ 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.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.oidc.customtab.DefaultOidcActionFlow @@ -30,6 +31,7 @@ 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 import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService +import io.element.android.tests.testutils.waitForPredicate import kotlinx.coroutines.test.runTest import org.junit.Test @@ -92,6 +94,124 @@ class ConfirmAccountProviderPresenterTest { } } + @Test + fun `present - oidc - cancel with failure`() = runTest { + val authenticationService = FakeAuthenticationService() + val defaultOidcActionFlow = DefaultOidcActionFlow() + val presenter = createConfirmAccountProviderPresenter( + matrixAuthenticationService = authenticationService, + defaultOidcActionFlow = defaultOidcActionFlow, + ) + authenticationService.givenHomeserver(A_HOMESERVER_OIDC) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue) + val loadingState = awaitItem() + assertThat(loadingState.submitEnabled).isTrue() + assertThat(loadingState.loginFlow).isInstanceOf(Async.Loading::class.java) + val successState = awaitItem() + assertThat(successState.submitEnabled).isFalse() + assertThat(successState.loginFlow).isInstanceOf(Async.Success::class.java) + assertThat(successState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java) + authenticationService.givenOidcCancelError(A_THROWABLE) + defaultOidcActionFlow.post(OidcAction.GoBack) + val cancelFailureState = awaitItem() + assertThat(cancelFailureState.loginFlow).isInstanceOf(Async.Failure::class.java) + } + } + + @Test + fun `present - oidc - cancel with success`() = runTest { + val authenticationService = FakeAuthenticationService() + val defaultOidcActionFlow = DefaultOidcActionFlow() + val presenter = createConfirmAccountProviderPresenter( + matrixAuthenticationService = authenticationService, + defaultOidcActionFlow = defaultOidcActionFlow, + ) + authenticationService.givenHomeserver(A_HOMESERVER_OIDC) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue) + val loadingState = awaitItem() + assertThat(loadingState.submitEnabled).isTrue() + assertThat(loadingState.loginFlow).isInstanceOf(Async.Loading::class.java) + val successState = awaitItem() + assertThat(successState.submitEnabled).isFalse() + assertThat(successState.loginFlow).isInstanceOf(Async.Success::class.java) + assertThat(successState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java) + defaultOidcActionFlow.post(OidcAction.GoBack) + val cancelFinalState = awaitItem() + assertThat(cancelFinalState.loginFlow).isInstanceOf(Async.Uninitialized::class.java) + } + } + + @Test + fun `present - oidc - success with failure`() = runTest { + val authenticationService = FakeAuthenticationService() + val defaultOidcActionFlow = DefaultOidcActionFlow() + val presenter = createConfirmAccountProviderPresenter( + matrixAuthenticationService = authenticationService, + defaultOidcActionFlow = defaultOidcActionFlow, + ) + authenticationService.givenHomeserver(A_HOMESERVER_OIDC) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue) + val loadingState = awaitItem() + assertThat(loadingState.submitEnabled).isTrue() + assertThat(loadingState.loginFlow).isInstanceOf(Async.Loading::class.java) + val successState = awaitItem() + assertThat(successState.submitEnabled).isFalse() + assertThat(successState.loginFlow).isInstanceOf(Async.Success::class.java) + assertThat(successState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java) + authenticationService.givenLoginError(A_THROWABLE) + defaultOidcActionFlow.post(OidcAction.Success("aUrl")) + val cancelLoadingState = awaitItem() + assertThat(cancelLoadingState.loginFlow).isInstanceOf(Async.Loading::class.java) + val cancelFailureState = awaitItem() + assertThat(cancelFailureState.loginFlow).isInstanceOf(Async.Failure::class.java) + } + } + + @Test + fun `present - oidc - success with success`() = runTest { + val authenticationService = FakeAuthenticationService() + val defaultOidcActionFlow = DefaultOidcActionFlow() + val defaultLoginUserStory = DefaultLoginUserStory().apply { + setLoginFlowIsDone(false) + } + val presenter = createConfirmAccountProviderPresenter( + matrixAuthenticationService = authenticationService, + defaultOidcActionFlow = defaultOidcActionFlow, + defaultLoginUserStory = defaultLoginUserStory, + ) + authenticationService.givenHomeserver(A_HOMESERVER_OIDC) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue) + val loadingState = awaitItem() + assertThat(loadingState.submitEnabled).isTrue() + assertThat(loadingState.loginFlow).isInstanceOf(Async.Loading::class.java) + val successState = awaitItem() + assertThat(successState.submitEnabled).isFalse() + assertThat(successState.loginFlow).isInstanceOf(Async.Success::class.java) + assertThat(successState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java) + assertThat(defaultLoginUserStory.loginFlowIsDone.value).isFalse() + defaultOidcActionFlow.post(OidcAction.Success("aUrl")) + val successSuccessState = awaitItem() + assertThat(successSuccessState.loginFlow).isInstanceOf(Async.Loading::class.java) + waitForPredicate { defaultLoginUserStory.loginFlowIsDone.value } + } + } + @Test fun `present - submit fails`() = runTest { val authenticationService = FakeAuthenticationService() diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/WaitingForAssertion.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/WaitingForAssertion.kt new file mode 100644 index 0000000000..6818aafc5b --- /dev/null +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/WaitingForAssertion.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.tests.testutils + +import kotlinx.coroutines.delay + +suspend fun waitForPredicate( + delayBetweenAttemptsMillis: Long = 1, + maxNumberOfAttempts: Int = 20, + predicate: () -> Boolean, +) { + for (i in 0..maxNumberOfAttempts) { + if (predicate()) return + if (i < maxNumberOfAttempts) delay(delayBetweenAttemptsMillis) + } + throw AssertionError("Predicate was not true after $maxNumberOfAttempts attempts") +} From 10e8517766ef226520df9a623dc7edd4a3a5c4d2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 23 Aug 2023 14:42:46 +0200 Subject: [PATCH 5/7] Implement didRefreshTokens(): update database with updated SessionData. --- .../libraries/matrix/impl/RustMatrixClient.kt | 5 ++- .../auth/RustMatrixAuthenticationService.kt | 15 +------ .../libraries/matrix/impl/mapper/Session.kt | 32 +++++++++++++++ .../sessionstorage/api/SessionStore.kt | 6 +++ .../impl/memory/InMemorySessionStore.kt | 4 ++ .../impl/DatabaseSessionStore.kt | 18 ++++++++ .../libraries/matrix/session/SessionData.sq | 3 ++ .../impl/DatabaseSessionStoreTests.kt | 41 +++++++++++++++++++ 8 files changed, 109 insertions(+), 15 deletions(-) create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mapper/Session.kt 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 58d82ec116..c68913b705 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 @@ -40,6 +40,7 @@ import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.impl.core.toProgressWatcher +import io.element.android.libraries.matrix.impl.mapper.toSessionData import io.element.android.libraries.matrix.impl.media.RustMediaLoader import io.element.android.libraries.matrix.impl.notification.RustNotificationService import io.element.android.libraries.matrix.impl.pushers.RustPushersService @@ -122,7 +123,9 @@ class RustMatrixClient constructor( override fun didRefreshTokens() { Timber.w("didRefreshTokens()") - // TODO handle refresh token + appCoroutineScope.launch { + sessionStore.updateData(client.session().toSessionData()) + } } } 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 fe6bf7dea9..6014644733 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 @@ -28,18 +28,16 @@ import io.element.android.libraries.matrix.api.auth.OidcDetails import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.impl.RustMatrixClientFactory import io.element.android.libraries.matrix.impl.exception.mapClientException +import io.element.android.libraries.matrix.impl.mapper.toSessionData import io.element.android.libraries.network.useragent.UserAgentProvider -import io.element.android.libraries.sessionstorage.api.SessionData import io.element.android.libraries.sessionstorage.api.SessionStore 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 -import java.util.Date import javax.inject.Inject import org.matrix.rustcomponents.sdk.AuthenticationService as RustAuthenticationService @@ -155,14 +153,3 @@ class RustMatrixAuthenticationService @Inject constructor( } } } - -private fun Session.toSessionData() = SessionData( - userId = userId, - deviceId = deviceId, - accessToken = accessToken, - refreshToken = refreshToken, - homeserverUrl = homeserverUrl, - oidcData = oidcData, - slidingSyncProxy = slidingSyncProxy, - loginTimestamp = Date(), -) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mapper/Session.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mapper/Session.kt new file mode 100644 index 0000000000..825c6f4397 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mapper/Session.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.impl.mapper + +import io.element.android.libraries.sessionstorage.api.SessionData +import org.matrix.rustcomponents.sdk.Session +import java.util.Date + +internal fun Session.toSessionData() = SessionData( + userId = userId, + deviceId = deviceId, + accessToken = accessToken, + refreshToken = refreshToken, + homeserverUrl = homeserverUrl, + oidcData = oidcData, + slidingSyncProxy = slidingSyncProxy, + loginTimestamp = Date(), +) diff --git a/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionStore.kt b/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionStore.kt index d79d700030..2b3398f76c 100644 --- a/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionStore.kt +++ b/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionStore.kt @@ -23,6 +23,12 @@ interface SessionStore { fun isLoggedIn(): Flow fun sessionsFlow(): Flow> suspend fun storeData(sessionData: SessionData) + + /** + * Will update the session data matching the userId, except the value of loginTimestamp. + * No op if userId is not found in DB. + */ + suspend fun updateData(sessionData: SessionData) suspend fun getSession(sessionId: String): SessionData? suspend fun getAllSessions(): List suspend fun getLatestSession(): SessionData? diff --git a/libraries/session-storage/impl-memory/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/memory/InMemorySessionStore.kt b/libraries/session-storage/impl-memory/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/memory/InMemorySessionStore.kt index e23e34983c..df78149eef 100644 --- a/libraries/session-storage/impl-memory/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/memory/InMemorySessionStore.kt +++ b/libraries/session-storage/impl-memory/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/memory/InMemorySessionStore.kt @@ -38,6 +38,10 @@ class InMemorySessionStore : SessionStore { sessionDataFlow.value = sessionData } + override suspend fun updateData(sessionData: SessionData) { + sessionDataFlow.value = sessionData + } + override suspend fun getSession(sessionId: String): SessionData? { return sessionDataFlow.value.takeIf { it?.userId == sessionId } } diff --git a/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStore.kt b/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStore.kt index 9394b66e66..eb273411a0 100644 --- a/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStore.kt +++ b/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStore.kt @@ -46,6 +46,24 @@ class DatabaseSessionStore @Inject constructor( database.sessionDataQueries.insertSessionData(sessionData.toDbModel()) } + override suspend fun updateData(sessionData: SessionData) { + val result = database.sessionDataQueries.selectByUserId(sessionData.userId) + .executeAsOneOrNull() + ?.toApiModel() + + if (result == null) { + Timber.e("User ${sessionData.userId} not found in session database") + return + } + + // Copy new data from SDK, but keep login timestamp + database.sessionDataQueries.updateSession( + sessionData.copy( + loginTimestamp = result.loginTimestamp, + ).toDbModel() + ) + } + override suspend fun getLatestSession(): SessionData? { return database.sessionDataQueries.selectFirst() .executeAsOneOrNull() 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 f1dfc51b71..05049c5635 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 @@ -24,3 +24,6 @@ INSERT INTO SessionData VALUES ?; removeSession: DELETE FROM SessionData WHERE userId = ?; + +updateSession: +REPLACE INTO SessionData VALUES ?; 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 57c911c0b2..e035ff9ae1 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 @@ -109,4 +109,45 @@ class DatabaseSessionStoreTests { assertThat(database.sessionDataQueries.selectByUserId(aSessionData.userId).executeAsOneOrNull()).isNull() } + + @Test + fun `update session update all fields except loginTimestamp`() = runTest { + val firstSessionData = SessionData( + userId = "userId", + deviceId = "deviceId", + accessToken = "accessToken", + refreshToken = "refreshToken", + homeserverUrl = "homeserverUrl", + slidingSyncProxy = "slidingSyncProxy", + loginTimestamp = 1, + oidcData = "aOidcData", + ) + val secondSessionData = SessionData( + userId = "userId", + deviceId = "deviceIdAltered", + accessToken = "accessTokenAltered", + refreshToken = "refreshTokenAltered", + homeserverUrl = "homeserverUrlAltered", + slidingSyncProxy = "slidingSyncProxyAltered", + loginTimestamp = 2, + oidcData = "aOidcDataAltered", + ) + assertThat(firstSessionData.userId).isEqualTo(secondSessionData.userId) + assertThat(firstSessionData.loginTimestamp).isNotEqualTo(secondSessionData.loginTimestamp) + + database.sessionDataQueries.insertSessionData(firstSessionData) + databaseSessionStore.updateData(secondSessionData.toApiModel()) + + // Get the altered session + val alteredSession = databaseSessionStore.getSession(firstSessionData.userId)!!.toDbModel() + + assertThat(alteredSession.userId).isEqualTo(secondSessionData.userId) + assertThat(alteredSession.deviceId).isEqualTo(secondSessionData.deviceId) + assertThat(alteredSession.accessToken).isEqualTo(secondSessionData.accessToken) + assertThat(alteredSession.refreshToken).isEqualTo(secondSessionData.refreshToken) + assertThat(alteredSession.homeserverUrl).isEqualTo(secondSessionData.homeserverUrl) + assertThat(alteredSession.slidingSyncProxy).isEqualTo(secondSessionData.slidingSyncProxy) + assertThat(alteredSession.loginTimestamp).isEqualTo(/* Not altered! */ firstSessionData.loginTimestamp) + assertThat(alteredSession.oidcData).isEqualTo(secondSessionData.oidcData) + } } From d9ff14d9bce0df584a7fc84f2960cfe7843d7dc4 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Wed, 23 Aug 2023 13:28:17 +0000 Subject: [PATCH 6/7] Update screenshots --- ...ll_PreferencesRootViewDark--1_1_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...ll_PreferencesRootViewDark--1_1_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...l_PreferencesRootViewLight--0_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...l_PreferencesRootViewLight--0_0_null_1,NEXUS_5,1.0,en].png | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_0,NEXUS_5,1.0,en].png index 9202a41eb0..d590c4eb8e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2a80a93fd971e46b3a62c5e2679bfdab30cc01589398842346a2c7815af299fe -size 35413 +oid sha256:72eee76cc8244eb54f147fc589c7b200dc3a46db4ea7306dbd6757918e4fffde +size 39744 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_1,NEXUS_5,1.0,en].png index 7def318962..a3449da98e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:49d6cc10dee437df8428616173de9b94b1b80c4b9edc72057e64b6dd51fa608d -size 34701 +oid sha256:8796e5f70cdd09087ed22ede78c3aed985dcd57e073f68d83dd5884e4f29a2c8 +size 39042 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_0,NEXUS_5,1.0,en].png index 3eb39d8862..370689a2fc 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:522926ef7065fab0176b686e49cf27ad29d4fd44d18d462cb156d45332cf368d -size 37511 +oid sha256:97250f48dfa0cf2320f837eb87ad90d8a1e73642fbb2d571f326d689c4fc10e0 +size 42373 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_1,NEXUS_5,1.0,en].png index 914485e483..6bf7f8fdb0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6aaddaf0c8b536ddc231c74b6bdcb22114fca8e7445e97af91c064333873af2c -size 37614 +oid sha256:73f2811197c014d91834d39dad1fe18abdaea9b2510d8deccead39c10a1b5aa8 +size 42473 From 281df6aec59383cd3e5fe5abf8f20e7346bc5f48 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 23 Aug 2023 16:04:55 +0200 Subject: [PATCH 7/7] Changelog. --- changelog.d/1127.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1127.feature diff --git a/changelog.d/1127.feature b/changelog.d/1127.feature new file mode 100644 index 0000000000..da2c6b3cf1 --- /dev/null +++ b/changelog.d/1127.feature @@ -0,0 +1 @@ +Enable OIDC support.