mirror of
https://github.com/bitwarden/android.git
synced 2025-12-10 00:06:22 -06:00
[PM-21385] Defer feature flag check for Bitwarden account sync (#5222)
This commit is contained in:
parent
34aed2ac65
commit
54efc74907
Binary file not shown.
@ -40,6 +40,7 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.flatMapConcat
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
@ -155,44 +156,22 @@ class AuthenticatorRepositoryImpl @Inject constructor(
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override val sharedCodesStateFlow: StateFlow<SharedVerificationCodesState> by lazy {
|
||||
if (!featureFlagManager.getFeatureFlag(FlagKey.PasswordManagerSync)) {
|
||||
MutableStateFlow(SharedVerificationCodesState.FeatureNotEnabled)
|
||||
} else {
|
||||
authenticatorBridgeManager
|
||||
.accountSyncStateFlow
|
||||
.flatMapLatest { accountSyncState ->
|
||||
when (accountSyncState) {
|
||||
AccountSyncState.AppNotInstalled ->
|
||||
MutableStateFlow(SharedVerificationCodesState.AppNotInstalled)
|
||||
|
||||
AccountSyncState.SyncNotEnabled ->
|
||||
MutableStateFlow(SharedVerificationCodesState.SyncNotEnabled)
|
||||
|
||||
AccountSyncState.Error ->
|
||||
MutableStateFlow(SharedVerificationCodesState.Error)
|
||||
|
||||
AccountSyncState.Loading ->
|
||||
MutableStateFlow(SharedVerificationCodesState.Loading)
|
||||
|
||||
AccountSyncState.OsVersionNotSupported -> MutableStateFlow(
|
||||
SharedVerificationCodesState.OsVersionNotSupported,
|
||||
)
|
||||
|
||||
is AccountSyncState.Success -> {
|
||||
val verificationCodesList =
|
||||
accountSyncState.accounts.toAuthenticatorItems()
|
||||
totpCodeManager
|
||||
.getTotpCodesFlow(verificationCodesList)
|
||||
.map { SharedVerificationCodesState.Success(it) }
|
||||
}
|
||||
}
|
||||
featureFlagManager
|
||||
.getFeatureFlagFlow(FlagKey.PasswordManagerSync)
|
||||
.flatMapLatest { isFeatureEnabled ->
|
||||
if (isFeatureEnabled) {
|
||||
authenticatorBridgeManager
|
||||
.accountSyncStateFlow
|
||||
.flatMapConcat { it.toSharedVerificationCodesStateFlow() }
|
||||
} else {
|
||||
flowOf(SharedVerificationCodesState.FeatureNotEnabled)
|
||||
}
|
||||
.stateIn(
|
||||
scope = unconfinedScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue = SharedVerificationCodesState.Loading,
|
||||
)
|
||||
}
|
||||
}
|
||||
.stateIn(
|
||||
scope = unconfinedScope,
|
||||
started = SharingStarted.WhileSubscribed(STOP_TIMEOUT_DELAY_MS),
|
||||
initialValue = SharedVerificationCodesState.Loading,
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@ -298,6 +277,33 @@ class AuthenticatorRepositoryImpl @Inject constructor(
|
||||
override val firstTimeAccountSyncFlow: Flow<Unit>
|
||||
get() = firstTimeAccountSyncChannel.receiveAsFlow()
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
private fun AccountSyncState.toSharedVerificationCodesStateFlow(): Flow<SharedVerificationCodesState> =
|
||||
when (this) {
|
||||
AccountSyncState.AppNotInstalled ->
|
||||
flowOf(SharedVerificationCodesState.AppNotInstalled)
|
||||
|
||||
AccountSyncState.SyncNotEnabled ->
|
||||
flowOf(SharedVerificationCodesState.SyncNotEnabled)
|
||||
|
||||
AccountSyncState.Error ->
|
||||
flowOf(SharedVerificationCodesState.Error)
|
||||
|
||||
AccountSyncState.Loading ->
|
||||
flowOf(SharedVerificationCodesState.Loading)
|
||||
|
||||
AccountSyncState.OsVersionNotSupported -> flowOf(
|
||||
SharedVerificationCodesState.OsVersionNotSupported,
|
||||
)
|
||||
|
||||
is AccountSyncState.Success -> {
|
||||
val verificationCodesList = accounts.toAuthenticatorItems()
|
||||
totpCodeManager
|
||||
.getTotpCodesFlow(verificationCodesList)
|
||||
.map { SharedVerificationCodesState.Success(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun encodeVaultDataToCsv(fileUri: Uri): ExportDataResult {
|
||||
val headerLine =
|
||||
"folder,favorite,type,name,login_uri,login_totp"
|
||||
|
||||
@ -45,8 +45,14 @@ class AuthenticatorRepositoryTest {
|
||||
private val mockFileManager = mockk<FileManager>()
|
||||
private val mockImportManager = mockk<ImportManager>()
|
||||
private val mockDispatcherManager = FakeDispatcherManager()
|
||||
private val mutablePasswordSyncFlagStateFlow = MutableStateFlow(true)
|
||||
private val mockFeatureFlagManager = mockk<FeatureFlagManager> {
|
||||
every { getFeatureFlag(FlagKey.PasswordManagerSync) } returns true
|
||||
every {
|
||||
getFeatureFlagFlow(FlagKey.PasswordManagerSync)
|
||||
} returns mutablePasswordSyncFlagStateFlow
|
||||
every {
|
||||
getFeatureFlag(FlagKey.PasswordManagerSync)
|
||||
} returns mutablePasswordSyncFlagStateFlow.value
|
||||
}
|
||||
private val settingsRepository: SettingsRepository = mockk {
|
||||
every { previouslySyncedBitwardenAccountIds } returns emptySet()
|
||||
@ -84,25 +90,27 @@ class AuthenticatorRepositoryTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `sharedCodesStateFlow value should be FeatureNotEnabled when feature flag is off`() {
|
||||
every {
|
||||
mockFeatureFlagManager.getFeatureFlag(FlagKey.PasswordManagerSync)
|
||||
} returns false
|
||||
val repository = AuthenticatorRepositoryImpl(
|
||||
authenticatorDiskSource = fakeAuthenticatorDiskSource,
|
||||
authenticatorBridgeManager = mockAuthenticatorBridgeManager,
|
||||
featureFlagManager = mockFeatureFlagManager,
|
||||
totpCodeManager = mockTotpCodeManager,
|
||||
fileManager = mockFileManager,
|
||||
importManager = mockImportManager,
|
||||
dispatcherManager = mockDispatcherManager,
|
||||
settingRepository = settingsRepository,
|
||||
)
|
||||
assertEquals(
|
||||
SharedVerificationCodesState.FeatureNotEnabled,
|
||||
repository.sharedCodesStateFlow.value,
|
||||
)
|
||||
}
|
||||
fun `sharedCodesStateFlow value should be FeatureNotEnabled when feature flag is off`() =
|
||||
runTest {
|
||||
val repository = AuthenticatorRepositoryImpl(
|
||||
authenticatorDiskSource = fakeAuthenticatorDiskSource,
|
||||
authenticatorBridgeManager = mockAuthenticatorBridgeManager,
|
||||
featureFlagManager = mockFeatureFlagManager,
|
||||
totpCodeManager = mockTotpCodeManager,
|
||||
fileManager = mockFileManager,
|
||||
importManager = mockImportManager,
|
||||
dispatcherManager = mockDispatcherManager,
|
||||
settingRepository = settingsRepository,
|
||||
)
|
||||
mutablePasswordSyncFlagStateFlow.value = false
|
||||
mutableAccountSyncStateFlow.value = AccountSyncState.Success(emptyList())
|
||||
repository.sharedCodesStateFlow.test {
|
||||
assertEquals(
|
||||
SharedVerificationCodesState.FeatureNotEnabled,
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ciphersStateFlow should emit sorted authenticator items when disk source changes`() =
|
||||
@ -117,9 +125,6 @@ class AuthenticatorRepositoryTest {
|
||||
|
||||
@Test
|
||||
fun `sharedCodesStateFlow should emit FeatureNotEnabled when feature flag is off`() = runTest {
|
||||
every {
|
||||
mockFeatureFlagManager.getFeatureFlag(FlagKey.PasswordManagerSync)
|
||||
} returns false
|
||||
val repository = AuthenticatorRepositoryImpl(
|
||||
authenticatorDiskSource = fakeAuthenticatorDiskSource,
|
||||
authenticatorBridgeManager = mockAuthenticatorBridgeManager,
|
||||
@ -130,6 +135,7 @@ class AuthenticatorRepositoryTest {
|
||||
dispatcherManager = mockDispatcherManager,
|
||||
settingRepository = settingsRepository,
|
||||
)
|
||||
mutablePasswordSyncFlagStateFlow.value = false
|
||||
repository.sharedCodesStateFlow.test {
|
||||
assertEquals(
|
||||
SharedVerificationCodesState.FeatureNotEnabled,
|
||||
|
||||
@ -156,6 +156,13 @@ internal class AuthenticatorBridgeManagerImpl(
|
||||
|
||||
if (!isBound) {
|
||||
mutableSharedAccountsStateFlow.value = AccountSyncState.Error
|
||||
} else if (mutableSharedAccountsStateFlow.value == AccountSyncState.AppNotInstalled) {
|
||||
// This scenario occurs when the Authenticator is installed before Bitwarden, because
|
||||
// `AppNotInstalled` is the initial state. Binding to the service simply means Bitwarden
|
||||
// is installed, but does not indicate whether syncing is enabled. When/if syncing is
|
||||
// toggled in Bitwarden, `onServiceConnected` will be invoked and the state
|
||||
// will be updated.
|
||||
mutableSharedAccountsStateFlow.value = AccountSyncState.SyncNotEnabled
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user