mirror of
https://github.com/bitwarden/android.git
synced 2025-02-16 20:09:59 +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,
|
userId: String,
|
||||||
cipher: Cipher?,
|
cipher: Cipher?,
|
||||||
): Boolean
|
): 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 =
|
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
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Reference in a new issue