Merge pull request #1961 from vector-im/feature/bma/keyBackupIteration

Key backup iteration
This commit is contained in:
Benoit Marty
2023-12-06 11:18:50 +01:00
committed by GitHub
7 changed files with 86 additions and 11 deletions

View File

@@ -89,7 +89,7 @@ class SecureBackupEnterRecoveryKeyPresenter @Inject constructor(
action: MutableState<Async<Unit>>
) = launch {
suspend {
encryptionService.fixRecoveryIssues(recoveryKey).getOrThrow()
encryptionService.recover(recoveryKey).getOrThrow()
}.runCatchingUpdatingState(action)
}
}

View File

@@ -73,7 +73,7 @@ class SecureBackupEnterRecoveryKeyPresenterTest {
inProgress = false,
)
)
encryptionService.givenFixRecoveryIssuesFailure(AN_EXCEPTION)
encryptionService.givenRecoverFailure(AN_EXCEPTION)
withRecoveryKeyState.eventSink(SecureBackupEnterRecoveryKeyEvents.Submit)
val loadingState = awaitItem()
assertThat(loadingState.submitAction).isEqualTo(Async.Loading<Unit>())
@@ -85,7 +85,7 @@ class SecureBackupEnterRecoveryKeyPresenterTest {
val clearedState = awaitItem()
assertThat(clearedState.submitAction).isEqualTo(Async.Uninitialized)
assertThat(clearedState.isSubmitEnabled).isTrue()
encryptionService.givenFixRecoveryIssuesFailure(null)
encryptionService.givenRecoverFailure(null)
clearedState.eventSink(SecureBackupEnterRecoveryKeyEvents.Submit)
val loadingState2 = awaitItem()
assertThat(loadingState2.submitAction).isEqualTo(Async.Loading<Unit>())

View File

@@ -43,9 +43,9 @@ interface EncryptionService {
suspend fun doesBackupExistOnServer(): Result<Boolean>
/**
* Note: accept bot recoveryKey and passphrase.
* Note: accept both recoveryKey and passphrase.
*/
suspend fun fixRecoveryIssues(recoveryKey: String): Result<Unit>
suspend fun recover(recoveryKey: String): Result<Unit>
/**
* Wait for backup upload steady state.

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) 2023 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 io.element.android.libraries.matrix.api.encryption
import io.element.android.libraries.matrix.api.exception.ClientException
sealed class RecoveryException(message: String) : Exception(message) {
class SecretStorage(message: String) : RecoveryException(message)
data object BackupExistsOnServer : RecoveryException("BackupExistsOnServer")
data class Client(val exception: ClientException) : RecoveryException(exception.message ?: "Unknown error")
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (c) 2023 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 io.element.android.libraries.matrix.impl.encryption
import io.element.android.libraries.matrix.api.encryption.RecoveryException
import io.element.android.libraries.matrix.api.exception.ClientException
import io.element.android.libraries.matrix.impl.exception.mapClientException
import org.matrix.rustcomponents.sdk.RecoveryException as RustRecoveryException
fun Throwable.mapRecoveryException(): RecoveryException {
return when (this) {
is RustRecoveryException.SecretStorage -> RecoveryException.SecretStorage(
message = errorMessage
)
is RustRecoveryException.BackupExistsOnServer -> RecoveryException.BackupExistsOnServer
is RustRecoveryException.Client -> RecoveryException.Client(
source.mapClientException()
)
else -> RecoveryException.Client(
ClientException.Other("Unknown error")
)
}
}

View File

@@ -17,6 +17,7 @@
package io.element.android.libraries.matrix.impl.encryption
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.extensions.mapFailure
import io.element.android.libraries.matrix.api.encryption.BackupState
import io.element.android.libraries.matrix.api.encryption.BackupUploadState
import io.element.android.libraries.matrix.api.encryption.EnableRecoveryProgress
@@ -110,6 +111,8 @@ internal class RustEncryptionService(
override suspend fun enableBackups(): Result<Unit> = withContext(dispatchers.io) {
runCatching {
service.enableBackups()
}.mapFailure {
it.mapRecoveryException()
}
}
@@ -127,6 +130,8 @@ internal class RustEncryptionService(
)
// enableRecovery returns the encryption key, but we read it from the state flow
.let { }
}.mapFailure {
it.mapRecoveryException()
}
}
@@ -164,24 +169,32 @@ internal class RustEncryptionService(
override suspend fun disableRecovery(): Result<Unit> = withContext(dispatchers.io) {
runCatching {
service.disableRecovery()
}.mapFailure {
it.mapRecoveryException()
}
}
override suspend fun isLastDevice(): Result<Boolean> = withContext(dispatchers.io) {
runCatching {
service.isLastDevice()
}.mapFailure {
it.mapRecoveryException()
}
}
override suspend fun resetRecoveryKey(): Result<String> = withContext(dispatchers.io) {
runCatching {
service.resetRecoveryKey()
}.mapFailure {
it.mapRecoveryException()
}
}
override suspend fun fixRecoveryIssues(recoveryKey: String): Result<Unit> = withContext(dispatchers.io) {
override suspend fun recover(recoveryKey: String): Result<Unit> = withContext(dispatchers.io) {
runCatching {
service.recover(recoveryKey)
}.mapFailure {
it.mapRecoveryException()
}
}
}

View File

@@ -33,7 +33,7 @@ class FakeEncryptionService : EncryptionService {
override val enableRecoveryProgressStateFlow: MutableStateFlow<EnableRecoveryProgress> = MutableStateFlow(EnableRecoveryProgress.Starting)
private var waitForBackupUploadSteadyStateFlow: Flow<BackupUploadState> = flowOf()
private var fixRecoveryIssuesFailure: Exception? = null
private var recoverFailure: Exception? = null
private var doesBackupExistOnServerResult: Result<Boolean> = Result.success(true)
override suspend fun enableBackups(): Result<Unit> = simulateLongTask {
@@ -44,8 +44,8 @@ class FakeEncryptionService : EncryptionService {
disableRecoveryFailure = exception
}
fun givenFixRecoveryIssuesFailure(exception: Exception?) {
fixRecoveryIssuesFailure = exception
fun givenRecoverFailure(exception: Exception?) {
recoverFailure = exception
}
override suspend fun disableRecovery(): Result<Unit> = simulateLongTask {
@@ -61,8 +61,8 @@ class FakeEncryptionService : EncryptionService {
return doesBackupExistOnServerResult
}
override suspend fun fixRecoveryIssues(recoveryKey: String): Result<Unit> = simulateLongTask {
fixRecoveryIssuesFailure?.let { return Result.failure(it) }
override suspend fun recover(recoveryKey: String): Result<Unit> = simulateLongTask {
recoverFailure?.let { return Result.failure(it) }
return Result.success(Unit)
}