mirror of
https://github.com/bitwarden/android.git
synced 2025-03-16 11:18:45 +03:00
BIT-990: Initialize Crypto for Vault (#213)
This commit is contained in:
parent
7fc571bb92
commit
a9295ff981
16 changed files with 481 additions and 14 deletions
|
@ -18,10 +18,12 @@ import com.x8bit.bitwarden.data.auth.repository.model.LoginResult
|
|||
import com.x8bit.bitwarden.data.auth.repository.model.RegisterResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.toUserState
|
||||
import com.x8bit.bitwarden.data.auth.util.KdfParamsConstants.DEFAULT_PBKDF2_ITERATIONS
|
||||
import com.x8bit.bitwarden.data.auth.util.toSdkParams
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.util.asSuccess
|
||||
import com.x8bit.bitwarden.data.platform.util.flatMap
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
|
@ -30,22 +32,20 @@ import kotlinx.coroutines.flow.StateFlow
|
|||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
private const val DEFAULT_KDF_ITERATIONS = 600000
|
||||
|
||||
/**
|
||||
* Default implementation of [AuthRepository].
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
@Singleton
|
||||
class AuthRepositoryImpl @Inject constructor(
|
||||
class AuthRepositoryImpl constructor(
|
||||
private val accountsService: AccountsService,
|
||||
private val haveIBeenPwnedService: HaveIBeenPwnedService,
|
||||
private val identityService: IdentityService,
|
||||
private val authSdkSource: AuthSdkSource,
|
||||
private val authDiskSource: AuthDiskSource,
|
||||
private val vaultRepository: VaultRepository,
|
||||
dispatcherManager: DispatcherManager,
|
||||
) : AuthRepository {
|
||||
private val scope = CoroutineScope(dispatcherManager.io)
|
||||
|
@ -122,6 +122,7 @@ class AuthRepositoryImpl @Inject constructor(
|
|||
privateKey = it.privateKey,
|
||||
)
|
||||
}
|
||||
vaultRepository.unlockVaultAndSync(masterPassword = password)
|
||||
LoginResult.Success
|
||||
}
|
||||
|
||||
|
@ -176,7 +177,7 @@ class AuthRepositoryImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
val kdf = Kdf.Pbkdf2(DEFAULT_KDF_ITERATIONS.toUInt())
|
||||
val kdf = Kdf.Pbkdf2(iterations = DEFAULT_PBKDF2_ITERATIONS.toUInt())
|
||||
return authSdkSource
|
||||
.makeRegisterKeys(
|
||||
email = email,
|
||||
|
|
|
@ -8,6 +8,7 @@ import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource
|
|||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepositoryImpl
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
@ -31,6 +32,7 @@ object AuthRepositoryModule {
|
|||
authSdkSource: AuthSdkSource,
|
||||
authDiskSource: AuthDiskSource,
|
||||
dispatchers: DispatcherManager,
|
||||
vaultRepository: VaultRepository,
|
||||
): AuthRepository = AuthRepositoryImpl(
|
||||
accountsService = accountsService,
|
||||
identityService = identityService,
|
||||
|
@ -38,5 +40,6 @@ object AuthRepositoryModule {
|
|||
authDiskSource = authDiskSource,
|
||||
haveIBeenPwnedService = haveIBeenPwnedService,
|
||||
dispatcherManager = dispatchers,
|
||||
vaultRepository = vaultRepository,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
package com.x8bit.bitwarden.data.auth.repository.util
|
||||
|
||||
import com.bitwarden.core.Kdf
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.KdfTypeJson
|
||||
import com.x8bit.bitwarden.data.auth.util.KdfParamsConstants
|
||||
|
||||
/**
|
||||
* Convert [AccountJson.Profile] to [Kdf] params for use with Bitwarden SDK.
|
||||
*/
|
||||
fun AccountJson.Profile.toSdkParams(): Kdf {
|
||||
return when (this.kdfType) {
|
||||
KdfTypeJson.ARGON2_ID -> Kdf.Argon2id(
|
||||
iterations = (kdfIterations ?: KdfParamsConstants.DEFAULT_ARGON2_ITERATIONS).toUInt(),
|
||||
memory = (kdfMemory ?: KdfParamsConstants.DEFAULT_ARGON2_MEMORY).toUInt(),
|
||||
parallelism =
|
||||
(kdfParallelism ?: KdfParamsConstants.DEFAULT_ARGON2_PARALLELISM).toUInt(),
|
||||
)
|
||||
|
||||
KdfTypeJson.PBKDF2_SHA256 -> Kdf.Pbkdf2(
|
||||
iterations = (kdfIterations ?: KdfParamsConstants.DEFAULT_PBKDF2_ITERATIONS).toUInt(),
|
||||
)
|
||||
|
||||
else -> Kdf.Pbkdf2(iterations = KdfParamsConstants.DEFAULT_PBKDF2_ITERATIONS.toUInt())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package com.x8bit.bitwarden.data.auth.util
|
||||
|
||||
import com.bitwarden.core.Kdf
|
||||
|
||||
/**
|
||||
* Constants relating to [Kdf] initialization defaults.
|
||||
*/
|
||||
object KdfParamsConstants {
|
||||
|
||||
/**
|
||||
* The default number of iterations when calculating a user's password for [Kdf.Pbkdf2].
|
||||
*/
|
||||
const val DEFAULT_PBKDF2_ITERATIONS: Int = 600000
|
||||
|
||||
/**
|
||||
* The default number of iterations when calculating a user's password for [Kdf.Argon2id].
|
||||
*/
|
||||
const val DEFAULT_ARGON2_ITERATIONS: Int = 3
|
||||
|
||||
/**
|
||||
* The default amount of memory to use when calculating a password hash (MB) for [Kdf.Argon2id].
|
||||
*/
|
||||
const val DEFAULT_ARGON2_MEMORY: Int = 64
|
||||
|
||||
/**
|
||||
* The default number of threads to use when calculating a password hash for [Kdf.Argon2id].
|
||||
*/
|
||||
const val DEFAULT_ARGON2_PARALLELISM: Int = 4
|
||||
}
|
|
@ -5,11 +5,20 @@ import com.bitwarden.core.CipherListView
|
|||
import com.bitwarden.core.CipherView
|
||||
import com.bitwarden.core.Folder
|
||||
import com.bitwarden.core.FolderView
|
||||
import com.bitwarden.core.InitCryptoRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.InitializeCryptoResult
|
||||
|
||||
/**
|
||||
* Source of vault information and functionality from the Bitwarden SDK.
|
||||
*/
|
||||
interface VaultSdkSource {
|
||||
|
||||
/**
|
||||
* Attempts to initialize cryptography functionality for the Bitwarden SDK
|
||||
* with a given [InitCryptoRequest].
|
||||
*/
|
||||
suspend fun initializeCrypto(request: InitCryptoRequest): Result<InitializeCryptoResult>
|
||||
|
||||
/**
|
||||
* Decrypts a [Cipher] returning a [CipherView] wrapped in a [Result].
|
||||
*/
|
||||
|
|
|
@ -5,7 +5,11 @@ import com.bitwarden.core.CipherListView
|
|||
import com.bitwarden.core.CipherView
|
||||
import com.bitwarden.core.Folder
|
||||
import com.bitwarden.core.FolderView
|
||||
import com.bitwarden.core.InitCryptoRequest
|
||||
import com.bitwarden.sdk.BitwardenException
|
||||
import com.bitwarden.sdk.ClientCrypto
|
||||
import com.bitwarden.sdk.ClientVault
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.InitializeCryptoResult
|
||||
|
||||
/**
|
||||
* Primary implementation of [VaultSdkSource] that serves as a convenience wrapper around a
|
||||
|
@ -13,7 +17,20 @@ import com.bitwarden.sdk.ClientVault
|
|||
*/
|
||||
class VaultSdkSourceImpl(
|
||||
private val clientVault: ClientVault,
|
||||
private val clientCrypto: ClientCrypto,
|
||||
) : VaultSdkSource {
|
||||
override suspend fun initializeCrypto(
|
||||
request: InitCryptoRequest,
|
||||
): Result<InitializeCryptoResult> =
|
||||
runCatching {
|
||||
try {
|
||||
clientCrypto.initializeCrypto(req = request)
|
||||
InitializeCryptoResult.Success
|
||||
} catch (exception: BitwardenException) {
|
||||
// The only truly expected error from the SDK is an incorrect password.
|
||||
InitializeCryptoResult.AuthenticationError
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun decryptCipher(cipher: Cipher): Result<CipherView> =
|
||||
runCatching { clientVault.ciphers().decrypt(cipher) }
|
||||
|
|
|
@ -14,11 +14,15 @@ import javax.inject.Singleton
|
|||
*/
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class VaultSdkModule {
|
||||
object VaultSdkModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesVaultSdkSource(
|
||||
client: Client,
|
||||
): VaultSdkSource = VaultSdkSourceImpl(clientVault = client.vault())
|
||||
): VaultSdkSource =
|
||||
VaultSdkSourceImpl(
|
||||
clientVault = client.vault(),
|
||||
clientCrypto = client.crypto(),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package com.x8bit.bitwarden.data.vault.datasource.sdk.model
|
||||
|
||||
/**
|
||||
* Models result of initializing cryptography functionality for the Bitwarden SDK.
|
||||
*/
|
||||
sealed class InitializeCryptoResult {
|
||||
|
||||
/**
|
||||
* Successfully initialized cryptography functionality.
|
||||
*/
|
||||
data object Success : InitializeCryptoResult()
|
||||
|
||||
/**
|
||||
* Incorrect password provided.
|
||||
*/
|
||||
data object AuthenticationError : InitializeCryptoResult()
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
package com.x8bit.bitwarden.data.vault.repository
|
||||
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
|
||||
|
||||
/**
|
||||
* Responsible for managing vault data inside the network layer.
|
||||
*/
|
||||
|
@ -8,5 +10,10 @@ interface VaultRepository {
|
|||
/**
|
||||
* Attempt to sync the vault data.
|
||||
*/
|
||||
suspend fun sync()
|
||||
fun sync()
|
||||
|
||||
/**
|
||||
* Attempt to initialize crypto and sync the vault data.
|
||||
*/
|
||||
suspend fun unlockVaultAndSync(masterPassword: String): VaultUnlockResult
|
||||
}
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
package com.x8bit.bitwarden.data.vault.repository
|
||||
|
||||
import com.bitwarden.core.InitCryptoRequest
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.toSdkParams
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.service.SyncService
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkCipherList
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkFolderList
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.toVaultUnlockResult
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -24,7 +28,7 @@ class VaultRepositoryImpl constructor(
|
|||
|
||||
private var syncJob: Job = Job().apply { complete() }
|
||||
|
||||
override suspend fun sync() {
|
||||
override fun sync() {
|
||||
if (!syncJob.isCompleted) return
|
||||
syncJob = scope.launch {
|
||||
syncService
|
||||
|
@ -36,7 +40,6 @@ class VaultRepositoryImpl constructor(
|
|||
privateKey = syncResponse.profile?.privateKey,
|
||||
)
|
||||
// TODO transform into domain object consumable by VaultViewModel BIT-205.
|
||||
// TODO initialize crypto in BIT-990
|
||||
syncResponse.ciphers?.let { networkCiphers ->
|
||||
vaultSdkSource.decryptCipherList(
|
||||
cipherList = networkCiphers.toEncryptedSdkCipherList(),
|
||||
|
@ -55,6 +58,13 @@ class VaultRepositoryImpl constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun unlockVaultAndSync(masterPassword: String): VaultUnlockResult {
|
||||
return initializeCrypto(masterPassword = masterPassword)
|
||||
.also { vaultUnlockedResult ->
|
||||
if (vaultUnlockedResult is VaultUnlockResult.Success) sync()
|
||||
}
|
||||
}
|
||||
|
||||
private fun storeUserKeyAndPrivateKey(
|
||||
userKey: String?,
|
||||
privateKey: String?,
|
||||
|
@ -72,4 +82,30 @@ class VaultRepositoryImpl constructor(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("ReturnCount")
|
||||
private suspend fun initializeCrypto(masterPassword: String): VaultUnlockResult {
|
||||
val userState = authDiskSource.userState
|
||||
?: return VaultUnlockResult.InvalidStateError
|
||||
val userKey = authDiskSource.getUserKey(userId = userState.activeUserId)
|
||||
?: return VaultUnlockResult.InvalidStateError
|
||||
val privateKey = authDiskSource.getPrivateKey(userId = userState.activeUserId)
|
||||
?: return VaultUnlockResult.InvalidStateError
|
||||
return vaultSdkSource
|
||||
.initializeCrypto(
|
||||
request = InitCryptoRequest(
|
||||
kdfParams = userState.activeAccount.profile.toSdkParams(),
|
||||
email = userState.activeAccount.profile.email,
|
||||
password = masterPassword,
|
||||
userKey = userKey,
|
||||
privateKey = privateKey,
|
||||
// TODO use actual organization keys BIT-1091
|
||||
organizationKeys = mapOf(),
|
||||
),
|
||||
)
|
||||
.fold(
|
||||
onFailure = { VaultUnlockResult.GenericError },
|
||||
onSuccess = { it.toVaultUnlockResult() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ import javax.inject.Singleton
|
|||
*/
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class VaultRepositoryModule {
|
||||
object VaultRepositoryModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package com.x8bit.bitwarden.data.vault.repository.model
|
||||
|
||||
/**
|
||||
* Models result of unlocking the vault.
|
||||
*/
|
||||
sealed class VaultUnlockResult {
|
||||
|
||||
/**
|
||||
* Vault successfully unlocked.
|
||||
*/
|
||||
data object Success : VaultUnlockResult()
|
||||
|
||||
/**
|
||||
* Incorrect password provided.
|
||||
*/
|
||||
data object AuthenticationError : VaultUnlockResult()
|
||||
|
||||
/**
|
||||
* Unable to access user state information.
|
||||
*/
|
||||
data object InvalidStateError : VaultUnlockResult()
|
||||
|
||||
/**
|
||||
* Generic error thrown by Bitwarden SDK.
|
||||
*/
|
||||
data object GenericError : VaultUnlockResult()
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package com.x8bit.bitwarden.data.vault.repository.util
|
||||
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.InitializeCryptoResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
|
||||
|
||||
/**
|
||||
* Transform a [InitializeCryptoResult] to [VaultUnlockResult].
|
||||
*/
|
||||
fun InitializeCryptoResult.toVaultUnlockResult(): VaultUnlockResult =
|
||||
when (this) {
|
||||
InitializeCryptoResult.AuthenticationError -> VaultUnlockResult.AuthenticationError
|
||||
InitializeCryptoResult.Success -> VaultUnlockResult.Success
|
||||
}
|
|
@ -31,6 +31,8 @@ import com.x8bit.bitwarden.data.auth.util.toSdkParams
|
|||
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.util.asSuccess
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
|
@ -45,12 +47,14 @@ import org.junit.jupiter.api.Assertions.assertNull
|
|||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@Suppress("LargeClass")
|
||||
class AuthRepositoryTest {
|
||||
|
||||
private val dispatcherManager: DispatcherManager = FakeDispatcherManager()
|
||||
private val accountsService: AccountsService = mockk()
|
||||
private val identityService: IdentityService = mockk()
|
||||
private val haveIBeenPwnedService: HaveIBeenPwnedService = mockk()
|
||||
private val vaultRepository: VaultRepository = mockk()
|
||||
private val fakeAuthDiskSource = FakeAuthDiskSource()
|
||||
private val authSdkSource = mockk<AuthSdkSource> {
|
||||
coEvery {
|
||||
|
@ -85,6 +89,7 @@ class AuthRepositoryTest {
|
|||
authSdkSource = authSdkSource,
|
||||
authDiskSource = fakeAuthDiskSource,
|
||||
dispatcherManager = dispatcherManager,
|
||||
vaultRepository = vaultRepository,
|
||||
)
|
||||
|
||||
@BeforeEach
|
||||
|
@ -183,7 +188,8 @@ class AuthRepositoryTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `login get token succeeds should return Success and update AuthState and stored keys`() =
|
||||
@Suppress("MaxLineLength")
|
||||
fun `login get token succeeds should return Success, update AuthState, update stored keys, and unlockVaultAndSync`() =
|
||||
runTest {
|
||||
val successResponse = GET_TOKEN_RESPONSE_SUCCESS
|
||||
coEvery {
|
||||
|
@ -197,6 +203,9 @@ class AuthRepositoryTest {
|
|||
)
|
||||
}
|
||||
.returns(Result.success(successResponse))
|
||||
coEvery {
|
||||
vaultRepository.unlockVaultAndSync(masterPassword = PASSWORD)
|
||||
} returns VaultUnlockResult.Success
|
||||
every {
|
||||
GET_TOKEN_RESPONSE_SUCCESS.toUserState(previousUserState = null)
|
||||
} returns SINGLE_USER_STATE_1
|
||||
|
@ -219,6 +228,9 @@ class AuthRepositoryTest {
|
|||
captchaToken = null,
|
||||
)
|
||||
}
|
||||
coVerify {
|
||||
vaultRepository.unlockVaultAndSync(masterPassword = PASSWORD)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -597,6 +609,9 @@ class AuthRepositoryTest {
|
|||
captchaToken = null,
|
||||
)
|
||||
} returns Result.success(successResponse)
|
||||
coEvery {
|
||||
vaultRepository.unlockVaultAndSync(masterPassword = PASSWORD)
|
||||
} returns VaultUnlockResult.Success
|
||||
every {
|
||||
GET_TOKEN_RESPONSE_SUCCESS.toUserState(previousUserState = null)
|
||||
} returns SINGLE_USER_STATE_1
|
||||
|
@ -643,6 +658,9 @@ class AuthRepositoryTest {
|
|||
captchaToken = null,
|
||||
)
|
||||
} returns Result.success(successResponse)
|
||||
coEvery {
|
||||
vaultRepository.unlockVaultAndSync(masterPassword = PASSWORD)
|
||||
} returns VaultUnlockResult.Success
|
||||
every {
|
||||
GET_TOKEN_RESPONSE_SUCCESS.toUserState(previousUserState = SINGLE_USER_STATE_2)
|
||||
} returns MULTI_USER_STATE
|
||||
|
|
|
@ -5,22 +5,99 @@ import com.bitwarden.core.CipherListView
|
|||
import com.bitwarden.core.CipherView
|
||||
import com.bitwarden.core.Folder
|
||||
import com.bitwarden.core.FolderView
|
||||
import com.bitwarden.core.InitCryptoRequest
|
||||
import com.bitwarden.sdk.BitwardenException
|
||||
import com.bitwarden.sdk.ClientCrypto
|
||||
import com.bitwarden.sdk.ClientVault
|
||||
import com.x8bit.bitwarden.data.platform.util.asFailure
|
||||
import com.x8bit.bitwarden.data.platform.util.asSuccess
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.InitializeCryptoResult
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.IllegalStateException
|
||||
|
||||
class VaultSdkSourceTest {
|
||||
private val clientVault = mockk<ClientVault>()
|
||||
|
||||
private val clientCrypto = mockk<ClientCrypto>()
|
||||
private val vaultSdkSource: VaultSdkSource = VaultSdkSourceImpl(
|
||||
clientVault = clientVault,
|
||||
clientCrypto = clientCrypto,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `initializeCrypto with sdk success should return InitializeCryptoResult Success`() =
|
||||
runBlocking {
|
||||
val mockInitCryptoRequest = mockk<InitCryptoRequest>()
|
||||
coEvery {
|
||||
clientCrypto.initializeCrypto(
|
||||
req = mockInitCryptoRequest,
|
||||
)
|
||||
} returns Unit
|
||||
val result = vaultSdkSource.initializeCrypto(
|
||||
request = mockInitCryptoRequest,
|
||||
)
|
||||
assertEquals(
|
||||
InitializeCryptoResult.Success.asSuccess(),
|
||||
result,
|
||||
)
|
||||
coVerify {
|
||||
clientCrypto.initializeCrypto(
|
||||
req = mockInitCryptoRequest,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `initializeCrypto with sdk failure should return failure`() = runBlocking {
|
||||
val mockInitCryptoRequest = mockk<InitCryptoRequest>()
|
||||
val expectedException = IllegalStateException("mock")
|
||||
coEvery {
|
||||
clientCrypto.initializeCrypto(
|
||||
req = mockInitCryptoRequest,
|
||||
)
|
||||
} throws expectedException
|
||||
val result = vaultSdkSource.initializeCrypto(
|
||||
request = mockInitCryptoRequest,
|
||||
)
|
||||
assertEquals(
|
||||
expectedException.asFailure(),
|
||||
result,
|
||||
)
|
||||
coVerify {
|
||||
clientCrypto.initializeCrypto(
|
||||
req = mockInitCryptoRequest,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `initializeCrypto with BitwardenException failure should return AuthenticationError`() =
|
||||
runBlocking {
|
||||
val mockInitCryptoRequest = mockk<InitCryptoRequest>()
|
||||
val expectedException = BitwardenException.E(message = "")
|
||||
coEvery {
|
||||
clientCrypto.initializeCrypto(
|
||||
req = mockInitCryptoRequest,
|
||||
)
|
||||
} throws expectedException
|
||||
val result = vaultSdkSource.initializeCrypto(
|
||||
request = mockInitCryptoRequest,
|
||||
)
|
||||
assertEquals(
|
||||
InitializeCryptoResult.AuthenticationError.asSuccess(),
|
||||
result,
|
||||
)
|
||||
coVerify {
|
||||
clientCrypto.initializeCrypto(
|
||||
req = mockInitCryptoRequest,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Cipher decrypt should call SDK and return a Result with correct data`() = runBlocking {
|
||||
val mockCipher = mockk<Cipher>()
|
||||
|
|
|
@ -1,18 +1,25 @@
|
|||
package com.x8bit.bitwarden.data.vault.repository
|
||||
|
||||
import com.bitwarden.core.InitCryptoRequest
|
||||
import com.bitwarden.core.Kdf
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.util.FakeAuthDiskSource
|
||||
import com.x8bit.bitwarden.data.auth.util.KdfParamsConstants.DEFAULT_PBKDF2_ITERATIONS
|
||||
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockSyncResponse
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.service.SyncService
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.InitializeCryptoResult
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.createMockSdkCipher
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.createMockSdkFolder
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class VaultRepositoryTest {
|
||||
|
@ -50,6 +57,182 @@ class VaultRepositoryTest {
|
|||
privateKey = "mockPrivateKey-1",
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unlockVaultAndSync with initializeCrypto Success should sync and return Success`() =
|
||||
runTest {
|
||||
coEvery {
|
||||
syncService.sync()
|
||||
} returns Result.success(createMockSyncResponse(number = 1))
|
||||
coEvery {
|
||||
vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1)))
|
||||
} returns mockk()
|
||||
coEvery {
|
||||
vaultSdkSource.decryptFolderList(listOf(createMockSdkFolder(1)))
|
||||
} returns mockk()
|
||||
fakeAuthDiskSource.storePrivateKey(
|
||||
userId = "mockUserId",
|
||||
privateKey = "mockPrivateKey-1",
|
||||
)
|
||||
fakeAuthDiskSource.storeUserKey(
|
||||
userId = "mockUserId",
|
||||
userKey = "mockKey-1",
|
||||
)
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
coEvery {
|
||||
vaultSdkSource.initializeCrypto(
|
||||
request = InitCryptoRequest(
|
||||
kdfParams = Kdf.Pbkdf2(iterations = DEFAULT_PBKDF2_ITERATIONS.toUInt()),
|
||||
email = "email",
|
||||
password = "mockPassword-1",
|
||||
userKey = "mockKey-1",
|
||||
privateKey = "mockPrivateKey-1",
|
||||
organizationKeys = mapOf(),
|
||||
),
|
||||
)
|
||||
} returns Result.success(InitializeCryptoResult.Success)
|
||||
|
||||
val result = vaultRepository.unlockVaultAndSync(masterPassword = "mockPassword-1")
|
||||
|
||||
assertEquals(
|
||||
VaultUnlockResult.Success,
|
||||
result,
|
||||
)
|
||||
coVerify { syncService.sync() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unlockVaultAndSync with initializeCrypto failure should return GenericError`() =
|
||||
runTest {
|
||||
coEvery {
|
||||
syncService.sync()
|
||||
} returns Result.success(createMockSyncResponse(number = 1))
|
||||
coEvery {
|
||||
vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1)))
|
||||
} returns mockk()
|
||||
coEvery {
|
||||
vaultSdkSource.decryptFolderList(listOf(createMockSdkFolder(1)))
|
||||
} returns mockk()
|
||||
fakeAuthDiskSource.storePrivateKey(
|
||||
userId = "mockUserId",
|
||||
privateKey = "mockPrivateKey-1",
|
||||
)
|
||||
fakeAuthDiskSource.storeUserKey(
|
||||
userId = "mockUserId",
|
||||
userKey = "mockKey-1",
|
||||
)
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
coEvery {
|
||||
vaultSdkSource.initializeCrypto(
|
||||
request = InitCryptoRequest(
|
||||
kdfParams = Kdf.Pbkdf2(iterations = DEFAULT_PBKDF2_ITERATIONS.toUInt()),
|
||||
email = "email",
|
||||
password = "mockPassword-1",
|
||||
userKey = "mockKey-1",
|
||||
privateKey = "mockPrivateKey-1",
|
||||
organizationKeys = mapOf(),
|
||||
),
|
||||
)
|
||||
} returns Result.failure(IllegalStateException())
|
||||
|
||||
val result = vaultRepository.unlockVaultAndSync(masterPassword = "mockPassword-1")
|
||||
|
||||
assertEquals(
|
||||
VaultUnlockResult.GenericError,
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `unlockVaultAndSync with initializeCrypto AuthenticationError should return AuthenticationError`() =
|
||||
runTest {
|
||||
coEvery { syncService.sync() } returns Result.success(createMockSyncResponse(number = 1))
|
||||
coEvery {
|
||||
vaultSdkSource.decryptCipherList(listOf(createMockSdkCipher(1)))
|
||||
} returns mockk()
|
||||
coEvery {
|
||||
vaultSdkSource.decryptFolderList(listOf(createMockSdkFolder(1)))
|
||||
} returns mockk()
|
||||
fakeAuthDiskSource.storePrivateKey(
|
||||
userId = "mockUserId",
|
||||
privateKey = "mockPrivateKey-1",
|
||||
)
|
||||
fakeAuthDiskSource.storeUserKey(
|
||||
userId = "mockUserId",
|
||||
userKey = "mockKey-1",
|
||||
)
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
coEvery {
|
||||
vaultSdkSource.initializeCrypto(
|
||||
request = InitCryptoRequest(
|
||||
kdfParams = Kdf.Pbkdf2(iterations = DEFAULT_PBKDF2_ITERATIONS.toUInt()),
|
||||
email = "email",
|
||||
password = "",
|
||||
userKey = "mockKey-1",
|
||||
privateKey = "mockPrivateKey-1",
|
||||
organizationKeys = mapOf(),
|
||||
),
|
||||
)
|
||||
} returns Result.success(InitializeCryptoResult.AuthenticationError)
|
||||
|
||||
val result = vaultRepository.unlockVaultAndSync(masterPassword = "")
|
||||
assertEquals(
|
||||
VaultUnlockResult.AuthenticationError,
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unlockVaultAndSync with missing user state should return InvalidStateError `() =
|
||||
runTest {
|
||||
fakeAuthDiskSource.userState = null
|
||||
|
||||
val result = vaultRepository.unlockVaultAndSync(masterPassword = "")
|
||||
|
||||
assertEquals(
|
||||
VaultUnlockResult.InvalidStateError,
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unlockVaultAndSync with missing user key should return InvalidStateError `() =
|
||||
runTest {
|
||||
val result = vaultRepository.unlockVaultAndSync(masterPassword = "")
|
||||
fakeAuthDiskSource.storeUserKey(
|
||||
userId = "mockUserId",
|
||||
userKey = null,
|
||||
)
|
||||
fakeAuthDiskSource.storePrivateKey(
|
||||
userId = "mockUserId",
|
||||
privateKey = "mockPrivateKey-1",
|
||||
)
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
assertEquals(
|
||||
VaultUnlockResult.InvalidStateError,
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unlockVaultAndSync with missing private key should return InvalidStateError `() =
|
||||
runTest {
|
||||
val result = vaultRepository.unlockVaultAndSync(masterPassword = "")
|
||||
fakeAuthDiskSource.storeUserKey(
|
||||
userId = "mockUserId",
|
||||
userKey = "mockKey-1",
|
||||
)
|
||||
fakeAuthDiskSource.storePrivateKey(
|
||||
userId = "mockUserId",
|
||||
privateKey = null,
|
||||
)
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
assertEquals(
|
||||
VaultUnlockResult.InvalidStateError,
|
||||
result,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val MOCK_USER_STATE = UserStateJson(
|
||||
|
|
Loading…
Add table
Reference in a new issue