Add AuthSdkSource (#118)

This commit is contained in:
Brian Yencho
2023-10-16 13:52:18 -05:00
committed by GitHub
parent c7794f0ca4
commit 1f197b4796
12 changed files with 591 additions and 16 deletions

View File

@@ -0,0 +1,159 @@
package com.x8bit.bitwarden.data.auth.datasource.sdk
import com.bitwarden.core.Kdf
import com.bitwarden.core.MasterPasswordPolicyOptions
import com.bitwarden.core.RegisterKeyResponse
import com.bitwarden.sdk.ClientAuth
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength
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.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
class AuthSdkSourceTest {
private val clientAuth = mockk<ClientAuth>()
private val authSkdSource: AuthSdkSource = AuthSdkSourceImpl(
clientAuth = clientAuth,
)
@Test
fun `hashPassword should call SDK and return a Result with the correct data`() = runBlocking {
val email = "email"
val password = "password"
val kdf = mockk<Kdf>()
val expectedResult = "hashedPassword"
coEvery {
clientAuth.hashPassword(
email = email,
password = password,
kdfParams = kdf,
)
} returns expectedResult
val result = authSkdSource.hashPassword(
email = email,
password = password,
kdf = kdf,
)
assertEquals(
expectedResult.asSuccess(),
result,
)
coVerify {
clientAuth.hashPassword(
email = email,
password = password,
kdfParams = kdf,
)
}
}
@Test
fun `makeRegisterKeys should call SDK and return a Result with the correct data`() =
runBlocking {
val email = "email"
val password = "password"
val kdf = mockk<Kdf>()
val expectedResult = mockk<RegisterKeyResponse>()
coEvery {
clientAuth.makeRegisterKeys(
email = email,
password = password,
kdf = kdf,
)
} returns expectedResult
val result = authSkdSource.makeRegisterKeys(
email = email,
password = password,
kdf = kdf,
)
assertEquals(
expectedResult.asSuccess(),
result,
)
coVerify {
clientAuth.makeRegisterKeys(
email = email,
password = password,
kdf = kdf,
)
}
}
// TODO: This test is disabled due to issue here with mocking UByte (BIT-877).
// See: https://github.com/mockk/mockk/issues/544
@Disabled
@Test
fun `passwordStrength should call SDK and return a Result with the correct data`() =
runBlocking {
val email = "email"
val password = "password"
val additionalInputs = listOf("test1", "test2")
val sdkResult = 3.toUByte()
val expectedResult = PasswordStrength.LEVEL_3
coEvery {
clientAuth.passwordStrength(
email = email,
password = password,
additionalInputs = additionalInputs,
)
} returns sdkResult
val result = authSkdSource.passwordStrength(
email = email,
password = password,
additionalInputs = additionalInputs,
)
assertEquals(
expectedResult.asSuccess(),
result,
)
coVerify {
clientAuth.passwordStrength(
email = email,
password = password,
additionalInputs = additionalInputs,
)
}
}
@Test
fun `satisfiesPolicy should call SDK and return a Result with the correct data`() =
runBlocking {
val password = "password"
val passwordStrength = PasswordStrength.LEVEL_3
val rawStrength = 3.toUByte()
val policy = mockk<MasterPasswordPolicyOptions>()
val expectedResult = true
coEvery {
clientAuth.satisfiesPolicy(
password = password,
strength = rawStrength,
policy = policy,
)
} returns expectedResult
val result = authSkdSource.satisfiesPolicy(
password = password,
passwordStrength = passwordStrength,
policy = policy,
)
assertEquals(
expectedResult.asSuccess(),
result,
)
coVerify {
clientAuth.satisfiesPolicy(
password = password,
strength = rawStrength,
policy = policy,
)
}
}
}

View File

@@ -1,7 +1,6 @@
package com.x8bit.bitwarden.data.auth.repository
import app.cash.turbine.test
import com.bitwarden.sdk.Client
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthState
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson
@@ -10,6 +9,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.PreLoginResponseJs
import com.x8bit.bitwarden.data.auth.datasource.network.service.AccountsService
import com.x8bit.bitwarden.data.auth.datasource.network.service.IdentityService
import com.x8bit.bitwarden.data.auth.datasource.network.util.CaptchaCallbackTokenResult
import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource
import com.x8bit.bitwarden.data.auth.util.toSdkParams
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.AuthTokenInterceptor
import io.mockk.clearMocks
@@ -30,20 +30,20 @@ class AuthRepositoryTest {
private val identityService: IdentityService = mockk()
private val authInterceptor = mockk<AuthTokenInterceptor>()
private val fakeAuthDiskSource = FakeAuthDiskSource()
private val mockBitwardenSdk = mockk<Client> {
private val authSdkSource = mockk<AuthSdkSource> {
coEvery {
auth().hashPassword(
hashPassword(
email = EMAIL,
password = PASSWORD,
kdfParams = PRE_LOGIN_SUCCESS.kdfParams.toSdkParams(),
kdf = PRE_LOGIN_SUCCESS.kdfParams.toSdkParams(),
)
} returns PASSWORD_HASH
} returns Result.success(PASSWORD_HASH)
}
private val repository = AuthRepositoryImpl(
accountsService = accountsService,
identityService = identityService,
bitwardenSdkClient = mockBitwardenSdk,
authSdkSource = authSdkSource,
authDiskSource = fakeAuthDiskSource,
authTokenInterceptor = authInterceptor,
)

View File

@@ -0,0 +1,151 @@
package com.x8bit.bitwarden.data.platform.datasource.sdk.util
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength
import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toInt
import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toPasswordStrengthOrNull
import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toUByte
import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toUInt
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
class PasswordStrengthExtensionsTest {
@Nested
inner class IntegerType {
@Test
fun `toPasswordStrengthOrNull returns the correct values in 0 to 4 range`() {
mapOf(
0 to PasswordStrength.LEVEL_0,
1 to PasswordStrength.LEVEL_1,
2 to PasswordStrength.LEVEL_2,
3 to PasswordStrength.LEVEL_3,
4 to PasswordStrength.LEVEL_4,
)
.forEach { (intValue, level) ->
assertEquals(
level,
intValue.toPasswordStrengthOrNull(),
)
}
}
@Test
fun `toPasswordStrengthOrNull returns null outside the 0 to 4 range`() {
listOf(-2, -1, 5, 6).forEach { intValue ->
assertNull(
intValue.toPasswordStrengthOrNull(),
)
}
}
@Test
fun `toInt returns the correct Int for each level`() {
mapOf(
PasswordStrength.LEVEL_0 to 0,
PasswordStrength.LEVEL_1 to 1,
PasswordStrength.LEVEL_2 to 2,
PasswordStrength.LEVEL_3 to 3,
PasswordStrength.LEVEL_4 to 4,
)
.forEach { (level, intValue) ->
assertEquals(
intValue,
level.toInt(),
)
}
}
}
@Nested
inner class UByteType {
@Test
fun `toPasswordStrengthOrNull returns the correct values in 0 to 4 range`() {
mapOf(
0.toUByte() to PasswordStrength.LEVEL_0,
1.toUByte() to PasswordStrength.LEVEL_1,
2.toUByte() to PasswordStrength.LEVEL_2,
3.toUByte() to PasswordStrength.LEVEL_3,
4.toUByte() to PasswordStrength.LEVEL_4,
)
.forEach { (uByteValue, level) ->
assertEquals(
level,
uByteValue.toPasswordStrengthOrNull(),
)
}
}
@Test
fun `toPasswordStrengthOrNull returns null outside the 0 to 4 range`() {
listOf(5.toUByte(), 6.toUByte()).forEach { uByteValue ->
assertNull(
uByteValue.toPasswordStrengthOrNull(),
)
}
}
@Test
fun `toUByte returns the correct UByte for each level`() {
mapOf(
PasswordStrength.LEVEL_0 to 0.toUByte(),
PasswordStrength.LEVEL_1 to 1.toUByte(),
PasswordStrength.LEVEL_2 to 2.toUByte(),
PasswordStrength.LEVEL_3 to 3.toUByte(),
PasswordStrength.LEVEL_4 to 4.toUByte(),
)
.forEach { (level, uByteValue) ->
assertEquals(
uByteValue,
level.toUByte(),
)
}
}
}
@Nested
inner class UIntType {
@Test
fun `toPasswordStrengthOrNull returns the correct values in 0 to 4 range`() {
mapOf(
0u to PasswordStrength.LEVEL_0,
1u to PasswordStrength.LEVEL_1,
2u to PasswordStrength.LEVEL_2,
3u to PasswordStrength.LEVEL_3,
4u to PasswordStrength.LEVEL_4,
)
.forEach { (uIntValue, level) ->
assertEquals(
level,
uIntValue.toPasswordStrengthOrNull(),
)
}
}
@Test
fun `toPasswordStrengthOrNull returns null outside the 0 to 4 range`() {
listOf(5u, 6u).forEach { uIntValue ->
assertNull(
uIntValue.toPasswordStrengthOrNull(),
)
}
}
@Test
fun `toUInt returns the correct UInt for each level`() {
mapOf(
PasswordStrength.LEVEL_0 to 0u,
PasswordStrength.LEVEL_1 to 1u,
PasswordStrength.LEVEL_2 to 2u,
PasswordStrength.LEVEL_3 to 3u,
PasswordStrength.LEVEL_4 to 4u,
)
.forEach { (level, uIntValue) ->
assertEquals(
uIntValue,
level.toUInt(),
)
}
}
}
}

View File

@@ -49,4 +49,21 @@ class ResultTest {
assertTrue(stringResult.isFailure)
assertEquals(expectedException, stringResult.exceptionOrNull())
}
@Test
fun `asSuccess returns a success Result with the correct content`() {
assertEquals(
Result.success("Test"),
"Test".asSuccess(),
)
}
@Test
fun `asFailure returns a failure Result with the correct content`() {
val throwable = IllegalStateException("Test")
assertEquals(
Result.failure<Nothing>(throwable),
throwable.asFailure(),
)
}
}