From eb82c245ad87cb72896762dcd3af6b06a17a6de8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 31 Oct 2023 20:49:48 +0100 Subject: [PATCH] Secure backup: create a feature flag (disabled) --- features/logout/impl/build.gradle.kts | 2 ++ .../features/logout/impl/LogoutPresenter.kt | 16 ++++++++++++++-- .../features/logout/impl/LogoutPresenterTest.kt | 3 +++ .../impl/root/PreferencesRootPresenter.kt | 6 +++++- .../impl/root/PreferencesRootPresenterTest.kt | 1 + features/roomlist/impl/build.gradle.kts | 2 ++ .../features/roomlist/impl/RoomListPresenter.kt | 10 +++++++++- .../roomlist/impl/RoomListPresenterTests.kt | 4 ++++ .../libraries/featureflag/api/FeatureFlags.kt | 6 ++++++ .../impl/StaticFeatureFlagProvider.kt | 1 + libraries/indicator/impl/build.gradle.kts | 2 ++ .../indicator/impl/DefaultIndicatorService.kt | 8 +++++++- .../android/samples/minimal/RoomListScreen.kt | 9 ++++++++- 13 files changed, 64 insertions(+), 6 deletions(-) diff --git a/features/logout/impl/build.gradle.kts b/features/logout/impl/build.gradle.kts index 8680b355ae..68f7b389b3 100644 --- a/features/logout/impl/build.gradle.kts +++ b/features/logout/impl/build.gradle.kts @@ -34,6 +34,7 @@ dependencies { implementation(projects.libraries.androidutils) implementation(projects.libraries.core) implementation(projects.libraries.architecture) + implementation(projects.libraries.featureflag.api) implementation(projects.libraries.matrix.api) implementation(projects.libraries.designsystem) implementation(projects.libraries.testtags) @@ -48,5 +49,6 @@ dependencies { testImplementation(libs.test.truth) testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.libraries.featureflag.test) testImplementation(projects.tests.testutils) } diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutPresenter.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutPresenter.kt index 982fb0d796..68bc9b76a0 100644 --- a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutPresenter.kt +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutPresenter.kt @@ -28,16 +28,21 @@ import androidx.compose.runtime.setValue import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState +import io.element.android.libraries.featureflag.api.FeatureFlagService +import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.encryption.BackupUploadState import io.element.android.libraries.matrix.api.encryption.EncryptionService import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import javax.inject.Inject class LogoutPresenter @Inject constructor( private val matrixClient: MatrixClient, private val encryptionService: EncryptionService, + private val featureFlagService: FeatureFlagService, ) : Presenter { @Composable @@ -47,8 +52,15 @@ class LogoutPresenter @Inject constructor( mutableStateOf(Async.Uninitialized) } - val backupUploadState: BackupUploadState by remember { - encryptionService.waitForBackupUploadSteadyState() + val secureStorageFlag by featureFlagService.isFeatureEnabledFlow(FeatureFlags.SecureStorage) + .collectAsState(initial = runBlocking { featureFlagService.isFeatureEnabled(FeatureFlags.SecureStorage) }) + + val backupUploadState: BackupUploadState by remember(secureStorageFlag) { + if (secureStorageFlag) { + encryptionService.waitForBackupUploadSteadyState() + } else { + flowOf(BackupUploadState.Done) + } } .collectAsState(initial = BackupUploadState.Unknown) diff --git a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPresenterTest.kt b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPresenterTest.kt index fa02cdd68f..170655eb83 100644 --- a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPresenterTest.kt +++ b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPresenterTest.kt @@ -21,6 +21,8 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.libraries.architecture.Async +import io.element.android.libraries.featureflag.api.FeatureFlags +import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.encryption.BackupUploadState import io.element.android.libraries.matrix.api.encryption.EncryptionService @@ -205,6 +207,7 @@ class LogoutPresenterTest { ): LogoutPresenter = LogoutPresenter( matrixClient = matrixClient, encryptionService = encryptionService, + featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.SecureStorage.key to true)), ) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt index 32e85ffb35..5e40d7e54d 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt @@ -39,6 +39,7 @@ import io.element.android.libraries.matrix.api.verification.SessionVerificationS import io.element.android.services.analytics.api.AnalyticsService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import javax.inject.Inject class PreferencesRootPresenter @Inject constructor( @@ -78,6 +79,9 @@ class PreferencesRootPresenter @Inject constructor( val showSecureBackupIndicator by indicatorService.showSettingChatBackupIndicator() + val secureStorageFlag by featureFlagService.isFeatureEnabledFlow(FeatureFlags.SecureStorage) + .collectAsState(initial = runBlocking { featureFlagService.isFeatureEnabled(FeatureFlags.SecureStorage) }) + val accountManagementUrl: MutableState = remember { mutableStateOf(null) } @@ -94,7 +98,7 @@ class PreferencesRootPresenter @Inject constructor( myUser = matrixUser.value, version = versionFormatter.get(), showCompleteVerification = showCompleteVerification, - showSecureBackup = !showCompleteVerification, + showSecureBackup = !showCompleteVerification && secureStorageFlag, showSecureBackupBadge = showSecureBackupIndicator, accountManagementUrl = accountManagementUrl.value, devicesManagementUrl = devicesManagementUrl.value, diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt index c9ef8b5560..4adea8e59c 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt @@ -56,6 +56,7 @@ class PreferencesRootPresenterTest { DefaultIndicatorService( sessionVerificationService = sessionVerificationService, encryptionService = FakeEncryptionService(), + featureFlagService = FakeFeatureFlagService(), ), ) moleculeFlow(RecompositionMode.Immediate) { diff --git a/features/roomlist/impl/build.gradle.kts b/features/roomlist/impl/build.gradle.kts index 5b52a05c2e..cb70d9078a 100644 --- a/features/roomlist/impl/build.gradle.kts +++ b/features/roomlist/impl/build.gradle.kts @@ -41,6 +41,7 @@ dependencies { implementation(projects.libraries.core) implementation(projects.libraries.androidutils) implementation(projects.libraries.architecture) + implementation(projects.libraries.featureflag.api) implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrixui) implementation(projects.libraries.designsystem) @@ -64,6 +65,7 @@ dependencies { testImplementation(libs.test.turbine) testImplementation(libs.test.robolectric) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.libraries.featureflag.test) testImplementation(projects.libraries.dateformatter.test) testImplementation(projects.libraries.eventformatter.test) testImplementation(projects.libraries.indicator.impl) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt index 4a1e10d318..ee2b706871 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt @@ -35,6 +35,8 @@ import io.element.android.features.roomlist.impl.datasource.RoomListDataSource import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState +import io.element.android.libraries.featureflag.api.FeatureFlagService +import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.indicator.api.IndicatorService import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.encryption.EncryptionService @@ -44,6 +46,7 @@ import io.element.android.libraries.matrix.api.user.getCurrentUser import io.element.android.libraries.matrix.api.verification.SessionVerificationService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import javax.inject.Inject private const val EXTENDED_RANGE_SIZE = 40 @@ -57,6 +60,7 @@ class RoomListPresenter @Inject constructor( private val leaveRoomPresenter: LeaveRoomPresenter, private val roomListDataSource: RoomListDataSource, private val encryptionService: EncryptionService, + private val featureFlagService: FeatureFlagService, private val indicatorService: IndicatorService, ) : Presenter { @@ -84,10 +88,14 @@ class RoomListPresenter @Inject constructor( derivedStateOf { canVerifySession && !verificationPromptDismissed } } val recoveryState by encryptionService.recoveryStateStateFlow.collectAsState() + val secureStorageFlag by featureFlagService.isFeatureEnabledFlow(FeatureFlags.SecureStorage) + .collectAsState(initial = runBlocking { featureFlagService.isFeatureEnabled(FeatureFlags.SecureStorage) }) var recoveryKeyPromptDismissed by rememberSaveable { mutableStateOf(false) } val displayRecoveryKeyPrompt by remember { derivedStateOf { - recoveryState == RecoveryState.INCOMPLETE && !recoveryKeyPromptDismissed + secureStorageFlag && + recoveryState == RecoveryState.INCOMPLETE && + !recoveryKeyPromptDismissed } } diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt index 66e0548215..2ac418e098 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt @@ -37,6 +37,8 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter import io.element.android.libraries.eventformatter.test.FakeRoomLastMessageFormatter +import io.element.android.libraries.featureflag.api.FeatureFlags +import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.indicator.impl.DefaultIndicatorService import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.encryption.BackupState @@ -417,9 +419,11 @@ class RoomListPresenterTests { appScope = coroutineScope ), encryptionService = encryptionService, + featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.SecureStorage.key to true)), indicatorService = DefaultIndicatorService( sessionVerificationService = sessionVerificationService, encryptionService = encryptionService, + featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.SecureStorage.key to true)), ), ) } diff --git a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt index e04c1e6cd2..b37dd538fd 100644 --- a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt +++ b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt @@ -66,5 +66,11 @@ enum class FeatureFlags( title = "Mentions", description = "Type `@` to get mention suggestions and insert them", defaultValue = false, + ), + SecureStorage( + key = "feature.securestorage", + title = "Chat backup", + description = "Allow access to backup and restore chat history settings", + defaultValue = false, ) } diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt index 3e67fef201..6c9f24c979 100644 --- a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt +++ b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt @@ -41,6 +41,7 @@ class StaticFeatureFlagProvider @Inject constructor() : FeatureFlags.PinUnlock -> true FeatureFlags.InRoomCalls -> true FeatureFlags.Mentions -> false + FeatureFlags.SecureStorage -> false } } else { false diff --git a/libraries/indicator/impl/build.gradle.kts b/libraries/indicator/impl/build.gradle.kts index cb7c6cf9f0..58b3ff5b96 100644 --- a/libraries/indicator/impl/build.gradle.kts +++ b/libraries/indicator/impl/build.gradle.kts @@ -31,6 +31,7 @@ dependencies { anvil(projects.anvilcodegen) implementation(libs.dagger) implementation(projects.libraries.di) + implementation(projects.libraries.featureflag.api) implementation(projects.libraries.matrix.api) implementation(projects.anvilannotations) @@ -38,6 +39,7 @@ dependencies { api(projects.libraries.indicator.api) + testImplementation(projects.libraries.featureflag.test) testImplementation(projects.libraries.matrix.test) testImplementation(libs.test.junit) testImplementation(libs.coroutines.test) diff --git a/libraries/indicator/impl/src/main/kotlin/io/element/android/libraries/indicator/impl/DefaultIndicatorService.kt b/libraries/indicator/impl/src/main/kotlin/io/element/android/libraries/indicator/impl/DefaultIndicatorService.kt index 96ec986534..0ff5ab058c 100644 --- a/libraries/indicator/impl/src/main/kotlin/io/element/android/libraries/indicator/impl/DefaultIndicatorService.kt +++ b/libraries/indicator/impl/src/main/kotlin/io/element/android/libraries/indicator/impl/DefaultIndicatorService.kt @@ -24,17 +24,21 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.featureflag.api.FeatureFlagService +import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.indicator.api.IndicatorService import io.element.android.libraries.matrix.api.encryption.BackupState import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.api.verification.SessionVerificationService +import kotlinx.coroutines.runBlocking import javax.inject.Inject @ContributesBinding(SessionScope::class) class DefaultIndicatorService @Inject constructor( private val sessionVerificationService: SessionVerificationService, private val encryptionService: EncryptionService, + private val featureFlagService: FeatureFlagService, ) : IndicatorService { @Composable @@ -51,6 +55,8 @@ class DefaultIndicatorService @Inject constructor( @Composable override fun showSettingChatBackupIndicator(): State { + val secureStorageFlag by featureFlagService.isFeatureEnabledFlow(FeatureFlags.SecureStorage) + .collectAsState(initial = runBlocking { featureFlagService.isFeatureEnabled(FeatureFlags.SecureStorage) }) val backupState by encryptionService.backupStateStateFlow.collectAsState() val recoveryState by encryptionService.recoveryStateStateFlow.collectAsState() @@ -63,7 +69,7 @@ class DefaultIndicatorService @Inject constructor( RecoveryState.DISABLED, RecoveryState.INCOMPLETE, ) - showForBackup || showForRecovery + secureStorageFlag && (showForBackup || showForRecovery) } } } diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt index b43e7d940a..1d74718aa9 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt @@ -36,6 +36,8 @@ import io.element.android.libraries.eventformatter.impl.DefaultRoomLastMessageFo import io.element.android.libraries.eventformatter.impl.ProfileChangeContentFormatter import io.element.android.libraries.eventformatter.impl.RoomMembershipContentFormatter import io.element.android.libraries.eventformatter.impl.StateContentFormatter +import io.element.android.libraries.featureflag.impl.DefaultFeatureFlagService +import io.element.android.libraries.featureflag.impl.StaticFeatureFlagProvider import io.element.android.libraries.indicator.impl.DefaultIndicatorService import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId @@ -62,6 +64,9 @@ class RoomListScreen( private val sessionVerificationService = matrixClient.sessionVerificationService() private val encryptionService = matrixClient.encryptionService() private val stringProvider = AndroidStringProvider(context.resources) + private val featureFlagService = DefaultFeatureFlagService( + providers = setOf(StaticFeatureFlagProvider()) + ) private val presenter = RoomListPresenter( client = matrixClient, sessionVerificationService = sessionVerificationService, @@ -87,7 +92,9 @@ class RoomListScreen( indicatorService = DefaultIndicatorService( sessionVerificationService = sessionVerificationService, encryptionService = encryptionService, - ) + featureFlagService = featureFlagService, + ), + featureFlagService = featureFlagService, ) @Composable