mirror of
https://github.com/bitwarden/android.git
synced 2024-11-22 01:16:02 +03:00
[PM-9410] Filter matching FIDO 2 credentials after vault unlock (#3648)
This commit is contained in:
parent
deb8f811e5
commit
b26e1a082e
16 changed files with 369 additions and 23 deletions
|
@ -10,6 +10,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.util.getFido2AssertionRequestOrNull
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2CredentialRequestOrNull
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2GetCredentialsRequestOrNull
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager
|
||||
import com.x8bit.bitwarden.data.autofill.util.getAutofillSaveItemOrNull
|
||||
import com.x8bit.bitwarden.data.autofill.util.getAutofillSelectionDataOrNull
|
||||
|
@ -184,6 +185,7 @@ class MainViewModel @Inject constructor(
|
|||
val hasVaultShortcut = intent.isMyVaultShortcut
|
||||
val fido2CredentialRequestData = intent.getFido2CredentialRequestOrNull()
|
||||
val fido2CredentialAssertionRequest = intent.getFido2AssertionRequestOrNull()
|
||||
val fido2GetCredentialsRequest = intent.getFido2GetCredentialsRequestOrNull()
|
||||
when {
|
||||
passwordlessRequestData != null -> {
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
|
@ -247,6 +249,13 @@ class MainViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
fido2GetCredentialsRequest != null -> {
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.Fido2GetCredentials(
|
||||
fido2GetCredentialsRequest = fido2GetCredentialsRequest,
|
||||
)
|
||||
}
|
||||
|
||||
hasGeneratorShortcut -> {
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.GeneratorShortcut
|
||||
|
|
|
@ -23,7 +23,7 @@ data class Fido2GetCredentialsRequest(
|
|||
val callingAppInfo: CallingAppInfo
|
||||
get() = CallingAppInfo(packageName, signingInfo, origin)
|
||||
|
||||
val getCredentialsRequest: BeginGetPublicKeyCredentialOption
|
||||
val option: BeginGetPublicKeyCredentialOption
|
||||
get() = BeginGetPublicKeyCredentialOption(
|
||||
candidateQueryData,
|
||||
id,
|
||||
|
|
|
@ -6,7 +6,7 @@ import com.bitwarden.fido.Fido2CredentialAutofillView
|
|||
/**
|
||||
* Represents the result of a FIDO 2 Get Credentials request.
|
||||
*/
|
||||
sealed class Fido2GetCredentialResult {
|
||||
sealed class Fido2GetCredentialsResult {
|
||||
/**
|
||||
* Indicates credentials were successfully queried.
|
||||
*
|
||||
|
@ -17,10 +17,10 @@ sealed class Fido2GetCredentialResult {
|
|||
data class Success(
|
||||
val options: BeginGetPublicKeyCredentialOption,
|
||||
val credentials: List<Fido2CredentialAutofillView>,
|
||||
) : Fido2GetCredentialResult()
|
||||
) : Fido2GetCredentialsResult()
|
||||
|
||||
/**
|
||||
* Indicates an error was encountered when querying for matching credentials.
|
||||
*/
|
||||
data object Error : Fido2GetCredentialResult()
|
||||
data object Error : Fido2GetCredentialsResult()
|
||||
}
|
|
@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName
|
|||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Models a FIDO 2 credential creation request options received from a Relying Party (RP).
|
||||
* Models FIDO 2 credential creation request options received from a Relying Party (RP).
|
||||
*/
|
||||
@Serializable
|
||||
data class PasskeyAttestationOptions(
|
||||
|
|
|
@ -4,9 +4,11 @@ import android.content.Intent
|
|||
import android.os.Build
|
||||
import androidx.credentials.CreatePublicKeyCredentialRequest
|
||||
import androidx.credentials.GetPublicKeyCredentialOption
|
||||
import androidx.credentials.provider.BeginGetPublicKeyCredentialOption
|
||||
import androidx.credentials.provider.PendingIntentHandler
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionRequest
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2GetCredentialsRequest
|
||||
import com.x8bit.bitwarden.data.platform.util.isBuildVersionBelow
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.EXTRA_KEY_CIPHER_ID
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.EXTRA_KEY_CREDENTIAL_ID
|
||||
|
@ -72,3 +74,34 @@ fun Intent.getFido2AssertionRequestOrNull(): Fido2CredentialAssertionRequest? {
|
|||
origin = systemRequest.callingAppInfo.origin,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this [Intent] contains a [Fido2GetCredentialsRequest] related to an ongoing FIDO 2
|
||||
* credential lookup process.
|
||||
*/
|
||||
fun Intent.getFido2GetCredentialsRequestOrNull(): Fido2GetCredentialsRequest? {
|
||||
if (isBuildVersionBelow(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)) return null
|
||||
|
||||
val systemRequest = PendingIntentHandler
|
||||
.retrieveBeginGetCredentialRequest(this)
|
||||
?: return null
|
||||
|
||||
val option: BeginGetPublicKeyCredentialOption = systemRequest
|
||||
.beginGetCredentialOptions
|
||||
.firstNotNullOfOrNull { it as? BeginGetPublicKeyCredentialOption }
|
||||
?: return null
|
||||
|
||||
val callingAppInfo = systemRequest
|
||||
.callingAppInfo
|
||||
?: return null
|
||||
|
||||
return Fido2GetCredentialsRequest(
|
||||
candidateQueryData = option.candidateQueryData,
|
||||
id = option.id,
|
||||
requestJson = option.requestJson,
|
||||
clientDataHash = option.clientDataHash,
|
||||
packageName = callingAppInfo.packageName,
|
||||
signingInfo = callingAppInfo.signingInfo,
|
||||
origin = callingAppInfo.origin,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package com.x8bit.bitwarden.ui.autofill.fido2.manager
|
||||
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2GetCredentialResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2GetCredentialsResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult
|
||||
|
||||
/**
|
||||
|
@ -22,5 +22,5 @@ interface Fido2CompletionManager {
|
|||
/**
|
||||
* Complete the FIDO 2 "Get credentials" process with the provided [result].
|
||||
*/
|
||||
fun completeFido2GetCredentialRequest(result: Fido2GetCredentialResult)
|
||||
fun completeFido2GetCredentialRequest(result: Fido2GetCredentialsResult)
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ import androidx.credentials.provider.PendingIntentHandler
|
|||
import androidx.credentials.provider.PublicKeyCredentialEntry
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2GetCredentialResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2GetCredentialsResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.processor.GET_PASSKEY_INTENT
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
|
@ -93,11 +93,11 @@ class Fido2CompletionManagerImpl(
|
|||
}
|
||||
}
|
||||
|
||||
override fun completeFido2GetCredentialRequest(result: Fido2GetCredentialResult) {
|
||||
override fun completeFido2GetCredentialRequest(result: Fido2GetCredentialsResult) {
|
||||
val resultIntent = Intent()
|
||||
val responseBuilder = BeginGetCredentialResponse.Builder()
|
||||
when (result) {
|
||||
is Fido2GetCredentialResult.Success -> {
|
||||
is Fido2GetCredentialsResult.Success -> {
|
||||
val entries = result
|
||||
.credentials
|
||||
.map {
|
||||
|
@ -130,7 +130,7 @@ class Fido2CompletionManagerImpl(
|
|||
)
|
||||
}
|
||||
|
||||
Fido2GetCredentialResult.Error,
|
||||
Fido2GetCredentialsResult.Error,
|
||||
-> {
|
||||
PendingIntentHandler.setGetCredentialException(
|
||||
resultIntent,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package com.x8bit.bitwarden.ui.autofill.fido2.manager
|
||||
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2GetCredentialResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2GetCredentialsResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult
|
||||
|
||||
/**
|
||||
|
@ -13,5 +13,5 @@ object Fido2CompletionManagerUnsupportedApiImpl : Fido2CompletionManager {
|
|||
|
||||
override fun completeFido2Assertion(result: Fido2CredentialAssertionResult) = Unit
|
||||
|
||||
override fun completeFido2GetCredentialRequest(result: Fido2GetCredentialResult) = Unit
|
||||
override fun completeFido2GetCredentialRequest(result: Fido2GetCredentialsResult) = Unit
|
||||
}
|
||||
|
|
|
@ -30,7 +30,9 @@ import com.x8bit.bitwarden.ui.platform.manager.permissions.PermissionsManagerImp
|
|||
* Helper [Composable] that wraps a [content] and provides manager classes via [CompositionLocal].
|
||||
*/
|
||||
@Composable
|
||||
fun LocalManagerProvider(content: @Composable () -> Unit) {
|
||||
fun LocalManagerProvider(
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val activity = LocalContext.current as Activity
|
||||
val fido2IntentManager: IntentManager = IntentManagerImpl(activity)
|
||||
val fido2CompletionManager =
|
||||
|
|
|
@ -169,6 +169,10 @@ fun VaultItemListingScreen(
|
|||
is VaultItemListingEvent.CompleteFido2Assertion -> {
|
||||
fido2CompletionManager.completeFido2Assertion(event.result)
|
||||
}
|
||||
|
||||
is VaultItemListingEvent.CompleteFido2GetCredentialsRequest -> {
|
||||
fido2CompletionManager.completeFido2GetCredentialRequest(event.result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@ import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
|
|||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionRequest
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2GetCredentialsRequest
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2GetCredentialsResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.UserVerificationRequirement
|
||||
|
@ -104,9 +106,13 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
val autofillSelectionData = specialCircumstance as? SpecialCircumstance.AutofillSelection
|
||||
val fido2CreationData = specialCircumstance as? SpecialCircumstance.Fido2Save
|
||||
val fido2AssertionData = specialCircumstance as? SpecialCircumstance.Fido2Assertion
|
||||
val fido2GetCredentialsData =
|
||||
specialCircumstance as? SpecialCircumstance.Fido2GetCredentials
|
||||
val shouldFinishOnComplete = autofillSelectionData
|
||||
?.shouldFinishWhenComplete
|
||||
?: (fido2CreationData != null || fido2AssertionData != null)
|
||||
?: (fido2CreationData != null ||
|
||||
fido2AssertionData != null ||
|
||||
fido2GetCredentialsData != null)
|
||||
val dialogState = fido2CreationData
|
||||
?.let { VaultItemListingState.DialogState.Loading(R.string.loading.asText()) }
|
||||
VaultItemListingState(
|
||||
|
@ -130,6 +136,7 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
hasMasterPassword = userState.activeAccount.hasMasterPassword,
|
||||
fido2CredentialRequest = fido2CreationData?.fido2CredentialRequest,
|
||||
fido2CredentialAssertionRequest = fido2AssertionData?.fido2AssertionRequest,
|
||||
fido2GetCredentialsRequest = fido2GetCredentialsData?.fido2GetCredentialsRequest,
|
||||
isPremium = userState.activeAccount.isPremium,
|
||||
)
|
||||
},
|
||||
|
@ -182,7 +189,8 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
VaultItemListingsAction.Internal.VaultDataReceive(
|
||||
it
|
||||
.filterForAutofillIfNecessary()
|
||||
.filterForFido2CreationIfNecessary(),
|
||||
.filterForFido2CreationIfNecessary()
|
||||
.filterForFidoGetCredentialsIfNecessary(),
|
||||
)
|
||||
}
|
||||
.onEach(::sendAction)
|
||||
|
@ -1240,7 +1248,31 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
|
||||
private fun vaultLoadedReceive(vaultData: DataState.Loaded<VaultData>) {
|
||||
updateStateWithVaultData(vaultData = vaultData.data, clearDialogState = true)
|
||||
sendEvent(VaultItemListingEvent.DismissPullToRefresh)
|
||||
state.fido2GetCredentialsRequest
|
||||
?.let { fido2GetCredentialsRequest ->
|
||||
val relyingPartyId = fido2CredentialManager
|
||||
.getPasskeyAssertionOptionsOrNull(
|
||||
requestJson = fido2GetCredentialsRequest.option.requestJson,
|
||||
)
|
||||
?.relyingPartyId
|
||||
?: run {
|
||||
showFido2ErrorDialog()
|
||||
return
|
||||
}
|
||||
sendEvent(
|
||||
VaultItemListingEvent.CompleteFido2GetCredentialsRequest(
|
||||
Fido2GetCredentialsResult.Success(
|
||||
options = fido2GetCredentialsRequest.option,
|
||||
credentials = vaultData
|
||||
.data
|
||||
.fido2CredentialAutofillViewList
|
||||
?.filter { it.rpId == relyingPartyId }
|
||||
?: emptyList(),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
?: sendEvent(VaultItemListingEvent.DismissPullToRefresh)
|
||||
}
|
||||
|
||||
private fun vaultLoadingReceive() {
|
||||
|
@ -1531,6 +1563,27 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the given vault data and filters it for FIDO 2 credential selection.
|
||||
*/
|
||||
@Suppress("MaxLineLength")
|
||||
private suspend fun DataState<VaultData>.filterForFidoGetCredentialsIfNecessary(): DataState<VaultData> {
|
||||
val request = state.fido2GetCredentialsRequest ?: return this
|
||||
return this.map { vaultData ->
|
||||
val matchUri = request.origin
|
||||
?: request.packageName
|
||||
.toAndroidAppUriString()
|
||||
|
||||
vaultData.copy(
|
||||
cipherViewList = cipherMatchingManager.filterCiphersForMatches(
|
||||
ciphers = vaultData.cipherViewList,
|
||||
matchUri = matchUri,
|
||||
),
|
||||
fido2CredentialAutofillViewList = vaultData.toFido2CredentialAutofillViews(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt and filter the fido 2 autofill credentials.
|
||||
*/
|
||||
|
@ -1582,6 +1635,7 @@ data class VaultItemListingState(
|
|||
val autofillSelectionData: AutofillSelectionData? = null,
|
||||
val fido2CredentialRequest: Fido2CredentialRequest? = null,
|
||||
val fido2CredentialAssertionRequest: Fido2CredentialAssertionRequest? = null,
|
||||
val fido2GetCredentialsRequest: Fido2GetCredentialsRequest? = null,
|
||||
val shouldFinishOnComplete: Boolean = false,
|
||||
val hasMasterPassword: Boolean,
|
||||
val isPremium: Boolean,
|
||||
|
@ -2077,6 +2131,15 @@ sealed class VaultItemListingEvent {
|
|||
data class CompleteFido2Assertion(
|
||||
val result: Fido2CredentialAssertionResult,
|
||||
) : VaultItemListingEvent()
|
||||
|
||||
/**
|
||||
* FIDO 2 credential lookup result has been received and the process is ready to be completed.
|
||||
*
|
||||
* @property result The result of querying for matching FIDO 2 credentials.
|
||||
*/
|
||||
data class CompleteFido2GetCredentialsRequest(
|
||||
val result: Fido2GetCredentialsResult,
|
||||
) : VaultItemListingEvent()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,11 +12,14 @@ 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.Fido2CredentialAssertionRequest
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2GetCredentialsRequest
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.createMockFido2CredentialAssertionRequest
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.createMockFido2CredentialRequest
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.createMockFido2GetCredentialsRequest
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2AssertionRequestOrNull
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2CredentialRequestOrNull
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2GetCredentialsRequestOrNull
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManagerImpl
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
|
||||
|
@ -494,6 +497,27 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on ReceiveFirstIntent with fido2 get credentials request data should set the special circumstance to Fido2GetCredentials`() {
|
||||
val viewModel = createViewModel()
|
||||
val mockGetCredentialsRequest = createMockFido2GetCredentialsRequest(number = 1)
|
||||
val mockIntent = createMockFido2GetCredentialsIntent(mockGetCredentialsRequest)
|
||||
|
||||
every { intentManager.getShareDataFromIntent(mockIntent) } returns null
|
||||
|
||||
viewModel.trySendAction(
|
||||
MainAction.ReceiveFirstIntent(
|
||||
intent = mockIntent,
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
SpecialCircumstance.Fido2GetCredentials(mockGetCredentialsRequest),
|
||||
specialCircumstanceManager.specialCircumstance,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on ReceiveNewIntent with share data should set the special circumstance to ShareNewSend`() {
|
||||
|
@ -731,3 +755,18 @@ private fun createMockFido2AssertionIntent(
|
|||
every { isMyVaultShortcut } returns false
|
||||
every { isPasswordGeneratorShortcut } returns false
|
||||
}
|
||||
|
||||
private fun createMockFido2GetCredentialsIntent(
|
||||
fido2GetCredentialsRequest: Fido2GetCredentialsRequest = createMockFido2GetCredentialsRequest(
|
||||
number = 1,
|
||||
),
|
||||
): Intent = mockk<Intent> {
|
||||
every { getFido2GetCredentialsRequestOrNull() } returns fido2GetCredentialsRequest
|
||||
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
||||
every { getAutofillSelectionDataOrNull() } returns null
|
||||
every { getAutofillSaveItemOrNull() } returns null
|
||||
every { getFido2CredentialRequestOrNull() } returns null
|
||||
every { getFido2AssertionRequestOrNull() } returns null
|
||||
every { isMyVaultShortcut } returns false
|
||||
every { isPasswordGeneratorShortcut } returns false
|
||||
}
|
||||
|
|
|
@ -2,16 +2,21 @@ package com.x8bit.bitwarden.data.autofill.fido2.util
|
|||
|
||||
import android.content.Intent
|
||||
import android.content.pm.SigningInfo
|
||||
import android.service.credentials.BeginGetCredentialRequest
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.credentials.CreatePasswordRequest
|
||||
import androidx.credentials.CreatePublicKeyCredentialRequest
|
||||
import androidx.credentials.GetPasswordOption
|
||||
import androidx.credentials.GetPublicKeyCredentialOption
|
||||
import androidx.credentials.provider.BeginGetPasswordOption
|
||||
import androidx.credentials.provider.BeginGetPublicKeyCredentialOption
|
||||
import androidx.credentials.provider.CallingAppInfo
|
||||
import androidx.credentials.provider.PendingIntentHandler
|
||||
import androidx.credentials.provider.ProviderCreateCredentialRequest
|
||||
import androidx.credentials.provider.ProviderGetCredentialRequest
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionRequest
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2GetCredentialsRequest
|
||||
import com.x8bit.bitwarden.data.platform.util.isBuildVersionBelow
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.EXTRA_KEY_CIPHER_ID
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.EXTRA_KEY_CREDENTIAL_ID
|
||||
|
@ -285,4 +290,102 @@ class Fido2IntentUtilsTest {
|
|||
|
||||
assertNull(assertionRequest)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `getFido2GetCredentialsRequestOrNull should return Fido2GetCredentialRequest when present`() {
|
||||
val intent = mockk<Intent>()
|
||||
val mockOption = BeginGetPublicKeyCredentialOption(
|
||||
candidateQueryData = bundleOf(),
|
||||
id = "mockId",
|
||||
requestJson = "mockRequestJson",
|
||||
clientDataHash = byteArrayOf(0),
|
||||
)
|
||||
val mockCallingAppInfo = CallingAppInfo(
|
||||
packageName = "mockPackageName",
|
||||
signingInfo = SigningInfo(),
|
||||
origin = "mockOrigin",
|
||||
)
|
||||
|
||||
every {
|
||||
PendingIntentHandler.retrieveBeginGetCredentialRequest(intent)
|
||||
} returns mockk {
|
||||
every { beginGetCredentialOptions } returns listOf(mockOption)
|
||||
every { callingAppInfo } returns mockCallingAppInfo
|
||||
}
|
||||
|
||||
val result = intent.getFido2GetCredentialsRequestOrNull()
|
||||
|
||||
assertEquals(
|
||||
Fido2GetCredentialsRequest(
|
||||
candidateQueryData = mockOption.candidateQueryData,
|
||||
id = mockOption.id,
|
||||
requestJson = mockOption.requestJson,
|
||||
clientDataHash = mockOption.clientDataHash,
|
||||
packageName = mockCallingAppInfo.packageName,
|
||||
signingInfo = mockCallingAppInfo.signingInfo,
|
||||
origin = mockCallingAppInfo.origin,
|
||||
),
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getGido2GetCredentialsRequestOrNull should return null when build version is below 34`() {
|
||||
val intent = mockk<Intent>()
|
||||
every { isBuildVersionBelow(34) } returns true
|
||||
|
||||
val result = intent.getFido2GetCredentialsRequestOrNull()
|
||||
|
||||
assertNull(result)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `getFido2GetCredentialsRequestOrNull should return null when retrieveBeginGetCredentialRequest is null`() {
|
||||
val intent = mockk<Intent> {
|
||||
every {
|
||||
getParcelableExtra(
|
||||
"android.service.credentials.extra.BEGIN_GET_CREDENTIAL_REQUEST",
|
||||
BeginGetCredentialRequest::class.java,
|
||||
)
|
||||
} returns null
|
||||
}
|
||||
every { PendingIntentHandler.retrieveProviderGetCredentialRequest(intent) } returns null
|
||||
val result = intent.getFido2GetCredentialsRequestOrNull()
|
||||
assertNull(result)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `getFido2GetCredentialRequestOrNull should return null when no passkey credential options are present`() {
|
||||
val intent = mockk<Intent>()
|
||||
every { PendingIntentHandler.retrieveBeginGetCredentialRequest(intent) } returns mockk {
|
||||
every { beginGetCredentialOptions } returns listOf(mockk<BeginGetPasswordOption>())
|
||||
}
|
||||
val result = intent.getFido2GetCredentialsRequestOrNull()
|
||||
assertNull(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getFido2GetCredentialRequestOrNull should return null when calling app info is null`() {
|
||||
val intent = mockk<Intent>()
|
||||
val mockOption = createMockBeginGetPublicKeyCredentialOption(number = 1)
|
||||
every { PendingIntentHandler.retrieveBeginGetCredentialRequest(intent) } returns mockk {
|
||||
every { beginGetCredentialOptions } returns listOf(mockOption)
|
||||
every { callingAppInfo } returns null
|
||||
}
|
||||
val result = intent.getFido2GetCredentialsRequestOrNull()
|
||||
assertNull(result)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createMockBeginGetPublicKeyCredentialOption(
|
||||
number: Int,
|
||||
): BeginGetPublicKeyCredentialOption =
|
||||
BeginGetPublicKeyCredentialOption(
|
||||
candidateQueryData = bundleOf(),
|
||||
id = "mockId-$number",
|
||||
requestJson = "mockRequestJson-$number",
|
||||
clientDataHash = byteArrayOf(0),
|
||||
)
|
||||
|
|
|
@ -9,7 +9,7 @@ import androidx.credentials.provider.PublicKeyCredentialEntry
|
|||
import com.bitwarden.fido.Fido2CredentialAutofillView
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2GetCredentialResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2GetCredentialsResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.processor.GET_PASSKEY_INTENT
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockFido2CredentialAutofillView
|
||||
|
@ -76,7 +76,7 @@ class Fido2CompletionManagerTest {
|
|||
|
||||
@Test
|
||||
fun `completeFido2GetCredentials should perform no operations`() {
|
||||
val mockGetCredentialResult = mockk<Fido2GetCredentialResult>()
|
||||
val mockGetCredentialResult = mockk<Fido2GetCredentialsResult>()
|
||||
fido2CompletionManager.completeFido2GetCredentialRequest(mockGetCredentialResult)
|
||||
verify {
|
||||
mockGetCredentialResult wasNot Called
|
||||
|
@ -174,7 +174,7 @@ class Fido2CompletionManagerTest {
|
|||
fun `completeFido2GetCredentials should set BeginGetCredentialResponse, set activity result, then finish activity when result is Success`() {
|
||||
fido2CompletionManager
|
||||
.completeFido2GetCredentialRequest(
|
||||
Fido2GetCredentialResult.Success(
|
||||
Fido2GetCredentialsResult.Success(
|
||||
options = mockk(),
|
||||
credentials = emptyList(),
|
||||
),
|
||||
|
@ -210,7 +210,7 @@ class Fido2CompletionManagerTest {
|
|||
|
||||
fido2CompletionManager
|
||||
.completeFido2GetCredentialRequest(
|
||||
Fido2GetCredentialResult.Success(
|
||||
Fido2GetCredentialsResult.Success(
|
||||
options = mockk(),
|
||||
credentials = mockFido2AutofillViewList,
|
||||
),
|
||||
|
@ -258,7 +258,7 @@ class Fido2CompletionManagerTest {
|
|||
|
||||
fido2CompletionManager
|
||||
.completeFido2GetCredentialRequest(
|
||||
Fido2GetCredentialResult.Success(
|
||||
Fido2GetCredentialsResult.Success(
|
||||
options = mockk(),
|
||||
credentials = mockFido2AutofillViewList,
|
||||
),
|
||||
|
@ -284,7 +284,7 @@ class Fido2CompletionManagerTest {
|
|||
@Test
|
||||
fun `completeFido2GetCredentials should set GetCredentialException, set activity result, then finish activity when result is Error`() {
|
||||
fido2CompletionManager
|
||||
.completeFido2GetCredentialRequest(Fido2GetCredentialResult.Error)
|
||||
.completeFido2GetCredentialRequest(Fido2GetCredentialsResult.Error)
|
||||
verifyActivityResultIsSetAndFinishedAfter {
|
||||
PendingIntentHandler.setGetCredentialException(any(), any())
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import androidx.compose.ui.test.performTextInput
|
|||
import androidx.core.net.toUri
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2GetCredentialsResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
|
@ -89,6 +90,7 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
|||
private val fido2CompletionManager: Fido2CompletionManager = mockk {
|
||||
every { completeFido2Registration(any()) } just runs
|
||||
every { completeFido2Assertion(any()) } just runs
|
||||
every { completeFido2GetCredentialRequest(any()) } just runs
|
||||
}
|
||||
private val biometricsManager: BiometricsManager = mockk()
|
||||
private val mutableEventFlow = bufferedMutableSharedFlow<VaultItemListingEvent>()
|
||||
|
@ -1791,6 +1793,15 @@ class VaultItemListingScreenTest : BaseComposeTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `CompleteFido2GetCredentials event should call Fido2CompletionManager with result`() {
|
||||
val result = Fido2GetCredentialsResult.Success(mockk(), mockk())
|
||||
mutableEventFlow.tryEmit(VaultItemListingEvent.CompleteFido2GetCredentialsRequest(result))
|
||||
verify {
|
||||
fido2CompletionManager.completeFido2GetCredentialRequest(result)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Fido2UserVerification event should perform user verification when it is supported`() {
|
||||
every {
|
||||
|
|
|
@ -15,6 +15,7 @@ import com.x8bit.bitwarden.data.auth.repository.model.VaultUnlockType
|
|||
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2GetCredentialsRequest
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.UserVerificationRequirement
|
||||
|
@ -1381,6 +1382,87 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `vaultDataStateFlow Loaded with Fido2GetCredentials special circumstance should update ViewState to Content with filtered data`() =
|
||||
runTest {
|
||||
setupMockUri()
|
||||
|
||||
val cipherView1 = createMockCipherView(
|
||||
number = 1,
|
||||
fido2Credentials = createMockSdkFido2CredentialList(number = 1),
|
||||
)
|
||||
val cipherView2 = createMockCipherView(
|
||||
number = 2,
|
||||
fido2Credentials = createMockSdkFido2CredentialList(number = 1),
|
||||
)
|
||||
|
||||
every {
|
||||
fido2CredentialManager.getPasskeyAssertionOptionsOrNull(any())
|
||||
} returns createMockPasskeyAssertionOptions(
|
||||
number = 1,
|
||||
)
|
||||
coEvery {
|
||||
vaultRepository.getDecryptedFido2CredentialAutofillViews(
|
||||
cipherViewList = listOf(cipherView1, cipherView2),
|
||||
)
|
||||
} returns DecryptFido2CredentialAutofillViewResult.Success(emptyList())
|
||||
|
||||
mockFilteredCiphers = listOf(cipherView1)
|
||||
|
||||
val fido2GetCredentialRequest = Fido2GetCredentialsRequest(
|
||||
requestJson = "{}",
|
||||
packageName = "com.x8bit.bitwarden",
|
||||
signingInfo = SigningInfo(),
|
||||
origin = "mockOrigin",
|
||||
candidateQueryData = mockk(),
|
||||
clientDataHash = byteArrayOf(0),
|
||||
id = "mockId",
|
||||
)
|
||||
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.Fido2GetCredentials(
|
||||
fido2GetCredentialsRequest = fido2GetCredentialRequest,
|
||||
)
|
||||
val dataState = DataState.Loaded(
|
||||
data = VaultData(
|
||||
cipherViewList = listOf(cipherView1, cipherView2),
|
||||
folderViewList = listOf(createMockFolderView(number = 1)),
|
||||
collectionViewList = listOf(createMockCollectionView(number = 1)),
|
||||
sendViewList = listOf(createMockSendView(number = 1)),
|
||||
),
|
||||
)
|
||||
|
||||
val viewModel = createVaultItemListingViewModel()
|
||||
|
||||
mutableVaultDataStateFlow.value = dataState
|
||||
|
||||
assertEquals(
|
||||
createVaultItemListingState(
|
||||
viewState = VaultItemListingState.ViewState.Content(
|
||||
displayCollectionList = emptyList(),
|
||||
displayItemList = listOf(
|
||||
createMockDisplayItemForCipher(number = 1)
|
||||
.copy(
|
||||
secondSubtitleTestTag = "PasskeySite",
|
||||
),
|
||||
),
|
||||
displayFolderList = emptyList(),
|
||||
),
|
||||
)
|
||||
.copy(
|
||||
fido2GetCredentialsRequest = fido2GetCredentialRequest,
|
||||
shouldFinishOnComplete = true,
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
coVerify {
|
||||
vaultRepository.getDecryptedFido2CredentialAutofillViews(
|
||||
cipherViewList = listOf(cipherView1, cipherView2),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `vaultDataStateFlow Loaded with empty items should update ViewState to NoItems`() =
|
||||
runTest {
|
||||
|
|
Loading…
Reference in a new issue