mirror of
https://github.com/bitwarden/android.git
synced 2025-03-16 03:08:50 +03:00
Create manager class to isolate logic for ciphers (#1432)
This commit is contained in:
parent
913b53bcac
commit
6392918f52
8 changed files with 2392 additions and 2281 deletions
|
@ -0,0 +1,112 @@
|
|||
package com.x8bit.bitwarden.data.vault.manager
|
||||
|
||||
import android.net.Uri
|
||||
import com.bitwarden.core.CipherView
|
||||
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.DeleteAttachmentResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DownloadAttachmentResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.RestoreCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.ShareCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult
|
||||
|
||||
/**
|
||||
* Manages the creating, updating, and deleting ciphers and their attachments.
|
||||
*/
|
||||
@Suppress("TooManyFunctions")
|
||||
interface CipherManager {
|
||||
/**
|
||||
* Attempt to create a cipher.
|
||||
*/
|
||||
suspend fun createCipher(
|
||||
cipherView: CipherView,
|
||||
): CreateCipherResult
|
||||
|
||||
/**
|
||||
* Attempt to create a cipher that belongs to an organization.
|
||||
*/
|
||||
suspend fun createCipherInOrganization(
|
||||
cipherView: CipherView,
|
||||
collectionIds: List<String>,
|
||||
): CreateCipherResult
|
||||
|
||||
/**
|
||||
* Attempt to create an attachment for the given [cipherView].
|
||||
*/
|
||||
suspend fun createAttachment(
|
||||
cipherId: String,
|
||||
cipherView: CipherView,
|
||||
fileSizeBytes: String,
|
||||
fileName: String,
|
||||
fileUri: Uri,
|
||||
): CreateAttachmentResult
|
||||
|
||||
/**
|
||||
* Attempt to download an attachment file, specified by [attachmentId], for the given
|
||||
* [cipherView].
|
||||
*/
|
||||
suspend fun downloadAttachment(
|
||||
cipherView: CipherView,
|
||||
attachmentId: String,
|
||||
): DownloadAttachmentResult
|
||||
|
||||
/**
|
||||
* Attempt to delete a cipher.
|
||||
*/
|
||||
suspend fun hardDeleteCipher(
|
||||
cipherId: String,
|
||||
): DeleteCipherResult
|
||||
|
||||
/**
|
||||
* Attempt to soft delete a cipher.
|
||||
*/
|
||||
suspend fun softDeleteCipher(
|
||||
cipherId: String,
|
||||
cipherView: CipherView,
|
||||
): DeleteCipherResult
|
||||
|
||||
/**
|
||||
* Attempt to delete an attachment from a send.
|
||||
*/
|
||||
suspend fun deleteCipherAttachment(
|
||||
cipherId: String,
|
||||
attachmentId: String,
|
||||
cipherView: CipherView,
|
||||
): DeleteAttachmentResult
|
||||
|
||||
/**
|
||||
* Attempt to restore a cipher.
|
||||
*/
|
||||
suspend fun restoreCipher(
|
||||
cipherId: String,
|
||||
cipherView: CipherView,
|
||||
): RestoreCipherResult
|
||||
|
||||
/**
|
||||
* Attempt to share a cipher to the collections with the given collectionIds.
|
||||
*/
|
||||
suspend fun shareCipher(
|
||||
cipherId: String,
|
||||
organizationId: String,
|
||||
cipherView: CipherView,
|
||||
collectionIds: List<String>,
|
||||
): ShareCipherResult
|
||||
|
||||
/**
|
||||
* Attempt to update a cipher.
|
||||
*/
|
||||
suspend fun updateCipher(
|
||||
cipherId: String,
|
||||
cipherView: CipherView,
|
||||
): UpdateCipherResult
|
||||
|
||||
/**
|
||||
* Attempt to update a cipher with the given collectionIds.
|
||||
*/
|
||||
suspend fun updateCipherCollections(
|
||||
cipherId: String,
|
||||
cipherView: CipherView,
|
||||
collectionIds: List<String>,
|
||||
): ShareCipherResult
|
||||
}
|
|
@ -0,0 +1,419 @@
|
|||
package com.x8bit.bitwarden.data.vault.manager
|
||||
|
||||
import android.net.Uri
|
||||
import com.bitwarden.core.AttachmentView
|
||||
import com.bitwarden.core.CipherView
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.util.flatMap
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateCipherInOrganizationJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.ShareCipherJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateCipherCollectionsJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.UpdateCipherResponseJson
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.service.CiphersService
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
||||
import com.x8bit.bitwarden.data.vault.manager.model.DownloadResult
|
||||
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.DeleteAttachmentResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DownloadAttachmentResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.RestoreCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.ShareCipherResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult
|
||||
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.toEncryptedSdkCipher
|
||||
import java.io.File
|
||||
import java.time.Clock
|
||||
|
||||
/**
|
||||
* The default implementation of the [CipherManager].
|
||||
*/
|
||||
@Suppress("TooManyFunctions")
|
||||
class CipherManagerImpl(
|
||||
private val fileManager: FileManager,
|
||||
private val authDiskSource: AuthDiskSource,
|
||||
private val ciphersService: CiphersService,
|
||||
private val vaultDiskSource: VaultDiskSource,
|
||||
private val vaultSdkSource: VaultSdkSource,
|
||||
private val clock: Clock,
|
||||
) : CipherManager {
|
||||
private val activeUserId: String? get() = authDiskSource.userState?.activeUserId
|
||||
|
||||
override suspend fun createCipher(cipherView: CipherView): CreateCipherResult {
|
||||
val userId = activeUserId ?: return CreateCipherResult.Error
|
||||
return vaultSdkSource
|
||||
.encryptCipher(
|
||||
userId = userId,
|
||||
cipherView = cipherView,
|
||||
)
|
||||
.flatMap { ciphersService.createCipher(body = it.toEncryptedNetworkCipher()) }
|
||||
.onSuccess { vaultDiskSource.saveCipher(userId = userId, cipher = it) }
|
||||
.fold(
|
||||
onFailure = { CreateCipherResult.Error },
|
||||
onSuccess = { CreateCipherResult.Success },
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun createCipherInOrganization(
|
||||
cipherView: CipherView,
|
||||
collectionIds: List<String>,
|
||||
): CreateCipherResult {
|
||||
val userId = activeUserId ?: return CreateCipherResult.Error
|
||||
return vaultSdkSource
|
||||
.encryptCipher(
|
||||
userId = userId,
|
||||
cipherView = cipherView,
|
||||
)
|
||||
.flatMap { cipher ->
|
||||
ciphersService.createCipherInOrganization(
|
||||
body = CreateCipherInOrganizationJsonRequest(
|
||||
cipher = cipher.toEncryptedNetworkCipher(),
|
||||
collectionIds = collectionIds,
|
||||
),
|
||||
)
|
||||
}
|
||||
.onSuccess {
|
||||
vaultDiskSource.saveCipher(
|
||||
userId = userId,
|
||||
cipher = it.copy(collectionIds = collectionIds),
|
||||
)
|
||||
}
|
||||
.fold(
|
||||
onFailure = { CreateCipherResult.Error },
|
||||
onSuccess = { CreateCipherResult.Success },
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun hardDeleteCipher(cipherId: String): DeleteCipherResult {
|
||||
val userId = activeUserId ?: return DeleteCipherResult.Error
|
||||
return ciphersService
|
||||
.hardDeleteCipher(cipherId = cipherId)
|
||||
.onSuccess { vaultDiskSource.deleteCipher(userId = userId, cipherId = cipherId) }
|
||||
.fold(
|
||||
onSuccess = { DeleteCipherResult.Success },
|
||||
onFailure = { DeleteCipherResult.Error },
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun softDeleteCipher(
|
||||
cipherId: String,
|
||||
cipherView: CipherView,
|
||||
): DeleteCipherResult {
|
||||
val userId = activeUserId ?: return DeleteCipherResult.Error
|
||||
return ciphersService
|
||||
.softDeleteCipher(cipherId = cipherId)
|
||||
.onSuccess {
|
||||
vaultSdkSource
|
||||
.encryptCipher(
|
||||
userId = userId,
|
||||
cipherView = cipherView.copy(deletedDate = clock.instant()),
|
||||
)
|
||||
.onSuccess { cipher ->
|
||||
vaultDiskSource.saveCipher(
|
||||
userId = userId,
|
||||
cipher = cipher.toEncryptedNetworkCipherResponse(),
|
||||
)
|
||||
}
|
||||
}
|
||||
.fold(
|
||||
onSuccess = { DeleteCipherResult.Success },
|
||||
onFailure = { DeleteCipherResult.Error },
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun deleteCipherAttachment(
|
||||
cipherId: String,
|
||||
attachmentId: String,
|
||||
cipherView: CipherView,
|
||||
): DeleteAttachmentResult {
|
||||
val userId = activeUserId ?: return DeleteAttachmentResult.Error
|
||||
return ciphersService
|
||||
.deleteCipherAttachment(
|
||||
cipherId = cipherId,
|
||||
attachmentId = attachmentId,
|
||||
)
|
||||
.flatMap {
|
||||
vaultSdkSource.encryptCipher(
|
||||
userId = userId,
|
||||
cipherView = cipherView.copy(
|
||||
attachments = cipherView.attachments?.mapNotNull {
|
||||
if (it.id == attachmentId) null else it
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
.onSuccess { cipher ->
|
||||
vaultDiskSource.saveCipher(
|
||||
userId = userId,
|
||||
cipher = cipher.toEncryptedNetworkCipherResponse(),
|
||||
)
|
||||
}
|
||||
.fold(
|
||||
onSuccess = { DeleteAttachmentResult.Success },
|
||||
onFailure = { DeleteAttachmentResult.Error },
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun restoreCipher(
|
||||
cipherId: String,
|
||||
cipherView: CipherView,
|
||||
): RestoreCipherResult {
|
||||
val userId = activeUserId ?: return RestoreCipherResult.Error
|
||||
return ciphersService
|
||||
.restoreCipher(cipherId = cipherId)
|
||||
.flatMap {
|
||||
vaultSdkSource.encryptCipher(
|
||||
userId = userId,
|
||||
cipherView = cipherView.copy(deletedDate = null),
|
||||
)
|
||||
}
|
||||
.onSuccess { cipher ->
|
||||
vaultDiskSource.saveCipher(
|
||||
userId = userId,
|
||||
cipher = cipher.toEncryptedNetworkCipherResponse(),
|
||||
)
|
||||
}
|
||||
.fold(
|
||||
onSuccess = { RestoreCipherResult.Success },
|
||||
onFailure = { RestoreCipherResult.Error },
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun updateCipher(
|
||||
cipherId: String,
|
||||
cipherView: CipherView,
|
||||
): UpdateCipherResult {
|
||||
val userId = activeUserId ?: return UpdateCipherResult.Error(errorMessage = null)
|
||||
return vaultSdkSource
|
||||
.encryptCipher(
|
||||
userId = userId,
|
||||
cipherView = cipherView,
|
||||
)
|
||||
.flatMap { cipher ->
|
||||
ciphersService.updateCipher(
|
||||
cipherId = cipherId,
|
||||
body = cipher.toEncryptedNetworkCipher(),
|
||||
)
|
||||
}
|
||||
.map { response ->
|
||||
when (response) {
|
||||
is UpdateCipherResponseJson.Invalid -> {
|
||||
UpdateCipherResult.Error(errorMessage = response.message)
|
||||
}
|
||||
|
||||
is UpdateCipherResponseJson.Success -> {
|
||||
vaultDiskSource.saveCipher(
|
||||
userId = userId,
|
||||
cipher = response.cipher.copy(collectionIds = cipherView.collectionIds),
|
||||
)
|
||||
UpdateCipherResult.Success
|
||||
}
|
||||
}
|
||||
}
|
||||
.fold(
|
||||
onFailure = { UpdateCipherResult.Error(errorMessage = null) },
|
||||
onSuccess = { it },
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun shareCipher(
|
||||
cipherId: String,
|
||||
organizationId: String,
|
||||
cipherView: CipherView,
|
||||
collectionIds: List<String>,
|
||||
): ShareCipherResult {
|
||||
val userId = activeUserId ?: return ShareCipherResult.Error
|
||||
return vaultSdkSource
|
||||
.moveToOrganization(
|
||||
userId = userId,
|
||||
organizationId = organizationId,
|
||||
cipherView = cipherView,
|
||||
)
|
||||
.flatMap { vaultSdkSource.encryptCipher(userId = userId, cipherView = it) }
|
||||
.flatMap { cipher ->
|
||||
ciphersService.shareCipher(
|
||||
cipherId = cipherId,
|
||||
body = ShareCipherJsonRequest(
|
||||
cipher = cipher.toEncryptedNetworkCipher(),
|
||||
collectionIds = collectionIds,
|
||||
),
|
||||
)
|
||||
}
|
||||
.onSuccess {
|
||||
vaultDiskSource.saveCipher(
|
||||
userId = userId,
|
||||
cipher = it.copy(collectionIds = collectionIds),
|
||||
)
|
||||
}
|
||||
.fold(
|
||||
onFailure = { ShareCipherResult.Error },
|
||||
onSuccess = { ShareCipherResult.Success },
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun updateCipherCollections(
|
||||
cipherId: String,
|
||||
cipherView: CipherView,
|
||||
collectionIds: List<String>,
|
||||
): ShareCipherResult {
|
||||
val userId = activeUserId ?: return ShareCipherResult.Error
|
||||
return ciphersService
|
||||
.updateCipherCollections(
|
||||
cipherId = cipherId,
|
||||
body = UpdateCipherCollectionsJsonRequest(collectionIds = collectionIds),
|
||||
)
|
||||
.flatMap {
|
||||
vaultSdkSource.encryptCipher(
|
||||
userId = userId,
|
||||
cipherView = cipherView.copy(collectionIds = collectionIds),
|
||||
)
|
||||
}
|
||||
.onSuccess { cipher ->
|
||||
vaultDiskSource.saveCipher(
|
||||
userId = userId,
|
||||
cipher = cipher.toEncryptedNetworkCipherResponse(),
|
||||
)
|
||||
}
|
||||
.fold(
|
||||
onSuccess = { ShareCipherResult.Success },
|
||||
onFailure = { ShareCipherResult.Error },
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
override suspend fun createAttachment(
|
||||
cipherId: String,
|
||||
cipherView: CipherView,
|
||||
fileSizeBytes: String,
|
||||
fileName: String,
|
||||
fileUri: Uri,
|
||||
): CreateAttachmentResult {
|
||||
val userId = activeUserId ?: return CreateAttachmentResult.Error
|
||||
val attachmentView = AttachmentView(
|
||||
id = null,
|
||||
url = null,
|
||||
size = fileSizeBytes,
|
||||
sizeName = null,
|
||||
fileName = fileName,
|
||||
key = null,
|
||||
)
|
||||
return vaultSdkSource
|
||||
.encryptCipher(
|
||||
userId = userId,
|
||||
cipherView = cipherView,
|
||||
)
|
||||
.flatMap { cipher ->
|
||||
fileManager
|
||||
.writeUriToCache(fileUri = fileUri)
|
||||
.flatMap { cacheFile ->
|
||||
vaultSdkSource
|
||||
.encryptAttachment(
|
||||
userId = userId,
|
||||
cipher = cipher,
|
||||
attachmentView = attachmentView,
|
||||
decryptedFilePath = cacheFile.absolutePath,
|
||||
encryptedFilePath = "${cacheFile.absolutePath}.enc",
|
||||
)
|
||||
.flatMap { attachment ->
|
||||
ciphersService
|
||||
.createAttachment(
|
||||
cipherId = cipherId,
|
||||
body = AttachmentJsonRequest(
|
||||
// We know these values are present because
|
||||
// - the filename/size are passed into the function
|
||||
// - the SDK call fills in the key
|
||||
fileName = requireNotNull(attachment.fileName),
|
||||
key = requireNotNull(attachment.key),
|
||||
fileSize = requireNotNull(attachment.size),
|
||||
),
|
||||
)
|
||||
.flatMap { attachmentJsonResponse ->
|
||||
val encryptedFile = File("${cacheFile.absolutePath}.enc")
|
||||
ciphersService
|
||||
.uploadAttachment(
|
||||
attachmentJsonResponse = attachmentJsonResponse,
|
||||
encryptedFile = encryptedFile,
|
||||
)
|
||||
.onSuccess {
|
||||
fileManager.delete(cacheFile, encryptedFile)
|
||||
}
|
||||
.onFailure {
|
||||
fileManager.delete(cacheFile, encryptedFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.map { it.copy(collectionIds = cipherView.collectionIds) }
|
||||
.onSuccess {
|
||||
// Save the send immediately, regardless of whether the decrypt succeeds
|
||||
vaultDiskSource.saveCipher(userId = userId, cipher = it)
|
||||
}
|
||||
.flatMap {
|
||||
vaultSdkSource.decryptCipher(
|
||||
userId = userId,
|
||||
cipher = it.toEncryptedSdkCipher(),
|
||||
)
|
||||
}
|
||||
.fold(
|
||||
onFailure = { CreateAttachmentResult.Error },
|
||||
onSuccess = { CreateAttachmentResult.Success(cipherView = it) },
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("ReturnCount")
|
||||
override suspend fun downloadAttachment(
|
||||
cipherView: CipherView,
|
||||
attachmentId: String,
|
||||
): DownloadAttachmentResult {
|
||||
val userId = activeUserId ?: return DownloadAttachmentResult.Failure
|
||||
|
||||
val cipher = vaultSdkSource
|
||||
.encryptCipher(
|
||||
userId = userId,
|
||||
cipherView = cipherView,
|
||||
)
|
||||
.fold(
|
||||
onSuccess = { it },
|
||||
onFailure = { return DownloadAttachmentResult.Failure },
|
||||
)
|
||||
val attachment = cipher.attachments?.find { it.id == attachmentId }
|
||||
?: return DownloadAttachmentResult.Failure
|
||||
|
||||
val attachmentData = ciphersService
|
||||
.getCipherAttachment(
|
||||
cipherId = requireNotNull(cipher.id),
|
||||
attachmentId = attachmentId,
|
||||
)
|
||||
.fold(
|
||||
onSuccess = { it },
|
||||
onFailure = { return DownloadAttachmentResult.Failure },
|
||||
)
|
||||
|
||||
val url = attachmentData.url ?: return DownloadAttachmentResult.Failure
|
||||
|
||||
val encryptedFile = when (val result = fileManager.downloadFileToCache(url)) {
|
||||
DownloadResult.Failure -> return DownloadAttachmentResult.Failure
|
||||
is DownloadResult.Success -> result.file
|
||||
}
|
||||
|
||||
val decryptedFile = File(encryptedFile.path + "_decrypted")
|
||||
return vaultSdkSource
|
||||
.decryptFile(
|
||||
userId = userId,
|
||||
cipher = cipher,
|
||||
attachment = attachment,
|
||||
encryptedFilePath = encryptedFile.path,
|
||||
decryptedFilePath = decryptedFile.path,
|
||||
)
|
||||
.onSuccess { fileManager.delete(encryptedFile) }
|
||||
.onFailure { fileManager.delete(encryptedFile) }
|
||||
.fold(
|
||||
onSuccess = { DownloadAttachmentResult.Success(file = decryptedFile) },
|
||||
onFailure = { DownloadAttachmentResult.Failure },
|
||||
)
|
||||
}
|
||||
}
|
|
@ -8,8 +8,12 @@ import com.x8bit.bitwarden.data.auth.manager.UserLogoutManager
|
|||
import com.x8bit.bitwarden.data.platform.manager.AppForegroundManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
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.DownloadService
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
||||
import com.x8bit.bitwarden.data.vault.manager.CipherManager
|
||||
import com.x8bit.bitwarden.data.vault.manager.CipherManagerImpl
|
||||
import com.x8bit.bitwarden.data.vault.manager.FileManager
|
||||
import com.x8bit.bitwarden.data.vault.manager.FileManagerImpl
|
||||
import com.x8bit.bitwarden.data.vault.manager.TotpCodeManager
|
||||
|
@ -31,6 +35,24 @@ import javax.inject.Singleton
|
|||
@InstallIn(SingletonComponent::class)
|
||||
object VaultManagerModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCipherManager(
|
||||
ciphersService: CiphersService,
|
||||
vaultDiskSource: VaultDiskSource,
|
||||
vaultSdkSource: VaultSdkSource,
|
||||
authDiskSource: AuthDiskSource,
|
||||
fileManager: FileManager,
|
||||
clock: Clock,
|
||||
): CipherManager = CipherManagerImpl(
|
||||
fileManager = fileManager,
|
||||
authDiskSource = authDiskSource,
|
||||
ciphersService = ciphersService,
|
||||
vaultDiskSource = vaultDiskSource,
|
||||
vaultSdkSource = vaultSdkSource,
|
||||
clock = clock,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFileManager(
|
||||
|
|
|
@ -10,26 +10,19 @@ import com.bitwarden.core.SendType
|
|||
import com.bitwarden.core.SendView
|
||||
import com.bitwarden.crypto.Kdf
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||
import com.x8bit.bitwarden.data.vault.manager.CipherManager
|
||||
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.DownloadAttachmentResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.ExportVaultDataResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.RestoreCipherResult
|
||||
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
|
||||
|
@ -42,7 +35,7 @@ import kotlinx.coroutines.flow.StateFlow
|
|||
* Responsible for managing vault data inside the network layer.
|
||||
*/
|
||||
@Suppress("TooManyFunctions")
|
||||
interface VaultRepository : VaultLockManager {
|
||||
interface VaultRepository : CipherManager, VaultLockManager {
|
||||
|
||||
/**
|
||||
* The [VaultFilterType] for the current user.
|
||||
|
@ -193,87 +186,6 @@ interface VaultRepository : VaultLockManager {
|
|||
organizationKeys: Map<String, String>?,
|
||||
): VaultUnlockResult
|
||||
|
||||
/**
|
||||
* Attempt to create a cipher.
|
||||
*/
|
||||
suspend fun createCipher(cipherView: CipherView): CreateCipherResult
|
||||
|
||||
/**
|
||||
* Attempt to create a cipher that belongs to an organization.
|
||||
*/
|
||||
suspend fun createCipherInOrganization(
|
||||
cipherView: CipherView,
|
||||
collectionIds: List<String>,
|
||||
): CreateCipherResult
|
||||
|
||||
/**
|
||||
* Attempt to create an attachment for the given [cipherView].
|
||||
*/
|
||||
suspend fun createAttachment(
|
||||
cipherId: String,
|
||||
cipherView: CipherView,
|
||||
fileSizeBytes: String,
|
||||
fileName: String,
|
||||
fileUri: Uri,
|
||||
): CreateAttachmentResult
|
||||
|
||||
/**
|
||||
* Attempt to download an attachment file, specified by [attachmentId], for the given
|
||||
* [cipherView].
|
||||
*/
|
||||
suspend fun downloadAttachment(
|
||||
cipherView: CipherView,
|
||||
attachmentId: String,
|
||||
): DownloadAttachmentResult
|
||||
|
||||
/**
|
||||
* Attempt to delete a cipher.
|
||||
*/
|
||||
suspend fun hardDeleteCipher(cipherId: String): DeleteCipherResult
|
||||
|
||||
/**
|
||||
* Attempt to soft delete a cipher.
|
||||
*/
|
||||
suspend fun softDeleteCipher(
|
||||
cipherId: String,
|
||||
cipherView: CipherView,
|
||||
): DeleteCipherResult
|
||||
|
||||
/**
|
||||
* Attempt to restore a cipher.
|
||||
*/
|
||||
suspend fun restoreCipher(
|
||||
cipherId: String,
|
||||
cipherView: CipherView,
|
||||
): RestoreCipherResult
|
||||
|
||||
/**
|
||||
* Attempt to update a cipher.
|
||||
*/
|
||||
suspend fun updateCipher(
|
||||
cipherId: String,
|
||||
cipherView: CipherView,
|
||||
): UpdateCipherResult
|
||||
|
||||
/**
|
||||
* Attempt to share a cipher to the collections with the given collectionIds.
|
||||
*/
|
||||
suspend fun shareCipher(
|
||||
cipherId: String,
|
||||
organizationId: String,
|
||||
cipherView: CipherView,
|
||||
collectionIds: List<String>,
|
||||
): ShareCipherResult
|
||||
|
||||
/**
|
||||
* Attempt to update a cipher with the given collectionIds.
|
||||
*/
|
||||
suspend fun updateCipherCollections(
|
||||
cipherId: String,
|
||||
cipherView: CipherView,
|
||||
collectionIds: List<String>,
|
||||
): ShareCipherResult
|
||||
|
||||
/**
|
||||
* Attempt to create a send. The [fileUri] _must_ be present when the given [SendView] has a
|
||||
* [SendView.type] of [SendType.FILE].
|
||||
|
@ -303,15 +215,6 @@ interface VaultRepository : VaultLockManager {
|
|||
*/
|
||||
suspend fun deleteSend(sendId: String): DeleteSendResult
|
||||
|
||||
/**
|
||||
* Attempt to delete an attachment from a send.
|
||||
*/
|
||||
suspend fun deleteCipherAttachment(
|
||||
cipherId: String,
|
||||
attachmentId: String,
|
||||
cipherView: CipherView,
|
||||
): DeleteAttachmentResult
|
||||
|
||||
/**
|
||||
* Attempt to create a folder.
|
||||
*/
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package com.x8bit.bitwarden.data.vault.repository
|
||||
|
||||
import android.net.Uri
|
||||
import com.bitwarden.core.AttachmentView
|
||||
import com.bitwarden.core.CipherType
|
||||
import com.bitwarden.core.CipherView
|
||||
import com.bitwarden.core.CollectionView
|
||||
|
@ -40,14 +39,9 @@ import com.x8bit.bitwarden.data.platform.util.asFailure
|
|||
import com.x8bit.bitwarden.data.platform.util.asSuccess
|
||||
import com.x8bit.bitwarden.data.platform.util.flatMap
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateCipherInOrganizationJsonRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateFileSendResponse
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateSendJsonResponse
|
||||
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.UpdateCipherCollectionsJsonRequest
|
||||
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
|
||||
|
@ -55,37 +49,27 @@ 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
|
||||
import com.x8bit.bitwarden.data.vault.manager.CipherManager
|
||||
import com.x8bit.bitwarden.data.vault.manager.FileManager
|
||||
import com.x8bit.bitwarden.data.vault.manager.TotpCodeManager
|
||||
import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
|
||||
import com.x8bit.bitwarden.data.vault.manager.model.DownloadResult
|
||||
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.DownloadAttachmentResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.ExportVaultDataResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.RemovePasswordSendResult
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.RestoreCipherResult
|
||||
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
|
||||
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
|
||||
|
@ -122,9 +106,7 @@ import kotlinx.coroutines.flow.stateIn
|
|||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import retrofit2.HttpException
|
||||
import java.io.File
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
/**
|
||||
|
@ -146,6 +128,7 @@ class VaultRepositoryImpl(
|
|||
private val vaultSdkSource: VaultSdkSource,
|
||||
private val authDiskSource: AuthDiskSource,
|
||||
private val settingsDiskSource: SettingsDiskSource,
|
||||
private val cipherManager: CipherManager,
|
||||
private val fileManager: FileManager,
|
||||
private val vaultLockManager: VaultLockManager,
|
||||
private val totpCodeManager: TotpCodeManager,
|
||||
|
@ -154,6 +137,7 @@ class VaultRepositoryImpl(
|
|||
private val clock: Clock,
|
||||
dispatcherManager: DispatcherManager,
|
||||
) : VaultRepository,
|
||||
CipherManager by cipherManager,
|
||||
VaultLockManager by vaultLockManager {
|
||||
|
||||
private val unconfinedScope = CoroutineScope(dispatcherManager.unconfined)
|
||||
|
@ -617,409 +601,6 @@ class VaultRepositoryImpl(
|
|||
organizationKeys = organizationKeys,
|
||||
)
|
||||
|
||||
override suspend fun createCipher(cipherView: CipherView): CreateCipherResult {
|
||||
val userId = activeUserId ?: return CreateCipherResult.Error
|
||||
return vaultSdkSource
|
||||
.encryptCipher(
|
||||
userId = userId,
|
||||
cipherView = cipherView,
|
||||
)
|
||||
.flatMap { cipher ->
|
||||
ciphersService
|
||||
.createCipher(
|
||||
body = cipher.toEncryptedNetworkCipher(),
|
||||
)
|
||||
}
|
||||
.fold(
|
||||
onFailure = {
|
||||
CreateCipherResult.Error
|
||||
},
|
||||
onSuccess = {
|
||||
vaultDiskSource.saveCipher(userId = userId, cipher = it)
|
||||
CreateCipherResult.Success
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun createCipherInOrganization(
|
||||
cipherView: CipherView,
|
||||
collectionIds: List<String>,
|
||||
): CreateCipherResult {
|
||||
val userId = activeUserId ?: return CreateCipherResult.Error
|
||||
return vaultSdkSource
|
||||
.encryptCipher(
|
||||
userId = userId,
|
||||
cipherView = cipherView,
|
||||
)
|
||||
.flatMap { cipher ->
|
||||
ciphersService
|
||||
.createCipherInOrganization(
|
||||
body = CreateCipherInOrganizationJsonRequest(
|
||||
cipher = cipher.toEncryptedNetworkCipher(),
|
||||
collectionIds = collectionIds,
|
||||
),
|
||||
)
|
||||
}
|
||||
.onSuccess {
|
||||
vaultDiskSource.saveCipher(
|
||||
userId = userId,
|
||||
cipher = it.copy(collectionIds = collectionIds),
|
||||
)
|
||||
}
|
||||
.fold(
|
||||
onFailure = { CreateCipherResult.Error },
|
||||
onSuccess = { CreateCipherResult.Success },
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun hardDeleteCipher(cipherId: String): DeleteCipherResult {
|
||||
val userId = activeUserId ?: return DeleteCipherResult.Error
|
||||
return ciphersService
|
||||
.hardDeleteCipher(cipherId)
|
||||
.onSuccess { vaultDiskSource.deleteCipher(userId, cipherId) }
|
||||
.fold(
|
||||
onSuccess = { DeleteCipherResult.Success },
|
||||
onFailure = { DeleteCipherResult.Error },
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun softDeleteCipher(
|
||||
cipherId: String,
|
||||
cipherView: CipherView,
|
||||
): DeleteCipherResult {
|
||||
val userId = activeUserId ?: return DeleteCipherResult.Error
|
||||
return ciphersService
|
||||
.softDeleteCipher(cipherId)
|
||||
.fold(
|
||||
onSuccess = {
|
||||
vaultSdkSource
|
||||
.encryptCipher(
|
||||
userId = userId,
|
||||
cipherView = cipherView.copy(
|
||||
deletedDate = Instant.now(),
|
||||
),
|
||||
)
|
||||
.onSuccess { cipher ->
|
||||
vaultDiskSource.saveCipher(
|
||||
userId = userId,
|
||||
cipher = cipher.toEncryptedNetworkCipherResponse(),
|
||||
)
|
||||
}
|
||||
DeleteCipherResult.Success
|
||||
},
|
||||
onFailure = { DeleteCipherResult.Error },
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun deleteCipherAttachment(
|
||||
cipherId: String,
|
||||
attachmentId: String,
|
||||
cipherView: CipherView,
|
||||
): DeleteAttachmentResult {
|
||||
val userId = activeUserId ?: return DeleteAttachmentResult.Error
|
||||
return ciphersService
|
||||
.deleteCipherAttachment(
|
||||
cipherId = cipherId,
|
||||
attachmentId = attachmentId,
|
||||
)
|
||||
.flatMap {
|
||||
vaultSdkSource
|
||||
.encryptCipher(
|
||||
userId = userId,
|
||||
cipherView = cipherView.copy(
|
||||
attachments = cipherView.attachments?.mapNotNull {
|
||||
if (it.id == attachmentId) null else it
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
.onSuccess { cipher ->
|
||||
vaultDiskSource.saveCipher(
|
||||
userId = userId,
|
||||
cipher = cipher.toEncryptedNetworkCipherResponse(),
|
||||
)
|
||||
}
|
||||
.fold(
|
||||
onSuccess = { DeleteAttachmentResult.Success },
|
||||
onFailure = { DeleteAttachmentResult.Error },
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun restoreCipher(
|
||||
cipherId: String,
|
||||
cipherView: CipherView,
|
||||
): RestoreCipherResult {
|
||||
val userId = activeUserId ?: return RestoreCipherResult.Error
|
||||
return ciphersService
|
||||
.restoreCipher(cipherId)
|
||||
.flatMap {
|
||||
vaultSdkSource.encryptCipher(
|
||||
userId = userId,
|
||||
cipherView = cipherView.copy(
|
||||
deletedDate = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
.onSuccess { cipher ->
|
||||
vaultDiskSource.saveCipher(
|
||||
userId = userId,
|
||||
cipher = cipher.toEncryptedNetworkCipherResponse(),
|
||||
)
|
||||
}
|
||||
.fold(
|
||||
onSuccess = { RestoreCipherResult.Success },
|
||||
onFailure = { RestoreCipherResult.Error },
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun updateCipher(
|
||||
cipherId: String,
|
||||
cipherView: CipherView,
|
||||
): UpdateCipherResult {
|
||||
val userId = activeUserId ?: return UpdateCipherResult.Error(null)
|
||||
return vaultSdkSource
|
||||
.encryptCipher(
|
||||
userId = userId,
|
||||
cipherView = cipherView,
|
||||
)
|
||||
.flatMap { cipher ->
|
||||
ciphersService.updateCipher(
|
||||
cipherId = cipherId,
|
||||
body = cipher.toEncryptedNetworkCipher(),
|
||||
)
|
||||
}
|
||||
.fold(
|
||||
onFailure = { UpdateCipherResult.Error(errorMessage = null) },
|
||||
onSuccess = { response ->
|
||||
when (response) {
|
||||
is UpdateCipherResponseJson.Invalid -> {
|
||||
UpdateCipherResult.Error(errorMessage = response.message)
|
||||
}
|
||||
|
||||
is UpdateCipherResponseJson.Success -> {
|
||||
vaultDiskSource.saveCipher(
|
||||
userId = userId,
|
||||
cipher = response.cipher
|
||||
.copy(collectionIds = cipherView.collectionIds),
|
||||
)
|
||||
UpdateCipherResult.Success
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun shareCipher(
|
||||
cipherId: String,
|
||||
organizationId: String,
|
||||
cipherView: CipherView,
|
||||
collectionIds: List<String>,
|
||||
): ShareCipherResult {
|
||||
val userId = activeUserId ?: return ShareCipherResult.Error
|
||||
return vaultSdkSource
|
||||
.moveToOrganization(
|
||||
userId = userId,
|
||||
organizationId = organizationId,
|
||||
cipherView = cipherView,
|
||||
)
|
||||
.flatMap { vaultSdkSource.encryptCipher(userId = userId, cipherView = it) }
|
||||
.flatMap { cipher ->
|
||||
ciphersService.shareCipher(
|
||||
cipherId = cipherId,
|
||||
body = ShareCipherJsonRequest(
|
||||
cipher = cipher.toEncryptedNetworkCipher(),
|
||||
collectionIds = collectionIds,
|
||||
),
|
||||
)
|
||||
}
|
||||
.onSuccess {
|
||||
vaultDiskSource.saveCipher(
|
||||
userId = userId,
|
||||
cipher = it.copy(collectionIds = collectionIds),
|
||||
)
|
||||
}
|
||||
.fold(
|
||||
onFailure = { ShareCipherResult.Error },
|
||||
onSuccess = { ShareCipherResult.Success },
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun updateCipherCollections(
|
||||
cipherId: String,
|
||||
cipherView: CipherView,
|
||||
collectionIds: List<String>,
|
||||
): ShareCipherResult {
|
||||
val userId = activeUserId ?: return ShareCipherResult.Error
|
||||
return ciphersService
|
||||
.updateCipherCollections(
|
||||
cipherId = cipherId,
|
||||
body = UpdateCipherCollectionsJsonRequest(collectionIds = collectionIds),
|
||||
)
|
||||
.flatMap {
|
||||
vaultSdkSource
|
||||
.encryptCipher(
|
||||
userId = userId,
|
||||
cipherView = cipherView.copy(collectionIds = collectionIds),
|
||||
)
|
||||
}
|
||||
.onSuccess { cipher ->
|
||||
vaultDiskSource.saveCipher(
|
||||
userId = userId,
|
||||
cipher = cipher.toEncryptedNetworkCipherResponse(),
|
||||
)
|
||||
}
|
||||
.fold(
|
||||
onSuccess = { ShareCipherResult.Success },
|
||||
onFailure = { ShareCipherResult.Error },
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
override suspend fun createAttachment(
|
||||
cipherId: String,
|
||||
cipherView: CipherView,
|
||||
fileSizeBytes: String,
|
||||
fileName: String,
|
||||
fileUri: Uri,
|
||||
): CreateAttachmentResult {
|
||||
val userId = activeUserId ?: return CreateAttachmentResult.Error
|
||||
val attachmentView = AttachmentView(
|
||||
id = null,
|
||||
url = null,
|
||||
size = fileSizeBytes,
|
||||
sizeName = null,
|
||||
fileName = fileName,
|
||||
key = null,
|
||||
)
|
||||
return vaultSdkSource
|
||||
.encryptCipher(
|
||||
userId = userId,
|
||||
cipherView = cipherView,
|
||||
)
|
||||
.flatMap { cipher ->
|
||||
fileManager
|
||||
.writeUriToCache(fileUri = fileUri)
|
||||
.flatMap { cacheFile ->
|
||||
vaultSdkSource
|
||||
.encryptAttachment(
|
||||
userId = userId,
|
||||
cipher = cipher,
|
||||
attachmentView = attachmentView,
|
||||
decryptedFilePath = cacheFile.absolutePath,
|
||||
encryptedFilePath = "${cacheFile.absolutePath}.enc",
|
||||
)
|
||||
.flatMap { attachment ->
|
||||
ciphersService
|
||||
.createAttachment(
|
||||
cipherId = cipherId,
|
||||
body = AttachmentJsonRequest(
|
||||
// We know these values are present because
|
||||
// - the filename/size are passed into the function
|
||||
// - the SDK call fills in the key
|
||||
fileName = requireNotNull(attachment.fileName),
|
||||
key = requireNotNull(attachment.key),
|
||||
fileSize = requireNotNull(attachment.size),
|
||||
),
|
||||
)
|
||||
.flatMap { attachmentJsonResponse ->
|
||||
val encryptedFile = File("${cacheFile.absolutePath}.enc")
|
||||
ciphersService
|
||||
.uploadAttachment(
|
||||
attachmentJsonResponse = attachmentJsonResponse,
|
||||
encryptedFile = encryptedFile,
|
||||
)
|
||||
.onSuccess {
|
||||
fileManager
|
||||
.delete(
|
||||
cacheFile,
|
||||
encryptedFile,
|
||||
)
|
||||
}
|
||||
.onFailure {
|
||||
fileManager
|
||||
.delete(
|
||||
cacheFile,
|
||||
encryptedFile,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.map { it.copy(collectionIds = cipherView.collectionIds) }
|
||||
.onSuccess {
|
||||
// Save the send immediately, regardless of whether the decrypt succeeds
|
||||
vaultDiskSource.saveCipher(userId = userId, cipher = it)
|
||||
}
|
||||
.flatMap {
|
||||
vaultSdkSource.decryptCipher(
|
||||
userId = userId,
|
||||
cipher = it.toEncryptedSdkCipher(),
|
||||
)
|
||||
}
|
||||
.fold(
|
||||
onFailure = { CreateAttachmentResult.Error },
|
||||
onSuccess = { CreateAttachmentResult.Success(it) },
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("ReturnCount")
|
||||
override suspend fun downloadAttachment(
|
||||
cipherView: CipherView,
|
||||
attachmentId: String,
|
||||
): DownloadAttachmentResult {
|
||||
val userId = requireNotNull(authDiskSource.userState?.activeUserId)
|
||||
|
||||
val cipher = vaultSdkSource
|
||||
.encryptCipher(
|
||||
userId = userId,
|
||||
cipherView = cipherView,
|
||||
)
|
||||
.fold(
|
||||
onSuccess = { it },
|
||||
onFailure = { return DownloadAttachmentResult.Failure },
|
||||
)
|
||||
val attachment = cipher.attachments?.find { it.id == attachmentId }
|
||||
?: return DownloadAttachmentResult.Failure
|
||||
|
||||
val attachmentData = ciphersService
|
||||
.getCipherAttachment(
|
||||
cipherId = requireNotNull(cipher.id),
|
||||
attachmentId = attachmentId,
|
||||
)
|
||||
.fold(
|
||||
onSuccess = { it },
|
||||
onFailure = { return DownloadAttachmentResult.Failure },
|
||||
)
|
||||
|
||||
val url = attachmentData.url ?: return DownloadAttachmentResult.Failure
|
||||
|
||||
val encryptedFile = when (val result = fileManager.downloadFileToCache(url)) {
|
||||
DownloadResult.Failure -> return DownloadAttachmentResult.Failure
|
||||
is DownloadResult.Success -> result.file
|
||||
}
|
||||
|
||||
val decryptedFile = File(encryptedFile.path + "_decrypted")
|
||||
return vaultSdkSource
|
||||
.decryptFile(
|
||||
userId = userId,
|
||||
cipher = cipher,
|
||||
attachment = attachment,
|
||||
encryptedFilePath = encryptedFile.path,
|
||||
decryptedFilePath = decryptedFile.path,
|
||||
)
|
||||
.fold(
|
||||
onSuccess = {
|
||||
encryptedFile.delete()
|
||||
DownloadAttachmentResult.Success(decryptedFile)
|
||||
},
|
||||
onFailure = {
|
||||
encryptedFile.delete()
|
||||
DownloadAttachmentResult.Failure
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("ReturnCount")
|
||||
override suspend fun createSend(
|
||||
sendView: SendView,
|
||||
|
|
|
@ -11,6 +11,7 @@ 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
|
||||
import com.x8bit.bitwarden.data.vault.manager.CipherManager
|
||||
import com.x8bit.bitwarden.data.vault.manager.FileManager
|
||||
import com.x8bit.bitwarden.data.vault.manager.TotpCodeManager
|
||||
import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
|
||||
|
@ -41,6 +42,7 @@ object VaultRepositoryModule {
|
|||
vaultSdkSource: VaultSdkSource,
|
||||
authDiskSource: AuthDiskSource,
|
||||
settingsDiskSource: SettingsDiskSource,
|
||||
cipherManager: CipherManager,
|
||||
fileManager: FileManager,
|
||||
vaultLockManager: VaultLockManager,
|
||||
dispatcherManager: DispatcherManager,
|
||||
|
@ -57,6 +59,7 @@ object VaultRepositoryModule {
|
|||
vaultSdkSource = vaultSdkSource,
|
||||
authDiskSource = authDiskSource,
|
||||
settingsDiskSource = settingsDiskSource,
|
||||
cipherManager = cipherManager,
|
||||
fileManager = fileManager,
|
||||
vaultLockManager = vaultLockManager,
|
||||
dispatcherManager = dispatcherManager,
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue