mirror of
https://github.com/element-hq/element-android
synced 2024-11-27 03:48:12 +03:00
Calling signout multi sessions use case in main screen for other sessions
This commit is contained in:
parent
1bda54323a
commit
0f8e5919da
4 changed files with 150 additions and 10 deletions
|
@ -20,6 +20,12 @@ import im.vector.app.core.platform.VectorViewModelAction
|
|||
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||
|
||||
sealed class DevicesAction : VectorViewModelAction {
|
||||
// ReAuth
|
||||
object SsoAuthDone : DevicesAction()
|
||||
data class PasswordAuthDone(val password: String) : DevicesAction()
|
||||
object ReAuthCancelled : DevicesAction()
|
||||
|
||||
// Others
|
||||
object VerifyCurrentSession : DevicesAction()
|
||||
data class MarkAsManuallyVerified(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesAction()
|
||||
object MultiSignoutOtherSessions : DevicesAction()
|
||||
|
|
|
@ -17,17 +17,21 @@
|
|||
package im.vector.app.features.settings.devices.v2
|
||||
|
||||
import im.vector.app.core.platform.VectorViewEvents
|
||||
import im.vector.app.features.settings.devices.v2.othersessions.OtherSessionsViewEvents
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
||||
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
|
||||
|
||||
sealed class DevicesViewEvent : VectorViewEvents {
|
||||
data class Loading(val message: CharSequence? = null) : DevicesViewEvent()
|
||||
data class Failure(val throwable: Throwable) : DevicesViewEvent()
|
||||
data class RequestReAuth(val registrationFlowResponse: RegistrationFlowResponse, val lastErrorCode: String?) : DevicesViewEvent()
|
||||
data class PromptRenameDevice(val deviceInfo: DeviceInfo) : DevicesViewEvent()
|
||||
data class RequestReAuth(
|
||||
val registrationFlowResponse: RegistrationFlowResponse,
|
||||
val lastErrorCode: String?
|
||||
) : DevicesViewEvent()
|
||||
|
||||
data class ShowVerifyDevice(val userId: String, val transactionId: String?) : DevicesViewEvent()
|
||||
object SelfVerification : DevicesViewEvent()
|
||||
data class ShowManuallyVerify(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesViewEvent()
|
||||
object PromptResetSecrets : DevicesViewEvent()
|
||||
object SignoutSuccess : DevicesViewEvent()
|
||||
data class SignoutError(val error: Throwable) : DevicesViewEvent()
|
||||
}
|
||||
|
|
|
@ -21,24 +21,42 @@ import com.airbnb.mvrx.Success
|
|||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.R
|
||||
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.resources.StringProvider
|
||||
import im.vector.app.features.auth.PendingAuthHandler
|
||||
import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
|
||||
import im.vector.app.features.settings.devices.v2.signout.InterceptSignoutFlowResponseUseCase
|
||||
import im.vector.app.features.settings.devices.v2.signout.SignoutSessionResult
|
||||
import im.vector.app.features.settings.devices.v2.signout.SignoutSessionsUseCase
|
||||
import im.vector.app.features.settings.devices.v2.verification.CheckIfCurrentSessionCanBeVerifiedUseCase
|
||||
import im.vector.app.features.settings.devices.v2.verification.GetCurrentSessionCrossSigningInfoUseCase
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
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.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth
|
||||
import timber.log.Timber
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
import kotlin.coroutines.Continuation
|
||||
|
||||
class DevicesViewModel @AssistedInject constructor(
|
||||
@Assisted initialState: DevicesViewState,
|
||||
activeSessionHolder: ActiveSessionHolder,
|
||||
private val stringProvider: StringProvider,
|
||||
private val getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
|
||||
private val getDeviceFullInfoListUseCase: GetDeviceFullInfoListUseCase,
|
||||
private val refreshDevicesOnCryptoDevicesChangeUseCase: RefreshDevicesOnCryptoDevicesChangeUseCase,
|
||||
private val checkIfCurrentSessionCanBeVerifiedUseCase: CheckIfCurrentSessionCanBeVerifiedUseCase,
|
||||
private val signoutSessionsUseCase: SignoutSessionsUseCase,
|
||||
private val interceptSignoutFlowResponseUseCase: InterceptSignoutFlowResponseUseCase,
|
||||
private val pendingAuthHandler: PendingAuthHandler,
|
||||
refreshDevicesUseCase: RefreshDevicesUseCase,
|
||||
) : VectorSessionsListViewModel<DevicesViewState, DevicesAction, DevicesViewEvent>(initialState, activeSessionHolder, refreshDevicesUseCase) {
|
||||
|
||||
|
@ -98,6 +116,9 @@ class DevicesViewModel @AssistedInject constructor(
|
|||
// TODO update unit tests
|
||||
override fun handle(action: DevicesAction) {
|
||||
when (action) {
|
||||
is DevicesAction.PasswordAuthDone -> handlePasswordAuthDone(action)
|
||||
DevicesAction.ReAuthCancelled -> handleReAuthCancelled()
|
||||
DevicesAction.SsoAuthDone -> handleSsoAuthDone()
|
||||
is DevicesAction.VerifyCurrentSession -> handleVerifyCurrentSessionAction()
|
||||
is DevicesAction.MarkAsManuallyVerified -> handleMarkAsManuallyVerifiedAction()
|
||||
DevicesAction.MultiSignoutOtherSessions -> handleMultiSignoutOtherSessions()
|
||||
|
@ -119,7 +140,79 @@ class DevicesViewModel @AssistedInject constructor(
|
|||
// TODO implement when needed
|
||||
}
|
||||
|
||||
private fun handleMultiSignoutOtherSessions() {
|
||||
// TODO call multi signout use case with all other devices than the current one
|
||||
private fun handleMultiSignoutOtherSessions() = withState { state ->
|
||||
viewModelScope.launch {
|
||||
setLoading(true)
|
||||
val deviceIds = getDeviceIdsOfOtherSessions(state)
|
||||
if (deviceIds.isEmpty()) {
|
||||
return@launch
|
||||
}
|
||||
val signoutResult = signout(deviceIds)
|
||||
setLoading(false)
|
||||
|
||||
if (signoutResult.isSuccess) {
|
||||
onSignoutSuccess()
|
||||
} else {
|
||||
when (val failure = signoutResult.exceptionOrNull()) {
|
||||
null -> onSignoutSuccess()
|
||||
else -> onSignoutFailure(failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDeviceIdsOfOtherSessions(state: DevicesViewState): List<String> {
|
||||
val currentDeviceId = state.currentSessionCrossSigningInfo.deviceId
|
||||
return state.devices()
|
||||
?.mapNotNull { fullInfo -> fullInfo.deviceInfo.deviceId.takeUnless { it == currentDeviceId } }
|
||||
.orEmpty()
|
||||
}
|
||||
|
||||
private suspend fun signout(deviceIds: List<String>) = signoutSessionsUseCase.execute(deviceIds, object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
when (val result = interceptSignoutFlowResponseUseCase.execute(flowResponse, errCode, promise)) {
|
||||
is SignoutSessionResult.ReAuthNeeded -> onReAuthNeeded(result)
|
||||
is SignoutSessionResult.Completed -> Unit
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
private fun onReAuthNeeded(reAuthNeeded: SignoutSessionResult.ReAuthNeeded) {
|
||||
Timber.d("onReAuthNeeded")
|
||||
pendingAuthHandler.pendingAuth = DefaultBaseAuth(session = reAuthNeeded.flowResponse.session)
|
||||
pendingAuthHandler.uiaContinuation = reAuthNeeded.uiaContinuation
|
||||
_viewEvents.post(DevicesViewEvent.RequestReAuth(reAuthNeeded.flowResponse, reAuthNeeded.errCode))
|
||||
}
|
||||
|
||||
private fun setLoading(isLoading: Boolean) {
|
||||
setState { copy(isLoading = isLoading) }
|
||||
}
|
||||
|
||||
private fun onSignoutSuccess() {
|
||||
Timber.d("signout success")
|
||||
refreshDeviceList()
|
||||
_viewEvents.post(DevicesViewEvent.SignoutSuccess)
|
||||
}
|
||||
|
||||
private fun onSignoutFailure(failure: Throwable) {
|
||||
Timber.e("signout failure", failure)
|
||||
val failureMessage = if (failure is Failure.OtherServerError && failure.httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED) {
|
||||
stringProvider.getString(R.string.authentication_error)
|
||||
} else {
|
||||
stringProvider.getString(R.string.matrix_error)
|
||||
}
|
||||
_viewEvents.post(DevicesViewEvent.SignoutError(Exception(failureMessage)))
|
||||
}
|
||||
|
||||
private fun handleSsoAuthDone() {
|
||||
pendingAuthHandler.ssoAuthDone()
|
||||
}
|
||||
|
||||
private fun handlePasswordAuthDone(action: DevicesAction.PasswordAuthDone) {
|
||||
pendingAuthHandler.passwordAuthDone(action.password)
|
||||
}
|
||||
|
||||
private fun handleReAuthCancelled() {
|
||||
pendingAuthHandler.reAuthCancelled()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package im.vector.app.features.settings.devices.v2
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
|
@ -30,6 +31,7 @@ import dagger.hilt.android.AndroidEntryPoint
|
|||
import im.vector.app.R
|
||||
import im.vector.app.core.date.VectorDateFormatter
|
||||
import im.vector.app.core.dialogs.ManuallyVerifyDialog
|
||||
import im.vector.app.core.extensions.registerStartForActivityResult
|
||||
import im.vector.app.core.extensions.setTextColor
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.core.resources.ColorProvider
|
||||
|
@ -37,6 +39,7 @@ import im.vector.app.core.resources.DrawableProvider
|
|||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.databinding.FragmentSettingsDevicesBinding
|
||||
import im.vector.app.features.VectorFeatures
|
||||
import im.vector.app.features.auth.ReAuthActivity
|
||||
import im.vector.app.features.crypto.recover.SetupMode
|
||||
import im.vector.app.features.crypto.verification.VerificationBottomSheet
|
||||
import im.vector.app.features.login.qr.QrCodeLoginArgs
|
||||
|
@ -48,6 +51,7 @@ import im.vector.app.features.settings.devices.v2.list.SESSION_IS_MARKED_AS_INAC
|
|||
import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationView
|
||||
import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationViewState
|
||||
import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
|
||||
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
||||
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -102,10 +106,7 @@ class VectorSettingsDevicesFragment :
|
|||
private fun observeViewEvents() {
|
||||
viewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is DevicesViewEvent.Loading -> showLoading(it.message)
|
||||
is DevicesViewEvent.Failure -> showFailure(it.throwable)
|
||||
is DevicesViewEvent.RequestReAuth -> Unit // TODO. Next PR
|
||||
is DevicesViewEvent.PromptRenameDevice -> Unit // TODO. Next PR
|
||||
is DevicesViewEvent.RequestReAuth -> askForReAuthentication(it)
|
||||
is DevicesViewEvent.ShowVerifyDevice -> {
|
||||
VerificationBottomSheet.withArgs(
|
||||
roomId = null,
|
||||
|
@ -124,6 +125,8 @@ class VectorSettingsDevicesFragment :
|
|||
is DevicesViewEvent.PromptResetSecrets -> {
|
||||
navigator.open4SSetup(requireActivity(), SetupMode.PASSPHRASE_AND_NEEDED_SECRETS_RESET)
|
||||
}
|
||||
is DevicesViewEvent.SignoutError -> showFailure(it.error)
|
||||
is DevicesViewEvent.SignoutSuccess -> Unit // do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -137,6 +140,7 @@ class VectorSettingsDevicesFragment :
|
|||
views.deviceListHeaderOtherSessions.setOnMenuItemClickListener { menuItem ->
|
||||
when (menuItem.itemId) {
|
||||
R.id.otherSessionsHeaderMultiSignout -> {
|
||||
// TODO ask for confirmation
|
||||
viewModel.handle(DevicesAction.MultiSignoutOtherSessions)
|
||||
true
|
||||
}
|
||||
|
@ -366,4 +370,37 @@ class VectorSettingsDevicesFragment :
|
|||
excludeCurrentDevice = true
|
||||
)
|
||||
}
|
||||
|
||||
private val reAuthActivityResultLauncher = registerStartForActivityResult { activityResult ->
|
||||
if (activityResult.resultCode == Activity.RESULT_OK) {
|
||||
when (activityResult.data?.extras?.getString(ReAuthActivity.RESULT_FLOW_TYPE)) {
|
||||
LoginFlowTypes.SSO -> {
|
||||
viewModel.handle(DevicesAction.SsoAuthDone)
|
||||
}
|
||||
LoginFlowTypes.PASSWORD -> {
|
||||
val password = activityResult.data?.extras?.getString(ReAuthActivity.RESULT_VALUE) ?: ""
|
||||
viewModel.handle(DevicesAction.PasswordAuthDone(password))
|
||||
}
|
||||
else -> {
|
||||
viewModel.handle(DevicesAction.ReAuthCancelled)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
viewModel.handle(DevicesAction.ReAuthCancelled)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the re auth activity to get credentials.
|
||||
*/
|
||||
private fun askForReAuthentication(reAuthReq: DevicesViewEvent.RequestReAuth) {
|
||||
ReAuthActivity.newIntent(
|
||||
requireContext(),
|
||||
reAuthReq.registrationFlowResponse,
|
||||
reAuthReq.lastErrorCode,
|
||||
getString(R.string.devices_delete_dialog_title)
|
||||
).let { intent ->
|
||||
reAuthActivityResultLauncher.launch(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue