Merge pull request #2844 from vector-im/feature/bma/fix_account_deactivation

Fix account deactivation crash
This commit is contained in:
Benoit Marty 2021-02-19 13:47:30 +01:00 committed by GitHub
commit 9614d55612
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 118 additions and 141 deletions

View File

@ -14,6 +14,7 @@ Bugfix 🐛:
- VoIP : fix audio devices output - VoIP : fix audio devices output
- Fix crash after initial sync on Dendrite - Fix crash after initial sync on Dendrite
- Fix crash reported by PlayStore (#2707) - Fix crash reported by PlayStore (#2707)
- Fix crash when deactivating an account
Translations 🗣: Translations 🗣:
- -

View File

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

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 * Try to convert to a RegistrationFlowResponse. Return null in the cases it's not possible
*/ */
fun Throwable.toRegistrationFlowResponse(): RegistrationFlowResponse? { fun Throwable.toRegistrationFlowResponse(): RegistrationFlowResponse? {
return if (this is Failure.OtherServerError && this.httpCode == 401) { return if (this is Failure.OtherServerError && httpCode == 401) {
tryOrNull { tryOrNull {
MoshiProvider.providesMoshi() MoshiProvider.providesMoshi()
.adapter(RegistrationFlowResponse::class.java) .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) // 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( RegistrationFlowResponse(
flows = this.error.flows, flows = error.flows,
session = this.error.session, session = error.session,
completedStages = this.error.completedStages, completedStages = error.completedStages,
params = this.error.params params = error.params
) )
} else null } else {
null
}
} else { } else {
null null
} }

View File

