diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSource.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSource.kt index 956fb7396..4e2b5efe1 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSource.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSource.kt @@ -26,6 +26,11 @@ interface VaultDiskSource { */ fun getOfflineCiphers(userId: String): Flow> + /** + * Deletes an offline cipher from the data source for the given [userId] and [cipherId]. + */ + suspend fun deleteOfflineCipher(userId: String, cipherId: String) + /** * Retrieves all ciphers from the data source for a given [userId]. */ diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSourceImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSourceImpl.kt index abfe008f8..cb9bbf8fd 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSourceImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSourceImpl.kt @@ -105,6 +105,10 @@ class VaultDiskSourceImpl( }, ) + override suspend fun deleteOfflineCipher(userId: String, cipherId: String) { + offlineCiphersDao.deleteCipher(userId, cipherId) + } + override fun getCiphers( userId: String, ): Flow> = diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/CipherManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/CipherManagerImpl.kt index a11dfb35a..f07bd4d0f 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/CipherManagerImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/CipherManagerImpl.kt @@ -6,11 +6,13 @@ import com.bitwarden.vault.AttachmentView import com.bitwarden.vault.Cipher import com.bitwarden.vault.CipherView import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource +import com.x8bit.bitwarden.data.platform.manager.NetworkConnectionManager 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.CreateCipherInOrganizationJsonRequest +import com.x8bit.bitwarden.data.vault.datasource.network.model.OfflineCipherJson 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 @@ -25,16 +27,28 @@ 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.toCipher 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 com.x8bit.bitwarden.data.vault.repository.util.toNetworkAttachmentRequest +import com.x8bit.bitwarden.data.vault.repository.util.toOfflineCipher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flatMapConcat +import kotlinx.coroutines.flow.flattenConcat +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch import java.io.File import java.time.Clock /** * The default implementation of the [CipherManager]. */ +@OptIn(ExperimentalCoroutinesApi::class) @Suppress("TooManyFunctions") class CipherManagerImpl( private val fileManager: FileManager, @@ -43,9 +57,74 @@ class CipherManagerImpl( private val vaultDiskSource: VaultDiskSource, private val vaultSdkSource: VaultSdkSource, private val clock: Clock, + private val networkConnectionManager: NetworkConnectionManager, + private val externalScope: CoroutineScope, ) : CipherManager { + private val activeUserId: String? get() = authDiskSource.userState?.activeUserId + init { + externalScope.launch { + networkConnectionManager.isNetworkConnectedFlow + .map { + if (it && activeUserId != null) { + // Device went online + + // TODO: We need to add support for non active users! + vaultDiskSource.getOfflineCiphers(activeUserId!!) + } else { + flowOf(listOf()) + } + }.flattenConcat().collect { + if (activeUserId == null) { + return@collect + } + + val userId = activeUserId!! + + it.map { c -> + val cipher = c.toOfflineCipher().toCipher() + when (cipher.id) { + null -> ciphersService.createCipher(body = cipher.toEncryptedNetworkCipher()) + .onSuccess { + vaultDiskSource.saveCipher(userId = userId, cipher = it) + vaultDiskSource.deleteOfflineCipher(userId = userId, cipherId = c.id) + } + .fold( + onFailure = { CreateCipherResult.Error }, + onSuccess = { CreateCipherResult.Success }, + ) + else -> ciphersService.updateCipher( + cipherId = cipher.id!!, + body = cipher.toEncryptedNetworkCipher() + ).map { response -> + when (response) { + is UpdateCipherResponseJson.Invalid -> { + UpdateCipherResult.Error(errorMessage = response.message) + } + + is UpdateCipherResponseJson.Success -> { + vaultDiskSource.saveCipher( + userId = userId, + // TODO: Why are we doing this? + cipher = response.cipher.copy(collectionIds = cipher.collectionIds), + ) + UpdateCipherResult.Success + } + } + } + .fold( + onFailure = { UpdateCipherResult.Error(errorMessage = null) }, + onSuccess = { it }, + ) + } + + } + } + } + } + + override suspend fun createOfflineCipher(cipherView: CipherView): CreateCipherResult { val userId = activeUserId ?: return CreateCipherResult.Error diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/di/VaultManagerModule.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/di/VaultManagerModule.kt index 519ea6fc3..b6b85c613 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/di/VaultManagerModule.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/di/VaultManagerModule.kt @@ -6,6 +6,7 @@ import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource import com.x8bit.bitwarden.data.auth.manager.TrustedDeviceManager import com.x8bit.bitwarden.data.auth.manager.UserLogoutManager import com.x8bit.bitwarden.data.platform.manager.AppForegroundManager +import com.x8bit.bitwarden.data.platform.manager.NetworkConnectionManager 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 @@ -44,6 +45,7 @@ object VaultManagerModule { authDiskSource: AuthDiskSource, fileManager: FileManager, clock: Clock, + networkConnectionManager: NetworkConnectionManager ): CipherManager = CipherManagerImpl( fileManager = fileManager, authDiskSource = authDiskSource, @@ -51,6 +53,7 @@ object VaultManagerModule { vaultDiskSource = vaultDiskSource, vaultSdkSource = vaultSdkSource, clock = clock, + networkConnectionManager = networkConnectionManager ) @Provides