SoftLogout: recovery with SSO
This commit is contained in:
parent
183d6b53bd
commit
4e74b545ad
@ -17,6 +17,7 @@
|
||||
package im.vector.matrix.android.api.session.signout
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
||||
/**
|
||||
@ -31,6 +32,12 @@ interface SignOutService {
|
||||
fun signInAgain(password: String,
|
||||
callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Update the session with credentials received after SSO
|
||||
*/
|
||||
fun updateCredentials(credentials: Credentials,
|
||||
callback: MatrixCallback<Unit>): Cancelable
|
||||
|
||||
/**
|
||||
* Sign out, and release the session, clear all the session data, including crypto data
|
||||
* @param sigOutFromHomeserver true if the sign out request has to be done
|
||||
|
@ -17,14 +17,21 @@
|
||||
package im.vector.matrix.android.internal.session.signout
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.api.session.signout.SignOutService
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import im.vector.matrix.android.internal.task.launchToCallback
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultSignOutService @Inject constructor(private val signOutTask: SignOutTask,
|
||||
private val signInAgainTask: SignInAgainTask,
|
||||
private val sessionParamsStore: SessionParamsStore,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val taskExecutor: TaskExecutor) : SignOutService {
|
||||
|
||||
override fun signInAgain(password: String,
|
||||
@ -36,6 +43,13 @@ internal class DefaultSignOutService @Inject constructor(private val signOutTask
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun updateCredentials(credentials: Credentials,
|
||||
callback: MatrixCallback<Unit>): Cancelable {
|
||||
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
sessionParamsStore.updateCredentials(credentials)
|
||||
}
|
||||
}
|
||||
|
||||
override fun signOut(sigOutFromHomeserver: Boolean,
|
||||
callback: MatrixCallback<Unit>): Cancelable {
|
||||
return signOutTask
|
||||
|
@ -55,4 +55,7 @@ sealed class LoginAction : VectorViewModelAction {
|
||||
object ResetSignMode : ResetAction()
|
||||
object ResetLogin : ResetAction()
|
||||
object ResetResetPassword : ResetAction()
|
||||
|
||||
// For the soft logout case
|
||||
data class SetupSsoForSessionRecovery(val homeServerUrl: String, val deviceId: String) : LoginAction()
|
||||
}
|
||||
|
@ -102,6 +102,18 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
||||
is LoginAction.ResetPasswordMailConfirmed -> handleResetPasswordMailConfirmed()
|
||||
is LoginAction.RegisterAction -> handleRegisterAction(action)
|
||||
is LoginAction.ResetAction -> handleResetAction(action)
|
||||
is LoginAction.SetupSsoForSessionRecovery -> handleSetupSsoForSessionRecovery(action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSetupSsoForSessionRecovery(action: LoginAction.SetupSsoForSessionRecovery) {
|
||||
setState {
|
||||
copy(
|
||||
signMode = SignMode.SignIn,
|
||||
loginMode = LoginMode.Sso,
|
||||
homeServerUrl = action.homeServerUrl,
|
||||
deviceId = action.deviceId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,9 @@ data class LoginViewState(
|
||||
val resetPasswordEmail: String? = null,
|
||||
@PersistState
|
||||
val homeServerUrl: String? = null,
|
||||
// For SSO session recovery
|
||||
@PersistState
|
||||
val deviceId: String? = null,
|
||||
|
||||
// Network result
|
||||
@PersistState
|
||||
|
@ -30,10 +30,14 @@ 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.error.ErrorFormatter
|
||||
import im.vector.riotx.core.utils.AssetReader
|
||||
import im.vector.riotx.features.signout.soft.SoftLogoutAction
|
||||
import im.vector.riotx.features.signout.soft.SoftLogoutViewModel
|
||||
import kotlinx.android.synthetic.main.fragment_login_web.*
|
||||
import timber.log.Timber
|
||||
import java.net.URLDecoder
|
||||
@ -48,9 +52,12 @@ class LoginWebFragment @Inject constructor(
|
||||
private val errorFormatter: ErrorFormatter
|
||||
) : AbstractLoginFragment() {
|
||||
|
||||
private val softLogoutViewModel: SoftLogoutViewModel by activityViewModel()
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_login_web
|
||||
|
||||
private var isWebViewLoaded = false
|
||||
private var isForSessionRecovery = false
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
@ -60,6 +67,9 @@ class LoginWebFragment @Inject constructor(
|
||||
|
||||
override fun updateWithState(state: LoginViewState) {
|
||||
setupTitle(state)
|
||||
|
||||
isForSessionRecovery = state.deviceId?.isNotBlank() == true
|
||||
|
||||
if (!isWebViewLoaded) {
|
||||
setupWebView(state)
|
||||
isWebViewLoaded = true
|
||||
@ -110,13 +120,22 @@ class LoginWebFragment @Inject constructor(
|
||||
}
|
||||
|
||||
private fun launchWebView(state: LoginViewState) {
|
||||
if (state.signMode == SignMode.SignIn) {
|
||||
loginWebWebView.loadUrl(state.homeServerUrl?.trim { it == '/' } + "/_matrix/static/client/login/")
|
||||
} else {
|
||||
// MODE_REGISTER
|
||||
loginWebWebView.loadUrl(state.homeServerUrl?.trim { it == '/' } + "/_matrix/static/client/register/")
|
||||
val url = buildString {
|
||||
append(state.homeServerUrl?.trim { it == '/' })
|
||||
if (state.signMode == SignMode.SignIn) {
|
||||
append("/_matrix/static/client/login/")
|
||||
state.deviceId?.takeIf { it.isNotBlank() }?.let {
|
||||
// But https://github.com/matrix-org/synapse/issues/5755
|
||||
append("?device_id=$it")
|
||||
}
|
||||
} else {
|
||||
// MODE_REGISTER
|
||||
append("/_matrix/static/client/register/")
|
||||
}
|
||||
}
|
||||
|
||||
loginWebWebView.loadUrl(url)
|
||||
|
||||
loginWebWebView.webViewClient = object : WebViewClient() {
|
||||
override fun onReceivedSslError(view: WebView, handler: SslErrorHandler,
|
||||
error: SslError) {
|
||||
@ -212,10 +231,7 @@ class LoginWebFragment @Inject constructor(
|
||||
if (state.signMode == SignMode.SignIn) {
|
||||
try {
|
||||
if (action == "onLogin") {
|
||||
val credentials = javascriptResponse.credentials
|
||||
if (credentials != null) {
|
||||
loginViewModel.handle(LoginAction.WebLoginSuccess(credentials))
|
||||
}
|
||||
javascriptResponse.credentials?.let { notifyViewModel(it) }
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## shouldOverrideUrlLoading() : failed")
|
||||
@ -224,10 +240,7 @@ class LoginWebFragment @Inject constructor(
|
||||
// MODE_REGISTER
|
||||
// check the required parameters
|
||||
if (action == "onRegistered") {
|
||||
val credentials = javascriptResponse.credentials
|
||||
if (credentials != null) {
|
||||
loginViewModel.handle(LoginAction.WebLoginSuccess(credentials))
|
||||
}
|
||||
javascriptResponse.credentials?.let { notifyViewModel(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -239,6 +252,14 @@ class LoginWebFragment @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyViewModel(credentials: Credentials) {
|
||||
if (isForSessionRecovery) {
|
||||
softLogoutViewModel.handle(SoftLogoutAction.WebLoginSuccess(credentials))
|
||||
} else {
|
||||
loginViewModel.handle(LoginAction.WebLoginSuccess(credentials))
|
||||
}
|
||||
}
|
||||
|
||||
override fun resetViewModel() {
|
||||
loginViewModel.handle(LoginAction.ResetLogin)
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
package im.vector.riotx.features.signout.soft
|
||||
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||
|
||||
sealed class SoftLogoutAction : VectorViewModelAction {
|
||||
@ -24,5 +25,5 @@ sealed class SoftLogoutAction : VectorViewModelAction {
|
||||
object TogglePassword : SoftLogoutAction()
|
||||
|
||||
data class SignInAgain(val password: String) : SoftLogoutAction()
|
||||
// TODO Add reset pwd...
|
||||
data class WebLoginSuccess(val credentials: Credentials) : SoftLogoutAction()
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import com.airbnb.mvrx.Success
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.epoxy.loadingItem
|
||||
import im.vector.riotx.core.error.ErrorFormatter
|
||||
import im.vector.riotx.core.extensions.toReducedUrl
|
||||
import im.vector.riotx.core.resources.StringProvider
|
||||
import im.vector.riotx.features.login.LoginMode
|
||||
import im.vector.riotx.features.signout.soft.epoxy.*
|
||||
@ -71,7 +72,7 @@ class SoftLogoutController @Inject constructor(
|
||||
loginTextItem {
|
||||
id("signText1")
|
||||
text(stringProvider.getString(R.string.soft_logout_signin_notice,
|
||||
state.homeServerUrl,
|
||||
state.homeServerUrl.toReducedUrl(),
|
||||
state.userDisplayName,
|
||||
state.userId))
|
||||
}
|
||||
|
@ -31,6 +31,8 @@ import im.vector.riotx.core.extensions.hideKeyboard
|
||||
import im.vector.riotx.features.MainActivity
|
||||
import im.vector.riotx.features.MainActivityArgs
|
||||
import im.vector.riotx.features.login.AbstractLoginFragment
|
||||
import im.vector.riotx.features.login.LoginAction
|
||||
import im.vector.riotx.features.login.LoginMode
|
||||
import im.vector.riotx.features.login.LoginNavigation
|
||||
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
|
||||
import javax.inject.Inject
|
||||
@ -56,6 +58,14 @@ class SoftLogoutFragment @Inject constructor(
|
||||
|
||||
softLogoutViewModel.subscribe(this) { softLogoutViewState ->
|
||||
softLogoutController.update(softLogoutViewState)
|
||||
|
||||
if (softLogoutViewState.asyncHomeServerLoginFlowRequest.invoke() == LoginMode.Sso) {
|
||||
// Prepare the loginViewModel for a SSO recovery
|
||||
loginViewModel.handle(LoginAction.SetupSsoForSessionRecovery(
|
||||
softLogoutViewState.homeServerUrl,
|
||||
softLogoutViewState.deviceId
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,7 +94,7 @@ class SoftLogoutFragment @Inject constructor(
|
||||
}
|
||||
|
||||
override fun ssoSubmit() {
|
||||
// TODO loginSharedActionViewModel.post(LoginNavigation.Sso)
|
||||
loginSharedActionViewModel.post(LoginNavigation.OnSignModeSelected)
|
||||
}
|
||||
|
||||
override fun clearData() {
|
||||
|
@ -54,8 +54,9 @@ class SoftLogoutViewModel @AssistedInject constructor(
|
||||
val activity: SoftLogoutActivity = (viewModelContext as ActivityViewModelContext).activity()
|
||||
val userId = activity.session.myUserId
|
||||
return SoftLogoutViewState(
|
||||
homeServerUrl = activity.session.sessionParams.homeServerConnectionConfig.homeServerUri.toString().toReducedUrl(),
|
||||
homeServerUrl = activity.session.sessionParams.homeServerConnectionConfig.homeServerUri.toString(),
|
||||
userId = userId,
|
||||
deviceId = activity.session.sessionParams.credentials.deviceId ?: "",
|
||||
userDisplayName = activity.session.getUser(userId)?.displayName ?: userId,
|
||||
hasUnsavedKeys = activity.session.hasUnsavedKeys()
|
||||
)
|
||||
@ -139,14 +140,11 @@ class SoftLogoutViewModel @AssistedInject constructor(
|
||||
})
|
||||
}
|
||||
|
||||
// TODO Cleanup
|
||||
// private val _viewEvents = PublishDataSource<LoginViewEvents>()
|
||||
// val viewEvents: DataSource<LoginViewEvents> = _viewEvents
|
||||
|
||||
override fun handle(action: SoftLogoutAction) {
|
||||
when (action) {
|
||||
is SoftLogoutAction.RetryLoginFlow -> getSupportedLoginFlow()
|
||||
is SoftLogoutAction.SignInAgain -> handleSignInAgain(action)
|
||||
is SoftLogoutAction.WebLoginSuccess -> handleWebLoginSuccess(action)
|
||||
is SoftLogoutAction.PasswordChanged -> handlePasswordChange(action)
|
||||
is SoftLogoutAction.TogglePassword -> handleTogglePassword()
|
||||
}
|
||||
@ -171,6 +169,30 @@ class SoftLogoutViewModel @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleWebLoginSuccess(action: SoftLogoutAction.WebLoginSuccess) {
|
||||
setState {
|
||||
copy(
|
||||
asyncLoginAction = Loading()
|
||||
)
|
||||
}
|
||||
currentTask = session.updateCredentials(action.credentials,
|
||||
object : MatrixCallback<Unit> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
setState {
|
||||
copy(
|
||||
asyncLoginAction = Fail(failure)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSuccess(data: Unit) {
|
||||
onSessionRestored()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
private fun handleSignInAgain(action: SoftLogoutAction.SignInAgain) {
|
||||
setState {
|
||||
copy(
|
||||
@ -190,21 +212,25 @@ class SoftLogoutViewModel @AssistedInject constructor(
|
||||
}
|
||||
|
||||
override fun onSuccess(data: Unit) {
|
||||
activeSessionHolder.setActiveSession(session)
|
||||
// Start the sync
|
||||
session.startSync(true)
|
||||
|
||||
// TODO Configure and start ? Check that the push still works...
|
||||
setState {
|
||||
copy(
|
||||
asyncLoginAction = Success(Unit)
|
||||
)
|
||||
}
|
||||
onSessionRestored()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun onSessionRestored() {
|
||||
activeSessionHolder.setActiveSession(session)
|
||||
// Start the sync
|
||||
session.startSync(true)
|
||||
|
||||
// TODO Configure and start ? Check that the push still works...
|
||||
setState {
|
||||
copy(
|
||||
asyncLoginAction = Success(Unit)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
|
||||
|
@ -24,6 +24,7 @@ data class SoftLogoutViewState(
|
||||
val asyncLoginAction: Async<Unit> = Uninitialized,
|
||||
val homeServerUrl: String,
|
||||
val userId: String,
|
||||
val deviceId: String,
|
||||
val userDisplayName: String,
|
||||
val hasUnsavedKeys: Boolean,
|
||||
val passwordShown: Boolean = false,
|
||||
|
Loading…
x
Reference in New Issue
Block a user