@ -27,7 +27,8 @@ interface AccountService {
* @param password Current password. * @param password Current password.
* @param newPassword New password * @param newPassword New password
*/ */
suspend fun changePassword(password: String, newPassword: String) suspend fun changePassword(password: String,
newPassword: String)
/** /**
* Deactivate the account. * 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 * 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. * 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 * @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 * 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 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 getCryptoVersion(context: Context, longFormat: Boolean): String
fun isCryptoEnabled(): Boolean fun isCryptoEnabled(): Boolean

View File

@ -16,14 +16,25 @@
package org.matrix.android.sdk.internal.auth.registration 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.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse
import org.matrix.android.sdk.api.auth.UIABaseAuth
import timber.log.Timber import timber.log.Timber
import kotlin.coroutines.suspendCoroutine 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}") Timber.d("## UIA: check error ${failure.message}")
val flowResponse = failure.toRegistrationFlowResponse() val flowResponse = failure.toRegistrationFlowResponse()
?: return false.also { ?: return false.also {
@ -38,16 +49,16 @@ internal suspend fun handleUIA(failure: Throwable, interceptor: UserInteractiveA
suspendCoroutine<UIABaseAuth> { continuation -> suspendCoroutine<UIABaseAuth> { continuation ->
interceptor.performStage(flowResponse, (failure as? Failure.ServerError)?.error?.code, continuation) interceptor.performStage(flowResponse, (failure as? Failure.ServerError)?.error?.code, continuation)
} }
} catch (failure: Throwable) { } catch (failure2: Throwable) {
Timber.w(failure, "## UIA: failed to participate") Timber.w(failure2, "## UIA: failed to participate")
return false return false
} }
Timber.d("## UIA: updated auth $authUpdate") Timber.d("## UIA: updated auth")
return try { return try {
retryBlock(authUpdate) retryBlock(authUpdate)
true true
} catch (failure: Throwable) { } catch (failure3: Throwable) {
handleUIA(failure, interceptor, retryBlock) 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.ClaimOneTimeKeysForUsersDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultClaimOneTimeKeysForUsersDevice 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.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.DefaultDownloadKeysForUsers
import org.matrix.android.sdk.internal.crypto.tasks.DefaultEncryptEventTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultEncryptEventTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultGetDeviceInfoTask 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.DefaultUploadSignaturesTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultUploadSigningKeysTask 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.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.DownloadKeysForUsersTask
import org.matrix.android.sdk.internal.crypto.tasks.EncryptEventTask import org.matrix.android.sdk.internal.crypto.tasks.EncryptEventTask
import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask
@ -240,9 +238,6 @@ internal abstract class CryptoModule {
@Binds @Binds
abstract fun bindClaimOneTimeKeysForUsersDeviceTask(task: DefaultClaimOneTimeKeysForUsersDevice): ClaimOneTimeKeysForUsersDeviceTask abstract fun bindClaimOneTimeKeysForUsersDeviceTask(task: DefaultClaimOneTimeKeysForUsersDevice): ClaimOneTimeKeysForUsersDeviceTask
@Binds
abstract fun bindDeleteDeviceWithUserPasswordTask(task: DefaultDeleteDeviceWithUserPasswordTask): DeleteDeviceWithUserPasswordTask
@Binds @Binds
abstract fun bindCrossSigningService(service: DefaultCrossSigningService): CrossSigningService 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.repository.WarnOnUnknownDeviceRepository
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore 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.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.GetDeviceInfoTask
import org.matrix.android.sdk.internal.crypto.tasks.GetDevicesTask import org.matrix.android.sdk.internal.crypto.tasks.GetDevicesTask
import org.matrix.android.sdk.internal.crypto.tasks.SetDeviceNameTask import org.matrix.android.sdk.internal.crypto.tasks.SetDeviceNameTask
@ -153,9 +152,8 @@ internal class DefaultCryptoService @Inject constructor(
// Repository // Repository
private val megolmEncryptionFactory: MXMegolmEncryptionFactory, private val megolmEncryptionFactory: MXMegolmEncryptionFactory,
private val olmEncryptionFactory: MXOlmEncryptionFactory, private val olmEncryptionFactory: MXOlmEncryptionFactory,
private val deleteDeviceTask: DeleteDeviceTask,
private val deleteDeviceWithUserPasswordTask: DeleteDeviceWithUserPasswordTask,
// Tasks // Tasks
private val deleteDeviceTask: DeleteDeviceTask,
private val getDevicesTask: GetDevicesTask, private val getDevicesTask: GetDevicesTask,
private val getDeviceInfoTask: GetDeviceInfoTask, private val getDeviceInfoTask: GetDeviceInfoTask,
private val setDeviceNameTask: SetDeviceNameTask, private val setDeviceNameTask: SetDeviceNameTask,
@ -217,15 +215,6 @@ internal class DefaultCryptoService @Inject constructor(
.executeBy(taskExecutor) .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 { override fun getCryptoVersion(context: Context, longFormat: Boolean): String {
return if (longFormat) olmManager.getDetailedVersion(context) else olmManager.version return if (longFormat) olmManager.getDetailedVersion(context) else olmManager.version
} }

View File

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

View File

@ -16,10 +16,9 @@
package org.matrix.android.sdk.internal.session.account 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.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.internal.auth.registration.handleUIA 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.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.cleanup.CleanupSession import org.matrix.android.sdk.internal.session.cleanup.CleanupSession
@ -30,8 +29,8 @@ import javax.inject.Inject
internal interface DeactivateAccountTask : Task<DeactivateAccountTask.Params, Unit> { internal interface DeactivateAccountTask : Task<DeactivateAccountTask.Params, Unit> {
data class Params( data class Params(
val userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor,
val eraseAllData: Boolean, val eraseAllData: Boolean,
val userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor,
val userAuthParam: UIABaseAuth? = null val userAuthParam: UIABaseAuth? = null
) )
} }
@ -39,7 +38,6 @@ internal interface DeactivateAccountTask : Task<DeactivateAccountTask.Params, Un
internal class DefaultDeactivateAccountTask @Inject constructor( internal class DefaultDeactivateAccountTask @Inject constructor(
private val accountAPI: AccountAPI, private val accountAPI: AccountAPI,
private val globalErrorReceiver: GlobalErrorReceiver, private val globalErrorReceiver: GlobalErrorReceiver,
@UserId private val userId: String,
private val identityDisconnectTask: IdentityDisconnectTask, private val identityDisconnectTask: IdentityDisconnectTask,
private val cleanupSession: CleanupSession private val cleanupSession: CleanupSession
) : DeactivateAccountTask { ) : DeactivateAccountTask {
@ -47,23 +45,33 @@ internal class DefaultDeactivateAccountTask @Inject constructor(
override suspend fun execute(params: DeactivateAccountTask.Params) { override suspend fun execute(params: DeactivateAccountTask.Params) {
val deactivateAccountParams = DeactivateAccountParams.create(params.userAuthParam, params.eraseAllData) val deactivateAccountParams = DeactivateAccountParams.create(params.userAuthParam, params.eraseAllData)
try { val canCleanup = try {
executeRequest<Unit>(globalErrorReceiver) { executeRequest<Unit>(globalErrorReceiver) {
apiCall = accountAPI.deactivate(deactivateAccountParams) apiCall = accountAPI.deactivate(deactivateAccountParams)
} }
true
} catch (throwable: Throwable) { } catch (throwable: Throwable) {
if (!handleUIA(throwable, params.userInteractiveAuthInterceptor) { auth -> if (!handleUIA(
execute(params.copy(userAuthParam = auth)) failure = throwable,
} interceptor = params.userInteractiveAuthInterceptor,
retryBlock = { authUpdate ->
execute(params.copy(userAuthParam = authUpdate))
}
)
) { ) {
Timber.d("## UIA: propagate failure") 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)) changePasswordTask.execute(ChangePasswordTask.Params(password, newPassword))
} }
override suspend fun deactivateAccount(userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, eraseAllData: Boolean) { override suspend fun deactivateAccount(eraseAllData: Boolean, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) {
deactivateAccountTask.execute(DeactivateAccountTask.Params(userInteractiveAuthInterceptor, eraseAllData)) 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.PendingThreePidEntity
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
import org.matrix.android.sdk.internal.di.SessionDatabase 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.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.task.Task
@ -47,11 +46,12 @@ internal class DefaultFinalizeAddingThreePidTask @Inject constructor(
private val profileAPI: ProfileAPI, private val profileAPI: ProfileAPI,
@SessionDatabase private val monarchy: Monarchy, @SessionDatabase private val monarchy: Monarchy,
private val pendingThreePidMapper: PendingThreePidMapper, private val pendingThreePidMapper: PendingThreePidMapper,
@UserId private val userId: String,
private val globalErrorReceiver: GlobalErrorReceiver) : FinalizeAddingThreePidTask() { private val globalErrorReceiver: GlobalErrorReceiver) : FinalizeAddingThreePidTask() {
override suspend fun execute(params: Params) { override suspend fun execute(params: Params) {
if (params.userWantsToCancel.not()) { val canCleanup = if (params.userWantsToCancel) {
true
} else {
// Get the required pending data // Get the required pending data
val pendingThreePids = monarchy.fetchAllMappedSync( val pendingThreePids = monarchy.fetchAllMappedSync(
{ it.where(PendingThreePidEntity::class.java) }, { it.where(PendingThreePidEntity::class.java) },
@ -69,21 +69,30 @@ internal class DefaultFinalizeAddingThreePidTask @Inject constructor(
) )
apiCall = profileAPI.finalizeAddThreePid(body) apiCall = profileAPI.finalizeAddThreePid(body)
} }
true
} catch (throwable: Throwable) { } catch (throwable: Throwable) {
if (params.userInteractiveAuthInterceptor == null if (params.userInteractiveAuthInterceptor == null
|| !handleUIA(throwable, params.userInteractiveAuthInterceptor) { auth -> || !handleUIA(
execute(params.copy(userAuthParam = auth)) failure = throwable,
} interceptor = params.userInteractiveAuthInterceptor,
retryBlock = { authUpdate ->
execute(params.copy(userAuthParam = authUpdate))
}
)
) { ) {
Timber.d("## UIA: propagate failure") Timber.d("## UIA: propagate failure")
throw throwable.toRegistrationFlowResponse() throw throwable.toRegistrationFlowResponse()
?.let { Failure.RegistrationFlowError(it) } ?.let { Failure.RegistrationFlowError(it) }
?: throwable ?: throwable
} else {
false
} }
} }
} }
cleanupDatabase(params) if (canCleanup) {
cleanupDatabase(params)
}
} }
private suspend fun cleanupDatabase(params: Params) { private suspend fun cleanupDatabase(params: Params) {

View File

@ -113,7 +113,7 @@ class ReAuthActivity : SimpleFragmentActivity(), ReAuthViewModel.Factory {
override fun onResume() { override fun onResume() {
super.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) { withState(sharedViewModel) {
if (it.ssoFallbackPageWasShown) { if (it.ssoFallbackPageWasShown) {
Timber.d("## UIA ssoFallbackPageWasShown tentative success") 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 java.io.OutputStream
import kotlin.coroutines.Continuation import kotlin.coroutines.Continuation
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
class BootstrapSharedViewModel @AssistedInject constructor( class BootstrapSharedViewModel @AssistedInject constructor(
@Assisted initialState: BootstrapViewState, @Assisted initialState: BootstrapViewState,
@ -421,7 +422,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
_viewEvents.post(BootstrapViewEvents.RequestReAuth(flowResponse, errCode)) _viewEvents.post(BootstrapViewEvents.RequestReAuth(flowResponse, errCode))
} }
else -> { 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 timber.log.Timber
import kotlin.coroutines.Continuation import kotlin.coroutines.Continuation
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
class HomeActivityViewModel @AssistedInject constructor( class HomeActivityViewModel @AssistedInject constructor(
@Assisted initialState: HomeActivityViewState, @Assisted initialState: HomeActivityViewState,
@ -228,7 +229,7 @@ class HomeActivityViewModel @AssistedInject constructor(
) )
) )
} else { } 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

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

View File

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

View File

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

View File

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