Skip to content

Commit

Permalink
PM-13020: During totp flow master password reprompt should be honored (
Browse files Browse the repository at this point in the history
  • Loading branch information
david-livefront authored Oct 23, 2024
1 parent fca00d3 commit c5a266d
Show file tree
Hide file tree
Showing 13 changed files with 203 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,9 @@ fun SearchContent(
supportingLabelTestTag = it.subtitleTestTag,
optionsTestTag = it.overflowTestTag,
onClick = {
if (it.autofillSelectionOptions.isNotEmpty()) {
if (it.isTotp && it.shouldDisplayMasterPasswordReprompt) {
masterPasswordRepromptData = MasterPasswordRepromptData.Totp(it.id)
} else if (it.autofillSelectionOptions.isNotEmpty()) {
autofillSelectionOptionsItem = it
} else {
searchHandlers.onItemClick(it.id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,10 @@ class SearchViewModel @Inject constructor(
),
)
}

is MasterPasswordRepromptData.Totp -> {
trySendAction(SearchAction.ItemClick(itemId = data.cipherId))
}
}
}

Expand Down Expand Up @@ -680,6 +684,7 @@ class SearchViewModel @Inject constructor(
baseIconUrl = state.baseIconUrl,
isIconLoadingDisabled = state.isIconLoadingDisabled,
isAutofill = state.isAutofill,
isTotp = state.isTotp,
isPremiumUser = state.isPremium,
)
}
Expand Down Expand Up @@ -826,6 +831,7 @@ data class SearchState(
val overflowOptions: List<ListingItemOverflowAction>,
val overflowTestTag: String?,
val autofillSelectionOptions: List<AutofillSelectionOption>,
val isTotp: Boolean,
val shouldDisplayMasterPasswordReprompt: Boolean,
) : Parcelable
}
Expand Down Expand Up @@ -1171,6 +1177,14 @@ sealed class MasterPasswordRepromptData : Parcelable {
val cipherId: String,
) : MasterPasswordRepromptData()

/**
* Autofill was selected.
*/
@Parcelize
data class Totp(
val cipherId: String,
) : MasterPasswordRepromptData()

/**
* A cipher overflow menu item action was selected.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ fun List<CipherView>.toViewState(
hasMasterPassword: Boolean,
isIconLoadingDisabled: Boolean,
isAutofill: Boolean,
isTotp: Boolean,
isPremiumUser: Boolean,
): SearchState.ViewState =
when {
Expand All @@ -159,6 +160,7 @@ fun List<CipherView>.toViewState(
hasMasterPassword = hasMasterPassword,
isIconLoadingDisabled = isIconLoadingDisabled,
isAutofill = isAutofill,
isTotp = isTotp,
isPremiumUser = isPremiumUser,
)
.sortAlphabetically(),
Expand All @@ -172,11 +174,13 @@ fun List<CipherView>.toViewState(
}
}

@Suppress("LongParameterList")
private fun List<CipherView>.toDisplayItemList(
baseIconUrl: String,
hasMasterPassword: Boolean,
isIconLoadingDisabled: Boolean,
isAutofill: Boolean,
isTotp: Boolean,
isPremiumUser: Boolean,
): List<SearchState.DisplayItem> =
this.map {
Expand All @@ -185,15 +189,18 @@ private fun List<CipherView>.toDisplayItemList(
hasMasterPassword = hasMasterPassword,
isIconLoadingDisabled = isIconLoadingDisabled,
isAutofill = isAutofill,
isTotp = isTotp,
isPremiumUser = isPremiumUser,
)
}

@Suppress("LongParameterList")
private fun CipherView.toDisplayItem(
baseIconUrl: String,
hasMasterPassword: Boolean,
isIconLoadingDisabled: Boolean,
isAutofill: Boolean,
isTotp: Boolean,
isPremiumUser: Boolean,
): SearchState.DisplayItem =
SearchState.DisplayItem(
Expand Down Expand Up @@ -221,6 +228,7 @@ private fun CipherView.toDisplayItem(
.filter {
this.login != null || (it != AutofillSelectionOption.AUTOFILL_AND_SAVE)
},
isTotp = isTotp,
shouldDisplayMasterPasswordReprompt = reprompt == CipherRepromptType.PASSWORD,
)

Expand Down Expand Up @@ -360,6 +368,7 @@ private fun SendView.toDisplayItem(
overflowTestTag = "SendOptionsButton",
totpCode = null,
autofillSelectionOptions = emptyList(),
isTotp = false,
shouldDisplayMasterPasswordReprompt = false,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,11 +215,14 @@ fun VaultItemListingContent(
supportingLabelTestTag = it.subtitleTestTag,
optionsTestTag = it.optionsTestTag,
onClick = {
if (it.isAutofill && it.shouldShowMasterPasswordReprompt) {
masterPasswordRepromptData =
MasterPasswordRepromptData.Autofill(
cipherId = it.id,
)
if (it.isTotp && it.shouldShowMasterPasswordReprompt) {
masterPasswordRepromptData = MasterPasswordRepromptData.Totp(
cipherId = it.id,
)
} else if (it.isAutofill && it.shouldShowMasterPasswordReprompt) {
masterPasswordRepromptData = MasterPasswordRepromptData.Autofill(
cipherId = it.id,
)
} else {
vaultItemClick(it.id)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1153,6 +1153,10 @@ class VaultItemListingViewModel @Inject constructor(
VaultItemListingsAction.OverflowOptionClick(data.action),
)
}

is MasterPasswordRepromptData.Totp -> {
sendEvent(VaultItemListingEvent.NavigateToEditCipher(data.cipherId))
}
}
}

Expand Down Expand Up @@ -1972,6 +1976,7 @@ data class VaultItemListingState(
val optionsTestTag: String,
val isAutofill: Boolean,
val isFido2Creation: Boolean,
val isTotp: Boolean,
val shouldShowMasterPasswordReprompt: Boolean,
)

Expand Down Expand Up @@ -2551,6 +2556,14 @@ sealed class MasterPasswordRepromptData : Parcelable {
val cipherId: String,
) : MasterPasswordRepromptData()

/**
* Totp was selected.
*/
@Parcelize
data class Totp(
val cipherId: String,
) : MasterPasswordRepromptData()

