PM-19131 - Custom hidden fields not working properly if TOTP also configured (#4916)

This commit is contained in:
Phil Cappelli
2025-04-01 16:38:18 +00:00
committed by GitHub
5 changed files with 79 additions and 20 deletions

View File

@@ -1597,11 +1597,17 @@ data class VaultItemState(
* Represents a custom field, TextField, HiddenField, BooleanField, or LinkedField.
*/
sealed class Custom : Parcelable {
/**
* The unique ID of the custom field.
*/
abstract val id: String
/**
* Represents the data for displaying a custom text field.
*/
@Parcelize
data class TextField(
override val id: String,
val name: String,
val value: String,
val isCopyable: Boolean,
@@ -1612,6 +1618,7 @@ data class VaultItemState(
*/
@Parcelize
data class HiddenField(
override val id: String,
val name: String,
val value: String,
val isCopyable: Boolean,
@@ -1623,6 +1630,7 @@ data class VaultItemState(
*/
@Parcelize
data class BooleanField(
override val id: String,
val name: String,
val value: Boolean,
) : Custom()
@@ -1632,6 +1640,7 @@ data class VaultItemState(
*/
@Parcelize
data class LinkedField(
override val id: String,
val vaultLinkedFieldType: VaultLinkedFieldType,
val name: String,
) : Custom()

View File

@@ -57,7 +57,14 @@ fun CipherView.toViewState(
name = name,
requiresReprompt = (reprompt == CipherRepromptType.PASSWORD && hasMasterPassword) &&
previousState?.common?.requiresReprompt != false,
customFields = fields.orEmpty().map { it.toCustomField() },
customFields = fields.orEmpty().map { fieldView ->
fieldView.toCustomField(
previousState = previousState
?.common
?.customFields
?.find { it.id == fieldView.hashCode().toString() },
)
},
lastUpdated = revisionDate.toFormattedPattern(
pattern = LAST_UPDATED_DATE_TIME_PATTERN,
clock = clock,
@@ -195,27 +202,39 @@ fun CipherView.toViewState(
},
)
private fun FieldView.toCustomField(): VaultItemState.ViewState.Content.Common.Custom =
/**
* Transforms [FieldView] into [VaultItemState.ViewState.Content.Common.Custom].
*/
fun FieldView.toCustomField(
previousState: VaultItemState.ViewState.Content.Common.Custom?,
): VaultItemState.ViewState.Content.Common.Custom =
when (type) {
FieldType.TEXT -> VaultItemState.ViewState.Content.Common.Custom.TextField(
id = this.hashCode().toString(),
name = name.orEmpty(),
value = value.orZeroWidthSpace(),
isCopyable = !value.isNullOrBlank(),
)
FieldType.HIDDEN -> VaultItemState.ViewState.Content.Common.Custom.HiddenField(
id = this.hashCode().toString(),
name = name.orEmpty(),
value = value.orZeroWidthSpace(),
isCopyable = !value.isNullOrBlank(),
isVisible = false,
isVisible = (previousState as?
VaultItemState.ViewState.Content.Common.Custom.HiddenField)
?.isVisible
?: false,
)
FieldType.BOOLEAN -> VaultItemState.ViewState.Content.Common.Custom.BooleanField(
id = this.hashCode().toString(),
name = name.orEmpty(),
value = value?.toBoolean() ?: false,
)
FieldType.LINKED -> VaultItemState.ViewState.Content.Common.Custom.LinkedField(
id = this.hashCode().toString(),
vaultLinkedFieldType = VaultLinkedFieldType.fromId(requireNotNull(linkedId)),
name = name.orEmpty(),
)

View File

@@ -776,6 +776,7 @@ class VaultItemScreenTest : BaseComposeTest() {
@Test
fun `on show hidden field click should send HiddenFieldVisibilityClicked`() {
val textField = VaultItemState.ViewState.Content.Common.Custom.HiddenField(
id = "12345",
name = "hidden",
value = "hidden password",
isCopyable = true,
@@ -814,6 +815,7 @@ class VaultItemScreenTest : BaseComposeTest() {
@Test
fun `copy hidden field button should be displayed according to state`() {
val hiddenField = VaultItemState.ViewState.Content.Common.Custom.HiddenField(
id = "12345",
name = "hidden",
value = "hidden password",
isCopyable = true,
@@ -855,6 +857,7 @@ class VaultItemScreenTest : BaseComposeTest() {
@Test
fun `on copy hidden field click should send CopyCustomHiddenFieldClick`() {
val hiddenField = VaultItemState.ViewState.Content.Common.Custom.HiddenField(
id = "12345",
name = "hidden",
value = "hidden password",
isCopyable = true,
@@ -890,6 +893,7 @@ class VaultItemScreenTest : BaseComposeTest() {
@Test
fun `on copy text field click should send CopyCustomTextFieldClick`() {
val textField = VaultItemState.ViewState.Content.Common.Custom.TextField(
id = "12345",
name = "text",
value = "value",
isCopyable = true,
@@ -924,6 +928,7 @@ class VaultItemScreenTest : BaseComposeTest() {
@Test
fun `text field copy button should be displayed according to state`() {
val textField = VaultItemState.ViewState.Content.Common.Custom.TextField(
id = "12345",
name = "text",
value = "value",
isCopyable = true,
@@ -1552,6 +1557,7 @@ class VaultItemScreenTest : BaseComposeTest() {
// Adding a custom field so that we can scroll to it
// So we can see the Copy notes button but not have it covered by the FAB
val textField = VaultItemState.ViewState.Content.Common.Custom.TextField(
id = "12345",
name = "text",
value = "value",
isCopyable = true,
@@ -1587,6 +1593,7 @@ class VaultItemScreenTest : BaseComposeTest() {
// Adding a custom field so that we can scroll to it
// So we can see the Copy notes button but not have it covered by the FAB
val textField = VaultItemState.ViewState.Content.Common.Custom.TextField(
id = "12345",
name = "text",
value = "value",
isCopyable = true,
@@ -1642,6 +1649,7 @@ class VaultItemScreenTest : BaseComposeTest() {
// Adding a custom field so that we can scroll to it
// So we can see the Copy notes button but not have it covered by the FAB
val textField = VaultItemState.ViewState.Content.Common.Custom.TextField(
id = "12345",
name = "text",
value = "value",
isCopyable = true,
@@ -1885,11 +1893,13 @@ class VaultItemScreenTest : BaseComposeTest() {
fun `in login state, linked custom fields should be displayed according to state`() {
val linkedFieldUserName =
VaultItemState.ViewState.Content.Common.Custom.LinkedField(
id = "12345",
name = "linked username",
vaultLinkedFieldType = VaultLinkedFieldType.USERNAME,
)
val linkedFieldsPassword = VaultItemState.ViewState.Content.Common.Custom.LinkedField(
id = "12345",
name = "linked password",
vaultLinkedFieldType = VaultLinkedFieldType.PASSWORD,
)
@@ -3149,17 +3159,20 @@ private val DEFAULT_COMMON: VaultItemState.ViewState.Content.Common =
notes = "Lots of notes",
customFields = listOf(
VaultItemState.ViewState.Content.Common.Custom.TextField(
id = "12345",
name = "text",
value = "value",
isCopyable = true,
),
VaultItemState.ViewState.Content.Common.Custom.HiddenField(
id = "12345",
name = "hidden",
value = "hidden password",
isCopyable = true,
isVisible = false,
),
VaultItemState.ViewState.Content.Common.Custom.BooleanField(
id = "12345",
name = "boolean",
value = true,
),

View File

@@ -1070,6 +1070,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
runTest {
val loginState = DEFAULT_STATE.copy(viewState = DEFAULT_VIEW_STATE)
val field = VaultItemState.ViewState.Content.Common.Custom.HiddenField(
id = "12345",
name = "hidden",
value = "value",
isCopyable = true,
@@ -1135,6 +1136,7 @@ class VaultItemViewModelTest : BaseViewModelTest() {
runTest {
val hiddenField =
VaultItemState.ViewState.Content.Common.Custom.HiddenField(
id = "12345",
name = "hidden",
value = "value",
isCopyable = true,
@@ -3702,25 +3704,30 @@ class VaultItemViewModelTest : BaseViewModelTest() {
notes = "Lots of notes",
customFields = listOf(
VaultItemState.ViewState.Content.Common.Custom.TextField(
id = "12345",
name = "text",
value = "value",
isCopyable = true,
),
VaultItemState.ViewState.Content.Common.Custom.HiddenField(
id = "12345",
name = "hidden",
value = "value",
isCopyable = true,
isVisible = false,
),
VaultItemState.ViewState.Content.Common.Custom.BooleanField(
id = "12345",
name = "boolean",
value = true,
),
VaultItemState.ViewState.Content.Common.Custom.LinkedField(
id = "12345",
name = "linked username",
vaultLinkedFieldType = VaultLinkedFieldType.USERNAME,
),
VaultItemState.ViewState.Content.Common.Custom.LinkedField(
id = "12345",
name = "linked password",
vaultLinkedFieldType = VaultLinkedFieldType.PASSWORD,
),

View File

@@ -18,7 +18,6 @@ import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.components.model.IconData
import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemState
import com.x8bit.bitwarden.ui.vault.feature.item.model.TotpCodeItemData
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType
import kotlinx.collections.immutable.persistentListOf
import java.time.Instant
@@ -188,29 +187,41 @@ fun createCommonContent(
lastUpdated = "1/1/70 12:16 AM",
notes = "Lots of notes",
customFields = listOf(
VaultItemState.ViewState.Content.Common.Custom.TextField(
FieldView(
name = "text",
value = "value",
isCopyable = true,
),
VaultItemState.ViewState.Content.Common.Custom.HiddenField(
type = FieldType.TEXT,
linkedId = null,
)
.toCustomField(null),
FieldView(
name = "hidden",
value = "value",
isCopyable = true,
isVisible = false,
),
VaultItemState.ViewState.Content.Common.Custom.BooleanField(
type = FieldType.HIDDEN,
linkedId = null,
)
.toCustomField(null),
FieldView(
name = "boolean",
value = true,
),
VaultItemState.ViewState.Content.Common.Custom.LinkedField(
value = "true",
type = FieldType.BOOLEAN,
linkedId = null,
)
.toCustomField(null),
FieldView(
name = "linked username",
vaultLinkedFieldType = VaultLinkedFieldType.USERNAME,
),
VaultItemState.ViewState.Content.Common.Custom.LinkedField(
value = null,
type = FieldType.LINKED,
linkedId = 100U,
)
.toCustomField(null),
FieldView(
name = "linked password",
vaultLinkedFieldType = VaultLinkedFieldType.PASSWORD,
),
value = null,
type = FieldType.LINKED,
linkedId = 101U,
)
.toCustomField(null),
),
requiresReprompt = true,
requiresCloneConfirmation = true,