[issue-2610] Merge branch 'develop' of https://github.com/vector-im/element-android into feature/issue-2610-override-nick-color-via-user-account-data

This commit is contained in:
Péter Radics 2021-02-20 22:10:54 +01:00
commit 1ec0956b82
45 changed files with 932 additions and 980 deletions

23
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,23 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
# Updates for Github Actions used in the repo
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
# Updates for Gradle dependencies used in the app
- package-ecosystem: gradle
directory: "/"
schedule:
interval: "daily"
open-pull-requests-limit: 200
reviewers:
- "bmarty"
### ignore:
### - dependency-name: com.squareup.okhttp3:logging-interceptor
### versions: "> 3.12.10"

View File

@ -1,20 +1,14 @@
Changes in Element 1.0.18 (2021-XX-XX)
Changes in Element 1.1.1 (2021-XX-XX)
===================================================
Features ✨:
- Allow changing nick colors (#2610)
- VoIP : support for VoIP V1 protocol, transfer call and dial-pad
Improvements 🙌:
- VoIP : new tiles in timeline
- Improve room profile UX
- Upgrade Jitsi library from 2.9.3 to 3.1.0
- a11y improvements
-
Bugfix 🐛:
- VoIP : fix audio devices output
- Fix crash after initial sync on Dendrite
- Fix crash reported by PlayStore (#2707)
-
Translations 🗣:
-
@ -28,6 +22,31 @@ Build 🧱:
Test:
-
Other changes:
-
Changes in Element 1.1.0 (2021-02-19)
===================================================
Features ✨:
- VoIP : support for VoIP V1 protocol, transfer call and dial-pad
Improvements 🙌:
- VoIP : new tiles in timeline
- Improve room profile UX
- Upgrade Jitsi library from 2.9.3 to 3.1.0
- a11y improvements
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
SDK API changes ⚠️:
- Migrate AuthenticationService API to coroutines (#2449)
Other changes:
- New Dev Tools panel for developers
- Fix typos in CHANGES.md (#2811)
@ -1204,7 +1223,7 @@ Mode details here: https://medium.com/@RiotChat/introducing-the-riotx-beta-for-a
=======================================================
Changes in Element 1.X.X (2021-XX-XX)
Changes in Element 1.1.X (2021-XX-XX)
===================================================
Features ✨:

View File

@ -71,5 +71,5 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation 'com.google.android.material:material:1.2.1'
implementation 'com.google.android.material:material:1.3.0'
}

View File

@ -15,7 +15,7 @@ buildscript {
classpath 'com.android.tools.build:gradle:4.1.2'
classpath 'com.google.gms:google-services:4.3.5'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1'
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.1.1'
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.2'
// NOTE: Do not place your application dependencies here; they belong

View File

@ -0,0 +1,2 @@
Main changes in this version: VoIP (audio and video calls in DM) improvement and bug fixes!
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.1.0

View File

@ -9,7 +9,7 @@ buildscript {
jcenter()
}
dependencies {
classpath "io.realm:realm-gradle-plugin:10.1.2"
classpath "io.realm:realm-gradle-plugin:10.3.1"
}
}
@ -112,9 +112,9 @@ dependencies {
def lifecycle_version = '2.2.0'
def arch_version = '2.1.0'
def markwon_version = '3.1.0'
def daggerVersion = '2.31'
def daggerVersion = '2.32'
def work_version = '2.4.0'
def retrofit_version = '2.6.2'
def retrofit_version = '2.9.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
@ -130,7 +130,7 @@ dependencies {
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-moshi:$retrofit_version"
implementation(platform("com.squareup.okhttp3:okhttp-bom:4.8.1"))
implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.1"))
implementation 'com.squareup.okhttp3:okhttp'
implementation 'com.squareup.okhttp3:logging-interceptor'
implementation 'com.squareup.okhttp3:okhttp-urlconnection'
@ -141,7 +141,7 @@ dependencies {
implementation "ru.noties.markwon:core:$markwon_version"
// Image
implementation 'androidx.exifinterface:exifinterface:1.3.1'
implementation 'androidx.exifinterface:exifinterface:1.3.2'
// Database
implementation 'com.github.Zhuinden:realm-monarchy:0.7.1'
@ -155,7 +155,7 @@ dependencies {
implementation "io.arrow-kt:arrow-instances-core:$arrow_version"
// olm lib is now hosted by jitpack: https://jitpack.io/#org.matrix.gitlab.matrix-org/olm
implementation 'org.matrix.gitlab.matrix-org:olm:3.1.2'
implementation 'org.matrix.gitlab.matrix-org:olm:3.2.1'
// DI
implementation "com.google.dagger:dagger:$daggerVersion"
@ -166,14 +166,14 @@ dependencies {
implementation 'com.facebook.stetho:stetho-okhttp3:1.5.1'
// Phone number https://github.com/google/libphonenumber
implementation 'com.googlecode.libphonenumber:libphonenumber:8.10.23'
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.18'
testImplementation 'junit:junit:4.13'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.robolectric:robolectric:4.3'
//testImplementation 'org.robolectric:shadows-support-v4:3.0'
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
testImplementation 'io.mockk:mockk:1.9.2.kotlin12'
testImplementation 'org.amshove.kluent:kluent-android:1.61'
testImplementation 'org.amshove.kluent:kluent-android:1.65'
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
// Plant Timber tree for test
testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'

View File

@ -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
@ -46,12 +43,13 @@ class DeactivateAccountTest : InstrumentedTest {
@Test
fun deactivateAccountTest() {
val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = false))
val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true))
// Deactivate the account
commonTestHelper.runBlockingTest {
session.deactivateAccount(
object : UserInteractiveAuthInterceptor {
eraseAllData = false,
userInteractiveAuthInterceptor = object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.resume(
UserPasswordAuth(
@ -61,7 +59,8 @@ class DeactivateAccountTest : InstrumentedTest {
)
)
}
}, false)
}
)
}
// Try to login on the previous account, it will fail (M_USER_DEACTIVATED)
@ -75,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

View File

@ -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)

View File

@ -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()

View File

@ -16,7 +16,6 @@
package org.matrix.android.sdk.api.auth
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
@ -24,7 +23,6 @@ import org.matrix.android.sdk.api.auth.login.LoginWizard
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.util.Cancelable
/**
* This interface defines methods to authenticate or to create an account to a matrix server.
@ -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
}

View File

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

View File

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

View File

@ -53,22 +53,24 @@ fun Throwable.isInvalidUIAAuth(): Boolean {
* Try to convert to a RegistrationFlowResponse. Return null in the cases it's not possible
*/
fun Throwable.toRegistrationFlowResponse(): RegistrationFlowResponse? {
return if (this is Failure.OtherServerError && this.httpCode == 401) {
return if (this is Failure.OtherServerError && httpCode == 401) {
tryOrNull {
MoshiProvider.providesMoshi()
.adapter(RegistrationFlowResponse::class.java)
.fromJson(this.errorBody)
.fromJson(errorBody)
}
} else if (this is Failure.ServerError && this.httpCode == 401 && this.error.code == MatrixError.M_FORBIDDEN) {
} else if (this is Failure.ServerError && httpCode == 401 && error.code == MatrixError.M_FORBIDDEN) {
// This happens when the submission for this stage was bad (like bad password)
if (this.error.session != null && this.error.flows != null) {
if (error.session != null && error.flows != null) {
RegistrationFlowResponse(
flows = this.error.flows,
session = this.error.session,
completedStages = this.error.completedStages,
params = this.error.params
flows = error.flows,
session = error.session,
completedStages = error.completedStages,
params = error.params
)
} else null
} else {
null
}
} else {
null
}

View File

@ -27,7 +27,8 @@ interface AccountService {
* @param password Current password.
* @param newPassword New password
*/
suspend fun changePassword(password: String, newPassword: String)
suspend fun changePassword(password: String,
newPassword: String)
/**
* Deactivate the account.
@ -41,9 +42,10 @@ interface AccountService {
* be shared with any new or unregistered users, but registered users who already have access to these messages will still
* have access to their copy.
*
* @param password the account password
* @param eraseAllData set to true to forget all messages that have been sent. Warning: this will cause future users to see
* an incomplete view of conversations
* @param userInteractiveAuthInterceptor see [UserInteractiveAuthInterceptor]
*/
suspend fun deactivateAccount(userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, eraseAllData: Boolean)
suspend fun deactivateAccount(eraseAllData: Boolean,
userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor)
}

View File

@ -56,8 +56,6 @@ interface CryptoService {
fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, callback: MatrixCallback<Unit>)
fun deleteDeviceWithUserPassword(deviceId: String, authSession: String?, password: String, callback: MatrixCallback<Unit>)
fun getCryptoVersion(context: Context, longFormat: Boolean): String
fun isCryptoEnabled(): Boolean

View File

@ -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
}

View File

@ -18,10 +18,7 @@ package org.matrix.android.sdk.internal.auth
import android.net.Uri
import dagger.Lazy
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
@ -32,8 +29,6 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.api.util.NoOpCancellable
import org.matrix.android.sdk.api.util.appendParamToUrl
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse
@ -50,11 +45,6 @@ import org.matrix.android.sdk.internal.network.RetrofitFactory
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.network.httpclient.addSocketFactory
import org.matrix.android.sdk.internal.network.ssl.UnrecognizedCertificateException
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.task.configureWith
import org.matrix.android.sdk.internal.task.launchToCallback
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
import org.matrix.android.sdk.internal.util.toCancelable
import org.matrix.android.sdk.internal.wellknown.GetWellknownTask
import javax.inject.Inject
import javax.net.ssl.HttpsURLConnection
@ -63,14 +53,12 @@ internal class DefaultAuthenticationService @Inject constructor(
@Unauthenticated
private val okHttpClient: Lazy<OkHttpClient>,
private val retrofitFactory: RetrofitFactory,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val sessionParamsStore: SessionParamsStore,
private val sessionManager: SessionManager,
private val sessionCreator: SessionCreator,
private val pendingSessionStore: PendingSessionStore,
private val getWellknownTask: GetWellknownTask,
private val directLoginTask: DirectLoginTask,
private val taskExecutor: TaskExecutor
private val directLoginTask: DirectLoginTask
) : AuthenticationService {
private var pendingSessionData: PendingSessionData? = pendingSessionStore.getPendingSessionData()
@ -89,15 +77,11 @@ internal class DefaultAuthenticationService @Inject constructor(
}
}
override fun getLoginFlowOfSession(sessionId: String, callback: MatrixCallback<LoginFlowResult>): Cancelable {
override suspend fun getLoginFlowOfSession(sessionId: String): LoginFlowResult {
val homeServerConnectionConfig = sessionParamsStore.get(sessionId)?.homeServerConnectionConfig
?: throw IllegalStateException("Session not found")
return if (homeServerConnectionConfig == null) {
callback.onFailure(IllegalStateException("Session not found"))
NoOpCancellable
} else {
getLoginFlow(homeServerConnectionConfig, callback)
}
return getLoginFlow(homeServerConnectionConfig)
}
override fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String? {
@ -146,70 +130,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 {

View File

@ -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()
}
}

View File

@ -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 == '/' }

View File

@ -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) {

View File

@ -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)
}
}

View File

@ -16,14 +16,25 @@
package org.matrix.android.sdk.internal.auth.registration
import org.matrix.android.sdk.api.auth.UIABaseAuth
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse
import org.matrix.android.sdk.api.auth.UIABaseAuth
import timber.log.Timber
import kotlin.coroutines.suspendCoroutine
internal suspend fun handleUIA(failure: Throwable, interceptor: UserInteractiveAuthInterceptor, retryBlock: suspend (UIABaseAuth) -> Unit): Boolean {
/**
* Handle a UIA challenge
*
* @param failure the failure to handle
* @param interceptor see doc in [UserInteractiveAuthInterceptor]
* @param retryBlock called at the end of the process, in this block generally retry executing the task, with
* provided authUpdate
* @return true if UIA is handled without error
*/
internal suspend fun handleUIA(failure: Throwable,
interceptor: UserInteractiveAuthInterceptor,
retryBlock: suspend (UIABaseAuth) -> Unit): Boolean {
Timber.d("## UIA: check error ${failure.message}")
val flowResponse = failure.toRegistrationFlowResponse()
?: return false.also {
@ -38,16 +49,16 @@ internal suspend fun handleUIA(failure: Throwable, interceptor: UserInteractiveA
suspendCoroutine<UIABaseAuth> { continuation ->
interceptor.performStage(flowResponse, (failure as? Failure.ServerError)?.error?.code, continuation)
}
} catch (failure: Throwable) {
Timber.w(failure, "## UIA: failed to participate")
} catch (failure2: Throwable) {
Timber.w(failure2, "## UIA: failed to participate")
return false
}
Timber.d("## UIA: updated auth $authUpdate")
Timber.d("## UIA: updated auth")
return try {
retryBlock(authUpdate)
true
} catch (failure: Throwable) {
handleUIA(failure, interceptor, retryBlock)
} catch (failure3: Throwable) {
handleUIA(failure3, interceptor, retryBlock)
}
}

View File

@ -61,7 +61,6 @@ import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule
import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultClaimOneTimeKeysForUsersDevice
import org.matrix.android.sdk.internal.crypto.tasks.DefaultDeleteDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultDeleteDeviceWithUserPasswordTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultDownloadKeysForUsers
import org.matrix.android.sdk.internal.crypto.tasks.DefaultEncryptEventTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultGetDeviceInfoTask
@ -75,7 +74,6 @@ import org.matrix.android.sdk.internal.crypto.tasks.DefaultUploadKeysTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultUploadSignaturesTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultUploadSigningKeysTask
import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceWithUserPasswordTask
import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask
import org.matrix.android.sdk.internal.crypto.tasks.EncryptEventTask
import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask
@ -240,9 +238,6 @@ internal abstract class CryptoModule {
@Binds
abstract fun bindClaimOneTimeKeysForUsersDeviceTask(task: DefaultClaimOneTimeKeysForUsersDevice): ClaimOneTimeKeysForUsersDeviceTask
@Binds
abstract fun bindDeleteDeviceWithUserPasswordTask(task: DefaultDeleteDeviceWithUserPasswordTask): DeleteDeviceWithUserPasswordTask
@Binds
abstract fun bindCrossSigningService(service: DefaultCrossSigningService): CrossSigningService

View File

@ -75,7 +75,6 @@ import org.matrix.android.sdk.internal.crypto.model.toRest
import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceWithUserPasswordTask
import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask
import org.matrix.android.sdk.internal.crypto.tasks.GetDevicesTask
import org.matrix.android.sdk.internal.crypto.tasks.SetDeviceNameTask
@ -153,9 +152,8 @@ internal class DefaultCryptoService @Inject constructor(
// Repository
private val megolmEncryptionFactory: MXMegolmEncryptionFactory,
private val olmEncryptionFactory: MXOlmEncryptionFactory,
private val deleteDeviceTask: DeleteDeviceTask,
private val deleteDeviceWithUserPasswordTask: DeleteDeviceWithUserPasswordTask,
// Tasks
private val deleteDeviceTask: DeleteDeviceTask,
private val getDevicesTask: GetDevicesTask,
private val getDeviceInfoTask: GetDeviceInfoTask,
private val setDeviceNameTask: SetDeviceNameTask,
@ -217,15 +215,6 @@ internal class DefaultCryptoService @Inject constructor(
.executeBy(taskExecutor)
}
override fun deleteDeviceWithUserPassword(deviceId: String, authSession: String?, password: String, callback: MatrixCallback<Unit>) {
deleteDeviceWithUserPasswordTask
.configureWith(DeleteDeviceWithUserPasswordTask.Params(deviceId, authSession, password)) {
this.executionThread = TaskThread.CRYPTO
this.callback = callback
}
.executeBy(taskExecutor)
}
override fun getCryptoVersion(context: Context, longFormat: Boolean): String {
return if (longFormat) olmManager.getDetailedVersion(context) else olmManager.version
}

View File

@ -47,12 +47,16 @@ internal class DefaultDeleteDeviceTask @Inject constructor(
}
} catch (throwable: Throwable) {
if (params.userInteractiveAuthInterceptor == null
|| !handleUIA(throwable, params.userInteractiveAuthInterceptor) { auth ->
execute(params.copy(userAuthParam = auth))
}
|| !handleUIA(
failure = throwable,
interceptor = params.userInteractiveAuthInterceptor,
retryBlock = { authUpdate ->
execute(params.copy(userAuthParam = authUpdate))
}
)
) {
Timber.d("## UIA: propagate failure")
throw throwable
throw throwable
}
}
}

View File

@ -1,57 +0,0 @@
/*
* Copyright 2020 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.crypto.tasks
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
import org.matrix.android.sdk.internal.crypto.api.CryptoApi
import org.matrix.android.sdk.internal.crypto.model.rest.DeleteDeviceParams
import org.matrix.android.sdk.api.auth.UserPasswordAuth
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
internal interface DeleteDeviceWithUserPasswordTask : Task<DeleteDeviceWithUserPasswordTask.Params, Unit> {
data class Params(
val deviceId: String,
val authSession: String?,
val password: String
)
}
internal class DefaultDeleteDeviceWithUserPasswordTask @Inject constructor(
private val cryptoApi: CryptoApi,
@UserId private val userId: String,
private val globalErrorReceiver: GlobalErrorReceiver
) : DeleteDeviceWithUserPasswordTask {
override suspend fun execute(params: DeleteDeviceWithUserPasswordTask.Params) {
return executeRequest(globalErrorReceiver) {
apiCall = cryptoApi.deleteDevice(params.deviceId,
DeleteDeviceParams(
auth = UserPasswordAuth(
type = LoginFlowTypes.PASSWORD,
session = params.authSession,
user = userId,
password = params.password
).asMap()
)
)
}
}
}

View File

@ -126,11 +126,16 @@ internal class DefaultInitializeCrossSigningTask @Inject constructor(
uploadSigningKeysTask.execute(uploadSigningKeysParams)
} catch (failure: Throwable) {
if (params.interactiveAuthInterceptor == null
|| !handleUIA(failure, params.interactiveAuthInterceptor) { authUpdate ->
uploadSigningKeysTask.execute(uploadSigningKeysParams.copy(userAuthParam = authUpdate))
}) {
|| !handleUIA(
failure = failure,
interceptor = params.interactiveAuthInterceptor,
retryBlock = { authUpdate ->
uploadSigningKeysTask.execute(uploadSigningKeysParams.copy(userAuthParam = authUpdate))
}
)
) {
Timber.d("## UIA: propagate failure")
throw failure
throw failure
}
}

View File

@ -16,10 +16,9 @@
package org.matrix.android.sdk.internal.session.account
import org.matrix.android.sdk.api.auth.UIABaseAuth
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.internal.auth.registration.handleUIA
import org.matrix.android.sdk.api.auth.UIABaseAuth
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.cleanup.CleanupSession
@ -30,8 +29,8 @@ import javax.inject.Inject
internal interface DeactivateAccountTask : Task<DeactivateAccountTask.Params, Unit> {
data class Params(
val userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor,
val eraseAllData: Boolean,
val userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor,
val userAuthParam: UIABaseAuth? = null
)
}
@ -39,7 +38,6 @@ internal interface DeactivateAccountTask : Task<DeactivateAccountTask.Params, Un
internal class DefaultDeactivateAccountTask @Inject constructor(
private val accountAPI: AccountAPI,
private val globalErrorReceiver: GlobalErrorReceiver,
@UserId private val userId: String,
private val identityDisconnectTask: IdentityDisconnectTask,
private val cleanupSession: CleanupSession
) : DeactivateAccountTask {
@ -47,23 +45,33 @@ internal class DefaultDeactivateAccountTask @Inject constructor(
override suspend fun execute(params: DeactivateAccountTask.Params) {
val deactivateAccountParams = DeactivateAccountParams.create(params.userAuthParam, params.eraseAllData)
try {
val canCleanup = try {
executeRequest<Unit>(globalErrorReceiver) {
apiCall = accountAPI.deactivate(deactivateAccountParams)
}
true
} catch (throwable: Throwable) {
if (!handleUIA(throwable, params.userInteractiveAuthInterceptor) { auth ->
execute(params.copy(userAuthParam = auth))
}
if (!handleUIA(
failure = throwable,
interceptor = params.userInteractiveAuthInterceptor,
retryBlock = { authUpdate ->
execute(params.copy(userAuthParam = authUpdate))
}
)
) {
Timber.d("## UIA: propagate failure")
throw throwable
throw throwable
} else {
false
}
}
// Logout from identity server if any, ignoring errors
runCatching { identityDisconnectTask.execute(Unit) }
.onFailure { Timber.w(it, "Unable to disconnect identity server") }
cleanupSession.handle()
if (canCleanup) {
// Logout from identity server if any, ignoring errors
runCatching { identityDisconnectTask.execute(Unit) }
.onFailure { Timber.w(it, "Unable to disconnect identity server") }
cleanupSession.handle()
}
}
}

View File

@ -27,7 +27,7 @@ internal class DefaultAccountService @Inject constructor(private val changePassw
changePasswordTask.execute(ChangePasswordTask.Params(password, newPassword))
}
override suspend fun deactivateAccount(userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, eraseAllData: Boolean) {
deactivateAccountTask.execute(DeactivateAccountTask.Params(userInteractiveAuthInterceptor, eraseAllData))
override suspend fun deactivateAccount(eraseAllData: Boolean, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) {
deactivateAccountTask.execute(DeactivateAccountTask.Params(eraseAllData, userInteractiveAuthInterceptor))
}
}

View File

@ -26,7 +26,6 @@ import org.matrix.android.sdk.api.auth.UIABaseAuth
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.task.Task
@ -47,11 +46,12 @@ internal class DefaultFinalizeAddingThreePidTask @Inject constructor(
private val profileAPI: ProfileAPI,
@SessionDatabase private val monarchy: Monarchy,
private val pendingThreePidMapper: PendingThreePidMapper,
@UserId private val userId: String,
private val globalErrorReceiver: GlobalErrorReceiver) : FinalizeAddingThreePidTask() {
override suspend fun execute(params: Params) {
if (params.userWantsToCancel.not()) {
val canCleanup = if (params.userWantsToCancel) {
true
} else {
// Get the required pending data
val pendingThreePids = monarchy.fetchAllMappedSync(
{ it.where(PendingThreePidEntity::class.java) },
@ -69,21 +69,30 @@ internal class DefaultFinalizeAddingThreePidTask @Inject constructor(
)
apiCall = profileAPI.finalizeAddThreePid(body)
}
true
} catch (throwable: Throwable) {
if (params.userInteractiveAuthInterceptor == null
|| !handleUIA(throwable, params.userInteractiveAuthInterceptor) { auth ->
execute(params.copy(userAuthParam = auth))
}
|| !handleUIA(
failure = throwable,
interceptor = params.userInteractiveAuthInterceptor,
retryBlock = { authUpdate ->
execute(params.copy(userAuthParam = authUpdate))
}
)
) {
Timber.d("## UIA: propagate failure")
throw throwable.toRegistrationFlowResponse()
throw throwable.toRegistrationFlowResponse()
?.let { Failure.RegistrationFlowError(it) }
?: throwable
} else {
false
}
}
}
cleanupDatabase(params)
if (canCleanup) {
cleanupDatabase(params)
}
}
private suspend fun cleanupDatabase(params: Params) {

View File

@ -44,7 +44,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation "androidx.fragment:fragment-ktx:1.3.0-beta01"
implementation 'androidx.exifinterface:exifinterface:1.3.1'
implementation 'androidx.exifinterface:exifinterface:1.3.2'
// Log
implementation 'com.jakewharton.timber:timber:4.7.1'

View File

@ -12,8 +12,8 @@ kapt {
// Note: 2 digits max for each value
ext.versionMajor = 1
ext.versionMinor = 0
ext.versionPatch = 18
ext.versionMinor = 1
ext.versionPatch = 1
static def getGitTimestamp() {
def cmd = 'git show -s --format=%ct'
@ -287,21 +287,21 @@ android {
dependencies {
def epoxy_version = '4.1.0'
def epoxy_version = '4.4.0'
def fragment_version = '1.3.0-beta01'
def arrow_version = "0.8.2"
def markwon_version = '4.1.2'
def big_image_viewer_version = '1.6.2'
def glide_version = '4.11.0'
def big_image_viewer_version = '1.7.0'
def glide_version = '4.12.0'
def moshi_version = '1.11.0'
def daggerVersion = '2.31'
def autofill_version = "1.0.0"
def daggerVersion = '2.32'
def autofill_version = "1.1.0"
def work_version = '2.4.0'
def arch_version = '2.1.0'
def lifecycle_version = '2.2.0'
// Tests
def kluent_version = '1.61'
def kluent_version = '1.65'
def androidxTest_version = '1.3.0'
def espresso_version = '3.3.0'
@ -320,12 +320,12 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation "androidx.fragment:fragment-ktx:$fragment_version"
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation "androidx.sharetarget:sharetarget:1.0.0"
implementation "androidx.sharetarget:sharetarget:1.1.0"
implementation 'androidx.core:core-ktx:1.3.2'
implementation "androidx.media:media:1.2.1"
implementation "org.threeten:threetenbp:1.4.0:no-tzdb"
implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.7.0"
implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.9.0"
implementation "com.squareup.moshi:moshi-adapters:$moshi_version"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
@ -338,15 +338,15 @@ dependencies {
implementation 'com.facebook.stetho:stetho:1.5.1'
// Phone number https://github.com/google/libphonenumber
implementation 'com.googlecode.libphonenumber:libphonenumber:8.10.23'
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.18'
// rx
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation 'com.jakewharton.rxrelay2:rxrelay:2.1.1'
// RXBinding
implementation 'com.jakewharton.rxbinding3:rxbinding:3.0.0'
implementation 'com.jakewharton.rxbinding3:rxbinding-appcompat:3.0.0'
implementation 'com.jakewharton.rxbinding3:rxbinding:3.1.0'
implementation 'com.jakewharton.rxbinding3:rxbinding-appcompat:3.1.0'
implementation 'com.jakewharton.rxbinding3:rxbinding-material:3.0.0'
implementation("com.airbnb.android:epoxy:$epoxy_version")
@ -369,11 +369,11 @@ dependencies {
// UI
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
implementation 'com.google.android.material:material:1.3.0-alpha04'
implementation 'com.google.android.material:material:1.3.0'
implementation 'me.gujun.android:span:1.7'
implementation "io.noties.markwon:core:$markwon_version"
implementation "io.noties.markwon:html:$markwon_version"
implementation 'com.googlecode.htmlcompressor:htmlcompressor:1.4'
implementation 'com.googlecode.htmlcompressor:htmlcompressor:1.5.2'
implementation 'me.saket:better-link-movement-method:2.2.0'
implementation 'com.google.android:flexbox:1.1.1'
implementation "androidx.autofill:autofill:$autofill_version"
@ -384,7 +384,7 @@ dependencies {
implementation 'androidx.browser:browser:1.2.0'
// Passphrase strength helper
implementation 'com.nulab-inc:zxcvbn:1.2.7'
implementation 'com.nulab-inc:zxcvbn:1.4.0'
//Alerter
implementation 'com.tapadoo.android:alerter:5.1.2'
@ -412,14 +412,14 @@ dependencies {
implementation 'me.leolin:ShortcutBadger:1.1.22@aar'
// Chat effects
implementation 'nl.dionsegijn:konfetti:1.2.5'
implementation 'nl.dionsegijn:konfetti:1.2.6'
implementation 'com.github.jetradarmobile:android-snowfall:1.2.0'
// DI
implementation "com.google.dagger:dagger:$daggerVersion"
kapt "com.google.dagger:dagger-compiler:$daggerVersion"
// gplay flavor only
gplayImplementation('com.google.firebase:firebase-messaging:21.0.0') {
gplayImplementation('com.google.firebase:firebase-messaging:21.0.1') {
exclude group: 'com.google.firebase', module: 'firebase-core'
exclude group: 'com.google.firebase', module: 'firebase-analytics'
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
@ -442,7 +442,7 @@ dependencies {
// QR-code
// Stick to 3.3.3 because of https://github.com/zxing/zxing/issues/1170
implementation 'com.google.zxing:core:3.3.3'
implementation 'com.google.zxing:core:3.4.1'
implementation 'me.dm7.barcodescanner:zxing:1.9.13'
// Emoji Keyboard
@ -452,7 +452,7 @@ dependencies {
implementation 'im.dlg:android-dialer:1.2.5'
// TESTS
testImplementation 'junit:junit:4.13'
testImplementation 'junit:junit:4.13.2'
testImplementation "org.amshove.kluent:kluent-android:$kluent_version"
// Plant Timber tree for test
testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'

View File

@ -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)

View File

@ -113,7 +113,7 @@ class ReAuthActivity : SimpleFragmentActivity(), ReAuthViewModel.Factory {
override fun onResume() {
super.onResume()
// It's the only way we have to know if sso falback flow was successful
// It's the only way we have to know if sso fallback flow was successful
withState(sharedViewModel) {
if (it.ssoFallbackPageWasShown) {
Timber.d("## UIA ssoFallbackPageWasShown tentative success")

View File

@ -55,6 +55,7 @@ import org.matrix.android.sdk.internal.util.awaitCallback
import java.io.OutputStream
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
class BootstrapSharedViewModel @AssistedInject constructor(
@Assisted initialState: BootstrapViewState,
@ -421,7 +422,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
_viewEvents.post(BootstrapViewEvents.RequestReAuth(flowResponse, errCode))
}
else -> {
promise.resumeWith(Result.failure(UnsupportedOperationException()))
promise.resumeWithException(UnsupportedOperationException())
}
}
}

View File

@ -52,6 +52,7 @@ import org.matrix.android.sdk.rx.rx
import timber.log.Timber
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
class HomeActivityViewModel @AssistedInject constructor(
@Assisted initialState: HomeActivityViewState,
@ -228,7 +229,7 @@ class HomeActivityViewModel @AssistedInject constructor(
)
)
} else {
promise.resumeWith(Result.failure(Exception("Cannot silently initialize cross signing, UIA missing")))
promise.resumeWithException(Exception("Cannot silently initialize cross signing, UIA missing"))
}
}
},

View File

@ -19,6 +19,7 @@ package im.vector.app.features.login
import android.content.Context
import android.net.Uri
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
@ -27,8 +28,8 @@ import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.extensions.configureAndStart
@ -37,7 +38,8 @@ import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.ensureTrailingSlash
import im.vector.app.features.signout.soft.SoftLogoutActivity
import org.matrix.android.sdk.api.MatrixCallback
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
@ -51,7 +53,6 @@ import org.matrix.android.sdk.api.auth.registration.Stage
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.util.Cancelable
import timber.log.Timber
import java.util.concurrent.CancellationException
@ -117,7 +118,12 @@ class LoginViewModel @AssistedInject constructor(
private var loginConfig: LoginConfig? = null
private var currentTask: Cancelable? = null
private var currentJob: Job? = null
set(value) {
// Cancel any previous Job
field?.cancel()
field = value
}
override fun handle(action: LoginAction) {
when (action) {
@ -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? {

View File

@ -0,0 +1,64 @@
/*
* Copyright (c) 2021 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.
* 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 im.vector.app.features.rageshake
import java.io.PrintWriter
import java.io.StringWriter
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.TimeZone
import java.util.logging.Formatter
import java.util.logging.LogRecord
class LogFormatter : Formatter() {
override fun format(r: LogRecord): String {
if (!mIsTimeZoneSet) {
DATE_FORMAT.timeZone = TimeZone.getTimeZone("UTC")
mIsTimeZoneSet = true
}
val thrown = r.thrown
if (thrown != null) {
val sw = StringWriter()
val pw = PrintWriter(sw)
sw.write(r.message)
sw.write(LINE_SEPARATOR)
thrown.printStackTrace(pw)
pw.flush()
return sw.toString()
} else {
val b = StringBuilder()
val date = DATE_FORMAT.format(Date(r.millis))
b.append(date)
b.append("Z ")
b.append(r.message)
b.append(LINE_SEPARATOR)
return b.toString()
}
}
companion object {
private val LINE_SEPARATOR = System.getProperty("line.separator") ?: "\n"
// private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
private val DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss*SSSZZZZ", Locale.US)
private var mIsTimeZoneSet = false
}
}

View File

@ -22,45 +22,41 @@ import im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.tryOrNull
import timber.log.Timber
import java.io.File
import java.io.PrintWriter
import java.io.StringWriter
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.TimeZone
import java.util.logging.FileHandler
import java.util.logging.Formatter
import java.util.logging.Level
import java.util.logging.LogRecord
import java.util.logging.Logger
import javax.inject.Inject
import javax.inject.Singleton
private const val SIZE_20MB = 20 * 1024 * 1024
private const val SIZE_50MB = 50 * 1024 * 1024
@Singleton
class VectorFileLogger @Inject constructor(val context: Context, private val vectorPreferences: VectorPreferences) : Timber.Tree() {
class VectorFileLogger @Inject constructor(
context: Context,
private val vectorPreferences: VectorPreferences
) : Timber.Tree() {
private val maxLogSizeByte: Int
private val logRotationCount: Int
companion object {
private const val SIZE_20MB = 20 * 1024 * 1024
private const val SIZE_50MB = 50 * 1024 * 1024
}
init {
if (vectorPreferences.labAllowedExtendedLogging()) {
maxLogSizeByte = SIZE_50MB
logRotationCount = 15
} else {
maxLogSizeByte = SIZE_20MB
logRotationCount = 7
private val maxLogSizeByte = if (vectorPreferences.labAllowedExtendedLogging()) SIZE_50MB else SIZE_20MB
private val logRotationCount = if (vectorPreferences.labAllowedExtendedLogging()) 15 else 7
private val logger = Logger.getLogger(context.packageName).apply {
tryOrNull {
useParentHandlers = false
level = Level.ALL
}
}
private val sLogger = Logger.getLogger("im.vector.app")
private var sFileHandler: FileHandler? = null
private var sCacheDirectory: File? = null
private var sFileName = "elementLogs"
private val fileHandler: FileHandler?
private val cacheDirectory = File(context.cacheDir, "logs")
private var fileNamePrefix = "logs"
private val prioPrefixes = mapOf(
Log.VERBOSE to "V/ ",
@ -72,24 +68,29 @@ class VectorFileLogger @Inject constructor(val context: Context, private val vec
)
init {
val logsDirectoryFile = context.cacheDir.absolutePath + "/logs"
setLogDirectory(File(logsDirectoryFile))
try {
if (sCacheDirectory != null) {
sFileHandler = FileHandler(sCacheDirectory!!.absolutePath + "/" + sFileName + ".%g.txt", maxLogSizeByte, logRotationCount)
sFileHandler?.formatter = LogFormatter()
sLogger.useParentHandlers = false
sLogger.level = Level.ALL
sFileHandler?.let { sLogger.addHandler(it) }
}
} catch (e: Throwable) {
Timber.e(e, "Failed to initialize FileLogger")
if (!cacheDirectory.exists()) {
cacheDirectory.mkdirs()
}
for (i in 0..15) {
val file = File(cacheDirectory, "elementLogs.$i.txt")
tryOrNull { file.delete() }
}
fileHandler = tryOrNull("Failed to initialize FileLogger") {
FileHandler(
cacheDirectory.absolutePath + "/" + fileNamePrefix + ".%g.txt",
maxLogSizeByte,
logRotationCount
)
.also { it.formatter = LogFormatter() }
.also { logger.addHandler(it) }
}
}
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
fileHandler ?: return
GlobalScope.launch(Dispatchers.IO) {
if (sFileHandler == null) return@launch
if (skipLog(priority)) return@launch
if (t != null) {
logToFile(t)
@ -107,84 +108,22 @@ class VectorFileLogger @Inject constructor(val context: Context, private val vec
}
}
/**
* Set the directory to put log files.
*
* @param cacheDir The directory, usually [android.content.ContextWrapper.getCacheDir]
*/
private fun setLogDirectory(cacheDir: File) {
if (!cacheDir.exists()) {
cacheDir.mkdirs()
}
sCacheDirectory = cacheDir
}
/**
* Adds our own log files to the provided list of files.
*
* @param files The list of files to add to.
* @return The same list with more files added.
* @return The list of files with logs.
*/
fun getLogFiles(): List<File> {
val files = ArrayList<File>()
try {
// reported by GA
if (null != sFileHandler) {
sFileHandler!!.flush()
val absPath = sCacheDirectory?.absolutePath ?: return emptyList()
for (i in 0..logRotationCount) {
val filepath = "$absPath/$sFileName.$i.txt"
val file = File(filepath)
if (file.exists()) {
files.add(file)
return tryOrNull("## getLogFiles() failed") {
fileHandler
?.flush()
?.let { 0 until logRotationCount }
?.mapNotNull { index ->
File(cacheDirectory, "$fileNamePrefix.$index.txt")
.takeIf { it.exists() }
}
}
}
} catch (e: Exception) {
Timber.e(e, "## addLogFiles() failed")
}
return files
}
class LogFormatter : Formatter() {
override fun format(r: LogRecord): String {
if (!mIsTimeZoneSet) {
DATE_FORMAT.timeZone = TimeZone.getTimeZone("UTC")
mIsTimeZoneSet = true
}
val thrown = r.thrown
if (thrown != null) {
val sw = StringWriter()
val pw = PrintWriter(sw)
sw.write(r.message)
sw.write(LINE_SEPARATOR)
thrown.printStackTrace(pw)
pw.flush()
return sw.toString()
} else {
val b = StringBuilder()
val date = DATE_FORMAT.format(Date(r.millis))
b.append(date)
b.append("Z ")
b.append(r.message)
b.append(LINE_SEPARATOR)
return b.toString()
}
}
companion object {
private val LINE_SEPARATOR = System.getProperty("line.separator") ?: "\n"
// private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
private val DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss*SSSZZZZ", Locale.US)
private var mIsTimeZoneSet = false
}
.orEmpty()
}
/**
@ -193,20 +132,15 @@ class VectorFileLogger @Inject constructor(val context: Context, private val vec
* @param throwable the throwable to log
*/
private fun logToFile(throwable: Throwable?) {
if (null == sCacheDirectory || throwable == null) {
return
}
throwable ?: return
val errors = StringWriter()
throwable.printStackTrace(PrintWriter(errors))
sLogger.info(errors.toString())
logger.info(errors.toString())
}
private fun logToFile(level: String, tag: String, content: String) {
if (null == sCacheDirectory) {
return
}
val b = StringBuilder()
b.append(Thread.currentThread().id)
b.append(" ")
@ -215,6 +149,6 @@ class VectorFileLogger @Inject constructor(val context: Context, private val vec
b.append(tag)
b.append(": ")
b.append(content)
sLogger.info(b.toString())
logger.info(b.toString())
}
}

View File

@ -38,6 +38,7 @@ import org.matrix.android.sdk.api.auth.UserPasswordAuth
import timber.log.Timber
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
data class DeactivateAccountViewState(
val passwordShown: Boolean = false
@ -64,7 +65,7 @@ class DeactivateAccountViewModel @AssistedInject constructor(@Assisted private v
if (pendingAuth != null) {
uiaContinuation?.resume(pendingAuth!!)
} else {
uiaContinuation?.resumeWith(Result.failure((IllegalArgumentException())))
uiaContinuation?.resumeWithException(IllegalArgumentException())
}
}
is DeactivateAccountAction.PasswordAuthDone -> {
@ -79,7 +80,7 @@ class DeactivateAccountViewModel @AssistedInject constructor(@Assisted private v
}
DeactivateAccountAction.ReAuthCancelled -> {
Timber.d("## UIA - Reauth cancelled")
uiaContinuation?.resumeWith(Result.failure((Exception())))
uiaContinuation?.resumeWithException(Exception())
uiaContinuation = null
pendingAuth = null
}
@ -98,13 +99,15 @@ class DeactivateAccountViewModel @AssistedInject constructor(@Assisted private v
viewModelScope.launch {
val event = try {
session.deactivateAccount(
action.eraseAllData,
object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
_viewEvents.post(DeactivateAccountViewEvents.RequestReAuth(flowResponse, errCode))
pendingAuth = DefaultBaseAuth(session = flowResponse.session)
uiaContinuation = promise
}
}, action.eraseAllData)
}
)
DeactivateAccountViewEvents.Done
} catch (failure: Exception) {
if (failure.isInvalidUIAAuth()) {

View File

@ -49,6 +49,7 @@ import org.matrix.android.sdk.rx.rx
import timber.log.Timber
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
class CrossSigningSettingsViewModel @AssistedInject constructor(
@Assisted private val initialState: CrossSigningSettingsViewState,
@ -130,7 +131,7 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(
if (pendingAuth != null) {
uiaContinuation?.resume(pendingAuth!!)
} else {
uiaContinuation?.resumeWith(Result.failure((IllegalArgumentException())))
uiaContinuation?.resumeWithException(IllegalArgumentException())
}
}
is CrossSigningSettingsAction.PasswordAuthDone -> {
@ -146,7 +147,7 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(
CrossSigningSettingsAction.ReAuthCancelled -> {
Timber.d("## UIA - Reauth cancelled")
_viewEvents.post(CrossSigningSettingsViewEvents.HideModalWaitingView)
uiaContinuation?.resumeWith(Result.failure((Exception())))
uiaContinuation?.resumeWithException(Exception())
uiaContinuation = null
pendingAuth = null
}

View File

@ -64,6 +64,7 @@ import java.util.concurrent.TimeUnit
import javax.net.ssl.HttpsURLConnection
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
data class DevicesViewState(
val myDeviceId: String = "",
@ -217,7 +218,7 @@ class DevicesViewModel @AssistedInject constructor(
if (pendingAuth != null) {
uiaContinuation?.resume(pendingAuth!!)
} else {
uiaContinuation?.resumeWith(Result.failure((IllegalArgumentException())))
uiaContinuation?.resumeWithException(IllegalArgumentException())
}
Unit
}
@ -235,7 +236,7 @@ class DevicesViewModel @AssistedInject constructor(
DevicesAction.ReAuthCancelled -> {
Timber.d("## UIA - Reauth cancelled")
// _viewEvents.post(DevicesViewEvents.Loading)
uiaContinuation?.resumeWith(Result.failure((Exception())))
uiaContinuation?.resumeWithException(Exception())
uiaContinuation = null
pendingAuth = null
}

View File

@ -46,6 +46,7 @@ import org.matrix.android.sdk.rx.rx
import timber.log.Timber
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
class ThreePidsSettingsViewModel @AssistedInject constructor(
@Assisted initialState: ThreePidsSettingsViewState,
@ -140,7 +141,7 @@ class ThreePidsSettingsViewModel @AssistedInject constructor(
if (pendingAuth != null) {
uiaContinuation?.resume(pendingAuth!!)
} else {
uiaContinuation?.resumeWith(Result.failure((IllegalArgumentException())))
uiaContinuation?.resumeWithException(IllegalArgumentException())
}
}
is ThreePidsSettingsAction.PasswordAuthDone -> {
@ -155,7 +156,7 @@ class ThreePidsSettingsViewModel @AssistedInject constructor(
}
ThreePidsSettingsAction.ReAuthCancelled -> {
Timber.d("## UIA - Reauth cancelled")
uiaContinuation?.resumeWith(Result.failure((Exception())))
uiaContinuation?.resumeWithException(Exception())
uiaContinuation = null
pendingAuth = null
}

View File

@ -25,19 +25,17 @@ import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.extensions.hasUnsavedKeys
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.login.LoginMode
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.util.Cancelable
import timber.log.Timber
/**
@ -76,54 +74,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()
}
}

View File

@ -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"