From c9a7fa88b61999d3f34a7b3cfc9e8419567216fc Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 7 Nov 2023 17:27:50 +0100 Subject: [PATCH] RecoveryKey: remove space if a recovery key is pasted in the TextField --- .../SecureBackupEnterRecoveryKeyPresenter.kt | 11 ++++- .../impl/tools/RecoveryKeyTools.kt | 29 ++++++++++++ ...cureBackupEnterRecoveryKeyPresenterTest.kt | 2 + .../impl/tools/RecoveryKeyToolsTest.kt | 46 +++++++++++++++++++ 4 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyTools.kt create mode 100644 features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyToolsTest.kt diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenter.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenter.kt index fd32c0066d..bec4dd9217 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenter.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenter.kt @@ -26,6 +26,7 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyUserStory import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyViewState +import io.element.android.features.securebackup.impl.tools.RecoveryKeyTools import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState @@ -36,6 +37,7 @@ import javax.inject.Inject class SecureBackupEnterRecoveryKeyPresenter @Inject constructor( private val encryptionService: EncryptionService, + private val recoveryKeyTools: RecoveryKeyTools, ) : Presenter { @Composable @@ -54,7 +56,14 @@ class SecureBackupEnterRecoveryKeyPresenter @Inject constructor( submitAction.value = Async.Uninitialized } is SecureBackupEnterRecoveryKeyEvents.OnRecoveryKeyChange -> { - recoveryKey = event.recoveryKey.replace("\\s+".toRegex(), "") + val previousRecoveryKey = recoveryKey + recoveryKey = if (previousRecoveryKey.isEmpty() && recoveryKeyTools.isRecoveryKeyFormatValid(event.recoveryKey)) { + // A Recovery key has been entered, remove the spaces for a better rendering + event.recoveryKey.replace("\\s+".toRegex(), "") + } else { + // Keep the recovery key as entered by the user. May contains spaces. + event.recoveryKey + } } SecureBackupEnterRecoveryKeyEvents.Submit -> { // No need to remove the spaces, the SDK will do it. diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyTools.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyTools.kt new file mode 100644 index 0000000000..4bd32eccdf --- /dev/null +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyTools.kt @@ -0,0 +1,29 @@ +/* + * 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.features.securebackup.impl.tools + +import javax.inject.Inject + +private const val RECOVERY_KEY_LENGTH = 48 +private const val BASE_58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" + +class RecoveryKeyTools @Inject constructor() { + fun isRecoveryKeyFormatValid(recoveryKey: String): Boolean { + val recoveryKeyWithoutSpace = recoveryKey.replace("\\s+".toRegex(), "") + return recoveryKeyWithoutSpace.length == RECOVERY_KEY_LENGTH && recoveryKeyWithoutSpace.all { BASE_58_ALPHABET.contains(it) } + } +} diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenterTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenterTest.kt index bcdbb6bbe7..d34f869f6e 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenterTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenterTest.kt @@ -22,6 +22,7 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyUserStory import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyViewState +import io.element.android.features.securebackup.impl.tools.RecoveryKeyTools import io.element.android.libraries.architecture.Async import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.test.AN_EXCEPTION @@ -99,5 +100,6 @@ class SecureBackupEnterRecoveryKeyPresenterTest { encryptionService: EncryptionService = FakeEncryptionService(), ) = SecureBackupEnterRecoveryKeyPresenter( encryptionService = encryptionService, + recoveryKeyTools = RecoveryKeyTools(), ) } diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyToolsTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyToolsTest.kt new file mode 100644 index 0000000000..4629211fc9 --- /dev/null +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyToolsTest.kt @@ -0,0 +1,46 @@ +/* + * 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.features.securebackup.impl.tools + +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +class RecoveryKeyToolsTest { + + @Test + fun `isRecoveryKeyFormatValid return false for invalid key`() { + val sut = RecoveryKeyTools() + assertThat(sut.isRecoveryKeyFormatValid("")).isFalse() + // Wrong size + assertThat(sut.isRecoveryKeyFormatValid("abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabc")).isFalse() + assertThat(sut.isRecoveryKeyFormatValid("abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcda")).isFalse() + // Wrong alphabet 0 + assertThat(sut.isRecoveryKeyFormatValid("0bcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd")).isFalse() + // Wrong alphabet O + assertThat(sut.isRecoveryKeyFormatValid("Obcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd")).isFalse() + // Wrong alphabet l + assertThat(sut.isRecoveryKeyFormatValid("lbcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd")).isFalse() + } + + @Test + fun `isRecoveryKeyFormatValid return true for valid key`() { + val sut = RecoveryKeyTools() + assertThat(sut.isRecoveryKeyFormatValid("abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd")).isTrue() + // Spaces does not count + assertThat(sut.isRecoveryKeyFormatValid("abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd")).isTrue() + } +}