mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-02-09 00:29:00 +01:00
Login: add SSO support
This commit is contained in:
parent
db8ea0f5e8
commit
5fbd271b1c
@ -17,6 +17,7 @@
|
||||
package im.vector.matrix.android.api.auth
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
@ -62,4 +63,9 @@ interface Authenticator {
|
||||
* @return the associated session if any, or null
|
||||
*/
|
||||
fun getSession(sessionParams: SessionParams): Session?
|
||||
|
||||
/**
|
||||
* Create a session after a SSO successful login
|
||||
*/
|
||||
fun createSessionFromSso(credentials: Credentials, homeServerConnectionConfig: HomeServerConnectionConfig): Session
|
||||
}
|
@ -112,6 +112,12 @@ internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
|
||||
sessionManager.getOrCreateSession(sessionParams)
|
||||
}
|
||||
|
||||
override fun createSessionFromSso(credentials: Credentials, homeServerConnectionConfig: HomeServerConnectionConfig): Session {
|
||||
val sessionParams = SessionParams(credentials, homeServerConnectionConfig)
|
||||
sessionParamsStore.save(sessionParams)
|
||||
return sessionManager.getOrCreateSession(sessionParams)
|
||||
}
|
||||
|
||||
private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI {
|
||||
val retrofit = retrofitFactory.create(okHttpClient, homeServerConnectionConfig.homeServerUri.toString())
|
||||
return retrofit.create(AuthAPI::class.java)
|
||||
|
@ -30,4 +30,12 @@ data class InteractiveAuthenticationFlow(
|
||||
|
||||
@Json(name = "stages")
|
||||
val stages: List<String>? = null
|
||||
)
|
||||
) {
|
||||
|
||||
companion object {
|
||||
// Possible values for type
|
||||
const val TYPE_LOGIN_SSO = "m.login.sso"
|
||||
const val TYPE_LOGIN_TOKEN = "m.login.token"
|
||||
const val TYPE_LOGIN_PASSWORD = "m.login.password"
|
||||
}
|
||||
}
|
@ -48,6 +48,7 @@ import im.vector.riotx.features.home.room.list.RoomListFragment
|
||||
import im.vector.riotx.features.invite.VectorInviteView
|
||||
import im.vector.riotx.features.login.LoginActivity
|
||||
import im.vector.riotx.features.login.LoginFragment
|
||||
import im.vector.riotx.features.login.LoginSsoFallbackFragment
|
||||
import im.vector.riotx.features.media.ImageMediaViewerActivity
|
||||
import im.vector.riotx.features.media.VideoMediaViewerActivity
|
||||
import im.vector.riotx.features.navigation.Navigator
|
||||
@ -127,6 +128,8 @@ interface ScreenComponent {
|
||||
|
||||
fun inject(loginFragment: LoginFragment)
|
||||
|
||||
fun inject(loginSsoFallbackFragment: LoginSsoFallbackFragment)
|
||||
|
||||
fun inject(sasVerificationIncomingFragment: SASVerificationIncomingFragment)
|
||||
|
||||
fun inject(quickReactionFragment: QuickReactionFragment)
|
||||
|
@ -16,9 +16,12 @@
|
||||
|
||||
package im.vector.riotx.features.login
|
||||
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
|
||||
sealed class LoginActions {
|
||||
|
||||
data class UpdateHomeServer(val homeServerUrl: String) : LoginActions()
|
||||
data class Login(val login: String, val password: String) : LoginActions()
|
||||
data class SsoLoginSuccess(val credentials: Credentials) : LoginActions()
|
||||
|
||||
}
|
||||
|
@ -18,16 +18,30 @@ package im.vector.riotx.features.login
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.viewModel
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.di.ScreenComponent
|
||||
import im.vector.riotx.core.extensions.addFragment
|
||||
import im.vector.riotx.core.extensions.addFragmentToBackstack
|
||||
import im.vector.riotx.core.extensions.observeEvent
|
||||
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||
import im.vector.riotx.features.disclaimer.showDisclaimerDialog
|
||||
import im.vector.riotx.features.home.HomeActivity
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
class LoginActivity : VectorBaseActivity() {
|
||||
|
||||
// Supported navigation actions for this Activity
|
||||
sealed class Navigation {
|
||||
object OpenSsoLoginFallback : Navigation()
|
||||
object GoBack : Navigation()
|
||||
}
|
||||
|
||||
private val loginViewModel: LoginViewModel by viewModel()
|
||||
|
||||
@Inject lateinit var loginViewModelFactory: LoginViewModel.Factory
|
||||
|
||||
|
||||
@ -41,6 +55,21 @@ class LoginActivity : VectorBaseActivity() {
|
||||
if (isFirstCreation()) {
|
||||
addFragment(LoginFragment(), R.id.simpleFragmentContainer)
|
||||
}
|
||||
|
||||
loginViewModel.navigationLiveData.observeEvent(this) {
|
||||
when (it) {
|
||||
is Navigation.OpenSsoLoginFallback -> addFragmentToBackstack(LoginSsoFallbackFragment(), R.id.simpleFragmentContainer)
|
||||
is Navigation.GoBack -> supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
|
||||
}
|
||||
}
|
||||
|
||||
loginViewModel.selectSubscribe(this, LoginViewState::asyncLoginAction) {
|
||||
if (it is Success) {
|
||||
val intent = HomeActivity.newIntent(this)
|
||||
startActivity(intent)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -20,6 +20,7 @@ import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.transition.TransitionManager
|
||||
import com.airbnb.mvrx.*
|
||||
import com.jakewharton.rxbinding3.view.focusChanges
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
@ -30,12 +31,11 @@ import im.vector.riotx.core.extensions.setTextWithColoredPart
|
||||
import im.vector.riotx.core.extensions.showPassword
|
||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||
import im.vector.riotx.core.utils.openUrlInExternalBrowser
|
||||
import im.vector.riotx.features.home.HomeActivity
|
||||
import im.vector.riotx.features.homeserver.ServerUrlsRepository
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.functions.Function3
|
||||
import io.reactivex.rxkotlin.subscribeBy
|
||||
import kotlinx.android.synthetic.main.activity_login.*
|
||||
import kotlinx.android.synthetic.main.fragment_login.*
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
@ -51,7 +51,7 @@ class LoginFragment : VectorBaseFragment() {
|
||||
|
||||
@Inject lateinit var errorFormatter: ErrorFormatter
|
||||
|
||||
override fun getLayoutResId() = R.layout.activity_login
|
||||
override fun getLayoutResId() = R.layout.fragment_login
|
||||
|
||||
override fun injectWith(injector: ScreenComponent) {
|
||||
injector.inject(this)
|
||||
@ -107,6 +107,12 @@ class LoginFragment : VectorBaseFragment() {
|
||||
.subscribeBy { authenticateButton.isEnabled = it }
|
||||
.disposeOnDestroy()
|
||||
authenticateButton.setOnClickListener { authenticate() }
|
||||
|
||||
authenticateButtonSso.setOnClickListener { openSso() }
|
||||
}
|
||||
|
||||
private fun openSso() {
|
||||
viewModel.openSso()
|
||||
}
|
||||
|
||||
private fun setupPasswordReveal() {
|
||||
@ -128,25 +134,53 @@ class LoginFragment : VectorBaseFragment() {
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(viewModel) { state ->
|
||||
TransitionManager.beginDelayedTransition(login_fragment)
|
||||
|
||||
when (state.asyncHomeServerLoginFlowRequest) {
|
||||
is Loading -> {
|
||||
is Incomplete -> {
|
||||
progressBar.isVisible = true
|
||||
touchArea.isVisible = true
|
||||
|
||||
loginField.isVisible = false
|
||||
passwordContainer.isVisible = false
|
||||
authenticateButton.isVisible = false
|
||||
authenticateButtonSso.isVisible = false
|
||||
passwordShown = false
|
||||
renderPasswordField()
|
||||
}
|
||||
is Fail -> {
|
||||
is Fail -> {
|
||||
progressBar.isVisible = false
|
||||
touchArea.isVisible = false
|
||||
loginField.isVisible = false
|
||||
passwordContainer.isVisible = false
|
||||
authenticateButton.isVisible = false
|
||||
authenticateButtonSso.isVisible = false
|
||||
Toast.makeText(requireActivity(), "Authenticate failure: ${state.asyncHomeServerLoginFlowRequest.error}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
is Success -> {
|
||||
is Success -> {
|
||||
progressBar.isVisible = false
|
||||
touchArea.isVisible = false
|
||||
|
||||
// Check login flow
|
||||
// TODO
|
||||
when (state.asyncHomeServerLoginFlowRequest()) {
|
||||
LoginMode.Password -> {
|
||||
loginField.isVisible = true
|
||||
passwordContainer.isVisible = true
|
||||
authenticateButton.isVisible = true
|
||||
authenticateButtonSso.isVisible = false
|
||||
}
|
||||
LoginMode.Sso -> {
|
||||
loginField.isVisible = false
|
||||
passwordContainer.isVisible = false
|
||||
authenticateButton.isVisible = false
|
||||
authenticateButtonSso.isVisible = true
|
||||
}
|
||||
LoginMode.Unsupported -> {
|
||||
loginField.isVisible = false
|
||||
passwordContainer.isVisible = false
|
||||
authenticateButton.isVisible = false
|
||||
authenticateButtonSso.isVisible = false
|
||||
Toast.makeText(requireActivity(), "None of the homeserver login mode is supported by RiotX", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,11 +197,8 @@ class LoginFragment : VectorBaseFragment() {
|
||||
touchArea.isVisible = false
|
||||
Toast.makeText(requireActivity(), "Authenticate failure: ${state.asyncLoginAction.error}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
is Success -> {
|
||||
val intent = HomeActivity.newIntent(requireActivity())
|
||||
startActivity(intent)
|
||||
requireActivity().finish()
|
||||
}
|
||||
// Success is handled by the LoginActivity
|
||||
is Success -> Unit
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,303 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.login
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.DialogInterface
|
||||
import android.graphics.Bitmap
|
||||
import android.net.http.SslError
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import android.webkit.SslErrorHandler
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.di.ScreenComponent
|
||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||
import kotlinx.android.synthetic.main.fragment_login_sso_fallback.*
|
||||
import timber.log.Timber
|
||||
import java.net.URLDecoder
|
||||
|
||||
|
||||
/**
|
||||
* Only login is supported for the moment
|
||||
*/
|
||||
class LoginSsoFallbackFragment : VectorBaseFragment() {
|
||||
|
||||
private val viewModel: LoginViewModel by activityViewModel()
|
||||
|
||||
var homeServerUrl: String = ""
|
||||
|
||||
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 injectWith(injector: ScreenComponent) {
|
||||
injector.inject(this)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
setupToolbar(login_sso_fallback_toolbar)
|
||||
requireActivity().setTitle(R.string.login)
|
||||
|
||||
setupWebview()
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
private fun setupWebview() {
|
||||
login_sso_fallback_webview.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 = viewModel.getHomeServerUrl()
|
||||
|
||||
if (!homeServerUrl.endsWith("/")) {
|
||||
homeServerUrl += "/"
|
||||
}
|
||||
|
||||
// AppRTC requires third party cookies to work
|
||||
val cookieManager = android.webkit.CookieManager.getInstance()
|
||||
|
||||
// clear the cookies must be cleared
|
||||
if (cookieManager == null) {
|
||||
launchWebView()
|
||||
} else {
|
||||
if (!cookieManager.hasCookies()) {
|
||||
launchWebView()
|
||||
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
try {
|
||||
cookieManager.removeAllCookie()
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, " cookieManager.removeAllCookie() fails")
|
||||
}
|
||||
|
||||
launchWebView()
|
||||
} else {
|
||||
try {
|
||||
cookieManager.removeAllCookies { launchWebView() }
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, " cookieManager.removeAllCookie() fails")
|
||||
launchWebView()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchWebView() {
|
||||
if (mMode == Mode.MODE_LOGIN) {
|
||||
login_sso_fallback_webview.loadUrl(homeServerUrl + "_matrix/static/client/login/")
|
||||
} else {
|
||||
// MODE_REGISTER
|
||||
login_sso_fallback_webview.loadUrl(homeServerUrl + "_matrix/static/client/register/")
|
||||
}
|
||||
|
||||
login_sso_fallback_webview.webViewClient = object : WebViewClient() {
|
||||
override fun onReceivedSslError(view: WebView, handler: SslErrorHandler,
|
||||
error: SslError) {
|
||||
AlertDialog.Builder(requireActivity())
|
||||
.setMessage(R.string.ssl_could_not_verify)
|
||||
.setPositiveButton(R.string.ssl_trust) { dialog, which -> handler.proceed() }
|
||||
.setNegativeButton(R.string.ssl_do_not_trust) { dialog, which -> handler.cancel() }
|
||||
.setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event ->
|
||||
if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
handler.cancel()
|
||||
dialog.dismiss()
|
||||
return@OnKeyListener true
|
||||
}
|
||||
false
|
||||
})
|
||||
.show()
|
||||
}
|
||||
|
||||
override fun onReceivedError(view: WebView, errorCode: Int, description: String, failingUrl: String) {
|
||||
super.onReceivedError(view, errorCode, description, failingUrl)
|
||||
|
||||
// on error case, close this activity
|
||||
viewModel.goBack()
|
||||
}
|
||||
|
||||
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
|
||||
super.onPageStarted(view, url, favicon)
|
||||
|
||||
login_sso_fallback_toolbar.subtitle = url
|
||||
}
|
||||
|
||||
override fun onPageFinished(view: WebView, url: String) {
|
||||
// avoid infinite onPageFinished call
|
||||
if (url.startsWith("http")) {
|
||||
// Generic method to make a bridge between JS and the UIWebView
|
||||
val mxcJavascriptSendObjectMessage = "javascript:window.sendObjectMessage = function(parameters) {" +
|
||||
" var iframe = document.createElement('iframe');" +
|
||||
" iframe.setAttribute('src', 'js:' + JSON.stringify(parameters));" +
|
||||
" document.documentElement.appendChild(iframe);" +
|
||||
" iframe.parentNode.removeChild(iframe); iframe = null;" +
|
||||
" };"
|
||||
|
||||
view.loadUrl(mxcJavascriptSendObjectMessage)
|
||||
|
||||
if (mMode == Mode.MODE_LOGIN) {
|
||||
// The function the fallback page calls when the login is complete
|
||||
val mxcJavascriptOnRegistered = "javascript:window.matrixLogin.onLogin = function(response) {" +
|
||||
" sendObjectMessage({ 'action': 'onLogin', 'credentials': response });" +
|
||||
" };"
|
||||
|
||||
view.loadUrl(mxcJavascriptOnRegistered)
|
||||
} else {
|
||||
// MODE_REGISTER
|
||||
// The function the fallback page calls when the registration is complete
|
||||
val mxcJavascriptOnRegistered = "javascript:window.matrixRegistration.onRegistered" +
|
||||
" = function(homeserverUrl, userId, accessToken) {" +
|
||||
" sendObjectMessage({ 'action': 'onRegistered'," +
|
||||
" 'homeServer': homeserverUrl," +
|
||||
" 'userId': userId," +
|
||||
" 'accessToken': accessToken });" +
|
||||
" };"
|
||||
|
||||
view.loadUrl(mxcJavascriptOnRegistered)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Example of (formatted) url for MODE_LOGIN:
|
||||
*
|
||||
* <pre>
|
||||
* js:{
|
||||
* "action":"onLogin",
|
||||
* "credentials":{
|
||||
* "user_id":"@user:matrix.org",
|
||||
* "access_token":"[ACCESS_TOKEN]",
|
||||
* "home_server":"matrix.org",
|
||||
* "device_id":"[DEVICE_ID]",
|
||||
* "well_known":{
|
||||
* "m.homeserver":{
|
||||
* "base_url":"https://matrix.org/"
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
* @param view
|
||||
* @param url
|
||||
* @return
|
||||
*/
|
||||
override fun shouldOverrideUrlLoading(view: WebView, url: String?): Boolean {
|
||||
if (null != url && url.startsWith("js:")) {
|
||||
var json = url.substring(3)
|
||||
var parameters: Map<String, Any>? = null
|
||||
|
||||
try {
|
||||
// URL decode
|
||||
json = URLDecoder.decode(json, "UTF-8")
|
||||
|
||||
val adapter = MoshiProvider.providesMoshi().adapter(Map::class.java)
|
||||
|
||||
parameters = adapter.fromJson(json) as Map<String, Any>?
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## shouldOverrideUrlLoading() : fromJson failed")
|
||||
}
|
||||
|
||||
// succeeds to parse parameters
|
||||
if (parameters != null) {
|
||||
val action = parameters["action"] as String
|
||||
|
||||
if (mMode == Mode.MODE_LOGIN) {
|
||||
try {
|
||||
if (action == "onLogin") {
|
||||
val credentials = parameters["credentials"] as Map<String, String>
|
||||
|
||||
val userId = credentials["user_id"]
|
||||
val accessToken = credentials["access_token"]
|
||||
val homeServer = credentials["home_server"]
|
||||
val deviceId = credentials["device_id"]
|
||||
|
||||
// check if the parameters are defined
|
||||
if (null != homeServer && null != userId && null != accessToken) {
|
||||
val credentials = Credentials(
|
||||
userId = userId,
|
||||
accessToken = accessToken,
|
||||
homeServer = homeServer,
|
||||
deviceId = deviceId,
|
||||
refreshToken = null
|
||||
)
|
||||
|
||||
viewModel.handle(LoginActions.SsoLoginSuccess(credentials))
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## shouldOverrideUrlLoading() : failed")
|
||||
}
|
||||
|
||||
} else {
|
||||
// MODE_REGISTER
|
||||
// check the required parameters
|
||||
if (action == "onRegistered") {
|
||||
// TODO The keys are very strange, this code comes from Riot-Android...
|
||||
if (parameters.containsKey("homeServer")
|
||||
&& parameters.containsKey("userId")
|
||||
&& parameters.containsKey("accessToken")) {
|
||||
|
||||
// We cannot parse Credentials here because of https://github.com/matrix-org/synapse/issues/4756
|
||||
// Build on object manually
|
||||
val credentials = Credentials(
|
||||
userId = parameters["userId"] as String,
|
||||
accessToken = parameters["accessToken"] as String,
|
||||
homeServer = parameters["homeServer"] as String,
|
||||
// TODO We need deviceId on RiotX...
|
||||
deviceId = "TODO",
|
||||
refreshToken = null
|
||||
)
|
||||
|
||||
viewModel.handle(LoginActions.SsoLoginSuccess(credentials))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return super.shouldOverrideUrlLoading(view, url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBackPressed(): Boolean {
|
||||
return if (login_sso_fallback_webview.canGoBack()) {
|
||||
login_sso_fallback_webview.goBack()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
@ -16,6 +16,8 @@
|
||||
|
||||
package im.vector.riotx.features.login
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import arrow.core.Try
|
||||
import com.airbnb.mvrx.*
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
@ -25,10 +27,12 @@ import im.vector.matrix.android.api.auth.Authenticator
|
||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.internal.auth.data.InteractiveAuthenticationFlow
|
||||
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
|
||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||
import im.vector.riotx.core.extensions.configureAndStart
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import im.vector.riotx.core.utils.LiveEvent
|
||||
import im.vector.riotx.features.notifications.PushRuleTriggerListener
|
||||
|
||||
class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginViewState,
|
||||
@ -50,9 +54,9 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
|
||||
}
|
||||
private val _navigationLiveData = MutableLiveData<LiveEvent<LoginActivity.Navigation>>()
|
||||
val navigationLiveData: LiveData<LiveEvent<LoginActivity.Navigation>>
|
||||
get() = _navigationLiveData
|
||||
|
||||
var homeServerConnectionConfig: HomeServerConnectionConfig? = null
|
||||
private var currentTask: Cancelable? = null
|
||||
@ -62,6 +66,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
||||
when (action) {
|
||||
is LoginActions.UpdateHomeServer -> handleUpdateHomeserver(action)
|
||||
is LoginActions.Login -> handleLogin(action)
|
||||
is LoginActions.SsoLoginSuccess -> handleSsoLoginSuccess(action)
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,14 +88,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
||||
|
||||
authenticator.authenticate(homeServerConnectionConfigFinal, action.login, action.password, object : MatrixCallback<Session> {
|
||||
override fun onSuccess(data: Session) {
|
||||
activeSessionHolder.setActiveSession(data)
|
||||
data.configureAndStart(pushRuleTriggerListener)
|
||||
|
||||
setState {
|
||||
copy(
|
||||
asyncLoginAction = Success(Unit)
|
||||
)
|
||||
}
|
||||
onSessionCreated(data)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
@ -104,6 +102,24 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSessionCreated(session: Session) {
|
||||
activeSessionHolder.setActiveSession(session)
|
||||
session.configureAndStart(pushRuleTriggerListener)
|
||||
|
||||
setState {
|
||||
copy(
|
||||
asyncLoginAction = Success(Unit)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSsoLoginSuccess(action: LoginActions.SsoLoginSuccess) {
|
||||
val session = authenticator.createSessionFromSso(action.credentials, homeServerConnectionConfig!!)
|
||||
|
||||
onSessionCreated(session)
|
||||
}
|
||||
|
||||
|
||||
private fun handleUpdateHomeserver(action: LoginActions.UpdateHomeServer) {
|
||||
currentTask?.cancel()
|
||||
|
||||
@ -120,18 +136,16 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
||||
// This is invalid
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Fail(Throwable("Baf format"))
|
||||
asyncHomeServerLoginFlowRequest = Fail(Throwable("Bad format"))
|
||||
)
|
||||
}
|
||||
} else {
|
||||
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Loading()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
currentTask = authenticator.getLoginFlow(homeServerConnectionConfigFinal, object : MatrixCallback<LoginFlowResponse> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
setState {
|
||||
@ -142,28 +156,41 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
||||
}
|
||||
|
||||
override fun onSuccess(data: LoginFlowResponse) {
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Success(data)
|
||||
)
|
||||
val loginMode = when {
|
||||
// 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
|
||||
}
|
||||
|
||||
handleLoginFlowResponse(data)
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Success(loginMode)
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleLoginFlowResponse(loginFlowResponse: LoginFlowResponse) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
|
||||
currentTask?.cancel()
|
||||
}
|
||||
|
||||
fun openSso() {
|
||||
// Navigate to SSO
|
||||
_navigationLiveData.postValue(LiveEvent(LoginActivity.Navigation.OpenSsoLoginFallback))
|
||||
}
|
||||
|
||||
fun goBack() {
|
||||
// Navigate back
|
||||
_navigationLiveData.postValue(LiveEvent(LoginActivity.Navigation.GoBack))
|
||||
}
|
||||
|
||||
fun getHomeServerUrl(): String {
|
||||
return homeServerConnectionConfig?.homeServerUri?.toString() ?: ""
|
||||
}
|
||||
}
|
@ -19,9 +19,15 @@ package im.vector.riotx.features.login
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
|
||||
|
||||
data class LoginViewState(
|
||||
val asyncLoginAction: Async<Unit> = Uninitialized,
|
||||
val asyncHomeServerLoginFlowRequest: Async<LoginFlowResponse> = Uninitialized
|
||||
val asyncHomeServerLoginFlowRequest: Async<LoginMode> = Uninitialized
|
||||
) : MvRxState
|
||||
|
||||
|
||||
enum class LoginMode {
|
||||
Password,
|
||||
Sso,
|
||||
Unsupported
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/login_fragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
@ -54,6 +55,7 @@
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/passwordContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp">
|
||||
@ -114,6 +116,16 @@
|
||||
android:layout_marginTop="22dp"
|
||||
android:text="@string/auth_login" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/authenticateButtonSso"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginTop="22dp"
|
||||
android:text="@string/auth_login_sso"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
22
vector/src/main/res/layout/fragment_login_sso_fallback.xml
Normal file
22
vector/src/main/res/layout/fragment_login_sso_fallback.xml
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/login_sso_fallback_toolbar"
|
||||
style="@style/VectorToolbarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:subtitle="https://www.example.org"
|
||||
tools:title="@string/auth_login" />
|
||||
|
||||
<WebView
|
||||
android:id="@+id/login_sso_fallback_webview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</LinearLayout>
|
||||
|
Loading…
x
Reference in New Issue
Block a user