diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/repository/util/UserStateJsonExtensions.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/repository/util/UserStateJsonExtensions.kt index 640b16263e..3e2582341d 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/repository/util/UserStateJsonExtensions.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/repository/util/UserStateJsonExtensions.kt @@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.auth.repository.util import com.bitwarden.core.MasterPasswordUnlockData import com.bitwarden.data.repository.util.toEnvironmentUrlsOrDefault +import com.bitwarden.network.model.KdfJson import com.bitwarden.network.model.KdfTypeJson import com.bitwarden.network.model.MasterPasswordUnlockDataJson import com.bitwarden.network.model.OrganizationType @@ -161,29 +162,43 @@ fun UserStateJson.updateMasterPasswordUnlock( ) } +/** + * Updates the [UserStateJson] by setting the KDF values for the given [userId]. If the user is + * not present in the `UserStateJson`, nothing is updated. + */ +fun UserStateJson.updateKdf( + userId: String, + kdf: KdfJson?, +): UserStateJson { + val account = accounts[userId] ?: return this + val profile = account.profile + val updatedProfile = profile.copy( + kdfType = kdf?.kdfType, + kdfIterations = kdf?.iterations, + kdfMemory = kdf?.memory, + kdfParallelism = kdf?.parallelism, + ) + val updatedAccount = account.copy(profile = updatedProfile) + return this.copy( + accounts = accounts + .toMutableMap() + .apply { replace(userId, updatedAccount) }, + ) +} + /** * Updates the [UserStateJson] KDF settings to minimum requirements. */ -fun UserStateJson.toUserStateJsonKdfUpdatedMinimums(): UserStateJson { - val account = this.activeAccount - val profile = account.profile - val updatedProfile = profile - .copy( +fun UserStateJson.toUserStateJsonKdfUpdatedMinimums(): UserStateJson = + this.updateKdf( + userId = this.activeUserId, + kdf = KdfJson( kdfType = KdfTypeJson.PBKDF2_SHA256, - kdfIterations = DEFAULT_PBKDF2_ITERATIONS, - kdfMemory = null, - kdfParallelism = null, - ) - val updatedAccount = account.copy(profile = updatedProfile) - return this - .copy( - accounts = accounts - .toMutableMap() - .apply { - replace(activeUserId, updatedAccount) - }, - ) -} + iterations = DEFAULT_PBKDF2_ITERATIONS, + memory = null, + parallelism = null, + ), + ) /** * Converts the given [UserStateJson] to a [UserState] using the given [vaultState]. diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/sdk/statebridge/SdkStateBridge.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/sdk/statebridge/SdkStateBridge.kt index 024fe0552c..85bf7d4223 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/sdk/statebridge/SdkStateBridge.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/sdk/statebridge/SdkStateBridge.kt @@ -5,9 +5,13 @@ import com.bitwarden.core.StateBridgeForeignImpl import com.bitwarden.core.V2UpgradeToken import com.bitwarden.core.WrappedAccountCryptographicState import com.bitwarden.crypto.EncString +import com.bitwarden.crypto.Kdf import com.bitwarden.crypto.PasswordProtectedKeyEnvelope import com.bitwarden.crypto.SymmetricCryptoKey import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource +import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toKdfRequestModel +import com.x8bit.bitwarden.data.auth.repository.util.toSdkParams +import com.x8bit.bitwarden.data.auth.repository.util.updateKdf import com.x8bit.bitwarden.data.auth.repository.util.updateMasterPasswordUnlock import com.x8bit.bitwarden.data.vault.repository.util.toSdkMasterPasswordUnlock import com.x8bit.bitwarden.data.vault.repository.util.toV2UpgradeToken @@ -134,4 +138,21 @@ internal class SdkStateBridge( masterPasswordUnlock = null, ) } + + override suspend fun getKdfConfig(): Kdf? = + authDiskSource.userState?.accounts[userId]?.profile?.toSdkParams() + + override suspend fun setKdfConfig(value: Kdf) { + authDiskSource.userState = authDiskSource.userState?.updateKdf( + userId = userId, + kdf = value.toKdfRequestModel(), + ) + } + + override suspend fun clearKdfConfig() { + authDiskSource.userState = authDiskSource.userState?.updateKdf( + userId = userId, + kdf = null, + ) + } } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/SendManagerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/SendManagerImpl.kt index 0fa580367e..e3008ac9b7 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/SendManagerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/SendManagerImpl.kt @@ -77,6 +77,9 @@ class SendManagerImpl( when (send.type) { SendType.TEXT -> sendsService.createTextSend(send.toEncryptedNetworkSend()) SendType.FILE -> createFileSend(uri = fileUri, userId = userId, send = send) + SendType.ITEM -> { + NotImplementedError("[PM-41095] Support Item SendType").asFailure() + } } } .map { createSendResponse -> diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkSendExtensions.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkSendExtensions.kt index c16593f949..cf332d01c4 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkSendExtensions.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkSendExtensions.kt @@ -65,6 +65,7 @@ private fun SendType.toNetworkSendType(): SendTypeJson = when (this) { SendType.TEXT -> SendTypeJson.TEXT SendType.FILE -> SendTypeJson.FILE + SendType.ITEM -> TODO("[PM-41095] Support Item SendType") } /** diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchTypeDataExtensions.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchTypeDataExtensions.kt index ca04030eaa..71a3d3f871 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchTypeDataExtensions.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchTypeDataExtensions.kt @@ -403,6 +403,7 @@ private fun SendView.toDisplayItem( iconRes = when (type) { SendType.TEXT -> BitwardenDrawable.ic_file_text SendType.FILE -> BitwardenDrawable.ic_file + SendType.ITEM -> TODO("[PM-41095] Support Item SendType") }, ), extraIconList = toLabelIcons(clock = clock), diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/addedit/util/SendViewExtensions.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/addedit/util/SendViewExtensions.kt index 35d92805d9..c277263d04 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/addedit/util/SendViewExtensions.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/addedit/util/SendViewExtensions.kt @@ -60,5 +60,7 @@ fun SendView.toViewState( sizeBytes = null, ) } + + SendType.ITEM -> TODO("[PM-41095] Support Item SendType") }, ) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/util/SendDataExtensions.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/util/SendDataExtensions.kt index b4f8cbbe2f..6389d9b397 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/util/SendDataExtensions.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/util/SendDataExtensions.kt @@ -41,6 +41,7 @@ private fun List.toSendContent( type = when (sendView.type) { SendType.TEXT -> SendState.ViewState.Content.SendItem.Type.TEXT SendType.FILE -> SendState.ViewState.Content.SendItem.Type.FILE + SendType.ITEM -> TODO("[PM-41095] Support Item SendType") }, iconList = sendView.toLabelIcons(), shareUrl = sendView.toSendUrl(baseWebSendUrl), diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/util/SendTypeExtensions.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/util/SendTypeExtensions.kt index 0f2c2449b0..0754e20c32 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/util/SendTypeExtensions.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/util/SendTypeExtensions.kt @@ -10,4 +10,5 @@ fun SendType.toSendItemType(): SendItemType = when (this) { SendType.FILE -> SendItemType.FILE SendType.TEXT -> SendItemType.TEXT + SendType.ITEM -> TODO("[PM-41095] Support Item SendType") } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/viewsend/util/SendViewExtensions.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/viewsend/util/SendViewExtensions.kt index 1d86538260..c2412d0f8d 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/viewsend/util/SendViewExtensions.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/tools/feature/send/viewsend/util/SendViewExtensions.kt @@ -21,6 +21,7 @@ fun SendView.toViewSendViewStateContent( sendType = when (this.type) { SendType.FILE -> requireNotNull(this.file).toFileType() SendType.TEXT -> requireNotNull(this.text).toTextType() + SendType.ITEM -> TODO("[PM-41095] Support Item SendType") }, shareLink = this.toSendUrl(baseWebSendUrl = baseWebSendUrl), sendName = this.name, diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensions.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensions.kt index 613a293214..7d672c9cc5 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensions.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensions.kt @@ -628,6 +628,7 @@ private fun SendView.toDisplayItem( iconRes = when (type) { SendType.TEXT -> BitwardenDrawable.ic_file_text SendType.FILE -> BitwardenDrawable.ic_file + SendType.ITEM -> TODO("[PM-41095] Support Item SendType") }, ), iconTestTag = null, diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/sdk/statebridge/SdkStateBridgeTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/sdk/statebridge/SdkStateBridgeTest.kt index efd4aa26e3..0e7b903a39 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/sdk/statebridge/SdkStateBridgeTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/sdk/statebridge/SdkStateBridgeTest.kt @@ -12,6 +12,7 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson import com.x8bit.bitwarden.data.auth.datasource.disk.util.FakeAuthDiskSource import com.x8bit.bitwarden.data.auth.repository.model.createMockWrappedAccountCryptographicState +import com.x8bit.bitwarden.data.auth.repository.util.updateKdf import com.x8bit.bitwarden.data.auth.repository.util.updateMasterPasswordUnlock import com.x8bit.bitwarden.data.vault.repository.util.toSdkMasterPasswordUnlock import io.mockk.mockk @@ -306,6 +307,125 @@ class SdkStateBridgeTest { assertNull(stateBridge.getMasterpasswordUnlockData()) } + + @Test + fun `getKdfConfig should return null when there is no user state`() = runTest { + authDiskSource.userState = null + + assertNull(stateBridge.getKdfConfig()) + } + + @Test + fun `getKdfConfig should return null when the user is not present in the user state`() = + runTest { + val otherUserId = "otherUserId" + authDiskSource.userState = UserStateJson( + activeUserId = otherUserId, + accounts = mapOf(otherUserId to ACCOUNT), + ) + + assertNull(stateBridge.getKdfConfig()) + } + + @Test + fun `getKdfConfig should return the stored PBKDF2 params as the sdk model`() = runTest { + authDiskSource.userState = USER_STATE + + assertEquals(Kdf.Pbkdf2(iterations = 600_000u), stateBridge.getKdfConfig()) + } + + @Test + fun `getKdfConfig should return the stored ARGON2ID params as the sdk model`() = runTest { + authDiskSource.userState = USER_STATE.copy( + accounts = mapOf( + USER_ID to ACCOUNT.copy( + profile = ACCOUNT.profile.copy( + kdfType = KdfTypeJson.ARGON2_ID, + kdfIterations = 3, + kdfMemory = 64, + kdfParallelism = 4, + ), + ), + ), + ) + + assertEquals( + Kdf.Argon2id(iterations = 3u, memory = 64u, parallelism = 4u), + stateBridge.getKdfConfig(), + ) + } + + @Test + fun `setKdfConfig should update the user state with the PBKDF2 params`() = runTest { + authDiskSource.userState = USER_STATE + + stateBridge.setKdfConfig(value = Kdf.Pbkdf2(iterations = 700_000u)) + + assertEquals( + USER_STATE.updateKdf( + userId = USER_ID, + kdf = KdfJson( + kdfType = KdfTypeJson.PBKDF2_SHA256, + iterations = 700_000, + memory = null, + parallelism = null, + ), + ), + authDiskSource.userState, + ) + } + + @Test + fun `setKdfConfig should update the user state with the ARGON2ID params`() = runTest { + authDiskSource.userState = USER_STATE + + stateBridge.setKdfConfig( + value = Kdf.Argon2id(iterations = 3u, memory = 64u, parallelism = 4u), + ) + + assertEquals( + USER_STATE.updateKdf( + userId = USER_ID, + kdf = KdfJson( + kdfType = KdfTypeJson.ARGON2_ID, + iterations = 3, + memory = 64, + parallelism = 4, + ), + ), + authDiskSource.userState, + ) + } + + @Test + fun `setKdfConfig should do nothing when the user state is null`() = runTest { + authDiskSource.userState = null + + stateBridge.setKdfConfig(value = Kdf.Pbkdf2(iterations = 700_000u)) + + assertNull(authDiskSource.userState) + } + + @Test + fun `clearKdfConfig should clear the kdf params from the user state`() = runTest { + authDiskSource.userState = USER_STATE + + stateBridge.clearKdfConfig() + + assertEquals( + USER_STATE.updateKdf(userId = USER_ID, kdf = null), + authDiskSource.userState, + ) + } + + @Test + fun `clearKdfConfig should do nothing when the user state is null`() = runTest { + authDiskSource.userState = null + + stateBridge.clearKdfConfig() + + assertNull(authDiskSource.userState) + } } private const val USER_ID: String = "userId" diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchUtil.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchUtil.kt index 5df37dabbd..a835c3593a 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchUtil.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchUtil.kt @@ -452,4 +452,50 @@ fun createMockDisplayItemForSend( itemType = SearchState.DisplayItem.ItemType.Sends(type = sendType), ) } + + SendType.ITEM -> { + SearchState.DisplayItem( + id = "mockId-$number", + title = "mockName-$number", + titleTestTag = "SendNameLabel", + subtitle = "Oct 27, 2023, 12:00 PM", + subtitleTestTag = "SendDateLabel", + iconData = IconData.Local(BitwardenDrawable.ic_file_text), + extraIconList = persistentListOf( + IconData.Local( + iconRes = BitwardenDrawable.ic_key, + contentDescription = BitwardenString.password.asText(), + testTag = "PasswordProtectedSendIcon", + ), + IconData.Local( + iconRes = BitwardenDrawable.ic_send_max_access_count_reached, + contentDescription = BitwardenString.maximum_access_count_reached.asText(), + testTag = "MaxAccessSendIcon", + ), + ), + overflowOptions = persistentListOf( + ListingItemOverflowAction.SendAction.CopyUrlClick( + sendUrl = "https://vault.bitwarden.com/#/send/mockAccessId-$number/mockKey-$number", + ), + ListingItemOverflowAction.SendAction.ShareUrlClick( + sendUrl = "https://vault.bitwarden.com/#/send/mockAccessId-$number/mockKey-$number", + ), + ListingItemOverflowAction.SendAction.ViewClick( + sendId = "mockId-$number", + sendType = sendType, + ), + ListingItemOverflowAction.SendAction.EditClick( + sendId = "mockId-$number", + sendType = sendType, + ), + ListingItemOverflowAction.SendAction.RemovePasswordClick(sendId = "mockId-$number"), + ListingItemOverflowAction.SendAction.DeleteClick(sendId = "mockId-$number"), + ), + overflowTestTag = "SendOptionsButton", + totpCode = null, + autofillSelectionOptions = persistentListOf(), + shouldDisplayMasterPasswordReprompt = false, + itemType = SearchState.DisplayItem.ItemType.Sends(type = sendType), + ) + } } diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataUtil.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataUtil.kt index 7da7f93488..f08bead83a 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataUtil.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataUtil.kt @@ -516,6 +516,55 @@ fun createMockDisplayItemForSend( itemType = VaultItemListingState.DisplayItem.ItemType.Sends(type = sendType), ) } + + SendType.ITEM -> { + VaultItemListingState.DisplayItem( + id = "mockId-$number", + title = "mockName-$number".asText(), + titleTestTag = "SendNameLabel", + secondSubtitle = null, + secondSubtitleTestTag = null, + subtitle = "Oct 27, 2023, 12:00 PM", + subtitleTestTag = "SendDateLabel", + iconData = IconData.Local(BitwardenDrawable.ic_file_text), + extraIconList = persistentListOf( + IconData.Local( + iconRes = BitwardenDrawable.ic_key, + contentDescription = BitwardenString.password.asText(), + testTag = "PasswordProtectedSendIcon", + ), + IconData.Local( + iconRes = BitwardenDrawable.ic_send_max_access_count_reached, + contentDescription = BitwardenString.maximum_access_count_reached.asText(), + testTag = "MaxAccessSendIcon", + ), + ), + overflowOptions = listOf( + ListingItemOverflowAction.SendAction.CopyUrlClick( + sendUrl = "https://send.bitwarden.com/#mockAccessId-$number/mockKey-$number", + ), + ListingItemOverflowAction.SendAction.ShareUrlClick( + sendUrl = "https://send.bitwarden.com/#mockAccessId-$number/mockKey-$number", + ), + ListingItemOverflowAction.SendAction.ViewClick( + sendId = "mockId-$number", + sendType = sendType, + ), + ListingItemOverflowAction.SendAction.EditClick( + sendId = "mockId-$number", + sendType = sendType, + ), + ListingItemOverflowAction.SendAction.RemovePasswordClick(sendId = "mockId-$number"), + ListingItemOverflowAction.SendAction.DeleteClick(sendId = "mockId-$number"), + ), + optionsTestTag = "SendOptionsButton", + isAutofill = false, + isCredentialCreation = false, + shouldShowMasterPasswordReprompt = false, + iconTestTag = null, + itemType = VaultItemListingState.DisplayItem.ItemType.Sends(type = sendType), + ) + } } /** diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 84c99ee8ff..e89ebfc9ce 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -29,7 +29,7 @@ androidxRoom = "2.8.4" androidxSecurityCrypto = "1.1.0" androidxSplash = "1.2.0" androidxWork = "2.11.2" -bitwardenSdk = "3.0.0-8014-7eb08fbb" +bitwardenSdk = "3.0.0-8144-cf7ad654" crashlytics = "3.0.7" detekt = "1.23.8" firebaseBom = "34.15.0"