From 65e4cf83d86ef072ceb8a421a3f16ab1d0c76dd3 Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Tue, 9 Jun 2020 18:35:40 +0200 Subject: [PATCH 01/10] First commit for autofill blocklist --- .../keepass/activities/GroupActivity.kt | 3 +- .../keepass/database/element/Database.kt | 9 +- .../com/kunzisoft/keepass/model/SearchInfo.kt | 41 +++++++- .../settings/AutofillSettingsFragment.kt | 31 +++++- .../preference/InputListPreference.kt | 38 ++++++++ ...BlacklistPreferenceDialogFragmentCompat.kt | 94 +++++++++++++++++++ .../adapter/AutofillBlacklistAdapter.kt | 85 +++++++++++++++++ .../res/layout/pref_dialog_input_list.xml | 90 ++++++++++++++++++ .../pref_dialog_list_removable_item.xml | 43 +++++++++ app/src/main/res/values/donottranslate.xml | 1 + app/src/main/res/values/strings.xml | 3 + app/src/main/res/xml/preferences_autofill.xml | 8 +- 12 files changed, 435 insertions(+), 11 deletions(-) create mode 100644 app/src/main/java/com/kunzisoft/keepass/settings/preference/InputListPreference.kt create mode 100644 app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlacklistPreferenceDialogFragmentCompat.kt create mode 100644 app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/adapter/AutofillBlacklistAdapter.kt create mode 100644 app/src/main/res/layout/pref_dialog_input_list.xml create mode 100644 app/src/main/res/layout/pref_dialog_list_removable_item.xml 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 766cda86d..50dd998f8 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt @@ -307,9 +307,8 @@ class GroupActivity : LockingActivity(), val searchInfo: SearchInfo? = intent.getParcelableExtra(KEY_SEARCH_INFO) if (searchInfo != null) { intent.action = Intent.ACTION_SEARCH - val searchQuery = searchInfo.webDomain ?: searchInfo.applicationId intent.removeExtra(KEY_SEARCH_INFO) - intent.putExtra(SearchManager.QUERY, searchQuery) + intent.putExtra(SearchManager.QUERY, searchInfo.toString()) return true } return false diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt index 4a006c620..5cdbf0dd4 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt @@ -406,12 +406,9 @@ class Database { fun createVirtualGroupFromSearch(searchInfo: SearchInfo, max: Int = Integer.MAX_VALUE): Group? { - val query = (if (searchInfo.webDomain != null) - searchInfo.webDomain - else - searchInfo.applicationId) - ?: return null - return mSearchHelper?.createVirtualGroupWithSearchResult(this, query, SearchParameters().apply { + if (searchInfo.isNull()) + return null + return mSearchHelper?.createVirtualGroupWithSearchResult(this, searchInfo.toString(), SearchParameters().apply { searchInTitles = false searchInUserNames = false searchInPasswords = false diff --git a/app/src/main/java/com/kunzisoft/keepass/model/SearchInfo.kt b/app/src/main/java/com/kunzisoft/keepass/model/SearchInfo.kt index 8ad6e99f4..283d9f78c 100644 --- a/app/src/main/java/com/kunzisoft/keepass/model/SearchInfo.kt +++ b/app/src/main/java/com/kunzisoft/keepass/model/SearchInfo.kt @@ -1,13 +1,17 @@ package com.kunzisoft.keepass.model +import android.content.res.Resources import android.os.Parcel import android.os.Parcelable +import com.kunzisoft.keepass.utils.ObjectNameResource -class SearchInfo : Parcelable { +class SearchInfo : ObjectNameResource, Parcelable { var applicationId: String? = null var webDomain: String? = null + var genericInfo: String? = null + constructor() private constructor(parcel: Parcel) { @@ -15,6 +19,8 @@ class SearchInfo : Parcelable { applicationId = if (readAppId.isNullOrEmpty()) null else readAppId val readDomain = parcel.readString() webDomain = if (readDomain.isNullOrEmpty()) null else readDomain + val readGeneric = parcel.readString() + genericInfo = if (readGeneric.isNullOrEmpty()) null else readGeneric } override fun describeContents(): Int { @@ -24,6 +30,39 @@ class SearchInfo : Parcelable { override fun writeToParcel(parcel: Parcel, flags: Int) { parcel.writeString(applicationId ?: "") parcel.writeString(webDomain ?: "") + parcel.writeString(genericInfo ?: "") + } + + override fun getName(resources: Resources): String { + return applicationId ?: webDomain ?: genericInfo ?: "" + } + + fun isNull(): Boolean { + return applicationId == null && webDomain == null && genericInfo == null + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as SearchInfo + + if (applicationId != other.applicationId) return false + if (webDomain != other.webDomain) return false + if (genericInfo != other.genericInfo) return false + + return true + } + + override fun hashCode(): Int { + var result = applicationId?.hashCode() ?: 0 + result = 31 * result + (webDomain?.hashCode() ?: 0) + result = 31 * result + (genericInfo?.hashCode() ?: 0) + return result + } + + override fun toString(): String { + return applicationId ?: webDomain ?: genericInfo ?: "" } companion object { diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/AutofillSettingsFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/AutofillSettingsFragment.kt index 96558b1b4..3e560442c 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/AutofillSettingsFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/AutofillSettingsFragment.kt @@ -20,9 +20,11 @@ package com.kunzisoft.keepass.settings import android.os.Bundle +import androidx.fragment.app.DialogFragment +import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat - import com.kunzisoft.keepass.R +import com.kunzisoft.keepass.settings.preferencedialogfragment.AutofillBlacklistPreferenceDialogFragmentCompat class AutofillSettingsFragment : PreferenceFragmentCompat() { @@ -30,4 +32,31 @@ class AutofillSettingsFragment : PreferenceFragmentCompat() { // Load the preferences from an XML resource setPreferencesFromResource(R.xml.preferences_autofill, rootKey) } + + override fun onDisplayPreferenceDialog(preference: Preference?) { + var otherDialogFragment = false + + var dialogFragment: DialogFragment? = null + + when (preference?.key) { + getString(R.string.autofill_blocklist_key) -> { + dialogFragment = AutofillBlacklistPreferenceDialogFragmentCompat.newInstance(preference.key) + } + else -> otherDialogFragment = true + } + + if (dialogFragment != null) { + dialogFragment.setTargetFragment(this, 0) + dialogFragment.show(parentFragmentManager, TAG_AUTOFILL_PREF_FRAGMENT) + } + // Could not be handled here. Try with the super method. + else if (otherDialogFragment) { + super.onDisplayPreferenceDialog(preference) + } + } + + companion object { + + private const val TAG_AUTOFILL_PREF_FRAGMENT = "TAG_AUTOFILL_PREF_FRAGMENT" + } } diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputListPreference.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputListPreference.kt new file mode 100644 index 000000000..21965e957 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputListPreference.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2019 Jeremy Jamet / Kunzisoft. + * + * This file is part of KeePassDX. + * + * KeePassDX is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * KeePassDX is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with KeePassDX. If not, see . + * + */ +package com.kunzisoft.keepass.settings.preference + +import android.content.Context +import android.util.AttributeSet +import androidx.preference.DialogPreference +import com.kunzisoft.keepass.R +import com.kunzisoft.keepass.icons.IconPackChooser +import java.util.ArrayList + +open class InputListPreference @JvmOverloads constructor(context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = R.attr.dialogPreferenceStyle, + defStyleRes: Int = defStyleAttr) + : DialogPreference(context, attrs, defStyleAttr, defStyleRes) { + + override fun getDialogLayoutResource(): Int { + return R.layout.pref_dialog_input_list + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlacklistPreferenceDialogFragmentCompat.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlacklistPreferenceDialogFragmentCompat.kt new file mode 100644 index 000000000..454cf7359 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlacklistPreferenceDialogFragmentCompat.kt @@ -0,0 +1,94 @@ +/* + * Copyright 2020 Jeremy Jamet / Kunzisoft. + * + * This file is part of KeePassDX. + * + * KeePassDX is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * KeePassDX is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with KeePassDX. If not, see . + * + */ +package com.kunzisoft.keepass.settings.preferencedialogfragment + +import android.os.Bundle +import android.view.View +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.kunzisoft.keepass.R +import com.kunzisoft.keepass.model.SearchInfo +import com.kunzisoft.keepass.settings.preferencedialogfragment.adapter.AutofillBlacklistAdapter + +class AutofillBlacklistPreferenceDialogFragmentCompat + : InputPreferenceDialogFragmentCompat(), + AutofillBlacklistAdapter.ItemDeletedCallback { + + private var persistedItems = HashSet() + + private var filterAdapter: AutofillBlacklistAdapter? = null + + override fun onBindDialogView(view: View) { + super.onBindDialogView(view) + + // TODO persistedItems.add + + val addItemButton = view.findViewById(R.id.add_item_button) + addItemButton?.setOnClickListener { + persistedItems.add(SearchInfo().apply { + genericInfo = inputText + }) + filterAdapter?.replaceItems(persistedItems.toList()) + } + + val recyclerView = view.findViewById(R.id.pref_dialog_list) + recyclerView.layoutManager = LinearLayoutManager(context) + + activity?.let { activity -> + filterAdapter = AutofillBlacklistAdapter(activity) + filterAdapter?.setItemDeletedCallback(this) + recyclerView.adapter = filterAdapter + filterAdapter?.replaceItems(persistedItems.toList()) + } + } + + override fun onItemDeleted(item: SearchInfo) { + persistedItems.remove(item) + filterAdapter?.replaceItems(persistedItems.toList()) + } + + private fun getStringItems(): Set { + val setItems = HashSet() + persistedItems.forEach { + it.getName(resources).let { item -> + setItems.add(item) + } + } + return setItems + } + + override fun onDialogClosed(positiveResult: Boolean) { + if (positiveResult) { + preference.persistStringSet(getStringItems()) + } + } + + companion object { + + fun newInstance(key: String): AutofillBlacklistPreferenceDialogFragmentCompat { + val fragment = AutofillBlacklistPreferenceDialogFragmentCompat() + val bundle = Bundle(1) + bundle.putString(ARG_KEY, key) + fragment.arguments = bundle + + return fragment + } + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/adapter/AutofillBlacklistAdapter.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/adapter/AutofillBlacklistAdapter.kt new file mode 100644 index 000000000..7886ba443 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/adapter/AutofillBlacklistAdapter.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2020 Jeremy Jamet / Kunzisoft. + * + * This file is part of KeePassDX. + * + * KeePassDX is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * KeePassDX is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with KeePassDX. If not, see . + * + */ +package com.kunzisoft.keepass.settings.preferencedialogfragment.adapter + +import android.content.Context +import androidx.recyclerview.widget.RecyclerView +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView + +import com.kunzisoft.keepass.R +import com.kunzisoft.keepass.utils.ObjectNameResource + +import java.util.ArrayList + +class AutofillBlacklistAdapter(private val context: Context) + : RecyclerView.Adapter() { + + private val inflater: LayoutInflater = LayoutInflater.from(context) + + val items: MutableList = ArrayList() + + private var itemDeletedCallback: ItemDeletedCallback? = null + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BlacklistItemViewHolder { + val view = inflater.inflate(R.layout.pref_dialog_list_removable_item, parent, false) + return BlacklistItemViewHolder(view) + } + + override fun onBindViewHolder(holder: BlacklistItemViewHolder, position: Int) { + val item = this.items[position] + holder.textItem.text = item.getName(context.resources) + holder.deleteButton.setOnClickListener(OnItemDeleteClickListener(item)) + } + + override fun getItemCount(): Int { + return items.size + } + + fun replaceItems(items: List) { + this.items.clear() + this.items.addAll(items) + notifyDataSetChanged() + } + + private inner class OnItemDeleteClickListener(private val itemClicked: T) : View.OnClickListener { + + override fun onClick(view: View) { + itemDeletedCallback?.onItemDeleted(itemClicked) + notifyDataSetChanged() + } + } + + fun setItemDeletedCallback(itemDeletedCallback: ItemDeletedCallback) { + this.itemDeletedCallback = itemDeletedCallback + } + + interface ItemDeletedCallback { + fun onItemDeleted(item: T) + } + + class BlacklistItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + var textItem: TextView = itemView.findViewById(R.id.pref_dialog_list_text) + var deleteButton: ImageView = itemView.findViewById(R.id.pref_dialog_list_delete_button) + } +} diff --git a/app/src/main/res/layout/pref_dialog_input_list.xml b/app/src/main/res/layout/pref_dialog_input_list.xml new file mode 100644 index 000000000..1b3ca8eee --- /dev/null +++ b/app/src/main/res/layout/pref_dialog_input_list.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/pref_dialog_list_removable_item.xml b/app/src/main/res/layout/pref_dialog_list_removable_item.xml new file mode 100644 index 000000000..24f079b79 --- /dev/null +++ b/app/src/main/res/layout/pref_dialog_list_removable_item.xml @@ -0,0 +1,43 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 9c6b93295..6def8bff3 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -132,6 +132,7 @@ false autofill_auto_search_key true + autofill_blocklist_key settings_advanced_unlock_key diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d69fc5be0..30b9b083d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -52,6 +52,7 @@ Add node Add entry Add group + Add item File information Password checkbox Keyfile checkbox @@ -230,6 +231,7 @@ Do not kill the app… Space Search + Filter Sort Lowest first ↓ Groups before @@ -379,6 +381,7 @@ Audible keypresses Auto search Automatically suggest search results from the web domain or application Id + Blacklist Allow no master key Enable the \"Open\" button if no credentials are selected Delete password diff --git a/app/src/main/res/xml/preferences_autofill.xml b/app/src/main/res/xml/preferences_autofill.xml index ca7d0e860..6dc635efc 100644 --- a/app/src/main/res/xml/preferences_autofill.xml +++ b/app/src/main/res/xml/preferences_autofill.xml @@ -1,6 +1,6 @@ settings_advanced_unlock_key diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fa6c3d126..18dbd89c2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -380,8 +380,11 @@ Vibratory keypresses Audible keypresses Auto search - Automatically suggest search results from the web domain or application Id - Blocklist + Automatically suggest search results from the web domain or application ID + Application block list + Block list that prevents auto filling of apps + Web domain block list + Block list that prevents auto filling of web domains Allow no master key Enable the \"Open\" button if no credentials are selected Delete password diff --git a/app/src/main/res/xml/preferences_autofill.xml b/app/src/main/res/xml/preferences_autofill.xml index 6dc635efc..2cd5447c4 100644 --- a/app/src/main/res/xml/preferences_autofill.xml +++ b/app/src/main/res/xml/preferences_autofill.xml @@ -29,7 +29,12 @@ + android:key="@string/autofill_application_id_blocklist_key" + android:title="@string/autofill_application_id_blocklist_title" + android:summary="@string/autofill_application_id_blocklist_summary"/> + \ No newline at end of file From ade9af9ecd91effec4fdbbc98e925033e8fef703 Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Wed, 10 Jun 2020 16:43:23 +0200 Subject: [PATCH 05/10] Block list verification --- .../keepass/autofill/KeeAutofillService.kt | 60 ++++++++++++------- .../com/kunzisoft/keepass/model/SearchInfo.kt | 17 ++++++ .../keepass/settings/PreferencesUtil.kt | 12 ++++ ...listAppIdPreferenceDialogFragmentCompat.kt | 12 ++-- ...WebDomainPreferenceDialogFragmentCompat.kt | 15 ++--- 5 files changed, 76 insertions(+), 40 deletions(-) diff --git a/app/src/main/java/com/kunzisoft/keepass/autofill/KeeAutofillService.kt b/app/src/main/java/com/kunzisoft/keepass/autofill/KeeAutofillService.kt index d42e65023..d1744c822 100644 --- a/app/src/main/java/com/kunzisoft/keepass/autofill/KeeAutofillService.kt +++ b/app/src/main/java/com/kunzisoft/keepass/autofill/KeeAutofillService.kt @@ -30,6 +30,7 @@ import com.kunzisoft.keepass.activities.AutofillLauncherActivity import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.search.SearchHelper import com.kunzisoft.keepass.model.SearchInfo +import com.kunzisoft.keepass.settings.PreferencesUtil @RequiresApi(api = Build.VERSION_CODES.O) class KeeAutofillService : AutofillService() { @@ -45,32 +46,45 @@ class KeeAutofillService : AutofillService() { // Check user's settings for authenticating Responses and Datasets. StructureParser(latestStructure).parse()?.let { parseResult -> - val searchInfo = SearchInfo().apply { - applicationId = parseResult.applicationId - webDomain = parseResult.domain + // Build search info only if applicationId or webDomain are not blocked + var searchAllowed = true + parseResult.applicationId?.let { + if (PreferencesUtil.applicationIdBlocklist(this).contains(it)) + searchAllowed = false + } + parseResult.domain?.let { + if (PreferencesUtil.webDomainBlocklist(this).contains(it)) + searchAllowed = false } - SearchHelper.checkAutoSearchInfo(this, - Database.getInstance(), - searchInfo, - { items -> - val responseBuilder = FillResponse.Builder() - AutofillHelper.addHeader(responseBuilder, packageName, - parseResult.domain, parseResult.applicationId) - items.forEach { - responseBuilder.addDataset(AutofillHelper.buildDataset(this, it, parseResult)) + if (searchAllowed) { + val searchInfo = SearchInfo().apply { + applicationId = parseResult.applicationId + webDomain = parseResult.domain + } + + SearchHelper.checkAutoSearchInfo(this, + Database.getInstance(), + searchInfo, + { items -> + val responseBuilder = FillResponse.Builder() + AutofillHelper.addHeader(responseBuilder, packageName, + parseResult.domain, parseResult.applicationId) + items.forEach { + responseBuilder.addDataset(AutofillHelper.buildDataset(this, it, parseResult)) + } + callback.onSuccess(responseBuilder.build()) + }, + { + // Show UI if no search result + showUIForEntrySelection(parseResult, searchInfo, callback) + }, + { + // Show UI if database not open + showUIForEntrySelection(parseResult, searchInfo, callback) } - callback.onSuccess(responseBuilder.build()) - }, - { - // Show UI if no search result - showUIForEntrySelection(parseResult, searchInfo, callback) - }, - { - // Show UI if database not open - showUIForEntrySelection(parseResult, searchInfo, callback) - } - ) + ) + } } } diff --git a/app/src/main/java/com/kunzisoft/keepass/model/SearchInfo.kt b/app/src/main/java/com/kunzisoft/keepass/model/SearchInfo.kt index efb05b191..96ab97b4e 100644 --- a/app/src/main/java/com/kunzisoft/keepass/model/SearchInfo.kt +++ b/app/src/main/java/com/kunzisoft/keepass/model/SearchInfo.kt @@ -8,7 +8,21 @@ import com.kunzisoft.keepass.utils.ObjectNameResource class SearchInfo : ObjectNameResource, Parcelable { var applicationId: String? = null + set(value) { + field = when { + value == null -> null + Regex(APPLICATION_ID_REGEX).matches(value) -> value + else -> null + } + } var webDomain: String? = null + set(value) { + field = when { + value == null -> null + Regex(WEB_DOMAIN_REGEX).matches(value) -> value + else -> null + } + } constructor() @@ -59,6 +73,9 @@ class SearchInfo : ObjectNameResource, Parcelable { } companion object { + // https://gist.github.com/rishabhmhjn/8663966 + const val APPLICATION_ID_REGEX = "^(?:[a-zA-Z]+(?:\\d*[a-zA-Z_]*)*)(?:\\.[a-zA-Z]+(?:\\d*[a-zA-Z_]*)*)+\$" + const val WEB_DOMAIN_REGEX = "^(?!://)([a-zA-Z0-9-_]+\\.)*[a-zA-Z0-9][a-zA-Z0-9-_]+\\.[a-zA-Z]{2,11}?\$" @JvmField val CREATOR: Parcelable.Creator = object : Parcelable.Creator { 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 a3208bc15..9b0773e77 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.kt @@ -366,4 +366,16 @@ object PreferencesUtil { return prefs.getBoolean(context.getString(R.string.autofill_auto_search_key), context.resources.getBoolean(R.bool.autofill_auto_search_default)) } + + fun applicationIdBlocklist(context: Context): Set { + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + return prefs.getStringSet(context.getString(R.string.autofill_application_id_blocklist_key), null) + ?: emptySet() + } + + fun webDomainBlocklist(context: Context): Set { + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + return prefs.getStringSet(context.getString(R.string.autofill_web_domain_blocklist_key), null) + ?: emptySet() + } } diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlocklistAppIdPreferenceDialogFragmentCompat.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlocklistAppIdPreferenceDialogFragmentCompat.kt index d24628793..a783b91c3 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlocklistAppIdPreferenceDialogFragmentCompat.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlocklistAppIdPreferenceDialogFragmentCompat.kt @@ -26,17 +26,13 @@ class AutofillBlocklistAppIdPreferenceDialogFragmentCompat : AutofillBlocklistPreferenceDialogFragmentCompat() { override fun buildSearchInfoFromString(searchInfoString: String): SearchInfo? { - return if (Regex(APPLICATION_ID_REGEX).matches(searchInfoString)) { - SearchInfo().apply { this.applicationId = searchInfoString } - } else { - null - } + val newSearchInfo = searchInfoString + // remove chars not allowed in application ID + .replace(Regex("[^a-zA-Z0-9_.]+"), "") + return SearchInfo().apply { this.applicationId = newSearchInfo } } companion object { - // https://gist.github.com/rishabhmhjn/8663966 - private const val APPLICATION_ID_REGEX = "^(?:[a-zA-Z]+(?:\\d*[a-zA-Z_]*)*)(?:\\.[a-zA-Z]+(?:\\d*[a-zA-Z_]*)*)+\$" - fun newInstance(key: String): AutofillBlocklistAppIdPreferenceDialogFragmentCompat { val fragment = AutofillBlocklistAppIdPreferenceDialogFragmentCompat() val bundle = Bundle(1) diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlocklistWebDomainPreferenceDialogFragmentCompat.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlocklistWebDomainPreferenceDialogFragmentCompat.kt index 5a0baaca2..82c7aa0c5 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlocklistWebDomainPreferenceDialogFragmentCompat.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlocklistWebDomainPreferenceDialogFragmentCompat.kt @@ -27,18 +27,15 @@ class AutofillBlocklistWebDomainPreferenceDialogFragmentCompat : AutofillBlocklistPreferenceDialogFragmentCompat() { override fun buildSearchInfoFromString(searchInfoString: String): SearchInfo? { - // remove prefix https:// - val newSearchInfo = searchInfoString.replace(Regex("^.*?://"), "") - return if (Regex(WEB_DOMAIN_REGEX).matches(newSearchInfo)) { - SearchInfo().apply { webDomain = newSearchInfo } - } else { - null - } + val newSearchInfo = searchInfoString + // remove prefix https:// + .replace(Regex("^.*://"), "") + // Remove suffix /login... + .replace(Regex("/.*$"), "") + return SearchInfo().apply { webDomain = newSearchInfo } } companion object { - private const val WEB_DOMAIN_REGEX = "^(?!://)([a-zA-Z0-9-_]+\\.)*[a-zA-Z0-9][a-zA-Z0-9-_]+\\.[a-zA-Z]{2,11}?\$" - fun newInstance(key: String): AutofillBlocklistWebDomainPreferenceDialogFragmentCompat { val fragment = AutofillBlocklistWebDomainPreferenceDialogFragmentCompat() val bundle = Bundle(1) From 9e542d0bbefb60515d066d0d2d7cbb9794197632 Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Wed, 10 Jun 2020 17:52:35 +0200 Subject: [PATCH 06/10] Dialog input much better --- ...BlocklistPreferenceDialogFragmentCompat.kt | 33 +++++++++++-- .../InputPreferenceDialogFragmentCompat.kt | 49 +++++++++++++++---- app/src/main/res/values/strings.xml | 1 + 3 files changed, 70 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlocklistPreferenceDialogFragmentCompat.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlocklistPreferenceDialogFragmentCompat.kt index 28c171147..cdd56ac7b 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlocklistPreferenceDialogFragmentCompat.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlocklistPreferenceDialogFragmentCompat.kt @@ -20,6 +20,8 @@ package com.kunzisoft.keepass.settings.preferencedialogfragment import android.view.View +import android.view.inputmethod.EditorInfo +import android.widget.TextView import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.kunzisoft.keepass.R @@ -47,10 +49,20 @@ abstract class AutofillBlocklistPreferenceDialogFragmentCompat addSearchInfo(searchInfoString) } + setOnInputTextEditorActionListener(TextView.OnEditorActionListener { _, actionId, _ -> + when (actionId) { + EditorInfo.IME_ACTION_DONE -> { + addItemFromInputText() + hideKeyboard() + false + } + else -> false + } + }) + val addItemButton = view.findViewById(R.id.add_item_button) addItemButton?.setOnClickListener { - addSearchInfo(inputText) - filterAdapter?.replaceItems(persistedItems.toList()) + addItemFromInputText() } val recyclerView = view.findViewById(R.id.pref_dialog_list) @@ -64,10 +76,23 @@ abstract class AutofillBlocklistPreferenceDialogFragmentCompat } } - private fun addSearchInfo(searchInfoString: String) { + private fun addSearchInfo(searchInfoString: String): Boolean { val itemToAdd = buildSearchInfoFromString(searchInfoString) - if (itemToAdd != null && !itemToAdd.containsOnlyNullValues()) + return if (itemToAdd != null && !itemToAdd.containsOnlyNullValues()) { persistedItems.add(itemToAdd) + true + } else { + false + } + } + + private fun addItemFromInputText() { + if (addSearchInfo(inputText)) { + inputText = "" + } else { + setInputTextError(getString(R.string.error_string_type)) + } + filterAdapter?.replaceItems(persistedItems.toList()) } override fun onItemDeleted(item: SearchInfo) { diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/InputPreferenceDialogFragmentCompat.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/InputPreferenceDialogFragmentCompat.kt index 1db7c8e49..7932e3d3d 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/InputPreferenceDialogFragmentCompat.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/InputPreferenceDialogFragmentCompat.kt @@ -21,10 +21,12 @@ package com.kunzisoft.keepass.settings.preferencedialogfragment import android.view.View import android.view.inputmethod.EditorInfo +import android.view.inputmethod.InputMethodManager import android.widget.CompoundButton import android.widget.EditText import android.widget.TextView import androidx.annotation.StringRes +import androidx.core.content.ContextCompat import androidx.preference.PreferenceDialogFragmentCompat import com.kunzisoft.keepass.R @@ -34,6 +36,8 @@ abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCom private var textExplanationView: TextView? = null private var switchElementView: CompoundButton? = null + private var mOnInputTextEditorActionListener: TextView.OnEditorActionListener? = null + var inputText: String get() = this.inputTextView?.text?.toString() ?: "" set(inputText) { @@ -43,6 +47,14 @@ abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCom } } + fun setInputTextError(error: CharSequence) { + this.inputTextView?.error = error + } + + fun setOnInputTextEditorActionListener(onEditorActionListener: TextView.OnEditorActionListener) { + this.mOnInputTextEditorActionListener = onEditorActionListener + } + var explanationText: String? get() = textExplanationView?.text?.toString() ?: "" set(explanationText) { @@ -63,16 +75,21 @@ abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCom inputTextView = view.findViewById(R.id.input_text) inputTextView?.apply { imeOptions = EditorInfo.IME_ACTION_DONE - setOnEditorActionListener { _, actionId, _ -> - when (actionId) { - EditorInfo.IME_ACTION_DONE -> { - onDialogClosed(true) - dialog?.dismiss() - true - } - else -> { - false + setOnEditorActionListener { v, actionId, event -> + if (mOnInputTextEditorActionListener == null) { + when (actionId) { + EditorInfo.IME_ACTION_DONE -> { + onDialogClosed(true) + dialog?.dismiss() + true + } + else -> { + false + } } + } else { + mOnInputTextEditorActionListener?.onEditorAction(v, actionId, event) + ?: false } } } @@ -82,6 +99,20 @@ abstract class InputPreferenceDialogFragmentCompat : PreferenceDialogFragmentCom switchElementView?.visibility = View.GONE } + protected fun hideKeyboard(): Boolean { + context?.let { + ContextCompat.getSystemService(it, InputMethodManager::class.java)?.let { inputManager -> + activity?.currentFocus?.let { focus -> + val windowToken = focus.windowToken + if (windowToken != null) { + return inputManager.hideSoftInputFromWindow(windowToken, 0) + } + } + } + } + return false + } + fun setInoutText(@StringRes inputTextId: Int) { inputText = getString(inputTextId) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 18dbd89c2..94e63e242 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -132,6 +132,7 @@ Counter must be between %1$d and %2$d. Period must be between %1$d and %2$d seconds. Token must contain %1$d to %2$d digits. + This text does not match the requested item. Field name Field value Could not find file. Try reopening it from your file browser. From 6e2d84be337c191e0bcdf7293f9e3b8e6618cc64 Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Wed, 10 Jun 2020 18:02:52 +0200 Subject: [PATCH 07/10] Dialog input much better --- ...AutofillBlocklistPreferenceDialogFragmentCompat.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlocklistPreferenceDialogFragmentCompat.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlocklistPreferenceDialogFragmentCompat.kt index cdd56ac7b..ef9ee1af1 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlocklistPreferenceDialogFragmentCompat.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlocklistPreferenceDialogFragmentCompat.kt @@ -52,9 +52,14 @@ abstract class AutofillBlocklistPreferenceDialogFragmentCompat setOnInputTextEditorActionListener(TextView.OnEditorActionListener { _, actionId, _ -> when (actionId) { EditorInfo.IME_ACTION_DONE -> { - addItemFromInputText() - hideKeyboard() - false + if (inputText.isEmpty()) { + onDialogClosed(true) + dialog?.dismiss() + true + } else { + addItemFromInputText() + false + } } else -> false } From 6903099873a74b1babe17ddc873c25c815645332 Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Wed, 10 Jun 2020 18:16:37 +0200 Subject: [PATCH 08/10] Better block list algorithm --- .../kunzisoft/keepass/autofill/KeeAutofillService.kt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/kunzisoft/keepass/autofill/KeeAutofillService.kt b/app/src/main/java/com/kunzisoft/keepass/autofill/KeeAutofillService.kt index d1744c822..deb37bccd 100644 --- a/app/src/main/java/com/kunzisoft/keepass/autofill/KeeAutofillService.kt +++ b/app/src/main/java/com/kunzisoft/keepass/autofill/KeeAutofillService.kt @@ -49,12 +49,20 @@ class KeeAutofillService : AutofillService() { // Build search info only if applicationId or webDomain are not blocked var searchAllowed = true parseResult.applicationId?.let { - if (PreferencesUtil.applicationIdBlocklist(this).contains(it)) + if (PreferencesUtil.applicationIdBlocklist(this).any { appIdBlocked -> + it.contains(appIdBlocked) + } + ) { searchAllowed = false + } } parseResult.domain?.let { - if (PreferencesUtil.webDomainBlocklist(this).contains(it)) + if (PreferencesUtil.webDomainBlocklist(this).any { webDomainBlocked -> + it.contains(webDomainBlocked) + } + ) { searchAllowed = false + } } if (searchAllowed) { From 55cc782cc693b00a2ad6049f62d0b43d2fabff14 Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Wed, 10 Jun 2020 18:33:22 +0200 Subject: [PATCH 09/10] Keep list during orientation change --- ...BlocklistPreferenceDialogFragmentCompat.kt | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlocklistPreferenceDialogFragmentCompat.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlocklistPreferenceDialogFragmentCompat.kt index ef9ee1af1..11383d2b0 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlocklistPreferenceDialogFragmentCompat.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/AutofillBlocklistPreferenceDialogFragmentCompat.kt @@ -19,6 +19,7 @@ */ package com.kunzisoft.keepass.settings.preferencedialogfragment +import android.os.Bundle import android.view.View import android.view.inputmethod.EditorInfo import android.widget.TextView @@ -42,13 +43,27 @@ abstract class AutofillBlocklistPreferenceDialogFragmentCompat abstract fun buildSearchInfoFromString(searchInfoString: String): SearchInfo? + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // To get items for saved instance state + savedInstanceState?.getParcelableArray(ITEMS_KEY)?.let { + it.forEach { itemSaved -> + (itemSaved as SearchInfo?)?.let { item -> + persistedItems.add(item) + } + } + } ?: run { + // Or from preference + preference.getPersistedStringSet(emptySet()).forEach { searchInfoString -> + addSearchInfo(searchInfoString) + } + } + } + override fun onBindDialogView(view: View) { super.onBindDialogView(view) - preference.getPersistedStringSet(emptySet()).forEach { searchInfoString -> - addSearchInfo(searchInfoString) - } - setOnInputTextEditorActionListener(TextView.OnEditorActionListener { _, actionId, _ -> when (actionId) { EditorInfo.IME_ACTION_DONE -> { @@ -115,9 +130,18 @@ abstract class AutofillBlocklistPreferenceDialogFragmentCompat return setItems } + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putParcelableArray(ITEMS_KEY, persistedItems.toTypedArray()) + } + override fun onDialogClosed(positiveResult: Boolean) { if (positiveResult) { preference.persistStringSet(getStringItems()) } } + + companion object { + private const val ITEMS_KEY = "ITEMS_KEY" + } } From dff23865941826e9b8e05e2a2fb7048653ba9906 Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Wed, 10 Jun 2020 18:47:10 +0200 Subject: [PATCH 10/10] Fix shrink --- .../main/res/layout/pref_dialog_input_list.xml | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/app/src/main/res/layout/pref_dialog_input_list.xml b/app/src/main/res/layout/pref_dialog_input_list.xml index a5f31ee02..a26dc04c9 100644 --- a/app/src/main/res/layout/pref_dialog_input_list.xml +++ b/app/src/main/res/layout/pref_dialog_input_list.xml @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with KeePassDX. If not, see . --> - + android:paddingRight="24dp" /> - \ No newline at end of file + \ No newline at end of file