Migrate AuthenticationService API to coroutines (#2449)

This commit is contained in:
Benoit Marty 2021-02-17 17:56:13 +01:00 committed by Benoit Marty
parent 7115676393
commit 6aa5dc992d
9 changed files with 462 additions and 602 deletions

View file

@ -19,7 +19,7 @@ Translations 🗣:
-
SDK API changes ⚠️:
-
- Migrate AuthenticationService API to coroutines (#2449)
Build 🧱:
-

View file

@ -16,7 +16,6 @@
package org.matrix.android.sdk.api.auth
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
@ -24,7 +23,6 @@ import org.matrix.android.sdk.api.auth.login.LoginWizard
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.util.Cancelable
/**
* This interface defines methods to authenticate or to create an account to a matrix server.
@ -34,12 +32,12 @@ interface AuthenticationService {
* Request the supported login flows for this homeserver.
* This is the first method to call to be able to get a wizard to login or the create an account
*/
fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResult>): Cancelable
suspend fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult
/**
* Request the supported login flows for the corresponding sessionId.
*/
fun getLoginFlowOfSession(sessionId: String, callback: MatrixCallback<LoginFlowResult>): Cancelable
suspend fun getLoginFlowOfSession(sessionId: String): LoginFlowResult
/**
* Get a SSO url
@ -69,12 +67,12 @@ interface AuthenticationService {
/**
* Cancel pending login or pending registration
*/
fun cancelPendingLoginOrRegistration()
suspend fun cancelPendingLoginOrRegistration()
/**
* Reset all pending settings, including current HomeServerConnectionConfig
*/
fun reset()
suspend fun reset()
/**
* Check if there is an authenticated [Session].
@ -91,24 +89,21 @@ interface AuthenticationService {
/**
* Create a session after a SSO successful login
*/
fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
credentials: Credentials,
callback: MatrixCallback<Session>): Cancelable
suspend fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
credentials: Credentials): Session
/**
* Perform a wellknown request, using the domain from the matrixId
*/
fun getWellKnownData(matrixId: String,
homeServerConnectionConfig: HomeServerConnectionConfig?,
callback: MatrixCallback<WellknownResult>): Cancelable
suspend fun getWellKnownData(matrixId: String,
homeServerConnectionConfig: HomeServerConnectionConfig?): WellknownResult
/**
* Authenticate with a matrixId and a password
* Usually call this after a successful call to getWellKnownData()
*/
fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
matrixId: String,
password: String,
initialDeviceName: String,
callback: MatrixCallback<Session>): Cancelable
suspend fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
matrixId: String,
password: String,
initialDeviceName: String): Session
}

View file

@ -16,7 +16,6 @@
package org.matrix.android.sdk.api.auth.login
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.util.Cancelable
@ -29,26 +28,23 @@ interface LoginWizard {
* @param callback the matrix callback on which you'll receive the result of authentication.
* @return a [Cancelable]
*/
fun login(login: String,
password: String,
deviceName: String,
callback: MatrixCallback<Session>): Cancelable
suspend fun login(login: String,
password: String,
deviceName: String): Session
/**
* Exchange a login token to an access token
*/
fun loginWithToken(loginToken: String,
callback: MatrixCallback<Session>): Cancelable
suspend fun loginWithToken(loginToken: String): Session
/**
* Reset user password
*/
fun resetPassword(email: String,
newPassword: String,
callback: MatrixCallback<Unit>): Cancelable
suspend fun resetPassword(email: String,
newPassword: String)
/**
* Confirm the new password, once the user has checked his email
* Confirm the new password, once the user has checked their email
*/
fun resetPasswordMailConfirmed(callback: MatrixCallback<Unit>): Cancelable
suspend fun resetPasswordMailConfirmed()
}

View file

