This commit is contained in:
Matt Gibson 2024-10-24 12:55:13 -07:00
parent 425b085a96
commit 028d382a5b
No known key found for this signature in database
GPG key ID: 7CBCA182C13B0912
5 changed files with 110 additions and 18 deletions

View file

@ -22,6 +22,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult
import com.x8bit.bitwarden.data.vault.repository.model.DomainsData import com.x8bit.bitwarden.data.vault.repository.model.DomainsData
import com.x8bit.bitwarden.data.vault.repository.model.ExportVaultDataResult import com.x8bit.bitwarden.data.vault.repository.model.ExportVaultDataResult
import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
import com.x8bit.bitwarden.data.vault.repository.model.OfflineCipherView
import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult
import com.x8bit.bitwarden.data.vault.repository.model.SendData import com.x8bit.bitwarden.data.vault.repository.model.SendData
import com.x8bit.bitwarden.data.vault.repository.model.SyncVaultDataResult import com.x8bit.bitwarden.data.vault.repository.model.SyncVaultDataResult
@ -62,7 +63,7 @@ interface VaultRepository : CipherManager, VaultLockManager {
* Note that the [StateFlow.value] will return the last known value but the [StateFlow] itself * Note that the [StateFlow.value] will return the last known value but the [StateFlow] itself
* must be collected in order to trigger state changes. * must be collected in order to trigger state changes.
*/ */
val offlineCiphersStateFlow: StateFlow<DataState<List<CipherView>>> val offlineCiphersStateFlow: StateFlow<DataState<List<OfflineCipherView>>>
/** /**
* Flow that represents all ciphers for the active user. * Flow that represents all ciphers for the active user.
* *

View file

@ -63,6 +63,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult
import com.x8bit.bitwarden.data.vault.repository.model.DomainsData import com.x8bit.bitwarden.data.vault.repository.model.DomainsData
import com.x8bit.bitwarden.data.vault.repository.model.ExportVaultDataResult import com.x8bit.bitwarden.data.vault.repository.model.ExportVaultDataResult
import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
import com.x8bit.bitwarden.data.vault.repository.model.OfflineCipherView
import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult
import com.x8bit.bitwarden.data.vault.repository.model.SendData import com.x8bit.bitwarden.data.vault.repository.model.SendData
import com.x8bit.bitwarden.data.vault.repository.model.SyncVaultDataResult import com.x8bit.bitwarden.data.vault.repository.model.SyncVaultDataResult
@ -83,6 +84,8 @@ import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkFolder
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkFolderList import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkFolderList
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkSend import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkSend
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkSendList import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkSendList
import com.x8bit.bitwarden.data.vault.repository.util.toOfflineCipher
import com.x8bit.bitwarden.data.vault.repository.util.toOfflineCipherView
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toFilteredList import com.x8bit.bitwarden.ui.vault.feature.vault.util.toFilteredList
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
@ -158,7 +161,7 @@ class VaultRepositoryImpl(
private val mutableSendDataStateFlow = MutableStateFlow<DataState<SendData>>(DataState.Loading) private val mutableSendDataStateFlow = MutableStateFlow<DataState<SendData>>(DataState.Loading)
private val mutableOfflineCiphersStateFlow = private val mutableOfflineCiphersStateFlow =
MutableStateFlow<DataState<List<CipherView>>>(DataState.Loading) MutableStateFlow<DataState<List<OfflineCipherView>>>(DataState.Loading)
private val mutableCiphersStateFlow = private val mutableCiphersStateFlow =
MutableStateFlow<DataState<List<CipherView>>>(DataState.Loading) MutableStateFlow<DataState<List<CipherView>>>(DataState.Loading)
@ -208,8 +211,8 @@ class VaultRepositoryImpl(
override val totpCodeFlow: Flow<TotpCodeResult> override val totpCodeFlow: Flow<TotpCodeResult>
get() = mutableTotpCodeResultFlow.asSharedFlow() get() = mutableTotpCodeResultFlow.asSharedFlow()
override val offlineCiphersStateFlow: StateFlow<DataState<List<CipherView>>> override val offlineCiphersStateFlow: StateFlow<DataState<List<OfflineCipherView>>>
get() = mutableOfflineCiphersStateFlow.asStateFlow(); get() = mutableOfflineCiphersStateFlow.asStateFlow()
override val ciphersStateFlow: StateFlow<DataState<List<CipherView>>> override val ciphersStateFlow: StateFlow<DataState<List<CipherView>>>
get() = mutableCiphersStateFlow.asStateFlow() get() = mutableCiphersStateFlow.asStateFlow()
@ -945,18 +948,21 @@ class VaultRepositoryImpl(
private fun observeVaultDiskOfflineCiphers( private fun observeVaultDiskOfflineCiphers(
userId: String, userId: String,
): Flow<DataState<List<CipherView>>> = ): Flow<DataState<List<OfflineCipherView>>> =
vaultDiskSource.getOfflineCiphers(userId = userId) vaultDiskSource.getOfflineCiphers(userId = userId)
.onStart { mutableOfflineCiphersStateFlow.updateToPendingOrLoading() } .onStart { mutableOfflineCiphersStateFlow.updateToPendingOrLoading() }
.map { .map { ciphers ->
waitUntilUnlocked(userId = userId) waitUntilUnlocked(userId = userId)
vaultSdkSource vaultSdkSource
.decryptCipherList( .decryptCipherList(
userId = userId, userId = userId,
cipherList = it.toCipherList(), cipherList = ciphers.toCipherList(),
) )
.map {
it.zip(ciphers).map { (cipher, offlineJson) -> cipher.toOfflineCipherView(offlineJson.toOfflineCipher()) }
}
.fold( .fold(
onSuccess = { ciphers -> DataState.Loaded(ciphers.sortAlphabetically()) }, onSuccess = { views -> DataState.Loaded(views) },
onFailure = { throwable -> DataState.Error(throwable) }, onFailure = { throwable -> DataState.Error(throwable) },
) )
} }

View file

@ -0,0 +1,46 @@
package com.x8bit.bitwarden.data.vault.repository.model
import com.bitwarden.core.DateTime
import com.bitwarden.core.Uuid
import com.bitwarden.crypto.EncString
import com.bitwarden.vault.AttachmentView
import com.bitwarden.vault.CardView
import com.bitwarden.vault.CipherRepromptType
import com.bitwarden.vault.CipherType
import com.bitwarden.vault.FieldView
import com.bitwarden.vault.IdentityView
import com.bitwarden.vault.LocalDataView
import com.bitwarden.vault.LoginView
import com.bitwarden.vault.PasswordHistoryView
import com.bitwarden.vault.SecureNoteView
data class OfflineCipherView (
val id: Uuid?,
val organizationId: Uuid?,
val folderId: Uuid?,
val collectionIds: List<Uuid>,
/**
* Temporary, required to support re-encrypting existing items.
*/
val key: EncString?,
val name: String,
val notes: String?,
val type: CipherType,
val login: LoginView?,
val identity: IdentityView?,
val card: CardView?,
val secureNote: SecureNoteView?,
val favorite: Boolean,
val reprompt: CipherRepromptType,
val organizationUseTotp: Boolean,
val edit: Boolean,
val viewPassword: Boolean,
val localData: LocalDataView?,
val attachments: List<AttachmentView>?,
val fields: List<FieldView>?,
val passwordHistory: List<PasswordHistoryView>?,
val creationDate: DateTime,
val deletedDate: DateTime?,
val revisionDate: DateTime,
val mergeConflict: Boolean
)

