BIT-1078: Save login items (Networking) (#272)

This commit is contained in:
Ramsey Smith
2023-11-22 21:13:05 -07:00
committed by Álison Fernandes
parent 1f337e94f0
commit d36601fa3a
11 changed files with 324 additions and 15 deletions

View File

@@ -16,7 +16,7 @@ class LocalDateTimeSerializer : KSerializer<LocalDateTime> {
private val dateTimeFormatterDeserialization = DateTimeFormatter
.ofPattern("yyyy-MM-dd'T'HH:mm:ss.[SSSSSSS][SSSSSS][SSSSS][SSSS][SSS][SS][S]'Z'")
private val dateTimeFormatterSerialization =
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSSS'Z'")
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
override val descriptor: SerialDescriptor
get() = PrimitiveSerialDescriptor(serialName = "LocalDateTime", kind = PrimitiveKind.STRING)

View File

@@ -0,0 +1,18 @@
package com.x8bit.bitwarden.data.vault.datasource.network.api
import CipherJsonRequest
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
import retrofit2.http.Body
import retrofit2.http.POST
/**
* Defines raw calls under the /ciphers API with authentication applied.
*/
interface CiphersApi {
/**
* Create a cipher.
*/
@POST("ciphers")
suspend fun createCipher(@Body body: CipherJsonRequest): Result<SyncResponseJson.Cipher>
}

View File

@@ -3,6 +3,8 @@ package com.x8bit.bitwarden.data.vault.datasource.network.di
import com.x8bit.bitwarden.data.vault.datasource.network.service.SyncService
import com.x8bit.bitwarden.data.vault.datasource.network.service.SyncServiceImpl
import com.x8bit.bitwarden.data.platform.datasource.network.retrofit.Retrofits
import com.x8bit.bitwarden.data.vault.datasource.network.service.CiphersService
import com.x8bit.bitwarden.data.vault.datasource.network.service.CiphersServiceImpl
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@@ -17,6 +19,14 @@ import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
object VaultNetworkModule {
@Provides
@Singleton
fun provideCiphersService(
retrofits: Retrofits,
): CiphersService = CiphersServiceImpl(
ciphersApi = retrofits.authenticatedApiRetrofit.create(),
)
@Provides
@Singleton
fun provideSyncService(

View File

@@ -0,0 +1,71 @@
import com.x8bit.bitwarden.data.vault.datasource.network.model.CipherRepromptTypeJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.CipherTypeJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
import kotlinx.serialization.Contextual
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import java.time.LocalDateTime
/**
* Represents a cipher request.
*
* @property notes The notes of the cipher (nullable).
* @property reprompt The reprompt of the cipher.
* @property passwordHistory A list of password history objects
* associated with the cipher (nullable).
* @property type The type of cipher.
* @property login The login of the cipher.
* @property secureNote The secure note of the cipher.
* @property folderId The folder ID of the cipher (nullable).
* @property organizationId The organization ID of the cipher (nullable).
* @property identity The identity of the cipher.
* @property name The name of the cipher (nullable).
* @property fields A list of fields associated with the cipher (nullable).
* @property isFavorite If the cipher is a favorite.
* @property card The card of the cipher.
*/
@Serializable
data class CipherJsonRequest(
@SerialName("notes")
val notes: String?,
@SerialName("reprompt")
val reprompt: CipherRepromptTypeJson,
@SerialName("passwordHistory")
val passwordHistory: List<SyncResponseJson.Cipher.PasswordHistory>?,
@SerialName("lastKnownRevisionDate")
@Contextual
val lastKnownRevisionDate: LocalDateTime?,
@SerialName("type")
val type: CipherTypeJson,
@SerialName("login")
val login: SyncResponseJson.Cipher.Login?,
@SerialName("secureNote")
val secureNote: SyncResponseJson.Cipher.SecureNote?,
@SerialName("folderId")
val folderId: String?,
@SerialName("organizationId")
val organizationId: String?,
@SerialName("identity")
val identity: SyncResponseJson.Cipher.Identity?,
@SerialName("name")
val name: String?,
@SerialName("fields")
val fields: List<SyncResponseJson.Cipher.Field>?,
@SerialName("favorite")
val isFavorite: Boolean,
@SerialName("card")
val card: SyncResponseJson.Cipher.Card?,
)

View File

@@ -0,0 +1,14 @@
package com.x8bit.bitwarden.data.vault.datasource.network.service
import CipherJsonRequest
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
/**
* Provides an API for querying ciphers endpoints.
*/
interface CiphersService {
/**
* Attempt to create a cipher.
*/
suspend fun createCipher(body: CipherJsonRequest): Result<SyncResponseJson.Cipher>
}

View File

@@ -0,0 +1,12 @@
package com.x8bit.bitwarden.data.vault.datasource.network.service
import CipherJsonRequest
import com.x8bit.bitwarden.data.vault.datasource.network.api.CiphersApi
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
class CiphersServiceImpl constructor(
private val ciphersApi: CiphersApi,
) : CiphersService {
override suspend fun createCipher(body: CipherJsonRequest): Result<SyncResponseJson.Cipher> =
ciphersApi.createCipher(body = body)
}

View File

@@ -10,21 +10,22 @@ import com.x8bit.bitwarden.ui.vault.feature.vault.VaultState
* Transforms a [CipherView] into a [VaultState.ViewState.VaultItem].
*/
@Suppress("MagicNumber")
private fun CipherView.toVaultItem(): VaultState.ViewState.VaultItem =
when (type) {
private fun CipherView.toVaultItemOrNull(): VaultState.ViewState.VaultItem? {
val id = this.id ?: return null
return when (type) {
CipherType.LOGIN -> VaultState.ViewState.VaultItem.Login(
id = id.toString(),
id = id,
name = name.asText(),
username = login?.username?.asText(),
)
CipherType.SECURE_NOTE -> VaultState.ViewState.VaultItem.SecureNote(
id = id.toString(),
id = id,
name = name.asText(),
)
CipherType.CARD -> VaultState.ViewState.VaultItem.Card(
id = id.toString(),
id = id,
name = name.asText(),
brand = card?.brand?.asText(),
lastFourDigits = card?.number
@@ -33,11 +34,12 @@ private fun CipherView.toVaultItem(): VaultState.ViewState.VaultItem =
)
CipherType.IDENTITY -> VaultState.ViewState.VaultItem.Identity(
id = id.toString(),
id = id,
name = name.asText(),
firstName = identity?.firstName?.asText(),
)
}
}
/**
* Transforms [VaultData] into [VaultState.ViewState].
@@ -46,24 +48,28 @@ fun VaultData.toViewState(): VaultState.ViewState =
if (cipherViewList.isEmpty() && folderViewList.isEmpty()) {
VaultState.ViewState.NoItems
} else {
// Filter out any items with invalid IDs in the unlikely case they exist
val filteredCipherViewList = cipherViewList.filterNot { it.id.isNullOrBlank() }
VaultState.ViewState.Content(
loginItemsCount = cipherViewList.count { it.type == CipherType.LOGIN },
cardItemsCount = cipherViewList.count { it.type == CipherType.CARD },
identityItemsCount = cipherViewList.count { it.type == CipherType.IDENTITY },
secureNoteItemsCount = cipherViewList.count { it.type == CipherType.SECURE_NOTE },
loginItemsCount = filteredCipherViewList.count { it.type == CipherType.LOGIN },
cardItemsCount = filteredCipherViewList.count { it.type == CipherType.CARD },
identityItemsCount = filteredCipherViewList.count { it.type == CipherType.IDENTITY },
secureNoteItemsCount = filteredCipherViewList
.count { it.type == CipherType.SECURE_NOTE },
favoriteItems = cipherViewList
.filter { it.favorite }
.map { it.toVaultItem() },
.mapNotNull { it.toVaultItemOrNull() },
folderItems = folderViewList.map { folderView ->
VaultState.ViewState.FolderItem(
id = folderView.id,
name = folderView.name.asText(),
itemCount = cipherViewList.count { folderView.id == it.folderId },
itemCount = cipherViewList
.count { !it.id.isNullOrBlank() && folderView.id == it.folderId },
)
},
noFolderItems = cipherViewList
.filter { it.folderId.isNullOrBlank() }
.map { it.toVaultItem() },
.mapNotNull { it.toVaultItemOrNull() },
// TODO need to populate trash item count in BIT-969
trashItemsCount = 0,
)