Add KdfTypeJson and associated BaseEnumeratedIntSerializer (#131)

This commit is contained in:
Brian Yencho 2023-10-18 15:05:33 -05:00 committed by Álison Fernandes
parent aafd32fbc3
commit a55d6a519a
5 changed files with 131 additions and 14 deletions

View file

@ -11,7 +11,7 @@ import kotlinx.serialization.Serializable
@Serializable
data class InternalPreLoginResponseJson(
@SerialName("kdf")
val kdfType: Int,
val kdfType: KdfTypeJson,
@SerialName("kdfIterations")
val kdfIterations: UInt,
@SerialName("kdfMemory")

View file

@ -0,0 +1,20 @@
package com.x8bit.bitwarden.data.auth.datasource.network.model
import com.x8bit.bitwarden.data.platform.datasource.network.serializer.BaseEnumeratedIntSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* Represents different key derivation functions (KDFs).
*/
@Serializable(KdfTypeSerializer::class)
enum class KdfTypeJson {
@SerialName("1")
ARGON2_ID,
@SerialName("0")
PBKDF2_SHA256,
}
private class KdfTypeSerializer :
BaseEnumeratedIntSerializer<KdfTypeJson>(KdfTypeJson.values())

View file

@ -3,9 +3,6 @@ package com.x8bit.bitwarden.data.auth.datasource.network.model
import com.x8bit.bitwarden.data.platform.datasource.network.serializer.BaseSurrogateSerializer
import kotlinx.serialization.Serializable
private const val KDF_TYPE_ARGON2_ID = 1
private const val KDF_TYPE_PBKDF2_SHA256 = 0
/**
* Response body for pre login.
*/
@ -21,6 +18,11 @@ data class PreLoginResponseJson(
*/
sealed class KdfParams {
/**
* The associated [KdfTypeJson].
*/
abstract val kdfTypeJson: KdfTypeJson
/**
* Models params for the Argon2id algorithm.
*/
@ -28,12 +30,20 @@ data class PreLoginResponseJson(
val iterations: UInt,
val memory: UInt,
val parallelism: UInt,
) : KdfParams()
) : KdfParams() {
override val kdfTypeJson: KdfTypeJson
get() = KdfTypeJson.ARGON2_ID
}
/**
* Models params for the PBKDF2 algorithm.
*/
data class Pbkdf2(val iterations: UInt) : KdfParams()
data class Pbkdf2(
val iterations: UInt,
) : KdfParams() {
override val kdfTypeJson: KdfTypeJson
get() = KdfTypeJson.PBKDF2_SHA256
}
}
}
@ -45,13 +55,13 @@ private class PreLoginResponseSerializer :
override fun InternalPreLoginResponseJson.toExternalType(): PreLoginResponseJson =
PreLoginResponseJson(
kdfParams = when (this.kdfType) {
KDF_TYPE_PBKDF2_SHA256 -> {
KdfTypeJson.PBKDF2_SHA256 -> {
PreLoginResponseJson.KdfParams.Pbkdf2(
iterations = this.kdfIterations,
)
}
KDF_TYPE_ARGON2_ID -> {
KdfTypeJson.ARGON2_ID -> {
@Suppress("UnsafeCallOnNullableType")
PreLoginResponseJson.KdfParams.Argon2ID(
iterations = this.kdfIterations,
@ -59,10 +69,6 @@ private class PreLoginResponseSerializer :
parallelism = this.kdfParallelism!!,
)
}
else -> throw IllegalStateException(
"Unable to parse KDF params for unknown kdfType: ${this.kdfType}",
)
},
)
@ -70,7 +76,7 @@ private class PreLoginResponseSerializer :
when (val params = this.kdfParams) {
is PreLoginResponseJson.KdfParams.Argon2ID -> {
InternalPreLoginResponseJson(
kdfType = KDF_TYPE_ARGON2_ID,
kdfType = params.kdfTypeJson,
kdfIterations = params.iterations,
kdfMemory = params.memory,
kdfParallelism = params.parallelism,
@ -79,7 +85,7 @@ private class PreLoginResponseSerializer :
is PreLoginResponseJson.KdfParams.Pbkdf2 -> {
InternalPreLoginResponseJson(
kdfType = KDF_TYPE_PBKDF2_SHA256,
kdfType = params.kdfTypeJson,
kdfIterations = params.iterations,
)
}

View file

@ -0,0 +1,41 @@
package com.x8bit.bitwarden.data.platform.datasource.network.serializer
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
/**
* Base [KSerializer] for mapping an [Enum] with possible values given by [values] to/from integer
* values, which should be specified using [SerialName].
*/
@Suppress("UnnecessaryAbstractClass")
abstract class BaseEnumeratedIntSerializer<T : Enum<T>>(
private val values: Array<T>,
) : KSerializer<T> {
override val descriptor: SerialDescriptor
get() = PrimitiveSerialDescriptor(
serialName = this::class.java.simpleName,
kind = PrimitiveKind.INT,
)
override fun deserialize(decoder: Decoder): T {
val decodedValue = decoder.decodeInt().toString()
return values.first { it.serialNameAnnotation?.value == decodedValue }
}
override fun serialize(encoder: Encoder, value: T) {
encoder.encodeInt(
requireNotNull(
value.serialNameAnnotation?.value?.toInt(),
),
)
}
private val Enum<*>.serialNameAnnotation: SerialName?
get() = javaClass.getDeclaredField(name).getAnnotation(SerialName::class.java)
}

View file

@ -0,0 +1,50 @@
package com.x8bit.bitwarden.data.platform.datasource.network.serializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.encodeToJsonElement
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class BaseEnumeratedIntSerializerTest {
private val json = Json
@Test
fun `properly deserializes integers to enums`() {
assertEquals(
TestEnum.CASE_1,
json.decodeFromString<TestEnum>(
"""
1
""",
),
)
}
@Test
fun `properly serializes enums back to integers`() {
assertEquals(
json.parseToJsonElement(
"""
1
""",
),
json.encodeToJsonElement(
TestEnum.CASE_1,
),
)
}
}
@Serializable(TestEnumSerializer::class)
private enum class TestEnum {
@SerialName("1")
CASE_1,
@SerialName("2")
CASE_2,
}
private class TestEnumSerializer :
BaseEnumeratedIntSerializer<TestEnum>(values = TestEnum.values())