Login screens: login and registration fallback

This commit is contained in:
Benoit Marty 2019-11-15 11:57:18 +01:00
parent a1aa16715d
commit b7bfb20a2e
10 changed files with 73 additions and 67 deletions

View File

@ -37,7 +37,7 @@ import im.vector.riotx.features.home.room.detail.RoomDetailFragment
import im.vector.riotx.features.home.room.list.RoomListFragment
import im.vector.riotx.features.login.LoginFragment
import im.vector.riotx.features.login.LoginServerUrlFormFragment
import im.vector.riotx.features.login.LoginSsoFallbackFragment
import im.vector.riotx.features.login.LoginWebFragment
import im.vector.riotx.features.reactions.EmojiSearchResultFragment
import im.vector.riotx.features.roomdirectory.PublicRoomsFragment
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomFragment
@ -124,8 +124,8 @@ interface FragmentModule {
@Binds
@IntoMap
@FragmentKey(LoginSsoFallbackFragment::class)
fun bindLoginSsoFallbackFragment(fragment: LoginSsoFallbackFragment): Fragment
@FragmentKey(LoginWebFragment::class)
fun bindLoginWebFragment(fragment: LoginWebFragment): Fragment
@Binds
@IntoMap

View File

@ -24,7 +24,7 @@ sealed class LoginAction : VectorViewModelAction {
data class UpdateHomeServer(val homeServerUrl: String) : LoginAction()
data class UpdateSignMode(val signMode: SignMode) : LoginAction()
data class Login(val login: String, val password: String) : LoginAction()
data class SsoLoginSuccess(val credentials: Credentials) : LoginAction()
data class WebLoginSuccess(val credentials: Credentials) : LoginAction()
data class InitWith(val loginConfig: LoginConfig) : LoginAction()
// Reset actions

View File

@ -65,11 +65,11 @@ class LoginActivity : VectorBaseActivity() {
loginSharedActionViewModel.observe()
.subscribe {
when (it) {
is LoginNavigation.OpenServerSelection -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginServerSelectionFragment::class.java)
is LoginNavigation.OnServerSelectionDone -> onServerSelectionDone()
is LoginNavigation.OnSignModeSelected -> onSignModeSelected()
is LoginNavigation.OnLoginFlowRetrieved -> onLoginFlowRetrieved()
is LoginNavigation.OnSsoLoginFallbackError -> onSsoLoginFallbackError(it)
is LoginNavigation.OpenServerSelection -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginServerSelectionFragment::class.java)
is LoginNavigation.OnServerSelectionDone -> onServerSelectionDone()
is LoginNavigation.OnSignModeSelected -> onSignModeSelected()
is LoginNavigation.OnLoginFlowRetrieved -> onLoginFlowRetrieved()
is LoginNavigation.OnWebLoginError -> onWebLoginError(it)
}
}
.disposeOnDestroy()
@ -97,14 +97,14 @@ class LoginActivity : VectorBaseActivity() {
loginLoading.isVisible = loginViewState.isLoading()
}
private fun onSsoLoginFallbackError(onSsoLoginFallbackError: LoginNavigation.OnSsoLoginFallbackError) {
private fun onWebLoginError(onWebLoginError: LoginNavigation.OnWebLoginError) {
// Pop the backstack
supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
// And inform the user
AlertDialog.Builder(this)
.setTitle(R.string.dialog_title_error)
.setMessage(getString(R.string.login_sso_error_message, onSsoLoginFallbackError.description, onSsoLoginFallbackError.errorCode))
.setMessage(getString(R.string.login_sso_error_message, onWebLoginError.description, onWebLoginError.errorCode))
.setPositiveButton(R.string.ok, null)
.show()
}
@ -124,17 +124,28 @@ class LoginActivity : VectorBaseActivity() {
SignMode.SignIn -> {
// It depends on the LoginMode
withState(loginViewModel) {
when (it.asyncHomeServerLoginFlowRequest.invoke()) {
null -> error("Developer error")
LoginMode.Password -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginFragment::class.java)
LoginMode.Sso -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginSsoFallbackFragment::class.java)
LoginMode.Unsupported -> TODO() // TODO Import Fallback login fragment from Riot-Android
when (val loginMode = it.asyncHomeServerLoginFlowRequest.invoke()) {
null -> error("Developer error")
LoginMode.Password -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginFragment::class.java)
LoginMode.Sso -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginWebFragment::class.java)
is LoginMode.Unsupported -> onLoginModeNotSupported(loginMode)
}
}
}
}
}
private fun onLoginModeNotSupported(unsupportedLoginMode: LoginMode.Unsupported) {
AlertDialog.Builder(this)
.setTitle(R.string.app_name)
.setMessage(getString(R.string.login_mode_not_supported, unsupportedLoginMode.types.joinToString { "'$it'" }))
.setPositiveButton(R.string.yes) { _, _ ->
addFragmentToBackstack(R.id.loginFragmentContainer, LoginWebFragment::class.java)
}
.setNegativeButton(R.string.no, null)
.show()
}
override fun onResume() {
super.onResume()

View File

@ -105,11 +105,6 @@ class LoginFragment @Inject constructor(
loginSubmit.setOnClickListener { authenticate() }
}
// // TODO Move to server selection screen
// private fun openSso() {
// loginSharedActionViewModel.post(LoginNavigation.OpenSsoLoginFallback)
// }
private fun setupPasswordReveal() {
passwordShown = false

View File

@ -16,8 +16,8 @@
package im.vector.riotx.features.login
enum class LoginMode {
Password,
Sso,
Unsupported
sealed class LoginMode {
object Password : LoginMode()
object Sso : LoginMode()
data class Unsupported(val types: List<String>) : LoginMode()
}

View File

@ -24,6 +24,5 @@ sealed class LoginNavigation : VectorSharedAction {
object OnServerSelectionDone : LoginNavigation()
object OnLoginFlowRetrieved : LoginNavigation()
object OnSignModeSelected : LoginNavigation()
//object OpenSsoLoginFallback : LoginNavigation()
data class OnSsoLoginFallbackError(val errorCode: Int, val description: String, val failingUrl: String) : LoginNavigation()
data class OnWebLoginError(val errorCode: Int, val description: String, val failingUrl: String) : LoginNavigation()
}

View File

@ -73,7 +73,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
is LoginAction.InitWith -> handleInitWith(action)
is LoginAction.UpdateHomeServer -> handleUpdateHomeserver(action)
is LoginAction.Login -> handleLogin(action)
is LoginAction.SsoLoginSuccess -> handleSsoLoginSuccess(action)
is LoginAction.WebLoginSuccess -> handleWebLoginSuccess(action)
is LoginAction.ResetAction -> handleResetAction(action)
}
}
@ -167,7 +167,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
}
}
private fun handleSsoLoginSuccess(action: LoginAction.SsoLoginSuccess) {
private fun handleWebLoginSuccess(action: LoginAction.WebLoginSuccess) {
val homeServerConnectionConfigFinal = homeServerConnectionConfig
if (homeServerConnectionConfigFinal == null) {
@ -233,7 +233,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
// SSO login is taken first
data.flows.any { it.type == InteractiveAuthenticationFlow.TYPE_LOGIN_SSO } -> LoginMode.Sso
data.flows.any { it.type == InteractiveAuthenticationFlow.TYPE_LOGIN_PASSWORD } -> LoginMode.Password
else -> LoginMode.Unsupported
else -> LoginMode.Unsupported(data.flows.mapNotNull { it.type }.toList())
}
setState {

View File

@ -34,47 +34,47 @@ import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.riotx.R
import kotlinx.android.synthetic.main.fragment_login_sso_fallback.*
import kotlinx.android.synthetic.main.fragment_login_web.*
import timber.log.Timber
import java.net.URLDecoder
import javax.inject.Inject
/**
* Only login is supported for the moment
* This screen is displayed for SSO login and also when the application does not support login flow or registration flow
* of the homeserfver, as a fallback to login or to create an account
*/
class LoginSsoFallbackFragment @Inject constructor() : AbstractLoginFragment() {
class LoginWebFragment @Inject constructor() : AbstractLoginFragment() {
private var homeServerUrl: String = ""
private lateinit var homeServerUrl: String
private lateinit var signMode: SignMode
enum class Mode {
MODE_LOGIN,
// Not supported in RiotX for the moment
MODE_REGISTER
}
// Mode (MODE_LOGIN or MODE_REGISTER)
private var mMode = Mode.MODE_LOGIN
override fun getLayoutResId() = R.layout.fragment_login_sso_fallback
override fun getLayoutResId() = R.layout.fragment_login_web
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupToolbar(login_sso_fallback_toolbar)
login_sso_fallback_toolbar.title = getString(R.string.login)
homeServerUrl = loginViewModel.getHomeServerUrl()
signMode = loginViewModel.signMode.takeIf { it != SignMode.Unknown } ?: error("Developer error: Invalid sign mode")
setupWebview()
setupToolbar(loginWebToolbar)
setupTitle()
setupWebView()
}
private fun setupTitle() {
loginWebToolbar.title = when (signMode) {
SignMode.SignIn -> getString(R.string.login_signin)
else -> getString(R.string.login_signup)
}
}
@SuppressLint("SetJavaScriptEnabled")
private fun setupWebview() {
login_sso_fallback_webview.settings.javaScriptEnabled = true
private fun setupWebView() {
loginWebWebView.settings.javaScriptEnabled = true
// Due to https://developers.googleblog.com/2016/08/modernizing-oauth-interactions-in-native-apps.html, we hack
// the user agent to bypass the limitation of Google, as a quick fix (a proper solution will be to use the SSO SDK)
login_sso_fallback_webview.settings.userAgentString = "Mozilla/5.0 Google"
homeServerUrl = loginViewModel.getHomeServerUrl()
loginWebWebView.settings.userAgentString = "Mozilla/5.0 Google"
if (!homeServerUrl.endsWith("/")) {
homeServerUrl += "/"
@ -109,14 +109,14 @@ class LoginSsoFallbackFragment @Inject constructor() : AbstractLoginFragment() {
}
private fun launchWebView() {
if (mMode == Mode.MODE_LOGIN) {
login_sso_fallback_webview.loadUrl(homeServerUrl + "_matrix/static/client/login/")
if (signMode == SignMode.SignIn) {
loginWebWebView.loadUrl(homeServerUrl + "_matrix/static/client/login/")
} else {
// MODE_REGISTER
login_sso_fallback_webview.loadUrl(homeServerUrl + "_matrix/static/client/register/")
loginWebWebView.loadUrl(homeServerUrl + "_matrix/static/client/register/")
}
login_sso_fallback_webview.webViewClient = object : WebViewClient() {
loginWebWebView.webViewClient = object : WebViewClient() {
override fun onReceivedSslError(view: WebView, handler: SslErrorHandler,
error: SslError) {
AlertDialog.Builder(requireActivity())
@ -131,20 +131,20 @@ class LoginSsoFallbackFragment @Inject constructor() : AbstractLoginFragment() {
}
false
})
.setCancelable(false)
.show()
}
override fun onReceivedError(view: WebView, errorCode: Int, description: String, failingUrl: String) {
super.onReceivedError(view, errorCode, description, failingUrl)
// on error case, close this fragment
loginSharedActionViewModel.post(LoginNavigation.OnSsoLoginFallbackError(errorCode, description, failingUrl))
loginSharedActionViewModel.post(LoginNavigation.OnWebLoginError(errorCode, description, failingUrl))
}
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
login_sso_fallback_toolbar.subtitle = url
loginWebToolbar.subtitle = url
}
override fun onPageFinished(view: WebView, url: String) {
@ -160,7 +160,7 @@ class LoginSsoFallbackFragment @Inject constructor() : AbstractLoginFragment() {
view.loadUrl(mxcJavascriptSendObjectMessage)
if (mMode == Mode.MODE_LOGIN) {
if (signMode == SignMode.SignIn) {
// The function the fallback page calls when the login is complete
val mxcJavascriptOnRegistered = "javascript:window.matrixLogin.onLogin = function(response) {" +
" sendObjectMessage({ 'action': 'onLogin', 'credentials': response });" +
@ -227,7 +227,7 @@ class LoginSsoFallbackFragment @Inject constructor() : AbstractLoginFragment() {
if (parameters != null) {
val action = parameters["action"] as String
if (mMode == Mode.MODE_LOGIN) {
if (signMode == SignMode.SignIn) {
try {
if (action == "onLogin") {
@Suppress("UNCHECKED_CAST")
@ -248,7 +248,7 @@ class LoginSsoFallbackFragment @Inject constructor() : AbstractLoginFragment() {
refreshToken = null
)
loginViewModel.handle(LoginAction.SsoLoginSuccess(safeCredentials))
loginViewModel.handle(LoginAction.WebLoginSuccess(safeCredentials))
}
}
} catch (e: Exception) {
@ -273,7 +273,7 @@ class LoginSsoFallbackFragment @Inject constructor() : AbstractLoginFragment() {
refreshToken = null
)
loginViewModel.handle(LoginAction.SsoLoginSuccess(credentials))
loginViewModel.handle(LoginAction.WebLoginSuccess(credentials))
}
}
}
@ -291,8 +291,8 @@ class LoginSsoFallbackFragment @Inject constructor() : AbstractLoginFragment() {
}
override fun onBackPressed(): Boolean {
return if (login_sso_fallback_webview.canGoBack()) {
login_sso_fallback_webview.goBack()
return if (loginWebWebView.canGoBack()) {
loginWebWebView.goBack()
true
} else {
super.onBackPressed()

View File

@ -6,7 +6,7 @@
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/login_sso_fallback_toolbar"
android:id="@+id/loginWebToolbar"
style="@style/VectorToolbarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -15,7 +15,7 @@
tools:title="@string/auth_login" />
<WebView
android:id="@+id/login_sso_fallback_webview"
android:id="@+id/loginWebWebView"
android:layout_width="match_parent"
android:layout_height="match_parent" />

View File

@ -53,5 +53,6 @@
<string name="login_server_url_form_other_notice">Enter the address of a server or a Riot you want to connect to</string>
<string name="login_sso_error_message">An error occurred when loading the page: %1$s (%2$d)</string>
<string name="login_mode_not_supported">The application is not able to signin to this homeserver. The homeserver supports the following signin type(s): %1$s.\n\nDo you want to signin using a web client?</string>
</resources>