Skip to content

Commit 1de00fc

Browse files
committed
Refactor CredentialManager to use CipherListView and CipherMatchingManager
This commit refactors `BitwardenCredentialManagerImpl` and its tests to utilize `CipherListView` objects directly for credential retrieval and matching, instead of relying on `AutofillCipherProvider`. Key changes: - `BitwardenCredentialManagerImpl` now uses `CipherMatchingManager` to filter `CipherListViews` based on the calling app's package name. - The `getCredentialEntries` method in `BitwardenCredentialManagerImpl` has been updated to fetch and filter `CipherListViews` from `VaultRepository`. - `CredentialEntryBuilder` and its implementation now accept `List<CipherListView>` instead of `List<AutofillCipher.Login>` for building password credential entries. - The `AutofillCipherProvider` dependency has been removed from `BitwardenCredentialManagerImpl` and `CredentialProviderModule`. - Introduced `isActiveWithCopyablePassword` extension for `CipherListView`. - Tests for `BitwardenCredentialManagerImpl`, `CredentialEntryBuilderTest`, and related ViewModels have been updated to reflect these changes, including mocking `CipherMatchingManager` and providing `CipherListView` instances. - Updated `createMockProviderGetPasswordCredentialRequest` to accept a number parameter for easier test data generation. - Minor adjustments in test helpers and mock object creation to align with the refactoring. - `VaultItemListingViewModel` now checks `CipherListView.reprompt` before fetching the full `CipherView` for password credential requests. An error is now sent if the `CipherListView` is not found.
1 parent cc56505 commit 1de00fc

File tree

15 files changed

+587
-254
lines changed

15 files changed

+587
-254
lines changed

app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/util/CipherListViewExtensions.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.autofill.util
33
import com.bitwarden.vault.CardListView
44
import com.bitwarden.vault.CipherListView
55
import com.bitwarden.vault.CipherListViewType
6+
import com.bitwarden.vault.CopyableCipherFields
67
import com.bitwarden.vault.LoginListView
78

89
/**
@@ -11,6 +12,12 @@ import com.bitwarden.vault.LoginListView
1112
val CipherListView.isActiveWithFido2Credentials: Boolean
1213
get() = deletedDate == null && login?.hasFido2 ?: false
1314

15+
/**
16+
* Returns true when the cipher type is [CipherListViewType.Login] and is not deleted.
17+
*/
18+
val CipherListView.isActiveWithCopyablePassword: Boolean
19+
get() = deletedDate == null && copyableFields.contains(CopyableCipherFields.LOGIN_PASSWORD)
20+
1421
/**
1522
* Returns the [LoginListView] if the cipher is of type [CipherListViewType.Login], otherwise null.
1623
*/

app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/builder/CredentialEntryBuilder.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import androidx.credentials.provider.BeginGetPublicKeyCredentialOption
55
import androidx.credentials.provider.PasswordCredentialEntry
66
import androidx.credentials.provider.PublicKeyCredentialEntry
77
import com.bitwarden.fido.Fido2CredentialAutofillView
8-
import com.x8bit.bitwarden.data.autofill.model.AutofillCipher
8+
import com.bitwarden.vault.CipherListView
99

1010
/**
1111
* Builder for credential entries.
@@ -27,7 +27,7 @@ interface CredentialEntryBuilder {
2727
*/
2828
fun buildPasswordCredentialEntries(
2929
userId: String,
30-
passwordCredentialAutofillViews: List<AutofillCipher.Login>,
30+
cipherListViews: List<CipherListView>,
3131
beginGetPasswordCredentialOptions: List<BeginGetPasswordOption>,
3232
isUserVerified: Boolean,
3333
): List<PasswordCredentialEntry>

