mirror of
https://github.com/bitwarden/android.git
synced 2026-03-12 05:04:17 -05:00
[PM-26315] Register/unregister for CXP export based on feature flag (#5948)
This commit is contained in:
@@ -10,6 +10,7 @@ import com.x8bit.bitwarden.data.auth.manager.model.LogoutEvent
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.LogoutReason
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.PushDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.manager.CredentialExchangeRegistryManager
|
||||
import com.x8bit.bitwarden.data.tools.generator.datasource.disk.GeneratorDiskSource
|
||||
import com.x8bit.bitwarden.data.tools.generator.datasource.disk.PasswordHistoryDiskSource
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
|
||||
@@ -34,10 +35,12 @@ class UserLogoutManagerImpl(
|
||||
private val toastManager: ToastManager,
|
||||
private val vaultDiskSource: VaultDiskSource,
|
||||
private val vaultSdkSource: VaultSdkSource,
|
||||
private val credentialExchangeRegistryManager: CredentialExchangeRegistryManager,
|
||||
dispatcherManager: DispatcherManager,
|
||||
) : UserLogoutManager {
|
||||
private val scope = CoroutineScope(dispatcherManager.unconfined)
|
||||
private val unconfinedScope = CoroutineScope(dispatcherManager.unconfined)
|
||||
private val mainScope = CoroutineScope(dispatcherManager.main)
|
||||
private val ioScope = CoroutineScope(dispatcherManager.io)
|
||||
|
||||
private val mutableLogoutEventFlow: MutableSharedFlow<LogoutEvent> =
|
||||
bufferedMutableSharedFlow()
|
||||
@@ -58,8 +61,10 @@ class UserLogoutManagerImpl(
|
||||
)
|
||||
|
||||
if (!ableToSwitchToNewAccount) {
|
||||
// Update the user information and log out
|
||||
// Update the user information and log out.
|
||||
authDiskSource.userState = null
|
||||
// Unregister the application from CXP Export since there are no other accounts.
|
||||
ioScope.launch { credentialExchangeRegistryManager.unregister() }
|
||||
}
|
||||
|
||||
clearData(userId = userId)
|
||||
@@ -114,7 +119,7 @@ class UserLogoutManagerImpl(
|
||||
generatorDiskSource.clearData(userId = userId)
|
||||
pushDiskSource.clearData(userId = userId)
|
||||
settingsDiskSource.clearData(userId = userId)
|
||||
scope.launch {
|
||||
unconfinedScope.launch {
|
||||
passwordHistoryDiskSource.clearPasswordHistories(userId = userId)
|
||||
vaultDiskSource.deleteVaultData(userId = userId)
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.x8bit.bitwarden.data.auth.manager.UserLogoutManager
|
||||
import com.x8bit.bitwarden.data.auth.manager.UserLogoutManagerImpl
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.PushDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.manager.CredentialExchangeRegistryManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
||||
import com.x8bit.bitwarden.data.tools.generator.datasource.disk.GeneratorDiskSource
|
||||
import com.x8bit.bitwarden.data.tools.generator.datasource.disk.PasswordHistoryDiskSource
|
||||
@@ -117,6 +118,7 @@ object AuthManagerModule {
|
||||
vaultDiskSource: VaultDiskSource,
|
||||
vaultSdkSource: VaultSdkSource,
|
||||
dispatcherManager: DispatcherManager,
|
||||
credentialExchangeRegistryManager: CredentialExchangeRegistryManager,
|
||||
): UserLogoutManager =
|
||||
UserLogoutManagerImpl(
|
||||
authDiskSource = authDiskSource,
|
||||
@@ -128,6 +130,7 @@ object AuthManagerModule {
|
||||
vaultDiskSource = vaultDiskSource,
|
||||
vaultSdkSource = vaultSdkSource,
|
||||
dispatcherManager = dispatcherManager,
|
||||
credentialExchangeRegistryManager = credentialExchangeRegistryManager,
|
||||
)
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -354,21 +354,21 @@ interface SettingsDiskSource {
|
||||
fun getShowImportLoginsSettingBadgeFlow(userId: String): Flow<Boolean?>
|
||||
|
||||
/**
|
||||
* Gets whether or not the given [userId] has registered for export via the credential exchange
|
||||
* Gets whether or not the application has registered for export via the credential exchange
|
||||
* protocol.
|
||||
*/
|
||||
fun getVaultRegisteredForExport(userId: String): Boolean?
|
||||
fun getAppRegisteredForExport(): Boolean?
|
||||
|
||||
/**
|
||||
* Stores the given value for whether or not the given [userId] has registered for export via
|
||||
* Stores the given value for whether or not the application has registered for export via
|
||||
* the credential exchange protocol.
|
||||
*/
|
||||
fun storeVaultRegisteredForExport(userId: String, isRegistered: Boolean?)
|
||||
fun storeAppRegisteredForExport(isRegistered: Boolean?)
|
||||
|
||||
/**
|
||||
* Emits updates that track [getVaultRegisteredForExport] for the given [userId].
|
||||
* Emits updates that track [getAppRegisteredForExport].
|
||||
*/
|
||||
fun getVaultRegisteredForExportFlow(userId: String): Flow<Boolean?>
|
||||
fun getAppRegisteredForExportFlow(userId: String): Flow<Boolean?>
|
||||
|
||||
/**
|
||||
* Gets the number of qualifying add cipher actions for the device.
|
||||
|
||||
@@ -100,8 +100,7 @@ class SettingsDiskSourceImpl(
|
||||
|
||||
private val mutableScreenCaptureAllowedFlow = bufferedMutableSharedFlow<Boolean?>()
|
||||
|
||||
private val mutableVaultRegisteredForExportFlow =
|
||||
mutableMapOf<String, MutableSharedFlow<Boolean?>>()
|
||||
private val mutableVaultRegisteredForExportFlow = bufferedMutableSharedFlow<Boolean?>()
|
||||
|
||||
private val mutableIsDynamicColorsEnabledFlow = bufferedMutableSharedFlow<Boolean?>()
|
||||
|
||||
@@ -247,7 +246,6 @@ class SettingsDiskSourceImpl(
|
||||
storeLastSyncTime(userId = userId, lastSyncTime = null)
|
||||
storeClearClipboardFrequencySeconds(userId = userId, frequency = null)
|
||||
removeWithPrefix(prefix = ACCOUNT_BIOMETRIC_INTEGRITY_VALID_KEY.appendIdentifier(userId))
|
||||
storeVaultRegisteredForExport(userId = userId, isRegistered = null)
|
||||
storeAppResumeScreen(userId = userId, screenData = null)
|
||||
|
||||
// The following are intentionally not cleared so they can be
|
||||
@@ -509,17 +507,17 @@ class SettingsDiskSourceImpl(
|
||||
getMutableShowImportLoginsSettingBadgeFlow(userId)
|
||||
.onSubscription { emit(getShowImportLoginsSettingBadge(userId)) }
|
||||
|
||||
override fun getVaultRegisteredForExport(userId: String): Boolean? =
|
||||
getBoolean(IS_VAULT_REGISTERED_FOR_EXPORT.appendIdentifier(userId))
|
||||
override fun getAppRegisteredForExport(): Boolean? =
|
||||
getBoolean(IS_VAULT_REGISTERED_FOR_EXPORT)
|
||||
|
||||
override fun storeVaultRegisteredForExport(userId: String, isRegistered: Boolean?) {
|
||||
putBoolean(IS_VAULT_REGISTERED_FOR_EXPORT.appendIdentifier(userId), isRegistered)
|
||||
getMutableVaultRegisteredForExportFlow(userId).tryEmit(isRegistered)
|
||||
override fun storeAppRegisteredForExport(isRegistered: Boolean?) {
|
||||
putBoolean(IS_VAULT_REGISTERED_FOR_EXPORT, isRegistered)
|
||||
mutableVaultRegisteredForExportFlow.tryEmit(isRegistered)
|
||||
}
|
||||
|
||||
override fun getVaultRegisteredForExportFlow(userId: String): Flow<Boolean?> =
|
||||
getMutableVaultRegisteredForExportFlow(userId)
|
||||
.onSubscription { emit(getVaultRegisteredForExport(userId)) }
|
||||
override fun getAppRegisteredForExportFlow(userId: String): Flow<Boolean?> =
|
||||
mutableVaultRegisteredForExportFlow
|
||||
.onSubscription { emit(getAppRegisteredForExport()) }
|
||||
|
||||
override fun getAddCipherActionCount(): Int? = getInt(
|
||||
key = ADD_ACTION_COUNT,
|
||||
@@ -649,12 +647,6 @@ class SettingsDiskSourceImpl(
|
||||
bufferedMutableSharedFlow(replay = 1)
|
||||
}
|
||||
|
||||
private fun getMutableVaultRegisteredForExportFlow(
|
||||
userId: String,
|
||||
): MutableSharedFlow<Boolean?> = mutableVaultRegisteredForExportFlow.getOrPut(userId) {
|
||||
bufferedMutableSharedFlow(replay = 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates the user-scoped screen capture state to an app-wide state.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.x8bit.bitwarden.data.platform.manager
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.RegisterExportResult
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.UnregisterExportResult
|
||||
|
||||
/**
|
||||
* Manager for registering for Credential Exchange Protocol export.
|
||||
*/
|
||||
interface CredentialExchangeRegistryManager {
|
||||
|
||||
/**
|
||||
* Registers the application for Credential Exchange Protocol export.
|
||||
*/
|
||||
suspend fun register(): RegisterExportResult
|
||||
|
||||
/**
|
||||
* Unregisters the application for Credential Exchange Protocol export.
|
||||
*/
|
||||
suspend fun unregister(): UnregisterExportResult
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.x8bit.bitwarden.data.platform.manager
|
||||
|
||||
import androidx.credentials.providerevents.transfer.CredentialTypes
|
||||
import com.bitwarden.cxf.registry.CredentialExchangeRegistry
|
||||
import com.bitwarden.cxf.registry.model.RegistrationRequest
|
||||
import com.bitwarden.ui.platform.resource.BitwardenDrawable
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.RegisterExportResult
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.UnregisterExportResult
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Default implementation of [CredentialExchangeRegistryManager].
|
||||
*/
|
||||
class CredentialExchangeRegistryManagerImpl(
|
||||
private val credentialExchangeRegistry: CredentialExchangeRegistry,
|
||||
private val settingsDiskSource: SettingsDiskSource,
|
||||
) : CredentialExchangeRegistryManager {
|
||||
override suspend fun register(): RegisterExportResult = credentialExchangeRegistry
|
||||
.register(
|
||||
registrationRequest = RegistrationRequest(
|
||||
appNameRes = R.string.app_name,
|
||||
credentialTypes = setOf(
|
||||
CredentialTypes.CREDENTIAL_TYPE_BASIC_AUTH,
|
||||
CredentialTypes.CREDENTIAL_TYPE_PUBLIC_KEY,
|
||||
CredentialTypes.CREDENTIAL_TYPE_TOTP,
|
||||
CredentialTypes.CREDENTIAL_TYPE_CREDIT_CARD,
|
||||
CredentialTypes.CREDENTIAL_TYPE_SSH_KEY,
|
||||
CredentialTypes.CREDENTIAL_TYPE_ADDRESS,
|
||||
),
|
||||
iconResId = BitwardenDrawable.icon,
|
||||
),
|
||||
)
|
||||
.fold(
|
||||
onSuccess = {
|
||||
Timber.d("Successfully registered for CXP export")
|
||||
settingsDiskSource.storeAppRegisteredForExport(isRegistered = true)
|
||||
RegisterExportResult.Success
|
||||
},
|
||||
onFailure = {
|
||||
Timber.e(it, "Failed to register for CXP export")
|
||||
RegisterExportResult.Failure(it)
|
||||
},
|
||||
)
|
||||
|
||||
override suspend fun unregister(): UnregisterExportResult = credentialExchangeRegistry
|
||||
.unregister()
|
||||
.fold(
|
||||
onSuccess = {
|
||||
Timber.d("Successfully unregistered for CXP export")
|
||||
settingsDiskSource.storeAppRegisteredForExport(isRegistered = false)
|
||||
UnregisterExportResult.Success
|
||||
},
|
||||
onFailure = {
|
||||
Timber.e(it, "Failed to unregister for CXP export")
|
||||
UnregisterExportResult.Failure(it)
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import com.bitwarden.core.data.manager.realtime.RealtimeManager
|
||||
import com.bitwarden.core.data.manager.realtime.RealtimeManagerImpl
|
||||
import com.bitwarden.core.data.manager.toast.ToastManager
|
||||
import com.bitwarden.core.data.manager.toast.ToastManagerImpl
|
||||
import com.bitwarden.cxf.registry.CredentialExchangeRegistry
|
||||
import com.bitwarden.cxf.registry.dsl.credentialExchangeRegistry
|
||||
import com.bitwarden.data.manager.DispatcherManager
|
||||
import com.bitwarden.data.manager.DispatcherManagerImpl
|
||||
import com.bitwarden.data.manager.NativeLibraryManager
|
||||
@@ -34,6 +36,8 @@ import com.x8bit.bitwarden.data.platform.manager.BiometricsEncryptionManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.BiometricsEncryptionManagerImpl
|
||||
import com.x8bit.bitwarden.data.platform.manager.CertificateManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.CertificateManagerImpl
|
||||
import com.x8bit.bitwarden.data.platform.manager.CredentialExchangeRegistryManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.CredentialExchangeRegistryManagerImpl
|
||||
import com.x8bit.bitwarden.data.platform.manager.DatabaseSchemeManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.DatabaseSchemeManagerImpl
|
||||
import com.x8bit.bitwarden.data.platform.manager.DebugMenuFeatureFlagManagerImpl
|
||||
@@ -422,4 +426,22 @@ object PlatformManagerModule {
|
||||
clock = clock,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCredentialExchangeRegistry(
|
||||
application: Application,
|
||||
): CredentialExchangeRegistry = credentialExchangeRegistry(
|
||||
application = application,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCredentialExchangeRegistryManager(
|
||||
credentialExchangeRegistry: CredentialExchangeRegistry,
|
||||
settingsDiskSource: SettingsDiskSource,
|
||||
): CredentialExchangeRegistryManager = CredentialExchangeRegistryManagerImpl(
|
||||
credentialExchangeRegistry = credentialExchangeRegistry,
|
||||
settingsDiskSource = settingsDiskSource,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.x8bit.bitwarden.data.platform.manager.model
|
||||
|
||||
/**
|
||||
* Represents the result of registering for export.
|
||||
*/
|
||||
sealed class RegisterExportResult {
|
||||
|
||||
/**
|
||||
* Registration was successful.
|
||||
*/
|
||||
data object Success : RegisterExportResult()
|
||||
|
||||
/**
|
||||
* Registration failed.
|
||||
*/
|
||||
data class Failure(val throwable: Throwable?) : RegisterExportResult()
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.x8bit.bitwarden.data.platform.manager.model
|
||||
|
||||
/**
|
||||
* Represents the result of unregistering for Credential Exchange Protocol export.
|
||||
*/
|
||||
sealed class UnregisterExportResult {
|
||||
/**
|
||||
* Represents a successful unregistering for Credential Exchange Protocol export.
|
||||
*/
|
||||
data object Success : UnregisterExportResult()
|
||||
|
||||
/**
|
||||
* Represents a failure to unregister for Credential Exchange Protocol export.
|
||||
*/
|
||||
data class Failure(val throwable: Throwable?) : UnregisterExportResult()
|
||||
}
|
||||
@@ -278,19 +278,19 @@ interface SettingsRepository : FlightRecorderManager {
|
||||
fun storeUserHasLoggedInValue(userId: String)
|
||||
|
||||
/**
|
||||
* Returns true if the given [userId] has previously registered for export via the credential
|
||||
* Returns true if the application has previously registered for export via the credential
|
||||
* exchange protocol.
|
||||
*/
|
||||
fun isVaultRegisteredForExport(userId: String): Boolean
|
||||
fun isAppRegisteredForExport(): Boolean
|
||||
|
||||
/**
|
||||
* Stores that the given [userId] has previously registered for export via the credential
|
||||
* Stores that the application has previously registered for export via the credential
|
||||
* exchange protocol.
|
||||
*/
|
||||
fun storeVaultRegisteredForExport(userId: String, isRegistered: Boolean)
|
||||
fun storeAppRegisteredForExport(isRegistered: Boolean)
|
||||
|
||||
/**
|
||||
* Gets updates for the [isVaultRegisteredForExport] value for the given [userId].
|
||||
* Gets updates for the [isAppRegisteredForExport] value for the given [userId].
|
||||
*/
|
||||
fun getVaultRegisteredForExportFlow(userId: String): StateFlow<Boolean>
|
||||
fun getAppRegisteredForExportFlow(userId: String): StateFlow<Boolean>
|
||||
}
|
||||
|
||||
@@ -575,23 +575,23 @@ class SettingsRepositoryImpl(
|
||||
settingsDiskSource.storeUseHasLoggedInPreviously(userId)
|
||||
}
|
||||
|
||||
override fun isVaultRegisteredForExport(userId: String): Boolean {
|
||||
return settingsDiskSource.getVaultRegisteredForExport(userId) == true
|
||||
override fun isAppRegisteredForExport(): Boolean {
|
||||
return settingsDiskSource.getAppRegisteredForExport() == true
|
||||
}
|
||||
|
||||
override fun storeVaultRegisteredForExport(userId: String, isRegistered: Boolean) {
|
||||
settingsDiskSource.storeVaultRegisteredForExport(userId, isRegistered)
|
||||
override fun storeAppRegisteredForExport(isRegistered: Boolean) {
|
||||
settingsDiskSource.storeAppRegisteredForExport(isRegistered)
|
||||
}
|
||||
|
||||
override fun getVaultRegisteredForExportFlow(userId: String): StateFlow<Boolean> {
|
||||
override fun getAppRegisteredForExportFlow(userId: String): StateFlow<Boolean> {
|
||||
return settingsDiskSource
|
||||
.getVaultRegisteredForExportFlow(userId)
|
||||
.getAppRegisteredForExportFlow(userId)
|
||||
.map { it ?: false }
|
||||
.stateIn(
|
||||
scope = unconfinedScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = settingsDiskSource
|
||||
.getVaultRegisteredForExport(userId)
|
||||
.getAppRegisteredForExport()
|
||||
?: false,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.x8bit.bitwarden.ui.vault.feature.vault
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.core.data.manager.model.FlagKey
|
||||
import com.bitwarden.core.data.repository.model.DataState
|
||||
import com.bitwarden.core.util.persistentListOfNotNull
|
||||
import com.bitwarden.data.repository.util.baseIconUrl
|
||||
@@ -28,6 +29,8 @@ import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
||||
import com.x8bit.bitwarden.data.autofill.manager.browser.BrowserAutofillDialogManager
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.model.FlightRecorderDataSet
|
||||
import com.x8bit.bitwarden.data.platform.manager.CredentialExchangeRegistryManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.ReviewPromptManager
|
||||
@@ -100,6 +103,8 @@ class VaultViewModel @Inject constructor(
|
||||
private val specialCircumstanceManager: SpecialCircumstanceManager,
|
||||
private val networkConnectionManager: NetworkConnectionManager,
|
||||
private val browserAutofillDialogManager: BrowserAutofillDialogManager,
|
||||
private val credentialExchangeRegistryManager: CredentialExchangeRegistryManager,
|
||||
featureFlagManager: FeatureFlagManager,
|
||||
snackbarRelayManager: SnackbarRelayManager,
|
||||
) : BaseViewModel<VaultState, VaultEvent, VaultAction>(
|
||||
initialState = run {
|
||||
@@ -210,6 +215,11 @@ class VaultViewModel @Inject constructor(
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
featureFlagManager.getFeatureFlagFlow(FlagKey.CredentialExchangeProtocolExport)
|
||||
.map { VaultAction.Internal.CredentialExchangeProtocolExportFlagUpdateReceive(it) }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
viewModelScope.launch {
|
||||
delay(timeMillis = BROWSER_AUTOFILL_DIALOG_DELAY)
|
||||
mutableStateFlow.update { vaultState ->
|
||||
@@ -785,6 +795,22 @@ class VaultViewModel @Inject constructor(
|
||||
is VaultAction.Internal.DecryptionErrorReceive -> {
|
||||
handleDecryptionErrorReceive(action)
|
||||
}
|
||||
|
||||
is VaultAction.Internal.CredentialExchangeProtocolExportFlagUpdateReceive -> {
|
||||
handleCredentialExchangeProtocolExportFlagUpdateReceive(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCredentialExchangeProtocolExportFlagUpdateReceive(
|
||||
action: VaultAction.Internal.CredentialExchangeProtocolExportFlagUpdateReceive,
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
if (action.isCredentialExchangeProtocolExportEnabled) {
|
||||
credentialExchangeRegistryManager.register()
|
||||
} else {
|
||||
credentialExchangeRegistryManager.unregister()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1916,6 +1942,13 @@ sealed class VaultAction {
|
||||
val message: Text,
|
||||
val error: Throwable?,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that the Credential Exchange Protocol export flag has been updated.
|
||||
*/
|
||||
data class CredentialExchangeProtocolExportFlagUpdateReceive(
|
||||
val isCredentialExchangeProtocolExportEnabled: Boolean,
|
||||
) : Internal()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.LogoutReason
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.PushDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.manager.CredentialExchangeRegistryManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.UnregisterExportResult
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeoutAction
|
||||
import com.x8bit.bitwarden.data.tools.generator.datasource.disk.GeneratorDiskSource
|
||||
import com.x8bit.bitwarden.data.tools.generator.datasource.disk.PasswordHistoryDiskSource
|
||||
@@ -43,6 +45,7 @@ class UserLogoutManagerTest {
|
||||
every { clearData(any()) } just runs
|
||||
every { storeVaultTimeoutInMinutes(any(), any()) } just runs
|
||||
every { storeVaultTimeoutAction(any(), any()) } just runs
|
||||
every { storeAppRegisteredForExport(any()) } just runs
|
||||
}
|
||||
private val pushDiskSource: PushDiskSource = mockk {
|
||||
coEvery { clearData(any()) } just runs
|
||||
@@ -59,6 +62,9 @@ class UserLogoutManagerTest {
|
||||
private val toastManager: ToastManager = mockk {
|
||||
every { show(messageId = any()) } just runs
|
||||
}
|
||||
private val credentialExchangeRegistryManager: CredentialExchangeRegistryManager = mockk {
|
||||
coEvery { unregister() } returns UnregisterExportResult.Success
|
||||
}
|
||||
|
||||
private val userLogoutManager: UserLogoutManager =
|
||||
UserLogoutManagerImpl(
|
||||
@@ -71,17 +77,21 @@ class UserLogoutManagerTest {
|
||||
vaultDiskSource = vaultDiskSource,
|
||||
vaultSdkSource = vaultSdkSource,
|
||||
dispatcherManager = FakeDispatcherManager(),
|
||||
credentialExchangeRegistryManager = credentialExchangeRegistryManager,
|
||||
)
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `logout for single account should clear data associated with the given user and null out the user state`() {
|
||||
fun `logout for single account should clear data associated with the given user, null out the user state, and unregister app for export`() {
|
||||
val userId = USER_ID_1
|
||||
every { authDiskSource.userState } returns SINGLE_USER_STATE_1
|
||||
|
||||
userLogoutManager.logout(userId = USER_ID_1, reason = LogoutReason.Timeout)
|
||||
|
||||
verify { authDiskSource.userState = null }
|
||||
coVerify {
|
||||
authDiskSource.userState = null
|
||||
credentialExchangeRegistryManager.unregister()
|
||||
}
|
||||
assertDataCleared(userId = userId)
|
||||
}
|
||||
|
||||
|
||||
@@ -1342,35 +1342,31 @@ class SettingsDiskSourceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getVaultRegisteredForExport should pull from SharedPreferences`() {
|
||||
val mockUserId = "mockUserId"
|
||||
val vaultRegisteredForExportKey =
|
||||
"bwPreferencesStorage:isVaultRegisteredForExport_$mockUserId"
|
||||
fun `getAppRegisteredForExport should pull from SharedPreferences`() {
|
||||
val vaultRegisteredForExportKey = "bwPreferencesStorage:isVaultRegisteredForExport"
|
||||
fakeSharedPreferences.edit {
|
||||
putBoolean(vaultRegisteredForExportKey, true)
|
||||
}
|
||||
assertTrue(settingsDiskSource.getVaultRegisteredForExport(userId = mockUserId)!!)
|
||||
assertTrue(settingsDiskSource.getAppRegisteredForExport()!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `storeVaultRegisteredForExport should update SharedPreferences`() {
|
||||
val mockUserId = "mockUserId"
|
||||
val vaultRegisteredForExportKey =
|
||||
"bwPreferencesStorage:isVaultRegisteredForExport_$mockUserId"
|
||||
settingsDiskSource.storeVaultRegisteredForExport(mockUserId, true)
|
||||
fun `storeAppRegisteredForExport should update SharedPreferences`() {
|
||||
val vaultRegisteredForExportKey = "bwPreferencesStorage:isVaultRegisteredForExport"
|
||||
settingsDiskSource.storeAppRegisteredForExport(true)
|
||||
assertTrue(fakeSharedPreferences.getBoolean(vaultRegisteredForExportKey, false))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `storeVaultRegisteredForExport should update the flow value`() = runTest {
|
||||
fun `storeAppRegisteredForExport should update the flow value`() = runTest {
|
||||
val mockUserId = "mockUserId"
|
||||
settingsDiskSource.getVaultRegisteredForExportFlow(mockUserId).test {
|
||||
settingsDiskSource.getAppRegisteredForExportFlow(mockUserId).test {
|
||||
// The initial values of the Flow are in sync
|
||||
assertFalse(awaitItem() ?: false)
|
||||
settingsDiskSource.storeVaultRegisteredForExport(mockUserId, true)
|
||||
settingsDiskSource.storeAppRegisteredForExport(true)
|
||||
assertTrue(awaitItem() ?: false)
|
||||
// Update the value to false
|
||||
settingsDiskSource.storeVaultRegisteredForExport(mockUserId, false)
|
||||
settingsDiskSource.storeAppRegisteredForExport(false)
|
||||
assertFalse(awaitItem() ?: true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ class FakeSettingsDiskSource : SettingsDiskSource {
|
||||
private val userShowBrowserAutofillBadge = mutableMapOf<String, Boolean?>()
|
||||
private val userShowUnlockBadge = mutableMapOf<String, Boolean?>()
|
||||
private val userShowImportLoginsBadge = mutableMapOf<String, Boolean?>()
|
||||
private val vaultRegisteredForExport = mutableMapOf<String, Boolean?>()
|
||||
private var vaultRegisteredForExport: Boolean? = null
|
||||
private var addCipherActionCount: Int? = null
|
||||
private var generatedActionCount: Int? = null
|
||||
private var createSendActionCount: Int? = null
|
||||
@@ -102,12 +102,12 @@ class FakeSettingsDiskSource : SettingsDiskSource {
|
||||
private val mutableShowImportLoginsSettingBadgeFlowMap =
|
||||
mutableMapOf<String, MutableSharedFlow<Boolean?>>()
|
||||
|
||||
private val mutableVaultRegisteredForExportFlowMap =
|
||||
mutableMapOf<String, MutableSharedFlow<Boolean?>>()
|
||||
|
||||
private val mutableIsDynamicColorsEnabled =
|
||||
bufferedMutableSharedFlow<Boolean?>()
|
||||
|
||||
private val mutableVaultRegisteredForExportFlow =
|
||||
bufferedMutableSharedFlow<Boolean?>()
|
||||
|
||||
override var appLanguage: AppLanguage?
|
||||
get() = storedAppLanguage
|
||||
set(value) {
|
||||
@@ -244,7 +244,6 @@ class FakeSettingsDiskSource : SettingsDiskSource {
|
||||
mutableVaultTimeoutActionsFlowMap.remove(userId)
|
||||
mutableVaultTimeoutInMinutesFlowMap.remove(userId)
|
||||
mutableLastSyncCallFlowMap.remove(userId)
|
||||
mutableVaultRegisteredForExportFlowMap.remove(userId)
|
||||
}
|
||||
|
||||
override fun getLastSyncTime(userId: String): Instant? = storedLastSyncTime[userId]
|
||||
@@ -415,17 +414,17 @@ class FakeSettingsDiskSource : SettingsDiskSource {
|
||||
emit(getShowImportLoginsSettingBadge(userId = userId))
|
||||
}
|
||||
|
||||
override fun getVaultRegisteredForExport(userId: String): Boolean =
|
||||
vaultRegisteredForExport[userId] ?: false
|
||||
override fun getAppRegisteredForExport(): Boolean =
|
||||
vaultRegisteredForExport ?: false
|
||||
|
||||
override fun storeVaultRegisteredForExport(userId: String, isRegistered: Boolean?) {
|
||||
vaultRegisteredForExport[userId] = isRegistered
|
||||
getMutableVaultRegisteredForExportFlow(userId = userId).tryEmit(isRegistered)
|
||||
override fun storeAppRegisteredForExport(isRegistered: Boolean?) {
|
||||
vaultRegisteredForExport = isRegistered
|
||||
mutableVaultRegisteredForExportFlow.tryEmit(isRegistered)
|
||||
}
|
||||
|
||||
override fun getVaultRegisteredForExportFlow(userId: String): Flow<Boolean?> =
|
||||
getMutableVaultRegisteredForExportFlow(userId = userId).onSubscription {
|
||||
emit(getVaultRegisteredForExport(userId = userId))
|
||||
override fun getAppRegisteredForExportFlow(userId: String): Flow<Boolean?> =
|
||||
mutableVaultRegisteredForExportFlow.onSubscription {
|
||||
emit(getAppRegisteredForExport())
|
||||
}
|
||||
|
||||
override fun getAddCipherActionCount(): Int? {
|
||||
@@ -560,13 +559,5 @@ class FakeSettingsDiskSource : SettingsDiskSource {
|
||||
): MutableSharedFlow<Boolean?> = mutableShowImportLoginsSettingBadgeFlowMap.getOrPut(userId) {
|
||||
bufferedMutableSharedFlow(replay = 1)
|
||||
}
|
||||
|
||||
private fun getMutableVaultRegisteredForExportFlow(
|
||||
userId: String,
|
||||
): MutableSharedFlow<Boolean?> =
|
||||
mutableVaultRegisteredForExportFlowMap.getOrPut(userId) {
|
||||
bufferedMutableSharedFlow(replay = 1)
|
||||
}
|
||||
|
||||
//endregion Private helper functions
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
package com.x8bit.bitwarden.data.platform.manager
|
||||
|
||||
import androidx.credentials.providerevents.exception.RegisterExportUnknownErrorException
|
||||
import androidx.credentials.providerevents.transfer.CredentialTypes
|
||||
import androidx.credentials.providerevents.transfer.RegisterExportResponse
|
||||
import com.bitwarden.core.data.util.asFailure
|
||||
import com.bitwarden.core.data.util.asSuccess
|
||||
import com.bitwarden.cxf.registry.CredentialExchangeRegistry
|
||||
import com.bitwarden.cxf.registry.model.RegistrationRequest
|
||||
import com.bitwarden.ui.platform.resource.BitwardenDrawable
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.RegisterExportResult
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.UnregisterExportResult
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.runs
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class CredentialExchangeRegistryManagerImplTest {
|
||||
|
||||
private val credentialExchangeRegistry: CredentialExchangeRegistry = mockk {
|
||||
coEvery { register(any()) } returns RegisterExportResponse().asSuccess()
|
||||
coEvery { unregister() } returns RegisterExportResponse().asSuccess()
|
||||
}
|
||||
private val settingsDiskSource: SettingsDiskSource = mockk {
|
||||
every { getAppRegisteredForExport() } returns false
|
||||
every { storeAppRegisteredForExport(any()) } just runs
|
||||
}
|
||||
|
||||
private val registryManager: CredentialExchangeRegistryManager =
|
||||
CredentialExchangeRegistryManagerImpl(
|
||||
credentialExchangeRegistry = credentialExchangeRegistry,
|
||||
settingsDiskSource = settingsDiskSource,
|
||||
)
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `register should store app registered for export and return Success when registration is successful`() =
|
||||
runTest {
|
||||
val result = registryManager.register()
|
||||
|
||||
coVerify {
|
||||
credentialExchangeRegistry.register(
|
||||
registrationRequest = RegistrationRequest(
|
||||
appNameRes = R.string.app_name,
|
||||
credentialTypes = setOf(
|
||||
CredentialTypes.CREDENTIAL_TYPE_BASIC_AUTH,
|
||||
CredentialTypes.CREDENTIAL_TYPE_PUBLIC_KEY,
|
||||
CredentialTypes.CREDENTIAL_TYPE_TOTP,
|
||||
CredentialTypes.CREDENTIAL_TYPE_CREDIT_CARD,
|
||||
CredentialTypes.CREDENTIAL_TYPE_SSH_KEY,
|
||||
CredentialTypes.CREDENTIAL_TYPE_ADDRESS,
|
||||
),
|
||||
iconResId = BitwardenDrawable.icon,
|
||||
),
|
||||
)
|
||||
settingsDiskSource.storeAppRegisteredForExport(true)
|
||||
}
|
||||
|
||||
assertEquals(RegisterExportResult.Success, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `register should return Failure when registration fails`() =
|
||||
runTest {
|
||||
coEvery {
|
||||
credentialExchangeRegistry.register(any())
|
||||
} returns RegisterExportUnknownErrorException().asFailure()
|
||||
|
||||
val result = registryManager.register()
|
||||
|
||||
verify(exactly = 0) {
|
||||
settingsDiskSource.storeAppRegisteredForExport(any())
|
||||
}
|
||||
assertTrue(result is RegisterExportResult.Failure)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `unregister should store app registered for export and return Success when unregistration is successful`() =
|
||||
runTest {
|
||||
val result = registryManager.unregister()
|
||||
|
||||
coVerify {
|
||||
credentialExchangeRegistry.unregister()
|
||||
settingsDiskSource.storeAppRegisteredForExport(false)
|
||||
}
|
||||
|
||||
assertEquals(UnregisterExportResult.Success, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unregister should return Failure when unregistration fails`() = runTest {
|
||||
coEvery {
|
||||
credentialExchangeRegistry.unregister()
|
||||
} returns RegisterExportUnknownErrorException().asFailure()
|
||||
|
||||
val result = registryManager.unregister()
|
||||
|
||||
verify(exactly = 0) {
|
||||
settingsDiskSource.storeAppRegisteredForExport(any())
|
||||
}
|
||||
assertTrue(result is UnregisterExportResult.Failure)
|
||||
}
|
||||
}
|
||||
@@ -1236,39 +1236,35 @@ class SettingsRepositoryTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isVaultRegisteredForExport should return false if no value exists`() {
|
||||
assertFalse(settingsRepository.isVaultRegisteredForExport(userId = "userId"))
|
||||
fun `isAppRegisteredForExport should return false if no value exists`() {
|
||||
assertFalse(settingsRepository.isAppRegisteredForExport())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isVaultRegisteredForExport should return true if it exists`() {
|
||||
val userId = "userId"
|
||||
fakeSettingsDiskSource.storeVaultRegisteredForExport(userId = userId, isRegistered = true)
|
||||
assertTrue(settingsRepository.isVaultRegisteredForExport(userId = userId))
|
||||
fun `isAppRegisteredForExport should return true if it exists`() {
|
||||
fakeSettingsDiskSource.storeAppRegisteredForExport(isRegistered = true)
|
||||
assertTrue(settingsRepository.isAppRegisteredForExport())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `storeVaultRegisteredForExport should store value of true to disk`() {
|
||||
val userId = "userId"
|
||||
settingsRepository.storeVaultRegisteredForExport(userId = userId, isRegistered = true)
|
||||
assertTrue(fakeSettingsDiskSource.getVaultRegisteredForExport(userId = userId))
|
||||
fun `storeAppRegisteredForExport should store value of true to disk`() {
|
||||
settingsRepository.storeAppRegisteredForExport(isRegistered = true)
|
||||
assertTrue(fakeSettingsDiskSource.getAppRegisteredForExport())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getVaultRegisteredForExportFlow should react to changes in SettingsDiskSource`() =
|
||||
fun `getAppRegisteredForExportFlow should react to changes in SettingsDiskSource`() =
|
||||
runTest {
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
settingsRepository
|
||||
.getVaultRegisteredForExportFlow(userId = USER_ID)
|
||||
.getAppRegisteredForExportFlow(userId = USER_ID)
|
||||
.test {
|
||||
assertFalse(awaitItem())
|
||||
fakeSettingsDiskSource.storeVaultRegisteredForExport(
|
||||
userId = USER_ID,
|
||||
fakeSettingsDiskSource.storeAppRegisteredForExport(
|
||||
isRegistered = true,
|
||||
)
|
||||
assertTrue(awaitItem())
|
||||
fakeSettingsDiskSource.storeVaultRegisteredForExport(
|
||||
userId = USER_ID,
|
||||
fakeSettingsDiskSource.storeAppRegisteredForExport(
|
||||
isRegistered = false,
|
||||
)
|
||||
assertFalse(awaitItem())
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.x8bit.bitwarden.ui.vault.feature.vault
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.core.data.manager.model.FlagKey
|
||||
import com.bitwarden.core.data.repository.model.DataState
|
||||
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
||||
import com.bitwarden.data.repository.model.Environment
|
||||
@@ -25,6 +26,8 @@ import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
||||
import com.x8bit.bitwarden.data.autofill.manager.browser.BrowserAutofillDialogManager
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.model.FlightRecorderDataSet
|
||||
import com.x8bit.bitwarden.data.platform.manager.CredentialExchangeRegistryManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.FirstTimeActionManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.ReviewPromptManager
|
||||
@@ -33,7 +36,9 @@ import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardMan
|
||||
import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.FirstTimeState
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.RegisterExportResult
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.UnregisterExportResult
|
||||
import com.x8bit.bitwarden.data.platform.manager.network.NetworkConnectionManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCardListView
|
||||
@@ -61,7 +66,9 @@ import com.x8bit.bitwarden.ui.vault.feature.vault.util.toSnackbarData
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toViewState
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultItemCipherType
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
|
||||
import io.mockk.awaits
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
@@ -187,6 +194,17 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
every { delayDialog() } just runs
|
||||
}
|
||||
|
||||
private val credentialExchangeRegistryManager: CredentialExchangeRegistryManager = mockk {
|
||||
coEvery { register() } returns RegisterExportResult.Success
|
||||
coEvery { unregister() } returns UnregisterExportResult.Success
|
||||
}
|
||||
private val mutableCxpExportFeatureFlagFlow = MutableStateFlow(false)
|
||||
private val featureFlagManager: FeatureFlagManager = mockk {
|
||||
every {
|
||||
getFeatureFlagFlow(FlagKey.CredentialExchangeProtocolExport)
|
||||
} returns mutableCxpExportFeatureFlagFlow
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
unmockkStatic(FlightRecorderDataSet::toSnackbarData)
|
||||
@@ -2842,6 +2860,47 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `CredentialExchangeProtocolExportFlagUpdateReceive should register for export when flag is enabled`() =
|
||||
runTest {
|
||||
mutableCxpExportFeatureFlagFlow.value = false
|
||||
coEvery { credentialExchangeRegistryManager.register() } just awaits
|
||||
|
||||
val viewModel = createViewModel()
|
||||
|
||||
viewModel.trySendAction(
|
||||
VaultAction.Internal.CredentialExchangeProtocolExportFlagUpdateReceive(
|
||||
isCredentialExchangeProtocolExportEnabled = true,
|
||||
),
|
||||
)
|
||||
|
||||
coVerify {
|
||||
credentialExchangeRegistryManager.register()
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `CredentialExchangeProtocolExportFlagUpdateReceive should unregister when flag is disabled`() =
|
||||
runTest {
|
||||
mutableCxpExportFeatureFlagFlow.value = true
|
||||
every { settingsRepository.isAppRegisteredForExport() } returns true
|
||||
coEvery { credentialExchangeRegistryManager.unregister() } just awaits
|
||||
|
||||
val viewModel = createViewModel()
|
||||
|
||||
viewModel.trySendAction(
|
||||
VaultAction.Internal.CredentialExchangeProtocolExportFlagUpdateReceive(
|
||||
isCredentialExchangeProtocolExportEnabled = false,
|
||||
),
|
||||
)
|
||||
|
||||
coVerify {
|
||||
credentialExchangeRegistryManager.unregister()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createViewModel(): VaultViewModel =
|
||||
VaultViewModel(
|
||||
authRepository = authRepository,
|
||||
@@ -2857,6 +2916,8 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
specialCircumstanceManager = specialCircumstanceManager,
|
||||
networkConnectionManager = networkConnectionManager,
|
||||
browserAutofillDialogManager = browserAutofillDialogManager,
|
||||
credentialExchangeRegistryManager = credentialExchangeRegistryManager,
|
||||
featureFlagManager = featureFlagManager,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -29,5 +29,5 @@ interface CredentialExchangeRegistry {
|
||||
*
|
||||
* @return True if the unregistration was successful, false otherwise.
|
||||
*/
|
||||
suspend fun unregister(): RegisterExportResponse
|
||||
suspend fun unregister(): Result<RegisterExportResponse>
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.app.Application
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.toBitmapOrNull
|
||||
import androidx.credentials.providerevents.ProviderEventsManager
|
||||
import androidx.credentials.providerevents.exception.RegisterExportException
|
||||
import androidx.credentials.providerevents.transfer.ExportEntry
|
||||
import androidx.credentials.providerevents.transfer.RegisterExportRequest
|
||||
import androidx.credentials.providerevents.transfer.RegisterExportResponse
|
||||
@@ -11,6 +12,7 @@ import com.bitwarden.annotation.OmitFromCoverage
|
||||
import com.bitwarden.core.data.util.asFailure
|
||||
import com.bitwarden.core.data.util.asSuccess
|
||||
import com.bitwarden.cxf.registry.model.RegistrationRequest
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
@@ -40,23 +42,34 @@ internal class CredentialExchangeRegistryImpl(
|
||||
ExportEntry(
|
||||
id = UUID.randomUUID().toString(),
|
||||
accountDisplayName = null,
|
||||
userDisplayName = registrationRequest.appName,
|
||||
userDisplayName = application.getString(registrationRequest.appNameRes),
|
||||
icon = icon,
|
||||
supportedCredentialTypes = registrationRequest.credentialTypes,
|
||||
),
|
||||
),
|
||||
)
|
||||
return providerEventsManager
|
||||
.registerExport(request = request)
|
||||
.asSuccess()
|
||||
return try {
|
||||
providerEventsManager
|
||||
.registerExport(request = request)
|
||||
.asSuccess()
|
||||
} catch (e: RegisterExportException) {
|
||||
Timber.e(e, "Failed to register application for export.")
|
||||
e.asFailure()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun unregister(): RegisterExportResponse =
|
||||
providerEventsManager.registerExport(
|
||||
// This is a workaround for unregistering an account since an explicit "unregister" API
|
||||
// is not currently available.
|
||||
request = RegisterExportRequest(
|
||||
entries = emptyList(),
|
||||
),
|
||||
)
|
||||
override suspend fun unregister(): Result<RegisterExportResponse> =
|
||||
try {
|
||||
providerEventsManager.registerExport(
|
||||
// This is a workaround for unregistering an account since an explicit "unregister"
|
||||
// API is not currently available.
|
||||
request = RegisterExportRequest(
|
||||
entries = emptyList(),
|
||||
),
|
||||
)
|
||||
.asSuccess()
|
||||
} catch (e: RegisterExportException) {
|
||||
Timber.e(e, "Failed to unregister application for export.")
|
||||
e.asFailure()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
package com.bitwarden.cxf.registry.model
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
|
||||
/**
|
||||
* Represents a request to register as a credential provider that allows exporting credentials.
|
||||
*
|
||||
* @property appName The name of the application as it will be displayed to the user.
|
||||
* @property appNameRes The name of the application as it will be displayed to the user.
|
||||
* @property credentialTypes The types of credentials that can be exported.
|
||||
* @property iconResId Resource ID of a 36x36 pixel drawable to be displayed alongside the [appName]
|
||||
* when credential import is requested.
|
||||
* @property iconResId Resource ID of a 36x36 pixel drawable to be displayed alongside the
|
||||
* [appNameRes] when credential import is requested.
|
||||
*/
|
||||
data class RegistrationRequest(
|
||||
val appName: String,
|
||||
val credentialTypes: Set<String>,
|
||||
@field:StringRes
|
||||
val appNameRes: Int,
|
||||
@field:DrawableRes
|
||||
val iconResId: Int,
|
||||
val credentialTypes: Set<String>,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user