Merge pull request #5398 from vector-im/bugfix/eric/softlogout-ux-broken

Fixes broken SoftLogout UX for homeservers that support both Password and SSO
This commit is contained in:
Eric Decanini 2022-07-01 15:52:48 +01:00 committed by GitHub
commit bdb49f5946
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 1058 additions and 150 deletions

1
changelog.d/5398.bugfix Normal file
View file

@ -0,0 +1 @@
Adds LoginType to SessionParams to fix soft logout form not showing for SSO and Password type

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.api.auth
enum class LoginType {
PASSWORD,
SSO,
UNSUPPORTED,
CUSTOM,
DIRECT,
UNKNOWN;
companion object {
fun fromName(name: String) = when (name) {
PASSWORD.name -> PASSWORD
SSO.name -> SSO
UNSUPPORTED.name -> UNSUPPORTED
CUSTOM.name -> CUSTOM
DIRECT.name -> DIRECT
else -> UNKNOWN
}
}
}

View file

@ -16,6 +16,8 @@
package org.matrix.android.sdk.api.auth.data package org.matrix.android.sdk.api.auth.data
import org.matrix.android.sdk.api.auth.LoginType
/** /**
* This data class holds necessary data to open a session. * This data class holds necessary data to open a session.
* You don't have to manually instantiate it. * You don't have to manually instantiate it.
@ -34,7 +36,12 @@ data class SessionParams(
/** /**
* Set to false if the current token is not valid anymore. Application should not have to use this info. * Set to false if the current token is not valid anymore. Application should not have to use this info.
*/ */
val isTokenValid: Boolean val isTokenValid: Boolean,
/**
* The authentication method that was used to create the session.
*/
val loginType: LoginType,
) { ) {
/* /*
* Shortcuts. Usually the application should only need to use these shortcuts * Shortcuts. Usually the application should only need to use these shortcuts

View file

@ -83,6 +83,9 @@ internal abstract class AuthModule {
@Binds @Binds
abstract fun bindSessionCreator(creator: DefaultSessionCreator): SessionCreator abstract fun bindSessionCreator(creator: DefaultSessionCreator): SessionCreator
@Binds
abstract fun bindSessionParamsCreator(creator: DefaultSessionParamsCreator): SessionParamsCreator
@Binds @Binds
abstract fun bindDirectLoginTask(task: DefaultDirectLoginTask): DirectLoginTask abstract fun bindDirectLoginTask(task: DefaultDirectLoginTask): DirectLoginTask

View file

@ -22,6 +22,7 @@ import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.MatrixPatterns
import org.matrix.android.sdk.api.MatrixPatterns.getServerName import org.matrix.android.sdk.api.MatrixPatterns.getServerName
import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.LoginType
import org.matrix.android.sdk.api.auth.data.Credentials 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.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.data.LoginFlowResult import org.matrix.android.sdk.api.auth.data.LoginFlowResult
@ -361,7 +362,7 @@ internal class DefaultAuthenticationService @Inject constructor(
homeServerConnectionConfig: HomeServerConnectionConfig, homeServerConnectionConfig: HomeServerConnectionConfig,
credentials: Credentials credentials: Credentials
): Session { ): Session {
return sessionCreator.createSession(credentials, homeServerConnectionConfig) return sessionCreator.createSession(credentials, homeServerConnectionConfig, LoginType.SSO)
} }
override suspend fun getWellKnownData( override suspend fun getWellKnownData(

View file

@ -16,69 +16,41 @@
package org.matrix.android.sdk.internal.auth package org.matrix.android.sdk.internal.auth
import android.net.Uri import org.matrix.android.sdk.api.auth.LoginType
import org.matrix.android.sdk.api.auth.data.Credentials 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.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.data.SessionParams
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.SessionManager
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
internal interface SessionCreator { internal interface SessionCreator {
suspend fun createSession(credentials: Credentials, homeServerConnectionConfig: HomeServerConnectionConfig): Session
suspend fun createSession(
credentials: Credentials,
homeServerConnectionConfig: HomeServerConnectionConfig,
loginType: LoginType,
): Session
} }
internal class DefaultSessionCreator @Inject constructor( internal class DefaultSessionCreator @Inject constructor(
private val sessionParamsStore: SessionParamsStore, private val sessionParamsStore: SessionParamsStore,
private val sessionManager: SessionManager, private val sessionManager: SessionManager,
private val pendingSessionStore: PendingSessionStore, private val pendingSessionStore: PendingSessionStore,
private val isValidClientServerApiTask: IsValidClientServerApiTask private val sessionParamsCreator: SessionParamsCreator,
) : SessionCreator { ) : SessionCreator {
/** /**
* Credentials can affect the homeServerConnectionConfig, override homeserver url and/or * Credentials can affect the homeServerConnectionConfig, override homeserver url and/or
* identity server url if provided in the credentials. * identity server url if provided in the credentials.
*/ */
override suspend fun createSession(credentials: Credentials, homeServerConnectionConfig: HomeServerConnectionConfig): Session { override suspend fun createSession(
credentials: Credentials,
homeServerConnectionConfig: HomeServerConnectionConfig,
loginType: LoginType,
): Session {
// We can cleanup the pending session params // We can cleanup the pending session params
pendingSessionStore.delete() pendingSessionStore.delete()
val sessionParams = sessionParamsCreator.create(credentials, homeServerConnectionConfig, loginType)
val overriddenUrl = credentials.discoveryInformation?.homeServer?.baseURL
// remove trailing "/"
?.trim { it == '/' }
?.takeIf { it.isNotBlank() }
// It can be the same value, so in this case, do not check again the validity
?.takeIf { it != homeServerConnectionConfig.homeServerUriBase.toString() }
?.also { Timber.d("Overriding homeserver url to $it (will check if valid)") }
?.let { Uri.parse(it) }
?.takeIf {
// Validate the URL, if the configuration is wrong server side, do not override
tryOrNull {
isValidClientServerApiTask.execute(
IsValidClientServerApiTask.Params(
homeServerConnectionConfig.copy(homeServerUriBase = it)
)
)
.also { Timber.d("Overriding homeserver url: $it") }
} ?: true // In case of other error (no network, etc.), consider it is valid...
}
val sessionParams = SessionParams(
credentials = credentials,
homeServerConnectionConfig = homeServerConnectionConfig.copy(
homeServerUriBase = overriddenUrl ?: homeServerConnectionConfig.homeServerUriBase,
identityServerUri = credentials.discoveryInformation?.identityServer?.baseURL
// remove trailing "/"
?.trim { it == '/' }
?.takeIf { it.isNotBlank() }
?.also { Timber.d("Overriding identity server url to $it") }
?.let { Uri.parse(it) }
?: homeServerConnectionConfig.identityServerUri
),
isTokenValid = true)
sessionParamsStore.save(sessionParams) sessionParamsStore.save(sessionParams)
return sessionManager.getOrCreateSession(sessionParams) return sessionManager.getOrCreateSession(sessionParams)
} }

View file

@ -0,0 +1,83 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.auth
import android.net.Uri
import org.matrix.android.sdk.api.auth.LoginType
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.SessionParams
import org.matrix.android.sdk.api.extensions.tryOrNull
import timber.log.Timber
import javax.inject.Inject
internal interface SessionParamsCreator {
suspend fun create(
credentials: Credentials,
homeServerConnectionConfig: HomeServerConnectionConfig,
loginType: LoginType,
): SessionParams
}
internal class DefaultSessionParamsCreator @Inject constructor(
private val isValidClientServerApiTask: IsValidClientServerApiTask
) : SessionParamsCreator {
override suspend fun create(
credentials: Credentials,
homeServerConnectionConfig: HomeServerConnectionConfig,
loginType: LoginType,
) = SessionParams(
credentials = credentials,
homeServerConnectionConfig = homeServerConnectionConfig.overrideWithCredentials(credentials),
isTokenValid = true,
loginType = loginType,
)
private suspend fun HomeServerConnectionConfig.overrideWithCredentials(credentials: Credentials) = copy(
homeServerUriBase = credentials.getHomeServerUri(this) ?: homeServerUriBase,
identityServerUri = credentials.getIdentityServerUri() ?: identityServerUri
)
private suspend fun Credentials.getHomeServerUri(homeServerConnectionConfig: HomeServerConnectionConfig) =
discoveryInformation?.homeServer?.baseURL
?.trim { it == '/' }
?.takeIf { it.isNotBlank() }
// It can be the same value, so in this case, do not check again the validity
?.takeIf { it != homeServerConnectionConfig.homeServerUriBase.toString() }
?.also { Timber.d("Overriding homeserver url to $it (will check if valid)") }
?.let { Uri.parse(it) }
?.takeIf { validateUri(it, homeServerConnectionConfig) }
private suspend fun validateUri(uri: Uri, homeServerConnectionConfig: HomeServerConnectionConfig) =
// Validate the URL, if the configuration is wrong server side, do not override
tryOrNull {
performClientServerApiValidation(uri, homeServerConnectionConfig)
} ?: true // In case of other error (no network, etc.), consider it is valid...
private suspend fun performClientServerApiValidation(uri: Uri, homeServerConnectionConfig: HomeServerConnectionConfig) =
isValidClientServerApiTask.execute(
IsValidClientServerApiTask.Params(homeServerConnectionConfig.copy(homeServerUriBase = uri))
).also { Timber.d("Overriding homeserver url: $it") }
private fun Credentials.getIdentityServerUri() = discoveryInformation?.identityServer?.baseURL
?.trim { it == '/' }
?.takeIf { it.isNotBlank() }
?.also { Timber.d("Overriding identity server url to $it") }
?.let { Uri.parse(it) }
}

View file

@ -22,6 +22,7 @@ import org.matrix.android.sdk.internal.auth.db.migration.MigrateAuthTo001
import org.matrix.android.sdk.internal.auth.db.migration.MigrateAuthTo002 import org.matrix.android.sdk.internal.auth.db.migration.MigrateAuthTo002
import org.matrix.android.sdk.internal.auth.db.migration.MigrateAuthTo003 import org.matrix.android.sdk.internal.auth.db.migration.MigrateAuthTo003
import org.matrix.android.sdk.internal.auth.db.migration.MigrateAuthTo004 import org.matrix.android.sdk.internal.auth.db.migration.MigrateAuthTo004
import org.matrix.android.sdk.internal.auth.db.migration.MigrateAuthTo005
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -33,7 +34,7 @@ internal class AuthRealmMigration @Inject constructor() : RealmMigration {
override fun equals(other: Any?) = other is AuthRealmMigration override fun equals(other: Any?) = other is AuthRealmMigration
override fun hashCode() = 4000 override fun hashCode() = 4000
val schemaVersion = 4L val schemaVersion = 5L
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
Timber.d("Migrating Auth Realm from $oldVersion to $newVersion") Timber.d("Migrating Auth Realm from $oldVersion to $newVersion")
@ -42,5 +43,6 @@ internal class AuthRealmMigration @Inject constructor() : RealmMigration {
if (oldVersion < 2) MigrateAuthTo002(realm).perform() if (oldVersion < 2) MigrateAuthTo002(realm).perform()
if (oldVersion < 3) MigrateAuthTo003(realm).perform() if (oldVersion < 3) MigrateAuthTo003(realm).perform()
if (oldVersion < 4) MigrateAuthTo004(realm).perform() if (oldVersion < 4) MigrateAuthTo004(realm).perform()
if (oldVersion < 5) MigrateAuthTo005(realm).perform()
} }
} }