/**
* A cipher overflow menu item action was selected.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ fun VaultData.toViewState(
isFido2Creation = fido2CreationData != null,
fido2CredentialAutofillViews = fido2CredentialAutofillViews,
isPremiumUser = isPremiumUser,
isTotp = totpData != null,
),
displayFolderList = folderList.map { folderView ->
VaultItemListingState.FolderDisplayItem(
Expand Down Expand Up @@ -282,6 +283,7 @@ private fun List<CipherView>.toDisplayItemList(
isFido2Creation: Boolean,
fido2CredentialAutofillViews: List<Fido2CredentialAutofillView>?,
isPremiumUser: Boolean,
isTotp: Boolean,
): List<VaultItemListingState.DisplayItem> =
this.map {
it.toDisplayItem(
Expand All @@ -295,6 +297,7 @@ private fun List<CipherView>.toDisplayItemList(
fido2CredentialAutofillView.cipherId == it.id
},
isPremiumUser = isPremiumUser,
isTotp = isTotp,
)
}

Expand All @@ -318,6 +321,7 @@ private fun CipherView.toDisplayItem(
isFido2Creation: Boolean,
fido2CredentialAutofillView: Fido2CredentialAutofillView?,
isPremiumUser: Boolean,
isTotp: Boolean,
): VaultItemListingState.DisplayItem =
VaultItemListingState.DisplayItem(
id = id.orEmpty(),
Expand Down Expand Up @@ -345,6 +349,7 @@ private fun CipherView.toDisplayItem(
optionsTestTag = "CipherOptionsButton",
isAutofill = isAutofill,
isFido2Creation = isFido2Creation,
isTotp = isTotp,
shouldShowMasterPasswordReprompt = (reprompt == CipherRepromptType.PASSWORD) &&
hasMasterPassword,
)
Expand Down Expand Up @@ -416,6 +421,7 @@ private fun SendView.toDisplayItem(
isAutofill = false,
shouldShowMasterPasswordReprompt = false,
isFido2Creation = false,
isTotp = false,
)

@get:DrawableRes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,26 @@ class SearchScreenTest : BaseComposeTest() {
composeTestRule.assertNoDialogExists()
}

@Test
fun `clicking on totp when reprompt is required should show master password dialog`() {
mutableStateFlow.value = DEFAULT_STATE.copy(
viewState = SearchState.ViewState.Content(
displayItems = listOf(
createMockDisplayItemForCipher(number = 1, isTotp = true).copy(
shouldDisplayMasterPasswordReprompt = true,
),
),
),
totpData = mockk(),
)
composeTestRule
.onNodeWithText(text = "mockName-1")
.assertIsDisplayed()
.performClick()

composeTestRule.assertMasterPasswordDialogDisplayed()
}

@Test
fun `clicking cancel on the master password dialog should close the dialog`() {
mutableStateFlow.value = createStateForAutofill(isRepromptRequired = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,32 @@ class SearchViewModelTest : BaseViewModelTest() {
}
}

@Suppress("MaxLineLength")
@Test
fun `MasterPasswordRepromptSubmit for a request Success with a valid password for totp should emit NavigateToEditCipher`() =
runTest {
setupMockUri()
val cipherId = CIPHER_ID
val password = "password"

coEvery {
authRepository.validatePassword(password = password)
} returns ValidatePasswordResult.Success(isValid = true)
val viewModel = createViewModel(initialState = DEFAULT_STATE.copy(totpData = mockk()))

viewModel.eventFlow.test {
viewModel.trySendAction(
SearchAction.MasterPasswordRepromptSubmit(
password = password,
masterPasswordRepromptData = MasterPasswordRepromptData.Totp(
cipherId = cipherId,
),
),
)
assertEquals(SearchEvent.NavigateToEditCipher(cipherId), awaitItem())
}
}

@Suppress("MaxLineLength")
@Test
fun `MasterPasswordRepromptSubmit for a request Success with a valid password for an overflow action should perform the action`() =
Expand Down Expand Up @@ -990,6 +1016,7 @@ class SearchViewModelTest : BaseViewModelTest() {
isAutofill = false,
hasMasterPassword = true,
isPremiumUser = true,
isTotp = false,
)
} returns expectedViewState
val dataState = DataState.Loaded(
Expand Down Expand Up @@ -1092,6 +1119,7 @@ class SearchViewModelTest : BaseViewModelTest() {
isAutofill = false,
hasMasterPassword = true,
isPremiumUser = true,
isTotp = false,
)
} returns expectedViewState
mutableVaultDataStateFlow.tryEmit(
Expand Down Expand Up @@ -1204,6 +1232,7 @@ class SearchViewModelTest : BaseViewModelTest() {
isAutofill = false,
hasMasterPassword = true,
isPremiumUser = true,
isTotp = false,
)
} returns expectedViewState
val dataState = DataState.Error(
Expand Down Expand Up @@ -1319,6 +1348,7 @@ class SearchViewModelTest : BaseViewModelTest() {
isAutofill = false,
hasMasterPassword = true,
isPremiumUser = true,
isTotp = false,
)
} returns expectedViewState
val dataState = DataState.NoNetwork(
Expand Down Expand Up @@ -1494,6 +1524,7 @@ class SearchViewModelTest : BaseViewModelTest() {
isAutofill = true,
hasMasterPassword = true,
isPremiumUser = true,
isTotp = false,
)
} returns expectedViewState
val dataState = DataState.Loaded(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ class SearchTypeDataExtensionsTest {
isAutofill = false,
hasMasterPassword = true,
isPremiumUser = true,
isTotp = true,
)

assertEquals(SearchState.ViewState.Empty(message = null), result)
Expand All @@ -324,6 +325,7 @@ class SearchTypeDataExtensionsTest {
isAutofill = false,
hasMasterPassword = true,
isPremiumUser = true,
isTotp = false,
)

assertEquals(
Expand Down Expand Up @@ -364,6 +366,7 @@ class SearchTypeDataExtensionsTest {
isAutofill = true,
hasMasterPassword = true,
isPremiumUser = true,
isTotp = false,
)

assertEquals(
Expand Down Expand Up @@ -414,6 +417,7 @@ class SearchTypeDataExtensionsTest {
isAutofill = false,
hasMasterPassword = true,
isPremiumUser = true,
isTotp = true,
)

assertEquals(
Expand Down
Loading

0 comments on commit c5a266d

Please sign in to comment.