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
import org.matrix.android.sdk.api.auth.LoginType
/**
* This data class holds necessary data to open a session.
* 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.
*/
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

View file

@ -83,6 +83,9 @@ internal abstract class AuthModule {
@Binds
abstract fun bindSessionCreator(creator: DefaultSessionCreator): SessionCreator
@Binds
abstract fun bindSessionParamsCreator(creator: DefaultSessionParamsCreator): SessionParamsCreator
@Binds
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.getServerName
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.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
@ -361,7 +362,7 @@ internal class DefaultAuthenticationService @Inject constructor(
homeServerConnectionConfig: HomeServerConnectionConfig,
credentials: Credentials
): Session {
return sessionCreator.createSession(credentials, homeServerConnectionConfig)
return sessionCreator.createSession(credentials, homeServerConnectionConfig, LoginType.SSO)
}
override suspend fun getWellKnownData(

View file

@ -16,69 +16,41 @@
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 org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.internal.SessionManager
import timber.log.Timber
import javax.inject.Inject
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(
private val sessionParamsStore: SessionParamsStore,
private val sessionManager: SessionManager,
private val pendingSessionStore: PendingSessionStore,
private val isValidClientServerApiTask: IsValidClientServerApiTask
private val sessionParamsCreator: SessionParamsCreator,
) : SessionCreator {
/**
* Credentials can affect the homeServerConnectionConfig, override homeserver url and/or
* 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
pendingSessionStore.delete()
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)
val sessionParams = sessionParamsCreator.create(credentials, homeServerConnectionConfig, loginType)
sessionParamsStore.save(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.MigrateAuthTo003
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 javax.inject.Inject
@ -33,7 +34,7 @@ internal class AuthRealmMigration @Inject constructor() : RealmMigration {
override fun equals(other: Any?) = other is AuthRealmMigration
override fun hashCode() = 4000
val schemaVersion = 4L
val schemaVersion = 5L
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
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 < 3) MigrateAuthTo003(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 = "",
// 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
var isTokenValid: Boolean = true
var isTokenValid: Boolean = true,
var loginType: String = "",
) : RealmObject()

View file

@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.auth.db
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.HomeServerConnectionConfig
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) {
return null
}
return SessionParams(credentials, homeServerConnectionConfig, entity.isTokenValid)
return SessionParams(credentials, homeServerConnectionConfig, entity.isTokenValid, LoginType.fromName(entity.loginType))
}
fun map(sessionParams: SessionParams?): SessionParamsEntity? {
@ -54,7 +55,8 @@ internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
sessionParams.userId,
credentialsJson,
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
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.LoginWizard
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
@ -78,7 +79,7 @@ internal class DefaultLoginWizard(
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)
}
return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig, LoginType.SSO)
}
override suspend fun loginCustom(data: JsonDict): Session {
@ -100,7 +101,7 @@ internal class DefaultLoginWizard(
authAPI.login(data)
}
return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig, LoginType.CUSTOM)
}
override suspend fun resetPassword(email: String) {

View file

@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.auth.login
import dagger.Lazy
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.failure.Failure
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 {

View file

@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.auth.registration
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.LoginFlowTypes
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
@ -36,9 +37,9 @@ import org.matrix.android.sdk.internal.auth.db.PendingSessionData
* This class execute the registration request and is responsible to keep the session of interactive authentication.
*/
internal class DefaultRegistrationWizard(
authAPI: AuthAPI,
private val sessionCreator: SessionCreator,
private val pendingSessionStore: PendingSessionStore
authAPI: AuthAPI,
private val sessionCreator: SessionCreator,
private val pendingSessionStore: PendingSessionStore
) : RegistrationWizard {
private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
@ -64,7 +65,7 @@ internal class DefaultRegistrationWizard(
override suspend fun getRegistrationFlow(): RegistrationResult {
val params = RegistrationParams()
return performRegistrationRequest(params)
return performRegistrationRequest(params, LoginType.PASSWORD)
}
override suspend fun createAccount(
@ -73,43 +74,43 @@ internal class DefaultRegistrationWizard(
initialDeviceDisplayName: String?
): RegistrationResult {
val params = RegistrationParams(
username = userName,
password = password,
initialDeviceDisplayName = initialDeviceDisplayName
username = userName,
password = password,
initialDeviceDisplayName = initialDeviceDisplayName
)
return performRegistrationRequest(params)
.also {
pendingSessionData = pendingSessionData.copy(isRegistrationStarted = true)
.also { pendingSessionStore.savePendingSessionData(it) }
}
return performRegistrationRequest(params, LoginType.PASSWORD)
.also {
pendingSessionData = pendingSessionData.copy(isRegistrationStarted = true)
.also { pendingSessionStore.savePendingSessionData(it) }
}
}
override suspend fun performReCaptcha(response: String): RegistrationResult {
val safeSession = pendingSessionData.currentSession
?: throw IllegalStateException("developer error, call createAccount() method first")
?: throw IllegalStateException("developer error, call createAccount() method first")
val params = RegistrationParams(auth = AuthParams.createForCaptcha(safeSession, response))
return performRegistrationRequest(params)
return performRegistrationRequest(params, LoginType.PASSWORD)
}
override suspend fun acceptTerms(): RegistrationResult {
val safeSession = pendingSessionData.currentSession
?: 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))
return performRegistrationRequest(params)
return performRegistrationRequest(params, LoginType.PASSWORD)
}
override suspend fun addThreePid(threePid: RegisterThreePid): RegistrationResult {
pendingSessionData = pendingSessionData.copy(currentThreePidData = null)
.also { pendingSessionStore.savePendingSessionData(it) }
.also { pendingSessionStore.savePendingSessionData(it) }
return sendThreePid(threePid)
}
override suspend fun sendAgainThreePid(): RegistrationResult {
val safeCurrentThreePid = pendingSessionData.currentThreePidData?.threePid
?: throw IllegalStateException("developer error, call createAccount() method first")
?: throw IllegalStateException("developer error, call createAccount() method first")
return sendThreePid(safeCurrentThreePid)
}
@ -125,7 +126,7 @@ internal class DefaultRegistrationWizard(
)
pendingSessionData = pendingSessionData.copy(sendAttempt = pendingSessionData.sendAttempt + 1)
.also { pendingSessionStore.savePendingSessionData(it) }
.also { pendingSessionStore.savePendingSessionData(it) }
val params = RegistrationParams(
auth = if (threePid is RegisterThreePid.Email) {
@ -148,17 +149,17 @@ internal class DefaultRegistrationWizard(
)
// Store data
pendingSessionData = pendingSessionData.copy(currentThreePidData = ThreePidData.from(threePid, response, params))
.also { pendingSessionStore.savePendingSessionData(it) }
.also { pendingSessionStore.savePendingSessionData(it) }
// and send the sid a first time
return performRegistrationRequest(params)
return performRegistrationRequest(params, LoginType.PASSWORD)
}
override suspend fun checkIfEmailHasBeenValidated(delayMillis: Long): RegistrationResult {
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 {
@ -167,19 +168,19 @@ internal class DefaultRegistrationWizard(
private suspend fun validateThreePid(code: String): RegistrationResult {
val registrationParams = pendingSessionData.currentThreePidData?.registrationParams
?: throw IllegalStateException("developer error, no pending three pid")
?: throw IllegalStateException("developer error, no pending three pid")
val safeCurrentData = pendingSessionData.currentThreePidData ?: throw IllegalStateException("developer error, call createAccount() method first")
val url = safeCurrentData.addThreePidRegistrationResponse.submitUrl ?: throw IllegalStateException("Missing url to send the code")
val validationBody = ValidationCodeBody(
clientSecret = pendingSessionData.clientSecret,
sid = safeCurrentData.addThreePidRegistrationResponse.sid,
code = code
clientSecret = pendingSessionData.clientSecret,
sid = safeCurrentData.addThreePidRegistrationResponse.sid,
code = code
)
val validationResponse = validateCodeTask.execute(ValidateCodeTask.Params(url, validationBody))
if (validationResponse.isSuccess()) {
// The entered code is correct
// Same than validate email
return performRegistrationRequest(registrationParams, 3_000)
return performRegistrationRequest(registrationParams, LoginType.PASSWORD, 3_000)
} else {
// The code is not correct
throw Failure.SuccessError
@ -188,10 +189,10 @@ internal class DefaultRegistrationWizard(
override suspend fun dummy(): RegistrationResult {
val safeSession = pendingSessionData.currentSession
?: 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))
return performRegistrationRequest(params)
return performRegistrationRequest(params, LoginType.PASSWORD)
}
override suspend fun registrationCustom(
@ -204,25 +205,28 @@ internal class DefaultRegistrationWizard(
mutableParams["session"] = safeSession
val params = RegistrationCustomParams(auth = mutableParams)
return performRegistrationOtherRequest(params)
return performRegistrationOtherRequest(LoginType.CUSTOM, params)
}
private suspend fun performRegistrationRequest(
registrationParams: RegistrationParams,
loginType: LoginType,
delayMillis: Long = 0
): RegistrationResult {
delay(delayMillis)
return register { registerTask.execute(RegisterTask.Params(registrationParams)) }
return register(loginType) { registerTask.execute(RegisterTask.Params(registrationParams)) }
}
private suspend fun performRegistrationOtherRequest(
registrationCustomParams: RegistrationCustomParams
loginType: LoginType,
registrationCustomParams: RegistrationCustomParams,
): RegistrationResult {
return register { registerCustomTask.execute(RegisterCustomTask.Params(registrationCustomParams)) }
return register(loginType) { registerCustomTask.execute(RegisterCustomTask.Params(registrationCustomParams)) }
}
private suspend fun register(
execute: suspend () -> Credentials
loginType: LoginType,
execute: suspend () -> Credentials,
): RegistrationResult {
val credentials = try {
execute.invoke()
@ -237,8 +241,7 @@ internal class DefaultRegistrationWizard(
}
}
val session =
sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
val session = sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig, loginType)
return RegistrationResult.Success(session)
}

View file

@ -20,6 +20,7 @@ import android.content.Context
import io.realm.Realm
import io.realm.RealmConfiguration
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.DiscoveryInformation
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
@ -145,7 +146,8 @@ internal class DefaultLegacySessionImporter @Inject constructor(
forceUsageTlsVersions = legacyConfig.forceUsageOfTlsVersions()
),
// If token is not valid, this boolean will be updated later
isTokenValid = true
isTokenValid = true,
loginType = LoginType.UNKNOWN,
)
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.validateBackPressed
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.utils.openUrlInChromeCustomTab
import im.vector.app.databinding.ActivityLoginBinding
import im.vector.app.features.analytics.plan.MobileScreen
import im.vector.app.features.home.HomeActivity
@ -231,9 +232,9 @@ open class LoginActivity : VectorBaseActivity<ActivityLoginBinding>(), UnlockedA
}
private fun inferAuthDescription(loginViewState: LoginViewState) = when (loginViewState.signMode) {
SignMode.Unknown -> null
SignMode.SignUp -> AuthenticationDescription.Register(type = AuthenticationDescription.AuthenticationType.Other)
SignMode.SignIn -> AuthenticationDescription.Login
SignMode.Unknown -> null
SignMode.SignUp -> AuthenticationDescription.Register(type = AuthenticationDescription.AuthenticationType.Other)
SignMode.SignIn -> AuthenticationDescription.Login
SignMode.SignInWithMatrixId -> AuthenticationDescription.Login
}
@ -272,8 +273,8 @@ open class LoginActivity : VectorBaseActivity<ActivityLoginBinding>(), UnlockedA
SignMode.SignIn -> {
// It depends on the LoginMode
when (state.loginMode) {
LoginMode.Unknown,
is LoginMode.Sso -> error("Developer error")
LoginMode.Unknown -> error("Developer error")
is LoginMode.Sso -> launchSsoFlow()
is LoginMode.SsoAndPassword,
LoginMode.Password -> addFragmentToBackstack(
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.
*/

View file

@ -18,6 +18,7 @@ package im.vector.app.features.signout.soft
import com.airbnb.epoxy.EpoxyController
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Incomplete
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
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.loginTitleItem
import im.vector.app.features.signout.soft.epoxy.loginTitleSmallItem
import org.matrix.android.sdk.api.auth.LoginType
import javax.inject.Inject
class SoftLogoutController @Inject constructor(
@ -91,55 +93,76 @@ class SoftLogoutController @Inject constructor(
}
}
private fun buildForm(state: SoftLogoutViewState) {
private fun buildForm(state: SoftLogoutViewState) = when (state.asyncHomeServerLoginFlowRequest) {
is Fail -> buildLoginErrorWithRetryItem(state.asyncHomeServerLoginFlowRequest.error)
is Success -> buildLoginSuccessItem(state)
is Loading, Uninitialized -> buildLoadingItem()
is Incomplete -> Unit
}
private fun buildLoadingItem() {
loadingItem {
id("loading")
}
}
private fun buildLoginErrorWithRetryItem(error: Throwable) {
val host = this
when (state.asyncHomeServerLoginFlowRequest) {
Uninitialized,
is Loading -> {
loadingItem {
id("loading")
}
}
is Fail -> {
loginErrorWithRetryItem {
id("errorRetry")
text(host.errorFormatter.toHumanReadable(state.asyncHomeServerLoginFlowRequest.error))
listener { host.listener?.retry() }
}
}
is Success -> {
when (state.asyncHomeServerLoginFlowRequest.invoke()) {
LoginMode.Password -> {
loginPasswordFormItem {
id("passwordForm")
stringProvider(host.stringProvider)
passwordValue(state.enteredPassword)
submitEnabled(state.enteredPassword.isNotEmpty())
onPasswordEdited { host.listener?.passwordEdited(it) }
errorText((state.asyncLoginAction as? Fail)?.error?.let { host.errorFormatter.toHumanReadable(it) })
forgetPasswordClickListener { host.listener?.forgetPasswordClicked() }
submitClickListener { host.listener?.submit() }
}
}
is LoginMode.Sso -> {
loginCenterButtonItem {
id("sso")
text(host.stringProvider.getString(R.string.login_signin_sso))
listener { host.listener?.signinFallbackSubmit() }
}
}
is LoginMode.SsoAndPassword -> {
}
LoginMode.Unsupported -> {
loginCenterButtonItem {
id("fallback")
text(host.stringProvider.getString(R.string.login_signin))
listener { host.listener?.signinFallbackSubmit() }
}
}
LoginMode.Unknown -> Unit // Should not happen
}
}
loginErrorWithRetryItem {
id("errorRetry")
text(host.errorFormatter.toHumanReadable(error))
listener { host.listener?.retry() }
}
}
private fun buildLoginSuccessItem(state: SoftLogoutViewState) = when (state.asyncHomeServerLoginFlowRequest.invoke()) {
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 {
id("passwordForm")
stringProvider(host.stringProvider)
passwordValue(state.enteredPassword)
submitEnabled(state.enteredPassword.isNotEmpty())
onPasswordEdited { host.listener?.passwordEdited(it) }
errorText((state.asyncLoginAction as? Fail)?.error?.let { host.errorFormatter.toHumanReadable(it) })
forgetPasswordClickListener { host.listener?.forgetPasswordClicked() }
submitClickListener { host.listener?.submit() }
}
}
private fun buildLoginSSOForm() {
val host = this
loginCenterButtonItem {
id("sso")
text(host.stringProvider.getString(R.string.login_signin_sso))
listener { host.listener?.signinFallbackSubmit() }
}
}
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
}
}
private fun buildLoginUnsupportedForm() {
val host = this
loginCenterButtonItem {
id("fallback")
text(host.stringProvider.getString(R.string.login_signin))
listener { host.listener?.signinFallbackSubmit() }
}
}

View file

@ -35,14 +35,12 @@ import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.login.LoginMode
import kotlinx.coroutines.launch
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.session.Session
import org.matrix.android.sdk.api.session.getUser
import timber.log.Timber
/**
* TODO Test push: disable the pushers?
*/
class SoftLogoutViewModel @AssistedInject constructor(
@Assisted initialState: SoftLogoutViewState,
private val session: Session,
@ -70,7 +68,8 @@ class SoftLogoutViewModel @AssistedInject constructor(
userId = userId,
deviceId = session.sessionParams.deviceId.orEmpty(),
userDisplayName = session.getUser(userId)?.displayName ?: userId,
hasUnsavedKeys = session.hasUnsavedKeys()
hasUnsavedKeys = session.hasUnsavedKeys(),
loginType = session.sessionParams.loginType,
)
} else {
SoftLogoutViewState(
@ -78,7 +77,8 @@ class SoftLogoutViewModel @AssistedInject constructor(
userId = "",
deviceId = "",
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.Uninitialized
import im.vector.app.features.login.LoginMode
import org.matrix.android.sdk.api.auth.LoginType
data class SoftLogoutViewState(
val asyncHomeServerLoginFlowRequest: Async<LoginMode> = Uninitialized,
@ -31,7 +32,8 @@ data class SoftLogoutViewState(
val deviceId: String,
val userDisplayName: String,
val hasUnsavedKeys: Boolean,
val enteredPassword: String = ""
val loginType: LoginType,
val enteredPassword: String = "",
) : MavericksState {
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");
* you may not use this file except in compliance with the License.