[PM-8137] Populate add cipher form during passkey creation (#1431)

This commit is contained in:
Patrick Honkonen 2024-06-13 10:11:30 -04:00 committed by Álison Fernandes
parent 4128f16025
commit d534acdf7e
13 changed files with 384 additions and 22 deletions

View file

@ -1,6 +1,7 @@
package com.x8bit.bitwarden.data.autofill.fido2.manager
import com.bitwarden.vault.CipherView
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.PublicKeyCredentialCreationOptions
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CreateCredentialResult
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult
@ -17,6 +18,13 @@ interface Fido2CredentialManager {
fido2CredentialRequest: Fido2CredentialRequest,
): Fido2ValidateOriginResult
/**
* Attempt to extract FIDO 2 passkey creation options from the system [requestJson], or null.
*/
fun getPasskeyCreateOptionsOrNull(
requestJson: String,
): PublicKeyCredentialCreationOptions?
/**
* Attempt to create a FIDO2 credential from the given [credentialRequest] and associate it to
* the given [cipherView].

View file

@ -40,6 +40,17 @@ class Fido2CredentialManagerImpl(
}
}
override fun getPasskeyCreateOptionsOrNull(
requestJson: String,
): PublicKeyCredentialCreationOptions? =
try {
json.decodeFromString<PublicKeyCredentialCreationOptions>(requestJson)
} catch (e: SerializationException) {
null
} catch (e: IllegalArgumentException) {
null
}
@Suppress("ReturnCount")
private suspend fun validateCallingApplicationAssetLinks(
fido2CredentialRequest: Fido2CredentialRequest,

View file

@ -1,5 +1,6 @@
package com.x8bit.bitwarden.data.platform.manager.util
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
@ -31,3 +32,12 @@ fun SpecialCircumstance.toAutofillSelectionDataOrNull(): AutofillSelectionData?
SpecialCircumstance.VaultShortcut -> null
is SpecialCircumstance.Fido2Save -> null
}
/**
* Returns [Fido2CredentialRequest] when contained in the given [SpecialCircumstance].
*/
fun SpecialCircumstance.toFido2RequestOrNull(): Fido2CredentialRequest? =
when (this) {
is SpecialCircumstance.Fido2Save -> this.fido2CredentialRequest
else -> null
}

View file

@ -10,13 +10,12 @@ import java.security.MessageDigest
* returned. If this [CallingAppInfo] is a native RP application the package name will be returned.
* Otherwise, `null` is returned.
*/
fun CallingAppInfo.getFido2RpOrNull(): String? {
return if (isOriginPopulated()) {
fun CallingAppInfo.getFido2RpIdOrNull(): String? =
if (isOriginPopulated()) {
origin?.toHostOrPathOrNull()
} else {
packageName
}
}
/**
* Returns the signing certificate hash formatted as a hex string.

View file

@ -8,11 +8,13 @@ import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.model.BreachCountResult
import com.x8bit.bitwarden.data.auth.repository.model.UserState
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
import com.x8bit.bitwarden.data.platform.manager.util.toAutofillSaveItemOrNull
import com.x8bit.bitwarden.data.platform.manager.util.toAutofillSelectionDataOrNull
import com.x8bit.bitwarden.data.platform.manager.util.toFido2RequestOrNull
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.data.platform.repository.model.DataState
import com.x8bit.bitwarden.data.platform.repository.util.takeUntilLoaded
@ -80,6 +82,7 @@ class VaultAddEditViewModel @Inject constructor(
private val clipboardManager: BitwardenClipboardManager,
private val policyManager: PolicyManager,
private val vaultRepository: VaultRepository,
private val fido2CredentialManager: Fido2CredentialManager,
generatorRepository: GeneratorRepository,
private val settingsRepository: SettingsRepository,
private val specialCircumstanceManager: SpecialCircumstanceManager,
@ -102,6 +105,16 @@ class VaultAddEditViewModel @Inject constructor(
.specialCircumstance
?.toAutofillSelectionDataOrNull()
val fido2CreationRequest = specialCircumstanceManager
.specialCircumstance
?.toFido2RequestOrNull()
val fido2CreationOptions = fido2CreationRequest
?.let { request ->
fido2CredentialManager
.getPasskeyCreateOptionsOrNull(request.requestJson)
}
val dialogState =
if (!settingsRepository.initialAutofillDialogShown &&
vaultAddEditType is VaultAddEditType.AddItem &&
@ -120,6 +133,11 @@ class VaultAddEditViewModel @Inject constructor(
?.toDefaultAddTypeContent(isIndividualVaultDisabled)
?: autofillSaveItem
?.toDefaultAddTypeContent(isIndividualVaultDisabled)
?: fido2CreationRequest
?.toDefaultAddTypeContent(
creationOptions = fido2CreationOptions,
isIndividualVaultDisabled = isIndividualVaultDisabled,
)
?: VaultAddEditState.ViewState.Content(
common = VaultAddEditState.ViewState.Content.Common(),
isIndividualVaultDisabled = isIndividualVaultDisabled,
@ -131,9 +149,9 @@ class VaultAddEditViewModel @Inject constructor(
is VaultAddEditType.CloneItem -> VaultAddEditState.ViewState.Loading
},
dialog = dialogState,
// Set special conditions for autofill save
shouldShowCloseButton = autofillSaveItem == null,
shouldExitOnSave = autofillSaveItem != null,
// Set special conditions for autofill and fido2 save
shouldShowCloseButton = autofillSaveItem == null && fido2CreationOptions == null,
shouldExitOnSave = autofillSaveItem != null || fido2CreationOptions != null,
)
},
) {

View file

@ -0,0 +1,53 @@
package com.x8bit.bitwarden.ui.vault.feature.addedit.util
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.PublicKeyCredentialCreationOptions
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
import com.x8bit.bitwarden.data.platform.util.toUriOrNull
import com.x8bit.bitwarden.ui.platform.base.util.toAndroidAppUriString
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriItem
import java.util.UUID
/**
* Returns pre-filled content that may be used for an "add" type
* [VaultAddEditState.ViewState.Content] during FIDO 2 credential creation.
*/
fun Fido2CredentialRequest.toDefaultAddTypeContent(
creationOptions: PublicKeyCredentialCreationOptions?,
isIndividualVaultDisabled: Boolean,
): VaultAddEditState.ViewState.Content {
val rpUri = origin
?.toUriOrNull()
?.toString()
?: packageName
.toAndroidAppUriString()
val rpName = creationOptions
?.relyingParty
?.name
.orEmpty()
val username = creationOptions
?.user
?.name
.orEmpty()
return VaultAddEditState.ViewState.Content(
common = VaultAddEditState.ViewState.Content.Common(
name = rpName,
),
isIndividualVaultDisabled = isIndividualVaultDisabled,
type = VaultAddEditState.ViewState.Content.ItemType.Login(
username = username,
uriList = listOf(
UriItem(
id = UUID.randomUUID().toString(),
uri = rpUri,
match = null,
checksum = null,
),
),
),
)
}

View file

@ -24,7 +24,7 @@ import com.x8bit.bitwarden.data.platform.repository.model.DataState
import com.x8bit.bitwarden.data.platform.repository.util.baseIconUrl
import com.x8bit.bitwarden.data.platform.repository.util.baseWebSendUrl
import com.x8bit.bitwarden.data.platform.repository.util.map
import com.x8bit.bitwarden.data.platform.util.getFido2RpOrNull
import com.x8bit.bitwarden.data.platform.util.getFido2RpIdOrNull
import com.x8bit.bitwarden.data.vault.datasource.network.model.PolicyTypeJson
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.data.vault.repository.model.DeleteSendResult
@ -920,7 +920,7 @@ data class VaultItemListingState(
?.let { R.string.items_for_uri.asText(it) }
?: fido2CredentialRequest
?.callingAppInfo
?.getFido2RpOrNull()
?.getFido2RpIdOrNull()
?.let { R.string.items_for_uri.asText(it) }
?: itemListingType.titleText

View file

@ -12,6 +12,7 @@ import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult
import com.x8bit.bitwarden.data.platform.manager.AssetManager
import com.x8bit.bitwarden.data.platform.util.asFailure
import com.x8bit.bitwarden.data.platform.util.asSuccess
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.createMockPublicKeyCredentialCreationOptions
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
@ -23,6 +24,7 @@ import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.Json
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
@ -40,18 +42,10 @@ class Fido2CredentialManagerTest {
getDigitalAssetLinkForRp(relyingParty = any())
} returns DEFAULT_STATEMENT_LIST.asSuccess()
}
private val mockCreateOptions = mockk<PublicKeyCredentialCreationOptions> {
every {
relyingParty
} returns PublicKeyCredentialCreationOptions.PublicKeyCredentialRpEntity(
name = "mockRpName",
id = "www.bitwarden.com",
)
}
private val json = mockk<Json> {
every {
decodeFromString<PublicKeyCredentialCreationOptions>(any())
} returns mockCreateOptions
} returns createMockPublicKeyCredentialCreationOptions(number = 1)
}
private val mockPrivilegedCallingAppInfo = mockk<CallingAppInfo> {
every { packageName } returns "com.x8bit.bitwarden"
@ -255,6 +249,44 @@ class Fido2CredentialManagerTest {
)
}
@Test
fun `getPasskeyCreateOptionsOrNull should return passkey options when deserialized`() =
runTest {
assertEquals(
createMockPublicKeyCredentialCreationOptions(number = 1),
fido2CredentialManager.getPasskeyCreateOptionsOrNull(
requestJson = "",
),
)
}
@Test
fun `getPasskeyCreateOptionsOrNull should return null when deserialization fails`() =
runTest {
every {
json.decodeFromString<PublicKeyCredentialCreationOptions>(any())
} throws SerializationException()
assertNull(
fido2CredentialManager.getPasskeyCreateOptionsOrNull(
requestJson = "",
),
)
}
@Suppress("MaxLineLength")
@Test
fun `getPasskeyCreateOptionsOrNull should return null when IllegalArgumentException is thrown`() =
runTest {
every {
json.decodeFromString<PublicKeyCredentialCreationOptions>(any())
} throws IllegalArgumentException()
assertNull(
fido2CredentialManager.getPasskeyCreateOptionsOrNull(
requestJson = "",
),
)
}
@Test
fun `createCredentialForCipher should return error while not implemented`() {
val result = fido2CredentialManager.createCredentialForCipher(

View file

@ -1,5 +1,7 @@
package com.x8bit.bitwarden.data.platform.manager.util
import android.content.pm.SigningInfo
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
@ -38,6 +40,9 @@ class SpecialCircumstanceExtensionsTest {
passwordlessRequestData = mockk(),
shouldFinishWhenComplete = true,
),
SpecialCircumstance.Fido2Save(
fido2CredentialRequest = mockk(),
),
SpecialCircumstance.GeneratorShortcut,
SpecialCircumstance.VaultShortcut,
)
@ -77,6 +82,9 @@ class SpecialCircumstanceExtensionsTest {
passwordlessRequestData = mockk(),
shouldFinishWhenComplete = true,
),
SpecialCircumstance.Fido2Save(
fido2CredentialRequest = mockk(),
),
SpecialCircumstance.GeneratorShortcut,
SpecialCircumstance.VaultShortcut,
)
@ -84,4 +92,49 @@ class SpecialCircumstanceExtensionsTest {
assertNull(specialCircumstance.toAutofillSelectionDataOrNull())
}
}
@Test
fun `toFido2RequestOrNull should return a null value for other types`() {
listOf(
SpecialCircumstance.AutofillSelection(
autofillSelectionData = mockk(),
shouldFinishWhenComplete = true,
),
SpecialCircumstance.AutofillSave(
autofillSaveItem = mockk(),
),
SpecialCircumstance.ShareNewSend(
data = mockk(),
shouldFinishWhenComplete = true,
),
SpecialCircumstance.PasswordlessRequest(
passwordlessRequestData = mockk(),
shouldFinishWhenComplete = true,
),
SpecialCircumstance.GeneratorShortcut,
SpecialCircumstance.VaultShortcut,
)
.forEach { specialCircumstance ->
assertNull(specialCircumstance.toFido2RequestOrNull())
}
}
@Test
fun `toFido2RequestOrNull should return a non-null value for Fido2Save`() {
val fido2CredentialRequest = Fido2CredentialRequest(
userId = "mockUserId",
requestJson = "mockRequestJson",
packageName = "mockPackageName",
signingInfo = SigningInfo(),
origin = "mockOrigin",
)
assertEquals(
fido2CredentialRequest,
SpecialCircumstance
.Fido2Save(
fido2CredentialRequest = fido2CredentialRequest,
)
.toFido2RequestOrNull(),
)
}
}

