Calling signout multi sessions use case in main screen for other sessions
This commit is contained in:
parent
1bda54323a
commit
0f8e5919da
@ -20,6 +20,12 @@ import im.vector.app.core.platform.VectorViewModelAction
|
|||||||
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||||
|
|
||||||
sealed class DevicesAction : VectorViewModelAction {
|
sealed class DevicesAction : VectorViewModelAction {
|
||||||
|
// ReAuth
|
||||||
|
object SsoAuthDone : DevicesAction()
|
||||||
|
data class PasswordAuthDone(val password: String) : DevicesAction()
|
||||||
|
object ReAuthCancelled : DevicesAction()
|
||||||
|
|
||||||
|
// Others
|
||||||
object VerifyCurrentSession : DevicesAction()
|
object VerifyCurrentSession : DevicesAction()
|
||||||
data class MarkAsManuallyVerified(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesAction()
|
data class MarkAsManuallyVerified(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesAction()
|
||||||
object MultiSignoutOtherSessions : DevicesAction()
|
object MultiSignoutOtherSessions : DevicesAction()
|
||||||
|
@ -17,17 +17,21 @@
|
|||||||
package im.vector.app.features.settings.devices.v2
|
package im.vector.app.features.settings.devices.v2
|
||||||
|
|
||||||
import im.vector.app.core.platform.VectorViewEvents
|
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.auth.registration.RegistrationFlowResponse
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
|
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
|
||||||
|
|
||||||
sealed class DevicesViewEvent : VectorViewEvents {
|
sealed class DevicesViewEvent : VectorViewEvents {
|
||||||
data class Loading(val message: CharSequence? = null) : DevicesViewEvent()
|
data class RequestReAuth(
|
||||||
data class Failure(val throwable: Throwable) : DevicesViewEvent()
|
val registrationFlowResponse: RegistrationFlowResponse,
|
||||||
data class RequestReAuth(val registrationFlowResponse: RegistrationFlowResponse, val lastErrorCode: String?) : DevicesViewEvent()
|
val lastErrorCode: String?
|
||||||
data class PromptRenameDevice(val deviceInfo: DeviceInfo) : DevicesViewEvent()
|
) : DevicesViewEvent()
|
||||||
|
|
||||||
data class ShowVerifyDevice(val userId: String, val transactionId: String?) : DevicesViewEvent()
|
data class ShowVerifyDevice(val userId: String, val transactionId: String?) : DevicesViewEvent()
|
||||||
object SelfVerification : DevicesViewEvent()
|
object SelfVerification : DevicesViewEvent()
|
||||||
data class ShowManuallyVerify(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesViewEvent()
|
data class ShowManuallyVerify(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesViewEvent()
|
||||||
object PromptResetSecrets : 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.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
|
import im.vector.app.R
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
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.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.CheckIfCurrentSessionCanBeVerifiedUseCase
|
||||||
import im.vector.app.features.settings.devices.v2.verification.GetCurrentSessionCrossSigningInfoUseCase
|
import im.vector.app.features.settings.devices.v2.verification.GetCurrentSessionCrossSigningInfoUseCase
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
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.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(
|
class DevicesViewModel @AssistedInject constructor(
|
||||||
@Assisted initialState: DevicesViewState,
|
@Assisted initialState: DevicesViewState,
|
||||||
activeSessionHolder: ActiveSessionHolder,
|
activeSessionHolder: ActiveSessionHolder,
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
private val getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
|
private val getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
|
||||||
private val getDeviceFullInfoListUseCase: GetDeviceFullInfoListUseCase,
|
private val getDeviceFullInfoListUseCase: GetDeviceFullInfoListUseCase,
|
||||||
private val refreshDevicesOnCryptoDevicesChangeUseCase: RefreshDevicesOnCryptoDevicesChangeUseCase,
|
private val refreshDevicesOnCryptoDevicesChangeUseCase: RefreshDevicesOnCryptoDevicesChangeUseCase,
|
||||||
private val checkIfCurrentSessionCanBeVerifiedUseCase: CheckIfCurrentSessionCanBeVerifiedUseCase,
|
private val checkIfCurrentSessionCanBeVerifiedUseCase: CheckIfCurrentSessionCanBeVerifiedUseCase,
|
||||||
|
private val signoutSessionsUseCase: SignoutSessionsUseCase,
|
||||||
|
private val interceptSignoutFlowResponseUseCase: InterceptSignoutFlowResponseUseCase,
|
||||||
|
private val pendingAuthHandler: PendingAuthHandler,
|
||||||
refreshDevicesUseCase: RefreshDevicesUseCase,
|
refreshDevicesUseCase: RefreshDevicesUseCase,
|
||||||
) : VectorSessionsListViewModel<DevicesViewState, DevicesAction, DevicesViewEvent>(initialState, activeSessionHolder, refreshDevicesUseCase) {
|
) : VectorSessionsListViewModel<DevicesViewState, DevicesAction, DevicesViewEvent>(initialState, activeSessionHolder, refreshDevicesUseCase) {
|
||||||
|
|
||||||
@ -98,6 +116,9 @@ class DevicesViewModel @AssistedInject constructor(
|
|||||||
// TODO update unit tests
|
// TODO update unit tests
|
||||||
override fun handle(action: DevicesAction) {
|
override fun handle(action: DevicesAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
|
is DevicesAction.PasswordAuthDone -> handlePasswordAuthDone(action)
|
||||||
|
DevicesAction.ReAuthCancelled -> handleReAuthCancelled()
|
||||||
|
DevicesAction.SsoAuthDone -> handleSsoAuthDone()
|
||||||
is DevicesAction.VerifyCurrentSession -> handleVerifyCurrentSessionAction()
|
is DevicesAction.VerifyCurrentSession -> handleVerifyCurrentSessionAction()
|
||||||
is DevicesAction.MarkAsManuallyVerified -> handleMarkAsManuallyVerifiedAction()
|
is DevicesAction.MarkAsManuallyVerified -> handleMarkAsManuallyVerifiedAction()
|
||||||
DevicesAction.MultiSignoutOtherSessions -> handleMultiSignoutOtherSessions()
|
DevicesAction.MultiSignoutOtherSessions -> handleMultiSignoutOtherSessions()
|
||||||
@ -119,7 +140,79 @@ class DevicesViewModel @AssistedInject constructor(
|
|||||||
// TODO implement when needed
|
// TODO implement when needed
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleMultiSignoutOtherSessions() {
|
private fun handleMultiSignoutOtherSessions() = withState { state ->
|
||||||
// TODO call multi signout use case with all other devices than the current one
|
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
|
package im.vector.app.features.settings.devices.v2
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
@ -30,6 +31,7 @@ import dagger.hilt.android.AndroidEntryPoint
|
|||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.date.VectorDateFormatter
|
import im.vector.app.core.date.VectorDateFormatter
|
||||||
import im.vector.app.core.dialogs.ManuallyVerifyDialog
|
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.extensions.setTextColor
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.core.resources.ColorProvider
|
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.core.resources.StringProvider
|
||||||
import im.vector.app.databinding.FragmentSettingsDevicesBinding
|
import im.vector.app.databinding.FragmentSettingsDevicesBinding
|
||||||
import im.vector.app.features.VectorFeatures
|
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.recover.SetupMode
|
||||||
import im.vector.app.features.crypto.verification.VerificationBottomSheet
|
import im.vector.app.features.crypto.verification.VerificationBottomSheet
|
||||||
import im.vector.app.features.login.qr.QrCodeLoginArgs
|
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.SecurityRecommendationView
|
||||||
import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationViewState
|
import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationViewState
|
||||||
import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
|
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 org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -102,10 +106,7 @@ class VectorSettingsDevicesFragment :
|
|||||||
private fun observeViewEvents() {
|
private fun observeViewEvents() {
|
||||||
viewModel.observeViewEvents {
|
viewModel.observeViewEvents {
|
||||||
when (it) {
|
when (it) {
|
||||||
is DevicesViewEvent.Loading -> showLoading(it.message)
|
is DevicesViewEvent.RequestReAuth -> askForReAuthentication(it)
|
||||||
is DevicesViewEvent.Failure -> showFailure(it.throwable)
|
|
||||||
is DevicesViewEvent.RequestReAuth -> Unit // TODO. Next PR
|
|
||||||
is DevicesViewEvent.PromptRenameDevice -> Unit // TODO. Next PR
|
|
||||||
is DevicesViewEvent.ShowVerifyDevice -> {
|
is DevicesViewEvent.ShowVerifyDevice -> {
|
||||||
VerificationBottomSheet.withArgs(
|
VerificationBottomSheet.withArgs(
|
||||||
roomId = null,
|
roomId = null,
|
||||||
@ -124,6 +125,8 @@ class VectorSettingsDevicesFragment :
|
|||||||
is DevicesViewEvent.PromptResetSecrets -> {
|
is DevicesViewEvent.PromptResetSecrets -> {
|
||||||
navigator.open4SSetup(requireActivity(), SetupMode.PASSPHRASE_AND_NEEDED_SECRETS_RESET)
|
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 ->
|
views.deviceListHeaderOtherSessions.setOnMenuItemClickListener { menuItem ->
|
||||||
when (menuItem.itemId) {
|
when (menuItem.itemId) {
|
||||||
R.id.otherSessionsHeaderMultiSignout -> {
|
R.id.otherSessionsHeaderMultiSignout -> {
|
||||||
|
// TODO ask for confirmation
|
||||||
viewModel.handle(DevicesAction.MultiSignoutOtherSessions)
|
viewModel.handle(DevicesAction.MultiSignoutOtherSessions)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@ -366,4 +370,37 @@ class VectorSettingsDevicesFragment :
|
|||||||
excludeCurrentDevice = true
|
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 New Issue
Block a user