View file

@ -26,5 +26,6 @@ internal open class SessionParamsEntity(
var homeServerConnectionConfigJson: String = "", var homeServerConnectionConfigJson: String = "",
// Set to false when the token is invalid and the user has been soft logged out // Set to false when the token is invalid and the user has been soft logged out
// In case of hard logout, this object is deleted from DB // In case of hard logout, this object is deleted from DB
var isTokenValid: Boolean = true var isTokenValid: Boolean = true,
var loginType: String = "",
) : RealmObject() ) : RealmObject()

View file

@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.auth.db package org.matrix.android.sdk.internal.auth.db
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
import org.matrix.android.sdk.api.auth.LoginType
import org.matrix.android.sdk.api.auth.data.Credentials 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.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.auth.data.SessionParams
@ -37,7 +38,7 @@ internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
if (credentials == null || homeServerConnectionConfig == null) { if (credentials == null || homeServerConnectionConfig == null) {
return null return null
} }
return SessionParams(credentials, homeServerConnectionConfig, entity.isTokenValid) return SessionParams(credentials, homeServerConnectionConfig, entity.isTokenValid, LoginType.fromName(entity.loginType))
} }
fun map(sessionParams: SessionParams?): SessionParamsEntity? { fun map(sessionParams: SessionParams?): SessionParamsEntity? {
@ -54,7 +55,8 @@ internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
sessionParams.userId, sessionParams.userId,
credentialsJson, credentialsJson,
homeServerConnectionConfigJson, homeServerConnectionConfigJson,
sessionParams.isTokenValid sessionParams.isTokenValid,
sessionParams.loginType.name,
) )
} }
} }

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.auth.db.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.api.auth.LoginType
import org.matrix.android.sdk.internal.auth.db.SessionParamsEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
import timber.log.Timber
internal class MigrateAuthTo005(realm: DynamicRealm) : RealmMigrator(realm, 5) {
override fun doMigrate(realm: DynamicRealm) {
Timber.d("Update SessionParamsEntity to add LoginType")
realm.schema.get("SessionParamsEntity")
?.addField(SessionParamsEntityFields.LOGIN_TYPE, String::class.java)
?.setRequired(SessionParamsEntityFields.LOGIN_TYPE, true)
?.transform { it.set(SessionParamsEntityFields.LOGIN_TYPE, LoginType.UNKNOWN.name) }
}
}

