[PM-9844] Android - Non-Premium Users Can Copy TOTP Code From Item Menu (#3539)

This commit is contained in:
Dave Severns 2024-07-17 14:06:18 -04:00 committed by GitHub
parent f1c486bf9a
commit 3d584c84f2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 120 additions and 36 deletions

View file

@ -101,6 +101,7 @@ class SearchViewModel @Inject constructor(
isIconLoadingDisabled = settingsRepo.isIconLoadingDisabled, isIconLoadingDisabled = settingsRepo.isIconLoadingDisabled,
autofillSelectionData = autofillSelectionData, autofillSelectionData = autofillSelectionData,
hasMasterPassword = userState.activeAccount.hasMasterPassword, hasMasterPassword = userState.activeAccount.hasMasterPassword,
isPremium = userState.activeAccount.isPremium,
) )
}, },
) { ) {
@ -657,6 +658,7 @@ class SearchViewModel @Inject constructor(
baseIconUrl = state.baseIconUrl, baseIconUrl = state.baseIconUrl,
isIconLoadingDisabled = state.isIconLoadingDisabled, isIconLoadingDisabled = state.isIconLoadingDisabled,
isAutofill = state.isAutofill, isAutofill = state.isAutofill,
isPremiumUser = state.isPremium,
) )
} }
@ -701,6 +703,7 @@ data class SearchState(
// Internal // Internal
val autofillSelectionData: AutofillSelectionData? = null, val autofillSelectionData: AutofillSelectionData? = null,
val hasMasterPassword: Boolean, val hasMasterPassword: Boolean,
val isPremium: Boolean,
) : Parcelable { ) : Parcelable {
/** /**

View file

@ -140,12 +140,14 @@ private fun CipherView.matchedSearch(searchTerm: String): SortPriority? {
/** /**
* Transforms a list of [CipherView] into [SearchState.ViewState]. * Transforms a list of [CipherView] into [SearchState.ViewState].
*/ */
@Suppress("LongParameterList")
fun List<CipherView>.toViewState( fun List<CipherView>.toViewState(
searchTerm: String, searchTerm: String,
baseIconUrl: String, baseIconUrl: String,
hasMasterPassword: Boolean, hasMasterPassword: Boolean,
isIconLoadingDisabled: Boolean, isIconLoadingDisabled: Boolean,
isAutofill: Boolean, isAutofill: Boolean,
isPremiumUser: Boolean,
): SearchState.ViewState = ): SearchState.ViewState =
when { when {
searchTerm.isEmpty() -> SearchState.ViewState.Empty(message = null) searchTerm.isEmpty() -> SearchState.ViewState.Empty(message = null)
@ -156,6 +158,7 @@ fun List<CipherView>.toViewState(
hasMasterPassword = hasMasterPassword, hasMasterPassword = hasMasterPassword,
isIconLoadingDisabled = isIconLoadingDisabled, isIconLoadingDisabled = isIconLoadingDisabled,
isAutofill = isAutofill, isAutofill = isAutofill,
isPremiumUser = isPremiumUser,
), ),
) )
} }
@ -172,6 +175,7 @@ private fun List<CipherView>.toDisplayItemList(
hasMasterPassword: Boolean, hasMasterPassword: Boolean,
isIconLoadingDisabled: Boolean, isIconLoadingDisabled: Boolean,
isAutofill: Boolean, isAutofill: Boolean,
isPremiumUser: Boolean,
): List<SearchState.DisplayItem> = ): List<SearchState.DisplayItem> =
this.map { this.map {
it.toDisplayItem( it.toDisplayItem(
@ -179,6 +183,7 @@ private fun List<CipherView>.toDisplayItemList(
hasMasterPassword = hasMasterPassword, hasMasterPassword = hasMasterPassword,
isIconLoadingDisabled = isIconLoadingDisabled, isIconLoadingDisabled = isIconLoadingDisabled,
isAutofill = isAutofill, isAutofill = isAutofill,
isPremiumUser = isPremiumUser,
) )
} }
@ -187,6 +192,7 @@ private fun CipherView.toDisplayItem(
hasMasterPassword: Boolean, hasMasterPassword: Boolean,
isIconLoadingDisabled: Boolean, isIconLoadingDisabled: Boolean,
isAutofill: Boolean, isAutofill: Boolean,
isPremiumUser: Boolean,
): SearchState.DisplayItem = ): SearchState.DisplayItem =
SearchState.DisplayItem( SearchState.DisplayItem(
id = id.orEmpty(), id = id.orEmpty(),
@ -199,7 +205,10 @@ private fun CipherView.toDisplayItem(
isIconLoadingDisabled = isIconLoadingDisabled, isIconLoadingDisabled = isIconLoadingDisabled,
), ),
extraIconList = toLabelIcons(), extraIconList = toLabelIcons(),
overflowOptions = toOverflowActions(hasMasterPassword = hasMasterPassword), overflowOptions = toOverflowActions(
hasMasterPassword = hasMasterPassword,
isPremiumUser = isPremiumUser,
),
overflowTestTag = "CipherOptionsButton", overflowTestTag = "CipherOptionsButton",
totpCode = login?.totp, totpCode = login?.totp,
autofillSelectionOptions = AutofillSelectionOption autofillSelectionOptions = AutofillSelectionOption

View file

@ -123,6 +123,7 @@ class VaultItemListingViewModel @Inject constructor(
shouldFinishOnComplete = shouldFinishOnComplete, shouldFinishOnComplete = shouldFinishOnComplete,
hasMasterPassword = userState.activeAccount.hasMasterPassword, hasMasterPassword = userState.activeAccount.hasMasterPassword,
fido2CredentialRequest = fido2CreationData?.fido2CredentialRequest, fido2CredentialRequest = fido2CreationData?.fido2CredentialRequest,
isPremium = userState.activeAccount.isPremium,
) )
}, },
) { ) {
@ -1008,6 +1009,7 @@ class VaultItemListingViewModel @Inject constructor(
fido2CreationData = state.fido2CredentialRequest, fido2CreationData = state.fido2CredentialRequest,
fido2CredentialAutofillViews = vaultData fido2CredentialAutofillViews = vaultData
.fido2CredentialAutofillViewList, .fido2CredentialAutofillViewList,
isPremiumUser = state.isPremium,
) )
} }
@ -1127,6 +1129,7 @@ data class VaultItemListingState(
val fido2CredentialRequest: Fido2CredentialRequest? = null, val fido2CredentialRequest: Fido2CredentialRequest? = null,
val shouldFinishOnComplete: Boolean = false, val shouldFinishOnComplete: Boolean = false,
val hasMasterPassword: Boolean, val hasMasterPassword: Boolean,
val isPremium: Boolean,
) { ) {
/** /**
* Whether or not this represents a listing screen for autofill. * Whether or not this represents a listing screen for autofill.

View file

@ -104,6 +104,7 @@ fun VaultData.toViewState(
autofillSelectionData: AutofillSelectionData?, autofillSelectionData: AutofillSelectionData?,
fido2CreationData: Fido2CredentialRequest?, fido2CreationData: Fido2CredentialRequest?,
fido2CredentialAutofillViews: List<Fido2CredentialAutofillView>?, fido2CredentialAutofillViews: List<Fido2CredentialAutofillView>?,
isPremiumUser: Boolean,
): VaultItemListingState.ViewState { ): VaultItemListingState.ViewState {
val filteredCipherViewList = cipherViewList val filteredCipherViewList = cipherViewList
.filter { cipherView -> .filter { cipherView ->
@ -133,6 +134,7 @@ fun VaultData.toViewState(
isAutofill = autofillSelectionData != null, isAutofill = autofillSelectionData != null,
isFido2Creation = fido2CreationData != null, isFido2Creation = fido2CreationData != null,
fido2CredentialAutofillViews = fido2CredentialAutofillViews, fido2CredentialAutofillViews = fido2CredentialAutofillViews,
isPremiumUser = isPremiumUser,
), ),
displayFolderList = folderList.map { folderView -> displayFolderList = folderList.map { folderView ->
VaultItemListingState.FolderDisplayItem( VaultItemListingState.FolderDisplayItem(
@ -271,6 +273,7 @@ private fun List<CipherView>.toDisplayItemList(
isAutofill: Boolean, isAutofill: Boolean,
isFido2Creation: Boolean, isFido2Creation: Boolean,
fido2CredentialAutofillViews: List<Fido2CredentialAutofillView>?, fido2CredentialAutofillViews: List<Fido2CredentialAutofillView>?,
isPremiumUser: Boolean,
): List<VaultItemListingState.DisplayItem> = ): List<VaultItemListingState.DisplayItem> =
this.map { this.map {
it.toDisplayItem( it.toDisplayItem(
@ -283,6 +286,7 @@ private fun List<CipherView>.toDisplayItemList(
?.firstOrNull { fido2CredentialAutofillView -> ?.firstOrNull { fido2CredentialAutofillView ->
fido2CredentialAutofillView.cipherId == it.id fido2CredentialAutofillView.cipherId == it.id
}, },
isPremiumUser = isPremiumUser,
) )
} }
@ -305,6 +309,7 @@ private fun CipherView.toDisplayItem(
isAutofill: Boolean, isAutofill: Boolean,
isFido2Creation: Boolean, isFido2Creation: Boolean,
fido2CredentialAutofillView: Fido2CredentialAutofillView?, fido2CredentialAutofillView: Fido2CredentialAutofillView?,
isPremiumUser: Boolean,
): VaultItemListingState.DisplayItem = ): VaultItemListingState.DisplayItem =
VaultItemListingState.DisplayItem( VaultItemListingState.DisplayItem(
id = id.orEmpty(), id = id.orEmpty(),
@ -325,7 +330,10 @@ private fun CipherView.toDisplayItem(
), ),
iconTestTag = this.toIconTestTag(), iconTestTag = this.toIconTestTag(),
extraIconList = this.toLabelIcons(), extraIconList = this.toLabelIcons(),
overflowOptions = this.toOverflowActions(hasMasterPassword = hasMasterPassword), overflowOptions = this.toOverflowActions(
hasMasterPassword = hasMasterPassword,
isPremiumUser = isPremiumUser,
),
optionsTestTag = "CipherOptionsButton", optionsTestTag = "CipherOptionsButton",
isAutofill = isAutofill, isAutofill = isAutofill,
isFido2Creation = isFido2Creation, isFido2Creation = isFido2Creation,

View file

@ -11,6 +11,7 @@ import com.x8bit.bitwarden.ui.vault.model.VaultTrailingIcon
*/ */
fun CipherView.toOverflowActions( fun CipherView.toOverflowActions(
hasMasterPassword: Boolean, hasMasterPassword: Boolean,
isPremiumUser: Boolean,
): List<ListingItemOverflowAction.VaultAction> = ): List<ListingItemOverflowAction.VaultAction> =
this this
.id .id
@ -36,7 +37,10 @@ fun CipherView.toOverflowActions(
.takeIf { this.viewPassword }, .takeIf { this.viewPassword },
this.login?.totp this.login?.totp
?.let { ListingItemOverflowAction.VaultAction.CopyTotpClick(totpCode = it) } ?.let { ListingItemOverflowAction.VaultAction.CopyTotpClick(totpCode = it) }
.takeIf { this.type == CipherType.LOGIN }, .takeIf {
this.type == CipherType.LOGIN &&
(this.organizationUseTotp || isPremiumUser)
},
this.card?.number?.let { this.card?.number?.let {
ListingItemOverflowAction.VaultAction.CopyNumberClick( ListingItemOverflowAction.VaultAction.CopyNumberClick(
number = it, number = it,

View file

@ -83,6 +83,7 @@ fun VaultData.toViewState(
hasMasterPassword = hasMasterPassword, hasMasterPassword = hasMasterPassword,
isIconLoadingDisabled = isIconLoadingDisabled, isIconLoadingDisabled = isIconLoadingDisabled,
baseIconUrl = baseIconUrl, baseIconUrl = baseIconUrl,
isPremiumUser = isPremium,
) )
}, },
folderItems = filteredFolderViewList folderItems = filteredFolderViewList
@ -116,6 +117,7 @@ fun VaultData.toViewState(
hasMasterPassword = hasMasterPassword, hasMasterPassword = hasMasterPassword,
isIconLoadingDisabled = isIconLoadingDisabled, isIconLoadingDisabled = isIconLoadingDisabled,
baseIconUrl = baseIconUrl, baseIconUrl = baseIconUrl,
isPremiumUser = isPremium,
) )
} }
.takeIf { it.size < NO_FOLDER_ITEM_THRESHOLD } .takeIf { it.size < NO_FOLDER_ITEM_THRESHOLD }
@ -189,11 +191,12 @@ fun List<LoginUriView>?.toLoginIconData(
/** /**
* Transforms a [CipherView] into a [VaultState.ViewState.VaultItem]. * Transforms a [CipherView] into a [VaultState.ViewState.VaultItem].
*/ */
@Suppress("MagicNumber") @Suppress("MagicNumber", "LongMethod")
private fun CipherView.toVaultItemOrNull( private fun CipherView.toVaultItemOrNull(
hasMasterPassword: Boolean, hasMasterPassword: Boolean,
isIconLoadingDisabled: Boolean, isIconLoadingDisabled: Boolean,
baseIconUrl: String, baseIconUrl: String,
isPremiumUser: Boolean,
): VaultState.ViewState.VaultItem? { ): VaultState.ViewState.VaultItem? {
val id = this.id ?: return null val id = this.id ?: return null
return when (type) { return when (type) {
@ -206,7 +209,10 @@ private fun CipherView.toVaultItemOrNull(
baseIconUrl = baseIconUrl, baseIconUrl = baseIconUrl,
usePasskeyDefaultIcon = false, usePasskeyDefaultIcon = false,
), ),
overflowOptions = toOverflowActions(hasMasterPassword = hasMasterPassword), overflowOptions = toOverflowActions(
hasMasterPassword = hasMasterPassword,
isPremiumUser = isPremiumUser,
),
extraIconList = toLabelIcons(), extraIconList = toLabelIcons(),
shouldShowMasterPasswordReprompt = reprompt == CipherRepromptType.PASSWORD, shouldShowMasterPasswordReprompt = reprompt == CipherRepromptType.PASSWORD,
) )
@ -214,7 +220,10 @@ private fun CipherView.toVaultItemOrNull(
CipherType.SECURE_NOTE -> VaultState.ViewState.VaultItem.SecureNote( CipherType.SECURE_NOTE -> VaultState.ViewState.VaultItem.SecureNote(
id = id, id = id,
name = name.asText(), name = name.asText(),
overflowOptions = toOverflowActions(hasMasterPassword = hasMasterPassword), overflowOptions = toOverflowActions(
hasMasterPassword = hasMasterPassword,
isPremiumUser = isPremiumUser,
),
extraIconList = toLabelIcons(), extraIconList = toLabelIcons(),
shouldShowMasterPasswordReprompt = reprompt == CipherRepromptType.PASSWORD, shouldShowMasterPasswordReprompt = reprompt == CipherRepromptType.PASSWORD,
) )
@ -226,7 +235,10 @@ private fun CipherView.toVaultItemOrNull(
lastFourDigits = card?.number lastFourDigits = card?.number
?.takeLast(4) ?.takeLast(4)
?.asText(), ?.asText(),
overflowOptions = toOverflowActions(hasMasterPassword = hasMasterPassword), overflowOptions = toOverflowActions(
hasMasterPassword = hasMasterPassword,
isPremiumUser = isPremiumUser,
),
extraIconList = toLabelIcons(), extraIconList = toLabelIcons(),
shouldShowMasterPasswordReprompt = reprompt == CipherRepromptType.PASSWORD, shouldShowMasterPasswordReprompt = reprompt == CipherRepromptType.PASSWORD,
) )
@ -240,7 +252,10 @@ private fun CipherView.toVaultItemOrNull(
else -> "${identity?.firstName} ${identity?.lastName}" else -> "${identity?.firstName} ${identity?.lastName}"
} }
?.asText(), ?.asText(),
overflowOptions = toOverflowActions(hasMasterPassword = hasMasterPassword), overflowOptions = toOverflowActions(
hasMasterPassword = hasMasterPassword,
isPremiumUser = isPremiumUser,
),
extraIconList = toLabelIcons(), extraIconList = toLabelIcons(),
shouldShowMasterPasswordReprompt = reprompt == CipherRepromptType.PASSWORD, shouldShowMasterPasswordReprompt = reprompt == CipherRepromptType.PASSWORD,
) )

