diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/manager/UserLogoutManagerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/manager/UserLogoutManagerImpl.kt index 361ab1282f..b4c6d842eb 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/manager/UserLogoutManagerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/manager/UserLogoutManagerImpl.kt @@ -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 = 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) } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/manager/di/AuthManagerModule.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/manager/di/AuthManagerModule.kt index 86bc26548b..f315bc7126 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/manager/di/AuthManagerModule.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/manager/di/AuthManagerModule.kt @@ -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 diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSource.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSource.kt index 4c7e25c468..ac96d5c458 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSource.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSource.kt @@ -354,21 +354,21 @@ interface SettingsDiskSource { fun getShowImportLoginsSettingBadgeFlow(userId: String): Flow /** - * 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 + fun getAppRegisteredForExportFlow(userId: String): Flow /** * Gets the number of qualifying add cipher actions for the device. diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceImpl.kt index 75f569dc31..07b3f9c5ef 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceImpl.kt @@ -100,8 +100,7 @@ class SettingsDiskSourceImpl( private val mutableScreenCaptureAllowedFlow = bufferedMutableSharedFlow() - private val mutableVaultRegisteredForExportFlow = - mutableMapOf>() + private val mutableVaultRegisteredForExportFlow = bufferedMutableSharedFlow() private val mutableIsDynamicColorsEnabledFlow = bufferedMutableSharedFlow() @@ -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 = - getMutableVaultRegisteredForExportFlow(userId) - .onSubscription { emit(getVaultRegisteredForExport(userId)) } + override fun getAppRegisteredForExportFlow(userId: String): Flow = + 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 = mutableVaultRegisteredForExportFlow.getOrPut(userId) { - bufferedMutableSharedFlow(replay = 1) - } - /** * Migrates the user-scoped screen capture state to an app-wide state. * diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/CredentialExchangeRegistryManager.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/CredentialExchangeRegistryManager.kt new file mode 100644 index 0000000000..8dd96811ee --- /dev/null +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/CredentialExchangeRegistryManager.kt @@ -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 +} diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/CredentialExchangeRegistryManagerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/CredentialExchangeRegistryManagerImpl.kt new file mode 100644 index 0000000000..e74020ffc5 --- /dev/null +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/CredentialExchangeRegistryManagerImpl.kt @@ -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) + }, + ) +} diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/di/PlatformManagerModule.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/di/PlatformManagerModule.kt index a263fbc7ec..59c93a0652 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/di/PlatformManagerModule.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/di/PlatformManagerModule.kt @@ -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, + ) } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/RegisterExportResult.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/RegisterExportResult.kt new file mode 100644 index 0000000000..e19252eaec --- /dev/null +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/RegisterExportResult.kt @@ -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() +} diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/UnregisterExportResult.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/UnregisterExportResult.kt new file mode 100644 index 0000000000..2d1d8482c7 --- /dev/null +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/UnregisterExportResult.kt @@ -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() +} diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/SettingsRepository.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/SettingsRepository.kt index 933af7a31a..046ab5e63e 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/SettingsRepository.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/SettingsRepository.kt @@ -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 + fun getAppRegisteredForExportFlow(userId: String): StateFlow } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryImpl.kt index 8afd705644..74efac3ecb 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryImpl.kt @@ -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 { + override fun getAppRegisteredForExportFlow(userId: String): StateFlow { return settingsDiskSource - .getVaultRegisteredForExportFlow(userId) + .getAppRegisteredForExportFlow(userId) .map { it ?: false } .stateIn( scope = unconfinedScope, started = SharingStarted.Eagerly, initialValue = settingsDiskSource - .getVaultRegisteredForExport(userId) + .getAppRegisteredForExport() ?: false, ) } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt index 9a4e11dfdf..da80410215 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt @@ -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( 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() } } diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/manager/UserLogoutManagerTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/manager/UserLogoutManagerTest.kt index 87732e2bb6..bced19e086 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/manager/UserLogoutManagerTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/manager/UserLogoutManagerTest.kt @@ -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) } diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceTest.kt index 1764c7222b..be0815b31d 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceTest.kt @@ -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) } } diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/util/FakeSettingsDiskSource.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/util/FakeSettingsDiskSource.kt index a13afe8cfb..1a5395b91c 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/util/FakeSettingsDiskSource.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/util/FakeSettingsDiskSource.kt @@ -80,7 +80,7 @@ class FakeSettingsDiskSource : SettingsDiskSource { private val userShowBrowserAutofillBadge = mutableMapOf() private val userShowUnlockBadge = mutableMapOf() private val userShowImportLoginsBadge = mutableMapOf() - private val vaultRegisteredForExport = mutableMapOf() + 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>() - private val mutableVaultRegisteredForExportFlowMap = - mutableMapOf>() - private val mutableIsDynamicColorsEnabled = bufferedMutableSharedFlow() + private val mutableVaultRegisteredForExportFlow = + bufferedMutableSharedFlow() + 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 = - getMutableVaultRegisteredForExportFlow(userId = userId).onSubscription { - emit(getVaultRegisteredForExport(userId = userId)) + override fun getAppRegisteredForExportFlow(userId: String): Flow = + mutableVaultRegisteredForExportFlow.onSubscription { + emit(getAppRegisteredForExport()) } override fun getAddCipherActionCount(): Int? { @@ -560,13 +559,5 @@ class FakeSettingsDiskSource : SettingsDiskSource { ): MutableSharedFlow = mutableShowImportLoginsSettingBadgeFlowMap.getOrPut(userId) { bufferedMutableSharedFlow(replay = 1) } - - private fun getMutableVaultRegisteredForExportFlow( - userId: String, - ): MutableSharedFlow = - mutableVaultRegisteredForExportFlowMap.getOrPut(userId) { - bufferedMutableSharedFlow(replay = 1) - } - //endregion Private helper functions } diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/CredentialExchangeRegistryManagerImplTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/CredentialExchangeRegistryManagerImplTest.kt new file mode 100644 index 0000000000..dbf455cbbe --- /dev/null +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/CredentialExchangeRegistryManagerImplTest.kt @@ -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) + } +} diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryTest.kt index b2654eb4f5..7323599a64 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryTest.kt @@ -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()) diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModelTest.kt index 94af2d4a35..1ff3379ec4 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModelTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModelTest.kt @@ -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, ) } diff --git a/cxf/src/main/kotlin/com/bitwarden/cxf/registry/CredentialExchangeRegistry.kt b/cxf/src/main/kotlin/com/bitwarden/cxf/registry/CredentialExchangeRegistry.kt index 5cfea0d369..17463b9b37 100644 --- a/cxf/src/main/kotlin/com/bitwarden/cxf/registry/CredentialExchangeRegistry.kt +++ b/cxf/src/main/kotlin/com/bitwarden/cxf/registry/CredentialExchangeRegistry.kt @@ -29,5 +29,5 @@ interface CredentialExchangeRegistry { * * @return True if the unregistration was successful, false otherwise. */ - suspend fun unregister(): RegisterExportResponse + suspend fun unregister(): Result } diff --git a/cxf/src/main/kotlin/com/bitwarden/cxf/registry/CredentialExchangeRegistryImpl.kt b/cxf/src/main/kotlin/com/bitwarden/cxf/registry/CredentialExchangeRegistryImpl.kt index 14c323823e..b3750ae2bd 100644 --- a/cxf/src/main/kotlin/com/bitwarden/cxf/registry/CredentialExchangeRegistryImpl.kt +++ b/cxf/src/main/kotlin/com/bitwarden/cxf/registry/CredentialExchangeRegistryImpl.kt @@ -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 = + 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() + } } diff --git a/cxf/src/main/kotlin/com/bitwarden/cxf/registry/model/RegistrationRequest.kt b/cxf/src/main/kotlin/com/bitwarden/cxf/registry/model/RegistrationRequest.kt index ed1d054304..3c440923ea 100644 --- a/cxf/src/main/kotlin/com/bitwarden/cxf/registry/model/RegistrationRequest.kt +++ b/cxf/src/main/kotlin/com/bitwarden/cxf/registry/model/RegistrationRequest.kt @@ -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, + @field:StringRes + val appNameRes: Int, @field:DrawableRes val iconResId: Int, + val credentialTypes: Set, )