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,
|
request: Fido2CredentialRequest,
|
||||||
content: VaultAddEditState.ViewState.Content,
|
content: VaultAddEditState.ViewState.Content,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
if (fido2CredentialManager.isUserVerified) {
|
||||||
|
registerFido2CredentialToCipher(request, content.toCipherView())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val createOptions = fido2CredentialManager
|
val createOptions = fido2CredentialManager
|
||||||
.getPasskeyCreateOptionsOrNull(request.requestJson)
|
.getPasskeyCreateOptionsOrNull(request.requestJson)
|
||||||
?: run {
|
?: run {
|
||||||
|
@ -514,10 +520,12 @@ class VaultAddEditViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleUserVerificationLockOut() {
|
private fun handleUserVerificationLockOut() {
|
||||||
|
fido2CredentialManager.isUserVerified = false
|
||||||
showFido2ErrorDialog()
|
showFido2ErrorDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleUserVerificationSuccess() {
|
private fun handleUserVerificationSuccess() {
|
||||||
|
fido2CredentialManager.isUserVerified = true
|
||||||
specialCircumstanceManager
|
specialCircumstanceManager
|
||||||
.specialCircumstance
|
.specialCircumstance
|
||||||
?.toFido2RequestOrNull()
|
?.toFido2RequestOrNull()
|
||||||
|
@ -533,10 +541,12 @@ class VaultAddEditViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleUserVerificationFail() {
|
private fun handleUserVerificationFail() {
|
||||||
|
fido2CredentialManager.isUserVerified = false
|
||||||
showFido2ErrorDialog()
|
showFido2ErrorDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleFido2ErrorDialogDismissed() {
|
private fun handleFido2ErrorDialogDismissed() {
|
||||||
|
fido2CredentialManager.isUserVerified = false
|
||||||
clearDialogState()
|
clearDialogState()
|
||||||
sendEvent(
|
sendEvent(
|
||||||
VaultAddEditEvent.CompleteFido2Registration(
|
VaultAddEditEvent.CompleteFido2Registration(
|
||||||
|
@ -546,6 +556,7 @@ class VaultAddEditViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleUserVerificationCancelled() {
|
private fun handleUserVerificationCancelled() {
|
||||||
|
fido2CredentialManager.isUserVerified = false
|
||||||
clearDialogState()
|
clearDialogState()
|
||||||
sendEvent(
|
sendEvent(
|
||||||
VaultAddEditEvent.CompleteFido2Registration(
|
VaultAddEditEvent.CompleteFido2Registration(
|
||||||
|
@ -555,12 +566,8 @@ class VaultAddEditViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleUserVerificationNotSupported() {
|
private fun handleUserVerificationNotSupported() {
|
||||||
clearDialogState()
|
fido2CredentialManager.isUserVerified = false
|
||||||
sendEvent(
|
showFido2ErrorDialog()
|
||||||
VaultAddEditEvent.CompleteFido2Registration(
|
|
||||||
result = Fido2RegisterCredentialResult.Error,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleAddNewCustomFieldClick(
|
private fun handleAddNewCustomFieldClick(
|
||||||
|
|
|
@ -122,7 +122,10 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
getActivePolicies(type = PolicyTypeJson.PERSONAL_OWNERSHIP)
|
getActivePolicies(type = PolicyTypeJson.PERSONAL_OWNERSHIP)
|
||||||
} returns emptyList()
|
} 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 {
|
private val vaultRepository: VaultRepository = mockk {
|
||||||
every { vaultDataStateFlow } returns mutableVaultDataFlow
|
every { vaultDataStateFlow } returns mutableVaultDataFlow
|
||||||
every { totpCodeFlow } returns totpTestCodeFlow
|
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")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `in add mode during fido2, SaveClick should show fido2 error dialog when create options are null`() =
|
fun `in add mode during fido2, SaveClick should show fido2 error dialog when create options are null`() =
|
||||||
runTest {
|
runTest {
|
||||||
val mockUserId = "mockUserId"
|
val mockUserId = "mockUserId"
|
||||||
val fido2CredentialRequest = Fido2CredentialRequest(
|
val fido2CredentialRequest = createMockFido2CredentialRequest(number = 1)
|
||||||
userId = mockUserId,
|
|
||||||
requestJson = "mockRequestJson",
|
|
||||||
packageName = "mockPackageName",
|
|
||||||
signingInfo = mockk<SigningInfo>(),
|
|
||||||
origin = null,
|
|
||||||
)
|
|
||||||
specialCircumstanceManager.specialCircumstance =
|
specialCircumstanceManager.specialCircumstance =
|
||||||
SpecialCircumstance.Fido2Save(
|
SpecialCircumstance.Fido2Save(
|
||||||
fido2CredentialRequest = fido2CredentialRequest,
|
fido2CredentialRequest = fido2CredentialRequest,
|
||||||
|
@ -910,14 +959,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
@Test
|
@Test
|
||||||
fun `in add mode during fido2, SaveClick should emit fido user verification as optional when verification is PREFERRED`() =
|
fun `in add mode during fido2, SaveClick should emit fido user verification as optional when verification is PREFERRED`() =
|
||||||
runTest {
|
runTest {
|
||||||
val mockUserId = "mockUserId"
|
val fido2CredentialRequest = createMockFido2CredentialRequest(number = 1)
|
||||||
val fido2CredentialRequest = Fido2CredentialRequest(
|
|
||||||
userId = mockUserId,
|
|
||||||
requestJson = "mockRequestJson",
|
|
||||||
packageName = "mockPackageName",
|
|
||||||
signingInfo = mockk<SigningInfo>(),
|
|
||||||
origin = null,
|
|
||||||
)
|
|
||||||
specialCircumstanceManager.specialCircumstance =
|
specialCircumstanceManager.specialCircumstance =
|
||||||
SpecialCircumstance.Fido2Save(
|
SpecialCircumstance.Fido2Save(
|
||||||
fido2CredentialRequest = fido2CredentialRequest,
|
fido2CredentialRequest = fido2CredentialRequest,
|
||||||
|
@ -963,14 +1005,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
@Test
|
@Test
|
||||||
fun `in add mode during fido2, SaveClick should emit fido user verification as required when request user verification option is REQUIRED`() =
|
fun `in add mode during fido2, SaveClick should emit fido user verification as required when request user verification option is REQUIRED`() =
|
||||||
runTest {
|
runTest {
|
||||||
val mockUserId = "mockUserId"
|
val fido2CredentialRequest = createMockFido2CredentialRequest(number = 1)
|
||||||
val fido2CredentialRequest = Fido2CredentialRequest(
|
|
||||||
userId = mockUserId,
|
|
||||||
requestJson = "mockRequestJson",
|
|
||||||
packageName = "mockPackageName",
|
|
||||||
signingInfo = mockk<SigningInfo>(),
|
|
||||||
origin = null,
|
|
||||||
)
|
|
||||||
specialCircumstanceManager.specialCircumstance =
|
specialCircumstanceManager.specialCircumstance =
|
||||||
SpecialCircumstance.Fido2Save(
|
SpecialCircumstance.Fido2Save(
|
||||||
fido2CredentialRequest = fido2CredentialRequest,
|
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
|
@Test
|
||||||
fun `in add mode, createCipherInOrganization success should ShowToast and NavigateBack`() =
|
fun `in add mode, createCipherInOrganization success should ShowToast and NavigateBack`() =
|
||||||
runTest {
|
runTest {
|
||||||
|
@ -2869,9 +2949,10 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `UserVerificationLockout should display Fido2ErrorDialog`() {
|
fun `UserVerificationLockout should set isUserVerified to false and display Fido2ErrorDialog`() {
|
||||||
viewModel.trySendAction(VaultAddEditAction.Common.UserVerificationLockOut)
|
viewModel.trySendAction(VaultAddEditAction.Common.UserVerificationLockOut)
|
||||||
|
|
||||||
|
verify { fido2CredentialManager.isUserVerified = false }
|
||||||
assertEquals(
|
assertEquals(
|
||||||
VaultAddEditState.DialogState.Fido2Error(
|
VaultAddEditState.DialogState.Fido2Error(
|
||||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
message = R.string.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
||||||
|
@ -2882,10 +2963,11 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@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 {
|
runTest {
|
||||||
viewModel.trySendAction(VaultAddEditAction.Common.UserVerificationCancelled)
|
viewModel.trySendAction(VaultAddEditAction.Common.UserVerificationCancelled)
|
||||||
|
|
||||||
|
verify { fido2CredentialManager.isUserVerified = false }
|
||||||
assertNull(viewModel.stateFlow.value.dialog)
|
assertNull(viewModel.stateFlow.value.dialog)
|
||||||
viewModel.eventFlow.test {
|
viewModel.eventFlow.test {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
|
@ -2899,9 +2981,10 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `UserVerificationFail should display Fido2ErrorDialog`() {
|
fun `UserVerificationFail should set isUserVerified to false, and display Fido2ErrorDialog`() {
|
||||||
viewModel.trySendAction(VaultAddEditAction.Common.UserVerificationFail)
|
viewModel.trySendAction(VaultAddEditAction.Common.UserVerificationFail)
|
||||||
|
|
||||||
|
verify { fido2CredentialManager.isUserVerified = false }
|
||||||
assertEquals(
|
assertEquals(
|
||||||
VaultAddEditState.DialogState.Fido2Error(
|
VaultAddEditState.DialogState.Fido2Error(
|
||||||
message = R.string.passkey_operation_failed_because_user_could_not_be_verified.asText(),
|
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")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `UserVerificationSuccess should display Fido2ErrorDialog when SpecialCircumstance is null`() =
|
fun `UserVerificationSuccess should display Fido2ErrorDialog when SpecialCircumstance is null`() =
|
||||||
|
@ -2985,8 +3079,9 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `UserVerificationSuccess should register FIDO 2 credential`() =
|
fun `UserVerificationSuccess should set isUserVerified to true, and register FIDO 2 credential`() =
|
||||||
runTest {
|
runTest {
|
||||||
val mockRequest = createMockFido2CredentialRequest(number = 1)
|
val mockRequest = createMockFido2CredentialRequest(number = 1)
|
||||||
val mockResult = Fido2RegisterCredentialResult.Success(
|
val mockResult = Fido2RegisterCredentialResult.Success(
|
||||||
|
@ -3003,10 +3098,12 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
any(),
|
any(),
|
||||||
)
|
)
|
||||||
} returns mockResult
|
} returns mockResult
|
||||||
|
every { fido2CredentialManager.isUserVerified } returns true
|
||||||
|
|
||||||
viewModel.trySendAction(VaultAddEditAction.Common.UserVerificationSuccess)
|
viewModel.trySendAction(VaultAddEditAction.Common.UserVerificationSuccess)
|
||||||
|
|
||||||
coVerify {
|
coVerify {
|
||||||
|
fido2CredentialManager.isUserVerified = true
|
||||||
fido2CredentialManager.registerFido2Credential(
|
fido2CredentialManager.registerFido2Credential(
|
||||||
userId = any(),
|
userId = any(),
|
||||||
fido2CredentialRequest = mockRequest,
|
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")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `Fido2RegisterCredentialResult Error should show toast and emit CompleteFido2Registration result`() =
|
fun `Fido2RegisterCredentialResult Error should show toast and emit CompleteFido2Registration result`() =
|
||||||
|
|
Loading…
Reference in a new issue