fix: Search settings #2112 #2181

This commit is contained in:
J-Jamet
2025-09-24 22:36:50 +02:00
parent 5bcbbac97f
commit eed304ec40
23 changed files with 230 additions and 106 deletions

View File

@@ -4,6 +4,7 @@ KeePassDX(4.2.0)
* Dialog to manage missing signature #2152 #2155 #2161 #2160
* Capture error #2159
* Change Passkey Backup Eligibility & Backup State #2135 #2150
* Search settings #2112 #2181
* Small fixes #2157 #2164 #2171
KeePassDX(4.1.8)

View File

@@ -819,6 +819,7 @@ class GroupActivity : DatabaseLockActivity(),
searchFiltersView?.setNumbers(group.numberOfChildEntries)
searchFiltersView?.setCurrentGroupText(mMainGroup?.title ?: "")
searchFiltersView?.availableOther(mDatabase?.allowEntryCustomFields() ?: false)
searchFiltersView?.availableApplicationIds(mDatabase?.allowEntryCustomFields() ?: false)
searchFiltersView?.availableTags(mDatabase?.allowTags() ?: false)
searchFiltersView?.enableTags(mDatabase?.tagPool?.isNotEmpty() ?: false)
searchFiltersView?.availableSearchableGroup(mDatabase?.allowCustomSearchableGroup() ?: false)
@@ -1273,7 +1274,7 @@ class GroupActivity : DatabaseLockActivity(),
searchView = it.actionView as SearchView?
searchView?.apply {
setOnQueryTextFocusChangeListener(mOnSearchTextFocusChangeListener)
val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager?
val searchManager = getSystemService(SEARCH_SERVICE) as SearchManager?
(searchManager?.getSearchableInfo(
ComponentName(this@GroupActivity, GroupActivity::class.java)
))?.let { searchableInfo ->

View File

@@ -45,7 +45,7 @@ import com.kunzisoft.keepass.database.exception.RegisterInReadOnlyDatabaseExcept
import com.kunzisoft.keepass.database.helper.SearchHelper
import com.kunzisoft.keepass.model.RegisterInfo
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.utils.AppUtil
import com.kunzisoft.keepass.utils.AppUtil.getConcreteWebDomain
import com.kunzisoft.keepass.utils.getParcelableCompat
import com.kunzisoft.keepass.utils.getParcelableExtraCompat
import com.kunzisoft.keepass.view.toastError
@@ -81,10 +81,7 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
}
// Build search param
bundle.getParcelableCompat<SearchInfo>(KEY_SEARCH_INFO)?.let { searchInfo ->
AppUtil.getConcreteWebDomain(
this,
searchInfo.webDomain
) { concreteWebDomain ->
searchInfo.getConcreteWebDomain(this) { concreteWebDomain ->
// Pass extra for Autofill (EXTRA_ASSIST_STRUCTURE)
val assistStructure = AutofillHelper
.retrieveAutofillComponent(intent)
@@ -111,7 +108,7 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
KEY_REGISTER_INFO
)
val searchInfo = SearchInfo(registerInfo?.searchInfo)
AppUtil.getConcreteWebDomain(this, searchInfo.webDomain) { concreteWebDomain ->
searchInfo.getConcreteWebDomain(this) { concreteWebDomain ->
searchInfo.webDomain = concreteWebDomain
launchRegistration(database, searchInfo, registerInfo)
}

View File

@@ -33,7 +33,7 @@ import com.kunzisoft.keepass.database.exception.RegisterInReadOnlyDatabaseExcept
import com.kunzisoft.keepass.database.helper.SearchHelper
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.otp.OtpEntryFields
import com.kunzisoft.keepass.utils.AppUtil
import com.kunzisoft.keepass.utils.AppUtil.getConcreteWebDomain
import com.kunzisoft.keepass.utils.KeyboardUtil.isKeyboardActivatedInSettings
import com.kunzisoft.keepass.utils.getParcelableCompat
import com.kunzisoft.keepass.view.toastError
@@ -109,8 +109,7 @@ class EntrySelectionLauncherActivity : DatabaseModeActivity() {
this.webDomain = sharedWebDomain
this.otpString = otpString
}
AppUtil.getConcreteWebDomain(this, searchInfo.webDomain) { concreteWebDomain ->
searchInfo.getConcreteWebDomain(this) { concreteWebDomain ->
searchInfo.webDomain = concreteWebDomain
launch(database, searchInfo)
}

View File

@@ -53,7 +53,7 @@ import com.kunzisoft.keepass.model.RegisterInfo
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.settings.AutofillSettingsActivity
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.AppUtil
import com.kunzisoft.keepass.utils.AppUtil.getConcreteWebDomain
import org.joda.time.DateTime
@@ -120,7 +120,7 @@ class KeeAutofillService : AutofillService() {
webDomain = parseResult.webDomain
webScheme = parseResult.webScheme
}
AppUtil.getConcreteWebDomain(this, searchInfo.webDomain) { webDomainWithoutSubDomain ->
searchInfo.getConcreteWebDomain(this) { webDomainWithoutSubDomain ->
searchInfo.webDomain = webDomainWithoutSubDomain
val inlineSuggestionsRequest = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
&& autofillInlineSuggestionsEnabled) {

View File

@@ -93,8 +93,6 @@ class PasskeyProviderService : CredentialProviderService() {
private fun buildPasskeySearchInfo(relyingParty: String): SearchInfo {
return SearchInfo().apply {
this.relyingParty = relyingParty
this.isAPasskeySearch = true
this.query = relyingParty
}
}

View File

@@ -21,8 +21,10 @@ package com.kunzisoft.keepass.database.helper
import android.content.Context
import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.search.SearchParameters
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.settings.PreferencesUtil.searchSubDomains
import com.kunzisoft.keepass.timeout.TimeoutHelper
object SearchHelper {
@@ -61,8 +63,28 @@ object SearchHelper {
&& !searchInfo.containsOnlyNullValues()) {
// If search provide results
database.createVirtualGroupFromSearchInfo(
searchInfo,
MAX_SEARCH_ENTRY
searchParameters = SearchParameters().apply {
searchQuery = searchInfo.toString()
allowEmptyQuery = false
searchInTitles = false
searchInUsernames = false
searchInPasswords = false
searchInAppIds = searchInfo.isAppIdSearch
searchInUrls = searchInfo.isDomainSearch
searchByDomain = true
searchBySubDomain = searchSubDomains(context)
searchInRelyingParty = searchInfo.isPasskeySearch
searchInNotes = false
searchInOTP = searchInfo.isOTPSearch
searchInOther = false
searchInUUIDs = false
searchInTags = searchInfo.isTagSearch
searchInCurrentGroup = false
searchInSearchableGroup = true
searchInRecycleBin = false
searchInTemplates = false
},
max = MAX_SEARCH_ENTRY
)?.let { searchGroup ->
if (searchGroup.numberOfChildEntries > 0) {
searchWithoutUI = true

View File

@@ -108,7 +108,7 @@ object PreferencesUtil {
context.resources.getBoolean(R.bool.auto_focus_search_default))
}
fun searchSubdomains(context: Context): Boolean {
fun searchSubDomains(context: Context): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
return prefs.getBoolean(context.getString(R.string.subdomain_search_key),
context.resources.getBoolean(R.bool.subdomain_search_default))
@@ -352,6 +352,8 @@ object PreferencesUtil {
context.resources.getBoolean(R.bool.search_option_username_default))
searchInPasswords = prefs.getBoolean(context.getString(R.string.search_option_password_key),
context.resources.getBoolean(R.bool.search_option_password_default))
searchInAppIds = prefs.getBoolean(context.getString(R.string.search_option_application_id_key),
context.resources.getBoolean(R.bool.search_option_application_id_default))
searchInUrls = prefs.getBoolean(context.getString(R.string.search_option_url_key),
context.resources.getBoolean(R.bool.search_option_url_default))
searchInExpired = prefs.getBoolean(context.getString(R.string.search_option_expired_key),
@@ -389,6 +391,8 @@ object PreferencesUtil {
searchParameters.searchInUsernames)
putBoolean(context.getString(R.string.search_option_password_key),
searchParameters.searchInPasswords)
putBoolean(context.getString(R.string.search_option_application_id_key),
searchParameters.searchInAppIds)
putBoolean(context.getString(R.string.search_option_url_key),
searchParameters.searchInUrls)
putBoolean(context.getString(R.string.search_option_expired_key),

View File

@@ -18,6 +18,7 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import mozilla.components.lib.publicsuffixlist.PublicSuffixList
object AppUtil {
@@ -82,25 +83,34 @@ object AppUtil {
/**
* Get the concrete web domain AKA without sub domain if needed
*/
fun getConcreteWebDomain(context: Context,
webDomain: String?,
concreteWebDomain: (String?) -> Unit) {
CoroutineScope(Dispatchers.Main).launch {
if (webDomain != null) {
fun SearchInfo.getConcreteWebDomain(
context: Context,
concreteWebDomain: (String?) -> Unit
) {
CoroutineScope(Dispatchers.IO).launch {
val domain = webDomain
if (domain != null) {
// Warning, web domain can contains IP, don't crop in this case
if (PreferencesUtil.searchSubdomains(context)
|| Regex(SearchInfo.WEB_IP_REGEX).matches(webDomain)) {
if (PreferencesUtil.searchSubDomains(context)
|| Regex(SearchInfo.WEB_IP_REGEX).matches(domain)) {
withContext(Dispatchers.Main) {
concreteWebDomain.invoke(webDomain)
} else {
val publicSuffixList = PublicSuffixList(context)
concreteWebDomain.invoke(publicSuffixList
.getPublicSuffixPlusOne(webDomain).await())
}
} else {
val publicSuffixList = PublicSuffixList(context)
val publicSuffix = publicSuffixList
.getPublicSuffixPlusOne(domain).await()
withContext(Dispatchers.Main) {
concreteWebDomain.invoke(publicSuffix)
}
}
} else {
withContext(Dispatchers.Main) {
concreteWebDomain.invoke(null)
}
}
}
}
@RequiresApi(Build.VERSION_CODES.P)
fun getInstalledBrowsersWithSignatures(context: Context): List<AndroidPrivilegedApp> {

View File

@@ -3,7 +3,6 @@ package com.kunzisoft.keepass.view
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CompoundButton
import android.widget.ImageView
@@ -30,8 +29,10 @@ class SearchFiltersView @JvmOverloads constructor(context: Context,
private var searchTitle: CompoundButton
private var searchUsername: CompoundButton
private var searchPassword: CompoundButton
private var searchApplicationId: CompoundButton
private var searchURL: CompoundButton
private var searchByURLDomain: Boolean = false
private var searchByURLSubDomain: Boolean = false
private var searchExpired: CompoundButton
private var searchNotes: CompoundButton
private var searchOther: CompoundButton
@@ -50,8 +51,10 @@ class SearchFiltersView @JvmOverloads constructor(context: Context,
this.searchInTitles = searchTitle.isChecked
this.searchInUsernames = searchUsername.isChecked
this.searchInPasswords = searchPassword.isChecked
this.searchInAppIds = searchApplicationId.isChecked
this.searchInUrls = searchURL.isChecked
this.searchByDomain = searchByURLDomain
this.searchBySubDomain = searchByURLSubDomain
this.searchInExpired = searchExpired.isChecked
this.searchInNotes = searchNotes.isChecked
this.searchInOther = searchOther.isChecked
@@ -71,8 +74,10 @@ class SearchFiltersView @JvmOverloads constructor(context: Context,
searchTitle.isChecked = value.searchInTitles
searchUsername.isChecked = value.searchInUsernames
searchPassword.isChecked = value.searchInPasswords
searchApplicationId.isChecked = value.searchInAppIds
searchURL.isChecked = value.searchInUrls
searchByURLDomain = value.searchByDomain
searchByURLSubDomain = value.searchBySubDomain
searchExpired.isChecked = value.searchInExpired
searchNotes.isChecked = value.searchInNotes
searchOther.isChecked = value.searchInOther
@@ -87,7 +92,7 @@ class SearchFiltersView @JvmOverloads constructor(context: Context,
var onParametersChangeListener: ((searchParameters: SearchParameters) -> Unit)? = null
private var mOnParametersChangeListener: ((searchParameters: SearchParameters) -> Unit)? = {
// To recalculate height
if (searchAdvanceFiltersContainer?.visibility == View.VISIBLE) {
if (searchAdvanceFiltersContainer?.visibility == VISIBLE) {
searchAdvanceFiltersContainer?.expand(
false,
searchAdvanceFiltersContainer?.getFullHeight()
@@ -110,6 +115,7 @@ class SearchFiltersView @JvmOverloads constructor(context: Context,
searchTitle = findViewById(R.id.search_chip_title)
searchUsername = findViewById(R.id.search_chip_username)
searchPassword = findViewById(R.id.search_chip_password)
searchApplicationId = findViewById(R.id.search_chip_application_id)
searchURL = findViewById(R.id.search_chip_url)
searchExpired = findViewById(R.id.search_chip_expires)
searchNotes = findViewById(R.id.search_chip_note)
@@ -125,7 +131,7 @@ class SearchFiltersView @JvmOverloads constructor(context: Context,
// Expand menu with button
searchExpandButton.setOnClickListener {
val isVisible = searchAdvanceFiltersContainer?.visibility == View.VISIBLE
val isVisible = searchAdvanceFiltersContainer?.visibility == VISIBLE
if (isVisible)
closeAdvancedFilters()
else
@@ -156,6 +162,10 @@ class SearchFiltersView @JvmOverloads constructor(context: Context,
searchParameters.searchInPasswords = isChecked
mOnParametersChangeListener?.invoke(searchParameters)
}
searchApplicationId.setOnCheckedChangeListener { _, isChecked ->
searchParameters.searchInAppIds = isChecked
mOnParametersChangeListener?.invoke(searchParameters)
}
searchURL.setOnCheckedChangeListener { _, isChecked ->
searchParameters.searchInUrls = isChecked
mOnParametersChangeListener?.invoke(searchParameters)
@@ -213,6 +223,10 @@ class SearchFiltersView @JvmOverloads constructor(context: Context,
searchOther.isVisible = available
}
fun availableApplicationIds(available: Boolean) {
searchApplicationId.isVisible = available
}
fun availableTags(available: Boolean) {
searchTag.isVisible = available
}
@@ -245,14 +259,14 @@ class SearchFiltersView @JvmOverloads constructor(context: Context,
override fun setVisibility(visibility: Int) {
when (visibility) {
View.VISIBLE -> {
searchAdvanceFiltersContainer?.visibility = View.GONE
VISIBLE -> {
searchAdvanceFiltersContainer?.visibility = GONE
searchContainer.showByFading()
}
else -> {
searchContainer.hideByFading()
if (searchAdvanceFiltersContainer?.visibility == View.VISIBLE) {
searchAdvanceFiltersContainer?.visibility = View.INVISIBLE
if (searchAdvanceFiltersContainer?.visibility == VISIBLE) {
searchAdvanceFiltersContainer?.visibility = INVISIBLE
searchAdvanceFiltersContainer?.collapse()
}
}

View File

@@ -101,6 +101,13 @@
android:checked="false"
style="@style/KeepassDXStyle.Chip.Filter"
android:text="@string/entry_password"/>
<com.google.android.material.chip.Chip
android:id="@+id/search_chip_application_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
style="@style/KeepassDXStyle.Chip.Filter"
android:text="@string/entry_application_id"/>
<com.google.android.material.chip.Chip
android:id="@+id/search_chip_url"
android:layout_width="wrap_content"

View File

@@ -247,6 +247,8 @@
<bool name="search_option_username_default" translatable="false">true</bool>
<string name="search_option_password_key" translatable="false">search_option_password_key</string>
<bool name="search_option_password_default" translatable="false">false</bool>
<string name="search_option_application_id_key" translatable="false">search_option_application_id_key</string>
<bool name="search_option_application_id_default" translatable="false">false</bool>
<string name="search_option_url_key" translatable="false">search_option_url_key</string>
<bool name="search_option_url_default" translatable="false">true</bool>
<string name="search_option_expired_key" translatable="false">search_option_expired_key</string>

View File

@@ -110,6 +110,7 @@
<string name="auto_type_sequence">Auto-Type sequence</string>
<string name="entry_not_found">Could not find entry data.</string>
<string name="entry_password">Password</string>
<string name="entry_application_id">App Id</string>
<string name="tags">Tags</string>
<string name="custom_data">Custom data</string>
<string name="save">Save</string>

View File

@@ -55,7 +55,6 @@ import com.kunzisoft.keepass.database.merge.DatabaseKDBXMerger
import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.database.search.SearchParameters
import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.SingletonHolder
import com.kunzisoft.keepass.utils.StringUtil.toFormattedColorInt
@@ -886,12 +885,12 @@ open class Database {
}
fun createVirtualGroupFromSearchInfo(
searchInfo: SearchInfo,
searchParameters: SearchParameters,
max: Int = Integer.MAX_VALUE
): Group? {
return mSearchHelper.createVirtualGroupWithSearchResult(
database = this,
searchParameters = searchInfo.buildSearchParameters(),
searchParameters = searchParameters,
fromGroup = null,
max = max
)

View File

@@ -24,10 +24,13 @@ import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.node.NodeHandler
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.model.PasskeyEntryFields.FIELD_RELYING_PARTY
import com.kunzisoft.keepass.model.PasskeyEntryFields.isPasskeyExclusion
import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_FIELD
import com.kunzisoft.keepass.otp.OtpEntryFields.isOtpExclusion
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.isPasskey
import com.kunzisoft.keepass.model.PasskeyEntryFields.isRelyingParty
import com.kunzisoft.keepass.otp.OtpEntryFields.isOTP
import com.kunzisoft.keepass.otp.OtpEntryFields.isOTPURIField
import com.kunzisoft.keepass.utils.UUIDUtils.asHexString
import com.kunzisoft.keepass.utils.inTheSameDomainAs
@@ -124,8 +127,10 @@ class SearchHelper {
/**
* Return true if the search query in search parameters is found in available parameters
*/
fun searchInEntry(entry: Entry,
searchParameters: SearchParameters): Boolean {
fun searchInEntry(
entry: Entry,
searchParameters: SearchParameters
): Boolean {
// Not found if the search string is empty
if (searchParameters.searchQuery.isEmpty())
return searchParameters.allowEmptyQuery
@@ -149,15 +154,31 @@ class SearchHelper {
if (checkSearchQuery(entry.password, searchParameters))
return true
}
if (searchParameters.searchInAppIds) {
if (entry.getExtraFields().any { field ->
field.isAppId()
&& checkSearchQuery(field.protectedValue.stringValue, searchParameters)
})
return true
}
if (searchParameters.searchInUrls) {
if (checkSearchQuery(entry.url, searchParameters) { stringToCheck, word ->
if (searchParameters.searchByDomain) {
try {
stringToCheck.inTheSameDomainAs(word, sameSubDomain = true)
} catch (_: Exception) {
false
specialWebDomainComparison(searchParameters, stringToCheck, word)
}) {
return true
} else if (entry.getExtraFields().any { field ->
field.isWebDomain()
&& checkSearchQuery(field.protectedValue.stringValue, searchParameters) { stringToCheck, word ->
specialWebDomainComparison(searchParameters, stringToCheck, word)
}
} else null
}) {
return true
}
}
if (searchParameters.searchInRelyingParty) {
if(entry.getExtraFields().any { field ->
field.isRelyingParty()
&& checkSearchQuery(field.protectedValue.stringValue, searchParameters)
})
return true
}
@@ -172,22 +193,18 @@ class SearchHelper {
}
if (searchParameters.searchInOTP) {
if (entry.getExtraFields().any { field ->
field.name == OTP_FIELD
&& checkSearchQuery(field.protectedValue.stringValue, searchParameters)
})
return true
}
if (searchParameters.searchInRelyingParty) {
if(entry.getExtraFields().any { field ->
field.name == FIELD_RELYING_PARTY
field.isOTPURIField()
&& checkSearchQuery(field.protectedValue.stringValue, searchParameters)
})
return true
}
if (searchParameters.searchInOther) {
if (entry.getExtraFields().any { field ->
field.isOtpExclusion()
&& field.isPasskeyExclusion()
!field.isAppId()
&& !field.isAppIdSignature()
&& !field.isWebDomain()
&& !field.isOTP()
&& !field.isPasskey()
&& checkSearchQuery(field.protectedValue.toString(), searchParameters)
})
return true
@@ -199,6 +216,23 @@ class SearchHelper {
return false
}
private fun specialWebDomainComparison(
searchParameters: SearchParameters,
stringToCheck: String,
word: String
): Boolean? {
return if (searchParameters.searchByDomain) {
try {
stringToCheck.inTheSameDomainAs(
value = word,
sameSubDomain = searchParameters.searchBySubDomain
)
} catch (_: Exception) {
false
}
} else null
}
private fun checkSearchQuery(
stringToCheck: String,
searchParameters: SearchParameters,

View File

@@ -34,11 +34,14 @@ class SearchParameters() : Parcelable{
var searchInTitles = true
var searchInUsernames = true
var searchInPasswords = false
var searchInAppIds = true
var searchInUrls = true
var searchByDomain = false
var searchBySubDomain = false
var searchInRelyingParty = false
var searchInExpired = false
var searchInNotes = true
var searchInOTP = false
var searchInRelyingParty = false
var searchInOther = true
var searchInUUIDs = false
var searchInTags = false
@@ -47,7 +50,6 @@ class SearchParameters() : Parcelable{
var searchInSearchableGroup = true
var searchInRecycleBin = false
var searchInTemplates = false
var searchByDomain = false
constructor(parcel: Parcel) : this() {
searchQuery = parcel.readString() ?: searchQuery
@@ -57,11 +59,14 @@ class SearchParameters() : Parcelable{
searchInTitles = parcel.readByte() != 0.toByte()
searchInUsernames = parcel.readByte() != 0.toByte()
searchInPasswords = parcel.readByte() != 0.toByte()
searchInAppIds = parcel.readByte() != 0.toByte()
searchInUrls = parcel.readByte() != 0.toByte()
searchByDomain = parcel.readByte() != 0.toByte()
searchBySubDomain = parcel.readByte() != 0.toByte()
searchInRelyingParty = parcel.readByte() != 0.toByte()
searchInExpired = parcel.readByte() != 0.toByte()
searchInNotes = parcel.readByte() != 0.toByte()
searchInOTP = parcel.readByte() != 0.toByte()
searchInRelyingParty = parcel.readByte() != 0.toByte()
searchInOther = parcel.readByte() != 0.toByte()
searchInUUIDs = parcel.readByte() != 0.toByte()
searchInTags = parcel.readByte() != 0.toByte()
@@ -79,11 +84,14 @@ class SearchParameters() : Parcelable{
parcel.writeByte(if (searchInTitles) 1 else 0)
parcel.writeByte(if (searchInUsernames) 1 else 0)
parcel.writeByte(if (searchInPasswords) 1 else 0)
parcel.writeByte(if (searchInAppIds) 1 else 0)
parcel.writeByte(if (searchInUrls) 1 else 0)
parcel.writeByte(if (searchByDomain) 1 else 0)
parcel.writeByte(if (searchBySubDomain) 1 else 0)
parcel.writeByte(if (searchInRelyingParty) 1 else 0)
parcel.writeByte(if (searchInExpired) 1 else 0)
parcel.writeByte(if (searchInNotes) 1 else 0)
parcel.writeByte(if (searchInOTP) 1 else 0)
parcel.writeByte(if (searchInRelyingParty) 1 else 0)
parcel.writeByte(if (searchInOther) 1 else 0)
parcel.writeByte(if (searchInUUIDs) 1 else 0)
parcel.writeByte(if (searchInTags) 1 else 0)

View File

@@ -139,4 +139,25 @@ object AppOriginEntryField {
setWebDomain(webOrigin.origin, null, customFieldsAllowed)
}
}
/**
* Detect if the current field is an application id
*/
fun Field.isAppId(): Boolean {
return this.name.startsWith(APPLICATION_ID_FIELD_NAME)
}
/**
* Detect if the current field is an application id signature
*/
fun Field.isAppIdSignature(): Boolean {
return this.name.startsWith(APPLICATION_SIGNATURE_FIELD_NAME)
}
/**
* Detect if the current field is a web domain
*/
fun Field.isWebDomain(): Boolean {
return this.name.startsWith(WEB_DOMAIN_FIELD_NAME)
}
}

View File

@@ -31,11 +31,11 @@ import com.kunzisoft.keepass.model.AppOriginEntryField.setAppOrigin
import com.kunzisoft.keepass.model.AppOriginEntryField.setApplicationId
import com.kunzisoft.keepass.model.AppOriginEntryField.setWebDomain
import com.kunzisoft.keepass.model.CreditCardEntryFields.setCreditCard
import com.kunzisoft.keepass.model.PasskeyEntryFields.isPasskeyExclusion
import com.kunzisoft.keepass.model.PasskeyEntryFields.isPasskey
import com.kunzisoft.keepass.model.PasskeyEntryFields.setPasskey
import com.kunzisoft.keepass.otp.OtpElement
import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_TOKEN_FIELD
import com.kunzisoft.keepass.otp.OtpEntryFields.isOtpExclusion
import com.kunzisoft.keepass.otp.OtpEntryFields.isOTP
import com.kunzisoft.keepass.otp.OtpEntryFields.setOtp
import com.kunzisoft.keepass.utils.readBooleanCompat
import com.kunzisoft.keepass.utils.readListCompat
@@ -109,8 +109,7 @@ class EntryInfo : NodeInfo {
fun getCustomFieldsForFilling(): List<Field> {
return customFields.filter {
!it.isOtpExclusion()
&& !it.isPasskeyExclusion()
!it.isOTP() && !it.isPasskey()
}
}

View File

@@ -112,9 +112,9 @@ object PasskeyEntryFields {
}
/**
* Field ignored for a search or a form filling
* Detect if the current field is a Passkey
*/
fun Field.isPasskeyExclusion(): Boolean {
fun Field.isPasskey(): Boolean {
return when(name) {
PASSKEY_FIELD -> true
FIELD_USERNAME -> true
@@ -125,4 +125,11 @@ object PasskeyEntryFields {
else -> false
}
}
/**
* Detect if the current field is a Passkey relying party
*/
fun Field.isRelyingParty(): Boolean {
return name == FIELD_RELYING_PARTY
}
}

View File

@@ -4,7 +4,6 @@ import android.content.res.Resources
import android.net.Uri
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.search.SearchParameters
import com.kunzisoft.keepass.otp.OtpEntryFields
import com.kunzisoft.keepass.utils.ObjectNameResource
import com.kunzisoft.keepass.utils.readBooleanCompat
@@ -98,35 +97,25 @@ class SearchInfo : ObjectNameResource, Parcelable {
&& otpString == null
}
private fun isADomainSearch(): Boolean {
return toString() == webDomain && webDomain != null
}
var isTagSearch: Boolean = false
get() = tag != null
private set
var isAPasskeySearch: Boolean = false
var isAppIdSearch: Boolean = false
get() = applicationId != null
private set
var query: String? = null
var isDomainSearch: Boolean = false
get() = webDomain != null
private set
fun buildSearchParameters(): SearchParameters {
return SearchParameters().apply {
searchQuery = query ?: this@SearchInfo.toString()
allowEmptyQuery = false
searchInTitles = !isAPasskeySearch
searchInUsernames = false
searchInPasswords = false
searchInUrls = !isAPasskeySearch
searchByDomain = isADomainSearch()
searchInNotes = false
searchInOTP = false
searchInOther = true
searchInUUIDs = false
searchInTags = false
searchInRelyingParty = isAPasskeySearch
searchInCurrentGroup = false
searchInSearchableGroup = true
searchInRecycleBin = false
searchInTemplates = false
}
}
var isPasskeySearch: Boolean = false
get() = relyingParty != null
private set
var isOTPSearch: Boolean = false
get() = otpString != null
private set
override fun equals(other: Any?): Boolean {
if (this === other) return true

View File

@@ -39,7 +39,7 @@ object OtpEntryFields {
private val TAG = OtpEntryFields::class.java.name
// Field from KeePassXC
const val OTP_FIELD = "otp"
private const val OTP_FIELD = "otp"
// URL parameters (https://github.com/google/google-authenticator/wiki/Key-Uri-Format)
private const val OTP_SCHEME = "otpauth"
@@ -508,9 +508,9 @@ object OtpEntryFields {
}
/**
* Field ignored for a search or a form filling
* Detect if the current field is an OTP
*/
fun Field.isOtpExclusion(): Boolean {
fun Field.isOTP(): Boolean {
return when(name) {
OTP_FIELD -> true
TOTP_SEED_FIELD -> true
@@ -530,4 +530,11 @@ object OtpEntryFields {
else -> false
}
}
/**
* Detect if the current field is an OTP URI
*/
fun Field.isOTPURIField(): Boolean {
return name == OTP_FIELD
}
}

View File

@@ -1,2 +1,3 @@
* Passkeys management #1421 (Thx @cali-95)
* Search settings #2112 #2181
* Small fixes #2171 #2150 #2159

View File

@@ -0,0 +1,3 @@
* Gestion de Passkeys #1421 (Thx @cali-95)
* Paramètres de recherche #2112 #2181
* Petites corrections #2171 #2150 #2159