fix: Passwordless for multiple CredentialIds

This commit is contained in:
J-Jamet
2025-11-25 13:06:19 +01:00
parent 82a8776911
commit ed095ad0a7
8 changed files with 43 additions and 37 deletions

View File

@@ -950,14 +950,14 @@ class GroupActivity : DatabaseLockActivity(),
}
private fun errorIfNeededForPasskeySelection(searchInfo: SearchInfo?) {
if (mTypeMode == TypeMode.PASSKEY && searchInfo?.credentialId != null) {
if (mTypeMode == TypeMode.PASSKEY && searchInfo?.credentialIds.isNullOrEmpty().not()) {
removeSearch()
// Build response with the entry selected
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
buildPasskeyErrorAndSetResult(
resources = resources,
relyingPartyId = searchInfo.relyingParty,
credentialId = searchInfo.credentialId
credentialIds = searchInfo.credentialIds
)
}
onValidateSpecialMode()

View File

@@ -91,12 +91,13 @@ class PasskeyProviderService : CredentialProviderService() {
super.onDestroy()
}
private fun buildPasskeySearchInfo(relyingParty: String, credentialId: String? = null): SearchInfo {
private fun buildPasskeySearchInfo(
relyingParty: String,
credentialIds: List<String> = listOf()
): SearchInfo {
return SearchInfo().apply {
credentialId?.let {
this.credentialId = it
}
this.relyingParty = relyingParty
this.credentialIds = credentialIds
}
}
@@ -145,9 +146,10 @@ class PasskeyProviderService : CredentialProviderService() {
val publicKeyCredentialRequestOptions = PublicKeyCredentialRequestOptions(option.requestJson)
val relyingPartyId = publicKeyCredentialRequestOptions.rpId
val credentialId = publicKeyCredentialRequestOptions.allowCredentials.firstOrNull()?.id?.let { b64Encode(it) }
val searchInfo = buildPasskeySearchInfo(relyingPartyId, credentialId)
Log.d(TAG, "Build passkey search for relying party $relyingPartyId, credentialId $credentialId")
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(
context = this,
database = mDatabase,
@@ -181,7 +183,7 @@ class PasskeyProviderService : CredentialProviderService() {
},
onItemNotFound = { _ ->
Log.w(TAG, "No passkey found in the database with this relying party : $relyingPartyId")
if (credentialId == null) {
if (credentialIdList.isEmpty()) {
Log.d(TAG, "Add pending intent for passkey selection in opened database")
PasskeyLauncherActivity.getPendingIntent(
context = applicationContext,
@@ -207,7 +209,7 @@ class PasskeyProviderService : CredentialProviderService() {
getString(
R.string.error_passkey_credential_id,
relyingPartyId,
credentialId
credentialIdList
)
)
}

View File

@@ -207,12 +207,12 @@ object PasskeyHelper {
fun Activity.buildPasskeyErrorAndSetResult(
resources: Resources,
relyingPartyId: String?,
credentialId: String?
credentialIds: List<String>
) {
val error = resources.getString(
R.string.error_passkey_credential_id,
relyingPartyId,
credentialId
credentialIds
)
Log.e(javaClass.name, error)
Toast.makeText(

View File

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

View File

@@ -775,5 +775,5 @@
<string name="passkey_backup_eligibility">Passkey Backup Eligibility</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_credential_id">No passkey found with relying party %1$s and credentialId %2$s</string>
<string name="error_passkey_credential_id">No passkey found with relying party %1$s and credentialIds %2$s</string>
</resources>

View File

@@ -27,9 +27,9 @@ import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.model.AppOriginEntryField.isAppId
import com.kunzisoft.keepass.model.AppOriginEntryField.isAppIdSignature
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.isRelyingParty
import com.kunzisoft.keepass.model.PasskeyEntryFields.isCredentialId
import com.kunzisoft.keepass.otp.OtpEntryFields.isOTP
import com.kunzisoft.keepass.otp.OtpEntryFields.isOTPURIField
import com.kunzisoft.keepass.utils.UUIDUtils.asHexString
@@ -178,7 +178,7 @@ class SearchHelper {
}
if (searchParameters.searchInRelyingParty) {
val relyingParty = searchParameters.searchQuery
val credentialId = searchParameters.searchOption
val credentialIds = searchParameters.searchOptions
val containsRelyingParty = entry.getExtraFields().any { field ->
field.isRelyingParty()
&& checkSearchQuery(
@@ -191,17 +191,20 @@ class SearchHelper {
}
)
}
val containsCredentialId = if(credentialId == null) true
// Check empty to allow any credential if not defined
val containsCredentialId = if(credentialIds.isEmpty()) true
else entry.getExtraFields().any { field ->
field.isCredentialId()
&& checkSearchQuery(
stringToCheck = field.protectedValue.stringValue,
searchParameters = SearchParameters().apply {
searchQuery = credentialId
caseSensitive = false
isRegex = false
}
)
&& credentialIds.any { credentialId ->
checkSearchQuery(
stringToCheck = field.protectedValue.stringValue,
searchParameters = SearchParameters().apply {
searchQuery = credentialId
caseSensitive = false
isRegex = false
}
)
}
}
return containsRelyingParty && containsCredentialId
}

View File

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

View File

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