View file

@ -37,7 +37,7 @@ class CallingAppInfoExtensionsTest {
every { origin } returns "invalidUri9685%^$^&(*"
}
assertNull(mockCallingAppInfo.getFido2RpOrNull())
assertNull(mockCallingAppInfo.getFido2RpIdOrNull())
}
@Test
@ -47,7 +47,7 @@ class CallingAppInfoExtensionsTest {
every { origin } returns "mockUri"
}
assertEquals("mockUri", mockCallingAppInfo.getFido2RpOrNull())
assertEquals("mockUri", mockCallingAppInfo.getFido2RpIdOrNull())
}
@Test
@ -57,7 +57,7 @@ class CallingAppInfoExtensionsTest {
every { origin } returns null
}
assertNull(mockCallingAppInfo.getFido2RpOrNull())
assertNull(mockCallingAppInfo.getFido2RpIdOrNull())
}
@Test
@ -67,7 +67,7 @@ class CallingAppInfoExtensionsTest {
every { packageName } returns "mockPackageName"
}
assertEquals("mockPackageName", mockCallingAppInfo.getFido2RpOrNull())
assertEquals("mockPackageName", mockCallingAppInfo.getFido2RpIdOrNull())
}
@Test

View file

@ -1,5 +1,6 @@
package com.x8bit.bitwarden.ui.vault.feature.addedit
import android.content.pm.SigningInfo
import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test
import app.cash.turbine.turbineScope
@ -14,6 +15,8 @@ import com.x8bit.bitwarden.data.auth.repository.model.BreachCountResult
import com.x8bit.bitwarden.data.auth.repository.model.Organization
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.autofill.fido2.model.Fido2CredentialRequest
import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
@ -45,6 +48,7 @@ import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldAction
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldType
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriItem
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.toCustomField
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.createMockPublicKeyCredentialCreationOptions
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.toDefaultAddTypeContent
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.toViewState
import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType
@ -113,6 +117,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
getActivePolicies(type = PolicyTypeJson.PERSONAL_OWNERSHIP)
} returns emptyList()
}
private val fido2CredentialManager = mockk<Fido2CredentialManager>()
private val vaultRepository: VaultRepository = mockk {
every { vaultDataStateFlow } returns mutableVaultDataFlow
every { totpCodeFlow } returns totpTestCodeFlow
@ -293,6 +298,43 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
}
}
@Test
fun `initial add state should be correct when fido2 save`() = runTest {
val fido2CredentialRequest = Fido2CredentialRequest(
userId = "mockUserId-1",
requestJson = "mockRequestJson-1",
packageName = "mockPackageName-1",
signingInfo = SigningInfo(),
origin = null,
)
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.Fido2Save(
fido2CredentialRequest = fido2CredentialRequest,
)
val fido2ContentState = fido2CredentialRequest.toDefaultAddTypeContent(
creationOptions = createMockPublicKeyCredentialCreationOptions(number = 1),
isIndividualVaultDisabled = false,
)
val vaultAddEditType = VaultAddEditType.AddItem(VaultItemCipherType.LOGIN)
val initState = createVaultAddItemState(
vaultAddEditType = vaultAddEditType,
commonContentViewState = fido2ContentState.common,
typeContentViewState = fido2ContentState.type,
)
val viewModel = createAddVaultItemViewModel(
savedStateHandle = createSavedStateHandleWithState(
state = initState,
vaultAddEditType = vaultAddEditType,
),
)
assertEquals(
initState,
viewModel.stateFlow.value,
)
verify(exactly = 1) {
vaultRepository.vaultDataStateFlow
}
}
@Test
fun `initial edit state should be correct`() = runTest {
val vaultAddEditType = VaultAddEditType.EditItem(DEFAULT_EDIT_ITEM_ID)
@ -1889,6 +1931,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
policyManager = policyManager,
resourceManager = resourceManager,
authRepository = authRepository,
fido2CredentialManager = fido2CredentialManager,
settingsRepository = settingsRepository,
clock = fixedClock,
)
@ -2467,6 +2510,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
specialCircumstanceManager = specialCircumstanceManager,
policyManager = policyManager,
resourceManager = bitwardenResourceManager,
fido2CredentialManager = fido2CredentialManager,
authRepository = authRepository,
settingsRepository = settingsRepository,
clock = clock,
@ -2702,7 +2746,6 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
viewModel.trySendAction(action)
assertEquals(expectedState, viewModel.stateFlow.value.viewState)
}
//endregion Helper functions
}

