mirror of
https://github.com/bitwarden/android.git
synced 2024-11-22 09:25:58 +03:00
BIT-2345: Add error message when user needs to reenable biometrics (#1347)
This commit is contained in:
parent
8b3f67680a
commit
74648c17bc
7 changed files with 81 additions and 5 deletions
|
@ -35,4 +35,9 @@ interface BiometricsEncryptionManager {
|
|||
userId: String,
|
||||
cipher: Cipher?,
|
||||
): Boolean
|
||||
|
||||
/**
|
||||
* Returns a boolean indicating whether the system reflects biometric availability.
|
||||
*/
|
||||
fun isAccountBiometricIntegrityValid(userId: String): Boolean
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ class BiometricsEncryptionManagerImpl(
|
|||
override fun isBiometricIntegrityValid(userId: String, cipher: Cipher?): Boolean =
|
||||
isSystemBiometricIntegrityValid(userId, cipher) && isAccountBiometricIntegrityValid(userId)
|
||||
|
||||
private fun isAccountBiometricIntegrityValid(userId: String): Boolean {
|
||||
override fun isAccountBiometricIntegrityValid(userId: String): Boolean {
|
||||
val systemBioIntegrityState = settingsDiskSource
|
||||
.systemBiometricIntegritySource
|
||||
?: return false
|
||||
|
|
|
@ -27,6 +27,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
|
@ -232,6 +233,15 @@ fun VaultUnlockScreen(
|
|||
.fillMaxWidth(),
|
||||
)
|
||||
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) {
|
||||
BitwardenFilledButton(
|
||||
|
|
|
@ -74,6 +74,7 @@ class VaultUnlockViewModel @Inject constructor(
|
|||
isBiometricEnabled = isBiometricsEnabled,
|
||||
isBiometricsValid = isBiometricsValid,
|
||||
showAccountMenu = VaultUnlockArgs(savedStateHandle).unlockType == UnlockType.STANDARD,
|
||||
showBiometricInvalidatedMessage = false,
|
||||
vaultUnlockType = vaultUnlockType,
|
||||
userId = userState.activeUserId,
|
||||
)
|
||||
|
@ -168,8 +169,13 @@ class VaultUnlockViewModel @Inject constructor(
|
|||
),
|
||||
)
|
||||
} else {
|
||||
mutableStateFlow.update { it.copy(isBiometricsValid = false) }
|
||||
// TODO BIT-2345 show failure message when user added a new fingerprint
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
isBiometricsValid = false,
|
||||
showBiometricInvalidatedMessage = !biometricsEncryptionManager
|
||||
.isAccountBiometricIntegrityValid(state.userId),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -320,6 +326,7 @@ data class VaultUnlockState(
|
|||
val isBiometricsValid: Boolean,
|
||||
val isBiometricEnabled: Boolean,
|
||||
val showAccountMenu: Boolean,
|
||||
val showBiometricInvalidatedMessage: Boolean,
|
||||
val vaultUnlockType: VaultUnlockType,
|
||||
val userId: String,
|
||||
) : Parcelable {
|
||||
|
|
|
@ -9,6 +9,7 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
|
@ -40,3 +41,11 @@ fun BitwardenPolicyWarningText(
|
|||
.padding(8.dp),
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun BitwardenPolicyWarningText_preview() {
|
||||
BitwardenPolicyWarningText(
|
||||
text = "text",
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
fun `account button should update according to state`() {
|
||||
mutableStateFlow.update { it.copy(showAccountMenu = true) }
|
||||
|
@ -509,6 +528,7 @@ private val DEFAULT_STATE: VaultUnlockState = VaultUnlockState(
|
|||
isBiometricsValid = true,
|
||||
isBiometricEnabled = true,
|
||||
showAccountMenu = true,
|
||||
showBiometricInvalidatedMessage = false,
|
||||
userId = ACTIVE_ACCOUNT_SUMMARY.userId,
|
||||
vaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
|
||||
)
|
||||
|
|
|
@ -309,15 +309,39 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
|
|||
verify { encryptionManager.getOrCreateCipher(USER_ID) }
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@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 viewModel = createViewModel(state = initialState)
|
||||
every { encryptionManager.getOrCreateCipher(USER_ID) } returns null
|
||||
every { encryptionManager.isAccountBiometricIntegrityValid(USER_ID) } returns false
|
||||
|
||||
viewModel.trySendAction(VaultUnlockAction.BiometricsUnlockClick)
|
||||
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,
|
||||
)
|
||||
verify { encryptionManager.getOrCreateCipher(USER_ID) }
|
||||
|
@ -890,6 +914,7 @@ private val DEFAULT_STATE: VaultUnlockState = VaultUnlockState(
|
|||
isBiometricsValid = true,
|
||||
isBiometricEnabled = false,
|
||||
showAccountMenu = true,
|
||||
showBiometricInvalidatedMessage = false,
|
||||
userId = USER_ID,
|
||||
vaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue