diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSource.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSource.kt index a24d5aba7a1..ce06ea69b77 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSource.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSource.kt @@ -308,6 +308,16 @@ interface AuthDiskSource : AppIdProvider { */ fun storeEncryptedPin(userId: String, encryptedPin: String?) + /** + * Retrieves the number of digits in the PIN for the given [userId]. + */ + fun getPinLength(userId: String): Int? + + /** + * Stores the number of digits in the PIN for the given [userId]. + */ + fun storePinLength(userId: String, pinLength: Int?) + /** * Gets the organization keys for the given [userId] in the form of a mapping from organization * ID to encrypted organization key. diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceImpl.kt index 2a0e3a271d9..a3310feb513 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceImpl.kt @@ -48,6 +48,7 @@ private const val MASTER_KEY_ENCRYPTION_PRIVATE_KEY = "encPrivateKey" private const val PIN_PROTECTED_USER_KEY_KEY = "pinKeyEncryptedUserKey" private const val PIN_PROTECTED_USER_KEY_KEY_ENVELOPE = "pinKeyEncryptedUserKeyEnvelope" private const val ENCRYPTED_PIN_KEY = "protectedPin" +private const val PIN_LENGTH_KEY = "pinLength" private const val ORGANIZATIONS_KEY = "organizations" private const val ORGANIZATION_KEYS_KEY = "encOrgKeys" private const val TWO_FACTOR_TOKEN_KEY = "twoFactorToken" @@ -68,7 +69,7 @@ private const val V2_ENCRYPTED_MIGRATIONS_GRACE_PERIOD_START = /** * Primary implementation of [AuthDiskSource]. */ -@Suppress("TooManyFunctions") +@Suppress("TooManyFunctions", "LargeClass") class AuthDiskSourceImpl( encryptedSharedPreferences: SharedPreferences, keystoreEncryptedPreferences: SharedPreferences, @@ -194,6 +195,7 @@ class AuthDiskSourceImpl( storeShowImportLogins(userId = userId, showImportLogins = null) storeLastLockTimestamp(userId = userId, lastLockTimestamp = null) storeEncryptedPin(userId = userId, encryptedPin = null) + storePinLength(userId = userId, pinLength = null) storePinProtectedUserKey(userId = userId, pinProtectedUserKey = null) storeEphemeralPinProtectedUserKeyEnvelope( userId = userId, @@ -472,6 +474,16 @@ class AuthDiskSourceImpl( ) } + override fun getPinLength(userId: String): Int? = + getInt(key = PIN_LENGTH_KEY.appendIdentifier(userId)) + + override fun storePinLength(userId: String, pinLength: Int?) { + putInt( + key = PIN_LENGTH_KEY.appendIdentifier(userId), + value = pinLength, + ) + } + override fun getOrganizationKeys(userId: String): Map? = getString(key = ORGANIZATION_KEYS_KEY.appendIdentifier(userId)) ?.let { json.decodeFromStringOrNull(it) } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/manager/UserLogoutManagerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/manager/UserLogoutManagerImpl.kt index 4a1ee888622..c4874fab5ff 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/manager/UserLogoutManagerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/auth/manager/UserLogoutManagerImpl.kt @@ -83,6 +83,7 @@ internal class UserLogoutManagerImpl( val vaultTimeoutInMinutes = settingsDiskSource.getVaultTimeoutInMinutes(userId = userId) val vaultTimeoutAction = settingsDiskSource.getVaultTimeoutAction(userId = userId) val encryptedPin = authDiskSource.getEncryptedPin(userId = userId) + val pinLength = authDiskSource.getPinLength(userId = userId) val pinProtectedUserKey = authDiskSource.getPinProtectedUserKey(userId = userId) val ephemeralPinProtectedUserKeyEnvelope = authDiskSource .getEphemeralPinProtectedUserKeyEnvelope(userId = userId) @@ -116,6 +117,7 @@ internal class UserLogoutManagerImpl( } authDiskSource.apply { storeEncryptedPin(userId = userId, encryptedPin = encryptedPin) + storePinLength(userId = userId, pinLength = pinLength) storePinProtectedUserKey(userId = userId, pinProtectedUserKey = pinProtectedUserKey) storeEphemeralPinProtectedUserKeyEnvelope( userId = userId, 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 aa528c744bc..5b4155d4a45 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 @@ -95,6 +95,17 @@ internal class SdkStateBridge( authDiskSource.storeEncryptedPin(userId = userId, encryptedPin = null) } + override suspend fun setPinLength(value: UInt) { + authDiskSource.storePinLength(userId = userId, pinLength = value.toInt()) + } + + override suspend fun getPinLength(): UInt? = + authDiskSource.getPinLength(userId = userId)?.toUInt() + + override suspend fun clearPinLength() { + authDiskSource.storePinLength(userId = userId, pinLength = null) + } + override suspend fun setV2UpgradeToken(value: V2UpgradeToken) { authDiskSource.storeV2UpgradeToken( userId = userId, diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryImpl.kt index 3b9c3beb37e..9653b44205a 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryImpl.kt @@ -701,6 +701,9 @@ class SettingsRepositoryImpl( pinProtectedUserKeyEnvelope = null, ) storePinProtectedUserKey(userId = userId, pinProtectedUserKey = null) + // The SDK only repopulates this on unlock when it is absent, so a stale value here + // would survive a later PIN change. + storePinLength(userId = userId, pinLength = null) } } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensions.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensions.kt index f97b2271636..e84128c5090 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensions.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensions.kt @@ -397,6 +397,7 @@ private fun Login.toEncryptedNetworkLogin(): SyncResponseJson.Cipher.Login = uri = null, username = username, fido2Credentials = fido2Credentials?.toNetworkFido2Credentials(), + httpAuthenticationEnabled = httpAuthenticationEnabled, ) private fun List.toNetworkFido2Credentials() = @@ -534,6 +535,7 @@ fun SyncResponseJson.Cipher.Login.toSdkLogin(): Login = totp = totp, autofillOnPageLoad = shouldAutofillOnPageLoad, fido2Credentials = fido2Credentials?.toSdkFido2Credentials(), + httpAuthenticationEnabled = httpAuthenticationEnabled, ) private fun List.toSdkFido2Credentials() = diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultAddItemStateExtensions.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultAddItemStateExtensions.kt index fd9dcec43ec..5cc383b09d4 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultAddItemStateExtensions.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultAddItemStateExtensions.kt @@ -297,6 +297,10 @@ private fun VaultAddEditState.ViewState.Content.ItemType.toLoginView( uris = it.uriList.toLoginUriView(), totp = it.totp.orNullIfBlank(), autofillOnPageLoad = common.originalCipher?.login?.autofillOnPageLoad, + // Not modeled in the UI; preserved so saving does not clear a value set elsewhere. + httpAuthenticationEnabled = common.originalCipher + ?.login + ?.httpAuthenticationEnabled, fido2Credentials = common.originalCipher?.login?.fido2Credentials .takeIf { _ -> it.fido2CredentialCreationDateTime != null }, ) diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceTest.kt index b9d1f77a4e4..f0d771c0cba 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceTest.kt @@ -318,6 +318,7 @@ class AuthDiskSourceTest { biometricsKey = "1234-9876-0192", ) authDiskSource.storeEncryptedPin(userId = userId, encryptedPin = "encryptedPin") + authDiskSource.storePinLength(userId = userId, pinLength = 4) authDiskSource.storePinProtectedUserKey( userId = userId, pinProtectedUserKey = "pinProtectedUserKey", @@ -415,6 +416,7 @@ class AuthDiskSourceTest { assertNull(authDiskSource.getAuthenticatorSyncUnlockKey(userId = userId)) assertNull(authDiskSource.getShowImportLogins(userId = userId)) assertNull(authDiskSource.getEncryptedPin(userId = userId)) + assertNull(authDiskSource.getPinLength(userId = userId)) assertNull(authDiskSource.getPinProtectedUserKey(userId = userId)) assertNull(authDiskSource.getPinProtectedUserKeyEnvelope(userId = userId)) assertNull(authDiskSource.getEphemeralPinProtectedUserKeyEnvelope(userId = userId)) @@ -1071,6 +1073,45 @@ class AuthDiskSourceTest { ) } + @Test + fun `getPinLength should pull from SharedPreferences`() { + val pinLengthBaseKey = "bwPreferencesStorage:pinLength" + val mockUserId = "mockUserId" + val mockPinLength = 4 + fakeSharedPreferences + .edit { + putInt( + "${pinLengthBaseKey}_$mockUserId", + mockPinLength, + ) + } + val actual = authDiskSource.getPinLength(userId = mockUserId) + assertEquals( + mockPinLength, + actual, + ) + } + + @Test + fun `storePinLength should update SharedPreferences`() { + val pinLengthBaseKey = "bwPreferencesStorage:pinLength" + val mockUserId = "mockUserId" + val mockPinLength = 6 + authDiskSource.storePinLength( + userId = mockUserId, + pinLength = mockPinLength, + ) + val actual = fakeSharedPreferences + .getInt( + "${pinLengthBaseKey}_$mockUserId", + 0, + ) + assertEquals( + mockPinLength, + actual, + ) + } + @Test fun `getOrganizationKeys should pull from SharedPreferences`() { val organizationKeysBaseKey = "bwPreferencesStorage:encOrgKeys" diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/util/FakeAuthDiskSource.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/util/FakeAuthDiskSource.kt index 950d77584fb..67470a33bbf 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/util/FakeAuthDiskSource.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/util/FakeAuthDiskSource.kt @@ -50,6 +50,7 @@ class FakeAuthDiskSource : AuthDiskSource { private val storedUserAutoUnlockKeys = mutableMapOf() private val storedPinProtectedUserKeys = mutableMapOf>() private val storedEncryptedPins = mutableMapOf() + private val storedPinLengths = mutableMapOf() private val storedOrganizations = mutableMapOf?>() private val storedOrganizationKeys = mutableMapOf?>() @@ -100,6 +101,7 @@ class FakeAuthDiskSource : AuthDiskSource { storedEphemeralPinProtectedUserKeyEnvelopes.remove(userId) storedPersistentPinProtectedUserKeyEnvelopes.remove(userId) storedEncryptedPins.remove(userId) + storedPinLengths.remove(userId) storedPinProtectedUserKeys.remove(userId) storedV2UpgradeTokens.remove(userId) storedUserKeyIds.remove(userId) @@ -245,6 +247,12 @@ class FakeAuthDiskSource : AuthDiskSource { storedEncryptedPins[userId] = encryptedPin } + override fun getPinLength(userId: String): Int? = storedPinLengths[userId] + + override fun storePinLength(userId: String, pinLength: Int?) { + storedPinLengths[userId] = pinLength + } + override fun getOrganizationKeys( userId: String, ): Map? = storedOrganizationKeys[userId] @@ -523,6 +531,13 @@ class FakeAuthDiskSource : AuthDiskSource { assertEquals(encryptedPin, storedEncryptedPins[userId]) } + /** + * Assert that the [pinLength] was stored successfully using the [userId]. + */ + fun assertPinLength(userId: String, pinLength: Int?) { + assertEquals(pinLength, storedPinLengths[userId]) + } + /** * Assert that the [pinProtectedUserKey] was stored successfully using the [userId]. */ diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/manager/UserLogoutManagerTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/manager/UserLogoutManagerTest.kt index 489b2d28a2b..71878a9d5fb 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/manager/UserLogoutManagerTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/auth/manager/UserLogoutManagerTest.kt @@ -153,6 +153,7 @@ class UserLogoutManagerTest { val pinProtectedUserKey = "pinProtectedUserKey" val pinProtectedUserKeyEnvelope = "pinProtectedUserKeyEnvelope" val encryptedPin = "encryptedPin" + val pinLength = 4 every { settingsDiskSource.getVaultTimeoutInMinutes(userId = userId) @@ -189,6 +190,8 @@ class UserLogoutManagerTest { every { authDiskSource.storeEncryptedPin(userId = userId, encryptedPin = encryptedPin) } just runs + every { authDiskSource.getPinLength(userId = userId) } returns pinLength + every { authDiskSource.storePinLength(userId = userId, pinLength = pinLength) } just runs userLogoutManager.softLogout(userId = userId, reason = LogoutReason.Timeout) @@ -224,6 +227,7 @@ class UserLogoutManagerTest { pinProtectedUserKey = pinProtectedUserKey, ) authDiskSource.storeEncryptedPin(userId = userId, encryptedPin = encryptedPin) + authDiskSource.storePinLength(userId = userId, pinLength = pinLength) } } @@ -235,6 +239,7 @@ class UserLogoutManagerTest { val pinProtectedUserKey = "pinProtectedUserKey" val pinProtectedUserKeyEnvelope = "pinProtectedUserKeyEnvelope" val encryptedPin = "encryptedPin" + val pinLength = 4 every { settingsDiskSource.getVaultTimeoutInMinutes(userId = userId) @@ -271,6 +276,8 @@ class UserLogoutManagerTest { every { authDiskSource.storeEncryptedPin(userId = userId, encryptedPin = encryptedPin) } just runs + every { authDiskSource.getPinLength(userId = userId) } returns pinLength + every { authDiskSource.storePinLength(userId = userId, pinLength = pinLength) } just runs userLogoutManager.softLogout(userId = userId, reason = LogoutReason.Timeout) @@ -296,6 +303,7 @@ class UserLogoutManagerTest { pinProtectedUserKey = pinProtectedUserKey, ) authDiskSource.storeEncryptedPin(userId = userId, encryptedPin = encryptedPin) + authDiskSource.storePinLength(userId = userId, pinLength = pinLength) } } @@ -400,7 +408,9 @@ class UserLogoutManagerTest { every { authDiskSource.getPersistentPinProtectedUserKeyEnvelope(userId = userId) } returns "pinProtectedUserKeyEnvelope" + every { authDiskSource.getPinLength(userId = userId) } returns 4 every { authDiskSource.storeEncryptedPin(userId = userId, encryptedPin = any()) } just runs + every { authDiskSource.storePinLength(userId = userId, pinLength = any()) } just runs every { authDiskSource.storePinProtectedUserKey(userId = userId, pinProtectedUserKey = any()) } just runs 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 1e5eb4b839b..f041c2c32fc 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 @@ -180,6 +180,31 @@ class SdkStateBridgeTest { authDiskSource.assertEncryptedPin(userId = USER_ID, encryptedPin = null) } + @Test + fun `setPinLength should store the pin length`() = runTest { + stateBridge.setPinLength(value = 4u) + + authDiskSource.assertPinLength(userId = USER_ID, pinLength = 4) + } + + @Test + fun `getPinLength should return the stored pin length`() = runTest { + assertNull(stateBridge.getPinLength()) + + authDiskSource.storePinLength(userId = USER_ID, pinLength = 4) + + assertEquals(4u, stateBridge.getPinLength()) + } + + @Test + fun `clearPinLength should clear the pin length`() = runTest { + authDiskSource.storePinLength(userId = USER_ID, pinLength = 4) + + stateBridge.clearPinLength() + + authDiskSource.assertPinLength(userId = USER_ID, pinLength = null) + } + @Test fun `setV2UpgradeToken should store the token as a V2UpgradeTokenJson`() = runTest { stateBridge.setV2UpgradeToken(value = V2_UPGRADE_TOKEN) diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryTest.kt index 3506308e0c9..1e910914fbf 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryTest.kt @@ -1212,6 +1212,7 @@ class SettingsRepositoryTest { userId = USER_ID, pinProtectedUserKeyEnvelope = "pinProtectedUserKeyEnvelope", ) + storePinLength(userId = USER_ID, pinLength = 4) } settingsRepository.clearUnlockPin() @@ -1229,6 +1230,7 @@ class SettingsRepositoryTest { userId = USER_ID, pinProtectedUserKeyEnvelope = null, ) + assertPinLength(userId = USER_ID, pinLength = null) } } diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSourceTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSourceTest.kt index 1724605f37e..e27762639ea 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSourceTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSourceTest.kt @@ -514,6 +514,7 @@ private const val CIPHER_JSON = """ "password": "mockPassword-1", "passwordRevisionDate": "2023-10-27T12:00:00.000Z", "autofillOnPageLoad": false, + "httpAuthenticationEnabled": false, "uri": "mockUri-1", "username": "mockUsername-1", "fido2Credentials": [ diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/datasource/sdk/model/VaultSdkCipherUtil.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/datasource/sdk/model/VaultSdkCipherUtil.kt index 1f4b374d181..6543bd83846 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/datasource/sdk/model/VaultSdkCipherUtil.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/datasource/sdk/model/VaultSdkCipherUtil.kt @@ -246,6 +246,7 @@ fun createMockSdkLogin(number: Int, clock: Clock): Login = number = number, clock = clock, ), + httpAuthenticationEnabled = false, ) /** diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensionsTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensionsTest.kt index cd06a3c3b7a..1c74dcf8ff7 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensionsTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensionsTest.kt @@ -141,6 +141,45 @@ class VaultSdkCipherExtensionsTest { ) } + @Suppress("MaxLineLength") + @Test + fun `toSdkLogin should convert the httpAuthenticationEnabled of a SyncResponseJson Cipher Login`() { + val syncLogin = createMockLogin(number = 1, httpAuthenticationEnabled = true) + + val sdkLogin = syncLogin.toSdkLogin() + + assertEquals( + createMockSdkLogin(number = 1, clock = FIXED_CLOCK) + .copy(httpAuthenticationEnabled = true), + sdkLogin, + ) + } + + @Suppress("MaxLineLength") + @Test + fun `toEncryptedNetworkCipher should convert the httpAuthenticationEnabled of an Sdk Cipher Login`() { + val sdkCipher = createMockSdkCipher(number = 1, clock = FIXED_CLOCK) + .let { it.copy(login = it.login?.copy(httpAuthenticationEnabled = true)) } + + val syncCipher = sdkCipher.toEncryptedNetworkCipher( + encryptedFor = "mockEncryptedFor-1", + encryptedByKeyId = "mockEncryptedByKeyId-1", + ) + + assertEquals( + createMockCipherJsonRequest( + number = 1, + login = createMockLogin( + number = 1, + uri = null, + httpAuthenticationEnabled = true, + ), + archivedDate = FIXED_CLOCK.instant(), + ), + syncCipher, + ) + } + @Test fun `toSdkIdentity should convert a SyncResponseJson Cipher Identity to a Identity`() { val syncIdentity = createMockIdentity(number = 1) diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultAddItemStateExtensionsTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultAddItemStateExtensionsTest.kt index ebc0376413a..6b3df1e4e75 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultAddItemStateExtensionsTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultAddItemStateExtensionsTest.kt @@ -228,6 +228,43 @@ class VaultAddItemStateExtensionsTest { ) } + @Test + fun `toCipherView should preserve httpAuthenticationEnabled from the original cipher`() { + val cipherView = DEFAULT_LOGIN_CIPHER_VIEW.copy( + login = requireNotNull(DEFAULT_LOGIN_CIPHER_VIEW.login).copy( + httpAuthenticationEnabled = true, + ), + ) + val viewState = VaultAddEditState.ViewState.Content( + common = VaultAddEditState.ViewState.Content.Common( + originalCipher = cipherView, + name = "mockName-1", + selectedOwnerId = "mockOwnerId-1", + ), + isIndividualVaultDisabled = false, + type = VaultAddEditState.ViewState.Content.ItemType.Login( + username = "mockUsername-1", + password = "mockPassword-1", + ), + ) + + val result = viewState.toCipherView(clock = FIXED_CLOCK, isPremiumUser = true) + + assertEquals( + LoginView( + username = "mockUsername-1", + password = "mockPassword-1", + passwordRevisionDate = FIXED_CLOCK.instant(), + uris = null, + totp = null, + autofillOnPageLoad = false, + httpAuthenticationEnabled = true, + fido2Credentials = null, + ), + result.login, + ) + } + @Test fun `toCipherView should transform SecureNotes ItemType to CipherView`() { val viewState = VaultAddEditState.ViewState.Content( diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 772a7f7de85..157111c8cb4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -29,7 +29,7 @@ androidxRoom3 = "3.0.3" androidxSecurityCrypto = "1.1.0" androidxSplash = "1.2.0" androidxWork = "2.12.0" -bitwardenSdk = "4.0.0-9737-d9119f7a" +bitwardenSdk = "4.0.0-10242-e3cdc1ad" coil = "3.6.3" crashlytics = "3.0.8" detekt = "1.23.8" diff --git a/network/src/main/kotlin/com/bitwarden/network/model/SyncResponseJson.kt b/network/src/main/kotlin/com/bitwarden/network/model/SyncResponseJson.kt index 99e6dd1d8a1..5fab92cdecb 100644 --- a/network/src/main/kotlin/com/bitwarden/network/model/SyncResponseJson.kt +++ b/network/src/main/kotlin/com/bitwarden/network/model/SyncResponseJson.kt @@ -973,6 +973,8 @@ data class SyncResponseJson( * @property uri The URI (nullable). * @property username The username (nullable). * @property fido2Credentials A list of FIDO 2 credentials (nullable). + * @property httpAuthenticationEnabled If HTTP authentication is used for the login + * (nullable). */ @Serializable data class Login( @@ -1000,6 +1002,9 @@ data class SyncResponseJson( @SerialName("fido2Credentials") val fido2Credentials: List?, + + @SerialName("httpAuthenticationEnabled") + val httpAuthenticationEnabled: Boolean?, ) { /** * Represents a URI in the vault response. diff --git a/network/src/test/kotlin/com/bitwarden/network/service/CiphersServiceTest.kt b/network/src/test/kotlin/com/bitwarden/network/service/CiphersServiceTest.kt index 2855b1a059f..a6d77f415a9 100644 --- a/network/src/test/kotlin/com/bitwarden/network/service/CiphersServiceTest.kt +++ b/network/src/test/kotlin/com/bitwarden/network/service/CiphersServiceTest.kt @@ -568,6 +568,7 @@ private const val CREATE_ATTACHMENT_SUCCESS_JSON = """ "password": "mockPassword-1", "passwordRevisionDate": "2023-10-27T12:00:00.00Z", "autofillOnPageLoad": false, + "httpAuthenticationEnabled": false, "uri": "mockUri-1", "username": "mockUsername-1", "fido2Credentials": [ @@ -738,6 +739,7 @@ private const val CREATE_RESTORE_UPDATE_CIPHER_SUCCESS_JSON = """ "password": "mockPassword-1", "passwordRevisionDate": "2023-10-27T12:00:00.00Z", "autofillOnPageLoad": false, + "httpAuthenticationEnabled": false, "uri": "mockUri-1", "username": "mockUsername-1", "fido2Credentials": [ @@ -925,6 +927,7 @@ private const val UPDATE_CIPHER_COLLECTION_SUCCESS_JSON = """ "password": "mockPassword-1", "passwordRevisionDate": "2023-10-27T12:00:00.00Z", "autofillOnPageLoad": false, + "httpAuthenticationEnabled": false, "uri": "mockUri-1", "username": "mockUsername-1", "fido2Credentials": [ diff --git a/network/src/test/kotlin/com/bitwarden/network/service/SyncServiceTest.kt b/network/src/test/kotlin/com/bitwarden/network/service/SyncServiceTest.kt index e20a81ee1dc..4bf7065b2fe 100644 --- a/network/src/test/kotlin/com/bitwarden/network/service/SyncServiceTest.kt +++ b/network/src/test/kotlin/com/bitwarden/network/service/SyncServiceTest.kt @@ -412,6 +412,7 @@ private const val SYNC_SUCCESS_JSON = """ "password": "mockPassword-1", "passwordRevisionDate": "2023-10-27T12:00:00.00Z", "autofillOnPageLoad": false, + "httpAuthenticationEnabled": false, "uri": "mockUri-1", "username": "mockUsername-1", "fido2Credentials": [ diff --git a/network/src/testFixtures/kotlin/com/bitwarden/network/model/SyncResponseCipherUtil.kt b/network/src/testFixtures/kotlin/com/bitwarden/network/model/SyncResponseCipherUtil.kt index db7c765e6c5..101afdbee57 100644 --- a/network/src/testFixtures/kotlin/com/bitwarden/network/model/SyncResponseCipherUtil.kt +++ b/network/src/testFixtures/kotlin/com/bitwarden/network/model/SyncResponseCipherUtil.kt @@ -240,6 +240,7 @@ fun createMockLogin( fido2Credentials: List = listOf( createMockFido2Credential(number = number), ), + httpAuthenticationEnabled: Boolean? = false, ): SyncResponseJson.Cipher.Login = SyncResponseJson.Cipher.Login( username = username, @@ -250,6 +251,7 @@ fun createMockLogin( uris = uris, totp = totp, fido2Credentials = fido2Credentials, + httpAuthenticationEnabled = httpAuthenticationEnabled, ) /**