View file

@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.auth.login package org.matrix.android.sdk.internal.auth.login
import android.util.Patterns import android.util.Patterns
import org.matrix.android.sdk.api.auth.LoginType
import org.matrix.android.sdk.api.auth.login.LoginProfileInfo import org.matrix.android.sdk.api.auth.login.LoginProfileInfo
import org.matrix.android.sdk.api.auth.login.LoginWizard import org.matrix.android.sdk.api.auth.login.LoginWizard
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
@ -78,7 +79,7 @@ internal class DefaultLoginWizard(
authAPI.login(loginParams) authAPI.login(loginParams)
} }
return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig) return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig, LoginType.PASSWORD)
} }
/** /**
@ -92,7 +93,7 @@ internal class DefaultLoginWizard(
authAPI.login(loginParams) authAPI.login(loginParams)
} }
return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig) return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig, LoginType.SSO)
} }
override suspend fun loginCustom(data: JsonDict): Session { override suspend fun loginCustom(data: JsonDict): Session {
@ -100,7 +101,7 @@ internal class DefaultLoginWizard(
authAPI.login(data) authAPI.login(data)
} }
return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig) return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig, LoginType.CUSTOM)
} }
override suspend fun resetPassword(email: String) { override suspend fun resetPassword(email: String) {

View file

@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.auth.login
import dagger.Lazy import dagger.Lazy
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.auth.LoginType
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
@ -77,7 +78,7 @@ internal class DefaultDirectLoginTask @Inject constructor(
} }
} }
return sessionCreator.createSession(credentials, params.homeServerConnectionConfig) return sessionCreator.createSession(credentials, params.homeServerConnectionConfig, LoginType.DIRECT)
} }
private fun buildClient(homeServerConnectionConfig: HomeServerConnectionConfig): OkHttpClient { private fun buildClient(homeServerConnectionConfig: HomeServerConnectionConfig): OkHttpClient {

View file

@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.auth.registration package org.matrix.android.sdk.internal.auth.registration
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import org.matrix.android.sdk.api.auth.LoginType
import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes 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.RegisterThreePid
@ -64,7 +65,7 @@ internal class DefaultRegistrationWizard(
override suspend fun getRegistrationFlow(): RegistrationResult { override suspend fun getRegistrationFlow(): RegistrationResult {
val params = RegistrationParams() val params = RegistrationParams()
return performRegistrationRequest(params) return performRegistrationRequest(params, LoginType.PASSWORD)
} }
override suspend fun createAccount( override suspend fun createAccount(
@ -77,7 +78,7 @@ internal class DefaultRegistrationWizard(
password = password, password = password,
initialDeviceDisplayName = initialDeviceDisplayName initialDeviceDisplayName = initialDeviceDisplayName
) )
return performRegistrationRequest(params) return performRegistrationRequest(params, LoginType.PASSWORD)
.also { .also {
pendingSessionData = pendingSessionData.copy(isRegistrationStarted = true) pendingSessionData = pendingSessionData.copy(isRegistrationStarted = true)
.also { pendingSessionStore.savePendingSessionData(it) } .also { pendingSessionStore.savePendingSessionData(it) }
@ -89,7 +90,7 @@ internal class DefaultRegistrationWizard(
?: throw IllegalStateException("developer error, call createAccount() method first") ?: throw IllegalStateException("developer error, call createAccount() method first")
val params = RegistrationParams(auth = AuthParams.createForCaptcha(safeSession, response)) val params = RegistrationParams(auth = AuthParams.createForCaptcha(safeSession, response))
return performRegistrationRequest(params) return performRegistrationRequest(params, LoginType.PASSWORD)
} }
override suspend fun acceptTerms(): RegistrationResult { override suspend fun acceptTerms(): RegistrationResult {
@ -97,7 +98,7 @@ internal class DefaultRegistrationWizard(
?: throw IllegalStateException("developer error, call createAccount() method first") ?: throw IllegalStateException("developer error, call createAccount() method first")
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.TERMS, session = safeSession)) val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.TERMS, session = safeSession))
return performRegistrationRequest(params) return performRegistrationRequest(params, LoginType.PASSWORD)
} }
override suspend fun addThreePid(threePid: RegisterThreePid): RegistrationResult { override suspend fun addThreePid(threePid: RegisterThreePid): RegistrationResult {
@ -151,14 +152,14 @@ internal class DefaultRegistrationWizard(
.also { pendingSessionStore.savePendingSessionData(it) } .also { pendingSessionStore.savePendingSessionData(it) }
// and send the sid a first time // and send the sid a first time
return performRegistrationRequest(params) return performRegistrationRequest(params, LoginType.PASSWORD)
} }
override suspend fun checkIfEmailHasBeenValidated(delayMillis: Long): RegistrationResult { override suspend fun checkIfEmailHasBeenValidated(delayMillis: Long): RegistrationResult {
val safeParam = pendingSessionData.currentThreePidData?.registrationParams val safeParam = pendingSessionData.currentThreePidData?.registrationParams
?: throw IllegalStateException("developer error, no pending three pid") ?: throw IllegalStateException("developer error, no pending three pid")
return performRegistrationRequest(safeParam, delayMillis) return performRegistrationRequest(safeParam, LoginType.PASSWORD, delayMillis)
} }
override suspend fun handleValidateThreePid(code: String): RegistrationResult { override suspend fun handleValidateThreePid(code: String): RegistrationResult {
@ -179,7 +180,7 @@ internal class DefaultRegistrationWizard(
if (validationResponse.isSuccess()) { if (validationResponse.isSuccess()) {
// The entered code is correct // The entered code is correct
// Same than validate email // Same than validate email
return performRegistrationRequest(registrationParams, 3_000) return performRegistrationRequest(registrationParams, LoginType.PASSWORD, 3_000)
} else { } else {
// The code is not correct // The code is not correct
throw Failure.SuccessError throw Failure.SuccessError
@ -191,7 +192,7 @@ internal class DefaultRegistrationWizard(
?: throw IllegalStateException("developer error, call createAccount() method first") ?: throw IllegalStateException("developer error, call createAccount() method first")
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.DUMMY, session = safeSession)) val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.DUMMY, session = safeSession))
return performRegistrationRequest(params) return performRegistrationRequest(params, LoginType.PASSWORD)
} }
override suspend fun registrationCustom( override suspend fun registrationCustom(
@ -204,25 +205,28 @@ internal class DefaultRegistrationWizard(
mutableParams["session"] = safeSession mutableParams["session"] = safeSession
val params = RegistrationCustomParams(auth = mutableParams) val params = RegistrationCustomParams(auth = mutableParams)
return performRegistrationOtherRequest(params) return performRegistrationOtherRequest(LoginType.CUSTOM, params)
} }
private suspend fun performRegistrationRequest( private suspend fun performRegistrationRequest(
registrationParams: RegistrationParams, registrationParams: RegistrationParams,
loginType: LoginType,
delayMillis: Long = 0 delayMillis: Long = 0
): RegistrationResult { ): RegistrationResult {
delay(delayMillis) delay(delayMillis)
return register { registerTask.execute(RegisterTask.Params(registrationParams)) } return register(loginType) { registerTask.execute(RegisterTask.Params(registrationParams)) }
} }
private suspend fun performRegistrationOtherRequest( private suspend fun performRegistrationOtherRequest(
registrationCustomParams: RegistrationCustomParams loginType: LoginType,
registrationCustomParams: RegistrationCustomParams,
): RegistrationResult { ): RegistrationResult {
return register { registerCustomTask.execute(RegisterCustomTask.Params(registrationCustomParams)) } return register(loginType) { registerCustomTask.execute(RegisterCustomTask.Params(registrationCustomParams)) }
} }
private suspend fun register( private suspend fun register(
execute: suspend () -> Credentials loginType: LoginType,
execute: suspend () -> Credentials,
): RegistrationResult { ): RegistrationResult {
val credentials = try { val credentials = try {
execute.invoke() execute.invoke()
@ -237,8 +241,7 @@ internal class DefaultRegistrationWizard(
} }
} }
val session = val session = sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig, loginType)
sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
return RegistrationResult.Success(session) return RegistrationResult.Success(session)
} }

View file

@ -20,6 +20,7 @@ import android.content.Context
import io.realm.Realm import io.realm.Realm
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.auth.LoginType
import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.auth.data.DiscoveryInformation import org.matrix.android.sdk.api.auth.data.DiscoveryInformation
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
@ -145,7 +146,8 @@ internal class DefaultLegacySessionImporter @Inject constructor(
forceUsageTlsVersions = legacyConfig.forceUsageOfTlsVersions() forceUsageTlsVersions = legacyConfig.forceUsageOfTlsVersions()
), ),
// If token is not valid, this boolean will be updated later // If token is not valid, this boolean will be updated later
isTokenValid = true isTokenValid = true,
loginType = LoginType.UNKNOWN,
) )
Timber.d("Migration: save session") Timber.d("Migration: save session")

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.auth.db.migration
import org.junit.Test
import org.matrix.android.sdk.test.fakes.internal.auth.db.migration.Fake005MigrationRealm
class MigrateAuthTo005Test {
private val fakeRealm = Fake005MigrationRealm()
private val migrator = MigrateAuthTo005(fakeRealm.instance)
@Test
fun `when doMigrate, then LoginType field added`() {
migrator.doMigrate(fakeRealm.instance)
fakeRealm.verifyLoginTypeAdded()
}
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.auth.login
import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldNotBeEqualTo
import org.junit.Test
import org.matrix.android.sdk.api.auth.LoginType
class LoginTypeTest {
@Test
fun `when getting type fromName, then map correctly`() {
LoginType.fromName(LoginType.PASSWORD.name) shouldBeEqualTo LoginType.PASSWORD
LoginType.fromName(LoginType.SSO.name) shouldBeEqualTo LoginType.SSO
LoginType.fromName(LoginType.UNSUPPORTED.name) shouldBeEqualTo LoginType.UNSUPPORTED
LoginType.fromName(LoginType.CUSTOM.name) shouldBeEqualTo LoginType.CUSTOM
LoginType.fromName(LoginType.DIRECT.name) shouldBeEqualTo LoginType.DIRECT
LoginType.fromName(LoginType.UNKNOWN.name) shouldBeEqualTo LoginType.UNKNOWN
}
@Test // The failure of this test means that an existing type has not been correctly added to fromValue
fun `given non-unknown type name, when getting type fromName, then type is not UNKNOWN`() {
val types = LoginType.values()
types.forEach { type ->
if (type != LoginType.UNKNOWN) {
LoginType.fromName(type.name) shouldNotBeEqualTo LoginType.UNKNOWN
}
}
}
}

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.test.fakes.api
import io.mockk.mockk
import org.matrix.android.sdk.api.session.Session
class FakeSession {
val instance: Session = mockk()
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.test.fakes.internal
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import org.amshove.kluent.shouldBeEqualTo
import org.matrix.android.sdk.api.auth.data.SessionParams
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.test.fakes.api.FakeSession
internal class FakeSessionManager {
val instance: SessionManager = mockk()
init {
every { instance.getOrCreateSession(any()) } returns fakeSession.instance
}
fun assertSessionCreatedWithParams(session: Session, sessionParams: SessionParams) {
verify { instance.getOrCreateSession(sessionParams) }
session shouldBeEqualTo fakeSession.instance
}
companion object {
private val fakeSession = FakeSession()
}
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.test.fakes.internal.auth
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.mockk
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.internal.auth.IsValidClientServerApiTask
import org.matrix.android.sdk.internal.auth.IsValidClientServerApiTask.Params
internal class FakeIsValidClientServerApiTask {
init {
coEvery { instance.execute(any()) } returns true
}
val instance: IsValidClientServerApiTask = mockk()
fun givenValidationFails() {
coEvery { instance.execute(any()) } returns false
}
fun verifyExecutionWithConfig(config: HomeServerConnectionConfig) {
coVerify { instance.execute(Params(config)) }
}
fun verifyNoExecution() {
coVerify(inverse = true) { instance.execute(any()) }
}
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.test.fakes.internal.auth
import io.mockk.coJustRun
import io.mockk.coVerify
import io.mockk.mockk
import org.matrix.android.sdk.internal.auth.PendingSessionStore
internal class FakePendingSessionStore {
val instance: PendingSessionStore = mockk()
init {
coJustRun { instance.delete() }
}
fun verifyPendingSessionDataCleared() {
coVerify { instance.delete() }
}
}

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.test.fakes.internal.auth
import android.net.Uri
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import org.matrix.android.sdk.api.auth.LoginType
import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.internal.auth.SessionParamsCreator
import org.matrix.android.sdk.test.fixtures.SessionParamsFixture.aSessionParams
internal class FakeSessionParamsCreator {
val instance: SessionParamsCreator = mockk()
init {
mockkStatic(Uri::class)
every { Uri.parse(any()) } returns mockk()
coEvery { instance.create(any(), any(), any()) } returns sessionParams
}
fun verifyCreatedWithParameters(
credentials: Credentials,
homeServerConnectionConfig: HomeServerConnectionConfig,
loginType: LoginType,
) {
coVerify { instance.create(credentials, homeServerConnectionConfig, loginType) }
}
companion object {
val sessionParams = aSessionParams()
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.test.fakes.internal.auth
import io.mockk.coJustRun
import io.mockk.coVerify
import io.mockk.mockk
import org.matrix.android.sdk.api.auth.data.SessionParams
import org.matrix.android.sdk.internal.auth.SessionParamsStore
internal class FakeSessionParamsStore {
val instance: SessionParamsStore = mockk()
init {
coJustRun { instance.save(any()) }
}
fun verifyParamsSaved(sessionParams: SessionParams) {
coVerify { instance.save(sessionParams) }
}
}

View file

@ -0,0 +1,54 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.test.fakes.internal.auth.db.migration
import io.mockk.every
import io.mockk.mockk
import io.mockk.verifyOrder
import io.realm.DynamicRealm
import io.realm.RealmObjectSchema
import io.realm.RealmSchema
import org.matrix.android.sdk.internal.auth.db.SessionParamsEntityFields
class Fake005MigrationRealm {
val instance: DynamicRealm = mockk()
private val schema: RealmSchema = mockk()
private val objectSchema: RealmObjectSchema = mockk()
init {
every { instance.schema } returns schema
every { schema.get("SessionParamsEntity") } returns objectSchema
every { objectSchema.addField(any(), any()) } returns objectSchema
every { objectSchema.setRequired(any(), any()) } returns objectSchema
every { objectSchema.transform(any()) } returns objectSchema
}
fun verifyLoginTypeAdded() {
verifyLoginTypeFieldAddedAndTransformed()
}
private fun verifyLoginTypeFieldAddedAndTransformed() {
verifyOrder {
objectSchema["SessionParamsEntity"]
objectSchema.addField(SessionParamsEntityFields.LOGIN_TYPE, String::class.java)
objectSchema.setRequired(SessionParamsEntityFields.LOGIN_TYPE, true)
objectSchema.transform(any())
}
}
}

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.test.fakes.internal.auth.db.sessionparams
import com.squareup.moshi.JsonAdapter
import io.mockk.every
import io.mockk.mockk
import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.test.fakes.internal.auth.db.sessionparams.FakeSessionParamsMapperMoshi.Companion.sessionParams
import org.matrix.android.sdk.test.fakes.internal.auth.db.sessionparams.FakeSessionParamsMapperMoshi.Companion.sessionParamsEntity
import org.matrix.android.sdk.test.fixtures.CredentialsFixture.aCredentials
internal class FakeCredentialsJsonAdapter {
val instance: JsonAdapter<Credentials> = mockk()
init {
every { instance.fromJson(sessionParamsEntity.credentialsJson) } returns credentials
every { instance.toJson(sessionParams.credentials) } returns CREDENTIALS_JSON
}
fun givenNullDeserialization() {
every { instance.fromJson(sessionParamsEntity.credentialsJson) } returns null
}
fun givenNullSerialization() {
every { instance.toJson(credentials) } returns null
}
companion object {
val credentials = aCredentials()
const val CREDENTIALS_JSON = "credentials_json"
}
}

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.test.fakes.internal.auth.db.sessionparams
import com.squareup.moshi.JsonAdapter
import io.mockk.every
import io.mockk.mockk
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.test.fakes.internal.auth.db.sessionparams.FakeSessionParamsMapperMoshi.Companion.sessionParams
import org.matrix.android.sdk.test.fakes.internal.auth.db.sessionparams.FakeSessionParamsMapperMoshi.Companion.sessionParamsEntity
internal class FakeHomeServerConnectionConfigJsonAdapter {
val instance: JsonAdapter<HomeServerConnectionConfig> = mockk()
init {
every { instance.fromJson(sessionParamsEntity.homeServerConnectionConfigJson) } returns homeServerConnectionConfig
every { instance.toJson(sessionParams.homeServerConnectionConfig) } returns HOME_SERVER_CONNECTION_CONFIG_JSON
}
fun givenNullDeserialization() {
every { instance.fromJson(sessionParamsEntity.credentialsJson) } returns null
}
fun givenNullSerialization() {
every { instance.toJson(homeServerConnectionConfig) } returns null
}
companion object {
val homeServerConnectionConfig = HomeServerConnectionConfig.Builder().withHomeServerUri("homeserver").build()
const val HOME_SERVER_CONNECTION_CONFIG_JSON = "home_server_connection_config_json"
}
}

View file

@ -0,0 +1,86 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.test.fakes.internal.auth.db.sessionparams
import android.net.Uri
import com.squareup.moshi.Moshi
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldBeNull
import org.matrix.android.sdk.api.auth.LoginType
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.SessionParams
import org.matrix.android.sdk.api.auth.data.sessionId
import org.matrix.android.sdk.internal.auth.db.SessionParamsEntity
import org.matrix.android.sdk.test.fakes.internal.auth.db.sessionparams.FakeCredentialsJsonAdapter.Companion.CREDENTIALS_JSON
import org.matrix.android.sdk.test.fakes.internal.auth.db.sessionparams.FakeCredentialsJsonAdapter.Companion.credentials
import org.matrix.android.sdk.test.fakes.internal.auth.db.sessionparams.FakeHomeServerConnectionConfigJsonAdapter.Companion.HOME_SERVER_CONNECTION_CONFIG_JSON
import org.matrix.android.sdk.test.fakes.internal.auth.db.sessionparams.FakeHomeServerConnectionConfigJsonAdapter.Companion.homeServerConnectionConfig
import org.matrix.android.sdk.test.fixtures.SessionParamsEntityFixture.aSessionParamsEntity
import org.matrix.android.sdk.test.fixtures.SessionParamsFixture.aSessionParams
internal class FakeSessionParamsMapperMoshi {
val instance: Moshi = mockk()
private val credentialsJsonAdapter = FakeCredentialsJsonAdapter()
private val homeServerConnectionConfigAdapter = FakeHomeServerConnectionConfigJsonAdapter()
init {
mockkStatic(Uri::class)
every { Uri.parse(any()) } returns mockk()
every { instance.adapter(Credentials::class.java) } returns credentialsJsonAdapter.instance
every { instance.adapter(HomeServerConnectionConfig::class.java) } returns homeServerConnectionConfigAdapter.instance
}
fun assertSessionParamsWasMappedSuccessfully(sessionParams: SessionParams?) {
sessionParams shouldBeEqualTo SessionParams(
credentials,
homeServerConnectionConfig,
sessionParamsEntity.isTokenValid,
LoginType.fromName(sessionParamsEntity.loginType)
)
}
fun assertSessionParamsIsNull(sessionParams: SessionParams?) {
sessionParams.shouldBeNull()
}
fun assertSessionParamsEntityWasMappedSuccessfully(sessionParamsEntity: SessionParamsEntity?) {
sessionParamsEntity shouldBeEqualTo SessionParamsEntity(
sessionParams.credentials.sessionId(),
sessionParams.userId,
CREDENTIALS_JSON,
HOME_SERVER_CONNECTION_CONFIG_JSON,
sessionParams.isTokenValid,
sessionParams.loginType.name,
)
}
fun assertSessionParamsEntityIsNull(sessionParamsEntity: SessionParamsEntity?) {
sessionParamsEntity.shouldBeNull()
}
companion object {
val sessionParams = aSessionParams()
val sessionParamsEntity = aSessionParamsEntity()
val nullSessionParams: SessionParams? = null
val nullSessionParamsEntity: SessionParamsEntity? = null
}
}

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.test.fixtures
import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.auth.data.DiscoveryInformation
object CredentialsFixture {
fun aCredentials(
userId: String = "",
accessToken: String = "",
refreshToken: String? = null,
homeServer: String? = null,
deviceId: String? = null,
discoveryInformation: DiscoveryInformation? = null,
) = Credentials(
userId,
accessToken,
refreshToken,
homeServer,
deviceId,
discoveryInformation,
)
}

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.test.fixtures
import org.matrix.android.sdk.api.auth.data.DiscoveryInformation
import org.matrix.android.sdk.api.auth.data.WellKnownBaseConfig
object DiscoveryInformationFixture {
fun aDiscoveryInformation(
homeServer: WellKnownBaseConfig? = null,
identityServer: WellKnownBaseConfig? = null,
) = DiscoveryInformation(
homeServer,
identityServer
)
}

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.test.fixtures
import org.matrix.android.sdk.internal.auth.db.SessionParamsEntity
internal object SessionParamsEntityFixture {
fun aSessionParamsEntity(
sessionId: String = "",
userId: String = "",
credentialsJson: String = "",
homeServerConnectionConfigJson: String = "",
isTokenValid: Boolean = true,
loginType: String = "",
) = SessionParamsEntity(
sessionId,
userId,
credentialsJson,
homeServerConnectionConfigJson,
isTokenValid,
loginType,
)
}

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.test.fixtures
import org.matrix.android.sdk.api.auth.LoginType
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.SessionParams
import org.matrix.android.sdk.test.fixtures.CredentialsFixture.aCredentials
object SessionParamsFixture {
fun aSessionParams(
credentials: Credentials = aCredentials(),
homeServerConnectionConfig: HomeServerConnectionConfig = HomeServerConnectionConfig.Builder().withHomeServerUri("homeserver").build(),
isTokenValid: Boolean = false,
loginType: LoginType = LoginType.UNKNOWN,
) = SessionParams(
credentials,
homeServerConnectionConfig,
isTokenValid,
loginType,
)
}

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.test.fixtures
import org.matrix.android.sdk.api.auth.data.WellKnownBaseConfig
object WellKnownBaseConfigFixture {
fun aWellKnownBaseConfig(
baseUrl: String? = null,
) = WellKnownBaseConfig(
baseUrl,
)
}

View file

@ -37,6 +37,7 @@ import im.vector.app.core.extensions.addFragment
import im.vector.app.core.extensions.addFragmentToBackstack import im.vector.app.core.extensions.addFragmentToBackstack
import im.vector.app.core.extensions.validateBackPressed import im.vector.app.core.extensions.validateBackPressed
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.utils.openUrlInChromeCustomTab
import im.vector.app.databinding.ActivityLoginBinding import im.vector.app.databinding.ActivityLoginBinding
import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.analytics.plan.MobileScreen
import im.vector.app.features.home.HomeActivity import im.vector.app.features.home.HomeActivity
@ -272,8 +273,8 @@ open class LoginActivity : VectorBaseActivity<ActivityLoginBinding>(), UnlockedA
SignMode.SignIn -> { SignMode.SignIn -> {
// It depends on the LoginMode // It depends on the LoginMode
when (state.loginMode) { when (state.loginMode) {
LoginMode.Unknown, LoginMode.Unknown -> error("Developer error")
is LoginMode.Sso -> error("Developer error") is LoginMode.Sso -> launchSsoFlow()
is LoginMode.SsoAndPassword, is LoginMode.SsoAndPassword,
LoginMode.Password -> addFragmentToBackstack( LoginMode.Password -> addFragmentToBackstack(
views.loginFragmentContainer, views.loginFragmentContainer,
@ -293,6 +294,16 @@ open class LoginActivity : VectorBaseActivity<ActivityLoginBinding>(), UnlockedA
} }
} }
private fun launchSsoFlow() = withState(loginViewModel) { state ->
loginViewModel.getSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId,
providerId = null,
)?.let { ssoUrl ->
openUrlInChromeCustomTab(this, null, ssoUrl)
}
}
/** /**
* Handle the SSO redirection here. * Handle the SSO redirection here.
*/ */

