mirror of
https://github.com/bitwarden/android.git
synced 2025-03-16 19:28:44 +03:00
BIT-1033: Store user key and private key to disc (#197)
This commit is contained in:
parent
ad44263028
commit
ea5908d2ca
11 changed files with 390 additions and 98 deletions
|
@ -21,4 +21,24 @@ interface AuthDiskSource {
|
|||
* Emits updates that track [userState]. This will replay the last known value, if any.
|
||||
*/
|
||||
val userStateFlow: Flow<UserStateJson?>
|
||||
|
||||
/**
|
||||
* Retrieves a user key using a [userId].
|
||||
*/
|
||||
fun getUserKey(userId: String): String?
|
||||
|
||||
/**
|
||||
* Stores a user key using a [userId].
|
||||
*/
|
||||
fun storeUserKey(userId: String, userKey: String?)
|
||||
|
||||
/**
|
||||
* Retrieves a private key using a [userId].
|
||||
*/
|
||||
fun getPrivateKey(userId: String): String?
|
||||
|
||||
/**
|
||||
* Stores a private key using a [userId].
|
||||
*/
|
||||
fun storePrivateKey(userId: String, privateKey: String?)
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@ import kotlinx.serialization.json.Json
|
|||
|
||||
private const val REMEMBERED_EMAIL_ADDRESS_KEY = "$BASE_KEY:rememberedEmail"
|
||||
private const val STATE_KEY = "$BASE_KEY:state"
|
||||
private const val MASTER_KEY_ENCRYPTION_USER_KEY = "masterKeyEncryptedUserKey"
|
||||
private const val MASTER_KEY_ENCRYPTION_PRIVATE_KEY = "encPrivateKey"
|
||||
|
||||
/**
|
||||
* Primary implementation of [AuthDiskSource].
|
||||
|
@ -56,4 +58,24 @@ class AuthDiskSourceImpl(
|
|||
STATE_KEY -> mutableUserStateFlow.tryEmit(userState)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getUserKey(userId: String): String? =
|
||||
getString(key = "${MASTER_KEY_ENCRYPTION_USER_KEY}_$userId")
|
||||
|
||||
override fun storeUserKey(userId: String, userKey: String?) {
|
||||
putString(
|
||||
key = "${MASTER_KEY_ENCRYPTION_USER_KEY}_$userId",
|
||||
value = userKey,
|
||||
)
|
||||
}
|
||||
|
||||
override fun getPrivateKey(userId: String): String? =
|
||||
getString(key = "${MASTER_KEY_ENCRYPTION_PRIVATE_KEY}_$userId")
|
||||
|
||||
override fun storePrivateKey(userId: String, privateKey: String?) {
|
||||
putString(
|
||||
key = "${MASTER_KEY_ENCRYPTION_PRIVATE_KEY}_$userId",
|
||||
value = privateKey,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -112,6 +112,16 @@ class AuthRepositoryImpl @Inject constructor(
|
|||
.toUserState(
|
||||
previousUserState = authDiskSource.userState,
|
||||
)
|
||||
.also { userState ->
|
||||
authDiskSource.storeUserKey(
|
||||
userId = userState.activeUserId,
|
||||
userKey = it.key,
|
||||
)
|
||||
authDiskSource.storePrivateKey(
|
||||
userId = userState.activeUserId,
|
||||
privateKey = it.privateKey,
|
||||
)
|
||||
}
|
||||
LoginResult.Success
|
||||
}
|
||||
|
||||
|
@ -131,7 +141,8 @@ class AuthRepositoryImpl @Inject constructor(
|
|||
val updatedAccounts = currentUserState
|
||||
.accounts
|
||||
.filterKeys { it != activeUserId }
|
||||
|
||||
authDiskSource.storeUserKey(userId = activeUserId, userKey = null)
|
||||
authDiskSource.storePrivateKey(userId = activeUserId, privateKey = null)
|
||||
// Check if there is a new active user
|
||||
if (updatedAccounts.isNotEmpty()) {
|
||||
val (updatedActiveUserId, updatedActiveAccount) =
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.x8bit.bitwarden.data.vault.repository
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
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
|
||||
|
@ -15,6 +16,7 @@ import kotlinx.coroutines.launch
|
|||
class VaultRepositoryImpl constructor(
|
||||
private val syncService: SyncService,
|
||||
private val vaultSdkSource: VaultSdkSource,
|
||||
private val authDiskSource: AuthDiskSource,
|
||||
dispatcherManager: DispatcherManager,
|
||||
) : VaultRepository {
|
||||
|
||||
|
@ -29,8 +31,11 @@ class VaultRepositoryImpl constructor(
|
|||
.sync()
|
||||
.fold(
|
||||
onSuccess = { syncResponse ->
|
||||
storeUserKeyAndPrivateKey(
|
||||
userKey = syncResponse.profile?.key,
|
||||
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(
|
||||
|
@ -49,4 +54,22 @@ class VaultRepositoryImpl constructor(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun storeUserKeyAndPrivateKey(
|
||||
userKey: String?,
|
||||
privateKey: String?,
|
||||
) {
|
||||
val userId = authDiskSource.userState?.activeUserId ?: return
|
||||
if (userKey == null || privateKey == null) return
|
||||
authDiskSource.apply {
|
||||
storeUserKey(
|
||||
userId = userId,
|
||||
userKey = userKey,
|
||||
)
|
||||
storePrivateKey(
|
||||
userId = userId,
|
||||
privateKey = privateKey,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.x8bit.bitwarden.data.vault.repository.di
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
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
|
||||
|
@ -23,10 +24,12 @@ class VaultRepositoryModule {
|
|||
fun providesVaultRepository(
|
||||
syncService: SyncService,
|
||||
vaultSdkSource: VaultSdkSource,
|
||||
authDiskSource: AuthDiskSource,
|
||||
dispatcherManager: DispatcherManager,
|
||||
): VaultRepository = VaultRepositoryImpl(
|
||||
syncService = syncService,
|
||||
vaultSdkSource = vaultSdkSource,
|
||||
authDiskSource = authDiskSource,
|
||||
dispatcherManager = dispatcherManager,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -87,6 +87,79 @@ class AuthDiskSourceTest {
|
|||
assertEquals(USER_STATE, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getUserKey should pull from SharedPreferences`() {
|
||||
val mockUserId = "mockUserId"
|
||||
val mockUserKey = "mockUserKey"
|
||||
fakeSharedPreferences
|
||||
.edit()
|
||||
.putString(
|
||||
"masterKeyEncryptedUserKey_$mockUserId",
|
||||
mockUserKey,
|
||||
)
|
||||
.apply()
|
||||
val actual = authDiskSource.getUserKey(userId = mockUserId)
|
||||
assertEquals(
|
||||
mockUserKey,
|
||||
actual,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `storeUserKey should update SharedPreferences`() {
|
||||
val mockUserId = "mockUserId"
|
||||
val mockUserKey = "mockUserKey"
|
||||
authDiskSource.storeUserKey(
|
||||
userId = mockUserId,
|
||||
userKey = mockUserKey,
|
||||
)
|
||||
val actual = fakeSharedPreferences
|
||||
.getString(
|
||||
"masterKeyEncryptedUserKey_$mockUserId",
|
||||
null,
|
||||
)
|
||||
assertEquals(
|
||||
mockUserKey,
|
||||
actual,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getPrivateKey should pull from SharedPreferences`() {
|
||||
val mockUserId = "mockUserId"
|
||||
val mockPrivateKey = "mockPrivateKey"
|
||||
fakeSharedPreferences
|
||||
.edit()
|
||||
.putString(
|
||||
"encPrivateKey_$mockUserId",
|
||||
mockPrivateKey,
|
||||
)
|
||||
.apply()
|
||||
val actual = authDiskSource.getPrivateKey(userId = mockUserId)
|
||||
assertEquals(
|
||||
mockPrivateKey,
|
||||
actual,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `storePrivateKey should update SharedPreferences`() {
|
||||
val mockUserId = "mockUserId"
|
||||
val mockPrivateKey = "mockPrivateKey"
|
||||
authDiskSource.storePrivateKey(
|
||||
userId = mockUserId,
|
||||
privateKey = mockPrivateKey,
|
||||
)
|
||||
val actual = fakeSharedPreferences.getString(
|
||||
"encPrivateKey_$mockUserId",
|
||||
null,
|
||||
)
|
||||
assertEquals(
|
||||
mockPrivateKey,
|
||||
actual,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private const val USER_STATE_JSON = """
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.disk.util
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.onSubscription
|
||||
import org.junit.Assert.assertEquals
|
||||
|
||||
class FakeAuthDiskSource : AuthDiskSource {
|
||||
override var rememberedEmailAddress: String? = null
|
||||
|
||||
override var userState: UserStateJson? = null
|
||||
set(value) {
|
||||
field = value
|
||||
mutableUserStateFlow.tryEmit(value)
|
||||
}
|
||||
|
||||
override val userStateFlow: Flow<UserStateJson?>
|
||||
get() = mutableUserStateFlow.onSubscription { emit(userState) }
|
||||
|
||||
override fun getUserKey(userId: String): String? = storedUserKeys[userId]
|
||||
|
||||
override fun storeUserKey(userId: String, userKey: String?) {
|
||||
storedUserKeys[userId] = userKey
|
||||
}
|
||||
|
||||
override fun getPrivateKey(userId: String): String? = storedPrivateKeys[userId]
|
||||
|
||||
override fun storePrivateKey(userId: String, privateKey: String?) {
|
||||
storedPrivateKeys[userId] = privateKey
|
||||
}
|
||||
|
||||
private val mutableUserStateFlow =
|
||||
MutableSharedFlow<UserStateJson?>(
|
||||
replay = 1,
|
||||
extraBufferCapacity = Int.MAX_VALUE,
|
||||
)
|
||||
|
||||
private val storedUserKeys = mutableMapOf<String, String?>()
|
||||
|
||||
private val storedPrivateKeys = mutableMapOf<String, String?>()
|
||||
|
||||
/**
|
||||
* Assert that the [userKey] was stored successfully using the [userId].
|
||||
*/
|
||||
fun assertUserKey(userId: String, userKey: String?) {
|
||||
assertEquals(userKey, storedUserKeys[userId])
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the [privateKey] was stored successfully using the [userId].
|
||||
*/
|
||||
fun assertPrivateKey(userId: String, privateKey: String?) {
|
||||
assertEquals(privateKey, storedPrivateKeys[userId])
|
||||
}
|
||||
}
|
|
@ -4,9 +4,9 @@ import app.cash.turbine.test
|
|||
import com.bitwarden.core.Kdf
|
||||
import com.bitwarden.core.RegisterKeyResponse
|
||||
import com.bitwarden.core.RsaKeyPair
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
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.datasource.network.model.GetTokenResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.KdfTypeJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.KdfTypeJson.PBKDF2_SHA256
|
||||
|
@ -38,9 +38,6 @@ import io.mockk.every
|
|||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.unmockkStatic
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.onSubscription
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
|
@ -186,34 +183,43 @@ class AuthRepositoryTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `login get token succeeds should return Success and update AuthState`() = runTest {
|
||||
val successResponse = GET_TOKEN_RESPONSE_SUCCESS
|
||||
coEvery {
|
||||
accountsService.preLogin(email = EMAIL)
|
||||
} returns Result.success(PRE_LOGIN_SUCCESS)
|
||||
coEvery {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
passwordHash = PASSWORD_HASH,
|
||||
captchaToken = null,
|
||||
fun `login get token succeeds should return Success and update AuthState and stored keys`() =
|
||||
runTest {
|
||||
val successResponse = GET_TOKEN_RESPONSE_SUCCESS
|
||||
coEvery {
|
||||
accountsService.preLogin(email = EMAIL)
|
||||
} returns Result.success(PRE_LOGIN_SUCCESS)
|
||||
coEvery {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
passwordHash = PASSWORD_HASH,
|
||||
captchaToken = null,
|
||||
)
|
||||
}
|
||||
.returns(Result.success(successResponse))
|
||||
every {
|
||||
GET_TOKEN_RESPONSE_SUCCESS.toUserState(previousUserState = null)
|
||||
} returns SINGLE_USER_STATE_1
|
||||
val result = repository.login(email = EMAIL, password = PASSWORD, captchaToken = null)
|
||||
assertEquals(LoginResult.Success, result)
|
||||
assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value)
|
||||
coVerify { accountsService.preLogin(email = EMAIL) }
|
||||
fakeAuthDiskSource.assertPrivateKey(
|
||||
userId = USER_ID_1,
|
||||
privateKey = "privateKey",
|
||||
)
|
||||
}
|
||||
.returns(Result.success(successResponse))
|
||||
every {
|
||||
GET_TOKEN_RESPONSE_SUCCESS.toUserState(previousUserState = null)
|
||||
} returns SINGLE_USER_STATE_1
|
||||
val result = repository.login(email = EMAIL, password = PASSWORD, captchaToken = null)
|
||||
assertEquals(LoginResult.Success, result)
|
||||
assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value)
|
||||
coVerify { accountsService.preLogin(email = EMAIL) }
|
||||
coVerify {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
passwordHash = PASSWORD_HASH,
|
||||
captchaToken = null,
|
||||
fakeAuthDiskSource.assertUserKey(
|
||||
userId = USER_ID_1,
|
||||
userKey = "key",
|
||||
)
|
||||
coVerify {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
passwordHash = PASSWORD_HASH,
|
||||
captchaToken = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `login get token returns captcha request should return CaptchaRequired`() = runTest {
|
||||
|
@ -578,7 +584,7 @@ class AuthRepositoryTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `logout for single account should clear the access token`() = runTest {
|
||||
fun `logout for single account should clear the access toke and stored keys`() = runTest {
|
||||
// First login:
|
||||
val successResponse = GET_TOKEN_RESPONSE_SUCCESS
|
||||
coEvery {
|
||||
|
@ -608,45 +614,62 @@ class AuthRepositoryTest {
|
|||
|
||||
assertEquals(AuthState.Unauthenticated, awaitItem())
|
||||
assertNull(fakeAuthDiskSource.userState)
|
||||
fakeAuthDiskSource.assertPrivateKey(
|
||||
userId = USER_ID_1,
|
||||
privateKey = null,
|
||||
)
|
||||
fakeAuthDiskSource.assertUserKey(
|
||||
userId = USER_ID_1,
|
||||
userKey = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `logout for multiple accounts should update current access token`() = runTest {
|
||||
// First populate multiple user accounts
|
||||
fakeAuthDiskSource.userState = SINGLE_USER_STATE_2
|
||||
fun `logout for multiple accounts should update current access token and stored keys`() =
|
||||
runTest {
|
||||
// First populate multiple user accounts
|
||||
fakeAuthDiskSource.userState = SINGLE_USER_STATE_2
|
||||
|
||||
// Then login:
|
||||
val successResponse = GET_TOKEN_RESPONSE_SUCCESS
|
||||
coEvery {
|
||||
accountsService.preLogin(email = EMAIL)
|
||||
} returns Result.success(PRE_LOGIN_SUCCESS)
|
||||
coEvery {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
passwordHash = PASSWORD_HASH,
|
||||
captchaToken = null,
|
||||
)
|
||||
} returns Result.success(successResponse)
|
||||
every {
|
||||
GET_TOKEN_RESPONSE_SUCCESS.toUserState(previousUserState = SINGLE_USER_STATE_2)
|
||||
} returns MULTI_USER_STATE
|
||||
// Then login:
|
||||
val successResponse = GET_TOKEN_RESPONSE_SUCCESS
|
||||
coEvery {
|
||||
accountsService.preLogin(email = EMAIL)
|
||||
} returns Result.success(PRE_LOGIN_SUCCESS)
|
||||
coEvery {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
passwordHash = PASSWORD_HASH,
|
||||
captchaToken = null,
|
||||
)
|
||||
} returns Result.success(successResponse)
|
||||
every {
|
||||
GET_TOKEN_RESPONSE_SUCCESS.toUserState(previousUserState = SINGLE_USER_STATE_2)
|
||||
} returns MULTI_USER_STATE
|
||||
|
||||
repository.login(email = EMAIL, password = PASSWORD, captchaToken = null)
|
||||
repository.login(email = EMAIL, password = PASSWORD, captchaToken = null)
|
||||
|
||||
assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value)
|
||||
assertEquals(MULTI_USER_STATE, fakeAuthDiskSource.userState)
|
||||
assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value)
|
||||
assertEquals(MULTI_USER_STATE, fakeAuthDiskSource.userState)
|
||||
|
||||
// Then call logout:
|
||||
repository.authStateFlow.test {
|
||||
assertEquals(AuthState.Authenticated(ACCESS_TOKEN), awaitItem())
|
||||
// Then call logout:
|
||||
repository.authStateFlow.test {
|
||||
assertEquals(AuthState.Authenticated(ACCESS_TOKEN), awaitItem())
|
||||
|
||||
repository.logout()
|
||||
repository.logout()
|
||||
|
||||
assertEquals(AuthState.Authenticated(ACCESS_TOKEN_2), awaitItem())
|
||||
assertEquals(SINGLE_USER_STATE_2, fakeAuthDiskSource.userState)
|
||||
assertEquals(AuthState.Authenticated(ACCESS_TOKEN_2), awaitItem())
|
||||
assertEquals(SINGLE_USER_STATE_2, fakeAuthDiskSource.userState)
|
||||
fakeAuthDiskSource.assertPrivateKey(
|
||||
userId = USER_ID_1,
|
||||
privateKey = null,
|
||||
)
|
||||
fakeAuthDiskSource.assertUserKey(
|
||||
userId = USER_ID_1,
|
||||
userKey = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getPasswordStrength should be based on password length`() = runTest {
|
||||
|
@ -774,22 +797,3 @@ class AuthRepositoryTest {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class FakeAuthDiskSource : AuthDiskSource {
|
||||
override var rememberedEmailAddress: String? = null
|
||||
|
||||
override var userState: UserStateJson? = null
|
||||
set(value) {
|
||||
field = value
|
||||
mutableUserStateFlow.tryEmit(value)
|
||||
}
|
||||
|
||||
override val userStateFlow: Flow<UserStateJson?>
|
||||
get() = mutableUserStateFlow.onSubscription { emit(userState) }
|
||||
|
||||
private val mutableUserStateFlow =
|
||||
MutableSharedFlow<UserStateJson?>(
|
||||
replay = 1,
|
||||
extraBufferCapacity = Int.MAX_VALUE,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
package com.x8bit.bitwarden.data.vault.datasource.network.model
|
||||
|
||||
fun createMockSyncResponse(number: Int): SyncResponseJson =
|
||||
SyncResponseJson(
|
||||
folders = listOf(createMockFolder(number = number)),
|
||||
collections = listOf(createMockCollection(number = number)),
|
||||
profile = createMockProfile(number = number),
|
||||
ciphers = listOf(createMockCipher(number = number)),
|
||||
policies = listOf(createMockPolicy(number = number)),
|
||||
domains = createMockDomains(number = number),
|
||||
sends = listOf(createMockSend(number = number)),
|
||||
)
|
|
@ -2,14 +2,7 @@ package com.x8bit.bitwarden.data.vault.datasource.network.service
|
|||
|
||||
import com.x8bit.bitwarden.data.platform.base.BaseServiceTest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.api.SyncApi
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockCipher
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockCollection
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockDomains
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockFolder
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockPolicy
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockProfile
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockSend
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockSyncResponse
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import org.junit.Test
|
||||
|
@ -27,7 +20,7 @@ class SyncServiceTest : BaseServiceTest() {
|
|||
fun `sync should return the correct response`() = runTest {
|
||||
server.enqueue(MockResponse().setBody(SYNC_SUCCESS_JSON))
|
||||
val result = syncService.sync()
|
||||
assertEquals(SYNC_SUCCESS, result.getOrThrow())
|
||||
assertEquals(createMockSyncResponse(number = 1), result.getOrThrow())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -363,13 +356,3 @@ private const val SYNC_SUCCESS_JSON = """
|
|||
]
|
||||
}
|
||||
"""
|
||||
|
||||
private val SYNC_SUCCESS = SyncResponseJson(
|
||||
folders = listOf(createMockFolder(number = 1)),
|
||||
collections = listOf(createMockCollection(number = 1)),
|
||||
profile = createMockProfile(number = 1),
|
||||
ciphers = listOf(createMockCipher(number = 1)),
|
||||
policies = listOf(createMockPolicy(number = 1)),
|
||||
domains = createMockDomains(number = 1),
|
||||
sends = listOf(createMockSend(number = 1)),
|
||||
)
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
package com.x8bit.bitwarden.data.vault.repository
|
||||
|
||||
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.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.VaultSdkSource
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.createMockSdkCipher
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.createMockSdkFolder
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class VaultRepositoryTest {
|
||||
|
||||
private val dispatcherManager: DispatcherManager = FakeDispatcherManager()
|
||||
private val fakeAuthDiskSource = FakeAuthDiskSource()
|
||||
private val syncService: SyncService = mockk()
|
||||
private val vaultSdkSource: VaultSdkSource = mockk()
|
||||
private val vaultRepository = VaultRepositoryImpl(
|
||||
syncService = syncService,
|
||||
vaultSdkSource = vaultSdkSource,
|
||||
authDiskSource = fakeAuthDiskSource,
|
||||
dispatcherManager = dispatcherManager,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `sync when syncService Success should update AuthDiskSource with keys`() = 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.userState = MOCK_USER_STATE
|
||||
|
||||
vaultRepository.sync()
|
||||
|
||||
fakeAuthDiskSource.assertUserKey(
|
||||
userId = "mockUserId",
|
||||
userKey = "mockKey-1",
|
||||
)
|
||||
fakeAuthDiskSource.assertPrivateKey(
|
||||
userId = "mockUserId",
|
||||
privateKey = "mockPrivateKey-1",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val MOCK_USER_STATE = UserStateJson(
|
||||
activeUserId = "mockUserId",
|
||||
accounts = mapOf(
|
||||
"mockUserId" to AccountJson(
|
||||
profile = AccountJson.Profile(
|
||||
userId = "activeUserId",
|
||||
email = "email",
|
||||
isEmailVerified = true,
|
||||
name = null,
|
||||
stamp = null,
|
||||
organizationId = null,
|
||||
avatarColorHex = null,
|
||||
hasPremium = true,
|
||||
forcePasswordResetReason = null,
|
||||
kdfType = null,
|
||||
kdfIterations = null,
|
||||
kdfMemory = null,
|
||||
kdfParallelism = null,
|
||||
userDecryptionOptions = null,
|
||||
),
|
||||
tokens = AccountJson.Tokens(
|
||||
accessToken = "accessToken",
|
||||
refreshToken = "refreshToken",
|
||||
),
|
||||
settings = AccountJson.Settings(
|
||||
environmentUrlData = null,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
Loading…
Add table
Reference in a new issue