BIT-2345: Add error message when user needs to reenable biometrics (#1347)

This commit is contained in:
Caleb Derosier 2024-05-09 09:48:34 -06:00 committed by Álison Fernandes
parent 8b3f67680a
commit 74648c17bc
7 changed files with 81 additions and 5 deletions

View file

@ -35,4 +35,9 @@ interface BiometricsEncryptionManager {
userId: String, userId: String,
cipher: Cipher?, cipher: Cipher?,
): Boolean ): Boolean
/**
* Returns a boolean indicating whether the system reflects biometric availability.
*/
fun isAccountBiometricIntegrityValid(userId: String): Boolean
} }

View file

@ -75,7 +75,7 @@ class BiometricsEncryptionManagerImpl(
override fun isBiometricIntegrityValid(userId: String, cipher: Cipher?): Boolean = override fun isBiometricIntegrityValid(userId: String, cipher: Cipher?): Boolean =
isSystemBiometricIntegrityValid(userId, cipher) && isAccountBiometricIntegrityValid(userId) isSystemBiometricIntegrityValid(userId, cipher) && isAccountBiometricIntegrityValid(userId)
private fun isAccountBiometricIntegrityValid(userId: String): Boolean { override fun isAccountBiometricIntegrityValid(userId: String): Boolean {
val systemBioIntegrityState = settingsDiskSource val systemBioIntegrityState = settingsDiskSource
.systemBiometricIntegritySource .systemBiometricIntegritySource
?: return false ?: return false

View file

@ -27,6 +27,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
@ -232,6 +233,15 @@ fun VaultUnlockScreen(
.fillMaxWidth(), .fillMaxWidth(),
) )
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
} else if (state.showBiometricInvalidatedMessage) {
Text(
text = stringResource(R.string.account_biometric_invalidated),
textAlign = TextAlign.Start,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.error,
modifier = Modifier.padding(horizontal = 16.dp),
)
Spacer(modifier = Modifier.height(12.dp))
} }
if (!state.hideInput) { if (!state.hideInput) {
BitwardenFilledButton( BitwardenFilledButton(

View file

@ -74,6 +74,7 @@ class VaultUnlockViewModel @Inject constructor(
isBiometricEnabled = isBiometricsEnabled, isBiometricEnabled = isBiometricsEnabled,
isBiometricsValid = isBiometricsValid, isBiometricsValid = isBiometricsValid,
showAccountMenu = VaultUnlockArgs(savedStateHandle).unlockType == UnlockType.STANDARD, showAccountMenu = VaultUnlockArgs(savedStateHandle).unlockType == UnlockType.STANDARD,
showBiometricInvalidatedMessage = false,
vaultUnlockType = vaultUnlockType, vaultUnlockType = vaultUnlockType,
userId = userState.activeUserId, userId = userState.activeUserId,
) )
@ -168,8 +169,13 @@ class VaultUnlockViewModel @Inject constructor(
), ),
) )
} else { } else {
mutableStateFlow.update { it.copy(isBiometricsValid = false) } mutableStateFlow.update {
// TODO BIT-2345 show failure message when user added a new fingerprint it.copy(
isBiometricsValid = false,
showBiometricInvalidatedMessage = !biometricsEncryptionManager
.isAccountBiometricIntegrityValid(state.userId),
)
}
} }
} }
@ -320,6 +326,7 @@ data class VaultUnlockState(
val isBiometricsValid: Boolean, val isBiometricsValid: Boolean,
val isBiometricEnabled: Boolean, val isBiometricEnabled: Boolean,
val showAccountMenu: Boolean, val showAccountMenu: Boolean,
val showBiometricInvalidatedMessage: Boolean,
val vaultUnlockType: VaultUnlockType, val vaultUnlockType: VaultUnlockType,
val userId: String, val userId: String,
) : Parcelable { ) : Parcelable {

View file

@ -9,6 +9,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
/** /**
@ -40,3 +41,11 @@ fun BitwardenPolicyWarningText(
.padding(8.dp), .padding(8.dp),
) )
} }
@Preview
@Composable
private fun BitwardenPolicyWarningText_preview() {
BitwardenPolicyWarningText(
text = "text",
)
}

View file

@ -440,6 +440,25 @@ class VaultUnlockScreenTest : BaseComposeTest() {
} }
} }
@Suppress("MaxLineLength")
@Test
fun `biometric invalidated message should display according to state`() {
mutableStateFlow.update {
it.copy(
isBiometricsValid = false,
showBiometricInvalidatedMessage = true,
)
}
composeTestRule
.onNodeWithText("Biometric unlock for this account is disabled pending verification of master password.")
.assertIsDisplayed()
mutableStateFlow.update { it.copy(showBiometricInvalidatedMessage = false) }
composeTestRule
.onNodeWithText("Biometric unlock for this account is disabled pending verification of master password.")
.assertDoesNotExist()
}
@Test @Test
fun `account button should update according to state`() { fun `account button should update according to state`() {
mutableStateFlow.update { it.copy(showAccountMenu = true) } mutableStateFlow.update { it.copy(showAccountMenu = true) }
@ -509,6 +528,7 @@ private val DEFAULT_STATE: VaultUnlockState = VaultUnlockState(
isBiometricsValid = true, isBiometricsValid = true,
isBiometricEnabled = true, isBiometricEnabled = true,
showAccountMenu = true, showAccountMenu = true,
showBiometricInvalidatedMessage = false,
userId = ACTIVE_ACCOUNT_SUMMARY.userId, userId = ACTIVE_ACCOUNT_SUMMARY.userId,
vaultUnlockType = VaultUnlockType.MASTER_PASSWORD, vaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
) )

View file

@ -309,15 +309,39 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
verify { encryptionManager.getOrCreateCipher(USER_ID) } verify { encryptionManager.getOrCreateCipher(USER_ID) }
} }
@Suppress("MaxLineLength")
@Test @Test
fun `on BiometricsUnlockClick should disable isBiometricsValid when cipher is null`() { fun `on BiometricsUnlockClick should disable isBiometricsValid and show message when cipher is null and integrity check returns false`() {
val initialState = DEFAULT_STATE.copy(isBiometricsValid = true) val initialState = DEFAULT_STATE.copy(isBiometricsValid = true)
val viewModel = createViewModel(state = initialState) val viewModel = createViewModel(state = initialState)
every { encryptionManager.getOrCreateCipher(USER_ID) } returns null every { encryptionManager.getOrCreateCipher(USER_ID) } returns null
every { encryptionManager.isAccountBiometricIntegrityValid(USER_ID) } returns false
viewModel.trySendAction(VaultUnlockAction.BiometricsUnlockClick) viewModel.trySendAction(VaultUnlockAction.BiometricsUnlockClick)
assertEquals( assertEquals(
initialState.copy(isBiometricsValid = false), initialState.copy(
isBiometricsValid = false,
showBiometricInvalidatedMessage = true,
),
viewModel.stateFlow.value,
)
verify { encryptionManager.getOrCreateCipher(USER_ID) }
}
@Suppress("MaxLineLength")
@Test
fun `on BiometricsUnlockClick should disable isBiometricsValid and not show message when cipher is null and integrity check returns true`() {
val initialState = DEFAULT_STATE.copy(isBiometricsValid = true)
val viewModel = createViewModel(state = initialState)
every { encryptionManager.getOrCreateCipher(USER_ID) } returns null
every { encryptionManager.isAccountBiometricIntegrityValid(USER_ID) } returns true
viewModel.trySendAction(VaultUnlockAction.BiometricsUnlockClick)
assertEquals(
initialState.copy(
isBiometricsValid = false,
showBiometricInvalidatedMessage = false,
),
viewModel.stateFlow.value, viewModel.stateFlow.value,
) )
verify { encryptionManager.getOrCreateCipher(USER_ID) } verify { encryptionManager.getOrCreateCipher(USER_ID) }
@ -890,6 +914,7 @@ private val DEFAULT_STATE: VaultUnlockState = VaultUnlockState(
isBiometricsValid = true, isBiometricsValid = true,
isBiometricEnabled = false, isBiometricEnabled = false,
showAccountMenu = true, showAccountMenu = true,
showBiometricInvalidatedMessage = false,
userId = USER_ID, userId = USER_ID,
vaultUnlockType = VaultUnlockType.MASTER_PASSWORD, vaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
) )