mirror of
https://github.com/bitwarden/android.git
synced 2025-03-16 03:08:50 +03:00
BIT-2342: Hide verification codes for items with password reprompt (#1353)
This commit is contained in:
parent
179c5199e7
commit
903aa26876
10 changed files with 130 additions and 29 deletions
|
@ -1,5 +1,6 @@
|
|||
package com.x8bit.bitwarden.data.vault.manager
|
||||
|
||||
import com.bitwarden.core.CipherRepromptType
|
||||
import com.bitwarden.core.CipherView
|
||||
import com.bitwarden.core.DateTime
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
|
@ -111,6 +112,10 @@ class TotpCodeManagerImpl(
|
|||
id = cipherId,
|
||||
name = cipher.name,
|
||||
username = cipher.login?.username,
|
||||
hasPasswordReprompt = when (cipher.reprompt) {
|
||||
CipherRepromptType.PASSWORD -> true
|
||||
CipherRepromptType.NONE -> false
|
||||
},
|
||||
)
|
||||
}
|
||||
.onFailure {
|
||||
|
|
|
@ -14,6 +14,7 @@ import com.bitwarden.core.LoginUriView
|
|||
* @property id The cipher id of the item.
|
||||
* @property name The name of the cipher item.
|
||||
* @property username The username associated with the item.
|
||||
* @property hasPasswordReprompt Indicates whether this item has a master password reprompt.
|
||||
*/
|
||||
data class VerificationCodeItem(
|
||||
val code: String,
|
||||
|
@ -25,4 +26,5 @@ data class VerificationCodeItem(
|
|||
val id: String,
|
||||
val name: String,
|
||||
val username: String?,
|
||||
val hasPasswordReprompt: Boolean,
|
||||
)
|
||||
|
|
|
@ -32,6 +32,7 @@ import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
|||
* The verification code item displayed to the user.
|
||||
*
|
||||
* @param authCode The code for the item.
|
||||
* @param hideAuthCode Indicates whether the auth / verification code should be hidden.
|
||||
* @param label The label for the item.
|
||||
* @param periodSeconds The times span where the code is valid.
|
||||
* @param timeLeftSeconds The seconds remaining until a new code is needed.
|
||||
|
@ -45,6 +46,7 @@ import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
|||
@Composable
|
||||
fun VaultVerificationCodeItem(
|
||||
authCode: String,
|
||||
hideAuthCode: Boolean,
|
||||
label: String,
|
||||
periodSeconds: Int,
|
||||
timeLeftSeconds: Int,
|
||||
|
@ -103,21 +105,23 @@ fun VaultVerificationCodeItem(
|
|||
periodSeconds = periodSeconds,
|
||||
)
|
||||
|
||||
Text(
|
||||
text = authCode.chunked(3).joinToString(" "),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
|
||||
IconButton(
|
||||
onClick = onCopyClick,
|
||||
) {
|
||||
Icon(
|
||||
painter = rememberVectorPainter(id = R.drawable.ic_copy),
|
||||
contentDescription = stringResource(id = R.string.copy),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(24.dp),
|
||||
if (!hideAuthCode) {
|
||||
Text(
|
||||
text = authCode.chunked(3).joinToString(" "),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
|
||||
IconButton(
|
||||
onClick = onCopyClick,
|
||||
) {
|
||||
Icon(
|
||||
painter = rememberVectorPainter(id = R.drawable.ic_copy),
|
||||
contentDescription = stringResource(id = R.string.copy),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(24.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -132,6 +136,7 @@ private fun VerificationCodeItem_preview() {
|
|||
label = "Sample Label",
|
||||
supportingLabel = "Supporting Label",
|
||||
authCode = "1234567890".chunked(3).joinToString(" "),
|
||||
hideAuthCode = false,
|
||||
timeLeftSeconds = 15,
|
||||
periodSeconds = 30,
|
||||
onCopyClick = {},
|
||||
|
|
|
@ -179,6 +179,7 @@ private fun VerificationCodeContent(
|
|||
timeLeftSeconds = it.timeLeftSeconds,
|
||||
periodSeconds = it.periodSeconds,
|
||||
authCode = it.authCode,
|
||||
hideAuthCode = it.hideAuthCode,
|
||||
onCopyClick = { onCopyClick(it.authCode) },
|
||||
onItemClick = {
|
||||
itemClick(it.id)
|
||||
|
|
|
@ -277,6 +277,7 @@ class VerificationCodeViewModel @Inject constructor(
|
|||
VerificationCodeDisplayItem(
|
||||
id = item.id,
|
||||
authCode = item.code,
|
||||
hideAuthCode = item.hasPasswordReprompt,
|
||||
label = item.name,
|
||||
supportingLabel = item.username,
|
||||
periodSeconds = item.periodSeconds,
|
||||
|
@ -381,6 +382,7 @@ data class VerificationCodeDisplayItem(
|
|||
val timeLeftSeconds: Int,
|
||||
val periodSeconds: Int,
|
||||
val authCode: String,
|
||||
val hideAuthCode: Boolean,
|
||||
val startIcon: IconData = IconData.Local(R.drawable.ic_login_item),
|
||||
) : Parcelable
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ fun createMockCipherView(
|
|||
number: Int,
|
||||
isDeleted: Boolean = false,
|
||||
cipherType: CipherType = CipherType.LOGIN,
|
||||
repromptType: CipherRepromptType = CipherRepromptType.NONE,
|
||||
totp: String? = "mockTotp-$number",
|
||||
folderId: String? = "mockId-$number",
|
||||
clock: Clock = FIXED_CLOCK,
|
||||
|
@ -75,7 +76,7 @@ fun createMockCipherView(
|
|||
},
|
||||
favorite = false,
|
||||
passwordHistory = listOf(createMockPasswordHistoryView(number = number, clock)),
|
||||
reprompt = CipherRepromptType.NONE,
|
||||
reprompt = repromptType,
|
||||
secureNote = createMockSecureNoteView().takeIf { cipherType == CipherType.SECURE_NOTE },
|
||||
edit = false,
|
||||
organizationUseTotp = false,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.x8bit.bitwarden.data.vault.manager
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.core.CipherRepromptType
|
||||
import com.bitwarden.core.TotpResponse
|
||||
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
|
@ -94,16 +95,18 @@ class TotpCodeManagerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `getTotpCodeStateFlow should have loaded item with a valid data passed in`() = runTest {
|
||||
|
||||
fun `getTotpCodeStateFlow should have loaded item with valid data passed in`() = runTest {
|
||||
val totpResponse = TotpResponse("123456", 30u)
|
||||
coEvery {
|
||||
vaultSdkSource.generateTotp(any(), any(), any())
|
||||
} returns totpResponse.asSuccess()
|
||||
|
||||
val cipherView = createMockCipherView(1)
|
||||
val cipherView = createMockCipherView(
|
||||
number = 1,
|
||||
repromptType = CipherRepromptType.PASSWORD,
|
||||
)
|
||||
|
||||
val expected = createVerificationCodeItem()
|
||||
val expected = createVerificationCodeItem().copy(hasPasswordReprompt = true)
|
||||
|
||||
totpCodeManager.getTotpCodeStateFlow(userId, cipherView).test {
|
||||
assertEquals(DataState.Loaded(expected), awaitItem())
|
||||
|
|
|
@ -129,6 +129,49 @@ class VerificationCodeScreenTest : BaseComposeTest() {
|
|||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `auth code and copy button should be displayed according to state`() {
|
||||
val authCode = "123 456"
|
||||
mutableStateFlow.update {
|
||||
DEFAULT_STATE.copy(
|
||||
viewState = VerificationCodeState.ViewState.Content(
|
||||
verificationCodeDisplayItems = listOf(
|
||||
createDisplayItem(
|
||||
number = 1,
|
||||
hideAuthCode = false,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
composeTestRule
|
||||
.onNodeWithText(authCode)
|
||||
.assertIsDisplayed()
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription("Copy")
|
||||
.assertIsDisplayed()
|
||||
|
||||
mutableStateFlow.update {
|
||||
DEFAULT_STATE.copy(
|
||||
viewState = VerificationCodeState.ViewState.Content(
|
||||
verificationCodeDisplayItems = listOf(
|
||||
createDisplayItem(
|
||||
number = 1,
|
||||
hideAuthCode = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText(authCode)
|
||||
.assertIsNotDisplayed()
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription("Copy")
|
||||
.assertIsNotDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Items text should be displayed according to state`() {
|
||||
val items = "Items"
|
||||
|
@ -343,10 +386,14 @@ class VerificationCodeScreenTest : BaseComposeTest() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun createDisplayItem(number: Int): VerificationCodeDisplayItem =
|
||||
private fun createDisplayItem(
|
||||
number: Int,
|
||||
hideAuthCode: Boolean = false,
|
||||
): VerificationCodeDisplayItem =
|
||||
VerificationCodeDisplayItem(
|
||||
id = number.toString(),
|
||||
authCode = "123456",
|
||||
hideAuthCode = hideAuthCode,
|
||||
label = "Label $number",
|
||||
supportingLabel = "Supporting Label $number",
|
||||
periodSeconds = 30,
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.x8bit.bitwarden.ui.vault.feature.verificationcode
|
|||
|
||||
import android.net.Uri
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.core.CipherRepromptType
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
|
@ -163,7 +164,10 @@ class VerificationCodeViewModelTest : BaseViewModelTest() {
|
|||
|
||||
mutableAuthCodeFlow.tryEmit(
|
||||
value = DataState.Pending(
|
||||
data = listOf(createVerificationCodeItem()),
|
||||
data = listOf(
|
||||
createVerificationCodeItem(number = 1),
|
||||
createVerificationCodeItem(number = 2).copy(hasPasswordReprompt = true),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -205,7 +209,10 @@ class VerificationCodeViewModelTest : BaseViewModelTest() {
|
|||
|
||||
mutableAuthCodeFlow.tryEmit(
|
||||
value = DataState.Error(
|
||||
data = listOf(createVerificationCodeItem()),
|
||||
data = listOf(
|
||||
createVerificationCodeItem(number = 1),
|
||||
createVerificationCodeItem(number = 2).copy(hasPasswordReprompt = true),
|
||||
),
|
||||
error = IllegalStateException(),
|
||||
),
|
||||
)
|
||||
|
@ -317,7 +324,10 @@ class VerificationCodeViewModelTest : BaseViewModelTest() {
|
|||
|
||||
mutableAuthCodeFlow.tryEmit(
|
||||
value = DataState.NoNetwork(
|
||||
listOf(createVerificationCodeItem()),
|
||||
data = listOf(
|
||||
createVerificationCodeItem(number = 1),
|
||||
createVerificationCodeItem(number = 2).copy(hasPasswordReprompt = true),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -358,7 +368,10 @@ class VerificationCodeViewModelTest : BaseViewModelTest() {
|
|||
|
||||
mutableAuthCodeFlow.tryEmit(
|
||||
value = DataState.Loaded(
|
||||
listOf(createVerificationCodeItem()),
|
||||
data = listOf(
|
||||
createVerificationCodeItem(number = 1),
|
||||
createVerificationCodeItem(number = 2).copy(hasPasswordReprompt = true),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -461,6 +474,27 @@ class VerificationCodeViewModelTest : BaseViewModelTest() {
|
|||
VerificationCodeDisplayItem(
|
||||
id = cipherView.id.toString(),
|
||||
authCode = "123456",
|
||||
hideAuthCode = false,
|
||||
label = cipherView.name,
|
||||
supportingLabel = cipherView.login?.username,
|
||||
periodSeconds = 30,
|
||||
timeLeftSeconds = 30,
|
||||
startIcon = cipherView.login?.uris.toLoginIconData(
|
||||
isIconLoadingDisabled = initialState.isIconLoadingDisabled,
|
||||
baseIconUrl = initialState.baseIconUrl,
|
||||
),
|
||||
)
|
||||
},
|
||||
createMockCipherView(
|
||||
number = 2,
|
||||
isDeleted = false,
|
||||
repromptType = CipherRepromptType.PASSWORD,
|
||||
)
|
||||
.let { cipherView ->
|
||||
VerificationCodeDisplayItem(
|
||||
id = cipherView.id.toString(),
|
||||
authCode = "123456",
|
||||
hideAuthCode = true,
|
||||
label = cipherView.name,
|
||||
supportingLabel = cipherView.login?.username,
|
||||
periodSeconds = 30,
|
||||
|
|
|
@ -3,15 +3,16 @@ package com.x8bit.bitwarden.ui.vault.feature.verificationcode.util
|
|||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockLoginView
|
||||
import com.x8bit.bitwarden.data.vault.manager.model.VerificationCodeItem
|
||||
|
||||
fun createVerificationCodeItem() =
|
||||
fun createVerificationCodeItem(number: Int = 1) =
|
||||
VerificationCodeItem(
|
||||
code = "123456",
|
||||
totpCode = "mockTotp-1",
|
||||
totpCode = "mockTotp-$number",
|
||||
periodSeconds = 30,
|
||||
id = "mockId-1",
|
||||
id = "mockId-$number",
|
||||
issueTime = 1698408000000,
|
||||
timeLeftSeconds = 30,
|
||||
name = "mockName-1",
|
||||
name = "mockName-$number",
|
||||
uriLoginViewList = createMockLoginView(1).uris,
|
||||
username = "mockUsername-1",
|
||||
username = "mockUsername-$number",
|
||||
hasPasswordReprompt = false,
|
||||
)
|
||||
|
|
Loading…
Add table
Reference in a new issue