From 10a20f626dc9c39b421bd82e4994b024bc25303c Mon Sep 17 00:00:00 2001 From: Ramsey Smith <142836716+ramsey-livefront@users.noreply.github.com> Date: Tue, 31 Oct 2023 08:06:36 -0600 Subject: [PATCH] BIT-897 Decrypt sync response (#181) --- .../network/di/VaultNetworkModule.kt | 9 +- .../network/model/LinkedIdTypeJson.kt | 56 +-- .../network/model/SyncResponseJson.kt | 29 +- .../datasource/network/service/SyncService.kt | 13 + .../network/service/SyncServiceImpl.kt | 10 + .../vault/datasource/sdk/VaultSdkSource.kt | 32 ++ .../datasource/sdk/VaultSdkSourceImpl.kt | 29 ++ .../vault/datasource/sdk/di/VaultSdkModule.kt | 24 ++ .../data/vault/repository/VaultRepository.kt | 12 + .../vault/repository/VaultRepositoryImpl.kt | 52 +++ .../repository/di/VaultRepositoryModule.kt | 31 ++ .../util/VaultSdkCipherExtensions.kt | 243 ++++++++++++ .../util/VaultSdkFolderExtensions.kt | 23 ++ .../data/platform/base/BaseServiceTest.kt | 6 +- .../network/model/SyncResponseCipherUtil.kt | 134 +++++++ .../model/SyncResponseCollectionUtil.kt | 14 + .../network/model/SyncResponseDomainsUtil.kt | 22 + .../network/model/SyncResponseFolderUtil.kt | 13 + .../network/model/SyncResponsePolicyUtil.kt | 12 + .../network/model/SyncResponseProfileUtil.kt | 115 ++++++ .../network/model/SyncResponseSendUtil.kt | 37 ++ .../network/service/SyncServiceTest.kt | 375 ++++++++++++++++++ .../datasource/sdk/VaultSdkCipherUtil.kt | 159 ++++++++ .../datasource/sdk/VaultSdkFolderUtil.kt | 17 + .../datasource/sdk/VaultSdkSourceTest.kt | 115 ++++++ .../util/VaultSdkCipherExtensionsTest.kt | 244 ++++++++++++ .../util/VaultSdkFolderExtensionsTest.kt | 35 ++ 27 files changed, 1814 insertions(+), 47 deletions(-) create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/service/SyncService.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/service/SyncServiceImpl.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSource.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceImpl.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/di/VaultSdkModule.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepository.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryImpl.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/vault/repository/di/VaultRepositoryModule.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensions.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkFolderExtensions.kt create mode 100644 app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseCipherUtil.kt create mode 100644 app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseCollectionUtil.kt create mode 100644 app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseDomainsUtil.kt create mode 100644 app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseFolderUtil.kt create mode 100644 app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponsePolicyUtil.kt create mode 100644 app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseProfileUtil.kt create mode 100644 app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseSendUtil.kt create mode 100644 app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/service/SyncServiceTest.kt create mode 100644 app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkCipherUtil.kt create mode 100644 app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkFolderUtil.kt create mode 100644 app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceTest.kt create mode 100644 app/src/test/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensionsTest.kt create mode 100644 app/src/test/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkFolderExtensionsTest.kt diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/di/VaultNetworkModule.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/di/VaultNetworkModule.kt index a143b81380..2d47e7f7c4 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/di/VaultNetworkModule.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/di/VaultNetworkModule.kt @@ -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(), + ) } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/LinkedIdTypeJson.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/LinkedIdTypeJson.kt index ad9b703e9a..c29d4d4aa9 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/LinkedIdTypeJson.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/LinkedIdTypeJson.kt @@ -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 } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseJson.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseJson.kt index ea080f88ba..f1cd6798b9 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseJson.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseJson.kt @@ -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?, @@ -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?, diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/service/SyncService.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/service/SyncService.kt new file mode 100644 index 0000000000..b39c97fdbd --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/service/SyncService.kt @@ -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 +} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/service/SyncServiceImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/service/SyncServiceImpl.kt new file mode 100644 index 0000000000..215b21b7e0 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/network/service/SyncServiceImpl.kt @@ -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 = syncApi.sync() +} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSource.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSource.kt new file mode 100644 index 0000000000..77e37a4ca4 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSource.kt @@ -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 + + /** + * Decrypts a list of [Cipher]s returning a list of [CipherListView] wrapped in a [Result]. + */ + suspend fun decryptCipherList(cipherList: List): Result> + + /** + * Decrypts a [Folder] returning a [FolderView] wrapped in a [Result]. + */ + suspend fun decryptFolder(folder: Folder): Result + + /** + * Decrypts a list of [Folder]s returning a list of [FolderView] wrapped in a [Result]. + */ + suspend fun decryptFolderList(folderList: List): Result> +} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceImpl.kt new file mode 100644 index 0000000000..8026d06eb7 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceImpl.kt @@ -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 = + runCatching { clientVault.ciphers().decrypt(cipher) } + + override suspend fun decryptCipherList(cipherList: List): Result> = + runCatching { clientVault.ciphers().decryptList(cipherList) } + + override suspend fun decryptFolder(folder: Folder): Result = + runCatching { clientVault.folders().decrypt(folder) } + + override suspend fun decryptFolderList(folderList: List): Result> = + runCatching { clientVault.folders().decryptList(folderList) } +} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/di/VaultSdkModule.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/di/VaultSdkModule.kt new file mode 100644 index 0000000000..a69896cdcd --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/sdk/di/VaultSdkModule.kt @@ -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()) +} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepository.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepository.kt new file mode 100644 index 0000000000..9929709dcd --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepository.kt @@ -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() +} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryImpl.kt new file mode 100644 index 0000000000..efba27e93d --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryImpl.kt @@ -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. + }, + ) + } + } +} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/di/VaultRepositoryModule.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/di/VaultRepositoryModule.kt new file mode 100644 index 0000000000..79e32ef0e2 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/di/VaultRepositoryModule.kt @@ -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, + ) +} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensions.kt new file mode 100644 index 0000000000..887500fbb9 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensions.kt @@ -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.toEncryptedSdkCipherList(): List = + 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.toSdkLoginUriList(): List = + 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.toSdkAttachmentList(): List = + 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.toSdkFieldList(): List = + 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.toSdkPasswordHistoryList(): List = + 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 + } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkFolderExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkFolderExtensions.kt new file mode 100644 index 0000000000..5fbf086f5f --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkFolderExtensions.kt @@ -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.toEncryptedSdkFolderList(): List = + 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), + ) diff --git a/app/src/test/java/com/x8bit/bitwarden/data/platform/base/BaseServiceTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/platform/base/BaseServiceTest.kt index 2d6e91bdbf..9018e90d8b 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/platform/base/BaseServiceTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/platform/base/BaseServiceTest.kt @@ -2,7 +2,7 @@ package com.x8bit.bitwarden.data.platform.base import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import com.x8bit.bitwarden.data.platform.datasource.network.core.ResultCallAdapterFactory -import kotlinx.serialization.json.Json +import com.x8bit.bitwarden.data.platform.datasource.network.di.PlatformNetworkModule import okhttp3.MediaType.Companion.toMediaType import okhttp3.mockwebserver.MockWebServer import org.junit.After @@ -13,12 +13,14 @@ import retrofit2.Retrofit */ abstract class BaseServiceTest { + private val json = PlatformNetworkModule.providesJson() + protected val server = MockWebServer().apply { start() } protected val retrofit: Retrofit = Retrofit.Builder() .baseUrl(server.url("/").toString()) .addCallAdapterFactory(ResultCallAdapterFactory()) - .addConverterFactory(Json.asConverterFactory("application/json".toMediaType())) + .addConverterFactory(json.asConverterFactory("application/json".toMediaType())) .build() @After diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseCipherUtil.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseCipherUtil.kt new file mode 100644 index 0000000000..60a85904f0 --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseCipherUtil.kt @@ -0,0 +1,134 @@ +package com.x8bit.bitwarden.data.vault.datasource.network.model + +import java.time.LocalDateTime + +/** + * Create a mock [SyncResponseJson.Cipher] with a given [number]. + */ +fun createMockCipher(number: Int): SyncResponseJson.Cipher = + SyncResponseJson.Cipher( + id = "mockId-$number", + organizationId = "mockOrganizationId-$number", + folderId = "mockFolderId-$number", + collectionIds = listOf("mockCollectionId-$number"), + name = "mockName-$number", + notes = "mockNotes-$number", + type = CipherTypeJson.LOGIN, + login = createMockLogin(number = number), + creationDate = LocalDateTime.parse("2023-10-27T12:00:00"), + deletedDate = LocalDateTime.parse("2023-10-27T12:00:00"), + revisionDate = LocalDateTime.parse("2023-10-27T12:00:00"), + attachments = listOf(createMockAttachment(number = number)), + card = createMockCard(number = number), + fields = listOf(createMockField(number = number)), + identity = createMockIdentity(number = number), + isFavorite = false, + passwordHistory = listOf(createMockPasswordHistory(number = number)), + reprompt = CipherRepromptTypeJson.NONE, + secureNote = createMockSecureNote(), + shouldEdit = false, + shouldOrganizationUseTotp = false, + shouldViewPassword = false, + ) + +/** + * Create a mock [SyncResponseJson.Cipher.Identity] with a given [number]. + */ +fun createMockIdentity(number: Int): SyncResponseJson.Cipher.Identity = + SyncResponseJson.Cipher.Identity( + firstName = "mockFirstName-$number", + middleName = "mockMiddleName-$number", + lastName = "mockLastName-$number", + passportNumber = "mockPassportNumber-$number", + country = "mockCountry-$number", + address1 = "mockAddress1-$number", + address2 = "mockAddress2-$number", + address3 = "mockAddress3-$number", + city = "mockCity-$number", + postalCode = "mockPostalCode-$number", + title = "mockTitle-$number", + ssn = "mockSsn-$number", + phone = "mockPhone-$number", + company = "mockCompany-$number", + licenseNumber = "mockLicenseNumber-$number", + state = "mockState-$number", + email = "mockEmail-$number", + username = "mockUsername-$number", + ) + +/** + * Create a mock [SyncResponseJson.Cipher.Attachment] with a given [number]. + */ +fun createMockAttachment(number: Int): SyncResponseJson.Cipher.Attachment = + SyncResponseJson.Cipher.Attachment( + fileName = "mockFileName-$number", + size = 1, + sizeName = "mockSizeName-$number", + id = "mockId-$number", + url = "mockUrl-$number", + key = "mockKey-$number", + ) + +/** + * Create a mock [SyncResponseJson.Cipher.Card] with a given [number]. + */ +fun createMockCard(number: Int): SyncResponseJson.Cipher.Card = + SyncResponseJson.Cipher.Card( + number = "mockNumber-$number", + expMonth = "mockExpMonth-$number", + code = "mockCode-$number", + expirationYear = "mockExpirationYear-$number", + cardholderName = "mockCardholderName-$number", + brand = "mockBrand-$number", + ) + +/** + * Create a mock [SyncResponseJson.Cipher.PasswordHistory] with a given [number]. + */ +fun createMockPasswordHistory(number: Int): SyncResponseJson.Cipher.PasswordHistory = + SyncResponseJson.Cipher.PasswordHistory( + password = "mockPassword-$number", + lastUsedDate = LocalDateTime.parse("2023-10-27T12:00:00"), + ) + +/** + * Create a mock [SyncResponseJson.Cipher.SecureNote]. + */ +fun createMockSecureNote(): SyncResponseJson.Cipher.SecureNote = + SyncResponseJson.Cipher.SecureNote( + type = SecureNoteTypeJson.GENERIC, + ) + +/** + * Create a mock [SyncResponseJson.Cipher.Field] with a given [number]. + */ +fun createMockField(number: Int): SyncResponseJson.Cipher.Field = + SyncResponseJson.Cipher.Field( + linkedIdType = LinkedIdTypeJson.LOGIN_USERNAME, + name = "mockName-$number", + type = FieldTypeJson.HIDDEN, + value = "mockValue-$number", + ) + +/** + * Create a mock [SyncResponseJson.Cipher.Login] with a given [number]. + */ +fun createMockLogin(number: Int): SyncResponseJson.Cipher.Login = + SyncResponseJson.Cipher.Login( + username = "mockUsername-$number", + password = "mockPassword-$number", + passwordRevisionDate = LocalDateTime.parse("2023-10-27T12:00:00"), + shouldAutofillOnPageLoad = false, + uri = "mockUri-$number", + uris = listOf(createMockUri(number = number)), + totp = "mockTotp-$number", + ) + +/** + * Create a mock [SyncResponseJson.Cipher.Login.Uri] with a given [number]. + */ +fun createMockUri(number: Int): SyncResponseJson.Cipher.Login.Uri = + SyncResponseJson.Cipher.Login.Uri( + uri = "mockUri-$number", + uriMatchType = UriMatchTypeJson.HOST, + ) diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseCollectionUtil.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseCollectionUtil.kt new file mode 100644 index 0000000000..b97b2ead13 --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseCollectionUtil.kt @@ -0,0 +1,14 @@ +package com.x8bit.bitwarden.data.vault.datasource.network.model + +/** + * Create a mock [SyncResponseJson.Collection] with a given [number]. + */ +fun createMockCollection(number: Int): SyncResponseJson.Collection = + SyncResponseJson.Collection( + organizationId = "mockOrganizationId-$number", + shouldHidePasswords = false, + name = "mockName-$number", + externalId = "mockExternalId-$number", + isReadOnly = false, + id = "mockId-$number", + ) diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseDomainsUtil.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseDomainsUtil.kt new file mode 100644 index 0000000000..5524a741d0 --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseDomainsUtil.kt @@ -0,0 +1,22 @@ +package com.x8bit.bitwarden.data.vault.datasource.network.model + +/** + * Create a mock [SyncResponseJson.Domains] with a given [number]. + */ +fun createMockDomains(number: Int): SyncResponseJson.Domains = + SyncResponseJson.Domains( + globalEquivalentDomains = listOf( + SyncResponseJson.Domains.GlobalEquivalentDomain( + isExcluded = false, + domains = listOf( + "mockDomain-$number", + ), + type = 1, + ), + ), + equivalentDomains = listOf( + listOf( + "mockEquivalentDomain-$number", + ), + ), + ) diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseFolderUtil.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseFolderUtil.kt new file mode 100644 index 0000000000..14f2c9a2d9 --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseFolderUtil.kt @@ -0,0 +1,13 @@ +package com.x8bit.bitwarden.data.vault.datasource.network.model + +import java.time.LocalDateTime + +/** + * Create a mock [SyncResponseJson.Folder] with a given [number]. + */ +fun createMockFolder(number: Int): SyncResponseJson.Folder = + SyncResponseJson.Folder( + id = "mockId-$number", + name = "mockName-$number", + revisionDate = LocalDateTime.parse("2023-10-27T12:00:00"), + ) diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponsePolicyUtil.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponsePolicyUtil.kt new file mode 100644 index 0000000000..4c1336602c --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponsePolicyUtil.kt @@ -0,0 +1,12 @@ +package com.x8bit.bitwarden.data.vault.datasource.network.model + +/** + * Create a mock [SyncResponseJson.Policy] with a given [number]. + */ +fun createMockPolicy(number: Int): SyncResponseJson.Policy = + SyncResponseJson.Policy( + organizationId = "mockOrganizationId-$number", + id = "mockId-$number", + type = PolicyTypeJson.MASTER_PASSWORD, + isEnabled = false, + ) diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseProfileUtil.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseProfileUtil.kt new file mode 100644 index 0000000000..3c42c89155 --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseProfileUtil.kt @@ -0,0 +1,115 @@ +package com.x8bit.bitwarden.data.vault.datasource.network.model + +import java.time.LocalDateTime + +/** + * Create a mock [SyncResponseJson.Profile] with a given [number]. + */ +fun createMockProfile(number: Int): SyncResponseJson.Profile = + SyncResponseJson.Profile( + providerOrganizations = listOf(createMockOrganization(number = number)), + isPremiumFromOrganization = false, + shouldForcePasswordReset = false, + avatarColor = "mockAvatarColor-$number", + isEmailVerified = false, + isTwoFactorEnabled = false, + privateKey = "mockPrivateKey-$number", + isPremium = false, + culture = "mockCulture-$number", + name = "mockName-$number", + organizations = listOf(createMockOrganization(number = number)), + shouldUseKeyConnector = false, + id = "mockId-$number", + masterPasswordHint = "mockMasterPasswordHint-$number", + email = "mockEmail-$number", + key = "mockKey-$number", + securityStamp = "mockSecurityStamp-$number", + providers = listOf(createMockProvider(number = number)), + ) + +/** + * Create a mock [SyncResponseJson.Profile.Organization] with a given [number]. + */ +fun createMockOrganization(number: Int): SyncResponseJson.Profile.Organization = + SyncResponseJson.Profile.Organization( + shouldUsePolicies = false, + keyConnectorUrl = "mockKeyConnectorUrl-$number", + type = 1, + seats = 1, + isEnabled = false, + providerType = 1, + isResetPasswordEnrolled = false, + shouldUseSecretsManager = false, + maxCollections = 1, + isSelfHost = false, + shouldUseKeyConnector = false, + permissions = createMockPermissions(), + hasPublicAndPrivateKeys = false, + providerId = "mockProviderId-$number", + id = "mockId-$number", + shouldUseGroups = false, + shouldUseDirectory = false, + key = "mockKey-$number", + providerName = "mockProviderName-$number", + shouldUsersGetPremium = false, + maxStorageGb = 1, + identifier = "mockIdentifier-$number", + shouldUseSso = false, + shouldUseCustomPermissions = false, + isFamilySponsorshipAvailable = false, + shouldUseResetPassword = false, + planProductType = 1, + accessSecretsManager = false, + use2fa = false, + familySponsorshipToDelete = false, + userId = "mockUserId-$number", + shouldUseActivateAutofillPolicy = false, + shouldUseEvents = false, + familySponsorshipFriendlyName = "mockFamilySponsorshipFriendlyName-$number", + isKeyConnectorEnabled = false, + shouldUseTotp = false, + familySponsorshipLastSyncDate = LocalDateTime.parse("2023-10-27T12:00:00"), + shouldUseScim = false, + name = "mockName-$number", + shouldUseApi = false, + isSsoBound = false, + familySponsorshipValidUntil = LocalDateTime.parse("2023-10-27T12:00:00"), + status = 1, + ) + +/** + * Create a mock [SyncResponseJson.Profile.Permissions]. + */ +fun createMockPermissions(): SyncResponseJson.Profile.Permissions = + SyncResponseJson.Profile.Permissions( + shouldManageGroups = false, + shouldManageResetPassword = false, + shouldAccessReports = false, + shouldManagePolicies = false, + shouldDeleteAnyCollection = false, + shouldManageSso = false, + shouldDeleteAssignedCollections = false, + shouldManageUsers = false, + shouldManageScim = false, + shouldAccessImportExport = false, + shouldEditAnyCollection = false, + shouldAccessEventLogs = false, + shouldCreateNewCollections = false, + shouldEditAssignedCollections = false, + ) + +/** + * Create a mock [SyncResponseJson.Profile.Provider] with a given [number]. + */ +fun createMockProvider(number: Int): SyncResponseJson.Profile.Provider = + SyncResponseJson.Profile.Provider( + shouldUseEvents = false, + permissions = createMockPermissions(), + name = "mockName-$number", + id = "mockId-$number", + type = 1, + userId = "mockUserId-$number", + key = "mockKey-$number", + isEnabled = false, + status = 1, + ) diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseSendUtil.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseSendUtil.kt new file mode 100644 index 0000000000..ce8efc38b6 --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/model/SyncResponseSendUtil.kt @@ -0,0 +1,37 @@ +package com.x8bit.bitwarden.data.vault.datasource.network.model + +import java.time.LocalDateTime + +fun createMockSend(number: Int): SyncResponseJson.Send = + SyncResponseJson.Send( + accessCount = 1, + notes = "mockNotes-$number", + revisionDate = LocalDateTime.parse("2023-10-27T12:00:00"), + maxAccessCount = 1, + shouldHideEmail = false, + type = SendTypeJson.FILE, + accessId = "mockAccessId-$number", + password = "mockPassword-$number", + file = createMockFile(number = 1), + deletionDate = LocalDateTime.parse("2023-10-27T12:00:00"), + name = "mockName-$number", + isDisabled = false, + id = "mockId-$number", + text = createMockText(number = number), + key = "mockKey-$number", + expirationDate = LocalDateTime.parse("2023-10-27T12:00:00"), + ) + +fun createMockFile(number: Int): SyncResponseJson.Send.File = + SyncResponseJson.Send.File( + fileName = "mockFileName-$number", + size = 1, + sizeName = "mockSizeName-$number", + id = "mockId-$number", + ) + +fun createMockText(number: Int): SyncResponseJson.Send.Text = + SyncResponseJson.Send.Text( + isHidden = false, + text = "mockText-$number", + ) diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/service/SyncServiceTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/service/SyncServiceTest.kt new file mode 100644 index 0000000000..ff2bf073cc --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/network/service/SyncServiceTest.kt @@ -0,0 +1,375 @@ +package com.x8bit.bitwarden.data.vault.datasource.network.service + +import com.x8bit.bitwarden.data.platform.base.BaseServiceTest +import com.x8bit.bitwarden.data.vault.datasource.network.api.SyncApi +import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson +import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockCipher +import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockCollection +import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockDomains +import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockFolder +import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockPolicy +import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockProfile +import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockSend +import kotlinx.coroutines.test.runTest +import okhttp3.mockwebserver.MockResponse +import org.junit.Test +import org.junit.jupiter.api.Assertions.assertEquals +import retrofit2.create + +class SyncServiceTest : BaseServiceTest() { + private val syncApi: SyncApi = retrofit.create() + + private val syncService: SyncService = SyncServiceImpl( + syncApi = syncApi, + ) + + @Test + fun `sync should return the correct response`() = runTest { + server.enqueue(MockResponse().setBody(SYNC_SUCCESS_JSON)) + val result = syncService.sync() + assertEquals(SYNC_SUCCESS, result.getOrThrow()) + } +} + +private const val SYNC_SUCCESS_JSON = """ +{ + "profile": { + "id": "mockId-1", + "name": "mockName-1", + "email": "mockEmail-1", + "emailVerified": false, + "premium": false, + "premiumFromOrganization": false, + "masterPasswordHint": "mockMasterPasswordHint-1", + "culture": "mockCulture-1", + "twoFactorEnabled": false, + "key": "mockKey-1", + "privateKey": "mockPrivateKey-1", + "securityStamp": "mockSecurityStamp-1", + "forcePasswordReset": false, + "usesKeyConnector": false, + "avatarColor": "mockAvatarColor-1", + "organizations": [ + { + "usePolicies": false, + "keyConnectorUrl": "mockKeyConnectorUrl-1", + "type": 1, + "seats": 1, + "enabled": false, + "providerType": 1, + "resetPasswordEnrolled": false, + "useSecretsManager": false, + "maxCollections": 1, + "selfHost": false, + "useKeyConnector": false, + "permissions": { + "manageGroups": false, + "manageResetPassword": false, + "accessReports": false, + "managePolicies": false, + "deleteAnyCollection": false, + "manageSso": false, + "deleteAssignedCollections": false, + "manageUsers": false, + "manageScim": false, + "accessImportExport": false, + "editAnyCollection": false, + "accessEventLogs": false, + "createNewCollections": false, + "editAssignedCollections": false + }, + "hasPublicAndPrivateKeys": false, + "providerId": "mockProviderId-1", + "id": "mockId-1", + "useGroups": false, + "useDirectory": false, + "key": "mockKey-1", + "providerName": "mockProviderName-1", + "usersGetPremium": false, + "maxStorageGb": 1, + "identifier": "mockIdentifier-1", + "useSso": false, + "useCustomPermissions": false, + "familySponsorshipAvailable": false, + "useResetPassword": false, + "planProductType": 1, + "accessSecretsManager": false, + "use2fa": false, + "familySponsorshipToDelete": false, + "userId": "mockUserId-1", + "useActivateAutofillPolicy": false, + "useEvents": false, + "familySponsorshipFriendlyName": "mockFamilySponsorshipFriendlyName-1", + "keyConnectorEnabled": false, + "useTotp": false, + "familySponsorshipLastSyncDate": "2023-10-27T12:00:00.00Z", + "useScim": false, + "name": "mockName-1", + "useApi": false, + "ssoBound": false, + "familySponsorshipValidUntil": "2023-10-27T12:00:00.00Z", + "status": 1 + } + ], + "providers": [ + { + "useEvents": false, + "permissions": { + "manageGroups": false, + "manageResetPassword": false, + "accessReports": false, + "managePolicies": false, + "deleteAnyCollection": false, + "manageSso": false, + "deleteAssignedCollections": false, + "manageUsers": false, + "manageScim": false, + "accessImportExport": false, + "editAnyCollection": false, + "accessEventLogs": false, + "createNewCollections": false, + "editAssignedCollections": false + }, + "name": "mockName-1", + "id": "mockId-1", + "type": 1, + "userId": "mockUserId-1", + "key": "mockKey-1", + "enabled": false, + "status": 1 + } + ], + "providerOrganizations": [ + { + "usePolicies": false, + "keyConnectorUrl": "mockKeyConnectorUrl-1", + "type": 1, + "seats": 1, + "enabled": false, + "providerType": 1, + "resetPasswordEnrolled": false, + "useSecretsManager": false, + "maxCollections": 1, + "selfHost": false, + "useKeyConnector": false, + "permissions": { + "manageGroups": false, + "manageResetPassword": false, + "accessReports": false, + "managePolicies": false, + "deleteAnyCollection": false, + "manageSso": false, + "deleteAssignedCollections": false, + "manageUsers": false, + "manageScim": false, + "accessImportExport": false, + "editAnyCollection": false, + "accessEventLogs": false, + "createNewCollections": false, + "editAssignedCollections": false + }, + "hasPublicAndPrivateKeys": false, + "providerId": "mockProviderId-1", + "id": "mockId-1", + "useGroups": false, + "useDirectory": false, + "key": "mockKey-1", + "providerName": "mockProviderName-1", + "usersGetPremium": false, + "maxStorageGb": 1, + "identifier": "mockIdentifier-1", + "useSso": false, + "useCustomPermissions": false, + "familySponsorshipAvailable": false, + "useResetPassword": false, + "planProductType": 1, + "accessSecretsManager": false, + "use2fa": false, + "familySponsorshipToDelete": false, + "userId": "mockUserId-1", + "useActivateAutofillPolicy": false, + "useEvents": false, + "familySponsorshipFriendlyName": "mockFamilySponsorshipFriendlyName-1", + "keyConnectorEnabled": false, + "useTotp": false, + "familySponsorshipLastSyncDate": "2023-10-27T12:00:00.00Z", + "useScim": false, + "name": "mockName-1", + "useApi": false, + "ssoBound": false, + "familySponsorshipValidUntil": "2023-10-27T12:00:00.00Z", + "status": 1 + } + ] + }, + "folders": [ + { + "revisionDate": "2023-10-27T12:00:00.00Z", + "name": "mockName-1", + "id": "mockId-1" + } + ], + "collections": [ + { + "organizationId": "mockOrganizationId-1", + "hidePasswords": false, + "name": "mockName-1", + "externalId": "mockExternalId-1", + "readOnly": false, + "id": "mockId-1" + } + ], + "ciphers": [ + { + "notes": "mockNotes-1", + "attachments": [ + { + "fileName": "mockFileName-1", + "size": 1, + "sizeName": "mockSizeName-1", + "id": "mockId-1", + "url": "mockUrl-1", + "key": "mockKey-1" + } + ], + "organizationUseTotp": false, + "reprompt": 0, + "edit": false, + "passwordHistory": [ + { + "password": "mockPassword-1", + "lastUsedDate": "2023-10-27T12:00:00.00Z" + } + ], + "revisionDate": "2023-10-27T12:00:00.00Z", + "type": 1, + "login": { + "uris": [ + { + "match": 1, + "uri": "mockUri-1" + } + ], + "totp": "mockTotp-1", + "password": "mockPassword-1", + "passwordRevisionDate": "2023-10-27T12:00:00.00Z", + "autofillOnPageLoad": false, + "uri": "mockUri-1", + "username": "mockUsername-1" + }, + "creationDate": "2023-10-27T12:00:00.00Z", + "secureNote": { + "type": 0 + }, + "folderId": "mockFolderId-1", + "organizationId": "mockOrganizationId-1", + "deletedDate": "2023-10-27T12:00:00.00Z", + "identity": { + "passportNumber": "mockPassportNumber-1", + "lastName": "mockLastName-1", + "address3": "mockAddress3-1", + "address2": "mockAddress2-1", + "city": "mockCity-1", + "country": "mockCountry-1", + "address1": "mockAddress1-1", + "postalCode": "mockPostalCode-1", + "title": "mockTitle-1", + "ssn": "mockSsn-1", + "firstName": "mockFirstName-1", + "phone": "mockPhone-1", + "middleName": "mockMiddleName-1", + "company": "mockCompany-1", + "licenseNumber": "mockLicenseNumber-1", + "state": "mockState-1", + "email": "mockEmail-1", + "username": "mockUsername-1" + }, + "collectionIds": [ + "mockCollectionId-1" + ], + "name": "mockName-1", + "id": "mockId-1" + "fields": [ + { + "linkedId": 100, + "name": "mockName-1", + "type": 1, + "value": "mockValue-1" + } + ], + "viewPassword": false, + "favorite": false, + "card": { + "number": "mockNumber-1", + "expMonth": "mockExpMonth-1", + "code": "mockCode-1", + "expYear": "mockExpirationYear-1", + "cardholderName": "mockCardholderName-1", + "brand": "mockBrand-1" + } + } + ], + "domains": { + "equivalentDomains": [ + [ + "mockEquivalentDomain-1" + ] + ], + "globalEquivalentDomains": [ + { + "type": 1, + "domains": [ + "mockDomain-1" + ], + "excluded": false + } + ] + }, + "policies": [ + { + "organizationId": "mockOrganizationId-1", + "id": "mockId-1", + "type": 1, + "enabled": false + } + ], + "sends": [ + { + "accessCount": 1, + "notes": "mockNotes-1", + "revisionDate": "2023-10-27T12:00:00.00Z", + "maxAccessCount": 1, + "hideEmail": false, + "type": 1, + "accessId": "mockAccessId-1", + "password": "mockPassword-1", + "file": { + "fileName": "mockFileName-1", + "size": 1, + "sizeName": "mockSizeName-1", + "id": "mockId-1" + }, + "deletionDate": "2023-10-27T12:00:00.00Z", + "name": "mockName-1", + "disabled": false, + "id": "mockId-1", + "text": { + "hidden": false, + "text": "mockText-1" + }, + "key": "mockKey-1", + "expirationDate": "2023-10-27T12:00:00.00Z" + } + ] +} +""" + +private val SYNC_SUCCESS = SyncResponseJson( + folders = listOf(createMockFolder(number = 1)), + collections = listOf(createMockCollection(number = 1)), + profile = createMockProfile(number = 1), + ciphers = listOf(createMockCipher(number = 1)), + policies = listOf(createMockPolicy(number = 1)), + domains = createMockDomains(number = 1), + sends = listOf(createMockSend(number = 1)), +) diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkCipherUtil.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkCipherUtil.kt new file mode 100644 index 0000000000..b06e4a3b89 --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkCipherUtil.kt @@ -0,0 +1,159 @@ +package com.x8bit.bitwarden.data.vault.datasource.sdk + +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 java.time.LocalDateTime +import java.time.ZoneOffset + +/** + * Create a mock [Cipher] with a given [number]. + */ +fun createMockSdkCipher(number: Int): Cipher = + Cipher( + id = "mockId-$number", + organizationId = "mockOrganizationId-$number", + folderId = "mockFolderId-$number", + collectionIds = listOf("mockCollectionId-$number"), + name = "mockName-$number", + notes = "mockNotes-$number", + type = CipherType.LOGIN, + login = createMockSdkLogin(number = number), + creationDate = LocalDateTime + .parse("2023-10-27T12:00:00") + .toInstant(ZoneOffset.UTC), + deletedDate = LocalDateTime + .parse("2023-10-27T12:00:00") + .toInstant(ZoneOffset.UTC), + revisionDate = LocalDateTime + .parse("2023-10-27T12:00:00") + .toInstant(ZoneOffset.UTC), + attachments = listOf(createMockSdkAttachment(number = number)), + card = createMockSdkCard(number = number), + fields = listOf(createMockSdkField(number = number)), + identity = createMockSdkIdentity(number = number), + favorite = false, + passwordHistory = listOf(createMockSdkPasswordHistory(number = number)), + reprompt = CipherRepromptType.NONE, + secureNote = createMockSdkSecureNote(), + edit = false, + organizationUseTotp = false, + viewPassword = false, + localData = null, + ) + +/** + * Create a mock [SecureNote] with a given [number]. + */ +fun createMockSdkSecureNote(): SecureNote = + SecureNote( + type = SecureNoteType.GENERIC, + ) + +/** + * Create a mock [PasswordHistory] with a given [number]. + */ +fun createMockSdkPasswordHistory(number: Int): PasswordHistory = + PasswordHistory( + password = "mockPassword-$number", + lastUsedDate = LocalDateTime + .parse("2023-10-27T12:00:00") + .toInstant(ZoneOffset.UTC), + ) + +/** + * Create a mock [Identity] with a given [number]. + */ +fun createMockSdkIdentity(number: Int): Identity = + Identity( + firstName = "mockFirstName-$number", + middleName = "mockMiddleName-$number", + lastName = "mockLastName-$number", + passportNumber = "mockPassportNumber-$number", + country = "mockCountry-$number", + address1 = "mockAddress1-$number", + address2 = "mockAddress2-$number", + address3 = "mockAddress3-$number", + city = "mockCity-$number", + postalCode = "mockPostalCode-$number", + title = "mockTitle-$number", + ssn = "mockSsn-$number", + phone = "mockPhone-$number", + company = "mockCompany-$number", + licenseNumber = "mockLicenseNumber-$number", + state = "mockState-$number", + email = "mockEmail-$number", + username = "mockUsername-$number", + ) + +/** + * Create a mock [Field] with a given [number]. + */ +fun createMockSdkField(number: Int): Field = + Field( + linkedId = 100U, + name = "mockName-$number", + type = FieldType.HIDDEN, + value = "mockValue-$number", + ) + +/** + * Create a mock [Card] with a given [number]. + */ +fun createMockSdkCard(number: Int): Card = + Card( + number = "mockNumber-$number", + expMonth = "mockExpMonth-$number", + code = "mockCode-$number", + expYear = "mockExpirationYear-$number", + cardholderName = "mockCardholderName-$number", + brand = "mockBrand-$number", + ) + +/** + * Create a mock [Attachment] with a given [number]. + */ +fun createMockSdkAttachment(number: Int): Attachment = + Attachment( + fileName = "mockFileName-$number", + size = "1", + sizeName = "mockSizeName-$number", + id = "mockId-$number", + url = "mockUrl-$number", + key = "mockKey-$number", + ) + +/** + * Create a mock [Login] with a given [number]. + */ +fun createMockSdkLogin(number: Int): Login = + Login( + username = "mockUsername-$number", + password = "mockPassword-$number", + passwordRevisionDate = LocalDateTime + .parse("2023-10-27T12:00:00") + .toInstant(ZoneOffset.UTC), + autofillOnPageLoad = false, + uris = listOf(createMockSdkUri(number = number)), + totp = "mockTotp-$number", + ) + +/** + * Create a mock [LoginUri] with a given [number]. + */ +fun createMockSdkUri(number: Int): LoginUri = + LoginUri( + uri = "mockUri-$number", + match = UriMatchType.HOST, + ) diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkFolderUtil.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkFolderUtil.kt new file mode 100644 index 0000000000..682c184af3 --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkFolderUtil.kt @@ -0,0 +1,17 @@ +package com.x8bit.bitwarden.data.vault.datasource.sdk + +import com.bitwarden.core.Folder +import java.time.LocalDateTime +import java.time.ZoneOffset + +/** + * Create a mock [Folder] with a given [number]. + */ +fun createMockSdkFolder(number: Int): Folder = + Folder( + id = "mockId-$number", + name = "mockName-$number", + revisionDate = LocalDateTime + .parse("2023-10-27T12:00:00") + .toInstant(ZoneOffset.UTC), + ) diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceTest.kt new file mode 100644 index 0000000000..4f7745768b --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceTest.kt @@ -0,0 +1,115 @@ +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 +import com.x8bit.bitwarden.data.platform.util.asSuccess +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.jupiter.api.Test + +class VaultSdkSourceTest { + private val clientVault = mockk() + + private val vaultSdkSource: VaultSdkSource = VaultSdkSourceImpl( + clientVault = clientVault, + ) + + @Test + fun `Cipher decrypt should call SDK and return a Result with correct data`() = runBlocking { + val mockCipher = mockk() + val expectedResult = mockk() + coEvery { + clientVault.ciphers().decrypt( + cipher = mockCipher, + ) + } returns expectedResult + val result = vaultSdkSource.decryptCipher( + cipher = mockCipher, + ) + assertEquals( + expectedResult.asSuccess(), + result, + ) + coVerify { + clientVault.ciphers().decrypt( + cipher = mockCipher, + ) + } + } + + @Test + fun `Cipher decryptList should call SDK and return a Result with correct data`() = runBlocking { + val mockCiphers = mockk>() + val expectedResult = mockk>() + coEvery { + clientVault.ciphers().decryptList( + ciphers = mockCiphers, + ) + } returns expectedResult + val result = vaultSdkSource.decryptCipherList( + cipherList = mockCiphers, + ) + assertEquals( + expectedResult.asSuccess(), + result, + ) + coVerify { + clientVault.ciphers().decryptList( + ciphers = mockCiphers, + ) + } + } + + @Test + fun `Folder decrypt should call SDK and return a Result with correct data`() = runBlocking { + val mockFolder = mockk() + val expectedResult = mockk() + coEvery { + clientVault.folders().decrypt( + folder = mockFolder, + ) + } returns expectedResult + val result = vaultSdkSource.decryptFolder( + folder = mockFolder, + ) + assertEquals( + expectedResult.asSuccess(), + result, + ) + coVerify { + clientVault.folders().decrypt( + folder = mockFolder, + ) + } + } + + @Test + fun `Folder decryptList should call SDK and return a Result with correct data`() = runBlocking { + val mockFolders = mockk>() + val expectedResult = mockk>() + coEvery { + clientVault.folders().decryptList( + folders = mockFolders, + ) + } returns expectedResult + val result = vaultSdkSource.decryptFolderList( + folderList = mockFolders, + ) + assertEquals( + expectedResult.asSuccess(), + result, + ) + coVerify { + clientVault.folders().decryptList( + folders = mockFolders, + ) + } + } +} diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensionsTest.kt new file mode 100644 index 0000000000..908688c6c9 --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensionsTest.kt @@ -0,0 +1,244 @@ +package com.x8bit.bitwarden.data.vault.repository.util + +import com.bitwarden.core.CipherRepromptType +import com.bitwarden.core.CipherType +import com.bitwarden.core.FieldType +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.UriMatchTypeJson +import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockAttachment +import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockCard +import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockCipher +import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockField +import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockIdentity +import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockLogin +import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockPasswordHistory +import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockSecureNote +import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockUri +import com.x8bit.bitwarden.data.vault.datasource.sdk.createMockSdkAttachment +import com.x8bit.bitwarden.data.vault.datasource.sdk.createMockSdkCard +import com.x8bit.bitwarden.data.vault.datasource.sdk.createMockSdkCipher +import com.x8bit.bitwarden.data.vault.datasource.sdk.createMockSdkField +import com.x8bit.bitwarden.data.vault.datasource.sdk.createMockSdkIdentity +import com.x8bit.bitwarden.data.vault.datasource.sdk.createMockSdkLogin +import com.x8bit.bitwarden.data.vault.datasource.sdk.createMockSdkPasswordHistory +import com.x8bit.bitwarden.data.vault.datasource.sdk.createMockSdkSecureNote +import com.x8bit.bitwarden.data.vault.datasource.sdk.createMockSdkUri +import org.junit.Assert.assertEquals +import org.junit.Test + +class VaultSdkCipherExtensionsTest { + + @Test + fun `toEncryptedSdkCipherList should convert list of Network Cipher to List of Sdk Cipher`() { + val syncCiphers = listOf( + createMockCipher(number = 1), + createMockCipher(number = 2), + ) + val sdkCiphers = syncCiphers.toEncryptedSdkCipherList() + assertEquals( + listOf( + createMockSdkCipher(number = 1), + createMockSdkCipher(number = 2), + ), + sdkCiphers, + ) + } + + @Test + fun `toEncryptedSdkCipher should convert a SyncResponseJson Cipher to a Cipher`() { + val syncCipher = createMockCipher(number = 1) + val sdkCipher = syncCipher.toEncryptedSdkCipher() + assertEquals( + createMockSdkCipher(number = 1), + sdkCipher, + ) + } + + @Test + fun `toSdkLogin should convert a SyncResponseJson Cipher Login to a Login`() { + val syncLogin = createMockLogin(number = 1) + val sdkLogin = syncLogin.toSdkLogin() + assertEquals( + createMockSdkLogin(number = 1), + sdkLogin, + ) + } + + @Test + fun `toSdkIdentity should convert a SyncResponseJson Cipher Identity to a Identity`() { + val syncIdentity = createMockIdentity(number = 1) + val sdkIdentity = syncIdentity.toSdkIdentity() + assertEquals( + createMockSdkIdentity(number = 1), + sdkIdentity, + ) + } + + @Test + fun `toSdkCard should convert a SyncResponseJson Cipher Card to a Card`() { + val syncCard = createMockCard(number = 1) + val sdkCard = syncCard.toSdkCard() + assertEquals( + createMockSdkCard(number = 1), + sdkCard, + ) + } + + @Test + fun `toSdkSecureNote should convert a SyncResponseJson Cipher SecureNote to a SecureNote`() { + val syncSecureNote = createMockSecureNote() + val sdkSecureNote = syncSecureNote.toSdkSecureNote() + assertEquals( + createMockSdkSecureNote(), + sdkSecureNote, + ) + } + + @Test + fun `toSdkLoginUriList should convert list of LoginUri to List of Sdk LoginUri`() { + val syncLoginUris = listOf( + createMockUri(number = 1), + createMockUri(number = 2), + ) + val sdkLoginUris = syncLoginUris.toSdkLoginUriList() + assertEquals( + listOf( + createMockSdkUri(number = 1), + createMockSdkUri(number = 2), + ), + sdkLoginUris, + ) + } + + @Test + fun `toSdkLoginUri should convert Network Cipher LoginUri to Sdk LoginUri`() { + val syncLoginUri = createMockUri(number = 1) + val sdkLoginUri = syncLoginUri.toSdkLoginUri() + assertEquals( + createMockSdkUri(number = 1), + sdkLoginUri, + ) + } + + @Test + fun `toSdkAttachmentList should convert list of Attachment to List of Sdk Attachment`() { + val syncAttachments = listOf( + createMockAttachment(number = 1), + createMockAttachment(number = 2), + ) + val sdkAttachments = syncAttachments.toSdkAttachmentList() + assertEquals( + listOf( + createMockSdkAttachment(number = 1), + createMockSdkAttachment(number = 2), + ), + sdkAttachments, + ) + } + + @Test + fun `toSdkAttachment should convert Network Cipher Attachment to Sdk Attachment`() { + val syncAttachment = createMockAttachment(number = 1) + val sdkAttachment = syncAttachment.toSdkAttachment() + assertEquals( + createMockSdkAttachment(number = 1), + sdkAttachment, + ) + } + + @Test + fun `toSdkFieldList should convert list of Network Cipher Field to List of Sdk Field`() { + val syncFields = listOf( + createMockField(number = 1), + createMockField(number = 2), + ) + val sdkFields = syncFields.toSdkFieldList() + assertEquals( + listOf( + createMockSdkField(number = 1), + createMockSdkField(number = 2), + ), + sdkFields, + ) + } + + @Test + fun `toSdkField should convert Network Cipher Attachment to Sdk Attachment`() { + val syncField = createMockField(number = 1) + val sdkField = syncField.toSdkField() + assertEquals( + createMockSdkField(number = 1), + sdkField, + ) + } + + @Test + @Suppress("MaxLineLength") + fun `toSdkPasswordHistoryList should convert PasswordHistory list to Sdk PasswordHistory List`() { + val syncPasswordHistories = listOf( + createMockPasswordHistory(number = 1), + createMockPasswordHistory(number = 2), + ) + val sdkPasswordHistories = syncPasswordHistories.toSdkPasswordHistoryList() + assertEquals( + listOf( + createMockSdkPasswordHistory(number = 1), + createMockSdkPasswordHistory(number = 2), + ), + sdkPasswordHistories, + ) + } + + @Test + fun `toSdkPasswordHistory should convert PasswordHistory to Sdk PasswordHistory`() { + val syncPasswordHistory = createMockPasswordHistory(number = 1) + val sdkPasswordHistory = syncPasswordHistory.toSdkPasswordHistory() + assertEquals( + createMockSdkPasswordHistory(number = 1), + sdkPasswordHistory, + ) + } + + @Test + fun `toSdkCipherType should convert CipherTypeJson to CipherType`() { + val cipherType = CipherTypeJson.IDENTITY + val sdkCipherType = cipherType.toSdkCipherType() + assertEquals( + CipherType.IDENTITY, + sdkCipherType, + ) + } + + @Test + fun `toSdkMatchType should convert UriMatchTypeJson to UriMatchType`() { + val uriMatchType = UriMatchTypeJson.DOMAIN + val sdkUriMatchType = uriMatchType.toSdkMatchType() + assertEquals( + UriMatchType.DOMAIN, + sdkUriMatchType, + ) + } + + @Test + fun `toSdkRepromptType should convert CipherRepromptTypeJson to CipherRepromptType`() { + val repromptType = CipherRepromptTypeJson.NONE + val sdkRepromptType = repromptType.toSdkRepromptType() + assertEquals( + CipherRepromptType.NONE, + sdkRepromptType, + ) + } + + @Test + fun `toSdkFieldType should convert FieldTypeJson to FieldType`() { + val fieldType = FieldTypeJson.HIDDEN + val sdkFieldType = fieldType.toSdkFieldType() + assertEquals( + FieldType.HIDDEN, + sdkFieldType, + ) + } +} diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkFolderExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkFolderExtensionsTest.kt new file mode 100644 index 0000000000..4e5b9ce600 --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkFolderExtensionsTest.kt @@ -0,0 +1,35 @@ +package com.x8bit.bitwarden.data.vault.repository.util + +import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockFolder +import com.x8bit.bitwarden.data.vault.datasource.sdk.createMockSdkFolder +import org.junit.Assert.assertEquals +import org.junit.Test + +class VaultSdkFolderExtensionsTest { + + @Test + fun `toEncryptedSdkFolderList should convert list of NetworkFolder to List of SdkFolder`() { + val syncFolders = listOf( + createMockFolder(number = 1), + createMockFolder(number = 2), + ) + val sdkFolders = syncFolders.toEncryptedSdkFolderList() + assertEquals( + listOf( + createMockSdkFolder(number = 1), + createMockSdkFolder(number = 2), + ), + sdkFolders, + ) + } + + @Test + fun `toEncryptedSdkFolder should convert a NetworkFolder to a SdkFolder`() { + val syncFolder = createMockFolder(number = 1) + val sdkFolder = syncFolder.toEncryptedSdkFolder() + assertEquals( + createMockSdkFolder(number = 1), + sdkFolder, + ) + } +}