promoting the accept certificate to an explict ViewEvent

- allows a retryAction to be provided to the event to avoid mutatble state within the view model along with providing a clear path of execution
This commit is contained in:
Adam Brown 2022-08-18 09:50:43 +01:00
parent 0dcab56e34
commit 457f7fffee
6 changed files with 36 additions and 25 deletions

View File

@ -93,6 +93,8 @@ fun Throwable.isMissingEmailVerification() = this is Failure.ServerError &&
error.code == MatrixError.M_UNAUTHORIZED && error.code == MatrixError.M_UNAUTHORIZED &&
error.message == "Unable to get validated threepid" error.message == "Unable to get validated threepid"
fun Throwable.isUnrecognisedCertificate() = this is Failure.UnrecognizedCertificateFailure
/** /**
* Try to convert to a RegistrationFlowResponse. Return null in the cases it's not possible * Try to convert to a RegistrationFlowResponse. Return null in the cases it's not possible
*/ */

View File

@ -82,7 +82,7 @@ sealed interface OnboardingAction : VectorViewModelAction {
data class PostViewEvent(val viewEvent: OnboardingViewEvents) : OnboardingAction data class PostViewEvent(val viewEvent: OnboardingViewEvents) : OnboardingAction
data class UserAcceptCertificate(val fingerprint: Fingerprint) : OnboardingAction data class UserAcceptCertificate(val fingerprint: Fingerprint, val retryAction: OnboardingAction) : OnboardingAction
object PersonalizeProfile : OnboardingAction object PersonalizeProfile : OnboardingAction
data class UpdateDisplayName(val displayName: String) : OnboardingAction data class UpdateDisplayName(val displayName: String) : OnboardingAction

View File

@ -21,6 +21,7 @@ import im.vector.app.core.platform.VectorViewEvents
import im.vector.app.features.login.ServerType import im.vector.app.features.login.ServerType
import im.vector.app.features.login.SignMode import im.vector.app.features.login.SignMode
import org.matrix.android.sdk.api.auth.registration.Stage import org.matrix.android.sdk.api.auth.registration.Stage
import org.matrix.android.sdk.api.failure.Failure as SdkFailure
/** /**
* Transient events for Login. * Transient events for Login.
@ -29,6 +30,7 @@ sealed class OnboardingViewEvents : VectorViewEvents {
data class Loading(val message: CharSequence? = null) : OnboardingViewEvents() data class Loading(val message: CharSequence? = null) : OnboardingViewEvents()
data class Failure(val throwable: Throwable) : OnboardingViewEvents() data class Failure(val throwable: Throwable) : OnboardingViewEvents()
data class DeeplinkAuthenticationFailure(val retryAction: OnboardingAction) : OnboardingViewEvents() data class DeeplinkAuthenticationFailure(val retryAction: OnboardingAction) : OnboardingViewEvents()
data class UnrecognisedCertificateFailure(val retryAction: OnboardingAction, val cause: SdkFailure.UnrecognizedCertificateFailure) : OnboardingViewEvents()
object DisplayRegistrationFallback : OnboardingViewEvents() object DisplayRegistrationFallback : OnboardingViewEvents()
data class DisplayRegistrationStage(val stage: Stage) : OnboardingViewEvents() data class DisplayRegistrationStage(val stage: Stage) : OnboardingViewEvents()

View File

@ -60,7 +60,9 @@ import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
import org.matrix.android.sdk.api.auth.login.LoginWizard import org.matrix.android.sdk.api.auth.login.LoginWizard
import org.matrix.android.sdk.api.auth.registration.RegistrationAvailability import org.matrix.android.sdk.api.auth.registration.RegistrationAvailability
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.isHomeserverUnavailable import org.matrix.android.sdk.api.failure.isHomeserverUnavailable
import org.matrix.android.sdk.api.failure.isUnrecognisedCertificate
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider
import timber.log.Timber import timber.log.Timber
@ -113,8 +115,6 @@ class OnboardingViewModel @AssistedInject constructor(
} }
} }
// Store the last action, to redo it after user has trusted the untrusted certificate
private var lastAction: OnboardingAction? = null
private var currentHomeServerConnectionConfig: HomeServerConnectionConfig? = null private var currentHomeServerConnectionConfig: HomeServerConnectionConfig? = null
private val matrixOrgUrl = stringProvider.getString(R.string.matrix_org_server_url).ensureTrailingSlash() private val matrixOrgUrl = stringProvider.getString(R.string.matrix_org_server_url).ensureTrailingSlash()
@ -146,9 +146,9 @@ class OnboardingViewModel @AssistedInject constructor(
is OnboardingAction.UpdateServerType -> handleUpdateServerType(action) is OnboardingAction.UpdateServerType -> handleUpdateServerType(action)
is OnboardingAction.UpdateSignMode -> handleUpdateSignMode(action) is OnboardingAction.UpdateSignMode -> handleUpdateSignMode(action)
is OnboardingAction.InitWith -> handleInitWith(action) is OnboardingAction.InitWith -> handleInitWith(action)
is OnboardingAction.HomeServerChange -> withAction(action) { handleHomeserverChange(action) } is OnboardingAction.HomeServerChange -> handleHomeserverChange(action)
is OnboardingAction.UserNameEnteredAction -> handleUserNameEntered(action) is OnboardingAction.UserNameEnteredAction -> handleUserNameEntered(action)
is AuthenticateAction -> withAction(action) { handleAuthenticateAction(action) } is AuthenticateAction -> handleAuthenticateAction(action)
is OnboardingAction.LoginWithToken -> handleLoginWithToken(action) is OnboardingAction.LoginWithToken -> handleLoginWithToken(action)
is OnboardingAction.WebLoginSuccess -> handleWebLoginSuccess(action) is OnboardingAction.WebLoginSuccess -> handleWebLoginSuccess(action)
is OnboardingAction.ResetPassword -> handleResetPassword(action) is OnboardingAction.ResetPassword -> handleResetPassword(action)
@ -221,11 +221,6 @@ class OnboardingViewModel @AssistedInject constructor(
) )
} }
private fun withAction(action: OnboardingAction, block: (OnboardingAction) -> Unit) {
lastAction = action
block(action)
}
private fun handleAuthenticateAction(action: AuthenticateAction) { private fun handleAuthenticateAction(action: AuthenticateAction) {
when (action) { when (action) {
is AuthenticateAction.Register -> handleRegisterWith(action.username, action.password, action.initialDeviceName) is AuthenticateAction.Register -> handleRegisterWith(action.username, action.password, action.initialDeviceName)
@ -276,15 +271,15 @@ class OnboardingViewModel @AssistedInject constructor(
private fun handleUserAcceptCertificate(action: OnboardingAction.UserAcceptCertificate) { private fun handleUserAcceptCertificate(action: OnboardingAction.UserAcceptCertificate) {
// It happens when we get the login flow, or during direct authentication. // It happens when we get the login flow, or during direct authentication.
// So alter the homeserver config and retrieve again the login flow // So alter the homeserver config and retrieve again the login flow
when (val finalLastAction = lastAction) { when (action.retryAction) {
is OnboardingAction.HomeServerChange.SelectHomeServer -> { is OnboardingAction.HomeServerChange -> {
currentHomeServerConnectionConfig currentHomeServerConnectionConfig
?.let { it.copy(allowedFingerprints = it.allowedFingerprints + action.fingerprint) } ?.let { it.copy(allowedFingerprints = it.allowedFingerprints + action.fingerprint) }
?.let { startAuthenticationFlow(finalLastAction, it, serverTypeOverride = null) } ?.let { startAuthenticationFlow(action.retryAction, it, serverTypeOverride = null) }
} }
is AuthenticateAction.LoginDirect -> is AuthenticateAction.LoginDirect ->
handleDirectLogin( handleDirectLogin(
finalLastAction, action.retryAction,
HomeServerConnectionConfig.Builder() HomeServerConnectionConfig.Builder()
// Will be replaced by the task // Will be replaced by the task
.withHomeServerUri("https://dummy.org") .withHomeServerUri("https://dummy.org")
@ -589,9 +584,19 @@ class OnboardingViewModel @AssistedInject constructor(
currentJob = viewModelScope.launch { currentJob = viewModelScope.launch {
directLoginUseCase.execute(action, homeServerConnectionConfig).fold( directLoginUseCase.execute(action, homeServerConnectionConfig).fold(
onSuccess = { onSessionCreated(it, authenticationDescription = AuthenticationDescription.Login) }, onSuccess = { onSessionCreated(it, authenticationDescription = AuthenticationDescription.Login) },
onFailure = { onFailure = { error ->
setState { copy(isLoading = false) } setState { copy(isLoading = false) }
_viewEvents.post(OnboardingViewEvents.Failure(it)) when {
error.isUnrecognisedCertificate() -> {
_viewEvents.post(
OnboardingViewEvents.UnrecognisedCertificateFailure(
retryAction = action,
cause = error as Failure.UnrecognizedCertificateFailure
)
)
}
else -> _viewEvents.post(OnboardingViewEvents.Failure(error))
}
} }
) )
} }
@ -723,9 +728,10 @@ class OnboardingViewModel @AssistedInject constructor(
retryAction = (trigger as OnboardingAction.HomeServerChange.SelectHomeServer).resetToDefaultUrl() retryAction = (trigger as OnboardingAction.HomeServerChange.SelectHomeServer).resetToDefaultUrl()
) )
) )
else -> _viewEvents.post( error.isUnrecognisedCertificate() -> {
OnboardingViewEvents.Failure(error) _viewEvents.post(OnboardingViewEvents.UnrecognisedCertificateFailure(trigger, error as Failure.UnrecognizedCertificateFailure))
) }
else -> _viewEvents.post(OnboardingViewEvents.Failure(error))
} }
} }

View File

@ -33,7 +33,6 @@ import im.vector.app.features.onboarding.OnboardingViewEvents
import im.vector.app.features.onboarding.OnboardingViewModel import im.vector.app.features.onboarding.OnboardingViewModel
import im.vector.app.features.onboarding.OnboardingViewState import im.vector.app.features.onboarding.OnboardingViewState
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import org.matrix.android.sdk.api.failure.Failure
/** /**
* Parent Fragment for all the login/registration screens. * Parent Fragment for all the login/registration screens.
@ -68,6 +67,7 @@ abstract class AbstractFtueAuthFragment<VB : ViewBinding> : VectorBaseFragment<V
private fun handleOnboardingViewEvents(viewEvents: OnboardingViewEvents) { private fun handleOnboardingViewEvents(viewEvents: OnboardingViewEvents) {
when (viewEvents) { when (viewEvents) {
is OnboardingViewEvents.Failure -> showFailure(viewEvents.throwable) is OnboardingViewEvents.Failure -> showFailure(viewEvents.throwable)
is OnboardingViewEvents.UnrecognisedCertificateFailure -> showUnrecognizedCertificateFailure(viewEvents)
else -> else ->
// This is handled by the Activity // This is handled by the Activity
Unit Unit
@ -84,20 +84,20 @@ abstract class AbstractFtueAuthFragment<VB : ViewBinding> : VectorBaseFragment<V
is CancellationException -> is CancellationException ->
/* Ignore this error, user has cancelled the action */ /* Ignore this error, user has cancelled the action */
Unit Unit
is Failure.UnrecognizedCertificateFailure -> showUnrecognizedCertificateFailure(throwable)
else -> onError(throwable) else -> onError(throwable)
} }
} }
private fun showUnrecognizedCertificateFailure(failure: Failure.UnrecognizedCertificateFailure) { private fun showUnrecognizedCertificateFailure(event: OnboardingViewEvents.UnrecognisedCertificateFailure) {
// Ask the user to accept the certificate // Ask the user to accept the certificate
val cause = event.cause
unrecognizedCertificateDialog.show(requireActivity(), unrecognizedCertificateDialog.show(requireActivity(),
failure.fingerprint, cause.fingerprint,
failure.url, cause.url,
object : UnrecognizedCertificateDialog.Callback { object : UnrecognizedCertificateDialog.Callback {
override fun onAccept() { override fun onAccept() {
// User accept the certificate // User accept the certificate
viewModel.handle(OnboardingAction.UserAcceptCertificate(failure.fingerprint)) viewModel.handle(OnboardingAction.UserAcceptCertificate(cause.fingerprint, event.retryAction))
} }
override fun onIgnore() { override fun onIgnore() {

View File

@ -202,6 +202,7 @@ class FtueAuthVariant(
openMsisdnConfirmation(viewEvents.msisdn) openMsisdnConfirmation(viewEvents.msisdn)
} }
is OnboardingViewEvents.Failure, is OnboardingViewEvents.Failure,
is OnboardingViewEvents.UnrecognisedCertificateFailure,
is OnboardingViewEvents.Loading -> is OnboardingViewEvents.Loading ->
// This is handled by the Fragments // This is handled by the Fragments
Unit Unit