Compare commits

...

7 Commits

Author SHA1 Message Date
J-Jamet
964f4ae236 fix: Passkey subdomain #2291 2025-11-26 12:36:24 +01:00
J-Jamet
9146315001 fix: Remove Passkey error to be able to select search elements #2282 2025-11-26 11:22:47 +01:00
J-Jamet
ed095ad0a7 fix: Passwordless for multiple CredentialIds 2025-11-25 13:06:19 +01:00
J-Jamet
82a8776911 fix: Update Credential API code #2141 #2283 2025-11-25 12:24:59 +01:00
J-Jamet
753e9c4721 fix: Update Changelog #2282 2025-11-24 20:42:43 +01:00
J-Jamet
b64094ed20 fix: Show toast error in passwordless mode 2025-11-24 20:35:54 +01:00
J-Jamet
bc854c63f7 fix: Select passkey in passwordless mode #2282 2025-11-24 15:25:37 +01:00
14 changed files with 313 additions and 78 deletions

View File

@@ -1,6 +1,7 @@
KeePassDX(4.3.0) KeePassDX(4.3.0)
* Manual change of app language #1884 #1990 * Manual change of app language #1884 #1990
* Fix autofill username detection #2276 * Fix autofill username detection #2276
* Fix Passkey in passwordless mode #2282
KeePassDX(4.2.4) KeePassDX(4.2.4)
* Fix remembering database location #2262 * Fix remembering database location #2262

View File