View file

@ -0,0 +1,99 @@
package com.x8bit.bitwarden.ui.vault.feature.addedit.util
import android.content.pm.SigningInfo
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriItem
import io.mockk.every
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.util.UUID
class Fido2CredentialRequestExtensionsTest {
@BeforeEach
fun setUp() {
mockkStatic(UUID::class)
}
@AfterEach
fun tearDown() {
unmockkStatic(UUID::class)
}
@Suppress("MaxLineLength")
@Test
fun `toDefaultAddTypeContent should return the correct content when calling app is not privileged`() {
every { UUID.randomUUID().toString() } returns "uuid"
assertEquals(
VaultAddEditState.ViewState.Content(
common = VaultAddEditState.ViewState.Content.Common(
name = "mockPublicKeyCredentialRpEntityName-1",
),
isIndividualVaultDisabled = false,
type = VaultAddEditState.ViewState.Content.ItemType.Login(
username = "mockPublicKeyCredentialUserEntityName-1",
uriList = listOf(
UriItem(
id = "uuid",
uri = "androidapp://mockPackageName-1",
match = null,
checksum = null,
),
),
),
),
Fido2CredentialRequest(
userId = "mockUserId-1",
requestJson = "mockRequestJson-1",
packageName = "mockPackageName-1",
signingInfo = SigningInfo(),
origin = null,
)
.toDefaultAddTypeContent(
creationOptions = createMockPublicKeyCredentialCreationOptions(1),
isIndividualVaultDisabled = false,
),
)
}
@Suppress("MaxLineLength")
@Test
fun `toDefaultAddTypeContent should return the correct content when calling app is privileged`() {
every { UUID.randomUUID().toString() } returns "uuid"
assertEquals(
VaultAddEditState.ViewState.Content(
common = VaultAddEditState.ViewState.Content.Common(
name = "mockPublicKeyCredentialRpEntityName-1",
),
isIndividualVaultDisabled = false,
type = VaultAddEditState.ViewState.Content.ItemType.Login(
username = "mockPublicKeyCredentialUserEntityName-1",
uriList = listOf(
UriItem(
id = "uuid",
uri = "www.test.com",
match = null,
checksum = null,
),
),
),
),
Fido2CredentialRequest(
userId = "mockUserId-1",
requestJson = "mockRequestJson-1",
packageName = "mockPackageName-1",
signingInfo = SigningInfo(),
origin = "www.test.com",
)
.toDefaultAddTypeContent(
creationOptions = createMockPublicKeyCredentialCreationOptions(number = 1),
isIndividualVaultDisabled = false,
),
)
}
}

