mirror of
https://github.com/bitwarden/android.git
synced 2024-11-21 17:05:44 +03:00
PM-12259: Use validatePin SDK to validate the users pin (#4311)
This commit is contained in:
parent
1b0bc13903
commit
089136552b
5 changed files with 74 additions and 88 deletions
|
@ -2,7 +2,6 @@ package com.x8bit.bitwarden.data.auth.repository
|
||||||
|
|
||||||
import com.bitwarden.core.AuthRequestMethod
|
import com.bitwarden.core.AuthRequestMethod
|
||||||
import com.bitwarden.core.InitUserCryptoMethod
|
import com.bitwarden.core.InitUserCryptoMethod
|
||||||
import com.bitwarden.core.InitUserCryptoRequest
|
|
||||||
import com.bitwarden.crypto.HashPurpose
|
import com.bitwarden.crypto.HashPurpose
|
||||||
import com.bitwarden.crypto.Kdf
|
import com.bitwarden.crypto.Kdf
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||||
|
@ -114,7 +113,6 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.OrganizationType
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.PolicyTypeJson
|
import com.x8bit.bitwarden.data.vault.datasource.network.model.PolicyTypeJson
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.InitializeCryptoResult
|
|
||||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockData
|
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockData
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockError
|
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockError
|
||||||
|
@ -1249,41 +1247,17 @@ class AuthRepositoryImpl(
|
||||||
?.activeAccount
|
?.activeAccount
|
||||||
?.profile
|
?.profile
|
||||||
?: return ValidatePinResult.Error
|
?: return ValidatePinResult.Error
|
||||||
val privateKey = authDiskSource
|
|
||||||
.getPrivateKey(userId = activeAccount.userId)
|
|
||||||
?: return ValidatePinResult.Error
|
|
||||||
val pinProtectedUserKey = authDiskSource
|
val pinProtectedUserKey = authDiskSource
|
||||||
.getPinProtectedUserKey(userId = activeAccount.userId)
|
.getPinProtectedUserKey(userId = activeAccount.userId)
|
||||||
?: return ValidatePinResult.Error
|
?: return ValidatePinResult.Error
|
||||||
|
|
||||||
// HACK: As the SDK doesn't provide a way to directly validate the pin yet, we instead
|
|
||||||
// try to initialize the user crypto, and if it succeeds then the PIN is correct, otherwise
|
|
||||||
// the PIN is incorrect.
|
|
||||||
return vaultSdkSource
|
return vaultSdkSource
|
||||||
.initializeCrypto(
|
.validatePin(
|
||||||
userId = activeAccount.userId,
|
userId = activeAccount.userId,
|
||||||
request = InitUserCryptoRequest(
|
pin = pin,
|
||||||
kdfParams = activeAccount.toSdkParams(),
|
pinProtectedUserKey = pinProtectedUserKey,
|
||||||
email = activeAccount.email,
|
|
||||||
privateKey = privateKey,
|
|
||||||
method = InitUserCryptoMethod.Pin(
|
|
||||||
pin = pin,
|
|
||||||
pinProtectedUserKey = pinProtectedUserKey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.fold(
|
.fold(
|
||||||
onSuccess = {
|
onSuccess = { ValidatePinResult.Success(isValid = it) },
|
||||||
when (it) {
|
|
||||||
InitializeCryptoResult.Success -> {
|
|
||||||
ValidatePinResult.Success(isValid = true)
|
|
||||||
}
|
|
||||||
|
|
||||||
is InitializeCryptoResult.AuthenticationError -> {
|
|
||||||
ValidatePinResult.Success(isValid = false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onFailure = { ValidatePinResult.Error },
|
onFailure = { ValidatePinResult.Error },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,6 +94,15 @@ interface VaultSdkSource {
|
||||||
encryptedPin: String,
|
encryptedPin: String,
|
||||||
): Result<String>
|
): Result<String>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the user pin using the [pinProtectedUserKey].
|
||||||
|
*/
|
||||||
|
suspend fun validatePin(
|
||||||
|
userId: String,
|
||||||
|
pin: String,
|
||||||
|
pinProtectedUserKey: String,
|
||||||
|
): Result<Boolean>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the key for an auth request that is required to approve or decline it.
|
* Gets the key for an auth request that is required to approve or decline it.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -109,6 +109,17 @@ class VaultSdkSourceImpl(
|
||||||
.derivePinUserKey(encryptedPin = encryptedPin)
|
.derivePinUserKey(encryptedPin = encryptedPin)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun validatePin(
|
||||||
|
userId: String,
|
||||||
|
pin: String,
|
||||||
|
pinProtectedUserKey: String,
|
||||||
|
): Result<Boolean> =
|
||||||
|
runCatchingWithLogs {
|
||||||
|
getClient(userId = userId)
|
||||||
|
.auth()
|
||||||
|
.validatePin(pin = pin, pinProtectedUserKey = pinProtectedUserKey)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun getAuthRequestKey(
|
override suspend fun getAuthRequestKey(
|
||||||
publicKey: String,
|
publicKey: String,
|
||||||
userId: String,
|
userId: String,
|
||||||
|
|
|
@ -123,7 +123,6 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockOrganization
|
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockOrganization
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockPolicy
|
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockPolicy
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.InitializeCryptoResult
|
|
||||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockData
|
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockData
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
|
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
|
||||||
|
@ -5828,33 +5827,11 @@ class AuthRepositoryTest {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `validatePin returns ValidatePinResult Error when no private key found`() = runTest {
|
|
||||||
val pin = "PIN"
|
|
||||||
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
|
|
||||||
fakeAuthDiskSource.storePrivateKey(
|
|
||||||
userId = SINGLE_USER_STATE_1.activeUserId,
|
|
||||||
privateKey = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
val result = repository.validatePin(pin = pin)
|
|
||||||
|
|
||||||
assertEquals(
|
|
||||||
ValidatePinResult.Error,
|
|
||||||
result,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `validatePin returns ValidatePinResult Error when no pin protected user key found`() =
|
fun `validatePin returns ValidatePinResult Error when no pin protected user key found`() =
|
||||||
runTest {
|
runTest {
|
||||||
val pin = "PIN"
|
val pin = "PIN"
|
||||||
val privateKey = "privateKey"
|
|
||||||
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
|
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
|
||||||
fakeAuthDiskSource.storePrivateKey(
|
|
||||||
userId = SINGLE_USER_STATE_1.activeUserId,
|
|
||||||
privateKey = privateKey,
|
|
||||||
)
|
|
||||||
fakeAuthDiskSource.storePinProtectedUserKey(
|
fakeAuthDiskSource.storePinProtectedUserKey(
|
||||||
userId = SINGLE_USER_STATE_1.activeUserId,
|
userId = SINGLE_USER_STATE_1.activeUserId,
|
||||||
pinProtectedUserKey = null,
|
pinProtectedUserKey = null,
|
||||||
|
@ -5869,23 +5846,19 @@ class AuthRepositoryTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `validatePin returns ValidatePinResult Error when initialize crypto fails`() = runTest {
|
fun `validatePin returns ValidatePinResult Error when SDK validatePin fails`() = runTest {
|
||||||
val pin = "PIN"
|
val pin = "PIN"
|
||||||
val privateKey = "privateKey"
|
|
||||||
val pinProtectedUserKey = "pinProtectedUserKey"
|
val pinProtectedUserKey = "pinProtectedUserKey"
|
||||||
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
|
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
|
||||||
fakeAuthDiskSource.storePrivateKey(
|
|
||||||
userId = SINGLE_USER_STATE_1.activeUserId,
|
|
||||||
privateKey = privateKey,
|
|
||||||
)
|
|
||||||
fakeAuthDiskSource.storePinProtectedUserKey(
|
fakeAuthDiskSource.storePinProtectedUserKey(
|
||||||
userId = SINGLE_USER_STATE_1.activeUserId,
|
userId = SINGLE_USER_STATE_1.activeUserId,
|
||||||
pinProtectedUserKey = pinProtectedUserKey,
|
pinProtectedUserKey = pinProtectedUserKey,
|
||||||
)
|
)
|
||||||
coEvery {
|
coEvery {
|
||||||
vaultSdkSource.initializeCrypto(
|
vaultSdkSource.validatePin(
|
||||||
userId = SINGLE_USER_STATE_1.activeUserId,
|
userId = SINGLE_USER_STATE_1.activeUserId,
|
||||||
request = any(),
|
pin = pin,
|
||||||
|
pinProtectedUserKey = pinProtectedUserKey,
|
||||||
)
|
)
|
||||||
} returns Throwable().asFailure()
|
} returns Throwable().asFailure()
|
||||||
|
|
||||||
|
@ -5895,36 +5868,33 @@ class AuthRepositoryTest {
|
||||||
ValidatePinResult.Error,
|
ValidatePinResult.Error,
|
||||||
result,
|
result,
|
||||||
)
|
)
|
||||||
coVerify {
|
coVerify(exactly = 1) {
|
||||||
vaultSdkSource.initializeCrypto(
|
vaultSdkSource.validatePin(
|
||||||
userId = SINGLE_USER_STATE_1.activeUserId,
|
userId = SINGLE_USER_STATE_1.activeUserId,
|
||||||
request = any(),
|
pin = pin,
|
||||||
|
pinProtectedUserKey = pinProtectedUserKey,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `validatePin returns ValidatePinResult Success with valid false when initialize cryto returns AuthenticationError`() =
|
fun `validatePin returns ValidatePinResult Success with valid false when SDK validatePin returns false`() =
|
||||||
runTest {
|
runTest {
|
||||||
val pin = "PIN"
|
val pin = "PIN"
|
||||||
val privateKey = "privateKey"
|
|
||||||
val pinProtectedUserKey = "pinProtectedUserKey"
|
val pinProtectedUserKey = "pinProtectedUserKey"
|
||||||
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
|
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
|
||||||
fakeAuthDiskSource.storePrivateKey(
|
|
||||||
userId = SINGLE_USER_STATE_1.activeUserId,
|
|
||||||
privateKey = privateKey,
|
|
||||||
)
|
|
||||||
fakeAuthDiskSource.storePinProtectedUserKey(
|
fakeAuthDiskSource.storePinProtectedUserKey(
|
||||||
userId = SINGLE_USER_STATE_1.activeUserId,
|
userId = SINGLE_USER_STATE_1.activeUserId,
|
||||||
pinProtectedUserKey = pinProtectedUserKey,
|
pinProtectedUserKey = pinProtectedUserKey,
|
||||||
)
|
)
|
||||||
coEvery {
|
coEvery {
|
||||||
vaultSdkSource.initializeCrypto(
|
vaultSdkSource.validatePin(
|
||||||
userId = SINGLE_USER_STATE_1.activeUserId,
|
userId = SINGLE_USER_STATE_1.activeUserId,
|
||||||
request = any(),
|
pin = pin,
|
||||||
|
pinProtectedUserKey = pinProtectedUserKey,
|
||||||
)
|
)
|
||||||
} returns InitializeCryptoResult.AuthenticationError().asSuccess()
|
} returns false.asSuccess()
|
||||||
|
|
||||||
val result = repository.validatePin(pin = pin)
|
val result = repository.validatePin(pin = pin)
|
||||||
|
|
||||||
|
@ -5932,36 +5902,33 @@ class AuthRepositoryTest {
|
||||||
ValidatePinResult.Success(isValid = false),
|
ValidatePinResult.Success(isValid = false),
|
||||||
result,
|
result,
|
||||||
)
|
)
|
||||||
coVerify {
|
coVerify(exactly = 1) {
|
||||||
vaultSdkSource.initializeCrypto(
|
vaultSdkSource.validatePin(
|
||||||
userId = SINGLE_USER_STATE_1.activeUserId,
|
userId = SINGLE_USER_STATE_1.activeUserId,
|
||||||
request = any(),
|
pin = pin,
|
||||||
|
pinProtectedUserKey = pinProtectedUserKey,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `validatePin returns ValidatePinResult Success with valid true when initialize cryto returns Success`() =
|
fun `validatePin returns ValidatePinResult Success with valid true when SDK validatePin returns true`() =
|
||||||
runTest {
|
runTest {
|
||||||
val pin = "PIN"
|
val pin = "PIN"
|
||||||
val privateKey = "privateKey"
|
|
||||||
val pinProtectedUserKey = "pinProtectedUserKey"
|
val pinProtectedUserKey = "pinProtectedUserKey"
|
||||||
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
|
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
|
||||||
fakeAuthDiskSource.storePrivateKey(
|
|
||||||
userId = SINGLE_USER_STATE_1.activeUserId,
|
|
||||||
privateKey = privateKey,
|
|
||||||
)
|
|
||||||
fakeAuthDiskSource.storePinProtectedUserKey(
|
fakeAuthDiskSource.storePinProtectedUserKey(
|
||||||
userId = SINGLE_USER_STATE_1.activeUserId,
|
userId = SINGLE_USER_STATE_1.activeUserId,
|
||||||
pinProtectedUserKey = pinProtectedUserKey,
|
pinProtectedUserKey = pinProtectedUserKey,
|
||||||
)
|
)
|
||||||
coEvery {
|
coEvery {
|
||||||
vaultSdkSource.initializeCrypto(
|
vaultSdkSource.validatePin(
|
||||||
userId = SINGLE_USER_STATE_1.activeUserId,
|
userId = SINGLE_USER_STATE_1.activeUserId,
|
||||||
request = any(),
|
pin = pin,
|
||||||
|
pinProtectedUserKey = pinProtectedUserKey,
|
||||||
)
|
)
|
||||||
} returns InitializeCryptoResult.Success.asSuccess()
|
} returns true.asSuccess()
|
||||||
|
|
||||||
val result = repository.validatePin(pin = pin)
|
val result = repository.validatePin(pin = pin)
|
||||||
|
|
||||||
|
@ -5969,10 +5936,11 @@ class AuthRepositoryTest {
|
||||||
ValidatePinResult.Success(isValid = true),
|
ValidatePinResult.Success(isValid = true),
|
||||||
result,
|
result,
|
||||||
)
|
)
|
||||||
coVerify {
|
coVerify(exactly = 1) {
|
||||||
vaultSdkSource.initializeCrypto(
|
vaultSdkSource.validatePin(
|
||||||
userId = SINGLE_USER_STATE_1.activeUserId,
|
userId = SINGLE_USER_STATE_1.activeUserId,
|
||||||
request = any(),
|
pin = pin,
|
||||||
|
pinProtectedUserKey = pinProtectedUserKey,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -234,6 +234,30 @@ class VaultSdkSourceTest {
|
||||||
coVerify { sdkClientManager.getOrCreateClient(userId = userId) }
|
coVerify { sdkClientManager.getOrCreateClient(userId = userId) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `validatePin should call SDK and return a Result with the correct data`() =
|
||||||
|
runBlocking {
|
||||||
|
val userId = "userId"
|
||||||
|
val pin = "pin"
|
||||||
|
val pinProtectedUserKey = "pinProtectedUserKey"
|
||||||
|
val expectedResult = true
|
||||||
|
coEvery {
|
||||||
|
clientAuth.validatePin(pin = pin, pinProtectedUserKey = pinProtectedUserKey)
|
||||||
|
} returns expectedResult
|
||||||
|
|
||||||
|
val result = vaultSdkSource.validatePin(
|
||||||
|
userId = userId,
|
||||||
|
pin = pin,
|
||||||
|
pinProtectedUserKey = pinProtectedUserKey,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(expectedResult.asSuccess(), result)
|
||||||
|
coVerify(exactly = 1) {
|
||||||
|
clientAuth.validatePin(pin = pin, pinProtectedUserKey = pinProtectedUserKey)
|
||||||
|
sdkClientManager.getOrCreateClient(userId = userId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `getAuthRequestKey should call SDK and return a Result with correct data`() =
|
fun `getAuthRequestKey should call SDK and return a Result with correct data`() =
|
||||||
runBlocking {
|
runBlocking {
|
||||||
|
|
Loading…
Reference in a new issue