mirror of
https://github.com/bitwarden/android.git
synced 2026-06-08 08:06:32 -05:00
BIT-1128: Expose vault data (#227)
This commit is contained in:
committed by
Álison Fernandes
parent
a4c34a8704
commit
a43a541719
@@ -1,6 +1,7 @@
|
||||
package com.x8bit.bitwarden.data.platform.datasource.network.util
|
||||
|
||||
import okio.ByteString.Companion.decodeBase64
|
||||
import java.net.UnknownHostException
|
||||
import java.nio.charset.Charset
|
||||
import java.util.Base64
|
||||
|
||||
@@ -32,3 +33,11 @@ fun String.base64UrlDecodeOrNull(): String? =
|
||||
.replace("_", "/")
|
||||
.decodeBase64()
|
||||
?.string(Charset.defaultCharset())
|
||||
|
||||
/**
|
||||
* Returns true if the throwable represents a no network error.
|
||||
*/
|
||||
fun Throwable?.isNoConnectionError(): Boolean {
|
||||
return this is UnknownHostException ||
|
||||
this?.cause?.isNoConnectionError() ?: false
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.x8bit.bitwarden.data.platform.repository.model
|
||||
|
||||
/**
|
||||
* A data state that can be used as a template for data in the repository layer.
|
||||
*/
|
||||
sealed class DataState<out T> {
|
||||
|
||||
/**
|
||||
* Data that is being wrapped by [DataState].
|
||||
*/
|
||||
abstract val data: T?
|
||||
|
||||
/**
|
||||
* Loading state that has no data is available.
|
||||
*/
|
||||
data object Loading : DataState<Nothing>() {
|
||||
override val data: Nothing? get() = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Loaded state that has data available.
|
||||
*/
|
||||
data class Loaded<T>(
|
||||
override val data: T,
|
||||
) : DataState<T>()
|
||||
|
||||
/**
|
||||
* Pending state that has data available.
|
||||
*/
|
||||
data class Pending<T>(
|
||||
override val data: T,
|
||||
) : DataState<T>()
|
||||
|
||||
/**
|
||||
* Error state that may have data available.
|
||||
*/
|
||||
data class Error<T>(
|
||||
val error: Throwable,
|
||||
override val data: T? = null,
|
||||
) : DataState<T>()
|
||||
|
||||
/**
|
||||
* No network state that may have data is available.
|
||||
*/
|
||||
data class NoNetwork<T>(
|
||||
override val data: T? = null,
|
||||
) : DataState<T>()
|
||||
}
|
||||
@@ -1,12 +1,25 @@
|
||||
package com.x8bit.bitwarden.data.vault.repository
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
/**
|
||||
* Responsible for managing vault data inside the network layer.
|
||||
*/
|
||||
interface VaultRepository {
|
||||
|
||||
/**
|
||||
* Flow that represents the current vault data.
|
||||
*/
|
||||
val vaultDataStateFlow: StateFlow<DataState<VaultData>>
|
||||
|
||||
/**
|
||||
* Clear the in memory vault data.
|
||||
*/
|
||||
fun clearVaultData()
|
||||
|
||||
/**
|
||||
* Attempt to sync the vault data.
|
||||
*/
|
||||
|
||||
@@ -3,15 +3,24 @@ 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.datasource.network.util.isNoConnectionError
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||
import com.x8bit.bitwarden.data.platform.util.flatMap
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||
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.VaultData
|
||||
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.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
@@ -28,8 +37,23 @@ class VaultRepositoryImpl constructor(
|
||||
|
||||
private var syncJob: Job = Job().apply { complete() }
|
||||
|
||||
private val vaultDataMutableStateFlow =
|
||||
MutableStateFlow<DataState<VaultData>>(DataState.Loading)
|
||||
|
||||
override val vaultDataStateFlow: StateFlow<DataState<VaultData>>
|
||||
get() = vaultDataMutableStateFlow.asStateFlow()
|
||||
|
||||
override fun clearVaultData() {
|
||||
vaultDataMutableStateFlow.update { DataState.Loading }
|
||||
}
|
||||
|
||||
override fun sync() {
|
||||
if (!syncJob.isCompleted) return
|
||||
vaultDataMutableStateFlow.value.data?.let { data ->
|
||||
vaultDataMutableStateFlow.update {
|
||||
DataState.Pending(data = data)
|
||||
}
|
||||
}
|
||||
syncJob = scope.launch {
|
||||
syncService
|
||||
.sync()
|
||||
@@ -39,20 +63,23 @@ class VaultRepositoryImpl constructor(
|
||||
userKey = syncResponse.profile?.key,
|
||||
privateKey = syncResponse.profile?.privateKey,
|
||||
)
|
||||
// TODO transform into domain object consumable by VaultViewModel BIT-205.
|
||||
syncResponse.ciphers?.let { networkCiphers ->
|
||||
vaultSdkSource.decryptCipherList(
|
||||
cipherList = networkCiphers.toEncryptedSdkCipherList(),
|
||||
)
|
||||
}
|
||||
syncResponse.folders?.let { networkFolders ->
|
||||
vaultSdkSource.decryptFolderList(
|
||||
folderList = networkFolders.toEncryptedSdkFolderList(),
|
||||
)
|
||||
}
|
||||
decryptSyncResponseAndUpdateVaultDataState(
|
||||
syncResponse = syncResponse,
|
||||
)
|
||||
},
|
||||
onFailure = {
|
||||
// TODO handle failure BIT-205.
|
||||
onFailure = { throwable ->
|
||||
vaultDataMutableStateFlow.update {
|
||||
if (throwable.isNoConnectionError()) {
|
||||
DataState.NoNetwork(
|
||||
data = it.data,
|
||||
)
|
||||
} else {
|
||||
DataState.Error(
|
||||
error = throwable,
|
||||
data = it.data,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -108,4 +135,34 @@ class VaultRepositoryImpl constructor(
|
||||
onSuccess = { it.toVaultUnlockResult() },
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun decryptSyncResponseAndUpdateVaultDataState(syncResponse: SyncResponseJson) {
|
||||
val newState = vaultSdkSource
|
||||
.decryptCipherList(
|
||||
cipherList = (syncResponse.ciphers ?: emptyList())
|
||||
.toEncryptedSdkCipherList(),
|
||||
)
|
||||
.flatMap { decryptedCipherList ->
|
||||
vaultSdkSource
|
||||
.decryptFolderList(
|
||||
folderList = (syncResponse.folders ?: emptyList())
|
||||
.toEncryptedSdkFolderList(),
|
||||
)
|
||||
.map { decryptedFolderList ->
|
||||
decryptedCipherList to decryptedFolderList
|
||||
}
|
||||
}
|
||||
.fold(
|
||||
onSuccess = { (decryptedCipherList, decryptedFolderList) ->
|
||||
DataState.Loaded(
|
||||
data = VaultData(
|
||||
cipherListViewList = decryptedCipherList,
|
||||
folderViewList = decryptedFolderList,
|
||||
),
|
||||
)
|
||||
},
|
||||
onFailure = { DataState.Error(error = it) },
|
||||
)
|
||||
vaultDataMutableStateFlow.update { newState }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.x8bit.bitwarden.data.vault.repository.model
|
||||
|
||||
import com.bitwarden.core.CipherListView
|
||||
import com.bitwarden.core.FolderView
|
||||
|
||||
/**
|
||||
* Represents decrypted vault data.
|
||||
*
|
||||
* @param cipherListViewList List of decrypted ciphers.
|
||||
* @param folderViewList List of decrypted folders.
|
||||
*/
|
||||
data class VaultData(
|
||||
val cipherListViewList: List<CipherListView>,
|
||||
val folderViewList: List<FolderView>,
|
||||
)
|
||||
Reference in New Issue
Block a user