mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-22 17:35:54 +03:00
Merge pull request #2836 from vector-im/feature/bma/fix_redirection
Fix redirection
This commit is contained in:
commit
e12cbf92c3
17 changed files with 616 additions and 681 deletions
|
@ -14,13 +14,14 @@ Bugfix 🐛:
|
|||
- VoIP : fix audio devices output
|
||||
- Fix crash after initial sync on Dendrite
|
||||
- Fix crash reported by PlayStore (#2707)
|
||||
- Ignore url override from credential if it is not valid (#2822)
|
||||
- Fix crash when deactivating an account
|
||||
|
||||
Translations 🗣:
|
||||
-
|
||||
|
||||
SDK API changes ⚠️:
|
||||
-
|
||||
- Migrate AuthenticationService API to coroutines (#2449)
|
||||
|
||||
Build 🧱:
|
||||
-
|
||||
|
|
|
@ -26,15 +26,12 @@ import org.matrix.android.sdk.InstrumentedTest
|
|||
import org.matrix.android.sdk.api.auth.UIABaseAuth
|
||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||
import org.matrix.android.sdk.api.auth.UserPasswordAuth
|
||||
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationResult
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.failure.MatrixError
|
||||
import org.matrix.android.sdk.common.CommonTestHelper
|
||||
import org.matrix.android.sdk.common.SessionTestParams
|
||||
import org.matrix.android.sdk.common.TestConstants
|
||||
import org.matrix.android.sdk.common.TestMatrixCallback
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
|
@ -77,23 +74,23 @@ class DeactivateAccountTest : InstrumentedTest {
|
|||
// Try to create an account with the deactivate account user id, it will fail (M_USER_IN_USE)
|
||||
val hs = commonTestHelper.createHomeServerConfig()
|
||||
|
||||
commonTestHelper.doSync<LoginFlowResult> {
|
||||
commonTestHelper.matrix.authenticationService.getLoginFlow(hs, it)
|
||||
commonTestHelper.runBlockingTest {
|
||||
commonTestHelper.matrix.authenticationService.getLoginFlow(hs)
|
||||
}
|
||||
|
||||
var accountCreationError: Throwable? = null
|
||||
commonTestHelper.waitWithLatch {
|
||||
commonTestHelper.matrix.authenticationService
|
||||
.getRegistrationWizard()
|
||||
.createAccount(session.myUserId.substringAfter("@").substringBefore(":"),
|
||||
TestConstants.PASSWORD,
|
||||
null,
|
||||
object : TestMatrixCallback<RegistrationResult>(it, false) {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
accountCreationError = failure
|
||||
super.onFailure(failure)
|
||||
}
|
||||
})
|
||||
commonTestHelper.runBlockingTest {
|
||||
try {
|
||||
commonTestHelper.matrix.authenticationService
|
||||
.getRegistrationWizard()
|
||||
.createAccount(
|
||||
session.myUserId.substringAfter("@").substringBefore(":"),
|
||||
TestConstants.PASSWORD,
|
||||
null
|
||||
)
|
||||
} catch (failure: Throwable) {
|
||||
accountCreationError = failure
|
||||
}
|
||||
}
|
||||
|
||||
// Test the error
|
||||
|
|
|
@ -23,7 +23,6 @@ import org.matrix.android.sdk.api.Matrix
|
|||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.MatrixConfiguration
|
||||
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.registration.RegistrationResult
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
|
@ -210,22 +209,21 @@ class CommonTestHelper(context: Context) {
|
|||
sessionTestParams: SessionTestParams): Session {
|
||||
val hs = createHomeServerConfig()
|
||||
|
||||
doSync<LoginFlowResult> {
|
||||
matrix.authenticationService
|
||||
.getLoginFlow(hs, it)
|
||||
runBlockingTest {
|
||||
matrix.authenticationService.getLoginFlow(hs)
|
||||
}
|
||||
|
||||
doSync<RegistrationResult>(timeout = 60_000) {
|
||||
runBlockingTest(timeout = 60_000) {
|
||||
matrix.authenticationService
|
||||
.getRegistrationWizard()
|
||||
.createAccount(userName, password, null, it)
|
||||
.createAccount(userName, password, null)
|
||||
}
|
||||
|
||||
// Perform dummy step
|
||||
val registrationResult = doSync<RegistrationResult>(timeout = 60_000) {
|
||||
val registrationResult = runBlockingTest(timeout = 60_000) {
|
||||
matrix.authenticationService
|
||||
.getRegistrationWizard()
|
||||
.dummy(it)
|
||||
.dummy()
|
||||
}
|
||||
|
||||
assertTrue(registrationResult is RegistrationResult.Success)
|
||||
|
@ -249,15 +247,14 @@ class CommonTestHelper(context: Context) {
|
|||
sessionTestParams: SessionTestParams): Session {
|
||||
val hs = createHomeServerConfig()
|
||||
|
||||
doSync<LoginFlowResult> {
|
||||
matrix.authenticationService
|
||||
.getLoginFlow(hs, it)
|
||||
runBlockingTest {
|
||||
matrix.authenticationService.getLoginFlow(hs)
|
||||
}
|
||||
|
||||
val session = doSync<Session> {
|
||||
val session = runBlockingTest {
|
||||
matrix.authenticationService
|
||||
.getLoginWizard()
|
||||
.login(userName, password, "myDevice", it)
|
||||
.login(userName, password, "myDevice")
|
||||
}
|
||||
|
||||
if (sessionTestParams.withInitialSync) {
|
||||
|
@ -277,21 +274,19 @@ class CommonTestHelper(context: Context) {
|
|||
password: String): Throwable {
|
||||
val hs = createHomeServerConfig()
|
||||
|
||||
doSync<LoginFlowResult> {
|
||||
matrix.authenticationService
|
||||
.getLoginFlow(hs, it)
|
||||
runBlockingTest {
|
||||
matrix.authenticationService.getLoginFlow(hs)
|
||||
}
|
||||
|
||||
var requestFailure: Throwable? = null
|
||||
waitWithLatch { latch ->
|
||||
matrix.authenticationService
|
||||
.getLoginWizard()
|
||||
.login(userName, password, "myDevice", object : TestMatrixCallback<Session>(latch, onlySuccessful = false) {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
requestFailure = failure
|
||||
super.onFailure(failure)
|
||||
}
|
||||
})
|
||||
runBlockingTest {
|
||||
try {
|
||||
matrix.authenticationService
|
||||
.getLoginWizard()
|
||||
.login(userName, password, "myDevice")
|
||||
} catch (failure: Throwable) {
|
||||
requestFailure = failure
|
||||
}
|
||||
}
|
||||
|
||||
assertNotNull(requestFailure)
|
||||
|
|
|
@ -61,7 +61,7 @@ class SearchMessagesTest : InstrumentedTest {
|
|||
2)
|
||||
|
||||
run {
|
||||
var lock = CountDownLatch(1)
|
||||
val lock = CountDownLatch(1)
|
||||
|
||||
val eventListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
snapshot.count { it.root.content.toModel<MessageContent>()?.body?.startsWith(MESSAGE).orFalse() } == 2
|
||||
|
@ -70,7 +70,6 @@ class SearchMessagesTest : InstrumentedTest {
|
|||
aliceTimeline.addListener(eventListener)
|
||||
commonTestHelper.await(lock)
|
||||
|
||||
lock = CountDownLatch(1)
|
||||
val data = commonTestHelper.runBlockingTest {
|
||||
aliceSession
|
||||
.searchService()
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package org.matrix.android.sdk.api.auth
|
||||
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
|
||||
|
@ -24,7 +23,6 @@ import org.matrix.android.sdk.api.auth.login.LoginWizard
|
|||
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
|
||||
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
|
||||
/**
|
||||
* This interface defines methods to authenticate or to create an account to a matrix server.
|
||||
|
@ -32,14 +30,14 @@ import org.matrix.android.sdk.api.util.Cancelable
|
|||
interface AuthenticationService {
|
||||
/**
|
||||
* Request the supported login flows for this homeserver.
|
||||
* This is the first method to call to be able to get a wizard to login or the create an account
|
||||
* This is the first method to call to be able to get a wizard to login or to create an account
|
||||
*/
|
||||
fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResult>): Cancelable
|
||||
suspend fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult
|
||||
|
||||
/**
|
||||
* Request the supported login flows for the corresponding sessionId.
|
||||
*/
|
||||
fun getLoginFlowOfSession(sessionId: String, callback: MatrixCallback<LoginFlowResult>): Cancelable
|
||||
suspend fun getLoginFlowOfSession(sessionId: String): LoginFlowResult
|
||||
|
||||
/**
|
||||
* Get a SSO url
|
||||
|
@ -69,12 +67,12 @@ interface AuthenticationService {
|
|||
/**
|
||||
* Cancel pending login or pending registration
|
||||
*/
|
||||
fun cancelPendingLoginOrRegistration()
|
||||
suspend fun cancelPendingLoginOrRegistration()
|
||||
|
||||
/**
|
||||
* Reset all pending settings, including current HomeServerConnectionConfig
|
||||
*/
|
||||
fun reset()
|
||||
suspend fun reset()
|
||||
|
||||
/**
|
||||
* Check if there is an authenticated [Session].
|
||||
|
@ -91,24 +89,21 @@ interface AuthenticationService {
|
|||
/**
|
||||
* Create a session after a SSO successful login
|
||||
*/
|
||||
fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
credentials: Credentials,
|
||||
callback: MatrixCallback<Session>): Cancelable
|
||||
suspend fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
credentials: Credentials): Session
|
||||
|
||||
/**
|
||||
* Perform a wellknown request, using the domain from the matrixId
|
||||
*/
|
||||
fun getWellKnownData(matrixId: String,
|
||||
homeServerConnectionConfig: HomeServerConnectionConfig?,
|
||||
callback: MatrixCallback<WellknownResult>): Cancelable
|
||||
suspend fun getWellKnownData(matrixId: String,
|
||||
homeServerConnectionConfig: HomeServerConnectionConfig?): WellknownResult
|
||||
|
||||
/**
|
||||
* Authenticate with a matrixId and a password
|
||||
* Usually call this after a successful call to getWellKnownData()
|
||||
*/
|
||||
fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
matrixId: String,
|
||||
password: String,
|
||||
initialDeviceName: String,
|
||||
callback: MatrixCallback<Session>): Cancelable
|
||||
suspend fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
matrixId: String,
|
||||
password: String,
|
||||
initialDeviceName: String): Session
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package org.matrix.android.sdk.api.auth.login
|
||||
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
|
||||
|
@ -29,26 +28,23 @@ interface LoginWizard {
|
|||
* @param callback the matrix callback on which you'll receive the result of authentication.
|
||||
* @return a [Cancelable]
|
||||
*/
|
||||
fun login(login: String,
|
||||
password: String,
|
||||
deviceName: String,
|
||||
callback: MatrixCallback<Session>): Cancelable
|
||||
suspend fun login(login: String,
|
||||
password: String,
|
||||
deviceName: String): Session
|
||||
|
||||
/**
|
||||
* Exchange a login token to an access token
|
||||
*/
|
||||
fun loginWithToken(loginToken: String,
|
||||
callback: MatrixCallback<Session>): Cancelable
|
||||
suspend fun loginWithToken(loginToken: String): Session
|
||||
|
||||
/**
|
||||
* Reset user password
|
||||
*/
|
||||
fun resetPassword(email: String,
|
||||
newPassword: String,
|
||||
callback: MatrixCallback<Unit>): Cancelable
|
||||
suspend fun resetPassword(email: String,
|
||||
newPassword: String)
|
||||
|
||||
/**
|
||||
* Confirm the new password, once the user has checked his email
|
||||
* Confirm the new password, once the user has checked their email
|
||||
*/
|
||||
fun resetPasswordMailConfirmed(callback: MatrixCallback<Unit>): Cancelable
|
||||
suspend fun resetPasswordMailConfirmed()
|
||||
}
|
||||
|
|
|
@ -16,28 +16,25 @@
|
|||
|
||||
package org.matrix.android.sdk.api.auth.registration
|
||||
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
|
||||
interface RegistrationWizard {
|
||||
|
||||
fun getRegistrationFlow(callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
suspend fun getRegistrationFlow(): RegistrationResult
|
||||
|
||||
fun createAccount(userName: String, password: String, initialDeviceDisplayName: String?, callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
suspend fun createAccount(userName: String, password: String, initialDeviceDisplayName: String?): RegistrationResult
|
||||
|
||||
fun performReCaptcha(response: String, callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
suspend fun performReCaptcha(response: String): RegistrationResult
|
||||
|
||||
fun acceptTerms(callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
suspend fun acceptTerms(): RegistrationResult
|
||||
|
||||
fun dummy(callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
suspend fun dummy(): RegistrationResult
|
||||
|
||||
fun addThreePid(threePid: RegisterThreePid, callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
suspend fun addThreePid(threePid: RegisterThreePid): RegistrationResult
|
||||
|
||||
fun sendAgainThreePid(callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
suspend fun sendAgainThreePid(): RegistrationResult
|
||||
|
||||
fun handleValidateThreePid(code: String, callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
suspend fun handleValidateThreePid(code: String): RegistrationResult
|
||||
|
||||
fun checkIfEmailHasBeenValidated(delayMillis: Long, callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
suspend fun checkIfEmailHasBeenValidated(delayMillis: Long): RegistrationResult
|
||||
|
||||
val currentThreePid: String?
|
||||
|
||||
|
|
|
@ -20,7 +20,9 @@ import android.content.Context
|
|||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import io.realm.RealmConfiguration
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
||||
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
|
||||
import org.matrix.android.sdk.internal.auth.db.AuthRealmMigration
|
||||
import org.matrix.android.sdk.internal.auth.db.AuthRealmModule
|
||||
|
@ -32,8 +34,6 @@ import org.matrix.android.sdk.internal.database.RealmKeysUtils
|
|||
import org.matrix.android.sdk.internal.di.AuthDatabase
|
||||
import org.matrix.android.sdk.internal.legacy.DefaultLegacySessionImporter
|
||||
import org.matrix.android.sdk.internal.wellknown.WellknownModule
|
||||
import io.realm.RealmConfiguration
|
||||
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
||||
import java.io.File
|
||||
|
||||
@Module(includes = [WellknownModule::class])
|
||||
|
@ -82,6 +82,9 @@ internal abstract class AuthModule {
|
|||
@Binds
|
||||
abstract fun bindDirectLoginTask(task: DefaultDirectLoginTask): DirectLoginTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindIsValidClientServerApiTask(task: DefaultIsValidClientServerApiTask): IsValidClientServerApiTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindHomeServerHistoryService(service: DefaultHomeServerHistoryService): HomeServerHistoryService
|
||||
}
|
||||
|
|
|
@ -18,10 +18,7 @@ package org.matrix.android.sdk.internal.auth
|
|||
|
||||
import android.net.Uri
|
||||
import dagger.Lazy
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
|
@ -32,8 +29,6 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
|
|||
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
import org.matrix.android.sdk.api.util.NoOpCancellable
|
||||
import org.matrix.android.sdk.api.util.appendParamToUrl
|
||||
import org.matrix.android.sdk.internal.SessionManager
|
||||
import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse
|
||||
|
@ -50,11 +45,6 @@ import org.matrix.android.sdk.internal.network.RetrofitFactory
|
|||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.network.httpclient.addSocketFactory
|
||||
import org.matrix.android.sdk.internal.network.ssl.UnrecognizedCertificateException
|
||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||
import org.matrix.android.sdk.internal.task.configureWith
|
||||
import org.matrix.android.sdk.internal.task.launchToCallback
|
||||
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
||||
import org.matrix.android.sdk.internal.util.toCancelable
|
||||
import org.matrix.android.sdk.internal.wellknown.GetWellknownTask
|
||||
import javax.inject.Inject
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
@ -63,14 +53,12 @@ internal class DefaultAuthenticationService @Inject constructor(
|
|||
@Unauthenticated
|
||||
private val okHttpClient: Lazy<OkHttpClient>,
|
||||
private val retrofitFactory: RetrofitFactory,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val sessionParamsStore: SessionParamsStore,
|
||||
private val sessionManager: SessionManager,
|
||||
private val sessionCreator: SessionCreator,
|
||||
private val pendingSessionStore: PendingSessionStore,
|
||||
private val getWellknownTask: GetWellknownTask,
|
||||
private val directLoginTask: DirectLoginTask,
|
||||
private val taskExecutor: TaskExecutor
|
||||
private val directLoginTask: DirectLoginTask
|
||||
) : AuthenticationService {
|
||||
|
||||
private var pendingSessionData: PendingSessionData? = pendingSessionStore.getPendingSessionData()
|
||||
|
@ -89,15 +77,11 @@ internal class DefaultAuthenticationService @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun getLoginFlowOfSession(sessionId: String, callback: MatrixCallback<LoginFlowResult>): Cancelable {
|
||||
override suspend fun getLoginFlowOfSession(sessionId: String): LoginFlowResult {
|
||||
val homeServerConnectionConfig = sessionParamsStore.get(sessionId)?.homeServerConnectionConfig
|
||||
?: throw IllegalStateException("Session not found")
|
||||
|
||||
return if (homeServerConnectionConfig == null) {
|
||||
callback.onFailure(IllegalStateException("Session not found"))
|
||||
NoOpCancellable
|
||||
} else {
|
||||
getLoginFlow(homeServerConnectionConfig, callback)
|
||||
}
|
||||
return getLoginFlow(homeServerConnectionConfig)
|
||||
}
|
||||
|
||||
override fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String? {
|
||||
|
@ -146,70 +130,70 @@ internal class DefaultAuthenticationService @Inject constructor(
|
|||
?.trim { it == '/' }
|
||||
}
|
||||
|
||||
override fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResult>): Cancelable {
|
||||
/**
|
||||
* This is the entry point of the authentication service.
|
||||
* homeServerConnectionConfig contains a homeserver URL probably entered by the user, which can be a
|
||||
* valid homeserver API url, the url of Element Web, or anything else.
|
||||
*/
|
||||
override suspend fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
|
||||
pendingSessionData = null
|
||||
|
||||
return taskExecutor.executorScope.launch(coroutineDispatchers.main) {
|
||||
pendingSessionStore.delete()
|
||||
pendingSessionStore.delete()
|
||||
|
||||
val result = runCatching {
|
||||
getLoginFlowInternal(homeServerConnectionConfig)
|
||||
}
|
||||
result.fold(
|
||||
{
|
||||
if (it is LoginFlowResult.Success) {
|
||||
// The homeserver exists and up to date, keep the config
|
||||
// Homeserver url may have been changed, if it was a Riot url
|
||||
val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy(
|
||||
homeServerUri = Uri.parse(it.homeServerUrl)
|
||||
)
|
||||
|
||||
pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig)
|
||||
.also { data -> pendingSessionStore.savePendingSessionData(data) }
|
||||
}
|
||||
callback.onSuccess(it)
|
||||
},
|
||||
{
|
||||
if (it is UnrecognizedCertificateException) {
|
||||
callback.onFailure(Failure.UnrecognizedCertificateFailure(homeServerConnectionConfig.homeServerUri.toString(), it.fingerprint))
|
||||
} else {
|
||||
callback.onFailure(it)
|
||||
}
|
||||
}
|
||||
)
|
||||
val result = runCatching {
|
||||
getLoginFlowInternal(homeServerConnectionConfig)
|
||||
}
|
||||
.toCancelable()
|
||||
return result.fold(
|
||||
{
|
||||
if (it is LoginFlowResult.Success) {
|
||||
// The homeserver exists and up to date, keep the config
|
||||
// Homeserver url may have been changed, if it was a Riot url
|
||||
val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy(
|
||||
homeServerUri = Uri.parse(it.homeServerUrl)
|
||||
)
|
||||
|
||||
pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig)
|
||||
.also { data -> pendingSessionStore.savePendingSessionData(data) }
|
||||
}
|
||||
it
|
||||
},
|
||||
{
|
||||
if (it is UnrecognizedCertificateException) {
|
||||
throw Failure.UnrecognizedCertificateFailure(homeServerConnectionConfig.homeServerUri.toString(), it.fingerprint)
|
||||
} else {
|
||||
throw it
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun getLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
|
||||
return withContext(coroutineDispatchers.io) {
|
||||
val authAPI = buildAuthAPI(homeServerConnectionConfig)
|
||||
val authAPI = buildAuthAPI(homeServerConnectionConfig)
|
||||
|
||||
// First check the homeserver version
|
||||
runCatching {
|
||||
executeRequest<Versions>(null) {
|
||||
apiCall = authAPI.versions()
|
||||
}
|
||||
// First check the homeserver version
|
||||
return runCatching {
|
||||
executeRequest<Versions>(null) {
|
||||
apiCall = authAPI.versions()
|
||||
}
|
||||
.map { versions ->
|
||||
// Ok, it seems that the homeserver url is valid
|
||||
getLoginFlowResult(authAPI, versions, homeServerConnectionConfig.homeServerUri.toString())
|
||||
}
|
||||
.fold(
|
||||
{
|
||||
it
|
||||
},
|
||||
{
|
||||
if (it is Failure.OtherServerError
|
||||
&& it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
|
||||
// It's maybe a Riot url?
|
||||
getRiotDomainLoginFlowInternal(homeServerConnectionConfig)
|
||||
} else {
|
||||
throw it
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
.map { versions ->
|
||||
// Ok, it seems that the homeserver url is valid
|
||||
getLoginFlowResult(authAPI, versions, homeServerConnectionConfig.homeServerUri.toString())
|
||||
}
|
||||
.fold(
|
||||
{
|
||||
it
|
||||
},
|
||||
{
|
||||
if (it is Failure.OtherServerError
|
||||
&& it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
|
||||
// It's maybe a Riot url?
|
||||
getRiotDomainLoginFlowInternal(homeServerConnectionConfig)
|
||||
} else {
|
||||
throw it
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun getRiotDomainLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
|
||||
|
@ -338,12 +322,9 @@ internal class DefaultAuthenticationService @Inject constructor(
|
|||
?: let {
|
||||
pendingSessionData?.homeServerConnectionConfig?.let {
|
||||
DefaultRegistrationWizard(
|
||||
buildClient(it),
|
||||
retrofitFactory,
|
||||
coroutineDispatchers,
|
||||
buildAuthAPI(it),
|
||||
sessionCreator,
|
||||
pendingSessionStore,
|
||||
taskExecutor.executorScope
|
||||
pendingSessionStore
|
||||
).also {
|
||||
currentRegistrationWizard = it
|
||||
}
|
||||
|
@ -359,12 +340,9 @@ internal class DefaultAuthenticationService @Inject constructor(
|
|||
?: let {
|
||||
pendingSessionData?.homeServerConnectionConfig?.let {
|
||||
DefaultLoginWizard(
|
||||
buildClient(it),
|
||||
retrofitFactory,
|
||||
coroutineDispatchers,
|
||||
buildAuthAPI(it),
|
||||
sessionCreator,
|
||||
pendingSessionStore,
|
||||
taskExecutor.executorScope
|
||||
pendingSessionStore
|
||||
).also {
|
||||
currentLoginWizard = it
|
||||
}
|
||||
|
@ -372,7 +350,7 @@ internal class DefaultAuthenticationService @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun cancelPendingLoginOrRegistration() {
|
||||
override suspend fun cancelPendingLoginOrRegistration() {
|
||||
currentLoginWizard = null
|
||||
currentRegistrationWizard = null
|
||||
|
||||
|
@ -381,61 +359,39 @@ internal class DefaultAuthenticationService @Inject constructor(
|
|||
pendingSessionData = pendingSessionData?.homeServerConnectionConfig
|
||||
?.let { PendingSessionData(it) }
|
||||
.also {
|
||||
taskExecutor.executorScope.launch(coroutineDispatchers.main) {
|
||||
if (it == null) {
|
||||
// Should not happen
|
||||
pendingSessionStore.delete()
|
||||
} else {
|
||||
pendingSessionStore.savePendingSessionData(it)
|
||||
}
|
||||
if (it == null) {
|
||||
// Should not happen
|
||||
pendingSessionStore.delete()
|
||||
} else {
|
||||
pendingSessionStore.savePendingSessionData(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
override suspend fun reset() {
|
||||
currentLoginWizard = null
|
||||
currentRegistrationWizard = null
|
||||
|
||||
pendingSessionData = null
|
||||
|
||||
taskExecutor.executorScope.launch(coroutineDispatchers.main) {
|
||||
pendingSessionStore.delete()
|
||||
}
|
||||
pendingSessionStore.delete()
|
||||
}
|
||||
|
||||
override fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
credentials: Credentials,
|
||||
callback: MatrixCallback<Session>): Cancelable {
|
||||
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
createSessionFromSso(credentials, homeServerConnectionConfig)
|
||||
}
|
||||
override suspend fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
credentials: Credentials): Session {
|
||||
return sessionCreator.createSession(credentials, homeServerConnectionConfig)
|
||||
}
|
||||
|
||||
override fun getWellKnownData(matrixId: String,
|
||||
homeServerConnectionConfig: HomeServerConnectionConfig?,
|
||||
callback: MatrixCallback<WellknownResult>): Cancelable {
|
||||
return getWellknownTask
|
||||
.configureWith(GetWellknownTask.Params(matrixId, homeServerConnectionConfig)) {
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
override suspend fun getWellKnownData(matrixId: String,
|
||||
homeServerConnectionConfig: HomeServerConnectionConfig?): WellknownResult {
|
||||
return getWellknownTask.execute(GetWellknownTask.Params(matrixId, homeServerConnectionConfig))
|
||||
}
|
||||
|
||||
override fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
matrixId: String,
|
||||
password: String,
|
||||
initialDeviceName: String,
|
||||
callback: MatrixCallback<Session>): Cancelable {
|
||||
return directLoginTask
|
||||
.configureWith(DirectLoginTask.Params(homeServerConnectionConfig, matrixId, password, initialDeviceName)) {
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
private suspend fun createSessionFromSso(credentials: Credentials,
|
||||
homeServerConnectionConfig: HomeServerConnectionConfig): Session = withContext(coroutineDispatchers.computation) {
|
||||
sessionCreator.createSession(credentials, homeServerConnectionConfig)
|
||||
override suspend fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
matrixId: String,
|
||||
password: String,
|
||||
initialDeviceName: String): Session {
|
||||
return directLoginTask.execute(DirectLoginTask.Params(homeServerConnectionConfig, matrixId, password, initialDeviceName))
|
||||
}
|
||||
|
||||
private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI {
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright (c) 2021 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 dagger.Lazy
|
||||
import okhttp3.OkHttpClient
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse
|
||||
import org.matrix.android.sdk.internal.di.Unauthenticated
|
||||
import org.matrix.android.sdk.internal.network.RetrofitFactory
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.network.httpclient.addSocketFactory
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import javax.inject.Inject
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
||||
internal interface IsValidClientServerApiTask : Task<IsValidClientServerApiTask.Params, Boolean> {
|
||||
data class Params(
|
||||
val homeServerConnectionConfig: HomeServerConnectionConfig
|
||||
)
|
||||
}
|
||||
|
||||
internal class DefaultIsValidClientServerApiTask @Inject constructor(
|
||||
@Unauthenticated
|
||||
private val okHttpClient: Lazy<OkHttpClient>,
|
||||
private val retrofitFactory: RetrofitFactory
|
||||
) : IsValidClientServerApiTask {
|
||||
|
||||
override suspend fun execute(params: IsValidClientServerApiTask.Params): Boolean {
|
||||
val client = buildClient(params.homeServerConnectionConfig)
|
||||
val homeServerUrl = params.homeServerConnectionConfig.homeServerUri.toString()
|
||||
|
||||
val authAPI = retrofitFactory.create(client, homeServerUrl)
|
||||
.create(AuthAPI::class.java)
|
||||
|
||||
return try {
|
||||
executeRequest<LoginFlowResponse>(null) {
|
||||
apiCall = authAPI.getLoginFlows()
|
||||
}
|
||||
// We get a response, so the API is valid
|
||||
true
|
||||
} catch (failure: Throwable) {
|
||||
if (failure is Failure.OtherServerError
|
||||
&& failure.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
|
||||
// Probably not valid
|
||||
false
|
||||
} else {
|
||||
// Other error
|
||||
throw failure
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildClient(homeServerConnectionConfig: HomeServerConnectionConfig): OkHttpClient {
|
||||
return okHttpClient.get()
|
||||
.newBuilder()
|
||||
.addSocketFactory(homeServerConnectionConfig)
|
||||
.build()
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ import android.net.Uri
|
|||
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
|
||||
|
@ -32,7 +33,8 @@ internal interface SessionCreator {
|
|||
internal class DefaultSessionCreator @Inject constructor(
|
||||
private val sessionParamsStore: SessionParamsStore,
|
||||
private val sessionManager: SessionManager,
|
||||
private val pendingSessionStore: PendingSessionStore
|
||||
private val pendingSessionStore: PendingSessionStore,
|
||||
private val isValidClientServerApiTask: IsValidClientServerApiTask
|
||||
) : SessionCreator {
|
||||
|
||||
/**
|
||||
|
@ -43,16 +45,28 @@ internal class DefaultSessionCreator @Inject constructor(
|
|||
// We can cleanup the pending session params
|
||||
pendingSessionStore.delete()
|
||||
|
||||
val overriddenUrl = credentials.discoveryInformation?.homeServer?.baseURL
|
||||
// remove trailing "/"
|
||||
?.trim { it == '/' }
|
||||
?.takeIf { it.isNotBlank() }
|
||||
?.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(homeServerUri = 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(
|
||||
homeServerUri = credentials.discoveryInformation?.homeServer?.baseURL
|
||||
// remove trailing "/"
|
||||
?.trim { it == '/' }
|
||||
?.takeIf { it.isNotBlank() }
|
||||
?.also { Timber.d("Overriding homeserver url to $it") }
|
||||
?.let { Uri.parse(it) }
|
||||
?: homeServerConnectionConfig.homeServerUri,
|
||||
homeServerUri = overriddenUrl ?: homeServerConnectionConfig.homeServerUri,
|
||||
identityServerUri = credentials.discoveryInformation?.identityServer?.baseURL
|
||||
// remove trailing "/"
|
||||
?.trim { it == '/' }
|
||||
|
|
|
@ -17,13 +17,10 @@
|
|||
package org.matrix.android.sdk.internal.auth.login
|
||||
|
||||
import android.util.Patterns
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||
import org.matrix.android.sdk.api.auth.login.LoginWizard
|
||||
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
import org.matrix.android.sdk.api.util.NoOpCancellable
|
||||
import org.matrix.android.sdk.internal.auth.AuthAPI
|
||||
import org.matrix.android.sdk.internal.auth.PendingSessionStore
|
||||
import org.matrix.android.sdk.internal.auth.SessionCreator
|
||||
|
@ -34,56 +31,19 @@ import org.matrix.android.sdk.internal.auth.db.PendingSessionData
|
|||
import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationParams
|
||||
import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationResponse
|
||||
import org.matrix.android.sdk.internal.auth.registration.RegisterAddThreePidTask
|
||||
import org.matrix.android.sdk.internal.network.RetrofitFactory
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.launchToCallback
|
||||
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
internal class DefaultLoginWizard(
|
||||
okHttpClient: OkHttpClient,
|
||||
retrofitFactory: RetrofitFactory,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val authAPI: AuthAPI,
|
||||
private val sessionCreator: SessionCreator,
|
||||
private val pendingSessionStore: PendingSessionStore,
|
||||
private val coroutineScope: CoroutineScope
|
||||
private val pendingSessionStore: PendingSessionStore
|
||||
) : LoginWizard {
|
||||
|
||||
private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
|
||||
|
||||
private val authAPI = retrofitFactory.create(okHttpClient, pendingSessionData.homeServerConnectionConfig.homeServerUri.toString())
|
||||
.create(AuthAPI::class.java)
|
||||
|
||||
override fun login(login: String,
|
||||
password: String,
|
||||
deviceName: String,
|
||||
callback: MatrixCallback<Session>): Cancelable {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
loginInternal(login, password, deviceName)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#handling-the-authentication-endpoint
|
||||
*/
|
||||
override fun loginWithToken(loginToken: String, callback: MatrixCallback<Session>): Cancelable {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
val loginParams = TokenLoginParams(
|
||||
token = loginToken
|
||||
)
|
||||
val credentials = executeRequest<Credentials>(null) {
|
||||
apiCall = authAPI.login(loginParams)
|
||||
}
|
||||
|
||||
sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loginInternal(login: String,
|
||||
password: String,
|
||||
deviceName: String) = withContext(coroutineDispatchers.computation) {
|
||||
override suspend fun login(login: String,
|
||||
password: String,
|
||||
deviceName: String): Session {
|
||||
val loginParams = if (Patterns.EMAIL_ADDRESS.matcher(login).matches()) {
|
||||
PasswordLoginParams.thirdPartyIdentifier(ThreePidMedium.EMAIL, login, password, deviceName)
|
||||
} else {
|
||||
|
@ -93,16 +53,24 @@ internal class DefaultLoginWizard(
|
|||
apiCall = authAPI.login(loginParams)
|
||||
}
|
||||
|
||||
sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
|
||||
return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
|
||||
}
|
||||
|
||||
override fun resetPassword(email: String, newPassword: String, callback: MatrixCallback<Unit>): Cancelable {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
resetPasswordInternal(email, newPassword)
|
||||
/**
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#handling-the-authentication-endpoint
|
||||
*/
|
||||
override suspend fun loginWithToken(loginToken: String): Session {
|
||||
val loginParams = TokenLoginParams(
|
||||
token = loginToken
|
||||
)
|
||||
val credentials = executeRequest<Credentials>(null) {
|
||||
apiCall = authAPI.login(loginParams)
|
||||
}
|
||||
|
||||
return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
|
||||
}
|
||||
|
||||
private suspend fun resetPasswordInternal(email: String, newPassword: String) {
|
||||
override suspend fun resetPassword(email: String, newPassword: String) {
|
||||
val param = RegisterAddThreePidTask.Params(
|
||||
RegisterThreePid.Email(email),
|
||||
pendingSessionData.clientSecret,
|
||||
|
@ -120,21 +88,14 @@ internal class DefaultLoginWizard(
|
|||
.also { pendingSessionStore.savePendingSessionData(it) }
|
||||
}
|
||||
|
||||
override fun resetPasswordMailConfirmed(callback: MatrixCallback<Unit>): Cancelable {
|
||||
val safeResetPasswordData = pendingSessionData.resetPasswordData ?: run {
|
||||
callback.onFailure(IllegalStateException("developer error, no reset password in progress"))
|
||||
return NoOpCancellable
|
||||
}
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
resetPasswordMailConfirmedInternal(safeResetPasswordData)
|
||||
}
|
||||
}
|
||||
override suspend fun resetPasswordMailConfirmed() {
|
||||
val safeResetPasswordData = pendingSessionData.resetPasswordData
|
||||
?: throw IllegalStateException("developer error, no reset password in progress")
|
||||
|
||||
private suspend fun resetPasswordMailConfirmedInternal(resetPasswordData: ResetPasswordData) {
|
||||
val param = ResetPasswordMailConfirmed.create(
|
||||
pendingSessionData.clientSecret,
|
||||
resetPasswordData.addThreePidRegistrationResponse.sid,
|
||||
resetPasswordData.newPassword
|
||||
safeResetPasswordData.addThreePidRegistrationResponse.sid,
|
||||
safeResetPasswordData.newPassword
|
||||
)
|
||||
|
||||
executeRequest<Unit>(null) {
|
||||
|
|
|
@ -16,10 +16,7 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.auth.registration
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import okhttp3.OkHttpClient
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
||||
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationResult
|
||||
|
@ -27,31 +24,22 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
|
|||
import org.matrix.android.sdk.api.auth.registration.toFlowResult
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.failure.Failure.RegistrationFlowError
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
import org.matrix.android.sdk.api.util.NoOpCancellable
|
||||
import org.matrix.android.sdk.internal.auth.AuthAPI
|
||||
import org.matrix.android.sdk.internal.auth.PendingSessionStore
|
||||
import org.matrix.android.sdk.internal.auth.SessionCreator
|
||||
import org.matrix.android.sdk.internal.auth.db.PendingSessionData
|
||||
import org.matrix.android.sdk.internal.network.RetrofitFactory
|
||||
import org.matrix.android.sdk.internal.task.launchToCallback
|
||||
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
||||
|
||||
/**
|
||||
* This class execute the registration request and is responsible to keep the session of interactive authentication
|
||||
*/
|
||||
internal class DefaultRegistrationWizard(
|
||||
private val okHttpClient: OkHttpClient,
|
||||
private val retrofitFactory: RetrofitFactory,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
authAPI: AuthAPI,
|
||||
private val sessionCreator: SessionCreator,
|
||||
private val pendingSessionStore: PendingSessionStore,
|
||||
private val coroutineScope: CoroutineScope
|
||||
private val pendingSessionStore: PendingSessionStore
|
||||
) : RegistrationWizard {
|
||||
|
||||
private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
|
||||
|
||||
private val authAPI = buildAuthAPI()
|
||||
private val registerTask = DefaultRegisterTask(authAPI)
|
||||
private val registerAddThreePidTask = DefaultRegisterAddThreePidTask(authAPI)
|
||||
private val validateCodeTask = DefaultValidateCodeTask(authAPI)
|
||||
|
@ -71,70 +59,54 @@ internal class DefaultRegistrationWizard(
|
|||
override val isRegistrationStarted: Boolean
|
||||
get() = pendingSessionData.isRegistrationStarted
|
||||
|
||||
override fun getRegistrationFlow(callback: MatrixCallback<RegistrationResult>): Cancelable {
|
||||
override suspend fun getRegistrationFlow(): RegistrationResult {
|
||||
val params = RegistrationParams()
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
performRegistrationRequest(params)
|
||||
}
|
||||
return performRegistrationRequest(params)
|
||||
}
|
||||
|
||||
override fun createAccount(userName: String,
|
||||
password: String,
|
||||
initialDeviceDisplayName: String?,
|
||||
callback: MatrixCallback<RegistrationResult>): Cancelable {
|
||||
override suspend fun createAccount(userName: String,
|
||||
password: String,
|
||||
initialDeviceDisplayName: String?): RegistrationResult {
|
||||
val params = RegistrationParams(
|
||||
username = userName,
|
||||
password = password,
|
||||
initialDeviceDisplayName = initialDeviceDisplayName
|
||||
)
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
performRegistrationRequest(params)
|
||||
.also {
|
||||
pendingSessionData = pendingSessionData.copy(isRegistrationStarted = true)
|
||||
.also { pendingSessionStore.savePendingSessionData(it) }
|
||||
}
|
||||
}
|
||||
return performRegistrationRequest(params)
|
||||
.also {
|
||||
pendingSessionData = pendingSessionData.copy(isRegistrationStarted = true)
|
||||
.also { pendingSessionStore.savePendingSessionData(it) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun performReCaptcha(response: String, callback: MatrixCallback<RegistrationResult>): Cancelable {
|
||||
val safeSession = pendingSessionData.currentSession ?: run {
|
||||
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
|
||||
return NoOpCancellable
|
||||
}
|
||||
override suspend fun performReCaptcha(response: String): RegistrationResult {
|
||||
val safeSession = pendingSessionData.currentSession
|
||||
?: throw IllegalStateException("developer error, call createAccount() method first")
|
||||
|
||||
val params = RegistrationParams(auth = AuthParams.createForCaptcha(safeSession, response))
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
performRegistrationRequest(params)
|
||||
}
|
||||
return performRegistrationRequest(params)
|
||||
}
|
||||
|
||||
override fun acceptTerms(callback: MatrixCallback<RegistrationResult>): Cancelable {
|
||||
val safeSession = pendingSessionData.currentSession ?: run {
|
||||
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
|
||||
return NoOpCancellable
|
||||
}
|
||||
override suspend fun acceptTerms(): RegistrationResult {
|
||||
val safeSession = pendingSessionData.currentSession
|
||||
?: throw IllegalStateException("developer error, call createAccount() method first")
|
||||
|
||||
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.TERMS, session = safeSession))
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
performRegistrationRequest(params)
|
||||
}
|
||||
return performRegistrationRequest(params)
|
||||
}
|
||||
|
||||
override fun addThreePid(threePid: RegisterThreePid, callback: MatrixCallback<RegistrationResult>): Cancelable {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
pendingSessionData = pendingSessionData.copy(currentThreePidData = null)
|
||||
.also { pendingSessionStore.savePendingSessionData(it) }
|
||||
override suspend fun addThreePid(threePid: RegisterThreePid): RegistrationResult {
|
||||
pendingSessionData = pendingSessionData.copy(currentThreePidData = null)
|
||||
.also { pendingSessionStore.savePendingSessionData(it) }
|
||||
|
||||
sendThreePid(threePid)
|
||||
}
|
||||
return sendThreePid(threePid)
|
||||
}
|
||||
|
||||
override fun sendAgainThreePid(callback: MatrixCallback<RegistrationResult>): Cancelable {
|
||||
val safeCurrentThreePid = pendingSessionData.currentThreePidData?.threePid ?: run {
|
||||
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
|
||||
return NoOpCancellable
|
||||
}
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
sendThreePid(safeCurrentThreePid)
|
||||
}
|
||||
override suspend fun sendAgainThreePid(): RegistrationResult {
|
||||
val safeCurrentThreePid = pendingSessionData.currentThreePidData?.threePid
|
||||
?: throw IllegalStateException("developer error, call createAccount() method first")
|
||||
|
||||
return sendThreePid(safeCurrentThreePid)
|
||||
}
|
||||
|
||||
private suspend fun sendThreePid(threePid: RegisterThreePid): RegistrationResult {
|
||||
|
@ -173,20 +145,15 @@ internal class DefaultRegistrationWizard(
|
|||
return performRegistrationRequest(params)
|
||||
}
|
||||
|
||||
override fun checkIfEmailHasBeenValidated(delayMillis: Long, callback: MatrixCallback<RegistrationResult>): Cancelable {
|
||||
val safeParam = pendingSessionData.currentThreePidData?.registrationParams ?: run {
|
||||
callback.onFailure(IllegalStateException("developer error, no pending three pid"))
|
||||
return NoOpCancellable
|
||||
}
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
performRegistrationRequest(safeParam, delayMillis)
|
||||
}
|
||||
override suspend fun checkIfEmailHasBeenValidated(delayMillis: Long): RegistrationResult {
|
||||
val safeParam = pendingSessionData.currentThreePidData?.registrationParams
|
||||
?: throw IllegalStateException("developer error, no pending three pid")
|
||||
|
||||
return performRegistrationRequest(safeParam, delayMillis)
|
||||
}
|
||||
|
||||
override fun handleValidateThreePid(code: String, callback: MatrixCallback<RegistrationResult>): Cancelable {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
validateThreePid(code)
|
||||
}
|
||||
override suspend fun handleValidateThreePid(code: String): RegistrationResult {
|
||||
return validateThreePid(code)
|
||||
}
|
||||
|
||||
private suspend fun validateThreePid(code: String): RegistrationResult {
|
||||
|
@ -210,15 +177,12 @@ internal class DefaultRegistrationWizard(
|
|||
}
|
||||
}
|
||||
|
||||
override fun dummy(callback: MatrixCallback<RegistrationResult>): Cancelable {
|
||||
val safeSession = pendingSessionData.currentSession ?: run {
|
||||
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
|
||||
return NoOpCancellable
|
||||
}
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.DUMMY, session = safeSession))
|
||||
performRegistrationRequest(params)
|
||||
}
|
||||
override suspend fun dummy(): RegistrationResult {
|
||||
val safeSession = pendingSessionData.currentSession
|
||||
?: throw IllegalStateException("developer error, call createAccount() method first")
|
||||
|
||||
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.DUMMY, session = safeSession))
|
||||
return performRegistrationRequest(params)
|
||||
}
|
||||
|
||||
private suspend fun performRegistrationRequest(registrationParams: RegistrationParams,
|
||||
|
@ -239,9 +203,4 @@ internal class DefaultRegistrationWizard(
|
|||
val session = sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
|
||||
return RegistrationResult.Success(session)
|
||||
}
|
||||
|
||||
private fun buildAuthAPI(): AuthAPI {
|
||||
val retrofit = retrofitFactory.create(okHttpClient, pendingSessionData.homeServerConnectionConfig.homeServerUri.toString())
|
||||
return retrofit.create(AuthAPI::class.java)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,11 +23,11 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import org.junit.Assert
|
||||
import org.matrix.android.sdk.api.Matrix
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
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.registration.RegistrationResult
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.sync.SyncState
|
||||
|
@ -47,22 +47,21 @@ abstract class VerificationTestBase {
|
|||
withInitialSync: Boolean): Session {
|
||||
val hs = createHomeServerConfig()
|
||||
|
||||
doSync<LoginFlowResult> {
|
||||
matrix.authenticationService()
|
||||
.getLoginFlow(hs, it)
|
||||
runBlockingTest {
|
||||
matrix.authenticationService().getLoginFlow(hs)
|
||||
}
|
||||
|
||||
doSync<RegistrationResult> {
|
||||
runBlockingTest {
|
||||
matrix.authenticationService()
|
||||
.getRegistrationWizard()
|
||||
.createAccount(userName, password, null, it)
|
||||
.createAccount(userName, password, null)
|
||||
}
|
||||
|
||||
// Perform dummy step
|
||||
val registrationResult = doSync<RegistrationResult> {
|
||||
val registrationResult = runBlockingTest {
|
||||
matrix.authenticationService()
|
||||
.getRegistrationWizard()
|
||||
.dummy(it)
|
||||
.dummy()
|
||||
}
|
||||
|
||||
Assert.assertTrue(registrationResult is RegistrationResult.Success)
|
||||
|
@ -80,6 +79,14 @@ abstract class VerificationTestBase {
|
|||
.build()
|
||||
}
|
||||
|
||||
protected fun <T> runBlockingTest(timeout: Long = 20_000, block: suspend () -> T): T {
|
||||
return runBlocking {
|
||||
withTimeout(timeout) {
|
||||
block()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Transform a method with a MatrixCallback to a synchronous method
|
||||
inline fun <reified T> doSync(block: (MatrixCallback<T>) -> Unit): T {
|
||||
val lock = CountDownLatch(1)
|
||||
|
|
|
@ -19,6 +19,7 @@ package im.vector.app.features.login
|
|||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.airbnb.mvrx.ActivityViewModelContext
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Loading
|
||||
|
@ -27,8 +28,8 @@ import com.airbnb.mvrx.Success
|
|||
import com.airbnb.mvrx.Uninitialized
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.core.extensions.configureAndStart
|
||||
|
@ -37,7 +38,8 @@ import im.vector.app.core.platform.VectorViewModel
|
|||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.core.utils.ensureTrailingSlash
|
||||
import im.vector.app.features.signout.soft.SoftLogoutActivity
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
|
@ -51,7 +53,6 @@ import org.matrix.android.sdk.api.auth.registration.Stage
|
|||
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.CancellationException
|
||||
|
||||
|
@ -117,7 +118,12 @@ class LoginViewModel @AssistedInject constructor(
|
|||
|
||||
private var loginConfig: LoginConfig? = null
|
||||
|
||||
private var currentTask: Cancelable? = null
|
||||
private var currentJob: Job? = null
|
||||
set(value) {
|
||||
// Cancel any previous Job
|
||||
field?.cancel()
|
||||
field = value
|
||||
}
|
||||
|
||||
override fun handle(action: LoginAction) {
|
||||
when (action) {
|
||||
|
@ -140,7 +146,7 @@ class LoginViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private fun handleUserAcceptCertificate(action: LoginAction.UserAcceptCertificate) {
|
||||
// It happen when we get the login flow, or during direct authentication.
|
||||
// It happens when we get the login flow, or during direct authentication.
|
||||
// So alter the homeserver config and retrieve again the login flow
|
||||
when (val finalLastAction = lastAction) {
|
||||
is LoginAction.UpdateHomeServer -> {
|
||||
|
@ -186,22 +192,20 @@ class LoginViewModel @AssistedInject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
currentTask = safeLoginWizard.loginWithToken(
|
||||
action.loginToken,
|
||||
object : MatrixCallback<Session> {
|
||||
override fun onSuccess(data: Session) {
|
||||
onSessionCreated(data)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
_viewEvents.post(LoginViewEvents.Failure(failure))
|
||||
setState {
|
||||
copy(
|
||||
asyncLoginAction = Fail(failure)
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
currentJob = viewModelScope.launch {
|
||||
try {
|
||||
safeLoginWizard.loginWithToken(action.loginToken)
|
||||
} catch (failure: Throwable) {
|
||||
_viewEvents.post(LoginViewEvents.Failure(failure))
|
||||
setState {
|
||||
copy(
|
||||
asyncLoginAction = Fail(failure)
|
||||
)
|
||||
}
|
||||
null
|
||||
}
|
||||
?.let { onSessionCreated(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -231,46 +235,49 @@ class LoginViewModel @AssistedInject constructor(
|
|||
|
||||
private fun handleCheckIfEmailHasBeenValidated(action: LoginAction.CheckIfEmailHasBeenValidated) {
|
||||
// We do not want the common progress bar to be displayed, so we do not change asyncRegistration value in the state
|
||||
currentTask?.cancel()
|
||||
currentTask = registrationWizard?.checkIfEmailHasBeenValidated(action.delayMillis, registrationCallback)
|
||||
currentJob = executeRegistrationStep(withLoading = false) {
|
||||
it.checkIfEmailHasBeenValidated(action.delayMillis)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleStopEmailValidationCheck() {
|
||||
currentTask?.cancel()
|
||||
currentTask = null
|
||||
currentJob = null
|
||||
}
|
||||
|
||||
private fun handleValidateThreePid(action: LoginAction.ValidateThreePid) {
|
||||
setState { copy(asyncRegistration = Loading()) }
|
||||
currentTask = registrationWizard?.handleValidateThreePid(action.code, registrationCallback)
|
||||
currentJob = executeRegistrationStep {
|
||||
it.handleValidateThreePid(action.code)
|
||||
}
|
||||
}
|
||||
|
||||
private val registrationCallback = object : MatrixCallback<RegistrationResult> {
|
||||
override fun onSuccess(data: RegistrationResult) {
|
||||
/*
|
||||
// Simulate registration disabled
|
||||
onFailure(Failure.ServerError(MatrixError(
|
||||
code = MatrixError.FORBIDDEN,
|
||||
message = "Registration is disabled"
|
||||
), 403))
|
||||
*/
|
||||
|
||||
setState {
|
||||
copy(
|
||||
asyncRegistration = Uninitialized
|
||||
)
|
||||
}
|
||||
|
||||
when (data) {
|
||||
is RegistrationResult.Success -> onSessionCreated(data.session)
|
||||
is RegistrationResult.FlowResponse -> onFlowResponse(data.flowResult)
|
||||
}
|
||||
private fun executeRegistrationStep(withLoading: Boolean = true,
|
||||
block: suspend (RegistrationWizard) -> RegistrationResult): Job {
|
||||
if (withLoading) {
|
||||
setState { copy(asyncRegistration = Loading()) }
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
if (failure !is CancellationException) {
|
||||
_viewEvents.post(LoginViewEvents.Failure(failure))
|
||||
return viewModelScope.launch {
|
||||
try {
|
||||
registrationWizard?.let { block(it) }
|
||||
/*
|
||||
// Simulate registration disabled
|
||||
throw Failure.ServerError(MatrixError(
|
||||
code = MatrixError.FORBIDDEN,
|
||||
message = "Registration is disabled"
|
||||
), 403))
|
||||
*/
|
||||
} catch (failure: Throwable) {
|
||||
if (failure !is CancellationException) {
|
||||
_viewEvents.post(LoginViewEvents.Failure(failure))
|
||||
}
|
||||
null
|
||||
}
|
||||
?.let { data ->
|
||||
when (data) {
|
||||
is RegistrationResult.Success -> onSessionCreated(data.session)
|
||||
is RegistrationResult.FlowResponse -> onFlowResponse(data.flowResult)
|
||||
}
|
||||
}
|
||||
|
||||
setState {
|
||||
copy(
|
||||
asyncRegistration = Uninitialized
|
||||
|
@ -281,78 +288,68 @@ class LoginViewModel @AssistedInject constructor(
|
|||
|
||||
private fun handleAddThreePid(action: LoginAction.AddThreePid) {
|
||||
setState { copy(asyncRegistration = Loading()) }
|
||||
currentTask = registrationWizard?.addThreePid(action.threePid, object : MatrixCallback<RegistrationResult> {
|
||||
override fun onSuccess(data: RegistrationResult) {
|
||||
setState {
|
||||
copy(
|
||||
asyncRegistration = Uninitialized
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
currentJob = viewModelScope.launch {
|
||||
try {
|
||||
registrationWizard?.addThreePid(action.threePid)
|
||||
} catch (failure: Throwable) {
|
||||
_viewEvents.post(LoginViewEvents.Failure(failure))
|
||||
setState {
|
||||
copy(
|
||||
asyncRegistration = Uninitialized
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
setState {
|
||||
copy(
|
||||
asyncRegistration = Uninitialized
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSendAgainThreePid() {
|
||||
setState { copy(asyncRegistration = Loading()) }
|
||||
currentTask = registrationWizard?.sendAgainThreePid(object : MatrixCallback<RegistrationResult> {
|
||||
override fun onSuccess(data: RegistrationResult) {
|
||||
setState {
|
||||
copy(
|
||||
asyncRegistration = Uninitialized
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
currentJob = viewModelScope.launch {
|
||||
try {
|
||||
registrationWizard?.sendAgainThreePid()
|
||||
} catch (failure: Throwable) {
|
||||
_viewEvents.post(LoginViewEvents.Failure(failure))
|
||||
setState {
|
||||
copy(
|
||||
asyncRegistration = Uninitialized
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
setState {
|
||||
copy(
|
||||
asyncRegistration = Uninitialized
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAcceptTerms() {
|
||||
setState { copy(asyncRegistration = Loading()) }
|
||||
currentTask = registrationWizard?.acceptTerms(registrationCallback)
|
||||
currentJob = executeRegistrationStep {
|
||||
it.acceptTerms()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleRegisterDummy() {
|
||||
setState { copy(asyncRegistration = Loading()) }
|
||||
currentTask = registrationWizard?.dummy(registrationCallback)
|
||||
currentJob = executeRegistrationStep {
|
||||
it.dummy()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleRegisterWith(action: LoginAction.LoginOrRegister) {
|
||||
setState { copy(asyncRegistration = Loading()) }
|
||||
reAuthHelper.data = action.password
|
||||
currentTask = registrationWizard?.createAccount(
|
||||
action.username,
|
||||
action.password,
|
||||
action.initialDeviceName,
|
||||
registrationCallback
|
||||
)
|
||||
currentJob = executeRegistrationStep {
|
||||
it.createAccount(
|
||||
action.username,
|
||||
action.password,
|
||||
action.initialDeviceName
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCaptchaDone(action: LoginAction.CaptchaDone) {
|
||||
setState { copy(asyncRegistration = Loading()) }
|
||||
currentTask = registrationWizard?.performReCaptcha(action.captchaResponse, registrationCallback)
|
||||
currentJob = executeRegistrationStep {
|
||||
it.performReCaptcha(action.captchaResponse)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleResetAction(action: LoginAction.ResetAction) {
|
||||
// Cancel any request
|
||||
currentTask?.cancel()
|
||||
currentTask = null
|
||||
currentJob = null
|
||||
|
||||
when (action) {
|
||||
LoginAction.ResetHomeServerType -> {
|
||||
|
@ -363,16 +360,17 @@ class LoginViewModel @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
LoginAction.ResetHomeServerUrl -> {
|
||||
authenticationService.reset()
|
||||
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Uninitialized,
|
||||
homeServerUrl = null,
|
||||
loginMode = LoginMode.Unknown,
|
||||
serverType = ServerType.Unknown,
|
||||
loginModeSupportedTypes = emptyList()
|
||||
)
|
||||
viewModelScope.launch {
|
||||
authenticationService.reset()
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Uninitialized,
|
||||
homeServerUrl = null,
|
||||
loginMode = LoginMode.Unknown,
|
||||
serverType = ServerType.Unknown,
|
||||
loginModeSupportedTypes = emptyList()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
LoginAction.ResetSignMode -> {
|
||||
|
@ -386,13 +384,14 @@ class LoginViewModel @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
LoginAction.ResetLogin -> {
|
||||
authenticationService.cancelPendingLoginOrRegistration()
|
||||
|
||||
setState {
|
||||
copy(
|
||||
asyncLoginAction = Uninitialized,
|
||||
asyncRegistration = Uninitialized
|
||||
)
|
||||
viewModelScope.launch {
|
||||
authenticationService.cancelPendingLoginOrRegistration()
|
||||
setState {
|
||||
copy(
|
||||
asyncLoginAction = Uninitialized,
|
||||
asyncRegistration = Uninitialized
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
LoginAction.ResetResetPassword -> {
|
||||
|
@ -473,26 +472,27 @@ class LoginViewModel @AssistedInject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
currentTask = safeLoginWizard.resetPassword(action.email, action.newPassword, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
setState {
|
||||
copy(
|
||||
asyncResetPassword = Success(data),
|
||||
resetPasswordEmail = action.email
|
||||
)
|
||||
}
|
||||
|
||||
_viewEvents.post(LoginViewEvents.OnResetPasswordSendThreePidDone)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
currentJob = viewModelScope.launch {
|
||||
try {
|
||||
safeLoginWizard.resetPassword(action.email, action.newPassword)
|
||||
} catch (failure: Throwable) {
|
||||
setState {
|
||||
copy(
|
||||
asyncResetPassword = Fail(failure)
|
||||
)
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
})
|
||||
|
||||
setState {
|
||||
copy(
|
||||
asyncResetPassword = Success(Unit),
|
||||
resetPasswordEmail = action.email
|
||||
)
|
||||
}
|
||||
|
||||
_viewEvents.post(LoginViewEvents.OnResetPasswordSendThreePidDone)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -514,26 +514,26 @@ class LoginViewModel @AssistedInject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
currentTask = safeLoginWizard.resetPasswordMailConfirmed(object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
setState {
|
||||
copy(
|
||||
asyncResetMailConfirmed = Success(data),
|
||||
resetPasswordEmail = null
|
||||
)
|
||||
}
|
||||
|
||||
_viewEvents.post(LoginViewEvents.OnResetPasswordMailConfirmationSuccess)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
currentJob = viewModelScope.launch {
|
||||
try {
|
||||
safeLoginWizard.resetPasswordMailConfirmed()
|
||||
} catch (failure: Throwable) {
|
||||
setState {
|
||||
copy(
|
||||
asyncResetMailConfirmed = Fail(failure)
|
||||
)
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
})
|
||||
setState {
|
||||
copy(
|
||||
asyncResetMailConfirmed = Success(Unit),
|
||||
resetPasswordEmail = null
|
||||
)
|
||||
}
|
||||
|
||||
_viewEvents.post(LoginViewEvents.OnResetPasswordMailConfirmationSuccess)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -553,36 +553,36 @@ class LoginViewModel @AssistedInject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
authenticationService.getWellKnownData(action.username, homeServerConnectionConfig, object : MatrixCallback<WellknownResult> {
|
||||
override fun onSuccess(data: WellknownResult) {
|
||||
when (data) {
|
||||
is WellknownResult.Prompt ->
|
||||
onWellknownSuccess(action, data, homeServerConnectionConfig)
|
||||
is WellknownResult.FailPrompt ->
|
||||
// Relax on IS discovery if home server is valid
|
||||
if (data.homeServerUrl != null && data.wellKnown != null) {
|
||||
onWellknownSuccess(action, WellknownResult.Prompt(data.homeServerUrl!!, null, data.wellKnown!!), homeServerConnectionConfig)
|
||||
} else {
|
||||
onWellKnownError()
|
||||
}
|
||||
is WellknownResult.InvalidMatrixId -> {
|
||||
setState {
|
||||
copy(
|
||||
asyncLoginAction = Uninitialized
|
||||
)
|
||||
}
|
||||
_viewEvents.post(LoginViewEvents.Failure(Exception(stringProvider.getString(R.string.login_signin_matrix_id_error_invalid_matrix_id))))
|
||||
}
|
||||
else -> {
|
||||
currentJob = viewModelScope.launch {
|
||||
val data = try {
|
||||
authenticationService.getWellKnownData(action.username, homeServerConnectionConfig)
|
||||
} catch (failure: Throwable) {
|
||||
onDirectLoginError(failure)
|
||||
return@launch
|
||||
}
|
||||
when (data) {
|
||||
is WellknownResult.Prompt ->
|
||||
onWellknownSuccess(action, data, homeServerConnectionConfig)
|
||||
is WellknownResult.FailPrompt ->
|
||||
// Relax on IS discovery if home server is valid
|
||||
if (data.homeServerUrl != null && data.wellKnown != null) {
|
||||
onWellknownSuccess(action, WellknownResult.Prompt(data.homeServerUrl!!, null, data.wellKnown!!), homeServerConnectionConfig)
|
||||
} else {
|
||||
onWellKnownError()
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
onDirectLoginError(failure)
|
||||
}
|
||||
})
|
||||
is WellknownResult.InvalidMatrixId -> {
|
||||
setState {
|
||||
copy(
|
||||
asyncLoginAction = Uninitialized
|
||||
)
|
||||
}
|
||||
_viewEvents.post(LoginViewEvents.Failure(Exception(stringProvider.getString(R.string.login_signin_matrix_id_error_invalid_matrix_id))))
|
||||
}
|
||||
else -> {
|
||||
onWellKnownError()
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
|
||||
private fun onWellKnownError() {
|
||||
|
@ -594,9 +594,9 @@ class LoginViewModel @AssistedInject constructor(
|
|||
_viewEvents.post(LoginViewEvents.Failure(Exception(stringProvider.getString(R.string.autodiscover_well_known_error))))
|
||||
}
|
||||
|
||||
private fun onWellknownSuccess(action: LoginAction.LoginOrRegister,
|
||||
wellKnownPrompt: WellknownResult.Prompt,
|
||||
homeServerConnectionConfig: HomeServerConnectionConfig?) {
|
||||
private suspend fun onWellknownSuccess(action: LoginAction.LoginOrRegister,
|
||||
wellKnownPrompt: WellknownResult.Prompt,
|
||||
homeServerConnectionConfig: HomeServerConnectionConfig?) {
|
||||
val alteredHomeServerConnectionConfig = homeServerConnectionConfig
|
||||
?.copy(
|
||||
homeServerUri = Uri.parse(wellKnownPrompt.homeServerUrl),
|
||||
|
@ -607,20 +607,17 @@ class LoginViewModel @AssistedInject constructor(
|
|||
identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) }
|
||||
)
|
||||
|
||||
authenticationService.directAuthentication(
|
||||
alteredHomeServerConnectionConfig,
|
||||
action.username,
|
||||
action.password,
|
||||
action.initialDeviceName,
|
||||
object : MatrixCallback<Session> {
|
||||
override fun onSuccess(data: Session) {
|
||||
onSessionCreated(data)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
onDirectLoginError(failure)
|
||||
}
|
||||
})
|
||||
val data = try {
|
||||
authenticationService.directAuthentication(
|
||||
alteredHomeServerConnectionConfig,
|
||||
action.username,
|
||||
action.password,
|
||||
action.initialDeviceName)
|
||||
} catch (failure: Throwable) {
|
||||
onDirectLoginError(failure)
|
||||
return
|
||||
}
|
||||
onSessionCreated(data)
|
||||
}
|
||||
|
||||
private fun onDirectLoginError(failure: Throwable) {
|
||||
|
@ -657,35 +654,33 @@ class LoginViewModel @AssistedInject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
currentTask = safeLoginWizard.login(
|
||||
action.username,
|
||||
action.password,
|
||||
action.initialDeviceName,
|
||||
object : MatrixCallback<Session> {
|
||||
override fun onSuccess(data: Session) {
|
||||
currentJob = viewModelScope.launch {
|
||||
try {
|
||||
safeLoginWizard.login(
|
||||
action.username,
|
||||
action.password,
|
||||
action.initialDeviceName
|
||||
)
|
||||
} catch (failure: Throwable) {
|
||||
setState {
|
||||
copy(
|
||||
asyncLoginAction = Fail(failure)
|
||||
)
|
||||
}
|
||||
null
|
||||
}
|
||||
?.let {
|
||||
reAuthHelper.data = action.password
|
||||
onSessionCreated(data)
|
||||
onSessionCreated(it)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
setState {
|
||||
copy(
|
||||
asyncLoginAction = Fail(failure)
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startRegistrationFlow() {
|
||||
setState {
|
||||
copy(
|
||||
asyncRegistration = Loading()
|
||||
)
|
||||
currentJob = executeRegistrationStep {
|
||||
it.getRegistrationFlow()
|
||||
}
|
||||
|
||||
currentTask = registrationWizard?.getRegistrationFlow(registrationCallback)
|
||||
}
|
||||
|
||||
private fun startAuthenticationFlow() {
|
||||
|
@ -706,8 +701,9 @@ class LoginViewModel @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun onSessionCreated(session: Session) {
|
||||
private suspend fun onSessionCreated(session: Session) {
|
||||
activeSessionHolder.setActiveSession(session)
|
||||
|
||||
authenticationService.reset()
|
||||
session.configureAndStart(applicationContext)
|
||||
setState {
|
||||
|
@ -724,15 +720,17 @@ class LoginViewModel @AssistedInject constructor(
|
|||
// Should not happen
|
||||
Timber.w("homeServerConnectionConfig is null")
|
||||
} else {
|
||||
authenticationService.createSessionFromSso(homeServerConnectionConfigFinal, action.credentials, object : MatrixCallback<Session> {
|
||||
override fun onSuccess(data: Session) {
|
||||
onSessionCreated(data)
|
||||
currentJob = viewModelScope.launch {
|
||||
try {
|
||||
authenticationService.createSessionFromSso(homeServerConnectionConfigFinal, action.credentials)
|
||||
} catch (failure: Throwable) {
|
||||
setState {
|
||||
copy(asyncLoginAction = Fail(failure))
|
||||
}
|
||||
null
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) = setState {
|
||||
copy(asyncLoginAction = Fail(failure))
|
||||
}
|
||||
})
|
||||
?.let { onSessionCreated(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -749,21 +747,21 @@ class LoginViewModel @AssistedInject constructor(
|
|||
private fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig) {
|
||||
currentHomeServerConnectionConfig = homeServerConnectionConfig
|
||||
|
||||
currentTask?.cancel()
|
||||
currentTask = null
|
||||
authenticationService.cancelPendingLoginOrRegistration()
|
||||
currentJob = viewModelScope.launch {
|
||||
authenticationService.cancelPendingLoginOrRegistration()
|
||||
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Loading(),
|
||||
// If user has entered https://matrix.org, ensure that server type is ServerType.MatrixOrg
|
||||
// It is also useful to set the value again in the case of a certificate error on matrix.org
|
||||
serverType = if (homeServerConnectionConfig.homeServerUri.toString() == matrixOrgUrl) ServerType.MatrixOrg else serverType
|
||||
)
|
||||
}
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Loading(),
|
||||
// If user has entered https://matrix.org, ensure that server type is ServerType.MatrixOrg
|
||||
// It is also useful to set the value again in the case of a certificate error on matrix.org
|
||||
serverType = if (homeServerConnectionConfig.homeServerUri.toString() == matrixOrgUrl) ServerType.MatrixOrg else serverType
|
||||
)
|
||||
}
|
||||
|
||||
currentTask = authenticationService.getLoginFlow(homeServerConnectionConfig, object : MatrixCallback<LoginFlowResult> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
val data = try {
|
||||
authenticationService.getLoginFlow(homeServerConnectionConfig)
|
||||
} catch (failure: Throwable) {
|
||||
_viewEvents.post(LoginViewEvents.Failure(failure))
|
||||
setState {
|
||||
copy(
|
||||
|
@ -772,47 +770,39 @@ class LoginViewModel @AssistedInject constructor(
|
|||
serverType = if (serverType == ServerType.MatrixOrg) ServerType.Unknown else serverType
|
||||
)
|
||||
}
|
||||
null
|
||||
}
|
||||
|
||||
override fun onSuccess(data: LoginFlowResult) {
|
||||
if (data is LoginFlowResult.Success) {
|
||||
// Valid Homeserver, add it to the history.
|
||||
// Note: we add what the user has input, data.homeServerUrl can be different
|
||||
rememberHomeServer(homeServerConnectionConfig.homeServerUri.toString())
|
||||
|
||||
when (data) {
|
||||
is LoginFlowResult.Success -> {
|
||||
val loginMode = when {
|
||||
// SSO login is taken first
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO)
|
||||
&& data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders)
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders)
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
|
||||
else -> LoginMode.Unsupported
|
||||
}
|
||||
val loginMode = when {
|
||||
// SSO login is taken first
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO)
|
||||
&& data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders)
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders)
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
|
||||
else -> LoginMode.Unsupported
|
||||
}
|
||||
|
||||
// FIXME We should post a view event here normally?
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Uninitialized,
|
||||
homeServerUrl = data.homeServerUrl,
|
||||
loginMode = loginMode,
|
||||
loginModeSupportedTypes = data.supportedLoginTypes.toList()
|
||||
)
|
||||
}
|
||||
if ((loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported)
|
||||
|| data.isOutdatedHomeserver) {
|
||||
// Notify the UI
|
||||
_viewEvents.post(LoginViewEvents.OutdatedHomeserver)
|
||||
}
|
||||
}
|
||||
// FIXME We should post a view event here normally?
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Uninitialized,
|
||||
homeServerUrl = data.homeServerUrl,
|
||||
loginMode = loginMode,
|
||||
loginModeSupportedTypes = data.supportedLoginTypes.toList()
|
||||
)
|
||||
}
|
||||
if ((loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported)
|
||||
|| data.isOutdatedHomeserver) {
|
||||
// Notify the UI
|
||||
_viewEvents.post(LoginViewEvents.OutdatedHomeserver)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
currentTask?.cancel()
|
||||
super.onCleared()
|
||||
}
|
||||
}
|
||||
|
||||
fun getInitialHomeServerUrl(): String? {
|
||||
|
|
|
@ -25,19 +25,17 @@ import com.airbnb.mvrx.Success
|
|||
import com.airbnb.mvrx.Uninitialized
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.core.extensions.hasUnsavedKeys
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.features.login.LoginMode
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
|
||||
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
|
@ -76,54 +74,49 @@ class SoftLogoutViewModel @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private var currentTask: Cancelable? = null
|
||||
|
||||
init {
|
||||
// Get the supported login flow
|
||||
getSupportedLoginFlow()
|
||||
}
|
||||
|
||||
private fun getSupportedLoginFlow() {
|
||||
currentTask?.cancel()
|
||||
currentTask = null
|
||||
authenticationService.cancelPendingLoginOrRegistration()
|
||||
viewModelScope.launch {
|
||||
authenticationService.cancelPendingLoginOrRegistration()
|
||||
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Loading()
|
||||
)
|
||||
}
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Loading()
|
||||
)
|
||||
}
|
||||
|
||||
currentTask = authenticationService.getLoginFlowOfSession(session.sessionId, object : MatrixCallback<LoginFlowResult> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
val data = try {
|
||||
authenticationService.getLoginFlowOfSession(session.sessionId)
|
||||
} catch (failure: Throwable) {
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Fail(failure)
|
||||
)
|
||||
}
|
||||
null
|
||||
}
|
||||
|
||||
override fun onSuccess(data: LoginFlowResult) {
|
||||
when (data) {
|
||||
is LoginFlowResult.Success -> {
|
||||
val loginMode = when {
|
||||
// SSO login is taken first
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO)
|
||||
&& data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders)
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders)
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
|
||||
else -> LoginMode.Unsupported
|
||||
}
|
||||
if (data is LoginFlowResult.Success) {
|
||||
val loginMode = when {
|
||||
// SSO login is taken first
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO)
|
||||
&& data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders)
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders)
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
|
||||
else -> LoginMode.Unsupported
|
||||
}
|
||||
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Success(loginMode)
|
||||
)
|
||||
}
|
||||
}
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Success(loginMode)
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override fun handle(action: SoftLogoutAction) {
|
||||
|
@ -227,9 +220,4 @@ class SoftLogoutViewModel @AssistedInject constructor(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
currentTask?.cancel()
|
||||
super.onCleared()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,17 +81,19 @@
|
|||
app:layout_constraintTop_toBottomOf="@+id/loginSignupSigninSubmit"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<!-- Social Logins buttons -->
|
||||
<!-- Social Login buttons -->
|
||||
<LinearLayout
|
||||
android:id="@+id/loginSignupSigninSignInSocialLoginContainer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/loginSignupSigninSignIn">
|
||||
app:layout_constraintTop_toBottomOf="@id/loginSignupSigninSignIn"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/loginSignupSigninSocialLoginHeader"
|
||||
|
|
Loading…
Reference in a new issue