View file

@ -887,6 +887,7 @@ private val DEFAULT_STATE: SearchState = SearchState(
baseIconUrl = "www.test.com", baseIconUrl = "www.test.com",
isIconLoadingDisabled = false, isIconLoadingDisabled = false,
hasMasterPassword = true, hasMasterPassword = true,
isPremium = true,
) )
private fun createStateForAutofill( private fun createStateForAutofill(

View file

@ -861,6 +861,7 @@ class SearchViewModelTest : BaseViewModelTest() {
isIconLoadingDisabled = false, isIconLoadingDisabled = false,
isAutofill = false, isAutofill = false,
hasMasterPassword = true, hasMasterPassword = true,
isPremiumUser = true,
) )
} returns expectedViewState } returns expectedViewState
val dataState = DataState.Loaded( val dataState = DataState.Loaded(
@ -962,6 +963,7 @@ class SearchViewModelTest : BaseViewModelTest() {
isIconLoadingDisabled = false, isIconLoadingDisabled = false,
isAutofill = false, isAutofill = false,
hasMasterPassword = true, hasMasterPassword = true,
isPremiumUser = true,
) )
} returns expectedViewState } returns expectedViewState
mutableVaultDataStateFlow.tryEmit( mutableVaultDataStateFlow.tryEmit(
@ -1073,6 +1075,7 @@ class SearchViewModelTest : BaseViewModelTest() {
isIconLoadingDisabled = false, isIconLoadingDisabled = false,
isAutofill = false, isAutofill = false,
hasMasterPassword = true, hasMasterPassword = true,
isPremiumUser = true,
) )
} returns expectedViewState } returns expectedViewState
val dataState = DataState.Error( val dataState = DataState.Error(
@ -1187,6 +1190,7 @@ class SearchViewModelTest : BaseViewModelTest() {
isIconLoadingDisabled = false, isIconLoadingDisabled = false,
isAutofill = false, isAutofill = false,
hasMasterPassword = true, hasMasterPassword = true,
isPremiumUser = true,
) )
} returns expectedViewState } returns expectedViewState
val dataState = DataState.NoNetwork( val dataState = DataState.NoNetwork(
@ -1355,6 +1359,7 @@ class SearchViewModelTest : BaseViewModelTest() {
isIconLoadingDisabled = false, isIconLoadingDisabled = false,
isAutofill = true, isAutofill = true,
hasMasterPassword = true, hasMasterPassword = true,
isPremiumUser = true,
) )
} returns expectedViewState } returns expectedViewState
val dataState = DataState.Loaded( val dataState = DataState.Loaded(
@ -1387,6 +1392,7 @@ private val DEFAULT_STATE: SearchState = SearchState(
baseIconUrl = "https://vault.bitwarden.com/icons", baseIconUrl = "https://vault.bitwarden.com/icons",
isIconLoadingDisabled = false, isIconLoadingDisabled = false,
hasMasterPassword = true, hasMasterPassword = true,
isPremium = true,
) )
private val DEFAULT_USER_STATE = UserState( private val DEFAULT_USER_STATE = UserState(

View file

@ -298,6 +298,7 @@ class SearchTypeDataExtensionsTest {
isIconLoadingDisabled = false, isIconLoadingDisabled = false,
isAutofill = false, isAutofill = false,
hasMasterPassword = true, hasMasterPassword = true,
isPremiumUser = true,
) )
assertEquals(SearchState.ViewState.Empty(message = null), result) assertEquals(SearchState.ViewState.Empty(message = null), result)
@ -322,6 +323,7 @@ class SearchTypeDataExtensionsTest {
isIconLoadingDisabled = false, isIconLoadingDisabled = false,
isAutofill = false, isAutofill = false,
hasMasterPassword = true, hasMasterPassword = true,
isPremiumUser = true,
) )
assertEquals( assertEquals(
@ -361,6 +363,7 @@ class SearchTypeDataExtensionsTest {
isIconLoadingDisabled = false, isIconLoadingDisabled = false,
isAutofill = true, isAutofill = true,
hasMasterPassword = true, hasMasterPassword = true,
isPremiumUser = true,
) )
assertEquals( assertEquals(
@ -410,6 +413,7 @@ class SearchTypeDataExtensionsTest {
isIconLoadingDisabled = false, isIconLoadingDisabled = false,
isAutofill = false, isAutofill = false,
hasMasterPassword = true, hasMasterPassword = true,
isPremiumUser = true,
) )
assertEquals( assertEquals(

View file

@ -1713,6 +1713,7 @@ private val DEFAULT_STATE = VaultItemListingState(
dialogState = null, dialogState = null,
policyDisablesSend = false, policyDisablesSend = false,
hasMasterPassword = true, hasMasterPassword = true,
isPremium = false,
) )
private val STATE_FOR_AUTOFILL = DEFAULT_STATE.copy( private val STATE_FOR_AUTOFILL = DEFAULT_STATE.copy(

View file

@ -2374,6 +2374,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
policyDisablesSend = false, policyDisablesSend = false,
hasMasterPassword = true, hasMasterPassword = true,
fido2CredentialRequest = null, fido2CredentialRequest = null,
isPremium = true,
) )
} }

View file

@ -400,6 +400,7 @@ class VaultItemListingDataExtensionsTest {
fido2CreationData = null, fido2CreationData = null,
hasMasterPassword = true, hasMasterPassword = true,
fido2CredentialAutofillViews = null, fido2CredentialAutofillViews = null,
isPremiumUser = true,
) )
assertEquals( assertEquals(
@ -488,6 +489,7 @@ class VaultItemListingDataExtensionsTest {
fido2CreationData = null, fido2CreationData = null,
hasMasterPassword = true, hasMasterPassword = true,
fido2CredentialAutofillViews = fido2CredentialAutofillViews, fido2CredentialAutofillViews = fido2CredentialAutofillViews,
isPremiumUser = true,
) )
assertEquals( assertEquals(
@ -553,6 +555,7 @@ class VaultItemListingDataExtensionsTest {
fido2CreationData = null, fido2CreationData = null,
hasMasterPassword = true, hasMasterPassword = true,
fido2CredentialAutofillViews = null, fido2CredentialAutofillViews = null,
isPremiumUser = true,
), ),
) )
@ -574,6 +577,7 @@ class VaultItemListingDataExtensionsTest {
fido2CreationData = null, fido2CreationData = null,
hasMasterPassword = true, hasMasterPassword = true,
fido2CredentialAutofillViews = null, fido2CredentialAutofillViews = null,
isPremiumUser = true,
), ),
) )
@ -593,6 +597,7 @@ class VaultItemListingDataExtensionsTest {
fido2CreationData = null, fido2CreationData = null,
hasMasterPassword = true, hasMasterPassword = true,
fido2CredentialAutofillViews = null, fido2CredentialAutofillViews = null,
isPremiumUser = true,
), ),
) )
@ -615,6 +620,7 @@ class VaultItemListingDataExtensionsTest {
fido2CreationData = null, fido2CreationData = null,
hasMasterPassword = true, hasMasterPassword = true,
fido2CredentialAutofillViews = null, fido2CredentialAutofillViews = null,
isPremiumUser = true,
), ),
) )
@ -640,6 +646,7 @@ class VaultItemListingDataExtensionsTest {
), ),
hasMasterPassword = true, hasMasterPassword = true,
fido2CredentialAutofillViews = null, fido2CredentialAutofillViews = null,
isPremiumUser = true,
), ),
) )
} }
@ -781,6 +788,7 @@ class VaultItemListingDataExtensionsTest {
fido2CreationData = null, fido2CreationData = null,
hasMasterPassword = true, hasMasterPassword = true,
fido2CredentialAutofillViews = null, fido2CredentialAutofillViews = null,
isPremiumUser = true,
) )
assertEquals( assertEquals(
@ -823,6 +831,7 @@ class VaultItemListingDataExtensionsTest {
fido2CreationData = null, fido2CreationData = null,
hasMasterPassword = true, hasMasterPassword = true,
fido2CredentialAutofillViews = null, fido2CredentialAutofillViews = null,
isPremiumUser = true,
) )
assertEquals( assertEquals(

View file

@ -16,12 +16,7 @@ import org.junit.jupiter.api.Test
class CipherViewExtensionsTest { class CipherViewExtensionsTest {
@Test @Test
fun `toOverflowActions should return all actions for a login cipher`() { fun `toOverflowActions should return all actions for a login cipher when a user has premium`() {
val id = "mockId-1"
val username = "Bitwarden"
val password = "password"
val totpCode = "mockTotp-1"
val uri = "www.test.com"
val cipher = createMockCipherView(number = 1, cipherType = CipherType.LOGIN).copy( val cipher = createMockCipherView(number = 1, cipherType = CipherType.LOGIN).copy(
id = id, id = id,
login = createMockLoginView(number = 1).copy( login = createMockLoginView(number = 1).copy(
@ -31,7 +26,7 @@ class CipherViewExtensionsTest {
), ),
) )
val result = cipher.toOverflowActions(hasMasterPassword = false) val result = cipher.toOverflowActions(hasMasterPassword = false, isPremiumUser = true)
assertEquals( assertEquals(
listOf( listOf(
@ -53,14 +48,41 @@ class CipherViewExtensionsTest {
) )
} }
@Test
fun `toOverflowActions should not return TOTP action when a user does not have premium`() {
val cipher = createMockCipherView(number = 1, cipherType = CipherType.LOGIN).copy(
id = id,
login = createMockLoginView(number = 1).copy(
username = username,
password = password,
uris = listOf(createMockUriView(number = 1).copy(uri = uri)),
),
)
val result = cipher.toOverflowActions(hasMasterPassword = false, isPremiumUser = false)
assertEquals(
listOf(
ListingItemOverflowAction.VaultAction.ViewClick(cipherId = id),
ListingItemOverflowAction.VaultAction.EditClick(
cipherId = id,
requiresPasswordReprompt = false,
),
ListingItemOverflowAction.VaultAction.CopyUsernameClick(username = username),
ListingItemOverflowAction.VaultAction.CopyPasswordClick(
password = password,
requiresPasswordReprompt = false,
cipherId = id,
),
ListingItemOverflowAction.VaultAction.LaunchClick(url = uri),
),
result,
)
}
@Suppress("MaxLineLength") @Suppress("MaxLineLength")
@Test @Test
fun `toOverflowActions should return the correct actions when viewPassword is false for a login cipher`() { fun `toOverflowActions should return the correct actions when viewPassword is false for a login cipher`() {
val id = "mockId-1"
val username = "Bitwarden"
val password = "password"
val totpCode = "mockTotp-1"
val uri = "www.test.com"
val cipher = createMockCipherView(number = 1, cipherType = CipherType.LOGIN).copy( val cipher = createMockCipherView(number = 1, cipherType = CipherType.LOGIN).copy(
id = id, id = id,
login = createMockLoginView(number = 1).copy( login = createMockLoginView(number = 1).copy(
@ -70,7 +92,7 @@ class CipherViewExtensionsTest {
), ),
viewPassword = false, viewPassword = false,
) )
val result = cipher.toOverflowActions(hasMasterPassword = true) val result = cipher.toOverflowActions(hasMasterPassword = true, isPremiumUser = false)
assertEquals( assertEquals(
listOf( listOf(
@ -80,7 +102,6 @@ class CipherViewExtensionsTest {
requiresPasswordReprompt = true, requiresPasswordReprompt = true,
), ),
ListingItemOverflowAction.VaultAction.CopyUsernameClick(username = username), ListingItemOverflowAction.VaultAction.CopyUsernameClick(username = username),
ListingItemOverflowAction.VaultAction.CopyTotpClick(totpCode = totpCode),
ListingItemOverflowAction.VaultAction.LaunchClick(url = uri), ListingItemOverflowAction.VaultAction.LaunchClick(url = uri),
), ),
result, result,
@ -89,7 +110,6 @@ class CipherViewExtensionsTest {
@Test @Test
fun `toOverflowActions should return minimum actions for a login cipher`() { fun `toOverflowActions should return minimum actions for a login cipher`() {
val id = "mockId-1"
val cipher = createMockCipherView( val cipher = createMockCipherView(
number = 1, number = 1,
isDeleted = true, isDeleted = true,
@ -105,7 +125,7 @@ class CipherViewExtensionsTest {
), ),
) )
val result = cipher.toOverflowActions(hasMasterPassword = true) val result = cipher.toOverflowActions(hasMasterPassword = true, isPremiumUser = false)
assertEquals( assertEquals(
listOf(ListingItemOverflowAction.VaultAction.ViewClick(cipherId = id)), listOf(ListingItemOverflowAction.VaultAction.ViewClick(cipherId = id)),
@ -115,7 +135,6 @@ class CipherViewExtensionsTest {
@Test @Test
fun `toOverflowActions should return all actions for a card cipher`() { fun `toOverflowActions should return all actions for a card cipher`() {
val id = "mockId-1"
val number = "1322-2414-7634-2354" val number = "1322-2414-7634-2354"
val securityCode = "123" val securityCode = "123"
val cipher = createMockCipherView(number = 1, cipherType = CipherType.CARD).copy( val cipher = createMockCipherView(number = 1, cipherType = CipherType.CARD).copy(
@ -126,7 +145,7 @@ class CipherViewExtensionsTest {
), ),
) )
val result = cipher.toOverflowActions(hasMasterPassword = true) val result = cipher.toOverflowActions(hasMasterPassword = true, isPremiumUser = false)
assertEquals( assertEquals(
listOf( listOf(
@ -151,7 +170,6 @@ class CipherViewExtensionsTest {
@Test @Test
fun `toOverflowActions should return minimum actions for a card cipher`() { fun `toOverflowActions should return minimum actions for a card cipher`() {
val id = "mockId-1"
val cipher = createMockCipherView( val cipher = createMockCipherView(
number = 1, number = 1,
isDeleted = true, isDeleted = true,
@ -165,7 +183,7 @@ class CipherViewExtensionsTest {
), ),
) )
val result = cipher.toOverflowActions(hasMasterPassword = false) val result = cipher.toOverflowActions(hasMasterPassword = false, isPremiumUser = false)
assertEquals( assertEquals(
listOf(ListingItemOverflowAction.VaultAction.ViewClick(cipherId = id)), listOf(ListingItemOverflowAction.VaultAction.ViewClick(cipherId = id)),
@ -175,13 +193,12 @@ class CipherViewExtensionsTest {
@Test @Test
fun `toOverflowActions should return all actions for a identity cipher`() { fun `toOverflowActions should return all actions for a identity cipher`() {
val id = "mockId-1"
val cipher = createMockCipherView(number = 1, cipherType = CipherType.IDENTITY).copy( val cipher = createMockCipherView(number = 1, cipherType = CipherType.IDENTITY).copy(
id = id, id = id,
identity = createMockIdentityView(number = 1), identity = createMockIdentityView(number = 1),
) )
val result = cipher.toOverflowActions(hasMasterPassword = false) val result = cipher.toOverflowActions(hasMasterPassword = false, isPremiumUser = false)
assertEquals( assertEquals(
listOf( listOf(
@ -197,7 +214,6 @@ class CipherViewExtensionsTest {
@Test @Test
fun `toOverflowActions should return minimum actions for a identity cipher`() { fun `toOverflowActions should return minimum actions for a identity cipher`() {
val id = "mockId-1"
val cipher = createMockCipherView( val cipher = createMockCipherView(
number = 1, number = 1,
isDeleted = true, isDeleted = true,
@ -208,7 +224,7 @@ class CipherViewExtensionsTest {
identity = createMockIdentityView(number = 1), identity = createMockIdentityView(number = 1),
) )
val result = cipher.toOverflowActions(hasMasterPassword = true) val result = cipher.toOverflowActions(hasMasterPassword = true, false)
assertEquals( assertEquals(
listOf(ListingItemOverflowAction.VaultAction.ViewClick(cipherId = id)), listOf(ListingItemOverflowAction.VaultAction.ViewClick(cipherId = id)),
@ -218,7 +234,6 @@ class CipherViewExtensionsTest {
@Test @Test
fun `toOverflowActions should return all actions for a secure note cipher`() { fun `toOverflowActions should return all actions for a secure note cipher`() {
val id = "mockId-1"
val notes = "so secure" val notes = "so secure"
val cipher = createMockCipherView(number = 1, cipherType = CipherType.SECURE_NOTE).copy( val cipher = createMockCipherView(number = 1, cipherType = CipherType.SECURE_NOTE).copy(
id = id, id = id,
@ -226,7 +241,7 @@ class CipherViewExtensionsTest {
notes = notes, notes = notes,
) )
val result = cipher.toOverflowActions(hasMasterPassword = true) val result = cipher.toOverflowActions(hasMasterPassword = true, isPremiumUser = false)
assertEquals( assertEquals(
listOf( listOf(
@ -243,7 +258,6 @@ class CipherViewExtensionsTest {
@Test @Test
fun `toOverflowActions should return minimum actions for a secure note cipher`() { fun `toOverflowActions should return minimum actions for a secure note cipher`() {
val id = "mockId-1"
val cipher = createMockCipherView( val cipher = createMockCipherView(
number = 1, number = 1,
isDeleted = true, isDeleted = true,
@ -255,7 +269,7 @@ class CipherViewExtensionsTest {
notes = null, notes = null,
) )
val result = cipher.toOverflowActions(hasMasterPassword = false) val result = cipher.toOverflowActions(hasMasterPassword = false, isPremiumUser = false)
assertEquals( assertEquals(
listOf(ListingItemOverflowAction.VaultAction.ViewClick(cipherId = id)), listOf(ListingItemOverflowAction.VaultAction.ViewClick(cipherId = id)),
@ -358,3 +372,9 @@ class CipherViewExtensionsTest {
assertEquals(expected, result) assertEquals(expected, result)
} }
} }
private const val id = "mockId-1"
private const val username = "Bitwarden"
private const val password = "password"
private const val totpCode = "mockTotp-1"
private const val uri = "www.test.com"