mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 07:05:35 +03:00
[PM-8137] Perform FIDO 2 verification on item add/edit when required (#3532)
Some checks failed
Crowdin Push / Crowdin Push (push) Waiting to run
Scan / Check PR run (push) Failing after 0s
Scan / SAST scan (push) Has been skipped
Scan / Quality scan (push) Has been skipped
Test / Check PR run (push) Failing after 0s
Test / Test (push) Has been skipped
Some checks failed
Crowdin Push / Crowdin Push (push) Waiting to run
Scan / Check PR run (push) Failing after 0s
Scan / SAST scan (push) Has been skipped
Scan / Quality scan (push) Has been skipped
Test / Check PR run (push) Failing after 0s
Test / Test (push) Has been skipped
This commit is contained in:
parent
36270ec55a
commit
9b19c71d95
2 changed files with 138 additions and 51 deletions
|
@ -398,6 +398,12 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
request: Fido2CredentialRequest,
|
||||
content: VaultAddEditState.ViewState.Content,
|
||||
) {
|
||||
|
||||
if (fido2CredentialManager.isUserVerified) {
|
||||
registerFido2CredentialToCipher(request, content.toCipherView())
|
||||
return
|
||||
}
|
||||
|
||||
val createOptions = fido2CredentialManager
|
||||
.getPasskeyCreateOptionsOrNull(request.requestJson)
|
||||
?: run {
|
||||
|
@ -514,10 +520,12 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun handleUserVerificationLockOut() {
|
||||
fido2CredentialManager.isUserVerified = false
|
||||
showFido2ErrorDialog()
|
||||
}
|
||||
|
||||
private fun handleUserVerificationSuccess() {
|
||||
fido2CredentialManager.isUserVerified = true
|
||||
specialCircumstanceManager
|
||||
.specialCircumstance
|
||||
?.toFido2RequestOrNull()
|
||||
|
@ -533,10 +541,12 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun handleUserVerificationFail() {
|
||||
fido2CredentialManager.isUserVerified = false
|
||||
showFido2ErrorDialog()
|
||||
}
|
||||
|
||||
private fun handleFido2ErrorDialogDismissed() {
|
||||
fido2CredentialManager.isUserVerified = false
|
||||
clearDialogState()
|
||||
sendEvent(
|
||||
VaultAddEditEvent.CompleteFido2Registration(
|
||||
|
@ -546,6 +556,7 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun handleUserVerificationCancelled() {
|
||||
fido2CredentialManager.isUserVerified = false
|
||||
clearDialogState()
|
||||
sendEvent(
|
||||
VaultAddEditEvent.CompleteFido2Registration(
|
||||
|
@ -555,12 +566,8 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun handleUserVerificationNotSupported() {
|
||||
clearDialogState()
|
||||
sendEvent(
|
||||
VaultAddEditEvent.CompleteFido2Registration(
|
||||
result = Fido2RegisterCredentialResult.Error,
|
||||
),
|
||||
)
|
||||
fido2CredentialManager.isUserVerified = false
|
||||
showFido2ErrorDialog()
|
||||
}
|
||||
|
||||
private fun handleAddNewCustomFieldClick(
|
||||
|
|
|
@ -122,7 +122,10 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
getActivePolicies(type = PolicyTypeJson.PERSONAL_OWNERSHIP)
|
||||
} returns emptyList()
|
||||
}
|
||||
private val fido2CredentialManager = mockk<Fido2CredentialManager>()
|
||||
private val fido2CredentialManager = mockk<Fido2CredentialManager> {
|
||||
every { isUserVerified } returns false
|
||||
every { isUserVerified = any() } just runs
|
||||
}
|
||||
private val vaultRepository: VaultRepository = mockk {
|
||||
every { vaultDataStateFlow } returns mutableVaultDataFlow
|
||||
every { totpCodeFlow } returns totpTestCodeFlow
|
||||
|
@ -855,18 +858,64 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in add mode during fido2, SaveClick should skip user verification when user is verified`() =
|
||||
runTest {
|
||||
val fido2CredentialRequest = createMockFido2CredentialRequest(number = 1)
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.Fido2Save(
|
||||
fido2CredentialRequest = fido2CredentialRequest,
|
||||
)
|
||||
val stateWithName = createVaultAddItemState(
|
||||
vaultAddEditType = VaultAddEditType.AddItem(VaultItemCipherType.LOGIN),
|
||||
commonContentViewState = createCommonContentViewState(
|
||||
name = "mockName-1",
|
||||
),
|
||||
)
|
||||
.copy(shouldExitOnSave = true)
|
||||
|
||||
mutableVaultDataFlow.value = DataState.Loaded(
|
||||
createVaultData(),
|
||||
)
|
||||
val viewModel = createAddVaultItemViewModel(
|
||||
createSavedStateHandleWithState(
|
||||
state = stateWithName,
|
||||
vaultAddEditType = VaultAddEditType.AddItem(VaultItemCipherType.LOGIN),
|
||||
),
|
||||
)
|
||||
|
||||
every { authRepository.activeUserId } returns fido2CredentialRequest.userId
|
||||
every { fido2CredentialManager.isUserVerified } returns true
|
||||
coEvery {
|
||||
fido2CredentialManager.registerFido2Credential(
|
||||
userId = fido2CredentialRequest.userId,
|
||||
fido2CredentialRequest = fido2CredentialRequest,
|
||||
selectedCipherView = any(),
|
||||
)
|
||||
} returns Fido2RegisterCredentialResult.Success(registrationResponse = "mockResponse")
|
||||
|
||||
viewModel.trySendAction(VaultAddEditAction.Common.SaveClick)
|
||||
|
||||
coVerify {
|
||||
fido2CredentialManager.registerFido2Credential(
|
||||
userId = fido2CredentialRequest.userId,
|
||||
fido2CredentialRequest = fido2CredentialRequest,
|
||||
selectedCipherView = any(),
|
||||
)
|
||||
}
|
||||
|
||||
verify(exactly = 0) {
|
||||
fido2CredentialManager.getPasskeyCreateOptionsOrNull(any())
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in add mode during fido2, SaveClick should show fido2 error dialog when create options are null`() =
|
||||
runTest {
|
||||
val mockUserId = "mockUserId"
|
||||
val fido2CredentialRequest = Fido2CredentialRequest(
|
||||
userId = mockUserId,
|
||||
requestJson = "mockRequestJson",
|
||||
packageName = "mockPackageName",
|
||||
signingInfo = mockk<SigningInfo>(),
|
||||
origin = null,
|
||||
)
|
||||
val fido2CredentialRequest = createMockFido2CredentialRequest(number = 1)
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.Fido2Save(
|
||||
fido2CredentialRequest = fido2CredentialRequest,
|
||||
|
@ -910,14 +959,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
@Test
|
||||
fun `in add mode during fido2, SaveClick should emit fido user verification as optional when verification is PREFERRED`() =
|
||||
runTest {
|
||||
val mockUserId = "mockUserId"
|
||||
val fido2CredentialRequest = Fido2CredentialRequest(
|
||||
userId = mockUserId,
|
||||
requestJson = "mockRequestJson",
|
||||
packageName = "mockPackageName",
|
||||
signingInfo = mockk<SigningInfo>(),
|
||||
origin = null,
|
||||
)
|
||||
val fido2CredentialRequest = createMockFido2CredentialRequest(number = 1)
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.Fido2Save(
|
||||
fido2CredentialRequest = fido2CredentialRequest,
|
||||
|
@ -963,14 +1005,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
@Test
|
||||
fun `in add mode during fido2, SaveClick should emit fido user verification as required when request user verification option is REQUIRED`() =
|
||||
runTest {
|
||||
val mockUserId = "mockUserId"
|
||||
val fido2CredentialRequest = Fido2CredentialRequest(
|
||||
userId = mockUserId,
|
||||
requestJson = "mockRequestJson",
|
||||
packageName = "mockPackageName",
|
||||
signingInfo = mockk<SigningInfo>(),
|
||||
origin = null,
|
||||
)
|
||||
val fido2CredentialRequest = createMockFido2CredentialRequest(number = 1)
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.Fido2Save(
|
||||
fido2CredentialRequest = fido2CredentialRequest,
|
||||
|
@ -1012,6 +1047,51 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in add mode during fido2, SaveClick should show Fido2ErrorDialog when user is not verified and registration user verification option is null`() =
|
||||
runTest {
|
||||
val fido2CredentialRequest = createMockFido2CredentialRequest(number = 1)
|
||||
val stateWithName = createVaultAddItemState(
|
||||
vaultAddEditType = VaultAddEditType.AddItem(VaultItemCipherType.LOGIN),
|
||||
commonContentViewState = createCommonContentViewState(
|
||||
name = "mockName-1",
|
||||
),
|
||||
)
|
||||
.copy(shouldExitOnSave = true)
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.Fido2Save(
|
||||
fido2CredentialRequest = fido2CredentialRequest,
|
||||
)
|
||||
every {
|
||||
fido2CredentialManager.getPasskeyCreateOptionsOrNull(
|
||||
requestJson = fido2CredentialRequest.requestJson,
|
||||
)
|
||||
} returns createMockPublicKeyCredentialCreationOptions(
|
||||
number = 1,
|
||||
userVerificationRequirement = null,
|
||||
)
|
||||
mutableVaultDataFlow.value = DataState.Loaded(
|
||||
createVaultData(),
|
||||
)
|
||||
val viewModel = createAddVaultItemViewModel(
|
||||
createSavedStateHandleWithState(
|
||||
state = stateWithName,
|
||||
vaultAddEditType = VaultAddEditType.AddItem(VaultItemCipherType.LOGIN),
|
||||
),
|
||||
)
|
||||
|
||||
viewModel.trySendAction(VaultAddEditAction.Common.SaveClick)
|
||||
|
||||
assertEquals(
|
||||
VaultAddEditState.DialogState.Fido2Error(
|
||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified
|
||||
.asText(),
|
||||
),
|
||||
viewModel.stateFlow.value.dialog,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `in add mode, createCipherInOrganization success should ShowToast and NavigateBack`() =
|
||||
runTest {
|
||||
|
@ -2869,9 +2949,10 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `UserVerificationLockout should display Fido2ErrorDialog`() {
|
||||
fun `UserVerificationLockout should set isUserVerified to false and display Fido2ErrorDialog`() {
|
||||
viewModel.trySendAction(VaultAddEditAction.Common.UserVerificationLockOut)
|
||||
|
||||
verify { fido2CredentialManager.isUserVerified = false }
|
||||
assertEquals(
|
||||
VaultAddEditState.DialogState.Fido2Error(
|
||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
||||
|
@ -2882,10 +2963,11 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `UserVerificationCancelled should clear dialog state and emit CompleteFido2Create with cancelled result`() =
|
||||
fun `UserVerificationCancelled should clear dialog state, set isUserVerified to false, and emit CompleteFido2Create with cancelled result`() =
|
||||
runTest {
|
||||
viewModel.trySendAction(VaultAddEditAction.Common.UserVerificationCancelled)
|
||||
|
||||
verify { fido2CredentialManager.isUserVerified = false }
|
||||
assertNull(viewModel.stateFlow.value.dialog)
|
||||
viewModel.eventFlow.test {
|
||||
assertEquals(
|
||||
|
@ -2899,9 +2981,10 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `UserVerificationFail should display Fido2ErrorDialog`() {
|
||||
fun `UserVerificationFail should set isUserVerified to false, and display Fido2ErrorDialog`() {
|
||||
viewModel.trySendAction(VaultAddEditAction.Common.UserVerificationFail)
|
||||
|
||||
verify { fido2CredentialManager.isUserVerified = false }
|
||||
assertEquals(
|
||||
VaultAddEditState.DialogState.Fido2Error(
|
||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
||||
|
@ -2910,6 +2993,17 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `UserVerificationNotSupported should set isUserVerified to false and show Fido2ErrorDialog`() {
|
||||
viewModel.trySendAction(VaultAddEditAction.Common.UserVerificationNotSupported)
|
||||
verify { fido2CredentialManager.isUserVerified = false }
|
||||
assertEquals(
|
||||
VaultAddEditState.DialogState.Fido2Error(R.string.passkey_operation_failed_because_user_could_not_be_verified.asText()),
|
||||
viewModel.stateFlow.value.dialog,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `UserVerificationSuccess should display Fido2ErrorDialog when SpecialCircumstance is null`() =
|
||||
|
@ -2985,8 +3079,9 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `UserVerificationSuccess should register FIDO 2 credential`() =
|
||||
fun `UserVerificationSuccess should set isUserVerified to true, and register FIDO 2 credential`() =
|
||||
runTest {
|
||||
val mockRequest = createMockFido2CredentialRequest(number = 1)
|
||||
val mockResult = Fido2RegisterCredentialResult.Success(
|
||||
|
@ -3003,10 +3098,12 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
any(),
|
||||
)
|
||||
} returns mockResult
|
||||
every { fido2CredentialManager.isUserVerified } returns true
|
||||
|
||||
viewModel.trySendAction(VaultAddEditAction.Common.UserVerificationSuccess)
|
||||
|
||||
coVerify {
|
||||
fido2CredentialManager.isUserVerified = true
|
||||
fido2CredentialManager.registerFido2Credential(
|
||||
userId = any(),
|
||||
fido2CredentialRequest = mockRequest,
|
||||
|
@ -3026,23 +3123,6 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `UserVerificationNotSupported should clear dialog state and send CompleteFido2Registration event with Error`() =
|
||||
runTest {
|
||||
viewModel.trySendAction(VaultAddEditAction.Common.UserVerificationNotSupported)
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
assertNull(viewModel.stateFlow.value.dialog)
|
||||
assertEquals(
|
||||
VaultAddEditEvent.CompleteFido2Registration(
|
||||
result = Fido2RegisterCredentialResult.Error,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `Fido2RegisterCredentialResult Error should show toast and emit CompleteFido2Registration result`() =
|
||||
|
|
Loading…
Reference in a new issue