View file

@ -17,7 +17,7 @@ import com.bitwarden.vault.FolderView
* @param fido2CredentialAutofillViewList List of decrypted fido 2 credentials. * @param fido2CredentialAutofillViewList List of decrypted fido 2 credentials.
*/ */
data class VaultData( data class VaultData(
val offlineCipherViewList: List<CipherView>, val offlineCipherViewList: List<OfflineCipherView>,
val cipherViewList: List<CipherView>, val cipherViewList: List<CipherView>,
val collectionViewList: List<CollectionView>, val collectionViewList: List<CollectionView>,
val folderViewList: List<FolderView>, val folderViewList: List<FolderView>,

View file

@ -4,7 +4,9 @@ package com.x8bit.bitwarden.data.vault.repository.util
import com.bitwarden.core.DateTime import com.bitwarden.core.DateTime
import com.bitwarden.vault.Attachment import com.bitwarden.vault.Attachment
import com.bitwarden.vault.AttachmentView
import com.bitwarden.vault.Card import com.bitwarden.vault.Card
import com.bitwarden.vault.CardView
import com.bitwarden.vault.Cipher import com.bitwarden.vault.Cipher
import com.bitwarden.vault.CipherRepromptType import com.bitwarden.vault.CipherRepromptType
import com.bitwarden.vault.CipherType import com.bitwarden.vault.CipherType
@ -12,12 +14,18 @@ import com.bitwarden.vault.CipherView
import com.bitwarden.vault.Fido2Credential import com.bitwarden.vault.Fido2Credential
import com.bitwarden.vault.Field import com.bitwarden.vault.Field
import com.bitwarden.vault.FieldType import com.bitwarden.vault.FieldType
import com.bitwarden.vault.FieldView
import com.bitwarden.vault.Identity import com.bitwarden.vault.Identity
import com.bitwarden.vault.IdentityView
import com.bitwarden.vault.LocalDataView
import com.bitwarden.vault.Login import com.bitwarden.vault.Login
import com.bitwarden.vault.LoginUri import com.bitwarden.vault.LoginUri
import com.bitwarden.vault.LoginView
import com.bitwarden.vault.PasswordHistory import com.bitwarden.vault.PasswordHistory
import com.bitwarden.vault.PasswordHistoryView
import com.bitwarden.vault.SecureNote import com.bitwarden.vault.SecureNote
import com.bitwarden.vault.SecureNoteType import com.bitwarden.vault.SecureNoteType
import com.bitwarden.vault.SecureNoteView
import com.bitwarden.vault.UriMatchType import com.bitwarden.vault.UriMatchType
import com.x8bit.bitwarden.data.platform.util.SpecialCharWithPrecedenceComparator import com.x8bit.bitwarden.data.platform.util.SpecialCharWithPrecedenceComparator
import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonRequest import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonRequest
@ -31,6 +39,7 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.SecureNoteTypeJso
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.UriMatchTypeJson import com.x8bit.bitwarden.data.vault.datasource.network.model.UriMatchTypeJson
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.OfflineCipher import com.x8bit.bitwarden.data.vault.datasource.sdk.model.OfflineCipher
import com.x8bit.bitwarden.data.vault.repository.model.OfflineCipherView
import com.x8bit.bitwarden.ui.vault.feature.vault.model.NotificationSummary import com.x8bit.bitwarden.ui.vault.feature.vault.model.NotificationSummary
import java.time.ZoneOffset import java.time.ZoneOffset
import java.time.ZonedDateTime import java.time.ZonedDateTime
@ -63,7 +72,7 @@ fun Cipher.toEncryptedNetworkCipher(): CipherJsonRequest =
) )
fun Cipher.toOfflineCipher(): OfflineCipher = fun Cipher.toOfflineCipher(): OfflineCipher =
OfflineCipher ( OfflineCipher(
id = id, id = id,
organizationId = organizationId, organizationId = organizationId,
folderId = folderId, folderId = folderId,
@ -108,19 +117,51 @@ fun OfflineCipher.toOfflineCipherJson(): OfflineCipherJson =
passwordHistory = passwordHistory?.toEncryptedNetworkPasswordHistoryList(), passwordHistory = passwordHistory?.toEncryptedNetworkPasswordHistoryList(),
creationDate = ZonedDateTime.ofInstant(creationDate, ZoneOffset.UTC), creationDate = ZonedDateTime.ofInstant(creationDate, ZoneOffset.UTC),
deletedDate = deletedDate?.let { ZonedDateTime.ofInstant(deletedDate, ZoneOffset.UTC) }, deletedDate = deletedDate?.let { ZonedDateTime.ofInstant(deletedDate, ZoneOffset.UTC) },
revisionDate = ZonedDateTime.ofInstant(creationDate, ZoneOffset.UTC), revisionDate = ZonedDateTime.ofInstant(creationDate, ZoneOffset.UTC),
mergeConflict = false, // TODO: Copy from the new OfflineCipher type mergeConflict = false, // TODO: Copy from the new OfflineCipher type
) )
fun CipherView.toNotificationSummary(): NotificationSummary = fun OfflineCipherView.toNotificationSummary(): NotificationSummary =
NotificationSummary( NotificationSummary(
title = name, title = name,
subtitle = "edited on ${revisionDate.toString()}", subtitle = "edited on $revisionDate. Has Merge Conflict: $mergeConflict",
)
fun CipherView.toOfflineCipherView(offlineCipher: OfflineCipher) =
OfflineCipherView(
id = id,
organizationId = organizationId,
folderId = folderId,
collectionIds = collectionIds,
/**
* Temporary, required to support re-encrypting existing items.
*/
key = key,
name = name,
notes = notes,
type = type,
login = login,
identity = identity,
card = card,
secureNote = secureNote,
favorite = favorite,
reprompt = reprompt,
organizationUseTotp = organizationUseTotp,
edit = edit,
viewPassword = viewPassword,
localData = localData,
attachments = attachments,
fields = fields,
passwordHistory = passwordHistory,
creationDate = creationDate,
deletedDate = deletedDate,
revisionDate = revisionDate,
mergeConflict = offlineCipher.mergeConflict
) )
fun OfflineCipherJson.toOfflineCipher(): OfflineCipher = fun OfflineCipherJson.toOfflineCipher(): OfflineCipher =
OfflineCipher( OfflineCipher(
id = if(id.startsWith("create")) null else id, id = if (id.startsWith("create")) null else id,
organizationId = organizationId, organizationId = organizationId,
folderId = folderId, folderId = folderId,
collectionIds = collectionIds.orEmpty(), collectionIds = collectionIds.orEmpty(),
@ -143,8 +184,6 @@ fun OfflineCipherJson.toOfflineCipher(): OfflineCipher =
mergeConflict = mergeConflict mergeConflict = mergeConflict
) )
fun OfflineCipher.toCipher(): Cipher = fun OfflineCipher.toCipher(): Cipher =
Cipher( Cipher(
id = id, id = id,
@ -440,7 +479,7 @@ fun List<SyncResponseJson.Cipher>.toEncryptedSdkCipherList(): List<Cipher> =
* Converts a list of [OfflineCipherJson] objects to a list of corresponding * Converts a list of [OfflineCipherJson] objects to a list of corresponding
* Bitwarden SDK [Cipher] objects. * Bitwarden SDK [Cipher] objects.
*/ */
fun List<OfflineCipherJson>.toCipherList(): List<Cipher> = fun List<OfflineCipherJson>.toCipherList(): List<Cipher> =
map { it.toOfflineCipher().toCipher() } map { it.toOfflineCipher().toCipher() }
/** /**