diff --git a/CHANGES.md b/CHANGES.md index a8313cc722..62aeb5473d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,7 @@ Changes in Riot.imX 0.91.4 (2020-XX-XX) =================================================== Features ✨: - - + - Re-activate Wellknown support with updated UI (#1614) Improvements 🙌: - Upload device keys only once to the homeserver and fix crash when no network (#1629) @@ -21,7 +21,7 @@ SDK API changes ⚠️: - Build 🧱: - - Fix lint false-positive about WorkManger (#1012) + - Fix lint false-positive about WorkManager (#1012) - Upgrade build-tools from 3.5.3 to 3.6.6 - Upgrade gradle from 5.4.1 to 5.6.4 diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/login/DirectLoginTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/login/DirectLoginTask.kt index 22a921a094..51dbac2cc3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/login/DirectLoginTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/auth/login/DirectLoginTask.kt @@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.auth.login import dagger.Lazy import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig +import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.internal.auth.AuthAPI import im.vector.matrix.android.internal.auth.SessionCreator @@ -27,6 +28,7 @@ import im.vector.matrix.android.internal.di.Unauthenticated import im.vector.matrix.android.internal.network.RetrofitFactory import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.httpclient.addSocketFactory +import im.vector.matrix.android.internal.network.ssl.UnrecognizedCertificateException import im.vector.matrix.android.internal.task.Task import okhttp3.OkHttpClient import javax.inject.Inject @@ -49,13 +51,28 @@ internal class DefaultDirectLoginTask @Inject constructor( override suspend fun execute(params: DirectLoginTask.Params): Session { val client = buildClient(params.homeServerConnectionConfig) - val authAPI = retrofitFactory.create(client, params.homeServerConnectionConfig.homeServerUri.toString()) + val homeServerUrl = params.homeServerConnectionConfig.homeServerUri.toString() + + val authAPI = retrofitFactory.create(client, homeServerUrl) .create(AuthAPI::class.java) val loginParams = PasswordLoginParams.userIdentifier(params.userId, params.password, params.deviceName) - val credentials = executeRequest(null) { - apiCall = authAPI.login(loginParams) + val credentials = try { + executeRequest(null) { + apiCall = authAPI.login(loginParams) + } + } catch (throwable: Throwable) { + when (throwable) { + is UnrecognizedCertificateException -> { + throw Failure.UnrecognizedCertificateFailure( + homeServerUrl, + throwable.fingerprint + ) + } + else -> + throw throwable + } } return sessionCreator.createSession(credentials, params.homeServerConnectionConfig) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/wellknown/GetWellknownTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/wellknown/GetWellknownTask.kt index 8ded737c64..b42aef5f88 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/wellknown/GetWellknownTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/wellknown/GetWellknownTask.kt @@ -27,6 +27,7 @@ import im.vector.matrix.android.internal.di.Unauthenticated import im.vector.matrix.android.internal.network.RetrofitFactory import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.httpclient.addSocketFactory +import im.vector.matrix.android.internal.network.ssl.UnrecognizedCertificateException import im.vector.matrix.android.internal.session.homeserver.CapabilitiesAPI import im.vector.matrix.android.internal.session.identity.IdentityAuthAPI import im.vector.matrix.android.internal.task.Task @@ -106,6 +107,12 @@ internal class DefaultGetWellknownTask @Inject constructor( } } catch (throwable: Throwable) { when (throwable) { + is UnrecognizedCertificateException -> { + throw Failure.UnrecognizedCertificateFailure( + "https://$domain", + throwable.fingerprint + ) + } is Failure.NetworkConnection -> { WellknownResult.Ignore } diff --git a/vector/src/main/java/im/vector/riotx/core/utils/UrlUtils.kt b/vector/src/main/java/im/vector/riotx/core/utils/UrlUtils.kt index 0361fc9d71..c26cd85dc7 100644 --- a/vector/src/main/java/im/vector/riotx/core/utils/UrlUtils.kt +++ b/vector/src/main/java/im/vector/riotx/core/utils/UrlUtils.kt @@ -37,3 +37,11 @@ internal fun String.ensureProtocol(): String { else -> this } } + +internal fun String.ensureTrailingSlash(): String { + return when { + isEmpty() -> this + !endsWith("/") -> "$this/" + else -> this + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/login/AbstractLoginFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/AbstractLoginFragment.kt index 5927e5b117..90be21b6be 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/AbstractLoginFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/AbstractLoginFragment.kt @@ -73,6 +73,9 @@ abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed { override fun showFailure(throwable: Throwable) { when (throwable) { + is Failure.Cancelled -> + /* Ignore this error, user has cancelled the action */ + Unit is Failure.ServerError -> if (throwable.error.code == MatrixError.M_FORBIDDEN && throwable.httpCode == HttpsURLConnection.HTTP_FORBIDDEN /* 403 */) { diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt index 86be00702c..f71b0ecba4 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt @@ -151,8 +151,8 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable { // TODO Disabled because it provokes a flickering // ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim) }) - is LoginViewEvents.OnServerSelectionDone -> onServerSelectionDone() - is LoginViewEvents.OnSignModeSelected -> onSignModeSelected() + is LoginViewEvents.OnServerSelectionDone -> onServerSelectionDone(loginViewEvents) + is LoginViewEvents.OnSignModeSelected -> onSignModeSelected(loginViewEvents) is LoginViewEvents.OnLoginFlowRetrieved -> addFragmentToBackstack(R.id.loginFragmentContainer, if (loginViewEvents.isSso) { @@ -228,18 +228,20 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable { .show() } - private fun onServerSelectionDone() = withState(loginViewModel) { state -> - when (state.serverType) { + private fun onServerSelectionDone(loginViewEvents: LoginViewEvents.OnServerSelectionDone) { + when (loginViewEvents.serverType) { ServerType.MatrixOrg -> Unit // In this case, we wait for the login flow ServerType.Modular, ServerType.Other -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginServerUrlFormFragment::class.java, option = commonOption) + ServerType.Unknown -> Unit /* Should not happen */ } } - private fun onSignModeSelected() = withState(loginViewModel) { state -> - when (state.signMode) { + private fun onSignModeSelected(loginViewEvents: LoginViewEvents.OnSignModeSelected) = withState(loginViewModel) { state -> + // state.signMode could not be ready yet. So use value from the ViewEvent + when (loginViewEvents.signMode) { SignMode.Unknown -> error("Sign mode has to be set before calling this method") SignMode.SignUp -> { // This is managed by the LoginViewEvents diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginFragment.kt index eaf0a3cc78..ef8281fa89 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginFragment.kt @@ -54,6 +54,7 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() { private var passwordShown = false private var isSignupMode = false + // Temporary patch for https://github.com/vector-im/riotX-android/issues/1410, // waiting for https://github.com/matrix-org/synapse/issues/7576 private var isNumericOnlyUserIdForbidden = false @@ -138,6 +139,7 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() { loginServerIcon.isVisible = false loginTitle.text = getString(R.string.login_signin_matrix_id_title) loginNotice.text = getString(R.string.login_signin_matrix_id_notice) + loginPasswordNotice.isVisible = true } else { val resId = when (state.signMode) { SignMode.Unknown -> error("developer error") @@ -164,7 +166,9 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() { loginTitle.text = getString(resId, state.homeServerUrl.toReducedUrl()) loginNotice.text = getString(R.string.login_server_other_text) } + ServerType.Unknown -> Unit /* Should not happen */ } + loginPasswordNotice.isVisible = false } } diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginServerSelectionFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginServerSelectionFragment.kt index 79c5c7d024..3a1bba7f11 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginServerSelectionFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginServerSelectionFragment.kt @@ -19,7 +19,6 @@ package im.vector.riotx.features.login import android.os.Bundle import android.view.View import butterknife.OnClick -import com.airbnb.mvrx.withState import im.vector.riotx.R import im.vector.riotx.core.utils.openUrlInChromeCustomTab import kotlinx.android.synthetic.main.fragment_login_server_selection.* @@ -40,11 +39,7 @@ class LoginServerSelectionFragment @Inject constructor() : AbstractLoginFragment } private fun updateSelectedChoice(state: LoginViewState) { - state.serverType.let { - loginServerChoiceMatrixOrg.isChecked = it == ServerType.MatrixOrg - loginServerChoiceModular.isChecked = it == ServerType.Modular - loginServerChoiceOther.isChecked = it == ServerType.Other - } + loginServerChoiceMatrixOrg.isChecked = state.serverType == ServerType.MatrixOrg } private fun initTextViews() { @@ -61,42 +56,17 @@ class LoginServerSelectionFragment @Inject constructor() : AbstractLoginFragment @OnClick(R.id.loginServerChoiceMatrixOrg) fun selectMatrixOrg() { - if (loginServerChoiceMatrixOrg.isChecked) { - // Consider this is a submit - submit() - } else { - loginViewModel.handle(LoginAction.UpdateServerType(ServerType.MatrixOrg)) - } + loginViewModel.handle(LoginAction.UpdateServerType(ServerType.MatrixOrg)) } @OnClick(R.id.loginServerChoiceModular) fun selectModular() { - if (loginServerChoiceModular.isChecked) { - // Consider this is a submit - submit() - } else { - loginViewModel.handle(LoginAction.UpdateServerType(ServerType.Modular)) - } + loginViewModel.handle(LoginAction.UpdateServerType(ServerType.Modular)) } @OnClick(R.id.loginServerChoiceOther) fun selectOther() { - if (loginServerChoiceOther.isChecked) { - // Consider this is a submit - submit() - } else { - loginViewModel.handle(LoginAction.UpdateServerType(ServerType.Other)) - } - } - - @OnClick(R.id.loginServerSubmit) - fun submit() = withState(loginViewModel) { state -> - if (state.serverType == ServerType.MatrixOrg) { - // Request login flow here - loginViewModel.handle(LoginAction.UpdateHomeServer(getString(R.string.matrix_org_server_url))) - } else { - loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnServerSelectionDone)) - } + loginViewModel.handle(LoginAction.UpdateServerType(ServerType.Other)) } @OnClick(R.id.loginServerIKnowMyIdSubmit) diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginServerUrlFormFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginServerUrlFormFragment.kt index cb90ef2397..28331bc99e 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginServerUrlFormFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginServerUrlFormFragment.kt @@ -70,7 +70,7 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment() loginServerUrlFormHomeServerUrlTil.hint = getText(R.string.login_server_url_form_modular_hint) loginServerUrlFormNotice.text = getString(R.string.login_server_url_form_modular_notice) } - ServerType.Other -> { + else -> { loginServerUrlFormIcon.isVisible = false loginServerUrlFormTitle.text = getString(R.string.login_server_other_title) loginServerUrlFormText.text = getString(R.string.login_connect_to_a_custom_server) @@ -78,7 +78,6 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment() loginServerUrlFormHomeServerUrlTil.hint = getText(R.string.login_server_url_form_other_hint) loginServerUrlFormNotice.text = getString(R.string.login_server_url_form_other_notice) } - else -> error("This fragment should not be displayed in matrix.org mode") } } diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginSignUpSignInSelectionFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginSignUpSignInSelectionFragment.kt index 427ad99b41..6ac5993f30 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginSignUpSignInSelectionFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginSignUpSignInSelectionFragment.kt @@ -49,6 +49,7 @@ open class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLo loginSignupSigninTitle.text = getString(R.string.login_server_other_title) loginSignupSigninText.text = getString(R.string.login_connect_to, state.homeServerUrl.toReducedUrl()) } + ServerType.Unknown -> Unit /* Should not happen */ } } diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginViewEvents.kt index 9b69ba8a4f..fe5c00399b 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginViewEvents.kt @@ -33,9 +33,9 @@ sealed class LoginViewEvents : VectorViewEvents { // Navigation event object OpenServerSelection : LoginViewEvents() - object OnServerSelectionDone : LoginViewEvents() + data class OnServerSelectionDone(val serverType: ServerType) : LoginViewEvents() data class OnLoginFlowRetrieved(val isSso: Boolean) : LoginViewEvents() - object OnSignModeSelected : LoginViewEvents() + data class OnSignModeSelected(val signMode: SignMode) : LoginViewEvents() object OnForgetPasswordClicked : LoginViewEvents() object OnResetPasswordSendThreePidDone : LoginViewEvents() object OnResetPasswordMailConfirmationSuccess : LoginViewEvents() diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt index fc970297d1..7edc674b11 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt @@ -39,6 +39,7 @@ import im.vector.matrix.android.api.auth.registration.RegistrationResult import im.vector.matrix.android.api.auth.registration.RegistrationWizard import im.vector.matrix.android.api.auth.registration.Stage import im.vector.matrix.android.api.auth.wellknown.WellknownResult +import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.util.Cancelable import im.vector.riotx.R @@ -47,6 +48,7 @@ import im.vector.riotx.core.extensions.configureAndStart import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.resources.StringProvider +import im.vector.riotx.core.utils.ensureTrailingSlash import im.vector.riotx.features.call.WebRtcPeerConnectionManager import im.vector.riotx.features.notifications.PushRuleTriggerListener import im.vector.riotx.features.session.SessionListener @@ -87,8 +89,12 @@ class LoginViewModel @AssistedInject constructor( } } + // Store the last action, to redo it after user has trusted the untrusted certificate + private var lastAction: LoginAction? = null private var currentHomeServerConnectionConfig: HomeServerConnectionConfig? = null + private val matrixOrgUrl = stringProvider.getString(R.string.matrix_org_server_url).ensureTrailingSlash() + val currentThreePid: String? get() = registrationWizard?.currentThreePid @@ -111,8 +117,8 @@ class LoginViewModel @AssistedInject constructor( is LoginAction.UpdateServerType -> handleUpdateServerType(action) is LoginAction.UpdateSignMode -> handleUpdateSignMode(action) is LoginAction.InitWith -> handleInitWith(action) - is LoginAction.UpdateHomeServer -> handleUpdateHomeserver(action) - is LoginAction.LoginOrRegister -> handleLoginOrRegister(action) + is LoginAction.UpdateHomeServer -> handleUpdateHomeserver(action).also { lastAction = action } + is LoginAction.LoginOrRegister -> handleLoginOrRegister(action).also { lastAction = action } is LoginAction.LoginWithToken -> handleLoginWithToken(action) is LoginAction.WebLoginSuccess -> handleWebLoginSuccess(action) is LoginAction.ResetPassword -> handleResetPassword(action) @@ -126,10 +132,23 @@ class LoginViewModel @AssistedInject constructor( } private fun handleUserAcceptCertificate(action: LoginAction.UserAcceptCertificate) { - // It happen when we get the login flow, so alter the homeserver config and retrieve again the login flow - currentHomeServerConnectionConfig - ?.let { it.copy(allowedFingerprints = it.allowedFingerprints + action.fingerprint) } - ?.let { getLoginFlow(it) } + // It happen when we get the login flow, or during direct authentication. + // So alter the homeserver config and retrieve again the login flow + when (val finalLastAction = lastAction) { + is LoginAction.UpdateHomeServer -> + currentHomeServerConnectionConfig + ?.let { it.copy(allowedFingerprints = it.allowedFingerprints + action.fingerprint) } + ?.let { getLoginFlow(it) } + is LoginAction.LoginOrRegister -> + handleDirectLogin( + finalLastAction, + HomeServerConnectionConfig.Builder() + // Will be replaced by the task + .withHomeServerUri("https://dummy.org") + .withAllowedFingerPrints(listOf(action.fingerprint)) + .build() + ) + } } private fun handleLoginWithToken(action: LoginAction.LoginWithToken) { @@ -321,7 +340,7 @@ class LoginViewModel @AssistedInject constructor( LoginAction.ResetHomeServerType -> { setState { copy( - serverType = ServerType.MatrixOrg + serverType = ServerType.Unknown ) } } @@ -333,6 +352,7 @@ class LoginViewModel @AssistedInject constructor( asyncHomeServerLoginFlowRequest = Uninitialized, homeServerUrl = null, loginMode = LoginMode.Unknown, + serverType = ServerType.Unknown, loginModeSupportedTypes = emptyList() ) } @@ -379,9 +399,9 @@ class LoginViewModel @AssistedInject constructor( when (action.signMode) { SignMode.SignUp -> startRegistrationFlow() SignMode.SignIn -> startAuthenticationFlow() - SignMode.SignInWithMatrixId -> _viewEvents.post(LoginViewEvents.OnSignModeSelected) + SignMode.SignInWithMatrixId -> _viewEvents.post(LoginViewEvents.OnSignModeSelected(SignMode.SignInWithMatrixId)) SignMode.Unknown -> Unit - }.exhaustive + } } private fun handleUpdateServerType(action: LoginAction.UpdateServerType) { @@ -390,6 +410,15 @@ class LoginViewModel @AssistedInject constructor( serverType = action.serverType ) } + + when (action.serverType) { + ServerType.Unknown -> Unit /* Should not happen */ + ServerType.MatrixOrg -> + // Request login flow here + handle(LoginAction.UpdateHomeServer(matrixOrgUrl)) + ServerType.Modular, + ServerType.Other -> _viewEvents.post(LoginViewEvents.OnServerSelectionDone(action.serverType)) + }.exhaustive } private fun handleInitWith(action: LoginAction.InitWith) { @@ -427,7 +456,6 @@ class LoginViewModel @AssistedInject constructor( } override fun onFailure(failure: Throwable) { - // TODO Handled JobCancellationException setState { copy( asyncResetPassword = Fail(failure) @@ -469,7 +497,6 @@ class LoginViewModel @AssistedInject constructor( } override fun onFailure(failure: Throwable) { - // TODO Handled JobCancellationException setState { copy( asyncResetMailConfirmed = Fail(failure) @@ -485,23 +512,22 @@ class LoginViewModel @AssistedInject constructor( SignMode.Unknown -> error("Developer error, invalid sign mode") SignMode.SignIn -> handleLogin(action) SignMode.SignUp -> handleRegisterWith(action) - SignMode.SignInWithMatrixId -> handleDirectLogin(action) + SignMode.SignInWithMatrixId -> handleDirectLogin(action, null) }.exhaustive } - private fun handleDirectLogin(action: LoginAction.LoginOrRegister) { + private fun handleDirectLogin(action: LoginAction.LoginOrRegister, homeServerConnectionConfig: HomeServerConnectionConfig?) { setState { copy( asyncLoginAction = Loading() ) } - // TODO Handle certificate error in this case. Direct login is deactivated now, so we will handle that later - authenticationService.getWellKnownData(action.username, null, object : MatrixCallback { + authenticationService.getWellKnownData(action.username, homeServerConnectionConfig, object : MatrixCallback { override fun onSuccess(data: WellknownResult) { when (data) { is WellknownResult.Prompt -> - onWellknownSuccess(action, data) + onWellknownSuccess(action, data, homeServerConnectionConfig) is WellknownResult.InvalidMatrixId -> { setState { copy( @@ -522,23 +548,26 @@ class LoginViewModel @AssistedInject constructor( } override fun onFailure(failure: Throwable) { - setState { - copy( - asyncLoginAction = Fail(failure) - ) - } + onDirectLoginError(failure) } }) } - private fun onWellknownSuccess(action: LoginAction.LoginOrRegister, wellKnownPrompt: WellknownResult.Prompt) { - val homeServerConnectionConfig = HomeServerConnectionConfig( - homeServerUri = Uri.parse(wellKnownPrompt.homeServerUrl), - identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } - ) + private fun onWellknownSuccess(action: LoginAction.LoginOrRegister, + wellKnownPrompt: WellknownResult.Prompt, + homeServerConnectionConfig: HomeServerConnectionConfig?) { + val alteredHomeServerConnectionConfig = homeServerConnectionConfig + ?.copy( + homeServerUri = Uri.parse(wellKnownPrompt.homeServerUrl), + identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } + ) + ?: HomeServerConnectionConfig( + homeServerUri = Uri.parse(wellKnownPrompt.homeServerUrl), + identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) } + ) authenticationService.directAuthentication( - homeServerConnectionConfig, + alteredHomeServerConnectionConfig, action.username, action.password, action.initialDeviceName, @@ -548,15 +577,29 @@ class LoginViewModel @AssistedInject constructor( } override fun onFailure(failure: Throwable) { - setState { - copy( - asyncLoginAction = Fail(failure) - ) - } + onDirectLoginError(failure) } }) } + private fun onDirectLoginError(failure: Throwable) { + if (failure is Failure.UnrecognizedCertificateFailure) { + // Display this error in a dialog + _viewEvents.post(LoginViewEvents.Failure(failure)) + setState { + copy( + asyncLoginAction = Uninitialized + ) + } + } else { + setState { + copy( + asyncLoginAction = Fail(failure) + ) + } + } + } + private fun handleLogin(action: LoginAction.LoginOrRegister) { val safeLoginWizard = loginWizard @@ -584,7 +627,6 @@ class LoginViewModel @AssistedInject constructor( } override fun onFailure(failure: Throwable) { - // TODO Handled JobCancellationException setState { copy( asyncLoginAction = Fail(failure) @@ -609,7 +651,7 @@ class LoginViewModel @AssistedInject constructor( // Ensure Wizard is ready loginWizard - _viewEvents.post(LoginViewEvents.OnSignModeSelected) + _viewEvents.post(LoginViewEvents.OnSignModeSelected(SignMode.SignIn)) } private fun onFlowResponse(flowResult: FlowResult) { @@ -673,7 +715,10 @@ class LoginViewModel @AssistedInject constructor( setState { copy( - asyncHomeServerLoginFlowRequest = Loading() + asyncHomeServerLoginFlowRequest = Loading(), + // If user has entered https://matrix.org, ensure that server type is ServerType.MatrixOrg + // It is also useful to set the value again in the case of a certificate error on matrix.org + serverType = if (homeServerConnectionConfig.homeServerUri.toString() == matrixOrgUrl) ServerType.MatrixOrg else serverType ) } @@ -682,7 +727,9 @@ class LoginViewModel @AssistedInject constructor( _viewEvents.post(LoginViewEvents.Failure(failure)) setState { copy( - asyncHomeServerLoginFlowRequest = Uninitialized + asyncHomeServerLoginFlowRequest = Uninitialized, + // If we were trying to retrieve matrix.org login flow, also reset the serverType + serverType = if (serverType == ServerType.MatrixOrg) ServerType.Unknown else serverType ) } } diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginViewState.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginViewState.kt index 944d1f7d82..655966ce25 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginViewState.kt @@ -35,7 +35,7 @@ data class LoginViewState( // User choices @PersistState - val serverType: ServerType = ServerType.MatrixOrg, + val serverType: ServerType = ServerType.Unknown, @PersistState val signMode: SignMode = SignMode.Unknown, @PersistState diff --git a/vector/src/main/java/im/vector/riotx/features/login/ServerType.kt b/vector/src/main/java/im/vector/riotx/features/login/ServerType.kt index 4c7007c137..50dfee19f0 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/ServerType.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/ServerType.kt @@ -17,6 +17,7 @@ package im.vector.riotx.features.login enum class ServerType { + Unknown, MatrixOrg, Modular, Other diff --git a/vector/src/main/java/im/vector/riotx/features/signout/soft/SoftLogoutFragment.kt b/vector/src/main/java/im/vector/riotx/features/signout/soft/SoftLogoutFragment.kt index 13b90f26e8..fadcaa8055 100644 --- a/vector/src/main/java/im/vector/riotx/features/signout/soft/SoftLogoutFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/signout/soft/SoftLogoutFragment.kt @@ -93,8 +93,9 @@ class SoftLogoutFragment @Inject constructor( softLogoutViewModel.handle(SoftLogoutAction.SignInAgain(password)) } - override fun signinFallbackSubmit() { - loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnSignModeSelected)) + override fun signinFallbackSubmit() = withState(loginViewModel) { state -> + // The loginViewModel has been prepared for a SSO/login fallback recovery (above) + loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnSignModeSelected(state.signMode))) } override fun clearData() { diff --git a/vector/src/main/res/drawable/bg_login_server_selector.xml b/vector/src/main/res/drawable/bg_login_server_selector.xml index 57be1e5d54..3fcc4e006a 100644 --- a/vector/src/main/res/drawable/bg_login_server_selector.xml +++ b/vector/src/main/res/drawable/bg_login_server_selector.xml @@ -2,6 +2,7 @@ + diff --git a/vector/src/main/res/layout/fragment_login.xml b/vector/src/main/res/layout/fragment_login.xml index a35a60104d..8e7fc9e418 100644 --- a/vector/src/main/res/layout/fragment_login.xml +++ b/vector/src/main/res/layout/fragment_login.xml @@ -106,6 +106,16 @@ + + + - - + - - - - - - + + app:layout_constraintTop_toBottomOf="@+id/loginServerChoiceOther" /> diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index d8aad6f539..c3f0e9df41 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1995,10 +1995,11 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming Alternatively, if you already have an account and you know your Matrix identifier and your password, you can use this method: - Sign in with my Matrix identifier - Sign in - Enter your identifier and your password - User identifier + Sign in with Matrix ID + Sign in with Matrix ID + If you set up an account on a homeserver, use your Matrix ID (e.g. @user:domain.com) and password below. + Matrix ID + If you don’t know your password, go back to reset it. This is not a valid user identifier. Expected format: \'@user:homeserver.org\' Unable to find a valid homeserver. Please check your identifier