diff --git a/CHANGELOG b/CHANGELOG index 20a9a80a8..2687c56ab 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -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) diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt index b303e4fdd..2d301ca0a 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt @@ -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 -> diff --git a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/AutofillLauncherActivity.kt b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/AutofillLauncherActivity.kt index a52b9a492..cac803a1a 100644 --- a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/AutofillLauncherActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/AutofillLauncherActivity.kt @@ -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(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) } diff --git a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/EntrySelectionLauncherActivity.kt b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/EntrySelectionLauncherActivity.kt index 54eab99bc..075c67b83 100644 --- a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/EntrySelectionLauncherActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/activity/EntrySelectionLauncherActivity.kt @@ -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) } diff --git a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/autofill/KeeAutofillService.kt b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/autofill/KeeAutofillService.kt index 6c3a3aeea..b0eb8862a 100644 --- a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/autofill/KeeAutofillService.kt +++ b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/autofill/KeeAutofillService.kt @@ -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) { diff --git a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/passkey/PasskeyProviderService.kt b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/passkey/PasskeyProviderService.kt index 6ce2a6b68..af0d1e312 100644 --- a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/passkey/PasskeyProviderService.kt +++ b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/passkey/PasskeyProviderService.kt @@ -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 } } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/helper/SearchHelper.kt b/app/src/main/java/com/kunzisoft/keepass/database/helper/SearchHelper.kt index 5a7f846a0..277f796f3 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/helper/SearchHelper.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/helper/SearchHelper.kt @@ -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 diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.kt b/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.kt index 762b51196..e817718ce 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.kt @@ -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), diff --git a/app/src/main/java/com/kunzisoft/keepass/utils/AppUtil.kt b/app/src/main/java/com/kunzisoft/keepass/utils/AppUtil.kt index 9c64fcdc6..6e7bd66d3 100644 --- a/app/src/main/java/com/kunzisoft/keepass/utils/AppUtil.kt +++ b/app/src/main/java/com/kunzisoft/keepass/utils/AppUtil.kt @@ -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,22 +83,31 @@ 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)) { - concreteWebDomain.invoke(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()) + val publicSuffix = publicSuffixList + .getPublicSuffixPlusOne(domain).await() + withContext(Dispatchers.Main) { + concreteWebDomain.invoke(publicSuffix) + } } } else { - concreteWebDomain.invoke(null) + withContext(Dispatchers.Main) { + concreteWebDomain.invoke(null) + } } } } diff --git a/app/src/main/java/com/kunzisoft/keepass/view/SearchFiltersView.kt b/app/src/main/java/com/kunzisoft/keepass/view/SearchFiltersView.kt index 8f957e6f9..fddc63ead 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/SearchFiltersView.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/SearchFiltersView.kt @@ -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() } } diff --git a/app/src/main/res/layout/view_search_filters.xml b/app/src/main/res/layout/view_search_filters.xml index 55dfe13bf..63c9ce893 100644 --- a/app/src/main/res/layout/view_search_filters.xml +++ b/app/src/main/res/layout/view_search_filters.xml @@ -101,6 +101,13 @@ android:checked="false" style="@style/KeepassDXStyle.Chip.Filter" android:text="@string/entry_password"/> + true search_option_password_key false + search_option_application_id_key + false search_option_url_key true search_option_expired_key diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 16eae5ab7..bcb360531 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -110,6 +110,7 @@ Auto-Type sequence Could not find entry data. Password + App Id Tags Custom data Save diff --git a/database/src/main/java/com/kunzisoft/keepass/database/element/Database.kt b/database/src/main/java/com/kunzisoft/keepass/database/element/Database.kt index bc8e143c5..23a75212c 100644 --- a/database/src/main/java/com/kunzisoft/keepass/database/element/Database.kt +++ b/database/src/main/java/com/kunzisoft/keepass/database/element/Database.kt @@ -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 ) diff --git a/database/src/main/java/com/kunzisoft/keepass/database/search/SearchHelper.kt b/database/src/main/java/com/kunzisoft/keepass/database/search/SearchHelper.kt index 15012abad..bb2447924 100644 --- a/database/src/main/java/com/kunzisoft/keepass/database/search/SearchHelper.kt +++ b/database/src/main/java/com/kunzisoft/keepass/database/search/SearchHelper.kt @@ -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,16 +154,32 @@ 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 } if (searchParameters.searchInNotes) { @@ -171,24 +192,20 @@ class SearchHelper { return true } 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 - && checkSearchQuery(field.protectedValue.stringValue, searchParameters) + if (entry.getExtraFields().any { field -> + field.isOTPURIField() + && checkSearchQuery(field.protectedValue.stringValue, searchParameters) }) return true } if (searchParameters.searchInOther) { - if(entry.getExtraFields().any { field -> - field.isOtpExclusion() - && field.isPasskeyExclusion() - && checkSearchQuery(field.protectedValue.toString(), searchParameters) + if (entry.getExtraFields().any { field -> + !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, diff --git a/database/src/main/java/com/kunzisoft/keepass/database/search/SearchParameters.kt b/database/src/main/java/com/kunzisoft/keepass/database/search/SearchParameters.kt index b74b2e317..ceacf27aa 100644 --- a/database/src/main/java/com/kunzisoft/keepass/database/search/SearchParameters.kt +++ b/database/src/main/java/com/kunzisoft/keepass/database/search/SearchParameters.kt @@ -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) diff --git a/database/src/main/java/com/kunzisoft/keepass/model/AppOriginEntryField.kt b/database/src/main/java/com/kunzisoft/keepass/model/AppOriginEntryField.kt index 1799848f6..323a15c3e 100644 --- a/database/src/main/java/com/kunzisoft/keepass/model/AppOriginEntryField.kt +++ b/database/src/main/java/com/kunzisoft/keepass/model/AppOriginEntryField.kt @@ -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) + } } \ No newline at end of file diff --git a/database/src/main/java/com/kunzisoft/keepass/model/EntryInfo.kt b/database/src/main/java/com/kunzisoft/keepass/model/EntryInfo.kt index cb8e2ddd0..28ebb5cba 100644 --- a/database/src/main/java/com/kunzisoft/keepass/model/EntryInfo.kt +++ b/database/src/main/java/com/kunzisoft/keepass/model/EntryInfo.kt @@ -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 { return customFields.filter { - !it.isOtpExclusion() - && !it.isPasskeyExclusion() + !it.isOTP() && !it.isPasskey() } } diff --git a/database/src/main/java/com/kunzisoft/keepass/model/PasskeyEntryFields.kt b/database/src/main/java/com/kunzisoft/keepass/model/PasskeyEntryFields.kt index 50eef9c4f..202b690fe 100644 --- a/database/src/main/java/com/kunzisoft/keepass/model/PasskeyEntryFields.kt +++ b/database/src/main/java/com/kunzisoft/keepass/model/PasskeyEntryFields.kt @@ -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 + } } \ No newline at end of file diff --git a/database/src/main/java/com/kunzisoft/keepass/model/SearchInfo.kt b/database/src/main/java/com/kunzisoft/keepass/model/SearchInfo.kt index d3f4d20fc..978a68235 100644 --- a/database/src/main/java/com/kunzisoft/keepass/model/SearchInfo.kt +++ b/database/src/main/java/com/kunzisoft/keepass/model/SearchInfo.kt @@ -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 @@ -97,36 +96,26 @@ class SearchInfo : ObjectNameResource, Parcelable { && relyingParty == null && otpString == null } - - private fun isADomainSearch(): Boolean { - return toString() == webDomain && webDomain != null - } - var isAPasskeySearch: Boolean = false + var isTagSearch: Boolean = false + get() = tag != null + private set - var query: String? = null + var isAppIdSearch: Boolean = false + get() = applicationId != 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 isDomainSearch: Boolean = false + get() = webDomain != null + private set + + 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 diff --git a/database/src/main/java/com/kunzisoft/keepass/otp/OtpEntryFields.kt b/database/src/main/java/com/kunzisoft/keepass/otp/OtpEntryFields.kt index 8c7000efe..5d211e513 100644 --- a/database/src/main/java/com/kunzisoft/keepass/otp/OtpEntryFields.kt +++ b/database/src/main/java/com/kunzisoft/keepass/otp/OtpEntryFields.kt @@ -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 + } } diff --git a/fastlane/metadata/android/en-US/changelogs/143.txt b/fastlane/metadata/android/en-US/changelogs/143.txt index 534ca8036..5b41eedf8 100644 --- a/fastlane/metadata/android/en-US/changelogs/143.txt +++ b/fastlane/metadata/android/en-US/changelogs/143.txt @@ -1,2 +1,3 @@ * Passkeys management #1421 (Thx @cali-95) + * Search settings #2112 #2181 * Small fixes #2171 #2150 #2159 \ No newline at end of file diff --git a/fastlane/metadata/android/fr-FR/changelogs/143.txt b/fastlane/metadata/android/fr-FR/changelogs/143.txt new file mode 100644 index 000000000..7dbcb123b --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/143.txt @@ -0,0 +1,3 @@ + * Gestion de Passkeys #1421 (Thx @cali-95) + * Paramètres de recherche #2112 #2181 + * Petites corrections #2171 #2150 #2159 \ No newline at end of file