mirror of
https://github.com/bitwarden/android.git
synced 2024-11-21 17:05:44 +03:00
PM-15062 Checking if the user has a no longer supported biometric as their only way of unlocking their account. (#4338)
This commit is contained in:
parent
3092ba1fc6
commit
ec8e934bf4
10 changed files with 194 additions and 20 deletions
|
@ -2,8 +2,10 @@ package com.x8bit.bitwarden.data.auth.datasource.disk.model
|
||||||
|
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.KdfTypeJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.KdfTypeJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.UserDecryptionOptionsJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.UserDecryptionOptionsJson
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.JsonNames
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the current account information for a given user.
|
* Represents the current account information for a given user.
|
||||||
|
@ -45,6 +47,7 @@ data class AccountJson(
|
||||||
* @property kdfParallelism The number of threads to use when calculating a password hash.
|
* @property kdfParallelism The number of threads to use when calculating a password hash.
|
||||||
* @property userDecryptionOptions The options available to a user for decryption.
|
* @property userDecryptionOptions The options available to a user for decryption.
|
||||||
*/
|
*/
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Profile(
|
data class Profile(
|
||||||
@SerialName("userId")
|
@SerialName("userId")
|
||||||
|
@ -86,7 +89,8 @@ data class AccountJson(
|
||||||
@SerialName("kdfParallelism")
|
@SerialName("kdfParallelism")
|
||||||
val kdfParallelism: Int?,
|
val kdfParallelism: Int?,
|
||||||
|
|
||||||
@SerialName("accountDecryptionOptions")
|
@SerialName("userDecryptionOptions")
|
||||||
|
@JsonNames("accountDecryptionOptions")
|
||||||
val userDecryptionOptions: UserDecryptionOptionsJson?,
|
val userDecryptionOptions: UserDecryptionOptionsJson?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.JsonNames
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decryption options related to a user's key connector.
|
* Decryption options related to a user's key connector.
|
||||||
*
|
*
|
||||||
* @property keyConnectorUrl URL to the user's key connector.
|
* @property keyConnectorUrl URL to the user's key connector.
|
||||||
*/
|
*/
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
@Serializable
|
@Serializable
|
||||||
data class KeyConnectorUserDecryptionOptionsJson(
|
data class KeyConnectorUserDecryptionOptionsJson(
|
||||||
@SerialName("KeyConnectorUrl")
|
@SerialName("keyConnectorUrl")
|
||||||
|
@JsonNames("KeyConnectorUrl")
|
||||||
val keyConnectorUrl: String,
|
val keyConnectorUrl: String,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.JsonNames
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decryption options related to a user's trusted device.
|
* Decryption options related to a user's trusted device.
|
||||||
|
@ -13,20 +15,26 @@ import kotlinx.serialization.Serializable
|
||||||
* @property hasManageResetPasswordPermission Whether or not the user has manage reset password
|
* @property hasManageResetPasswordPermission Whether or not the user has manage reset password
|
||||||
* permission.
|
* permission.
|
||||||
*/
|
*/
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
@Serializable
|
@Serializable
|
||||||
data class TrustedDeviceUserDecryptionOptionsJson(
|
data class TrustedDeviceUserDecryptionOptionsJson(
|
||||||
@SerialName("EncryptedPrivateKey")
|
@SerialName("encryptedPrivateKey")
|
||||||
|
@JsonNames("EncryptedPrivateKey")
|
||||||
val encryptedPrivateKey: String?,
|
val encryptedPrivateKey: String?,
|
||||||
|
|
||||||
@SerialName("EncryptedUserKey")
|
@SerialName("encryptedUserKey")
|
||||||
|
@JsonNames("EncryptedUserKey")
|
||||||
val encryptedUserKey: String?,
|
val encryptedUserKey: String?,
|
||||||
|
|
||||||
@SerialName("HasAdminApproval")
|
@SerialName("hasAdminApproval")
|
||||||
|
@JsonNames("HasAdminApproval")
|
||||||
val hasAdminApproval: Boolean,
|
val hasAdminApproval: Boolean,
|
||||||
|
|
||||||
@SerialName("HasLoginApprovingDevice")
|
@SerialName("hasLoginApprovingDevice")
|
||||||
|
@JsonNames("HasLoginApprovingDevice")
|
||||||
val hasLoginApprovingDevice: Boolean,
|
val hasLoginApprovingDevice: Boolean,
|
||||||
|
|
||||||
@SerialName("HasManageResetPasswordPermission")
|
@SerialName("hasManageResetPasswordPermission")
|
||||||
|
@JsonNames("HasManageResetPasswordPermission")
|
||||||
val hasManageResetPasswordPermission: Boolean,
|
val hasManageResetPasswordPermission: Boolean,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.JsonNames
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The options available to a user for decryption.
|
* The options available to a user for decryption.
|
||||||
|
@ -12,14 +14,18 @@ import kotlinx.serialization.Serializable
|
||||||
* device.
|
* device.
|
||||||
* @property keyConnectorUserDecryptionOptions Decryption options related to a user's key connector.
|
* @property keyConnectorUserDecryptionOptions Decryption options related to a user's key connector.
|
||||||
*/
|
*/
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
@Serializable
|
@Serializable
|
||||||
data class UserDecryptionOptionsJson(
|
data class UserDecryptionOptionsJson(
|
||||||
@SerialName("HasMasterPassword")
|
@SerialName("hasMasterPassword")
|
||||||
|
@JsonNames("HasMasterPassword")
|
||||||
val hasMasterPassword: Boolean,
|
val hasMasterPassword: Boolean,
|
||||||
|
|
||||||
@SerialName("TrustedDeviceOption")
|
@SerialName("trustedDeviceOption")
|
||||||
|
@JsonNames("TrustedDeviceOption")
|
||||||
val trustedDeviceUserDecryptionOptions: TrustedDeviceUserDecryptionOptionsJson?,
|
val trustedDeviceUserDecryptionOptions: TrustedDeviceUserDecryptionOptionsJson?,
|
||||||
|
|
||||||
@SerialName("KeyConnectorOption")
|
@SerialName("keyConnectorOption")
|
||||||
|
@JsonNames("KeyConnectorOption")
|
||||||
val keyConnectorUserDecryptionOptions: KeyConnectorUserDecryptionOptionsJson?,
|
val keyConnectorUserDecryptionOptions: KeyConnectorUserDecryptionOptionsJson?,
|
||||||
)
|
)
|
||||||
|
|
|
@ -17,6 +17,7 @@ import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.material3.rememberTopAppBarState
|
import androidx.compose.material3.rememberTopAppBarState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
@ -85,6 +86,12 @@ fun VaultUnlockScreen(
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val resources = context.resources
|
val resources = context.resources
|
||||||
|
|
||||||
|
LaunchedEffect(state.requiresBiometricsLogin) {
|
||||||
|
if (state.requiresBiometricsLogin && !biometricsManager.isBiometricsSupported) {
|
||||||
|
viewModel.trySendAction(VaultUnlockAction.BiometricsNoLongerSupported)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val onBiometricsUnlockSuccess: (cipher: Cipher?) -> Unit = remember(viewModel) {
|
val onBiometricsUnlockSuccess: (cipher: Cipher?) -> Unit = remember(viewModel) {
|
||||||
{ viewModel.trySendAction(VaultUnlockAction.BiometricsUnlockSuccess(it)) }
|
{ viewModel.trySendAction(VaultUnlockAction.BiometricsUnlockSuccess(it)) }
|
||||||
}
|
}
|
||||||
|
@ -148,6 +155,22 @@ fun VaultUnlockScreen(
|
||||||
visibilityState = LoadingDialogState.Shown(R.string.loading.asText()),
|
visibilityState = LoadingDialogState.Shown(R.string.loading.asText()),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
VaultUnlockState.VaultUnlockDialog.BiometricsNoLongerSupported -> {
|
||||||
|
BitwardenBasicDialog(
|
||||||
|
visibilityState = BasicDialogState.Shown(
|
||||||
|
title = R.string.biometrics_no_longer_supported_title.asText(),
|
||||||
|
message = R.string.biometrics_no_longer_supported.asText(),
|
||||||
|
),
|
||||||
|
onDismissRequest = remember {
|
||||||
|
{
|
||||||
|
viewModel.trySendAction(
|
||||||
|
VaultUnlockAction.DismissBiometricsNoLongerSupportedDialog,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
null -> Unit
|
null -> Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -95,6 +95,7 @@ class VaultUnlockViewModel @Inject constructor(
|
||||||
fido2GetCredentialsRequest = null,
|
fido2GetCredentialsRequest = null,
|
||||||
// TODO: [PM-13076] Handle Fido2CredentialAssertionRequest special circumstance
|
// TODO: [PM-13076] Handle Fido2CredentialAssertionRequest special circumstance
|
||||||
fido2CredentialAssertionRequest = null,
|
fido2CredentialAssertionRequest = null,
|
||||||
|
hasMasterPassword = activeAccount.hasMasterPassword,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
@ -138,9 +139,32 @@ class VaultUnlockViewModel @Inject constructor(
|
||||||
is VaultUnlockAction.BiometricsUnlockSuccess -> handleBiometricsUnlockSuccess(action)
|
is VaultUnlockAction.BiometricsUnlockSuccess -> handleBiometricsUnlockSuccess(action)
|
||||||
VaultUnlockAction.UnlockClick -> handleUnlockClick()
|
VaultUnlockAction.UnlockClick -> handleUnlockClick()
|
||||||
is VaultUnlockAction.Internal -> handleInternalAction(action)
|
is VaultUnlockAction.Internal -> handleInternalAction(action)
|
||||||
|
VaultUnlockAction.BiometricsNoLongerSupported -> {
|
||||||
|
handleBiometricsNoLongerSupported()
|
||||||
|
}
|
||||||
|
|
||||||
|
VaultUnlockAction.DismissBiometricsNoLongerSupportedDialog -> {
|
||||||
|
handleDismissBiometricsNoLongerSupportedDialog()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleBiometricsNoLongerSupported() {
|
||||||
|
mutableStateFlow.update {
|
||||||
|
it.copy(
|
||||||
|
dialog = VaultUnlockState.VaultUnlockDialog.BiometricsNoLongerSupported,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleDismissBiometricsNoLongerSupportedDialog() {
|
||||||
|
mutableStateFlow.update {
|
||||||
|
it.copy(dialog = null)
|
||||||
|
}
|
||||||
|
authRepository.logout()
|
||||||
|
authRepository.hasPendingAccountAddition = true
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleAddAccountClick() {
|
private fun handleAddAccountClick() {
|
||||||
authRepository.hasPendingAccountAddition = true
|
authRepository.hasPendingAccountAddition = true
|
||||||
}
|
}
|
||||||
|
@ -362,6 +386,7 @@ class VaultUnlockViewModel @Inject constructor(
|
||||||
isBiometricEnabled = userState.activeAccount.isBiometricsEnabled,
|
isBiometricEnabled = userState.activeAccount.isBiometricsEnabled,
|
||||||
vaultUnlockType = userState.activeAccount.vaultUnlockType,
|
vaultUnlockType = userState.activeAccount.vaultUnlockType,
|
||||||
input = "",
|
input = "",
|
||||||
|
hasMasterPassword = userState.activeAccount.hasMasterPassword,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -403,6 +428,7 @@ data class VaultUnlockState(
|
||||||
val userId: String,
|
val userId: String,
|
||||||
val fido2GetCredentialsRequest: Fido2GetCredentialsRequest? = null,
|
val fido2GetCredentialsRequest: Fido2GetCredentialsRequest? = null,
|
||||||
val fido2CredentialAssertionRequest: Fido2CredentialAssertionRequest? = null,
|
val fido2CredentialAssertionRequest: Fido2CredentialAssertionRequest? = null,
|
||||||
|
private val hasMasterPassword: Boolean,
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -434,6 +460,15 @@ data class VaultUnlockState(
|
||||||
val fido2RequestUserId: String?
|
val fido2RequestUserId: String?
|
||||||
get() = fido2GetCredentialsRequest?.userId ?: fido2CredentialAssertionRequest?.userId
|
get() = fido2GetCredentialsRequest?.userId ?: fido2CredentialAssertionRequest?.userId
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the user requires biometrics to be able to unlock the account.
|
||||||
|
*/
|
||||||
|
val requiresBiometricsLogin: Boolean
|
||||||
|
get() = when (vaultUnlockType) {
|
||||||
|
VaultUnlockType.MASTER_PASSWORD -> !hasMasterPassword && isBiometricEnabled
|
||||||
|
VaultUnlockType.PIN -> false
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the various dialogs the vault unlock screen can display.
|
* Represents the various dialogs the vault unlock screen can display.
|
||||||
*/
|
*/
|
||||||
|
@ -452,6 +487,12 @@ data class VaultUnlockState(
|
||||||
*/
|
*/
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data object Loading : VaultUnlockDialog()
|
data object Loading : VaultUnlockDialog()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show dialog for when biometrics the user has is no longer supported.
|
||||||
|
*/
|
||||||
|
@Parcelize
|
||||||
|
data object BiometricsNoLongerSupported : VaultUnlockDialog()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -496,6 +537,11 @@ sealed class VaultUnlockAction {
|
||||||
*/
|
*/
|
||||||
data object DismissDialog : VaultUnlockAction()
|
data object DismissDialog : VaultUnlockAction()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The user has dismissed the biometrics not supported dialog
|
||||||
|
*/
|
||||||
|
data object DismissBiometricsNoLongerSupportedDialog : VaultUnlockAction()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The user has clicked on the logout confirmation button.
|
* The user has clicked on the logout confirmation button.
|
||||||
*/
|
*/
|
||||||
|
@ -548,6 +594,11 @@ sealed class VaultUnlockAction {
|
||||||
*/
|
*/
|
||||||
data object BiometricsLockOut : VaultUnlockAction()
|
data object BiometricsLockOut : VaultUnlockAction()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The user has biometric unlock setup that is no longer valid.
|
||||||
|
*/
|
||||||
|
data object BiometricsNoLongerSupported : VaultUnlockAction()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The user has clicked the unlock button.
|
* The user has clicked the unlock button.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1095,4 +1095,6 @@ Do you want to switch to this account?</string>
|
||||||
<string name="copy_email">Copy email</string>
|
<string name="copy_email">Copy email</string>
|
||||||
<string name="copy_phone">Copy phone number</string>
|
<string name="copy_phone">Copy phone number</string>
|
||||||
<string name="copy_address">Copy address</string>
|
<string name="copy_address">Copy address</string>
|
||||||
|
<string name="biometrics_no_longer_supported_title">Biometrics are no longer supported on this device</string>
|
||||||
|
<string name="biometrics_no_longer_supported">You’ve been logged out because your device’s biometrics don’t meet the latest security requirements. To update settings, log in once again or contact your administrator for access.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -1266,17 +1266,17 @@ private const val USER_STATE_JSON = """
|
||||||
"kdfIterations": 600000,
|
"kdfIterations": 600000,
|
||||||
"kdfMemory": 16,
|
"kdfMemory": 16,
|
||||||
"kdfParallelism": 4,
|
"kdfParallelism": 4,
|
||||||
"accountDecryptionOptions": {
|
"userDecryptionOptions": {
|
||||||
"HasMasterPassword": true,
|
"hasMasterPassword": true,
|
||||||
"TrustedDeviceOption": {
|
"trustedDeviceOption": {
|
||||||
"EncryptedPrivateKey": "encryptedPrivateKey",
|
"encryptedPrivateKey": "encryptedPrivateKey",
|
||||||
"EncryptedUserKey": "encryptedUserKey",
|
"encryptedUserKey": "encryptedUserKey",
|
||||||
"HasAdminApproval": true,
|
"hasAdminApproval": true,
|
||||||
"HasLoginApprovingDevice": true,
|
"hasLoginApprovingDevice": true,
|
||||||
"HasManageResetPasswordPermission": true
|
"hasManageResetPasswordPermission": true
|
||||||
},
|
},
|
||||||
"KeyConnectorOption": {
|
"keyConnectorOption": {
|
||||||
"KeyConnectorUrl": "keyConnectorUrl"
|
"keyConnectorUrl": "keyConnectorUrl"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -541,6 +541,51 @@ class VaultUnlockScreenTest : BaseComposeTest() {
|
||||||
.assertDoesNotExist()
|
.assertDoesNotExist()
|
||||||
composeTestRule.onNodeWithText("Unlock").assertDoesNotExist()
|
composeTestRule.onNodeWithText("Unlock").assertDoesNotExist()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `biometrics not supported dialog shows correctly`() {
|
||||||
|
mutableStateFlow.update {
|
||||||
|
it.copy(dialog = VaultUnlockState.VaultUnlockDialog.BiometricsNoLongerSupported)
|
||||||
|
}
|
||||||
|
composeTestRule
|
||||||
|
.onNodeWithText("Biometrics are no longer supported on this device")
|
||||||
|
.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `DismissBiometricsNoLongerSupportedDialog should be sent when dialog is dismissed`() {
|
||||||
|
mutableStateFlow.update {
|
||||||
|
it.copy(dialog = VaultUnlockState.VaultUnlockDialog.BiometricsNoLongerSupported)
|
||||||
|
}
|
||||||
|
composeTestRule
|
||||||
|
.onNodeWithText("Biometrics are no longer supported on this device")
|
||||||
|
.assertIsDisplayed()
|
||||||
|
|
||||||
|
composeTestRule
|
||||||
|
.onNodeWithText("Ok")
|
||||||
|
.performClick()
|
||||||
|
|
||||||
|
verify {
|
||||||
|
viewModel.trySendAction(VaultUnlockAction.DismissBiometricsNoLongerSupportedDialog)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `when biometric is needed but no longer supported BiometricsNoLongerSupported action is sent`() {
|
||||||
|
every { biometricsManager.isBiometricsSupported } returns false
|
||||||
|
mutableStateFlow.update {
|
||||||
|
it.copy(
|
||||||
|
isBiometricEnabled = true,
|
||||||
|
hasMasterPassword = false,
|
||||||
|
vaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
composeTestRule.waitForIdle()
|
||||||
|
verify {
|
||||||
|
viewModel.trySendAction(VaultUnlockAction.BiometricsNoLongerSupported)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val DEFAULT_ENVIRONMENT_URL: String = "vault.bitwarden.com"
|
private const val DEFAULT_ENVIRONMENT_URL: String = "vault.bitwarden.com"
|
||||||
|
@ -588,4 +633,5 @@ private val DEFAULT_STATE: VaultUnlockState = VaultUnlockState(
|
||||||
showBiometricInvalidatedMessage = false,
|
showBiometricInvalidatedMessage = false,
|
||||||
userId = ACTIVE_ACCOUNT_SUMMARY.userId,
|
userId = ACTIVE_ACCOUNT_SUMMARY.userId,
|
||||||
vaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
|
vaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
|
||||||
|
hasMasterPassword = true,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1227,6 +1227,35 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
|
||||||
verify { fido2CredentialManager.isUserVerified = false }
|
verify { fido2CredentialManager.isUserVerified = false }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `on BiometricsNoLongerSupported should show correct dialog state`() {
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
viewModel.trySendAction(VaultUnlockAction.BiometricsNoLongerSupported)
|
||||||
|
assertEquals(
|
||||||
|
DEFAULT_STATE.copy(
|
||||||
|
dialog = VaultUnlockState.VaultUnlockDialog.BiometricsNoLongerSupported,
|
||||||
|
),
|
||||||
|
viewModel.stateFlow.value,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `on DismissBiometricsNoLongerSupportedDialog should dismiss dialog state and log the user out`() {
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
viewModel.trySendAction(VaultUnlockAction.DismissBiometricsNoLongerSupportedDialog)
|
||||||
|
assertEquals(
|
||||||
|
DEFAULT_STATE.copy(
|
||||||
|
dialog = null,
|
||||||
|
),
|
||||||
|
viewModel.stateFlow.value,
|
||||||
|
)
|
||||||
|
verify(exactly = 1) {
|
||||||
|
authRepository.logout()
|
||||||
|
authRepository.hasPendingAccountAddition = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun createViewModel(
|
private fun createViewModel(
|
||||||
state: VaultUnlockState? = null,
|
state: VaultUnlockState? = null,
|
||||||
unlockType: UnlockType = UnlockType.STANDARD,
|
unlockType: UnlockType = UnlockType.STANDARD,
|
||||||
|
@ -1275,6 +1304,7 @@ private val DEFAULT_STATE: VaultUnlockState = VaultUnlockState(
|
||||||
showBiometricInvalidatedMessage = false,
|
showBiometricInvalidatedMessage = false,
|
||||||
userId = USER_ID,
|
userId = USER_ID,
|
||||||
vaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
|
vaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
|
||||||
|
hasMasterPassword = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val TRUSTED_DEVICE: UserState.TrustedDevice = UserState.TrustedDevice(
|
private val TRUSTED_DEVICE: UserState.TrustedDevice = UserState.TrustedDevice(
|
||||||
|
|
Loading…
Reference in a new issue