View file

@ -18,6 +18,7 @@ package im.vector.app.features.signout.soft
import com.airbnb.epoxy.EpoxyController import com.airbnb.epoxy.EpoxyController
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Incomplete
import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.Uninitialized
@ -35,6 +36,7 @@ import im.vector.app.features.signout.soft.epoxy.loginRedButtonItem
import im.vector.app.features.signout.soft.epoxy.loginTextItem import im.vector.app.features.signout.soft.epoxy.loginTextItem
import im.vector.app.features.signout.soft.epoxy.loginTitleItem import im.vector.app.features.signout.soft.epoxy.loginTitleItem
import im.vector.app.features.signout.soft.epoxy.loginTitleSmallItem import im.vector.app.features.signout.soft.epoxy.loginTitleSmallItem
import org.matrix.android.sdk.api.auth.LoginType
import javax.inject.Inject import javax.inject.Inject
class SoftLogoutController @Inject constructor( class SoftLogoutController @Inject constructor(
@ -91,25 +93,38 @@ class SoftLogoutController @Inject constructor(
} }
} }
private fun buildForm(state: SoftLogoutViewState) { private fun buildForm(state: SoftLogoutViewState) = when (state.asyncHomeServerLoginFlowRequest) {
val host = this is Fail -> buildLoginErrorWithRetryItem(state.asyncHomeServerLoginFlowRequest.error)
when (state.asyncHomeServerLoginFlowRequest) { is Success -> buildLoginSuccessItem(state)
Uninitialized, is Loading, Uninitialized -> buildLoadingItem()
is Loading -> { is Incomplete -> Unit
}
private fun buildLoadingItem() {
loadingItem { loadingItem {
id("loading") id("loading")
} }
} }
is Fail -> {
private fun buildLoginErrorWithRetryItem(error: Throwable) {
val host = this
loginErrorWithRetryItem { loginErrorWithRetryItem {
id("errorRetry") id("errorRetry")
text(host.errorFormatter.toHumanReadable(state.asyncHomeServerLoginFlowRequest.error)) text(host.errorFormatter.toHumanReadable(error))
listener { host.listener?.retry() } listener { host.listener?.retry() }
} }
} }
is Success -> {
when (state.asyncHomeServerLoginFlowRequest.invoke()) { private fun buildLoginSuccessItem(state: SoftLogoutViewState) = when (state.asyncHomeServerLoginFlowRequest.invoke()) {
LoginMode.Password -> { LoginMode.Password -> buildLoginPasswordForm(state)
is LoginMode.Sso -> buildLoginSSOForm()
is LoginMode.SsoAndPassword -> disambiguateLoginSSOAndPasswordForm(state)
LoginMode.Unsupported -> buildLoginUnsupportedForm()
LoginMode.Unknown, null -> Unit // Should not happen
}
private fun buildLoginPasswordForm(state: SoftLogoutViewState) {
val host = this
loginPasswordFormItem { loginPasswordFormItem {
id("passwordForm") id("passwordForm")
stringProvider(host.stringProvider) stringProvider(host.stringProvider)
@ -121,27 +136,35 @@ class SoftLogoutController @Inject constructor(
submitClickListener { host.listener?.submit() } submitClickListener { host.listener?.submit() }
} }
} }
is LoginMode.Sso -> {
private fun buildLoginSSOForm() {
val host = this
loginCenterButtonItem { loginCenterButtonItem {
id("sso") id("sso")
text(host.stringProvider.getString(R.string.login_signin_sso)) text(host.stringProvider.getString(R.string.login_signin_sso))
listener { host.listener?.signinFallbackSubmit() } listener { host.listener?.signinFallbackSubmit() }
} }
} }
is LoginMode.SsoAndPassword -> {
private fun disambiguateLoginSSOAndPasswordForm(state: SoftLogoutViewState) {
when (state.loginType) {
LoginType.PASSWORD -> buildLoginPasswordForm(state)
LoginType.SSO -> buildLoginSSOForm()
LoginType.DIRECT,
LoginType.CUSTOM,
LoginType.UNSUPPORTED -> buildLoginUnsupportedForm()
LoginType.UNKNOWN -> Unit
} }
LoginMode.Unsupported -> { }
private fun buildLoginUnsupportedForm() {
val host = this
loginCenterButtonItem { loginCenterButtonItem {
id("fallback") id("fallback")
text(host.stringProvider.getString(R.string.login_signin)) text(host.stringProvider.getString(R.string.login_signin))
listener { host.listener?.signinFallbackSubmit() } listener { host.listener?.signinFallbackSubmit() }
} }
} }
LoginMode.Unknown -> Unit // Should not happen
}
}
}
}
private fun buildClearDataSection() { private fun buildClearDataSection() {
val host = this val host = this

View file

@ -35,14 +35,12 @@ import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.login.LoginMode import im.vector.app.features.login.LoginMode
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.LoginType
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.getUser import org.matrix.android.sdk.api.session.getUser
import timber.log.Timber import timber.log.Timber
/**
* TODO Test push: disable the pushers?
*/
class SoftLogoutViewModel @AssistedInject constructor( class SoftLogoutViewModel @AssistedInject constructor(
@Assisted initialState: SoftLogoutViewState, @Assisted initialState: SoftLogoutViewState,
private val session: Session, private val session: Session,
@ -70,7 +68,8 @@ class SoftLogoutViewModel @AssistedInject constructor(
userId = userId, userId = userId,
deviceId = session.sessionParams.deviceId.orEmpty(), deviceId = session.sessionParams.deviceId.orEmpty(),
userDisplayName = session.getUser(userId)?.displayName ?: userId, userDisplayName = session.getUser(userId)?.displayName ?: userId,
hasUnsavedKeys = session.hasUnsavedKeys() hasUnsavedKeys = session.hasUnsavedKeys(),
loginType = session.sessionParams.loginType,
) )
} else { } else {
SoftLogoutViewState( SoftLogoutViewState(
@ -78,7 +77,8 @@ class SoftLogoutViewModel @AssistedInject constructor(
userId = "", userId = "",
deviceId = "", deviceId = "",
userDisplayName = "", userDisplayName = "",
hasUnsavedKeys = false hasUnsavedKeys = false,
loginType = LoginType.UNKNOWN,
) )
} }
} }

View file

@ -22,6 +22,7 @@ import com.airbnb.mvrx.MavericksState
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.Uninitialized
import im.vector.app.features.login.LoginMode import im.vector.app.features.login.LoginMode
import org.matrix.android.sdk.api.auth.LoginType
data class SoftLogoutViewState( data class SoftLogoutViewState(
val asyncHomeServerLoginFlowRequest: Async<LoginMode> = Uninitialized, val asyncHomeServerLoginFlowRequest: Async<LoginMode> = Uninitialized,
@ -31,7 +32,8 @@ data class SoftLogoutViewState(
val deviceId: String, val deviceId: String,
val userDisplayName: String, val userDisplayName: String,
val hasUnsavedKeys: Boolean, val hasUnsavedKeys: Boolean,
val enteredPassword: String = "" val loginType: LoginType,
val enteredPassword: String = "",
) : MavericksState { ) : MavericksState {
fun isLoading(): Boolean { fun isLoading(): Boolean {

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2021 New Vector Ltd * Copyright (c) 2022 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.