BIT-897 Decrypt sync response (#181)

This commit is contained in:
Ramsey Smith
2023-10-31 08:06:36 -06:00
committed by Álison Fernandes
parent 4a3e88f939
commit b31b859516
27 changed files with 1814 additions and 47 deletions

View File

@@ -1,7 +1,8 @@
package com.x8bit.bitwarden.data.vault.datasource.network.di
import com.x8bit.bitwarden.data.vault.datasource.network.service.SyncService
import com.x8bit.bitwarden.data.vault.datasource.network.service.SyncServiceImpl
import com.x8bit.bitwarden.data.platform.datasource.network.retrofit.Retrofits
import com.x8bit.bitwarden.data.vault.datasource.network.api.SyncApi
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@@ -18,7 +19,9 @@ object VaultNetworkModule {
@Provides
@Singleton
fun provideSyncApiService(
fun provideSyncService(
retrofits: Retrofits,
): SyncApi = retrofits.authenticatedApiRetrofit.create()
): SyncService = SyncServiceImpl(
syncApi = retrofits.authenticatedApiRetrofit.create(),
)
}

View File

@@ -8,19 +8,19 @@ import kotlinx.serialization.Serializable
* Represents different fields that a custom cipher field can be linked to.
*/
@Serializable(LinkedIdTypeSerializer::class)
enum class LinkedIdTypeJson {
enum class LinkedIdTypeJson(val value: UInt) {
// region LOGIN
/**
* The field is linked to the login's username.
*/
@SerialName("100")
LOGIN_USERNAME,
LOGIN_USERNAME(value = 100U),
/**
* The field is linked to the login's password.
*/
@SerialName("101")
LOGIN_PASSWORD,
LOGIN_PASSWORD(value = 101U),
// endregion LOGIN
// region CARD
@@ -28,37 +28,37 @@ enum class LinkedIdTypeJson {
* The field is linked to the card's cardholder name.
*/
@SerialName("300")
CARD_CARDHOLDER_NAME,
CARD_CARDHOLDER_NAME(value = 300U),
/**
* The field is linked to the card's expiration month.
*/
@SerialName("301")
CARD_EXP_MONTH,
CARD_EXP_MONTH(value = 301U),
/**
* The field is linked to the card's expiration year.
*/
@SerialName("302")
CARD_EXP_YEAR,
CARD_EXP_YEAR(value = 302U),
/**
* The field is linked to the card's code.
*/
@SerialName("303")
CARD_CODE,
CARD_CODE(value = 303U),
/**
* The field is linked to the card's brand.
*/
@SerialName("304")
CARD_BRAND,
CARD_BRAND(value = 304U),
/**
* The field is linked to the card's number.
*/
@SerialName("305")
CARD_NUMBER,
CARD_NUMBER(value = 305U),
// endregion CARD
// region IDENTITY
@@ -66,115 +66,115 @@ enum class LinkedIdTypeJson {
* The field is linked to the identity's title.
*/
@SerialName("400")
IDENTITY_TITLE,
IDENTITY_TITLE(value = 400U),
/**
* The field is linked to the identity's middle name.
*/
@SerialName("401")
IDENTITY_MIDDLE_NAME,
IDENTITY_MIDDLE_NAME(value = 401U),
/**
* The field is linked to the identity's address line 1.
*/
@SerialName("402")
IDENTITY_ADDRESS_1,
IDENTITY_ADDRESS_1(value = 402U),
/**
* The field is linked to the identity's address line 2.
*/
@SerialName("403")
IDENTITY_ADDRESS_2,
IDENTITY_ADDRESS_2(value = 403U),
/**
* The field is linked to the identity's address line 3.
*/
@SerialName("404")
IDENTITY_ADDRESS_3,
IDENTITY_ADDRESS_3(value = 404U),
/**
* The field is linked to the identity's city.
*/
@SerialName("405")
IDENTITY_CITY,
IDENTITY_CITY(value = 405U),
/**
* The field is linked to the identity's state.
*/
@SerialName("406")
IDENTITY_STATE,
IDENTITY_STATE(value = 406U),
/**
* The field is linked to the identity's postal code
*/
@SerialName("407")
IDENTITY_POSTAL_CODE,
IDENTITY_POSTAL_CODE(value = 407U),
/**
* The field is linked to the identity's country.
*/
@SerialName("408")
IDENTITY_COUNTRY,
IDENTITY_COUNTRY(value = 408U),
/**
* The field is linked to the identity's company.
*/
@SerialName("409")
IDENTITY_COMPANY,
IDENTITY_COMPANY(value = 409U),
/**
* The field is linked to the identity's email.
*/
@SerialName("410")
IDENTITY_EMAIL,
IDENTITY_EMAIL(value = 410U),
/**
* The field is linked to the identity's phone.
*/
@SerialName("411")
IDENTITY_PHONE,
IDENTITY_PHONE(value = 411U),
/**
* The field is linked to the identity's SSN.
*/
@SerialName("412")
IDENTITY_SSN,
IDENTITY_SSN(value = 412U),
/**
* The field is linked to the identity's username.
*/
@SerialName("413")
IDENTITY_USERNAME,
IDENTITY_USERNAME(value = 413U),
/**
* The field is linked to the identity's passport number.
*/
@SerialName("414")
IDENTITY_PASSPORT_NUMBER,
IDENTITY_PASSPORT_NUMBER(value = 414U),
/**
* The field is linked to the identity's license number.
*/
@SerialName("415")
IDENTITY_LICENSE_NUMBER,
IDENTITY_LICENSE_NUMBER(value = 415U),
/**
* The field is linked to the identity's first name.
*/
@SerialName("416")
IDENTITY_FIRST_NAME,
IDENTITY_FIRST_NAME(value = 416U),
/**
* The field is linked to the identity's last name.
*/
@SerialName("417")
IDENTITY_LAST_NAME,
IDENTITY_LAST_NAME(value = 417U),
/**
* The field is linked to the identity's full name.
*/
@SerialName("418")
IDENTITY_FULL_NAME,
IDENTITY_FULL_NAME(value = 418U),
// endregion IDENTITY
}

View File

@@ -77,7 +77,7 @@ data class SyncResponseJson(
/**
* Represents a folder in the vault response.
*
* @property revisionDate The revision date of the folder (nullable).
* @property revisionDate The revision date of the folder.
* @property name The name of the folder (nullable).
* @property id The ID of the folder.
*/
@@ -85,7 +85,7 @@ data class SyncResponseJson(
data class Folder(
@SerialName("revisionDate")
@Contextual
val revisionDate: LocalDateTime?, // Date
val revisionDate: LocalDateTime,
@SerialName("name")
val name: String?,
@@ -236,7 +236,7 @@ data class SyncResponseJson(
* @property shouldUseActivateAutofillPolicy If the organization should
* use auto fill policy.
* @property shouldUseEvents If the organization should use events.
* @property isFamilySponsorshipFriendlyName If the family sponsorship is a friendly name.
* @property familySponsorshipFriendlyName If the family sponsorship is a friendly name.
* @property isKeyConnectorEnabled If the key connector is enabled.
* @property shouldUseTotp If he organization should use TOTP.
* @property familySponsorshipLastSyncDate The last date the family sponsorship
@@ -351,7 +351,7 @@ data class SyncResponseJson(
val shouldUseEvents: Boolean,
@SerialName("familySponsorshipFriendlyName")
val isFamilySponsorshipFriendlyName: String?,
val familySponsorshipFriendlyName: String?,
@SerialName("keyConnectorEnabled")
val isKeyConnectorEnabled: Boolean,
@@ -376,7 +376,8 @@ data class SyncResponseJson(
val isSsoBound: Boolean,
@SerialName("familySponsorshipValidUntil")
val familySponsorshipValidUntil: String?,
@Contextual
val familySponsorshipValidUntil: LocalDateTime?,
@SerialName("status")
val status: Int,
@@ -499,10 +500,10 @@ data class SyncResponseJson(
* @property shouldEdit If the cipher can edit.
* @property passwordHistory A list of password history objects
* associated with the cipher (nullable).
* @property revisionDate The revision date of the cipher (nullable).
* @property revisionDate The revision date of the cipher.
* @property type The type of cipher.
* @property login The login of the cipher.
* @property creationDate The creation date of the cipher (nullable).
* @property creationDate The creation date of the cipher.
* @property secureNote The secure note of the cipher.
* @property folderId The folder ID of the cipher (nullable).
* @property organizationId The organization ID of the cipher (nullable).
@@ -538,20 +539,20 @@ data class SyncResponseJson(
@SerialName("revisionDate")
@Contextual
val revisionDate: LocalDateTime?,
val revisionDate: LocalDateTime,
@SerialName("type")
val type: CipherTypeJson,
@SerialName("login")
val login: Login,
val login: Login?,
@SerialName("creationDate")
@Contextual
val creationDate: LocalDateTime?,
val creationDate: LocalDateTime,
@SerialName("secureNote")
val secureNote: SecureNote,
val secureNote: SecureNote?,
@SerialName("folderId")
val folderId: String?,
@@ -564,7 +565,7 @@ data class SyncResponseJson(
val deletedDate: LocalDateTime?,
@SerialName("identity")
val identity: Identity,
val identity: Identity?,
@SerialName("collectionIds")
val collectionIds: List<String>?,
@@ -585,7 +586,7 @@ data class SyncResponseJson(
val isFavorite: Boolean,
@SerialName("card")
val card: Card,
val card: Card?,
) {
/**
* Represents an attachment in the vault response.
@@ -795,7 +796,7 @@ data class SyncResponseJson(
@Serializable
data class Uri(
@SerialName("match")
val uriMatchType: UriMatchTypeJson,
val uriMatchType: UriMatchTypeJson?,
@SerialName("uri")
val uri: String?,

View File

@@ -0,0 +1,13 @@
package com.x8bit.bitwarden.data.vault.datasource.network.service
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
/**
* Provides an API for querying sync endpoints.
*/
interface SyncService {
/**
* Make sync request to get vault items.
*/
suspend fun sync(): Result<SyncResponseJson>
}

View File

@@ -0,0 +1,10 @@
package com.x8bit.bitwarden.data.vault.datasource.network.service
import com.x8bit.bitwarden.data.vault.datasource.network.api.SyncApi
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
class SyncServiceImpl constructor(
private val syncApi: SyncApi,
) : SyncService {
override suspend fun sync(): Result<SyncResponseJson> = syncApi.sync()
}

View File

@@ -0,0 +1,32 @@
package com.x8bit.bitwarden.data.vault.datasource.sdk
import com.bitwarden.core.Cipher
import com.bitwarden.core.CipherListView
import com.bitwarden.core.CipherView
import com.bitwarden.core.Folder
import com.bitwarden.core.FolderView
/**
* Source of vault information and functionality from the Bitwarden SDK.
*/
interface VaultSdkSource {
/**
* Decrypts a [Cipher] returning a [CipherView] wrapped in a [Result].
*/
suspend fun decryptCipher(cipher: Cipher): Result<CipherView>
/**
* Decrypts a list of [Cipher]s returning a list of [CipherListView] wrapped in a [Result].
*/
suspend fun decryptCipherList(cipherList: List<Cipher>): Result<List<CipherListView>>
/**
* Decrypts a [Folder] returning a [FolderView] wrapped in a [Result].
*/
suspend fun decryptFolder(folder: Folder): Result<FolderView>
/**
* Decrypts a list of [Folder]s returning a list of [FolderView] wrapped in a [Result].
*/
suspend fun decryptFolderList(folderList: List<Folder>): Result<List<FolderView>>
}

View File

@@ -0,0 +1,29 @@
package com.x8bit.bitwarden.data.vault.datasource.sdk
import com.bitwarden.core.Cipher
import com.bitwarden.core.CipherListView
import com.bitwarden.core.CipherView
import com.bitwarden.core.Folder
import com.bitwarden.core.FolderView
import com.bitwarden.sdk.ClientVault
/**
* Primary implementation of [VaultSdkSource] that serves as a convenience wrapper around a
* [ClientVault].
*/
class VaultSdkSourceImpl(
private val clientVault: ClientVault,
) : VaultSdkSource {
override suspend fun decryptCipher(cipher: Cipher): Result<CipherView> =
runCatching { clientVault.ciphers().decrypt(cipher) }
override suspend fun decryptCipherList(cipherList: List<Cipher>): Result<List<CipherListView>> =
runCatching { clientVault.ciphers().decryptList(cipherList) }
override suspend fun decryptFolder(folder: Folder): Result<FolderView> =
runCatching { clientVault.folders().decrypt(folder) }
override suspend fun decryptFolderList(folderList: List<Folder>): Result<List<FolderView>> =
runCatching { clientVault.folders().decryptList(folderList) }
}

View File

@@ -0,0 +1,24 @@
package com.x8bit.bitwarden.data.vault.datasource.sdk.di
import com.bitwarden.sdk.Client
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSourceImpl
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
/**
* Provides SDK-related dependencies for the vault package.
*/
@Module
@InstallIn(SingletonComponent::class)
class VaultSdkModule {
@Provides
@Singleton
fun providesVaultSdkSource(
client: Client,
): VaultSdkSource = VaultSdkSourceImpl(clientVault = client.vault())
}

View File

@@ -0,0 +1,12 @@
package com.x8bit.bitwarden.data.vault.repository
/**
* Responsible for managing vault data inside the network layer.
*/
interface VaultRepository {
/**
* Attempt to sync the vault data.
*/
suspend fun sync()
}

View File

@@ -0,0 +1,52 @@
package com.x8bit.bitwarden.data.vault.repository
import com.x8bit.bitwarden.data.vault.datasource.network.service.SyncService
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkCipherList
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkFolderList
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
/**
* Default implementation of [VaultRepository].
*/
class VaultRepositoryImpl constructor(
private val syncService: SyncService,
private val vaultSdkSource: VaultSdkSource,
dispatcher: CoroutineDispatcher,
) : VaultRepository {
private val scope = CoroutineScope(dispatcher)
private var syncJob: Job = Job().apply { complete() }
override suspend fun sync() {
if (!syncJob.isCompleted) return
syncJob = scope.launch {
syncService
.sync()
.fold(
onSuccess = { syncResponse ->
// TODO transform into domain object consumable by VaultViewModel BIT-205.
// TODO initialize crypto in BIT-990
syncResponse.ciphers?.let { networkCiphers ->
vaultSdkSource.decryptCipherList(
cipherList = networkCiphers.toEncryptedSdkCipherList(),
)
}
syncResponse.folders?.let { networkFolders ->
vaultSdkSource.decryptFolderList(
folderList = networkFolders.toEncryptedSdkFolderList(),
)
}
},
onFailure = {
// TODO handle failure BIT-205.
},
)
}
}
}

View File

@@ -0,0 +1,31 @@
package com.x8bit.bitwarden.data.vault.repository.di
import com.x8bit.bitwarden.data.vault.datasource.network.service.SyncService
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.data.vault.repository.VaultRepositoryImpl
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.Dispatchers
import javax.inject.Singleton
/**
* Provides repositories in the vault package.
*/
@Module
@InstallIn(SingletonComponent::class)
class VaultRepositoryModule {
@Provides
@Singleton
fun providesVaultRepository(
syncService: SyncService,
vaultSdkSource: VaultSdkSource,
): VaultRepository = VaultRepositoryImpl(
syncService = syncService,
vaultSdkSource = vaultSdkSource,
dispatcher = Dispatchers.IO,
)
}

View File

@@ -0,0 +1,243 @@
@file:Suppress("TooManyFunctions")
package com.x8bit.bitwarden.data.vault.repository.util
import com.bitwarden.core.Attachment
import com.bitwarden.core.Card
import com.bitwarden.core.Cipher
import com.bitwarden.core.CipherRepromptType
import com.bitwarden.core.CipherType
import com.bitwarden.core.Field
import com.bitwarden.core.FieldType
import com.bitwarden.core.Identity
import com.bitwarden.core.Login
import com.bitwarden.core.LoginUri
import com.bitwarden.core.PasswordHistory
import com.bitwarden.core.SecureNote
import com.bitwarden.core.SecureNoteType
import com.bitwarden.core.UriMatchType
import com.x8bit.bitwarden.data.vault.datasource.network.model.CipherRepromptTypeJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.CipherTypeJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.FieldTypeJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.SecureNoteTypeJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.UriMatchTypeJson
import java.time.ZoneOffset
/**
* Converts a list of [SyncResponseJson.Cipher] objects to a list of corresponding
* Bitwarden SDK [Cipher] objects.
*/
fun List<SyncResponseJson.Cipher>.toEncryptedSdkCipherList(): List<Cipher> =
map { it.toEncryptedSdkCipher() }
/**
* Converts a of [SyncResponseJson.Cipher] object to a corresponding
* Bitwarden SDK [Cipher] object.
*/
fun SyncResponseJson.Cipher.toEncryptedSdkCipher(): Cipher =
Cipher(
id = id,
organizationId = organizationId,
folderId = folderId,
collectionIds = collectionIds.orEmpty(),
name = name.orEmpty(),
notes = notes,
type = type.toSdkCipherType(),
login = login?.toSdkLogin(),
identity = identity?.toSdkIdentity(),
card = card?.toSdkCard(),
secureNote = secureNote?.toSdkSecureNote(),
favorite = isFavorite,
reprompt = reprompt.toSdkRepromptType(),
organizationUseTotp = shouldOrganizationUseTotp,
edit = shouldEdit,
viewPassword = shouldViewPassword,
localData = null,
attachments = attachments?.toSdkAttachmentList(),
fields = fields?.toSdkFieldList(),
passwordHistory = passwordHistory?.toSdkPasswordHistoryList(),
creationDate = creationDate.toInstant(ZoneOffset.UTC),
deletedDate = deletedDate?.toInstant(ZoneOffset.UTC),
revisionDate = revisionDate.toInstant(ZoneOffset.UTC),
)
/**
* Transforms a [SyncResponseJson.Cipher.Login] into the corresponding Bitwarden SDK [Login].
*/
fun SyncResponseJson.Cipher.Login.toSdkLogin(): Login =
Login(
username = username,
password = password,
passwordRevisionDate = passwordRevisionDate?.toInstant(ZoneOffset.UTC),
uris = uris?.toSdkLoginUriList(),
totp = totp,
autofillOnPageLoad = shouldAutofillOnPageLoad,
)
/**
* Transforms a [SyncResponseJson.Cipher.Identity] into the corresponding Bitwarden SDK [Identity].
*/
fun SyncResponseJson.Cipher.Identity.toSdkIdentity(): Identity =
Identity(
title = title,
middleName = middleName,
firstName = firstName,
lastName = lastName,
address1 = address1,
address2 = address2,
address3 = address3,
city = city,
state = state,
postalCode = postalCode,
country = country,
company = company,
email = email,
phone = phone,
ssn = ssn,
username = username,
passportNumber = passportNumber,
licenseNumber = licenseNumber,
)
/**
* Transforms a [SyncResponseJson.Cipher.Card] into the corresponding Bitwarden SDK [Card].
*/
fun SyncResponseJson.Cipher.Card.toSdkCard(): Card =
Card(
cardholderName = cardholderName,
expMonth = expMonth,
expYear = expirationYear,
code = code,
brand = brand,
number = number,
)
/**
* Transforms a [SyncResponseJson.Cipher.SecureNote] into
* the corresponding Bitwarden SDK [SecureNote].
*/
fun SyncResponseJson.Cipher.SecureNote.toSdkSecureNote(): SecureNote =
SecureNote(
type = when (type) {
SecureNoteTypeJson.GENERIC -> SecureNoteType.GENERIC
},
)
/**
* Transforms a list of [SyncResponseJson.Cipher.Login.Uri] into
* a corresponding list of Bitwarden SDK [LoginUri].
*/
fun List<SyncResponseJson.Cipher.Login.Uri>.toSdkLoginUriList(): List<LoginUri> =
map { it.toSdkLoginUri() }
/**
* Transforms a [SyncResponseJson.Cipher.Login.Uri] into
* a corresponding Bitwarden SDK [LoginUri].
*/
fun SyncResponseJson.Cipher.Login.Uri.toSdkLoginUri(): LoginUri =
LoginUri(
uri = uri,
match = uriMatchType?.toSdkMatchType(),
)
/**
* Transforms a list of [SyncResponseJson.Cipher.Attachment] into
* a corresponding list of Bitwarden SDK [Attachment].
*/
fun List<SyncResponseJson.Cipher.Attachment>.toSdkAttachmentList(): List<Attachment> =
map { it.toSdkAttachment() }
/**
* Transforms a [SyncResponseJson.Cipher.Attachment] into
* a corresponding Bitwarden SDK [Attachment].
*/
fun SyncResponseJson.Cipher.Attachment.toSdkAttachment(): Attachment =
Attachment(
id = id,
url = url,
size = size.toString(),
sizeName = sizeName,
fileName = fileName,
key = key,
)
/**
* Transforms a list of [SyncResponseJson.Cipher.Field] into
* a corresponding list of Bitwarden SDK [Field].
*/
fun List<SyncResponseJson.Cipher.Field>.toSdkFieldList(): List<Field> =
map { it.toSdkField() }
/**
* Transforms a [SyncResponseJson.Cipher.Field] into
* a corresponding Bitwarden SDK [Field].
*/
fun SyncResponseJson.Cipher.Field.toSdkField(): Field =
Field(
name = name,
value = value,
type = type.toSdkFieldType(),
linkedId = linkedIdType?.value,
)
/**
* Transforms a list of [SyncResponseJson.Cipher.PasswordHistory] into
* a corresponding list of Bitwarden SDK [PasswordHistory].
*/
@Suppress("MaxLineLength")
fun List<SyncResponseJson.Cipher.PasswordHistory>.toSdkPasswordHistoryList(): List<PasswordHistory> =
map { it.toSdkPasswordHistory() }
/**
* Transforms a [SyncResponseJson.Cipher.PasswordHistory] into
* a corresponding Bitwarden SDK [PasswordHistory].
*/
fun SyncResponseJson.Cipher.PasswordHistory.toSdkPasswordHistory(): PasswordHistory =
PasswordHistory(
password = password,
lastUsedDate = lastUsedDate.toInstant(ZoneOffset.UTC),
)
/**
* Transforms a [CipherTypeJson] to the corresponding Bitwarden SDK [CipherType].
*/
fun CipherTypeJson.toSdkCipherType(): CipherType =
when (this) {
CipherTypeJson.LOGIN -> CipherType.LOGIN
CipherTypeJson.SECURE_NOTE -> CipherType.SECURE_NOTE
CipherTypeJson.CARD -> CipherType.CARD
CipherTypeJson.IDENTITY -> CipherType.IDENTITY
}
/**
* Transforms a [UriMatchTypeJson] to the corresponding Bitwarden SDK [UriMatchType].
*/
fun UriMatchTypeJson.toSdkMatchType(): UriMatchType =
when (this) {
UriMatchTypeJson.DOMAIN -> UriMatchType.DOMAIN
UriMatchTypeJson.HOST -> UriMatchType.HOST
UriMatchTypeJson.STARTS_WITH -> UriMatchType.STARTS_WITH
UriMatchTypeJson.EXACT -> UriMatchType.EXACT
UriMatchTypeJson.REGULAR_EXPRESSION -> UriMatchType.REGULAR_EXPRESSION
UriMatchTypeJson.NEVER -> UriMatchType.NEVER
}
/**
* Transforms a [CipherRepromptTypeJson] to the corresponding Bitwarden SDK [CipherRepromptType].
*/
fun CipherRepromptTypeJson.toSdkRepromptType(): CipherRepromptType =
when (this) {
CipherRepromptTypeJson.NONE -> CipherRepromptType.NONE
CipherRepromptTypeJson.PASSWORD -> CipherRepromptType.PASSWORD
}
/**
* Transforms a [FieldTypeJson] to the corresponding Bitwarden SDK [FieldType].
*/
fun FieldTypeJson.toSdkFieldType(): FieldType =
when (this) {
FieldTypeJson.TEXT -> FieldType.TEXT
FieldTypeJson.HIDDEN -> FieldType.HIDDEN
FieldTypeJson.BOOLEAN -> FieldType.BOOLEAN
FieldTypeJson.LINKED -> FieldType.LINKED
}

View File

@@ -0,0 +1,23 @@
package com.x8bit.bitwarden.data.vault.repository.util
import com.bitwarden.core.Folder
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
import java.time.ZoneOffset
/**
* Converts a list of [SyncResponseJson.Folder] objects to a list of corresponding
* Bitwarden SDK [Folder] objects.
*/
fun List<SyncResponseJson.Folder>.toEncryptedSdkFolderList(): List<Folder> =
map { it.toEncryptedSdkFolder() }
/**
* Converts a [SyncResponseJson.Folder] objects to a corresponding
* Bitwarden SDK [Folder] object.
*/
fun SyncResponseJson.Folder.toEncryptedSdkFolder(): Folder =
Folder(
id = id,
name = name.orEmpty(),
revisionDate = revisionDate.toInstant(ZoneOffset.UTC),
)