@@ -42,6 +42,7 @@ import androidx.credentials.provider.CredentialEntry
import androidx.credentials.provider.CredentialProviderService import androidx.credentials.provider.CredentialProviderService
import androidx.credentials.provider.ProviderClearCredentialStateRequest import androidx.credentials.provider.ProviderClearCredentialStateRequest
import androidx.credentials.provider.PublicKeyCredentialEntry import androidx.credentials.provider.PublicKeyCredentialEntry
import com.kunzisoft.encrypt.Base64Helper.Companion.b64Encode
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.buildIcon import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.buildIcon
import com.kunzisoft.keepass.credentialprovider.SpecialMode import com.kunzisoft.keepass.credentialprovider.SpecialMode
@@ -90,9 +91,13 @@ class PasskeyProviderService : CredentialProviderService() {
super.onDestroy() super.onDestroy()
} }
private fun buildPasskeySearchInfo(relyingParty: String): SearchInfo { private fun buildPasskeySearchInfo(
relyingParty: String,
credentialIds: List<String> = listOf()
): SearchInfo {
return SearchInfo().apply { return SearchInfo().apply {
this.relyingParty = relyingParty this.relyingParty = relyingParty
this.credentialIds = credentialIds
} }
} }
@@ -108,6 +113,7 @@ class PasskeyProviderService : CredentialProviderService() {
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(javaClass.simpleName, "onBeginGetCredentialRequest error", e) Log.e(javaClass.simpleName, "onBeginGetCredentialRequest error", e)
toastError(e)
callback.onError(GetCredentialUnknownException()) callback.onError(GetCredentialUnknownException())
} }
} }
@@ -136,12 +142,14 @@ class PasskeyProviderService : CredentialProviderService() {
option: BeginGetPublicKeyCredentialOption, option: BeginGetPublicKeyCredentialOption,
callback: (List<CredentialEntry>) -> Unit callback: (List<CredentialEntry>) -> Unit
) { ) {
val passkeyEntries: MutableList<CredentialEntry> = mutableListOf() val passkeyEntries: MutableList<CredentialEntry> = mutableListOf()
val relyingPartyId = PublicKeyCredentialRequestOptions(option.requestJson).rpId val publicKeyCredentialRequestOptions = PublicKeyCredentialRequestOptions(option.requestJson)
val searchInfo = buildPasskeySearchInfo(relyingPartyId) val relyingPartyId = publicKeyCredentialRequestOptions.rpId
Log.d(TAG, "Build passkey search for relying party $relyingPartyId") val credentialIdList = publicKeyCredentialRequestOptions.allowCredentials
.map { b64Encode(it.id) }
val searchInfo = buildPasskeySearchInfo(relyingPartyId, credentialIdList)
Log.d(TAG, "Build passkey search for relying party $relyingPartyId, credentialIds $credentialIdList")
SearchHelper.checkAutoSearchInfo( SearchHelper.checkAutoSearchInfo(
context = this, context = this,
database = mDatabase, database = mDatabase,
@@ -175,26 +183,36 @@ class PasskeyProviderService : CredentialProviderService() {
}, },
onItemNotFound = { _ -> onItemNotFound = { _ ->
Log.w(TAG, "No passkey found in the database with this relying party : $relyingPartyId") Log.w(TAG, "No passkey found in the database with this relying party : $relyingPartyId")
Log.d(TAG, "Add pending intent for passkey selection in opened database") if (credentialIdList.isEmpty()) {
PasskeyLauncherActivity.getPendingIntent( Log.d(TAG, "Add pending intent for passkey selection in opened database")
context = applicationContext, PasskeyLauncherActivity.getPendingIntent(
specialMode = SpecialMode.SELECTION, context = applicationContext,
searchInfo = searchInfo specialMode = SpecialMode.SELECTION,
)?.let { pendingIntent -> searchInfo = searchInfo
passkeyEntries.add( )?.let { pendingIntent ->
PublicKeyCredentialEntry( passkeyEntries.add(
context = applicationContext, PublicKeyCredentialEntry(
username = getString(R.string.passkey_database_username), context = applicationContext,
displayName = getString(R.string.passkey_selection_description), username = getString(R.string.passkey_database_username),
icon = defaultIcon, displayName = getString(R.string.passkey_selection_description),
pendingIntent = pendingIntent, icon = defaultIcon,
beginGetPublicKeyCredentialOption = option, pendingIntent = pendingIntent,
lastUsedTime = Instant.now(), beginGetPublicKeyCredentialOption = option,
isAutoSelectAllowed = isAutoSelectAllowed lastUsedTime = Instant.now(),
isAutoSelectAllowed = isAutoSelectAllowed
)
)
}
callback(passkeyEntries)
} else {
throw IOException(
getString(
R.string.error_passkey_credential_id,
relyingPartyId,
credentialIdList
) )
) )
} }
callback(passkeyEntries)
}, },
onDatabaseClosed = { onDatabaseClosed = {
Log.d(TAG, "Add pending intent for passkey selection in closed database") Log.d(TAG, "Add pending intent for passkey selection in closed database")
@@ -287,10 +305,11 @@ class PasskeyProviderService : CredentialProviderService() {
getString(R.string.passkey_database_username) getString(R.string.passkey_database_username)
else databaseName else databaseName
val createEntries: MutableList<CreateEntry> = mutableListOf() val createEntries: MutableList<CreateEntry> = mutableListOf()
val relyingPartyId = PublicKeyCredentialCreationOptions( val publicKeyCredentialCreationOptions = PublicKeyCredentialCreationOptions(
requestJson = request.requestJson, requestJson = request.requestJson,
clientDataHash = request.clientDataHash clientDataHash = request.clientDataHash
).relyingPartyEntity.id )
val relyingPartyId = publicKeyCredentialCreationOptions.relyingPartyEntity.id
val searchInfo = buildPasskeySearchInfo(relyingPartyId) val searchInfo = buildPasskeySearchInfo(relyingPartyId)
Log.d(TAG, "Build passkey search for relying party $relyingPartyId") Log.d(TAG, "Build passkey search for relying party $relyingPartyId")
SearchHelper.checkAutoSearchInfo( SearchHelper.checkAutoSearchInfo(

View File

@@ -16,7 +16,25 @@
package com.kunzisoft.keepass.credentialprovider.passkey.data package com.kunzisoft.keepass.credentialprovider.passkey.data
data class PublicKeyCredentialRpEntity(val name: String, val id: String) import com.kunzisoft.encrypt.Base64Helper
import org.json.JSONObject
data class PublicKeyCredentialRpEntity(
val name: String,
val id: String
) {
companion object {
fun JSONObject.getPublicKeyCredentialRpEntity(
parameterName: String
): PublicKeyCredentialRpEntity {
val rpJson = this.getJSONObject(parameterName)
return PublicKeyCredentialRpEntity(
rpJson.getString("name"),
rpJson.getString("id")
)
}
}
}
data class PublicKeyCredentialUserEntity( data class PublicKeyCredentialUserEntity(
val name: String, val name: String,
@@ -42,9 +60,41 @@ data class PublicKeyCredentialUserEntity(
result = 31 * result + displayName.hashCode() result = 31 * result + displayName.hashCode()
return result return result
} }
companion object {
fun JSONObject.getPublicKeyCredentialUserEntity(
parameterName: String
): PublicKeyCredentialUserEntity {
val rpUser = this.getJSONObject(parameterName)
return PublicKeyCredentialUserEntity(
rpUser.getString("name"),
Base64Helper.b64Decode(rpUser.getString("id")),
rpUser.getString("displayName")
)
}
}
} }
data class PublicKeyCredentialParameters(val type: String, val alg: Long) data class PublicKeyCredentialParameters(
val type: String,
val alg: Long
) {
companion object {
fun JSONObject.getPublicKeyCredentialParametersList(
parameterName: String
): List<PublicKeyCredentialParameters> {
val pubKeyCredParamsJson = this.getJSONArray(parameterName)
val pubKeyCredParamsTmp: MutableList<PublicKeyCredentialParameters> = mutableListOf()
for (i in 0 until pubKeyCredParamsJson.length()) {
val e = pubKeyCredParamsJson.getJSONObject(i)
pubKeyCredParamsTmp.add(
PublicKeyCredentialParameters(e.getString("type"), e.getLong("alg"))
)
}
return pubKeyCredParamsTmp.toList()
}
}
}
data class PublicKeyCredentialDescriptor( data class PublicKeyCredentialDescriptor(
val type: String, val type: String,
@@ -70,11 +120,97 @@ data class PublicKeyCredentialDescriptor(
result = 31 * result + transports.hashCode() result = 31 * result + transports.hashCode()
return result return result
} }
companion object {
fun JSONObject.getPublicKeyCredentialDescriptorList(
parameterName: String
): List<PublicKeyCredentialDescriptor> {
val credentialsJson = this.getJSONArray(parameterName)
val credentialsTmp: MutableList<PublicKeyCredentialDescriptor> = mutableListOf()
for (i in 0 until credentialsJson.length()) {
val credentialJson = credentialsJson.getJSONObject(i)
val transports: MutableList<String> = mutableListOf()
val transportsJson = credentialJson.getJSONArray("transports")
for (j in 0 until transportsJson.length()) {
transports.add(transportsJson.getString(j))
}
credentialsTmp.add(
PublicKeyCredentialDescriptor(
type = credentialJson.getString("type"),
id = Base64Helper.b64Decode(credentialJson.getString("id")),
transports = transports
)
)
}
return credentialsTmp.toList()
}
}
} }
data class AuthenticatorSelectionCriteria( data class AuthenticatorSelectionCriteria(
val authenticatorAttachment: String, val authenticatorAttachment: String? = null,
val residentKey: String, val residentKey: ResidentKeyRequirement? = null,
val requireResidentKey: Boolean = false, val requireResidentKey: Boolean?,
val userVerification: String = "preferred" val userVerification: UserVerificationRequirement? = UserVerificationRequirement.PREFERRED
) ) {
companion object {
fun JSONObject.getAuthenticatorSelectionCriteria(
parameterName: String
): AuthenticatorSelectionCriteria {
val authenticatorSelection = this.optJSONObject(parameterName)
?: return AuthenticatorSelectionCriteria(requireResidentKey = null)
val authenticatorAttachment = if (!authenticatorSelection.isNull("authenticatorAttachment"))
authenticatorSelection.getString("authenticatorAttachment") else null
var residentKey = if (!authenticatorSelection.isNull("residentKey"))
ResidentKeyRequirement.fromString(authenticatorSelection.getString("residentKey"))
else null
val requireResidentKey = authenticatorSelection.optBoolean("requireResidentKey", false)
val userVerification = UserVerificationRequirement.fromString(authenticatorSelection.optString("userVerification", "preferred"))
// https://www.w3.org/TR/webauthn-3/#enumdef-residentkeyrequirement
if (residentKey == null) {
residentKey = if (requireResidentKey) {
ResidentKeyRequirement.REQUIRED
} else {
ResidentKeyRequirement.DISCOURAGED
}
}
return AuthenticatorSelectionCriteria(
authenticatorAttachment = authenticatorAttachment,
residentKey = residentKey,
requireResidentKey = requireResidentKey,
userVerification = userVerification
)
}
}
}
// https://www.w3.org/TR/webauthn-3/#enumdef-residentkeyrequirement
enum class ResidentKeyRequirement(val value: String) {
DISCOURAGED("discouraged"),
PREFERRED("preferred"),
REQUIRED("required");
override fun toString(): String {
return value
}
companion object {
fun fromString(value: String): ResidentKeyRequirement? {
return ResidentKeyRequirement.entries.firstOrNull { it.value == value }
}
}
}
// https://www.w3.org/TR/webauthn-3/#enumdef-userverificationrequirement
enum class UserVerificationRequirement(val value: String) {
REQUIRED("required"),
PREFERRED("preferred"),
DISCOURAGED("discouraged");
override fun toString(): String {
return value
}
companion object {
fun fromString(value: String): UserVerificationRequirement? {
return UserVerificationRequirement.entries.firstOrNull { it.value == value }
}
}
}

View File

@@ -20,52 +20,42 @@
package com.kunzisoft.keepass.credentialprovider.passkey.data package com.kunzisoft.keepass.credentialprovider.passkey.data
import com.kunzisoft.encrypt.Base64Helper import com.kunzisoft.encrypt.Base64Helper
import com.kunzisoft.keepass.credentialprovider.passkey.data.AuthenticatorSelectionCriteria.Companion.getAuthenticatorSelectionCriteria
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialDescriptor.Companion.getPublicKeyCredentialDescriptorList
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialParameters.Companion.getPublicKeyCredentialParametersList
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialRpEntity.Companion.getPublicKeyCredentialRpEntity
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialUserEntity.Companion.getPublicKeyCredentialUserEntity
import org.json.JSONObject import org.json.JSONObject
class PublicKeyCredentialCreationOptions( class PublicKeyCredentialCreationOptions(
requestJson: String, requestJson: String,
var clientDataHash: ByteArray? var clientDataHash: ByteArray?
) { ) {
val json: JSONObject = JSONObject(requestJson) private val json: JSONObject = JSONObject(requestJson)
val relyingPartyEntity: PublicKeyCredentialRpEntity val relyingPartyEntity: PublicKeyCredentialRpEntity =
val userEntity: PublicKeyCredentialUserEntity json.getPublicKeyCredentialRpEntity("rp")
val challenge: ByteArray
val pubKeyCredParams: List<PublicKeyCredentialParameters>
var timeout: Long val userEntity: PublicKeyCredentialUserEntity =
var excludeCredentials: List<PublicKeyCredentialDescriptor> json.getPublicKeyCredentialUserEntity("user")
var authenticatorSelection: AuthenticatorSelectionCriteria
var attestation: String
init { val challenge: ByteArray =
val rpJson = json.getJSONObject("rp") Base64Helper.b64Decode(json.getString("challenge"))
relyingPartyEntity = PublicKeyCredentialRpEntity(rpJson.getString("name"), rpJson.getString("id"))
val rpUser = json.getJSONObject("user")
val userId = Base64Helper.b64Decode(rpUser.getString("id"))
userEntity =
PublicKeyCredentialUserEntity(
rpUser.getString("name"),
userId,
rpUser.getString("displayName")
)
challenge = Base64Helper.b64Decode(json.getString("challenge"))
val pubKeyCredParamsJson = json.getJSONArray("pubKeyCredParams")
val pubKeyCredParamsTmp: MutableList<PublicKeyCredentialParameters> = mutableListOf()
for (i in 0 until pubKeyCredParamsJson.length()) {
val e = pubKeyCredParamsJson.getJSONObject(i)
pubKeyCredParamsTmp.add(
PublicKeyCredentialParameters(e.getString("type"), e.getLong("alg"))
)
}
pubKeyCredParams = pubKeyCredParamsTmp.toList()
timeout = json.optLong("timeout", 0) val pubKeyCredParams: List<PublicKeyCredentialParameters> =
// TODO: Fix excludeCredentials and authenticatorSelection json.getPublicKeyCredentialParametersList("pubKeyCredParams")
excludeCredentials = emptyList()
authenticatorSelection = AuthenticatorSelectionCriteria("platform", "required") var timeout: Long =
attestation = json.optString("attestation", "none") json.optLong("timeout", 0)
}
var excludeCredentials: List<PublicKeyCredentialDescriptor> =
json.getPublicKeyCredentialDescriptorList("excludeCredentials")
var authenticatorSelection: AuthenticatorSelectionCriteria =
json.getAuthenticatorSelectionCriteria("authenticatorSelection")
var attestation: String =
json.optString("attestation", "none")
companion object { companion object {
private val TAG = PublicKeyCredentialCreationOptions::class.simpleName private val TAG = PublicKeyCredentialCreationOptions::class.simpleName

View File

@@ -20,12 +20,33 @@
package com.kunzisoft.keepass.credentialprovider.passkey.data package com.kunzisoft.keepass.credentialprovider.passkey.data
import com.kunzisoft.encrypt.Base64Helper import com.kunzisoft.encrypt.Base64Helper
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialDescriptor.Companion.getPublicKeyCredentialDescriptorList
import org.json.JSONObject import org.json.JSONObject
// https://www.w3.org/TR/webauthn-3/#enumdef-residentkeyrequirement
class PublicKeyCredentialRequestOptions(requestJson: String) { class PublicKeyCredentialRequestOptions(requestJson: String) {
val json: JSONObject = JSONObject(requestJson) private val json: JSONObject = JSONObject(requestJson)
val challenge: ByteArray = Base64Helper.b64Decode(json.getString("challenge"))
val timeout: Long = json.optLong("timeout", 0) val challenge: ByteArray =
val rpId: String = json.optString("rpId", "") Base64Helper.b64Decode(json.getString("challenge"))
val userVerification: String = json.optString("userVerification", "preferred")
val timeout: Long =
json.optLong("timeout", 0)
val rpId: String =
json.optString("rpId", "")
val allowCredentials: List<PublicKeyCredentialDescriptor> =
json.getPublicKeyCredentialDescriptorList("allowCredentials")
val userVerification: UserVerificationRequirement =
UserVerificationRequirement.fromString(
json.optString("userVerification", "preferred"))
?: UserVerificationRequirement.PREFERRED
// TODO Hints
val hints: List<String> = listOf()
// TODO Extensions
// val extensions: AuthenticationExtensionsClientInputs
} }

View File

@@ -22,6 +22,7 @@ package com.kunzisoft.keepass.credentialprovider.passkey.util
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.Resources
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyGenParameterSpec
@@ -200,6 +201,28 @@ object PasskeyHelper {
} }
} }
/**
* Build the Passkey error response
*/
fun Activity.buildPasskeyErrorAndSetResult(
resources: Resources,
relyingPartyId: String?,
credentialIds: List<String>
) {
val error = resources.getString(
R.string.error_passkey_credential_id,
relyingPartyId,
credentialIds
)
Log.e(javaClass.name, error)
Toast.makeText(
this,
error,
Toast.LENGTH_SHORT
).show()
setResult(Activity.RESULT_CANCELED)
}
/** /**
* Check the timestamp and authentication code transmitted via PendingIntent * Check the timestamp and authentication code transmitted via PendingIntent
*/ */

View File

@@ -94,6 +94,7 @@ object SearchHelper {
callback.invoke( callback.invoke(
SearchParameters().apply { SearchParameters().apply {
searchQuery = query searchQuery = query
searchOptions = optionsString()
allowEmptyQuery = false allowEmptyQuery = false
searchInTitles = false searchInTitles = false
searchInUsernames = false searchInUsernames = false

View File

@@ -775,4 +775,5 @@
<string name="passkey_backup_eligibility">Passkey Backup Eligibility</string> <string name="passkey_backup_eligibility">Passkey Backup Eligibility</string>
<string name="passkey_backup_state">Passkey Backup State</string> <string name="passkey_backup_state">Passkey Backup State</string>
<string name="error_passkey_result">Unable to return the passkey</string> <string name="error_passkey_result">Unable to return the passkey</string>
<string name="error_passkey_credential_id">No passkey found with relying party %1$s and credentialIds %2$s</string>
</resources> </resources>

View File

@@ -27,6 +27,7 @@ import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.model.AppOriginEntryField.isAppId import com.kunzisoft.keepass.model.AppOriginEntryField.isAppId
import com.kunzisoft.keepass.model.AppOriginEntryField.isAppIdSignature import com.kunzisoft.keepass.model.AppOriginEntryField.isAppIdSignature
import com.kunzisoft.keepass.model.AppOriginEntryField.isWebDomain import com.kunzisoft.keepass.model.AppOriginEntryField.isWebDomain
import com.kunzisoft.keepass.model.PasskeyEntryFields.isCredentialId
import com.kunzisoft.keepass.model.PasskeyEntryFields.isPasskey import com.kunzisoft.keepass.model.PasskeyEntryFields.isPasskey
import com.kunzisoft.keepass.model.PasskeyEntryFields.isRelyingParty import com.kunzisoft.keepass.model.PasskeyEntryFields.isRelyingParty
import com.kunzisoft.keepass.otp.OtpEntryFields.isOTP import com.kunzisoft.keepass.otp.OtpEntryFields.isOTP
@@ -176,11 +177,29 @@ class SearchHelper {
} }
} }
if (searchParameters.searchInRelyingParty) { if (searchParameters.searchInRelyingParty) {
if(entry.getExtraFields().any { field -> val relyingParty = searchParameters.searchQuery
val credentialIds = searchParameters.searchOptions
val containsRelyingParty = entry.getExtraFields().any { field ->
field.isRelyingParty() field.isRelyingParty()
&& checkSearchQuery(field.protectedValue.stringValue, searchParameters) && field.protectedValue.stringValue
}) .equals(relyingParty, ignoreCase = true)
return true }
// Check empty to allow any credential if not defined
val containsCredentialId = if(credentialIds.isEmpty()) true
else entry.getExtraFields().any { field ->
field.isCredentialId()
&& credentialIds.any { credentialId ->
checkSearchQuery(
stringToCheck = field.protectedValue.stringValue,
searchParameters = SearchParameters().apply {
searchQuery = credentialId
caseSensitive = false
isRegex = false
}
)
}
}
return containsRelyingParty && containsCredentialId
} }
if (searchParameters.searchInNotes) { if (searchParameters.searchInNotes) {
if (checkSearchQuery(entry.notes, searchParameters)) if (checkSearchQuery(entry.notes, searchParameters))

View File

@@ -27,6 +27,8 @@ import android.os.Parcelable
*/ */
class SearchParameters() : Parcelable{ class SearchParameters() : Parcelable{
var searchQuery: String = "" var searchQuery: String = ""
// Add an optional string to search with the main search query
var searchOptions: List<String> = listOf()
var allowEmptyQuery = true var allowEmptyQuery = true
var caseSensitive = false var caseSensitive = false
var isRegex = false var isRegex = false

View File

@@ -176,6 +176,13 @@ object PasskeyEntryFields {
} }
} }
/**
* Detect if the current field is a Passkey credential id
*/
fun Field.isCredentialId(): Boolean {
return name == FIELD_CREDENTIAL_ID
}
/** /**
* Detect if the current field is a Passkey relying party * Detect if the current field is a Passkey relying party
*/ */

View File

@@ -35,6 +35,7 @@ class SearchInfo : ObjectNameResource, Parcelable {
return if (webDomain == null) null else field return if (webDomain == null) null else field
} }
var relyingParty: String? = null var relyingParty: String? = null
var credentialIds: List<String> = listOf()
var otpString: String? = null var otpString: String? = null
constructor() constructor()
@@ -46,6 +47,7 @@ class SearchInfo : ObjectNameResource, Parcelable {
webDomain = toCopy?.webDomain webDomain = toCopy?.webDomain
webScheme = toCopy?.webScheme webScheme = toCopy?.webScheme
relyingParty = toCopy?.relyingParty relyingParty = toCopy?.relyingParty
credentialIds = toCopy?.credentialIds ?: listOf()
otpString = toCopy?.otpString otpString = toCopy?.otpString
} }
@@ -61,6 +63,9 @@ class SearchInfo : ObjectNameResource, Parcelable {
webScheme = if (readScheme.isNullOrEmpty()) null else readScheme webScheme = if (readScheme.isNullOrEmpty()) null else readScheme
val readRelyingParty = parcel.readString() val readRelyingParty = parcel.readString()
relyingParty = if (readRelyingParty.isNullOrEmpty()) null else readRelyingParty relyingParty = if (readRelyingParty.isNullOrEmpty()) null else readRelyingParty
val readCredentialIdList = mutableListOf<String>()
parcel.readStringList(readCredentialIdList)
credentialIds = readCredentialIdList.toList()
val readOtp = parcel.readString() val readOtp = parcel.readString()
otpString = if (readOtp.isNullOrEmpty()) null else readOtp otpString = if (readOtp.isNullOrEmpty()) null else readOtp
} }
@@ -76,6 +81,7 @@ class SearchInfo : ObjectNameResource, Parcelable {
parcel.writeString(webDomain ?: "") parcel.writeString(webDomain ?: "")
parcel.writeString(webScheme ?: "") parcel.writeString(webScheme ?: "")
parcel.writeString(relyingParty ?: "") parcel.writeString(relyingParty ?: "")
parcel.writeStringList(credentialIds)
parcel.writeString(otpString ?: "") parcel.writeString(otpString ?: "")
} }
@@ -94,6 +100,7 @@ class SearchInfo : ObjectNameResource, Parcelable {
&& webDomain == null && webDomain == null
&& webScheme == null && webScheme == null
&& relyingParty == null && relyingParty == null
&& credentialIds.isEmpty()
&& otpString == null && otpString == null
} }
@@ -127,6 +134,7 @@ class SearchInfo : ObjectNameResource, Parcelable {
if (webDomain != other.webDomain) return false if (webDomain != other.webDomain) return false
if (webScheme != other.webScheme) return false if (webScheme != other.webScheme) return false
if (relyingParty != other.relyingParty) return false if (relyingParty != other.relyingParty) return false
if (credentialIds != other.credentialIds) return false
if (otpString != other.otpString) return false if (otpString != other.otpString) return false
return true return true
@@ -139,6 +147,7 @@ class SearchInfo : ObjectNameResource, Parcelable {
result = 31 * result + (webDomain?.hashCode() ?: 0) result = 31 * result + (webDomain?.hashCode() ?: 0)
result = 31 * result + (webScheme?.hashCode() ?: 0) result = 31 * result + (webScheme?.hashCode() ?: 0)
result = 31 * result + (relyingParty?.hashCode() ?: 0) result = 31 * result + (relyingParty?.hashCode() ?: 0)
result = 31 * result + (credentialIds.hashCode())
result = 31 * result + (otpString?.hashCode() ?: 0) result = 31 * result + (otpString?.hashCode() ?: 0)
return result return result
} }
@@ -147,6 +156,10 @@ class SearchInfo : ObjectNameResource, Parcelable {
return otpString ?: webDomain ?: applicationId ?: relyingParty ?: tag ?: "" return otpString ?: webDomain ?: applicationId ?: relyingParty ?: tag ?: ""
} }
fun optionsString(): List<String> {
return if (isPasskeySearch && credentialIds.isNotEmpty()) credentialIds else listOf()
}
fun toRegisterInfo(): RegisterInfo { fun toRegisterInfo(): RegisterInfo {
return RegisterInfo(this) return RegisterInfo(this)
} }

View File

@@ -1,2 +1,3 @@
* Manual change of app language #1884 #1990 * Manual change of app language #1884 #1990
* Fix autofill username detection #2276 * Fix autofill username detection #2276
* Fix Passkey in passwordless mode #2282

View File

@@ -1,2 +1,3 @@
* Changement manuel de la langue de l'appli #1884 #1990 * Changement manuel de la langue de l'appli #1884 #1990
* Correction de la détection du nom d'utilisateur pour le remplissage auto #2276 * Correction de la détection du nom d'utilisateur pour le remplissage auto #2276
* Correction de Passkey en mode passwordless #2282