app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/builder/CredentialEntryBuilderImpl.kt

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ import androidx.credentials.provider.PasswordCredentialEntry
99
import androidx.credentials.provider.PublicKeyCredentialEntry
1010
import com.bitwarden.fido.Fido2CredentialAutofillView
1111
import com.bitwarden.ui.platform.resource.BitwardenDrawable
12+
import com.bitwarden.vault.CipherListView
1213
import com.x8bit.bitwarden.R
13-
import com.x8bit.bitwarden.data.autofill.model.AutofillCipher
14+
import com.x8bit.bitwarden.data.autofill.util.login
1415
import com.x8bit.bitwarden.data.credentials.processor.GET_PASSKEY_INTENT
1516
import com.x8bit.bitwarden.data.credentials.processor.GET_PASSWORD_INTENT
1617
import com.x8bit.bitwarden.data.credentials.util.setBiometricPromptDataIfSupported
@@ -47,12 +48,12 @@ class CredentialEntryBuilderImpl(
4748

4849
override fun buildPasswordCredentialEntries(
4950
userId: String,
50-
passwordCredentialAutofillViews: List<AutofillCipher.Login>,
51+
cipherListViews: List<CipherListView>,
5152
beginGetPasswordCredentialOptions: List<BeginGetPasswordOption>,
5253
isUserVerified: Boolean,
5354
): List<PasswordCredentialEntry> = beginGetPasswordCredentialOptions
5455
.flatMap { option ->
55-
passwordCredentialAutofillViews
56+
cipherListViews
5657
.toPasswordCredentialEntryList(
5758
userId = userId,
5859
option = option,
@@ -100,7 +101,7 @@ class CredentialEntryBuilderImpl(
100101
.build()
101102
}
102103

103-
private fun List<AutofillCipher.Login>.toPasswordCredentialEntryList(
104+
private fun List<CipherListView>.toPasswordCredentialEntryList(
104105
userId: String,
105106
option: BeginGetPasswordOption,
106107
isUserVerified: Boolean,
@@ -109,12 +110,13 @@ class CredentialEntryBuilderImpl(
109110
PasswordCredentialEntry
110111
.Builder(
111112
context = context,
112-
username = cipherView.username,
113+
username = cipherView.login?.username
114+
?: context.getString(R.string.no_username),
113115
pendingIntent = intentManager
114116
.createPasswordGetCredentialPendingIntent(
115117
action = GET_PASSWORD_INTENT,
116118
userId = userId,
117-
cipherId = cipherView.cipherId,
119+
cipherId = cipherView.id,
118120
isUserVerified = isUserVerified,
119121
requestCode = Random.nextInt(),
120122
),

app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/di/CredentialProviderModule.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import com.bitwarden.data.manager.DispatcherManager
77
import com.bitwarden.network.service.DigitalAssetLinkService
88
import com.bitwarden.sdk.Fido2CredentialStore
99
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
10-
import com.x8bit.bitwarden.data.autofill.provider.AutofillCipherProvider
1110
import com.x8bit.bitwarden.data.credentials.builder.CredentialEntryBuilder
1211
import com.x8bit.bitwarden.data.credentials.builder.CredentialEntryBuilderImpl
1312
import com.x8bit.bitwarden.data.credentials.datasource.disk.PrivilegedAppDiskSource
@@ -24,6 +23,7 @@ import com.x8bit.bitwarden.data.credentials.repository.PrivilegedAppRepositoryIm
2423
import com.x8bit.bitwarden.data.platform.manager.AssetManager
2524
import com.x8bit.bitwarden.data.platform.manager.BiometricsEncryptionManager
2625
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
26+
import com.x8bit.bitwarden.data.platform.manager.ciphermatching.CipherMatchingManager
2727
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
2828
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
2929
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
@@ -72,20 +72,20 @@ object CredentialProviderModule {
7272
fun provideBitwardenCredentialManager(
7373
vaultSdkSource: VaultSdkSource,
7474
fido2CredentialStore: Fido2CredentialStore,
75-
autofillCipherProvider: AutofillCipherProvider,
7675
json: Json,
7776
vaultRepository: VaultRepository,
7877
dispatcherManager: DispatcherManager,
7978
credentialEntryBuilder: CredentialEntryBuilder,
79+
cipherMatchingManager: CipherMatchingManager,
8080
): BitwardenCredentialManager =
8181
BitwardenCredentialManagerImpl(
8282
vaultSdkSource = vaultSdkSource,
8383
fido2CredentialStore = fido2CredentialStore,
84-
autofillCipherProvider = autofillCipherProvider,
8584
json = json,
8685
vaultRepository = vaultRepository,
8786
dispatcherManager = dispatcherManager,
8887
credentialEntryBuilder = credentialEntryBuilder,
88+
cipherMatchingManager = cipherMatchingManager,
8989
)
9090

9191
@Provides

app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/manager/BitwardenCredentialManagerImpl.kt

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,17 @@ import com.bitwarden.ui.platform.base.util.prefixHttpsIfNecessaryOrNull
2222
import com.bitwarden.ui.platform.base.util.toAndroidAppUriString
2323
import com.bitwarden.vault.CipherListView
2424
import com.bitwarden.vault.CipherView
25-
import com.x8bit.bitwarden.data.autofill.provider.AutofillCipherProvider
25+
import com.x8bit.bitwarden.data.autofill.util.isActiveWithCopyablePassword
2626
import com.x8bit.bitwarden.data.autofill.util.isActiveWithFido2Credentials
27+
import com.x8bit.bitwarden.data.autofill.util.login
2728
import com.x8bit.bitwarden.data.credentials.builder.CredentialEntryBuilder
2829
import com.x8bit.bitwarden.data.credentials.model.Fido2CredentialAssertionResult
2930
import com.x8bit.bitwarden.data.credentials.model.Fido2RegisterCredentialResult
3031
import com.x8bit.bitwarden.data.credentials.model.GetCredentialsRequest
3132
import com.x8bit.bitwarden.data.credentials.model.PasskeyAssertionOptions
3233
import com.x8bit.bitwarden.data.credentials.model.PasskeyAttestationOptions
3334
import com.x8bit.bitwarden.data.credentials.model.UserVerificationRequirement
35+
import com.x8bit.bitwarden.data.platform.manager.ciphermatching.CipherMatchingManager
3436
import com.x8bit.bitwarden.data.platform.util.getAppOrigin
3537
import com.x8bit.bitwarden.data.platform.util.getAppSigningSignatureFingerprint
3638
import com.x8bit.bitwarden.data.platform.util.getSignatureFingerprintAsHexString
@@ -54,10 +56,10 @@ import timber.log.Timber
5456
class BitwardenCredentialManagerImpl(
5557
private val vaultSdkSource: VaultSdkSource,
5658
private val fido2CredentialStore: Fido2CredentialStore,
57-
private val autofillCipherProvider: AutofillCipherProvider,
5859
private val credentialEntryBuilder: CredentialEntryBuilder,
5960
private val json: Json,
6061
private val vaultRepository: VaultRepository,
62+
private val cipherMatchingManager: CipherMatchingManager,
6163
dispatcherManager: DispatcherManager,
6264
) : BitwardenCredentialManager,
6365
Fido2CredentialStore by fido2CredentialStore {
@@ -173,7 +175,7 @@ class BitwardenCredentialManagerImpl(
173175
override suspend fun getCredentialEntries(
174176
getCredentialsRequest: GetCredentialsRequest,
175177
): Result<List<CredentialEntry>> = withContext(ioScope.coroutineContext) {
176-
val fido2CipherListViews = vaultRepository
178+
val cipherListViews = vaultRepository
177179
.decryptCipherListResultStateFlow
178180
.takeUntilLoaded()
179181
.fold(initial = emptyList<CipherListView>()) { _, dataState ->
@@ -182,7 +184,7 @@ class BitwardenCredentialManagerImpl(
182184
else -> emptyList()
183185
}
184186
}
185-
.filter { it.isActiveWithFido2Credentials }
187+
.filter { it.isActiveWithFido2Credentials || it.isActiveWithCopyablePassword }
186188
.ifEmpty { return@withContext emptyList<CredentialEntry>().asSuccess() }
187189

188190
val passwordCredentialResult = getCredentialsRequest
@@ -193,7 +195,10 @@ class BitwardenCredentialManagerImpl(
193195
.beginGetPasswordOptions
194196
.toPasswordCredentialEntries(
195197
userId = getCredentialsRequest.userId,
196-
packageName = packageName,
198+
cipherListViews = cipherMatchingManager.filterCiphersForMatches(
199+
cipherListViews = cipherListViews,
200+
matchUri = packageName.toAndroidAppUriString(),
201+
),
197202
)
198203
}
199204
.orEmpty()
@@ -202,7 +207,8 @@ class BitwardenCredentialManagerImpl(
202207
.beginGetPublicKeyCredentialOptions
203208
.toPublicKeyCredentialEntries(
204209
userId = getCredentialsRequest.userId,
205-
cipherListViews = fido2CipherListViews,
210+
cipherListViews = cipherListViews
211+
.filter { it.isActiveWithFido2Credentials },
206212
)
207213
.onFailure { Timber.e(it, "Failed to get FIDO 2 credential entries.") }
208214

@@ -231,6 +237,12 @@ class BitwardenCredentialManagerImpl(
231237
}
232238

233239
val cipherViews = cipherListViews
240+
.filter { cipherListView ->
241+
cipherListView.login
242+
?.fido2Credentials
243+
.orEmpty()
244+
.any { credential -> credential.rpId in relyingPartyIds }
245+
}
234246
.mapNotNull { cipherListView ->
235247
when (val result = vaultRepository.getCipher(cipherListView.id.orEmpty())) {
236248
GetCipherResult.CipherNotFound -> {
@@ -262,8 +274,7 @@ class BitwardenCredentialManagerImpl(
262274
credentialEntryBuilder
263275
.buildPublicKeyCredentialEntries(
264276
userId = userId,
265-
fido2CredentialAutofillViews = fido2AutofillViews
266-
.filter { it.rpId in relyingPartyIds },
277+
fido2CredentialAutofillViews = fido2AutofillViews,
267278
beginGetPublicKeyCredentialOptions = this,
268279
isUserVerified = isUserVerified,
269280
)
@@ -364,20 +375,16 @@ class BitwardenCredentialManagerImpl(
364375
},
365376
)
366377

367-
private suspend fun List<BeginGetPasswordOption>.toPasswordCredentialEntries(
378+
private fun List<BeginGetPasswordOption>.toPasswordCredentialEntries(
368379
userId: String,
369-
packageName: String,
380+
cipherListViews: List<CipherListView>,
370381
): List<CredentialEntry> {
371382
if (this.isEmpty()) return emptyList()
372383

373-
val ciphers = autofillCipherProvider
374-
.getLoginAutofillCiphers(packageName.toAndroidAppUriString())
375-
.filter { it.password.isNotEmpty() }
376-
377384
return credentialEntryBuilder
378385
.buildPasswordCredentialEntries(
379386
userId = userId,
380-
passwordCredentialAutofillViews = ciphers,
387+
cipherListViews = cipherListViews,
381388
beginGetPasswordCredentialOptions = this,
382389
isUserVerified = isUserVerified,
383390
)

app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2182,19 +2182,31 @@ class VaultItemListingViewModel @Inject constructor(
21822182
),
21832183
)
21842184
}
2185+
2186+
val cipherListView = vaultRepository.vaultDataStateFlow.value.data
2187+
?.decryptCipherListResult
2188+
?.successes
2189+
?.find { it.id == action.data.cipherId }
2190+
?: run {
2191+
sendCredentialItemNotFoundError()
2192+
return
2193+
}
2194+
2195+
if (
2196+
state.hasMasterPassword &&
2197+
cipherListView.reprompt == CipherRepromptType.PASSWORD
2198+
) {
2199+
repromptMasterPasswordForUserVerification(action.data.cipherId)
2200+
return
2201+
}
2202+
21852203
viewModelScope.launch {
21862204
val request = action.data
21872205
getCipherViewForCredentialOrNull(request.cipherId)
21882206
?.let { cipherView ->
2189-
if (state.hasMasterPassword &&
2190-
cipherView.reprompt == CipherRepromptType.PASSWORD
2191-
) {
2192-
repromptMasterPasswordForUserVerification(request.cipherId)
2193-
} else {
2194-
handlePasswordCredentialResult(
2195-
selectedCipher = cipherView,
2196-
)
2197-
}
2207+
handlePasswordCredentialResult(
2208+
selectedCipher = cipherView,
2209+
)
21982210
}
21992211
}
22002212
}
@@ -2209,8 +2221,8 @@ class VaultItemListingViewModel @Inject constructor(
22092221
)
22102222
}
22112223

2212-
private suspend fun sendCredentialItemNotFoundError() {
2213-
sendAction(
2224+
private fun sendCredentialItemNotFoundError() {
2225+
trySendAction(
22142226
VaultItemListingsAction.Internal.CredentialOperationFailureReceive(
22152227
title = R.string.an_error_has_occurred.asText(),
22162228
message = R.string

app/src/test/kotlin/com/x8bit/bitwarden/MainViewModelTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -812,7 +812,7 @@ class MainViewModelTest : BaseViewModelTest() {
812812
@Test
813813
fun `on ReceiveFirstIntent with password get request data should set the special circumstance to ProviderGetPasswordRequest`() {
814814
val viewModel = createViewModel()
815-
val mockProviderGetCredentialRequest = createMockProviderGetPasswordCredentialRequest()
815+
val mockProviderGetCredentialRequest = createMockProviderGetPasswordCredentialRequest(1)
816816
val passwordGetCredentialIntent = createMockIntent(
817817
mockProviderGetPasswordRequest = mockProviderGetCredentialRequest,
818818
)

0 commit comments

Comments
 (0)