From 5380c3078042e48b8609333ff2b28e57db32aeb0 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Thu, 22 Sep 2022 17:26:04 +0200 Subject: [PATCH] Adding signout action and corresponding use cases --- .../v2/overview/SessionOverviewAction.kt | 1 + .../v2/overview/SessionOverviewViewEvent.kt | 5 ++ .../v2/overview/SessionOverviewViewModel.kt | 33 +++++++++++ .../InterceptSignoutFlowResponseUseCase.kt | 59 +++++++++++++++++++ .../v2/signout/SignoutSessionResult.kt | 32 ++++++++++ .../v2/signout/SignoutSessionUseCase.kt | 40 +++++++++++++ 6 files changed, 170 insertions(+) create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/signout/InterceptSignoutFlowResponseUseCase.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/signout/SignoutSessionResult.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/signout/SignoutSessionUseCase.kt diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewAction.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewAction.kt index 1118c5dc5b..cea0281570 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewAction.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewAction.kt @@ -20,4 +20,5 @@ import im.vector.app.core.platform.VectorViewModelAction sealed class SessionOverviewAction : VectorViewModelAction { object VerifySession : SessionOverviewAction() + object SignoutSession : SessionOverviewAction() } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewEvent.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewEvent.kt index 8508b395ad..ac64e3dfe6 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewEvent.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewEvent.kt @@ -17,9 +17,14 @@ package im.vector.app.features.settings.devices.v2.overview import im.vector.app.core.platform.VectorViewEvents +import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse sealed class SessionOverviewViewEvent : VectorViewEvents { object ShowVerifyCurrentSession : SessionOverviewViewEvent() data class ShowVerifyOtherSession(val deviceId: String) : SessionOverviewViewEvent() object PromptResetSecrets : SessionOverviewViewEvent() + data class RequestReAuth( + val registrationFlowResponse: RegistrationFlowResponse, + val lastErrorCode: String? + ) : SessionOverviewViewEvent() } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt index 69556e039e..2f3116f40f 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt @@ -25,14 +25,22 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel +import im.vector.app.features.auth.PendingAuthHandler import im.vector.app.features.settings.devices.v2.IsCurrentSessionUseCase +import im.vector.app.features.settings.devices.v2.signout.InterceptSignoutFlowResponseUseCase +import im.vector.app.features.settings.devices.v2.signout.SignoutSessionUseCase import im.vector.app.features.settings.devices.v2.verification.CheckIfCurrentSessionCanBeVerifiedUseCase import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.auth.UIABaseAuth +import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor +import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel +import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth +import kotlin.coroutines.Continuation class SessionOverviewViewModel @AssistedInject constructor( @Assisted val initialState: SessionOverviewViewState, @@ -40,6 +48,9 @@ class SessionOverviewViewModel @AssistedInject constructor( private val isCurrentSessionUseCase: IsCurrentSessionUseCase, private val getDeviceFullInfoUseCase: GetDeviceFullInfoUseCase, private val checkIfCurrentSessionCanBeVerifiedUseCase: CheckIfCurrentSessionCanBeVerifiedUseCase, + private val signoutSessionUseCase: SignoutSessionUseCase, + private val interceptSignoutFlowResponseUseCase: InterceptSignoutFlowResponseUseCase, + private val pendingAuthHandler: PendingAuthHandler, ) : VectorViewModel(initialState) { companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() @@ -83,6 +94,7 @@ class SessionOverviewViewModel @AssistedInject constructor( override fun handle(action: SessionOverviewAction) { when (action) { is SessionOverviewAction.VerifySession -> handleVerifySessionAction() + SessionOverviewAction.SignoutSession -> handleSignoutSession() } } @@ -108,4 +120,25 @@ class SessionOverviewViewModel @AssistedInject constructor( private fun handleVerifyOtherSession(deviceId: String) { _viewEvents.post(SessionOverviewViewEvent.ShowVerifyOtherSession(deviceId)) } + + // TODO add unit tests + private fun handleSignoutSession() = withState { state -> + // TODO should we do something different when it is current session? + viewModelScope.launch { + signoutSessionUseCase.execute(state.deviceId, object : UserInteractiveAuthInterceptor { + override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation) { + when (val result = interceptSignoutFlowResponseUseCase.execute(flowResponse, errCode, promise)) { + is SignoutSessionResult.ReAuthNeeded -> onReAuthNeeded(result) + is SignoutSessionResult.Completed -> Unit // TODO refresh devices list? + post event to close the associated screen + } + } + }) + } + } + + private fun onReAuthNeeded(reAuthNeeded: SignoutSessionResult.ReAuthNeeded) { + pendingAuthHandler.pendingAuth = DefaultBaseAuth(session = reAuthNeeded.flowResponse.session) + pendingAuthHandler.uiaContinuation = reAuthNeeded.uiaContinuation + _viewEvents.post(SessionOverviewViewEvent.RequestReAuth(reAuthNeeded.flowResponse, reAuthNeeded.errCode)) + } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/signout/InterceptSignoutFlowResponseUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/signout/InterceptSignoutFlowResponseUseCase.kt new file mode 100644 index 0000000000..4e69452ab3 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/signout/InterceptSignoutFlowResponseUseCase.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.settings.devices.v2.signout + +import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.features.login.ReAuthHelper +import org.matrix.android.sdk.api.auth.UIABaseAuth +import org.matrix.android.sdk.api.auth.UserPasswordAuth +import org.matrix.android.sdk.api.auth.data.LoginFlowTypes +import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse +import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage +import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth +import javax.inject.Inject +import kotlin.coroutines.Continuation +import kotlin.coroutines.resume + +// TODO add unit tests +class InterceptSignoutFlowResponseUseCase @Inject constructor( + private val reAuthHelper: ReAuthHelper, + private val activeSessionHolder: ActiveSessionHolder, +) { + + fun execute( + flowResponse: RegistrationFlowResponse, + errCode: String?, + promise: Continuation + ): SignoutSessionResult { + return if (flowResponse.nextUncompletedStage() == LoginFlowTypes.PASSWORD && reAuthHelper.data != null && errCode == null) { + UserPasswordAuth( + session = null, + user = activeSessionHolder.getActiveSession().myUserId, + password = reAuthHelper.data + ).let { promise.resume(it) } + + SignoutSessionResult.Completed + } else { + SignoutSessionResult.ReAuthNeeded( + pendingAuth = DefaultBaseAuth(session = flowResponse.session), + uiaContinuation = promise, + flowResponse = flowResponse, + errCode = errCode + ) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/signout/SignoutSessionResult.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/signout/SignoutSessionResult.kt new file mode 100644 index 0000000000..fa1fb31b66 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/signout/SignoutSessionResult.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.settings.devices.v2.signout + +import org.matrix.android.sdk.api.auth.UIABaseAuth +import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse +import kotlin.coroutines.Continuation + +sealed class SignoutSessionResult { + data class ReAuthNeeded( + val pendingAuth: UIABaseAuth, + val uiaContinuation: Continuation, + val flowResponse: RegistrationFlowResponse, + val errCode: String? + ) : SignoutSessionResult() + + object Completed : SignoutSessionResult() +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/signout/SignoutSessionUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/signout/SignoutSessionUseCase.kt new file mode 100644 index 0000000000..b86dab13cd --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/signout/SignoutSessionUseCase.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.settings.devices.v2.signout + +import im.vector.app.core.di.ActiveSessionHolder +import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor +import org.matrix.android.sdk.api.util.awaitCallback +import javax.inject.Inject + +// TODO add unit tests +class SignoutSessionUseCase @Inject constructor( + private val activeSessionHolder: ActiveSessionHolder, +) { + + suspend fun execute(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor): Result { + return deleteDevice(deviceId, userInteractiveAuthInterceptor) + } + + private suspend fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) = runCatching { + awaitCallback { matrixCallback -> + activeSessionHolder.getActiveSession() + .cryptoService() + .deleteDevice(deviceId, userInteractiveAuthInterceptor, matrixCallback) + } + } +}