Adding the Repository folder calls. (#813)

This commit is contained in:
Oleg Semenenko
2024-01-28 10:50:14 -06:00
committed by Álison Fernandes
parent fa551fa6ab
commit 3be37766e2
15 changed files with 654 additions and 2 deletions

View File

@@ -39,6 +39,11 @@ interface VaultDiskSource {
*/
fun getDomains(userId: String): Flow<SyncResponseJson.Domains>
/**
* Deletes a folder from the data source for the given [userId] and [folderId].
*/
suspend fun deleteFolder(userId: String, folderId: String)
/**
* Saves a folder to the data source for the given [userId].
*/

View File

@@ -114,6 +114,10 @@ class VaultDiskSourceImpl(
json.decodeFromString<SyncResponseJson.Domains>(entity.domainsJson)
}
override suspend fun deleteFolder(userId: String, folderId: String) {
foldersDao.deleteFolder(userId = userId, folderId = folderId)
}
override suspend fun saveFolder(userId: String, folder: SyncResponseJson.Folder) {
foldersDao.insertFolder(
folder = FolderEntity(

View File

@@ -229,6 +229,18 @@ interface VaultSdkSource {
sendList: List<Send>,
): Result<List<SendView>>
/**
* Encrypts a [FolderView] for the user with the given [userId], returning a [Folder] wrapped
* in a [Result].
*
* This should only be called after a successful call to [initializeCrypto] for the associated
* user.
*/
suspend fun encryptFolder(
userId: String,
folder: FolderView,
): Result<Folder>
/**
* Decrypts a [Folder] for the user with the given [userId], returning a [FolderView] wrapped
* in a [Result].

View File

@@ -240,6 +240,17 @@ class VaultSdkSourceImpl(
}
}
override suspend fun encryptFolder(
userId: String,
folder: FolderView,
): Result<Folder> =
runCatching {
getClient(userId = userId)
.vault()
.folders()
.encrypt(folder)
}
override suspend fun decryptFolder(
userId: String,
folder: Folder,

View File

@@ -13,9 +13,11 @@ import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
import com.x8bit.bitwarden.data.vault.manager.model.VerificationCodeItem
import com.x8bit.bitwarden.data.vault.repository.model.CreateAttachmentResult
import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult
import com.x8bit.bitwarden.data.vault.repository.model.CreateFolderResult
import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult
import com.x8bit.bitwarden.data.vault.repository.model.DeleteAttachmentResult
import com.x8bit.bitwarden.data.vault.repository.model.DeleteCipherResult
import com.x8bit.bitwarden.data.vault.repository.model.DeleteFolderResult
import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult
import com.x8bit.bitwarden.data.vault.repository.model.DomainsData
import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
@@ -25,6 +27,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.SendData
import com.x8bit.bitwarden.data.vault.repository.model.ShareCipherResult
import com.x8bit.bitwarden.data.vault.repository.model.TotpCodeResult
import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult
import com.x8bit.bitwarden.data.vault.repository.model.UpdateFolderResult
import com.x8bit.bitwarden.data.vault.repository.model.UpdateSendResult
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
@@ -283,4 +286,19 @@ interface VaultRepository : VaultLockManager {
attachmentId: String,
cipherView: CipherView,
): DeleteAttachmentResult
/**
* Attempt to create a folder.
*/
suspend fun createFolder(folderView: FolderView): CreateFolderResult
/**
* Attempt to delete a folder.
*/
suspend fun deleteFolder(folderId: String): DeleteFolderResult
/**
* Attempt to update a folder.
*/
suspend fun updateFolder(folderId: String, folderView: FolderView): UpdateFolderResult
}

View File

@@ -31,8 +31,10 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonReq
import com.x8bit.bitwarden.data.vault.datasource.network.model.ShareCipherJsonRequest
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateCipherResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateFolderResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateSendResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.service.CiphersService
import com.x8bit.bitwarden.data.vault.datasource.network.service.FolderService
import com.x8bit.bitwarden.data.vault.datasource.network.service.SendsService
import com.x8bit.bitwarden.data.vault.datasource.network.service.SyncService
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
@@ -42,9 +44,11 @@ import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
import com.x8bit.bitwarden.data.vault.manager.model.VerificationCodeItem
import com.x8bit.bitwarden.data.vault.repository.model.CreateAttachmentResult
import com.x8bit.bitwarden.data.vault.repository.model.CreateCipherResult
import com.x8bit.bitwarden.data.vault.repository.model.CreateFolderResult
import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult
import com.x8bit.bitwarden.data.vault.repository.model.DeleteAttachmentResult
import com.x8bit.bitwarden.data.vault.repository.model.DeleteCipherResult
import com.x8bit.bitwarden.data.vault.repository.model.DeleteFolderResult
import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult
import com.x8bit.bitwarden.data.vault.repository.model.DomainsData
import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
@@ -54,6 +58,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.SendData
import com.x8bit.bitwarden.data.vault.repository.model.ShareCipherResult
import com.x8bit.bitwarden.data.vault.repository.model.TotpCodeResult
import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult
import com.x8bit.bitwarden.data.vault.repository.model.UpdateFolderResult
import com.x8bit.bitwarden.data.vault.repository.model.UpdateSendResult
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
@@ -61,10 +66,12 @@ import com.x8bit.bitwarden.data.vault.repository.util.sortAlphabetically
import com.x8bit.bitwarden.data.vault.repository.util.toDomainsData
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedNetworkCipher
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedNetworkCipherResponse
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedNetworkFolder
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedNetworkSend
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkCipher
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkCipherList
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkCollectionList
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkFolder
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkFolderList
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkSend
import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedSdkSendList
@@ -80,6 +87,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
@@ -107,6 +115,7 @@ class VaultRepositoryImpl(
private val syncService: SyncService,
private val ciphersService: CiphersService,
private val sendsService: SendsService,
private val folderService: FolderService,
private val vaultDiskSource: VaultDiskSource,
private val vaultSdkSource: VaultSdkSource,
private val authDiskSource: AuthDiskSource,
@@ -917,6 +926,95 @@ class VaultRepositoryImpl(
)
}
override suspend fun createFolder(folderView: FolderView): CreateFolderResult {
val userId = activeUserId ?: return CreateFolderResult.Error
return vaultSdkSource
.encryptFolder(
userId = userId,
folder = folderView,
)
.flatMap { folder ->
folderService
.createFolder(
body = folder.toEncryptedNetworkFolder(),
)
}
.onSuccess { vaultDiskSource.saveFolder(userId = userId, folder = it) }
.flatMap { vaultSdkSource.decryptFolder(userId, it.toEncryptedSdkFolder()) }
.fold(
onSuccess = { CreateFolderResult.Success(folderView = it) },
onFailure = { CreateFolderResult.Error },
)
}
override suspend fun updateFolder(
folderId: String,
folderView: FolderView,
): UpdateFolderResult {
val userId = activeUserId ?: return UpdateFolderResult.Error(null)
return vaultSdkSource
.encryptFolder(
userId = userId,
folder = folderView,
)
.flatMap { folder ->
folderService
.updateFolder(
folderId = folder.id.toString(),
body = folder.toEncryptedNetworkFolder(),
)
}
.fold(
onSuccess = { response ->
when (response) {
is UpdateFolderResponseJson.Success -> {
vaultDiskSource.saveFolder(userId, response.folder)
vaultSdkSource
.decryptFolder(
userId,
response.folder.toEncryptedSdkFolder(),
)
.fold(
onSuccess = { UpdateFolderResult.Success(it) },
onFailure = { UpdateFolderResult.Error(errorMessage = null) },
)
}
is UpdateFolderResponseJson.Invalid -> {
UpdateFolderResult.Error(response.message)
}
}
},
onFailure = { UpdateFolderResult.Error(it.message) },
)
}
override suspend fun deleteFolder(folderId: String): DeleteFolderResult {
val userId = activeUserId ?: return DeleteFolderResult.Error
return folderService
.deleteFolder(
folderId = folderId,
)
.onSuccess {
clearFolderIdFromCiphers(folderId, userId)
vaultDiskSource.deleteFolder(userId, folderId)
}
.fold(
onSuccess = { DeleteFolderResult.Success },
onFailure = { DeleteFolderResult.Error },
)
}
private suspend fun clearFolderIdFromCiphers(folderId: String, userId: String) {
vaultDiskSource.getCiphers(userId).firstOrNull()?.forEach {
if (it.folderId == folderId) {
vaultDiskSource.saveCipher(
userId, it.copy(folderId = null),
)
}
}
}
/**
* Checks if the given [userId] has an associated encrypted PIN key but not a pin-protected user
* key. This indicates a scenario in which a user has requested PIN unlocking but requires

View File

@@ -5,6 +5,7 @@ import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
import com.x8bit.bitwarden.data.vault.datasource.network.service.CiphersService
import com.x8bit.bitwarden.data.vault.datasource.network.service.FolderService
import com.x8bit.bitwarden.data.vault.datasource.network.service.SendsService
import com.x8bit.bitwarden.data.vault.datasource.network.service.SyncService
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
@@ -33,6 +34,7 @@ object VaultRepositoryModule {
syncService: SyncService,
sendsService: SendsService,
ciphersService: CiphersService,
folderService: FolderService,
vaultDiskSource: VaultDiskSource,
vaultSdkSource: VaultSdkSource,
authDiskSource: AuthDiskSource,
@@ -46,6 +48,7 @@ object VaultRepositoryModule {
syncService = syncService,
sendsService = sendsService,
ciphersService = ciphersService,
folderService = folderService,
vaultDiskSource = vaultDiskSource,
vaultSdkSource = vaultSdkSource,
authDiskSource = authDiskSource,

View File

@@ -0,0 +1,19 @@
package com.x8bit.bitwarden.data.vault.repository.model
import com.bitwarden.core.FolderView
/**
* Models result of creating a folder.
*/
sealed class CreateFolderResult {
/**
* Folder created successfully.
*/
data class Success(val folderView: FolderView) : CreateFolderResult()
/**
* Generic error while creating a folder.
*/
data object Error : CreateFolderResult()
}

View File

@@ -0,0 +1,17 @@
package com.x8bit.bitwarden.data.vault.repository.model
/**
* Models result of deleting a folder.
*/
sealed class DeleteFolderResult {
/**
* Folder deleted successfully.
*/
data object Success : DeleteFolderResult()
/**
* Generic error while deleting a folder.
*/
data object Error : DeleteFolderResult()
}

View File

@@ -0,0 +1,20 @@
package com.x8bit.bitwarden.data.vault.repository.model
import com.bitwarden.core.FolderView
/**
* Models result of updating a folder.
*/
sealed class UpdateFolderResult {
/**
* Folder updated successfully.
*/
data class Success(val folderView: FolderView) : UpdateFolderResult()
/**
* Generic error while updating a folder. The optional [errorMessage]
* may be displayed directly in the UI when present.
*/
data class Error(val errorMessage: String?) : UpdateFolderResult()
}

View File

@@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.vault.repository.util
import com.bitwarden.core.Folder
import com.bitwarden.core.FolderView
import com.x8bit.bitwarden.data.vault.datasource.network.model.FolderJsonRequest
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
import java.util.Locale
@@ -29,3 +30,10 @@ fun SyncResponseJson.Folder.toEncryptedSdkFolder(): Folder =
@JvmName("toAlphabeticallySortedFolderList")
fun List<FolderView>.sortAlphabetically(): List<FolderView> =
this.sortedBy { it.name.uppercase(Locale.getDefault()) }
/**
* Converts a Bitwarden SDK [Folder] objects to a corresponding
* [SyncResponseJson.Folder] object.
*/
fun Folder.toEncryptedNetworkFolder(): FolderJsonRequest =
FolderJsonRequest(name = name)