mirror of
https://github.com/bitwarden/android.git
synced 2025-02-16 11:59:57 +03:00
[PM-8137] Set initial FIDO 2 user verification state (#3463)
This commit is contained in:
parent
5ea2f1c736
commit
7653d71b3d
4 changed files with 88 additions and 12 deletions
|
@ -7,6 +7,7 @@ import androidx.lifecycle.viewModelScope
|
|||
import com.bitwarden.vault.CipherView
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.util.getPasswordlessRequestDataIntentOrNull
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2CredentialRequestOrNull
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager
|
||||
import com.x8bit.bitwarden.data.autofill.util.getAutofillSaveItemOrNull
|
||||
|
@ -44,9 +45,10 @@ class MainViewModel @Inject constructor(
|
|||
autofillSelectionManager: AutofillSelectionManager,
|
||||
private val specialCircumstanceManager: SpecialCircumstanceManager,
|
||||
private val garbageCollectionManager: GarbageCollectionManager,
|
||||
private val fido2CredentialManager: Fido2CredentialManager,
|
||||
private val intentManager: IntentManager,
|
||||
settingsRepository: SettingsRepository,
|
||||
vaultRepository: VaultRepository,
|
||||
private val vaultRepository: VaultRepository,
|
||||
private val authRepository: AuthRepository,
|
||||
private val savedStateHandle: SavedStateHandle,
|
||||
) : BaseViewModel<MainState, MainEvent, MainAction>(
|
||||
|
@ -218,6 +220,10 @@ class MainViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
fido2CredentialRequestData != null -> {
|
||||
// Set the user's verification status when a new FIDO 2 request is received to force
|
||||
// explicit verification if the user's vault is unlocked when the request is
|
||||
// received.
|
||||
fido2CredentialManager.isUserVerified = false
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.Fido2Save(
|
||||
fido2CredentialRequest = fido2CredentialRequestData,
|
||||
|
|
|
@ -8,6 +8,7 @@ import com.x8bit.bitwarden.R
|
|||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.VaultUnlockType
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.BiometricsEncryptionManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
|
@ -43,6 +44,7 @@ class VaultUnlockViewModel @Inject constructor(
|
|||
private val authRepository: AuthRepository,
|
||||
private val vaultRepo: VaultRepository,
|
||||
private val biometricsEncryptionManager: BiometricsEncryptionManager,
|
||||
private val fido2CredentialManager: Fido2CredentialManager,
|
||||
environmentRepo: EnvironmentRepository,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : BaseViewModel<VaultUnlockState, VaultUnlockEvent, VaultUnlockAction>(
|
||||
|
@ -253,6 +255,10 @@ class VaultUnlockViewModel @Inject constructor(
|
|||
return
|
||||
}
|
||||
|
||||
// Mark the user verified for this session if the unlock result is Success.
|
||||
fido2CredentialManager.isUserVerified =
|
||||
action.vaultUnlockResult is VaultUnlockResult.Success
|
||||
|
||||
when (action.vaultUnlockResult) {
|
||||
VaultUnlockResult.AuthenticationError -> {
|
||||
mutableStateFlow.update {
|
||||
|
|
|
@ -12,6 +12,7 @@ import com.x8bit.bitwarden.data.auth.util.getPasswordlessRequestDataIntentOrNull
|
|||
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.createMockFido2CredentialRequest
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2CredentialRequestOrNull
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManagerImpl
|
||||
|
@ -55,7 +56,6 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
private val mutableUserStateFlow = MutableStateFlow<UserState?>(null)
|
||||
private val mutableAppThemeFlow = MutableStateFlow(AppTheme.DEFAULT)
|
||||
private val mutableScreenCaptureAllowedFlow = MutableStateFlow(true)
|
||||
private val fido2CredentialManager = mockk<Fido2CredentialManager>()
|
||||
private val settingsRepository = mockk<SettingsRepository> {
|
||||
every { appTheme } returns AppTheme.DEFAULT
|
||||
every { appThemeStateFlow } returns mutableAppThemeFlow
|
||||
|
@ -78,6 +78,10 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
private val intentManager: IntentManager = mockk {
|
||||
every { getShareDataFromIntent(any()) } returns null
|
||||
}
|
||||
private val fido2CredentialManager = mockk<Fido2CredentialManager> {
|
||||
every { isUserVerified } returns true
|
||||
every { isUserVerified = any() } just runs
|
||||
}
|
||||
private val savedStateHandle = SavedStateHandle()
|
||||
|
||||
@BeforeEach
|
||||
|
@ -362,22 +366,16 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
signingInfo = SigningInfo(),
|
||||
origin = "mockOrigin",
|
||||
)
|
||||
val mockIntent = mockk<Intent> {
|
||||
every { getFido2CredentialRequestOrNull() } returns fido2CredentialRequest
|
||||
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
||||
every { getAutofillSelectionDataOrNull() } returns null
|
||||
every { getAutofillSaveItemOrNull() } returns null
|
||||
every { isMyVaultShortcut } returns false
|
||||
every { isPasswordGeneratorShortcut } returns false
|
||||
}
|
||||
every { intentManager.getShareDataFromIntent(mockIntent) } returns null
|
||||
val fido2Intent = createMockFido2RegistrationIntent(fido2CredentialRequest)
|
||||
|
||||
every { intentManager.getShareDataFromIntent(fido2Intent) } returns null
|
||||
coEvery {
|
||||
fido2CredentialManager.validateOrigin(any())
|
||||
} returns Fido2ValidateOriginResult.Success
|
||||
|
||||
viewModel.trySendAction(
|
||||
MainAction.ReceiveFirstIntent(
|
||||
intent = mockIntent,
|
||||
intent = fido2Intent,
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -389,6 +387,22 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on ReceiveFirstIntent with fido2 request data should set the user to unverified`() {
|
||||
val viewModel = createViewModel()
|
||||
val fido2Intent = createMockFido2RegistrationIntent()
|
||||
|
||||
viewModel.trySendAction(
|
||||
MainAction.ReceiveFirstIntent(
|
||||
intent = fido2Intent,
|
||||
),
|
||||
)
|
||||
|
||||
verify {
|
||||
fido2CredentialManager.isUserVerified = false
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on ReceiveFirstIntent with fido2 request data should switch users if active user is not selected`() {
|
||||
|
@ -633,6 +647,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
autofillSelectionManager = autofillSelectionManager,
|
||||
specialCircumstanceManager = specialCircumstanceManager,
|
||||
garbageCollectionManager = garbageCollectionManager,
|
||||
fido2CredentialManager = fido2CredentialManager,
|
||||
intentManager = intentManager,
|
||||
settingsRepository = settingsRepository,
|
||||
vaultRepository = vaultRepository,
|
||||
|
@ -669,3 +684,14 @@ private val DEFAULT_USER_STATE = UserState(
|
|||
activeUserId = "activeUserId",
|
||||
accounts = listOf(DEFAULT_ACCOUNT),
|
||||
)
|
||||
|
||||
private fun createMockFido2RegistrationIntent(
|
||||
fido2CredentialRequest: Fido2CredentialRequest = createMockFido2CredentialRequest(number = 1),
|
||||
): Intent = mockk<Intent> {
|
||||
every { getFido2CredentialRequestOrNull() } returns fido2CredentialRequest
|
||||
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
||||
every { getAutofillSelectionDataOrNull() } returns null
|
||||
every { getAutofillSaveItemOrNull() } returns null
|
||||
every { isMyVaultShortcut } returns false
|
||||
every { isPasswordGeneratorShortcut } returns false
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
|||
import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.VaultUnlockType
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.BiometricsEncryptionManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
|
@ -67,6 +68,10 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
} returns false
|
||||
}
|
||||
private val fido2CredentialManager: Fido2CredentialManager = mockk {
|
||||
every { isUserVerified } returns true
|
||||
every { isUserVerified = any() } just runs
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on init with biometrics enabled and valid should emit PromptForBiometrics`() = runTest {
|
||||
|
@ -871,6 +876,38 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on ReceiveVaultUnlockResult should set FIDO 2 user verification state to verified when result is Success`() {
|
||||
val viewModel = createViewModel()
|
||||
|
||||
viewModel.trySendAction(
|
||||
VaultUnlockAction.Internal.ReceiveVaultUnlockResult(
|
||||
userId = "activeUserId",
|
||||
vaultUnlockResult = VaultUnlockResult.Success,
|
||||
isBiometricLogin = true,
|
||||
),
|
||||
)
|
||||
|
||||
verify { fido2CredentialManager.isUserVerified = true }
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on ReceiveVaultUnlockResult should set FIDO 2 user verification state to not verified when result is not Success`() {
|
||||
val viewModel = createViewModel()
|
||||
|
||||
viewModel.trySendAction(
|
||||
VaultUnlockAction.Internal.ReceiveVaultUnlockResult(
|
||||
userId = "activeUserId",
|
||||
vaultUnlockResult = VaultUnlockResult.InvalidStateError,
|
||||
isBiometricLogin = false,
|
||||
),
|
||||
)
|
||||
|
||||
verify { fido2CredentialManager.isUserVerified = false }
|
||||
}
|
||||
|
||||
private fun createViewModel(
|
||||
state: VaultUnlockState? = null,
|
||||
unlockType: UnlockType = UnlockType.STANDARD,
|
||||
|
@ -886,6 +923,7 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
|
|||
vaultRepo = vaultRepo,
|
||||
environmentRepo = environmentRepo,
|
||||
biometricsEncryptionManager = biometricsEncryptionManager,
|
||||
fido2CredentialManager = fido2CredentialManager,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue