mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 07:05:35 +03:00
[PM-10644] Re-prompt master password for protected passkeys (#3682)
This commit is contained in:
parent
3819916241
commit
1e5bee2917
2 changed files with 137 additions and 1 deletions
|
@ -4,6 +4,7 @@ import android.os.Parcelable
|
|||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.fido.Fido2CredentialAutofillView
|
||||
import com.bitwarden.vault.CipherRepromptType
|
||||
import com.bitwarden.vault.CipherView
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
|
@ -1413,7 +1414,24 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
showFido2ErrorDialog()
|
||||
return
|
||||
}
|
||||
verifyUserAndAuthenticateCredential(request, selectedCipher)
|
||||
|
||||
if (state.hasMasterPassword &&
|
||||
selectedCipher.reprompt == CipherRepromptType.PASSWORD
|
||||
) {
|
||||
repromptMasterPasswordForFido2Assertion(request.cipherId)
|
||||
} else {
|
||||
verifyUserAndAuthenticateCredential(request, selectedCipher)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun repromptMasterPasswordForFido2Assertion(cipherId: String) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialogState = VaultItemListingState.DialogState.Fido2MasterPasswordPrompt(
|
||||
selectedCipherId = cipherId,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.content.pm.SigningInfo
|
|||
import android.net.Uri
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.vault.CipherRepromptType
|
||||
import com.bitwarden.vault.CipherView
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
|
@ -2697,6 +2698,123 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `Fido2AssertionRequest should prompt for master password when passkey is protected and user has master password`() {
|
||||
val mockAssertionRequest = createMockFido2CredentialAssertionRequest(number = 1)
|
||||
.copy(cipherId = "mockId-1")
|
||||
val mockFido2CredentialList = createMockSdkFido2CredentialList(number = 1)
|
||||
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Assertion(
|
||||
mockAssertionRequest,
|
||||
)
|
||||
every { fido2CredentialManager.isUserVerified } returns true
|
||||
every {
|
||||
fido2CredentialManager.getPasskeyAssertionOptionsOrNull(
|
||||
mockAssertionRequest.requestJson,
|
||||
)
|
||||
} returns createMockPasskeyAssertionOptions(
|
||||
number = 1,
|
||||
userVerificationRequirement = UserVerificationRequirement.PREFERRED,
|
||||
)
|
||||
every {
|
||||
vaultRepository
|
||||
.ciphersStateFlow
|
||||
.value
|
||||
.data
|
||||
} returns listOf(
|
||||
createMockCipherView(
|
||||
number = 1,
|
||||
fido2Credentials = mockFido2CredentialList,
|
||||
repromptType = CipherRepromptType.PASSWORD,
|
||||
),
|
||||
)
|
||||
every {
|
||||
fido2CredentialManager.getPasskeyAssertionOptionsOrNull(
|
||||
mockAssertionRequest.requestJson,
|
||||
)
|
||||
} returns createMockPasskeyAssertionOptions(number = 1)
|
||||
every { authRepository.activeUserId } returns null
|
||||
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
|
||||
assertEquals(
|
||||
VaultItemListingState.DialogState.Fido2MasterPasswordPrompt(
|
||||
selectedCipherId = mockAssertionRequest.cipherId!!,
|
||||
),
|
||||
viewModel.stateFlow.value.dialogState,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `Fido2AssertionRequest should not re-prompt master password when user does not have a master password`() =
|
||||
runTest {
|
||||
val mockAssertionRequest = createMockFido2CredentialAssertionRequest(number = 1)
|
||||
.copy(cipherId = "mockId-1")
|
||||
val mockFido2CredentialList = createMockSdkFido2CredentialList(number = 1)
|
||||
val mockCipherView = createMockCipherView(
|
||||
number = 1,
|
||||
fido2Credentials = mockFido2CredentialList,
|
||||
repromptType = CipherRepromptType.PASSWORD,
|
||||
)
|
||||
mutableUserStateFlow.value = DEFAULT_USER_STATE.copy(
|
||||
accounts = listOf(
|
||||
DEFAULT_ACCOUNT.copy(
|
||||
vaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
|
||||
|
||||
trustedDevice = UserState.TrustedDevice(
|
||||
isDeviceTrusted = true,
|
||||
hasMasterPassword = false,
|
||||
hasAdminApproval = true,
|
||||
hasLoginApprovingDevice = true,
|
||||
hasResetPasswordPermission = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Assertion(
|
||||
mockAssertionRequest,
|
||||
)
|
||||
every { fido2CredentialManager.isUserVerified } returns true
|
||||
every {
|
||||
fido2CredentialManager.getPasskeyAssertionOptionsOrNull(
|
||||
mockAssertionRequest.requestJson,
|
||||
)
|
||||
} returns createMockPasskeyAssertionOptions(
|
||||
number = 1,
|
||||
userVerificationRequirement = UserVerificationRequirement.PREFERRED,
|
||||
)
|
||||
every {
|
||||
vaultRepository
|
||||
.ciphersStateFlow
|
||||
.value
|
||||
.data
|
||||
} returns listOf(mockCipherView)
|
||||
every {
|
||||
fido2CredentialManager.getPasskeyAssertionOptionsOrNull(
|
||||
mockAssertionRequest.requestJson,
|
||||
)
|
||||
} returns createMockPasskeyAssertionOptions(number = 1)
|
||||
coEvery {
|
||||
fido2CredentialManager.authenticateFido2Credential(
|
||||
DEFAULT_USER_STATE.activeUserId,
|
||||
mockAssertionRequest,
|
||||
mockCipherView,
|
||||
)
|
||||
} returns Fido2CredentialAssertionResult.Success("responseJson")
|
||||
|
||||
createVaultItemListingViewModel()
|
||||
|
||||
coVerify {
|
||||
fido2CredentialManager.authenticateFido2Credential(
|
||||
userId = any(),
|
||||
request = any(),
|
||||
selectedCipherView = any(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `UserVerificationLockout should display Fido2ErrorDialog and set isUserVerified to false`() {
|
||||
|
|
Loading…
Reference in a new issue