View file

@ -0,0 +1,36 @@
package com.x8bit.bitwarden.ui.vault.feature.addedit.util
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.PublicKeyCredentialCreationOptions
/**
* Returns a mock FIDO 2 [PublicKeyCredentialCreationOptions] object to simulate a credential
* creation request.
*/
fun createMockPublicKeyCredentialCreationOptions(number: Int) =
PublicKeyCredentialCreationOptions(
authenticatorSelection = PublicKeyCredentialCreationOptions
.AuthenticatorSelectionCriteria(),
challenge = "mockPublicKeyCredentialCreationOptionsChallenge-$number",
excludeCredentials = listOf(
PublicKeyCredentialCreationOptions.PublicKeyCredentialDescriptor(
type = "mockPublicKeyCredentialDescriptorType-$number",
id = "mockPublicKeyCredentialDescriptorId-$number",
transports = listOf("mockPublicKeyCredentialDescriptorTransports-$number"),
),
),
pubKeyCredParams = listOf(
PublicKeyCredentialCreationOptions.PublicKeyCredentialParameters(
type = "PublicKeyCredentialParametersType-$number",
alg = number.toLong(),
),
),
relyingParty = PublicKeyCredentialCreationOptions.PublicKeyCredentialRpEntity(
name = "mockPublicKeyCredentialRpEntityName-$number",
id = "mockPublicKeyCredentialRpEntity-$number",
),
user = PublicKeyCredentialCreationOptions.PublicKeyCredentialUserEntity(
name = "mockPublicKeyCredentialUserEntityName-$number",
id = "mockPublicKeyCredentialUserEntityId-$number",
displayName = "mockPublicKeyCredentialUserEntityDisplayName-$number",
),
)