@ -16,28 +16,25 @@
package org.matrix.android.sdk.api.auth.registration
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.util.Cancelable
interface RegistrationWizard {
fun getRegistrationFlow(callback: MatrixCallback<RegistrationResult>): Cancelable
suspend fun getRegistrationFlow(): RegistrationResult
fun createAccount(userName: String, password: String, initialDeviceDisplayName: String?, callback: MatrixCallback<RegistrationResult>): Cancelable
suspend fun createAccount(userName: String, password: String, initialDeviceDisplayName: String?): RegistrationResult
fun performReCaptcha(response: String, callback: MatrixCallback<RegistrationResult>): Cancelable
suspend fun performReCaptcha(response: String): RegistrationResult
fun acceptTerms(callback: MatrixCallback<RegistrationResult>): Cancelable
suspend fun acceptTerms(): RegistrationResult
fun dummy(callback: MatrixCallback<RegistrationResult>): Cancelable
suspend fun dummy(): RegistrationResult
fun addThreePid(threePid: RegisterThreePid, callback: MatrixCallback<RegistrationResult>): Cancelable
suspend fun addThreePid(threePid: RegisterThreePid): RegistrationResult
fun sendAgainThreePid(callback: MatrixCallback<RegistrationResult>): Cancelable
suspend fun sendAgainThreePid(): RegistrationResult
fun handleValidateThreePid(code: String, callback: MatrixCallback<RegistrationResult>): Cancelable
suspend fun handleValidateThreePid(code: String): RegistrationResult
fun checkIfEmailHasBeenValidated(delayMillis: Long, callback: MatrixCallback<RegistrationResult>): Cancelable
suspend fun checkIfEmailHasBeenValidated(delayMillis: Long): RegistrationResult
val currentThreePid: String?

View file

@ -18,10 +18,7 @@ package org.matrix.android.sdk.internal.auth
import android.net.Uri
import dagger.Lazy
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
@ -32,8 +29,6 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.api.util.NoOpCancellable
import org.matrix.android.sdk.api.util.appendParamToUrl
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse
@ -50,11 +45,6 @@ import org.matrix.android.sdk.internal.network.RetrofitFactory
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.network.httpclient.addSocketFactory
import org.matrix.android.sdk.internal.network.ssl.UnrecognizedCertificateException
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.task.configureWith
import org.matrix.android.sdk.internal.task.launchToCallback
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
import org.matrix.android.sdk.internal.util.toCancelable
import org.matrix.android.sdk.internal.wellknown.GetWellknownTask
import javax.inject.Inject
import javax.net.ssl.HttpsURLConnection
@ -63,14 +53,12 @@ internal class DefaultAuthenticationService @Inject constructor(
@Unauthenticated
private val okHttpClient: Lazy<OkHttpClient>,
private val retrofitFactory: RetrofitFactory,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val sessionParamsStore: SessionParamsStore,
private val sessionManager: SessionManager,
private val sessionCreator: SessionCreator,
private val pendingSessionStore: PendingSessionStore,
private val getWellknownTask: GetWellknownTask,
private val directLoginTask: DirectLoginTask,
private val taskExecutor: TaskExecutor
private val directLoginTask: DirectLoginTask
) : AuthenticationService {
private var pendingSessionData: PendingSessionData? = pendingSessionStore.getPendingSessionData()
@ -89,15 +77,11 @@ internal class DefaultAuthenticationService @Inject constructor(
}
}
override fun getLoginFlowOfSession(sessionId: String, callback: MatrixCallback<LoginFlowResult>): Cancelable {
override suspend fun getLoginFlowOfSession(sessionId: String): LoginFlowResult {
val homeServerConnectionConfig = sessionParamsStore.get(sessionId)?.homeServerConnectionConfig
?: throw IllegalStateException("Session not found")
return if (homeServerConnectionConfig == null) {
callback.onFailure(IllegalStateException("Session not found"))
NoOpCancellable
} else {
getLoginFlow(homeServerConnectionConfig, callback)
}
return getLoginFlow(homeServerConnectionConfig)
}
override fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String? {
@ -146,70 +130,65 @@ internal class DefaultAuthenticationService @Inject constructor(
?.trim { it == '/' }
}
override fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResult>): Cancelable {
override suspend fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
pendingSessionData = null
return taskExecutor.executorScope.launch(coroutineDispatchers.main) {
pendingSessionStore.delete()
pendingSessionStore.delete()
val result = runCatching {
getLoginFlowInternal(homeServerConnectionConfig)
}
result.fold(
{
if (it is LoginFlowResult.Success) {
// The homeserver exists and up to date, keep the config
// Homeserver url may have been changed, if it was a Riot url
val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy(
homeServerUri = Uri.parse(it.homeServerUrl)
)
pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig)
.also { data -> pendingSessionStore.savePendingSessionData(data) }
}
callback.onSuccess(it)
},
{
if (it is UnrecognizedCertificateException) {
callback.onFailure(Failure.UnrecognizedCertificateFailure(homeServerConnectionConfig.homeServerUri.toString(), it.fingerprint))
} else {
callback.onFailure(it)
}
}
)
val result = runCatching {
getLoginFlowInternal(homeServerConnectionConfig)
}
.toCancelable()
return result.fold(
{
if (it is LoginFlowResult.Success) {
// The homeserver exists and up to date, keep the config
// Homeserver url may have been changed, if it was a Riot url
val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy(
homeServerUri = Uri.parse(it.homeServerUrl)
)
pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig)
.also { data -> pendingSessionStore.savePendingSessionData(data) }
}
it
},
{
if (it is UnrecognizedCertificateException) {
throw Failure.UnrecognizedCertificateFailure(homeServerConnectionConfig.homeServerUri.toString(), it.fingerprint)
} else {
throw it
}
}
)
}
private suspend fun getLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
return withContext(coroutineDispatchers.io) {
val authAPI = buildAuthAPI(homeServerConnectionConfig)
val authAPI = buildAuthAPI(homeServerConnectionConfig)
// First check the homeserver version
runCatching {
executeRequest<Versions>(null) {
apiCall = authAPI.versions()
}
// First check the homeserver version
return runCatching {
executeRequest<Versions>(null) {
apiCall = authAPI.versions()
}
.map { versions ->
// Ok, it seems that the homeserver url is valid
getLoginFlowResult(authAPI, versions, homeServerConnectionConfig.homeServerUri.toString())
}
.fold(
{
it
},
{
if (it is Failure.OtherServerError
&& it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
// It's maybe a Riot url?
getRiotDomainLoginFlowInternal(homeServerConnectionConfig)
} else {
throw it
}
}
)
}
.map { versions ->
// Ok, it seems that the homeserver url is valid
getLoginFlowResult(authAPI, versions, homeServerConnectionConfig.homeServerUri.toString())
}
.fold(
{
it
},
{
if (it is Failure.OtherServerError
&& it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
// It's maybe a Riot url?
getRiotDomainLoginFlowInternal(homeServerConnectionConfig)
} else {
throw it
}
}
)
}
private suspend fun getRiotDomainLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
@ -340,10 +319,8 @@ internal class DefaultAuthenticationService @Inject constructor(
DefaultRegistrationWizard(
buildClient(it),
retrofitFactory,
coroutineDispatchers,
sessionCreator,
pendingSessionStore,
taskExecutor.executorScope
pendingSessionStore
).also {
currentRegistrationWizard = it
}
@ -361,10 +338,8 @@ internal class DefaultAuthenticationService @Inject constructor(
DefaultLoginWizard(
buildClient(it),
retrofitFactory,
coroutineDispatchers,
sessionCreator,
pendingSessionStore,
taskExecutor.executorScope
pendingSessionStore
).also {
currentLoginWizard = it
}
@ -372,7 +347,7 @@ internal class DefaultAuthenticationService @Inject constructor(
}
}
override fun cancelPendingLoginOrRegistration() {
override suspend fun cancelPendingLoginOrRegistration() {
currentLoginWizard = null
currentRegistrationWizard = null
@ -381,61 +356,39 @@ internal class DefaultAuthenticationService @Inject constructor(
pendingSessionData = pendingSessionData?.homeServerConnectionConfig
?.let { PendingSessionData(it) }
.also {
taskExecutor.executorScope.launch(coroutineDispatchers.main) {
if (it == null) {
// Should not happen
pendingSessionStore.delete()
} else {
pendingSessionStore.savePendingSessionData(it)
}
if (it == null) {
// Should not happen
pendingSessionStore.delete()
} else {
pendingSessionStore.savePendingSessionData(it)
}
}
}
override fun reset() {
override suspend fun reset() {
currentLoginWizard = null
currentRegistrationWizard = null
pendingSessionData = null
taskExecutor.executorScope.launch(coroutineDispatchers.main) {
pendingSessionStore.delete()
}
pendingSessionStore.delete()
}
override fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
credentials: Credentials,
callback: MatrixCallback<Session>): Cancelable {
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
createSessionFromSso(credentials, homeServerConnectionConfig)
}
override suspend fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
credentials: Credentials): Session {
return sessionCreator.createSession(credentials, homeServerConnectionConfig)
}
override fun getWellKnownData(matrixId: String,
homeServerConnectionConfig: HomeServerConnectionConfig?,
callback: MatrixCallback<WellknownResult>): Cancelable {
return getWellknownTask
.configureWith(GetWellknownTask.Params(matrixId, homeServerConnectionConfig)) {
this.callback = callback
}
.executeBy(taskExecutor)
override suspend fun getWellKnownData(matrixId: String,
homeServerConnectionConfig: HomeServerConnectionConfig?): WellknownResult {
return getWellknownTask.execute(GetWellknownTask.Params(matrixId, homeServerConnectionConfig))
}
override fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
matrixId: String,
password: String,
initialDeviceName: String,
callback: MatrixCallback<Session>): Cancelable {
return directLoginTask
.configureWith(DirectLoginTask.Params(homeServerConnectionConfig, matrixId, password, initialDeviceName)) {
this.callback = callback
}
.executeBy(taskExecutor)
}
private suspend fun createSessionFromSso(credentials: Credentials,
homeServerConnectionConfig: HomeServerConnectionConfig): Session = withContext(coroutineDispatchers.computation) {
sessionCreator.createSession(credentials, homeServerConnectionConfig)
override suspend fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
matrixId: String,
password: String,
initialDeviceName: String): Session {
return directLoginTask.execute(DirectLoginTask.Params(homeServerConnectionConfig, matrixId, password, initialDeviceName))
}
private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI {

View file

@ -17,13 +17,11 @@
package org.matrix.android.sdk.internal.auth.login
import android.util.Patterns
import org.matrix.android.sdk.api.MatrixCallback
import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.auth.login.LoginWizard
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.api.util.NoOpCancellable
import org.matrix.android.sdk.internal.auth.AuthAPI
import org.matrix.android.sdk.internal.auth.PendingSessionStore
import org.matrix.android.sdk.internal.auth.SessionCreator
@ -36,19 +34,12 @@ import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistration
import org.matrix.android.sdk.internal.auth.registration.RegisterAddThreePidTask
import org.matrix.android.sdk.internal.network.RetrofitFactory
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.task.launchToCallback
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
internal class DefaultLoginWizard(
okHttpClient: OkHttpClient,
retrofitFactory: RetrofitFactory,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val sessionCreator: SessionCreator,
private val pendingSessionStore: PendingSessionStore,
private val coroutineScope: CoroutineScope
private val pendingSessionStore: PendingSessionStore
) : LoginWizard {
private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
@ -56,34 +47,9 @@ internal class DefaultLoginWizard(
private val authAPI = retrofitFactory.create(okHttpClient, pendingSessionData.homeServerConnectionConfig.homeServerUri.toString())
.create(AuthAPI::class.java)
override fun login(login: String,
password: String,
deviceName: String,
callback: MatrixCallback<Session>): Cancelable {
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
loginInternal(login, password, deviceName)
}
}
/**
* Ref: https://matrix.org/docs/spec/client_server/latest#handling-the-authentication-endpoint
*/
override fun loginWithToken(loginToken: String, callback: MatrixCallback<Session>): Cancelable {
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
val loginParams = TokenLoginParams(
token = loginToken
)
val credentials = executeRequest<Credentials>(null) {
apiCall = authAPI.login(loginParams)
}
sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
}
}
private suspend fun loginInternal(login: String,
password: String,
deviceName: String) = withContext(coroutineDispatchers.computation) {
override suspend fun login(login: String,
password: String,
deviceName: String): Session {
val loginParams = if (Patterns.EMAIL_ADDRESS.matcher(login).matches()) {
PasswordLoginParams.thirdPartyIdentifier(ThreePidMedium.EMAIL, login, password, deviceName)
} else {
@ -93,16 +59,24 @@ internal class DefaultLoginWizard(
apiCall = authAPI.login(loginParams)
}
sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
}
override fun resetPassword(email: String, newPassword: String, callback: MatrixCallback<Unit>): Cancelable {
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
resetPasswordInternal(email, newPassword)
/**
* Ref: https://matrix.org/docs/spec/client_server/latest#handling-the-authentication-endpoint
*/
override suspend fun loginWithToken(loginToken: String): Session {
val loginParams = TokenLoginParams(
token = loginToken
)
val credentials = executeRequest<Credentials>(null) {
apiCall = authAPI.login(loginParams)
}
return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
}
private suspend fun resetPasswordInternal(email: String, newPassword: String) {
override suspend fun resetPassword(email: String, newPassword: String) {
val param = RegisterAddThreePidTask.Params(
RegisterThreePid.Email(email),
pendingSessionData.clientSecret,
@ -120,21 +94,14 @@ internal class DefaultLoginWizard(
.also { pendingSessionStore.savePendingSessionData(it) }
}
override fun resetPasswordMailConfirmed(callback: MatrixCallback<Unit>): Cancelable {
val safeResetPasswordData = pendingSessionData.resetPasswordData ?: run {
callback.onFailure(IllegalStateException("developer error, no reset password in progress"))
return NoOpCancellable
}
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
resetPasswordMailConfirmedInternal(safeResetPasswordData)
}
}
override suspend fun resetPasswordMailConfirmed() {
val safeResetPasswordData = pendingSessionData.resetPasswordData
?: throw IllegalStateException("developer error, no reset password in progress")
private suspend fun resetPasswordMailConfirmedInternal(resetPasswordData: ResetPasswordData) {
val param = ResetPasswordMailConfirmed.create(
pendingSessionData.clientSecret,
resetPasswordData.addThreePidRegistrationResponse.sid,
resetPasswordData.newPassword
safeResetPasswordData.addThreePidRegistrationResponse.sid,
safeResetPasswordData.newPassword
)
executeRequest<Unit>(null) {

View file

@ -16,10 +16,8 @@
package org.matrix.android.sdk.internal.auth.registration
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
import org.matrix.android.sdk.api.auth.registration.RegistrationResult
@ -27,15 +25,11 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
import org.matrix.android.sdk.api.auth.registration.toFlowResult
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.Failure.RegistrationFlowError
import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.api.util.NoOpCancellable
import org.matrix.android.sdk.internal.auth.AuthAPI
import org.matrix.android.sdk.internal.auth.PendingSessionStore
import org.matrix.android.sdk.internal.auth.SessionCreator
import org.matrix.android.sdk.internal.auth.db.PendingSessionData
import org.matrix.android.sdk.internal.network.RetrofitFactory
import org.matrix.android.sdk.internal.task.launchToCallback
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
/**
* This class execute the registration request and is responsible to keep the session of interactive authentication
@ -43,10 +37,8 @@ import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
internal class DefaultRegistrationWizard(
private val okHttpClient: OkHttpClient,
private val retrofitFactory: RetrofitFactory,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val sessionCreator: SessionCreator,
private val pendingSessionStore: PendingSessionStore,
private val coroutineScope: CoroutineScope
private val pendingSessionStore: PendingSessionStore
) : RegistrationWizard {
private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
@ -59,82 +51,66 @@ internal class DefaultRegistrationWizard(
override val currentThreePid: String?
get() {
return when (val threePid = pendingSessionData.currentThreePidData?.threePid) {
is RegisterThreePid.Email -> threePid.email
is RegisterThreePid.Email -> threePid.email
is RegisterThreePid.Msisdn -> {
// Take formatted msisdn if provided by the server
pendingSessionData.currentThreePidData?.addThreePidRegistrationResponse?.formattedMsisdn?.takeIf { it.isNotBlank() } ?: threePid.msisdn
}
null -> null
null -> null
}
}
override val isRegistrationStarted: Boolean
get() = pendingSessionData.isRegistrationStarted
override fun getRegistrationFlow(callback: MatrixCallback<RegistrationResult>): Cancelable {
override suspend fun getRegistrationFlow(): RegistrationResult {
val params = RegistrationParams()
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
performRegistrationRequest(params)
}
return performRegistrationRequest(params)
}
override fun createAccount(userName: String,
password: String,
initialDeviceDisplayName: String?,
callback: MatrixCallback<RegistrationResult>): Cancelable {
override suspend fun createAccount(userName: String,
password: String,
initialDeviceDisplayName: String?): RegistrationResult {
val params = RegistrationParams(
username = userName,
password = password,
initialDeviceDisplayName = initialDeviceDisplayName
)
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
performRegistrationRequest(params)
.also {
pendingSessionData = pendingSessionData.copy(isRegistrationStarted = true)
.also { pendingSessionStore.savePendingSessionData(it) }
}
}
return performRegistrationRequest(params)
.also {
pendingSessionData = pendingSessionData.copy(isRegistrationStarted = true)
.also { pendingSessionStore.savePendingSessionData(it) }
}
}
override fun performReCaptcha(response: String, callback: MatrixCallback<RegistrationResult>): Cancelable {
val safeSession = pendingSessionData.currentSession ?: run {
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
return NoOpCancellable
}
override suspend fun performReCaptcha(response: String): RegistrationResult {
val safeSession = pendingSessionData.currentSession
?: throw IllegalStateException("developer error, call createAccount() method first")
val params = RegistrationParams(auth = AuthParams.createForCaptcha(safeSession, response))
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
performRegistrationRequest(params)
}
return performRegistrationRequest(params)
}
override fun acceptTerms(callback: MatrixCallback<RegistrationResult>): Cancelable {
val safeSession = pendingSessionData.currentSession ?: run {
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
return NoOpCancellable
}
override suspend fun acceptTerms(): RegistrationResult {
val safeSession = pendingSessionData.currentSession
?: throw IllegalStateException("developer error, call createAccount() method first")
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.TERMS, session = safeSession))
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
performRegistrationRequest(params)
}
return performRegistrationRequest(params)
}
override fun addThreePid(threePid: RegisterThreePid, callback: MatrixCallback<RegistrationResult>): Cancelable {
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
pendingSessionData = pendingSessionData.copy(currentThreePidData = null)
.also { pendingSessionStore.savePendingSessionData(it) }
override suspend fun addThreePid(threePid: RegisterThreePid): RegistrationResult {
pendingSessionData = pendingSessionData.copy(currentThreePidData = null)
.also { pendingSessionStore.savePendingSessionData(it) }
sendThreePid(threePid)
}
return sendThreePid(threePid)
}
override fun sendAgainThreePid(callback: MatrixCallback<RegistrationResult>): Cancelable {
val safeCurrentThreePid = pendingSessionData.currentThreePidData?.threePid ?: run {
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
return NoOpCancellable
}
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
sendThreePid(safeCurrentThreePid)
}
override suspend fun sendAgainThreePid(): RegistrationResult {
val safeCurrentThreePid = pendingSessionData.currentThreePidData?.threePid
?: throw IllegalStateException("developer error, call createAccount() method first")
return sendThreePid(safeCurrentThreePid)
}
private suspend fun sendThreePid(threePid: RegisterThreePid): RegistrationResult {
@ -173,20 +149,15 @@ internal class DefaultRegistrationWizard(
return performRegistrationRequest(params)
}
override fun checkIfEmailHasBeenValidated(delayMillis: Long, callback: MatrixCallback<RegistrationResult>): Cancelable {
val safeParam = pendingSessionData.currentThreePidData?.registrationParams ?: run {
callback.onFailure(IllegalStateException("developer error, no pending three pid"))
return NoOpCancellable
}
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
performRegistrationRequest(safeParam, delayMillis)
}
override suspend fun checkIfEmailHasBeenValidated(delayMillis: Long): RegistrationResult {
val safeParam = pendingSessionData.currentThreePidData?.registrationParams
?: throw IllegalStateException("developer error, no pending three pid")
return performRegistrationRequest(safeParam, delayMillis)
}
override fun handleValidateThreePid(code: String, callback: MatrixCallback<RegistrationResult>): Cancelable {
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
validateThreePid(code)
}
override suspend fun handleValidateThreePid(code: String): RegistrationResult {
return validateThreePid(code)
}
private suspend fun validateThreePid(code: String): RegistrationResult {
@ -210,15 +181,12 @@ internal class DefaultRegistrationWizard(
}
}
override fun dummy(callback: MatrixCallback<RegistrationResult>): Cancelable {
val safeSession = pendingSessionData.currentSession ?: run {
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
return NoOpCancellable
}
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.DUMMY, session = safeSession))
performRegistrationRequest(params)
}
override suspend fun dummy(): RegistrationResult {
val safeSession = pendingSessionData.currentSession
?: throw IllegalStateException("developer error, call createAccount() method first")
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.DUMMY, session = safeSession))
return performRegistrationRequest(params)
}
private suspend fun performRegistrationRequest(registrationParams: RegistrationParams,

View file

@ -19,6 +19,7 @@ package im.vector.app.features.login
import android.content.Context
import android.net.Uri
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
@ -27,8 +28,8 @@ import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.extensions.configureAndStart
@ -37,7 +38,8 @@ import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.ensureTrailingSlash
import im.vector.app.features.signout.soft.SoftLogoutActivity
import org.matrix.android.sdk.api.MatrixCallback
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
@ -51,7 +53,6 @@ import org.matrix.android.sdk.api.auth.registration.Stage
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.util.Cancelable
import timber.log.Timber
import java.util.concurrent.CancellationException
@ -117,7 +118,12 @@ class LoginViewModel @AssistedInject constructor(
private var loginConfig: LoginConfig? = null
private var currentTask: Cancelable? = null
private var currentJob: Job? = null
set(value) {
// Cancel any previous Job
field?.cancel()
field = value
}
override fun handle(action: LoginAction) {
when (action) {
@ -186,22 +192,20 @@ class LoginViewModel @AssistedInject constructor(
)
}
currentTask = safeLoginWizard.loginWithToken(
action.loginToken,
object : MatrixCallback<Session> {
override fun onSuccess(data: Session) {
onSessionCreated(data)
}
override fun onFailure(failure: Throwable) {
_viewEvents.post(LoginViewEvents.Failure(failure))
setState {
copy(
asyncLoginAction = Fail(failure)
)
}
}
})
currentJob = viewModelScope.launch {
try {
safeLoginWizard.loginWithToken(action.loginToken)
} catch (failure: Throwable) {
_viewEvents.post(LoginViewEvents.Failure(failure))
setState {
copy(
asyncLoginAction = Fail(failure)
)
}
null
}
?.let { onSessionCreated(it) }
}
}
}
@ -231,46 +235,49 @@ class LoginViewModel @AssistedInject constructor(
private fun handleCheckIfEmailHasBeenValidated(action: LoginAction.CheckIfEmailHasBeenValidated) {
// We do not want the common progress bar to be displayed, so we do not change asyncRegistration value in the state
currentTask?.cancel()
currentTask = registrationWizard?.checkIfEmailHasBeenValidated(action.delayMillis, registrationCallback)
currentJob = executeRegistrationStep(withLoading = false) {
it.checkIfEmailHasBeenValidated(action.delayMillis)
}
}
private fun handleStopEmailValidationCheck() {
currentTask?.cancel()
currentTask = null
currentJob = null
}
private fun handleValidateThreePid(action: LoginAction.ValidateThreePid) {
setState { copy(asyncRegistration = Loading()) }
currentTask = registrationWizard?.handleValidateThreePid(action.code, registrationCallback)
currentJob = executeRegistrationStep {
it.handleValidateThreePid(action.code)
}
}
private val registrationCallback = object : MatrixCallback<RegistrationResult> {
override fun onSuccess(data: RegistrationResult) {
/*
// Simulate registration disabled
onFailure(Failure.ServerError(MatrixError(
code = MatrixError.FORBIDDEN,
message = "Registration is disabled"
), 403))
*/
setState {
copy(
asyncRegistration = Uninitialized
)
}
when (data) {
is RegistrationResult.Success -> onSessionCreated(data.session)
is RegistrationResult.FlowResponse -> onFlowResponse(data.flowResult)
}
private fun executeRegistrationStep(withLoading: Boolean = true,
block: suspend (RegistrationWizard) -> RegistrationResult): Job {
if (withLoading) {
setState { copy(asyncRegistration = Loading()) }
}
override fun onFailure(failure: Throwable) {
if (failure !is CancellationException) {
_viewEvents.post(LoginViewEvents.Failure(failure))
return viewModelScope.launch {
try {
registrationWizard?.let { block(it) }
/*
// Simulate registration disabled
throw Failure.ServerError(MatrixError(
code = MatrixError.FORBIDDEN,
message = "Registration is disabled"
), 403))
*/
} catch (failure: Throwable) {
if (failure !is CancellationException) {
_viewEvents.post(LoginViewEvents.Failure(failure))
}
null
}
?.let { data ->
when (data) {
is RegistrationResult.Success -> onSessionCreated(data.session)
is RegistrationResult.FlowResponse -> onFlowResponse(data.flowResult)
}
}
setState {
copy(
asyncRegistration = Uninitialized
@ -281,78 +288,68 @@ class LoginViewModel @AssistedInject constructor(
private fun handleAddThreePid(action: LoginAction.AddThreePid) {
setState { copy(asyncRegistration = Loading()) }
currentTask = registrationWizard?.addThreePid(action.threePid, object : MatrixCallback<RegistrationResult> {
override fun onSuccess(data: RegistrationResult) {
setState {
copy(
asyncRegistration = Uninitialized
)
}
}
override fun onFailure(failure: Throwable) {
currentJob = viewModelScope.launch {
try {
registrationWizard?.addThreePid(action.threePid)
} catch (failure: Throwable) {
_viewEvents.post(LoginViewEvents.Failure(failure))
setState {
copy(
asyncRegistration = Uninitialized
)
}
}
})
setState {
copy(
asyncRegistration = Uninitialized
)
}
}
}
private fun handleSendAgainThreePid() {
setState { copy(asyncRegistration = Loading()) }
currentTask = registrationWizard?.sendAgainThreePid(object : MatrixCallback<RegistrationResult> {
override fun onSuccess(data: RegistrationResult) {
setState {
copy(
asyncRegistration = Uninitialized
)
}
}
override fun onFailure(failure: Throwable) {
currentJob = viewModelScope.launch {
try {
registrationWizard?.sendAgainThreePid()
} catch (failure: Throwable) {
_viewEvents.post(LoginViewEvents.Failure(failure))
setState {
copy(
asyncRegistration = Uninitialized
)
}
}
})
setState {
copy(
asyncRegistration = Uninitialized
)
}
}
}
private fun handleAcceptTerms() {
setState { copy(asyncRegistration = Loading()) }
currentTask = registrationWizard?.acceptTerms(registrationCallback)
currentJob = executeRegistrationStep {
it.acceptTerms()
}
}
private fun handleRegisterDummy() {
setState { copy(asyncRegistration = Loading()) }
currentTask = registrationWizard?.dummy(registrationCallback)
currentJob = executeRegistrationStep {
it.dummy()
}
}
private fun handleRegisterWith(action: LoginAction.LoginOrRegister) {
setState { copy(asyncRegistration = Loading()) }
reAuthHelper.data = action.password
currentTask = registrationWizard?.createAccount(
action.username,
action.password,
action.initialDeviceName,
registrationCallback
)
currentJob = executeRegistrationStep {
it.createAccount(
action.username,
action.password,
action.initialDeviceName
)
}
}
private fun handleCaptchaDone(action: LoginAction.CaptchaDone) {
setState { copy(asyncRegistration = Loading()) }
currentTask = registrationWizard?.performReCaptcha(action.captchaResponse, registrationCallback)
currentJob = executeRegistrationStep {
it.performReCaptcha(action.captchaResponse)
}
}
private fun handleResetAction(action: LoginAction.ResetAction) {
// Cancel any request
currentTask?.cancel()
currentTask = null
currentJob = null
when (action) {
LoginAction.ResetHomeServerType -> {
@ -363,16 +360,17 @@ class LoginViewModel @AssistedInject constructor(
}
}
LoginAction.ResetHomeServerUrl -> {
authenticationService.reset()
setState {
copy(
asyncHomeServerLoginFlowRequest = Uninitialized,
homeServerUrl = null,
loginMode = LoginMode.Unknown,
serverType = ServerType.Unknown,
loginModeSupportedTypes = emptyList()
)
viewModelScope.launch {
authenticationService.reset()
setState {
copy(
asyncHomeServerLoginFlowRequest = Uninitialized,
homeServerUrl = null,
loginMode = LoginMode.Unknown,
serverType = ServerType.Unknown,
loginModeSupportedTypes = emptyList()
)
}
}
}
LoginAction.ResetSignMode -> {
@ -386,13 +384,14 @@ class LoginViewModel @AssistedInject constructor(
}
}
LoginAction.ResetLogin -> {
authenticationService.cancelPendingLoginOrRegistration()
setState {
copy(
asyncLoginAction = Uninitialized,
asyncRegistration = Uninitialized
)
viewModelScope.launch {
authenticationService.cancelPendingLoginOrRegistration()
setState {
copy(
asyncLoginAction = Uninitialized,
asyncRegistration = Uninitialized
)
}
}
}
LoginAction.ResetResetPassword -> {
@ -473,26 +472,27 @@ class LoginViewModel @AssistedInject constructor(
)
}
currentTask = safeLoginWizard.resetPassword(action.email, action.newPassword, object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
setState {
copy(
asyncResetPassword = Success(data),
resetPasswordEmail = action.email
)
}
_viewEvents.post(LoginViewEvents.OnResetPasswordSendThreePidDone)
}
override fun onFailure(failure: Throwable) {
currentJob = viewModelScope.launch {
try {
safeLoginWizard.resetPassword(action.email, action.newPassword)
} catch (failure: Throwable) {
setState {
copy(
asyncResetPassword = Fail(failure)
)
}
return@launch
}
})
setState {
copy(
asyncResetPassword = Success(Unit),
resetPasswordEmail = action.email
)
}
_viewEvents.post(LoginViewEvents.OnResetPasswordSendThreePidDone)
}
}
}
@ -514,26 +514,26 @@ class LoginViewModel @AssistedInject constructor(
)
}
currentTask = safeLoginWizard.resetPasswordMailConfirmed(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
setState {
copy(
asyncResetMailConfirmed = Success(data),
resetPasswordEmail = null
)
}
_viewEvents.post(LoginViewEvents.OnResetPasswordMailConfirmationSuccess)
}
override fun onFailure(failure: Throwable) {
currentJob = viewModelScope.launch {
try {
safeLoginWizard.resetPasswordMailConfirmed()
} catch (failure: Throwable) {
setState {
copy(
asyncResetMailConfirmed = Fail(failure)
)
}
return@launch
}
})
setState {
copy(
asyncResetMailConfirmed = Success(Unit),
resetPasswordEmail = null
)
}
_viewEvents.post(LoginViewEvents.OnResetPasswordMailConfirmationSuccess)
}
}
}
@ -553,36 +553,36 @@ class LoginViewModel @AssistedInject constructor(
)
}
authenticationService.getWellKnownData(action.username, homeServerConnectionConfig, object : MatrixCallback<WellknownResult> {
override fun onSuccess(data: WellknownResult) {
when (data) {
is WellknownResult.Prompt ->
onWellknownSuccess(action, data, homeServerConnectionConfig)
is WellknownResult.FailPrompt ->
// Relax on IS discovery if home server is valid
if (data.homeServerUrl != null && data.wellKnown != null) {
onWellknownSuccess(action, WellknownResult.Prompt(data.homeServerUrl!!, null, data.wellKnown!!), homeServerConnectionConfig)
} else {
onWellKnownError()
}
is WellknownResult.InvalidMatrixId -> {
setState {
copy(
asyncLoginAction = Uninitialized
)
}
_viewEvents.post(LoginViewEvents.Failure(Exception(stringProvider.getString(R.string.login_signin_matrix_id_error_invalid_matrix_id))))
}
else -> {
currentJob = viewModelScope.launch {
val data = try {
authenticationService.getWellKnownData(action.username, homeServerConnectionConfig)
} catch (failure: Throwable) {
onDirectLoginError(failure)
return@launch
}
when (data) {
is WellknownResult.Prompt ->
onWellknownSuccess(action, data, homeServerConnectionConfig)
is WellknownResult.FailPrompt ->
// Relax on IS discovery if home server is valid
if (data.homeServerUrl != null && data.wellKnown != null) {
onWellknownSuccess(action, WellknownResult.Prompt(data.homeServerUrl!!, null, data.wellKnown!!), homeServerConnectionConfig)
} else {
onWellKnownError()
}
}.exhaustive
}
override fun onFailure(failure: Throwable) {
onDirectLoginError(failure)
}
})
is WellknownResult.InvalidMatrixId -> {
setState {
copy(
asyncLoginAction = Uninitialized
)
}
_viewEvents.post(LoginViewEvents.Failure(Exception(stringProvider.getString(R.string.login_signin_matrix_id_error_invalid_matrix_id))))
}
else -> {
onWellKnownError()
}
}.exhaustive
}
}
private fun onWellKnownError() {
@ -594,9 +594,9 @@ class LoginViewModel @AssistedInject constructor(
_viewEvents.post(LoginViewEvents.Failure(Exception(stringProvider.getString(R.string.autodiscover_well_known_error))))
}
private fun onWellknownSuccess(action: LoginAction.LoginOrRegister,
wellKnownPrompt: WellknownResult.Prompt,
homeServerConnectionConfig: HomeServerConnectionConfig?) {
private suspend fun onWellknownSuccess(action: LoginAction.LoginOrRegister,
wellKnownPrompt: WellknownResult.Prompt,
homeServerConnectionConfig: HomeServerConnectionConfig?) {
val alteredHomeServerConnectionConfig = homeServerConnectionConfig
?.copy(
homeServerUri = Uri.parse(wellKnownPrompt.homeServerUrl),
@ -607,20 +607,17 @@ class LoginViewModel @AssistedInject constructor(
identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) }
)
authenticationService.directAuthentication(
alteredHomeServerConnectionConfig,
action.username,
action.password,
action.initialDeviceName,
object : MatrixCallback<Session> {
override fun onSuccess(data: Session) {
onSessionCreated(data)
}
override fun onFailure(failure: Throwable) {
onDirectLoginError(failure)
}
})
val data = try {
authenticationService.directAuthentication(
alteredHomeServerConnectionConfig,
action.username,
action.password,
action.initialDeviceName)
} catch (failure: Throwable) {
onDirectLoginError(failure)
return
}
onSessionCreated(data)
}
private fun onDirectLoginError(failure: Throwable) {
@ -657,35 +654,33 @@ class LoginViewModel @AssistedInject constructor(
)
}
currentTask = safeLoginWizard.login(
action.username,
action.password,
action.initialDeviceName,
object : MatrixCallback<Session> {
override fun onSuccess(data: Session) {
currentJob = viewModelScope.launch {
try {
safeLoginWizard.login(
action.username,
action.password,
action.initialDeviceName
)
} catch (failure: Throwable) {
setState {
copy(
asyncLoginAction = Fail(failure)
)
}
null
}
?.let {
reAuthHelper.data = action.password
onSessionCreated(data)
onSessionCreated(it)
}
override fun onFailure(failure: Throwable) {
setState {
copy(
asyncLoginAction = Fail(failure)
)
}
}
})
}
}
}
private fun startRegistrationFlow() {
setState {
copy(
asyncRegistration = Loading()
)
currentJob = executeRegistrationStep {
it.getRegistrationFlow()
}
currentTask = registrationWizard?.getRegistrationFlow(registrationCallback)
}
private fun startAuthenticationFlow() {
@ -706,8 +701,9 @@ class LoginViewModel @AssistedInject constructor(
}
}
private fun onSessionCreated(session: Session) {
private suspend fun onSessionCreated(session: Session) {
activeSessionHolder.setActiveSession(session)
authenticationService.reset()
session.configureAndStart(applicationContext)
setState {
@ -724,15 +720,17 @@ class LoginViewModel @AssistedInject constructor(
// Should not happen
Timber.w("homeServerConnectionConfig is null")
} else {
authenticationService.createSessionFromSso(homeServerConnectionConfigFinal, action.credentials, object : MatrixCallback<Session> {
override fun onSuccess(data: Session) {
onSessionCreated(data)
currentJob = viewModelScope.launch {
try {
authenticationService.createSessionFromSso(homeServerConnectionConfigFinal, action.credentials)
} catch (failure: Throwable) {
setState {
copy(asyncLoginAction = Fail(failure))
}
null
}
override fun onFailure(failure: Throwable) = setState {
copy(asyncLoginAction = Fail(failure))
}
})
?.let { onSessionCreated(it) }
}
}
}
@ -749,21 +747,21 @@ class LoginViewModel @AssistedInject constructor(
private fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig) {
currentHomeServerConnectionConfig = homeServerConnectionConfig
currentTask?.cancel()
currentTask = null
authenticationService.cancelPendingLoginOrRegistration()
currentJob = viewModelScope.launch {
authenticationService.cancelPendingLoginOrRegistration()
setState {
copy(
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
)
}
setState {
copy(
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
)
}
currentTask = authenticationService.getLoginFlow(homeServerConnectionConfig, object : MatrixCallback<LoginFlowResult> {
override fun onFailure(failure: Throwable) {
try {
authenticationService.getLoginFlow(homeServerConnectionConfig)
} catch (failure: Throwable) {
_viewEvents.post(LoginViewEvents.Failure(failure))
setState {
copy(
@ -772,47 +770,42 @@ class LoginViewModel @AssistedInject constructor(
serverType = if (serverType == ServerType.MatrixOrg) ServerType.Unknown else serverType
)
}
null
}
?.let { data ->
// Valid Homeserver, add it to the history.
// Note: we add what the user has input, data.homeServerUrl can be different
rememberHomeServer(homeServerConnectionConfig.homeServerUri.toString())
override fun onSuccess(data: LoginFlowResult) {
// Valid Homeserver, add it to the history.
// Note: we add what the user has input, data.homeServerUrl can be different
rememberHomeServer(homeServerConnectionConfig.homeServerUri.toString())
when (data) {
is LoginFlowResult.Success -> {
val loginMode = when {
// SSO login is taken first
data.supportedLoginTypes.contains(LoginFlowTypes.SSO)
&& data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders)
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders)
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
else -> LoginMode.Unsupported
}
when (data) {
is LoginFlowResult.Success -> {
val loginMode = when {
// SSO login is taken first
data.supportedLoginTypes.contains(LoginFlowTypes.SSO)
&& data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders)
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders)
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
else -> LoginMode.Unsupported
}
// FIXME We should post a view event here normally?
setState {
copy(
asyncHomeServerLoginFlowRequest = Uninitialized,
homeServerUrl = data.homeServerUrl,
loginMode = loginMode,
loginModeSupportedTypes = data.supportedLoginTypes.toList()
)
}
if ((loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported)
|| data.isOutdatedHomeserver) {
// Notify the UI
_viewEvents.post(LoginViewEvents.OutdatedHomeserver)
// FIXME We should post a view event here normally?
setState {
copy(
asyncHomeServerLoginFlowRequest = Uninitialized,
homeServerUrl = data.homeServerUrl,
loginMode = loginMode,
loginModeSupportedTypes = data.supportedLoginTypes.toList()
)
}
if ((loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported)
|| data.isOutdatedHomeserver) {
// Notify the UI
_viewEvents.post(LoginViewEvents.OutdatedHomeserver)
}
}
}
}
}
}
})
}
override fun onCleared() {
currentTask?.cancel()
super.onCleared()
}
}
fun getInitialHomeServerUrl(): String? {

View file

@ -25,19 +25,17 @@ import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.extensions.hasUnsavedKeys
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.login.LoginMode
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.util.Cancelable
import timber.log.Timber
/**
@ -76,54 +74,52 @@ class SoftLogoutViewModel @AssistedInject constructor(
}
}
private var currentTask: Cancelable? = null
init {
// Get the supported login flow
getSupportedLoginFlow()
}
private fun getSupportedLoginFlow() {
currentTask?.cancel()
currentTask = null
authenticationService.cancelPendingLoginOrRegistration()
viewModelScope.launch {
authenticationService.cancelPendingLoginOrRegistration()
setState {
copy(
asyncHomeServerLoginFlowRequest = Loading()
)
}
setState {
copy(
asyncHomeServerLoginFlowRequest = Loading()
)
}
currentTask = authenticationService.getLoginFlowOfSession(session.sessionId, object : MatrixCallback<LoginFlowResult> {
override fun onFailure(failure: Throwable) {
try {
authenticationService.getLoginFlowOfSession(session.sessionId)
} catch (failure: Throwable) {
setState {
copy(
asyncHomeServerLoginFlowRequest = Fail(failure)
)
}
null
}
?.let { data ->
when (data) {
is LoginFlowResult.Success -> {
val loginMode = when {
// SSO login is taken first
data.supportedLoginTypes.contains(LoginFlowTypes.SSO)
&& data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders)
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders)
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
else -> LoginMode.Unsupported
}
override fun onSuccess(data: LoginFlowResult) {
when (data) {
is LoginFlowResult.Success -> {
val loginMode = when {
// SSO login is taken first
data.supportedLoginTypes.contains(LoginFlowTypes.SSO)
&& data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders)
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders)
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
else -> LoginMode.Unsupported
}
setState {
copy(
asyncHomeServerLoginFlowRequest = Success(loginMode)
)
setState {
copy(
asyncHomeServerLoginFlowRequest = Success(loginMode)
)
}
}
}
}
}
}
})
}
}
override fun handle(action: SoftLogoutAction) {
@ -227,9 +223,4 @@ class SoftLogoutViewModel @AssistedInject constructor(
)
}
}
override fun onCleared() {
currentTask?.cancel()
super.onCleared()
}
}