From a0bbc1a3137454121267751caec620027b82041a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Bispo?= Date: Tue, 9 Sep 2025 10:14:24 +0100 Subject: [PATCH] [PM-23278] Add updateKdf service call and test --- .../network/api/AuthenticatedAccountsApi.kt | 7 +++ .../bitwarden/network/model/KdfJsonRequest.kt | 22 ++++++++ ...erPasswordAuthenticationDataJsonRequest.kt | 19 +++++++ .../MasterPasswordUnlockDataJsonRequest.kt | 19 +++++++ .../network/model/UpdateKdfJsonRequest.kt | 25 ++++++++++ .../network/service/AccountsService.kt | 6 +++ .../network/service/AccountsServiceImpl.kt | 6 +++ .../network/service/AccountsServiceTest.kt | 50 +++++++++++++++++++ 8 files changed, 154 insertions(+) create mode 100644 network/src/main/kotlin/com/bitwarden/network/model/KdfJsonRequest.kt create mode 100644 network/src/main/kotlin/com/bitwarden/network/model/MasterPasswordAuthenticationDataJsonRequest.kt create mode 100644 network/src/main/kotlin/com/bitwarden/network/model/MasterPasswordUnlockDataJsonRequest.kt create mode 100644 network/src/main/kotlin/com/bitwarden/network/model/UpdateKdfJsonRequest.kt diff --git a/network/src/main/kotlin/com/bitwarden/network/api/AuthenticatedAccountsApi.kt b/network/src/main/kotlin/com/bitwarden/network/api/AuthenticatedAccountsApi.kt index 2a9f5db3fe..09d10ce5a6 100644 --- a/network/src/main/kotlin/com/bitwarden/network/api/AuthenticatedAccountsApi.kt +++ b/network/src/main/kotlin/com/bitwarden/network/api/AuthenticatedAccountsApi.kt @@ -5,6 +5,7 @@ import com.bitwarden.network.model.DeleteAccountRequestJson import com.bitwarden.network.model.NetworkResult import com.bitwarden.network.model.ResetPasswordRequestJson import com.bitwarden.network.model.SetPasswordRequestJson +import com.bitwarden.network.model.UpdateKdfJsonRequest import com.bitwarden.network.model.VerifyOtpRequestJson import retrofit2.http.Body import retrofit2.http.HTTP @@ -36,6 +37,12 @@ internal interface AuthenticatedAccountsApi { @POST("/accounts/request-otp") suspend fun requestOtp(): NetworkResult + /** + * Update the KDF settings for the current account. + */ + @POST("/accounts/kdf") + suspend fun updateKdf(@Body body: UpdateKdfJsonRequest): NetworkResult + @POST("/accounts/verify-otp") suspend fun verifyOtp( @Body body: VerifyOtpRequestJson, diff --git a/network/src/main/kotlin/com/bitwarden/network/model/KdfJsonRequest.kt b/network/src/main/kotlin/com/bitwarden/network/model/KdfJsonRequest.kt new file mode 100644 index 0000000000..d2ebdd621c --- /dev/null +++ b/network/src/main/kotlin/com/bitwarden/network/model/KdfJsonRequest.kt @@ -0,0 +1,22 @@ +package com.bitwarden.network.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Represents the request body used to create the kdf settings. + */ +@Serializable +data class KdfJsonRequest( + @SerialName("KdfType") + val kdfType: KdfTypeJson, + + @SerialName("Iterations") + val iterations: Int, + + @SerialName("Memory") + val memory: Int?, + + @SerialName("Parallelism") + val parallelism: Int?, +) diff --git a/network/src/main/kotlin/com/bitwarden/network/model/MasterPasswordAuthenticationDataJsonRequest.kt b/network/src/main/kotlin/com/bitwarden/network/model/MasterPasswordAuthenticationDataJsonRequest.kt new file mode 100644 index 0000000000..c07b6afd87 --- /dev/null +++ b/network/src/main/kotlin/com/bitwarden/network/model/MasterPasswordAuthenticationDataJsonRequest.kt @@ -0,0 +1,19 @@ +package com.bitwarden.network.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Represents the request body used to authenticate with the master password. + */ +@Serializable +data class MasterPasswordAuthenticationDataJsonRequest( + @SerialName("Kdf") + val kdf: KdfJsonRequest, + + @SerialName("MasterPasswordAuthenticationHash") + val masterPasswordAuthenticationHash: String, + + @SerialName("Salt") + val salt: String, +) diff --git a/network/src/main/kotlin/com/bitwarden/network/model/MasterPasswordUnlockDataJsonRequest.kt b/network/src/main/kotlin/com/bitwarden/network/model/MasterPasswordUnlockDataJsonRequest.kt new file mode 100644 index 0000000000..eb18967335 --- /dev/null +++ b/network/src/main/kotlin/com/bitwarden/network/model/MasterPasswordUnlockDataJsonRequest.kt @@ -0,0 +1,19 @@ +package com.bitwarden.network.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Represents the request body used to unlock with the master password. + */ +@Serializable +data class MasterPasswordUnlockDataJsonRequest( + @SerialName("Kdf") + val kdf: KdfJsonRequest, + + @SerialName("MasterKeyWrappedUserKey") + val masterKeyWrappedUserKey: String, + + @SerialName("Salt") + val salt: String, +) diff --git a/network/src/main/kotlin/com/bitwarden/network/model/UpdateKdfJsonRequest.kt b/network/src/main/kotlin/com/bitwarden/network/model/UpdateKdfJsonRequest.kt new file mode 100644 index 0000000000..2a6d8c0b0b --- /dev/null +++ b/network/src/main/kotlin/com/bitwarden/network/model/UpdateKdfJsonRequest.kt @@ -0,0 +1,25 @@ +package com.bitwarden.network.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Represents the request body used to update the user's kdf settings. + */ +@Serializable +data class UpdateKdfJsonRequest( + @SerialName("authenticationData") + val authenticationData: MasterPasswordAuthenticationDataJsonRequest, + + @SerialName("key") + val key: String, + + @SerialName("masterPasswordHash") + val masterPasswordHash: String, + + @SerialName("newMasterPasswordHash") + val newMasterPasswordHash: String, + + @SerialName("unlockData") + val unlockData: MasterPasswordUnlockDataJsonRequest, +) diff --git a/network/src/main/kotlin/com/bitwarden/network/service/AccountsService.kt b/network/src/main/kotlin/com/bitwarden/network/service/AccountsService.kt index e4ec8300a5..4d9557681d 100644 --- a/network/src/main/kotlin/com/bitwarden/network/service/AccountsService.kt +++ b/network/src/main/kotlin/com/bitwarden/network/service/AccountsService.kt @@ -8,6 +8,7 @@ import com.bitwarden.network.model.ResendEmailRequestJson import com.bitwarden.network.model.ResendNewDeviceOtpRequestJson import com.bitwarden.network.model.ResetPasswordRequestJson import com.bitwarden.network.model.SetPasswordRequestJson +import com.bitwarden.network.model.UpdateKdfJsonRequest /** * Provides an API for querying accounts endpoints. @@ -109,4 +110,9 @@ interface AccountsService { accessToken: String, masterKey: String, ): Result + + /** + * Update the KDF settings for the current account. + */ + suspend fun updateKdf(body: UpdateKdfJsonRequest): Result } diff --git a/network/src/main/kotlin/com/bitwarden/network/service/AccountsServiceImpl.kt b/network/src/main/kotlin/com/bitwarden/network/service/AccountsServiceImpl.kt index 618f656167..be9cc80bd5 100644 --- a/network/src/main/kotlin/com/bitwarden/network/service/AccountsServiceImpl.kt +++ b/network/src/main/kotlin/com/bitwarden/network/service/AccountsServiceImpl.kt @@ -16,6 +16,7 @@ import com.bitwarden.network.model.ResendEmailRequestJson import com.bitwarden.network.model.ResendNewDeviceOtpRequestJson import com.bitwarden.network.model.ResetPasswordRequestJson import com.bitwarden.network.model.SetPasswordRequestJson +import com.bitwarden.network.model.UpdateKdfJsonRequest import com.bitwarden.network.model.VerifyOtpRequestJson import com.bitwarden.network.model.toBitwardenError import com.bitwarden.network.util.HEADER_VALUE_BEARER_PREFIX @@ -183,4 +184,9 @@ internal class AccountsServiceImpl( body = KeyConnectorMasterKeyRequestJson(masterKey = masterKey), ) .toResult() + + override suspend fun updateKdf(body: UpdateKdfJsonRequest): Result = + authenticatedAccountsApi + .updateKdf(body) + .toResult() } diff --git a/network/src/test/kotlin/com/bitwarden/network/service/AccountsServiceTest.kt b/network/src/test/kotlin/com/bitwarden/network/service/AccountsServiceTest.kt index b99a7988df..8c6b779d05 100644 --- a/network/src/test/kotlin/com/bitwarden/network/service/AccountsServiceTest.kt +++ b/network/src/test/kotlin/com/bitwarden/network/service/AccountsServiceTest.kt @@ -6,15 +6,19 @@ import com.bitwarden.network.api.AuthenticatedKeyConnectorApi import com.bitwarden.network.api.UnauthenticatedAccountsApi import com.bitwarden.network.api.UnauthenticatedKeyConnectorApi import com.bitwarden.network.base.BaseServiceTest +import com.bitwarden.network.model.KdfJsonRequest import com.bitwarden.network.model.KdfTypeJson import com.bitwarden.network.model.KeyConnectorKeyRequestJson import com.bitwarden.network.model.KeyConnectorMasterKeyResponseJson +import com.bitwarden.network.model.MasterPasswordAuthenticationDataJsonRequest +import com.bitwarden.network.model.MasterPasswordUnlockDataJsonRequest import com.bitwarden.network.model.PasswordHintResponseJson import com.bitwarden.network.model.RegisterRequestJson import com.bitwarden.network.model.ResendEmailRequestJson import com.bitwarden.network.model.ResendNewDeviceOtpRequestJson import com.bitwarden.network.model.ResetPasswordRequestJson import com.bitwarden.network.model.SetPasswordRequestJson +import com.bitwarden.network.model.UpdateKdfJsonRequest import kotlinx.coroutines.test.runTest import okhttp3.mockwebserver.MockResponse import org.junit.jupiter.api.Assertions.assertEquals @@ -264,4 +268,50 @@ class AccountsServiceTest : BaseServiceTest() { ) assertTrue(result.isSuccess) } + + @Test + fun `updateKdf success should return Success`() = runTest { + val response = MockResponse().setResponseCode(200) + server.enqueue(response) + + val result = service.updateKdf(body = UPDATE_KDF_REQUEST) + + assertTrue(result.isSuccess) + } + + @Test + fun `updateKdf failure should return Failure`() = runTest { + val response = MockResponse().setResponseCode(400) + server.enqueue(response) + + val result = service.updateKdf(body = UPDATE_KDF_REQUEST) + + assertTrue(result.isFailure) + } + + private val UPDATE_KDF_REQUEST = UpdateKdfJsonRequest( + authenticationData = MasterPasswordAuthenticationDataJsonRequest( + kdf = KdfJsonRequest( + kdfType = KdfTypeJson.PBKDF2_SHA256, + iterations = 7, + memory = 1, + parallelism = 2, + ), + masterPasswordAuthenticationHash = "mockMasterPasswordHash", + salt = "mockSalt", + ), + key = "mockKey", + masterPasswordHash = "mockMasterPasswordHash", + newMasterPasswordHash = "mockNewMasterPasswordHash", + unlockData = MasterPasswordUnlockDataJsonRequest( + kdf = KdfJsonRequest( + kdfType = KdfTypeJson.PBKDF2_SHA256, + iterations = 7, + memory = 1, + parallelism = 2, + ), + masterKeyWrappedUserKey = "mockMasterPasswordKey", + salt = "mockSalt", + ), + ) }