diff options
author | M. Wieschollek <passwords.public@mdns.eu> | 2021-02-28 12:50:11 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-02-28 12:50:11 +0300 |
commit | 2d04bf0f1f483b144e7e65a8ed8ef2b429916641 (patch) | |
tree | 53cb91195e9d1879964fb409c4c3eb3d9cd02de0 | |
parent | f37008c8d32c71bf6fb9a13b1bc0e49d23109a0a (diff) | |
parent | 1792c3e7f7d3b04b02cb8550c48927ccdda18a49 (diff) |
Merge pull request #151 from flo-mic/master
Introduce custom settings for password recommendations
-rw-r--r-- | src/js/Controller/Setting/Get.js | 4 | ||||
-rw-r--r-- | src/js/Controller/Setting/Reset.js | 4 | ||||
-rw-r--r-- | src/js/Controller/Setting/Set.js | 24 | ||||
-rw-r--r-- | src/js/Manager/MiningManager.js | 8 | ||||
-rw-r--r-- | src/js/Manager/RecommendationManager.js | 60 | ||||
-rw-r--r-- | src/js/Search/Query/Field/FieldFactory.js | 16 | ||||
-rw-r--r-- | src/js/Search/Query/Field/FieldStartsWith.js | 42 | ||||
-rw-r--r-- | src/js/Settings/MasterSettingsProvider.js | 38 | ||||
-rw-r--r-- | src/platform/generic/_locales/de/messages.json | 42 | ||||
-rw-r--r-- | src/platform/generic/_locales/en/messages.json | 42 | ||||
-rw-r--r-- | src/vue/Components/Options/Settings.vue | 78 |
11 files changed, 325 insertions, 33 deletions
diff --git a/src/js/Controller/Setting/Get.js b/src/js/Controller/Setting/Get.js index 9d9cc63..cea64a7 100644 --- a/src/js/Controller/Setting/Get.js +++ b/src/js/Controller/Setting/Get.js @@ -20,7 +20,9 @@ export default class Get extends AbstractController { 'server.default', 'theme.current', 'theme.custom', - 'debug.localisation.enabled' + 'debug.localisation.enabled', + 'search.recommendation.mode', + 'search.recommendation.maxRows' ]; } diff --git a/src/js/Controller/Setting/Reset.js b/src/js/Controller/Setting/Reset.js index bd64e4e..3d05c9c 100644 --- a/src/js/Controller/Setting/Reset.js +++ b/src/js/Controller/Setting/Reset.js @@ -17,7 +17,9 @@ export default class Reset extends AbstractController { 'server.default', 'theme.current', 'theme.custom', - 'debug.localisation.enabled' + 'debug.localisation.enabled', + 'search.recommendation.mode', + 'search.recommendation.maxRows' ]; } diff --git a/src/js/Controller/Setting/Set.js b/src/js/Controller/Setting/Set.js index 6e211c8..7522560 100644 --- a/src/js/Controller/Setting/Set.js +++ b/src/js/Controller/Setting/Set.js @@ -36,6 +36,10 @@ export default class Set extends AbstractController { await this._setDefaultServer(value); } else if(setting === 'theme.current') { await this._setCurrentTheme(value); + } else if(setting === 'search.recommendation.mode') { + await this._setSearchRecommendationMode(value); + } else if(setting === 'search.recommendation.maxRows') { + await this._setSearchRecommendationMaxRows(Number(value)); } else if(this._booleanSettings.indexOf(setting) !== -1) { await this._setBoolean(setting, value); } else { @@ -83,6 +87,26 @@ export default class Set extends AbstractController { /** * + * @param {String} value + * @return {Promise<void>} + * @private + */ + async _setSearchRecommendationMode(value) { + await SettingsService.set('search.recommendation.mode', value); + } + + /** + * + * @param {Number} value + * @return {Promise<void>} + * @private + */ + async _setSearchRecommendationMaxRows(value) { + await SettingsService.set('search.recommendation.maxRows', value); + } + + /** + * * @param {String} setting * @param {Boolean} value * @return {Promise<void>} diff --git a/src/js/Manager/MiningManager.js b/src/js/Manager/MiningManager.js index 8647e12..ccb2eaf 100644 --- a/src/js/Manager/MiningManager.js +++ b/src/js/Manager/MiningManager.js @@ -3,6 +3,7 @@ import MiningItem from '@js/Models/Queue/MiningItem'; import ServerManager from '@js/Manager/ServerManager'; import ErrorManager from '@js/Manager/ErrorManager'; import TabManager from '@js/Manager/TabManager'; +import RecommendationManager from '@js/Manager/RecommendationManager'; import SearchQuery from '@js/Search/Query/SearchQuery'; import SearchIndex from '@js/Search/Index/SearchIndex'; import NotificationService from '@js/Services/NotificationService'; @@ -93,6 +94,8 @@ class MiningManager { if(basePassword !== null) { task.setTaskField('id', basePassword.getId()) .setTaskField('label', basePassword.getLabel()) + .setTaskField('url', basePassword.getUrl()) + .setTaskField('hidden', basePassword.getHidden()) .setTaskNew(false); } } @@ -221,7 +224,8 @@ class MiningManager { items = query .where( query.field('password').equals(data.password.value), - query.field('username').equals(data.user.value) + query.field('username').equals(data.user.value), + RecommendationManager.getFilterQuery(query, Url(data.url)) ) .type('password') .hidden(tab.tab.incognito) @@ -258,7 +262,7 @@ class MiningManager { items = query .where( query.field('username').equals(data.user.value), - query.field('host').contains(url.host) + RecommendationManager.getFilterQuery(query, Url(data.url)) ) .type('password') .hidden(tab.tab.incognito) diff --git a/src/js/Manager/RecommendationManager.js b/src/js/Manager/RecommendationManager.js index 3b9af73..018f4b8 100644 --- a/src/js/Manager/RecommendationManager.js +++ b/src/js/Manager/RecommendationManager.js @@ -3,6 +3,7 @@ import Url from 'url-parse'; import TabManager from '@js/Manager/TabManager'; import SearchIndex from '@js/Search/Index/SearchIndex'; import EventQueue from '@js/Event/EventQueue'; +import SettingsService from '@js/Services/SettingsService'; class RecommendationManager { @@ -34,6 +35,23 @@ class RecommendationManager { TabManager.tabChanged.on(this._tabEvent); TabManager.urlChanged.on(this._tabEvent); SearchIndex.listen.on(this._searchEvent); + this.initRecommendationOptions(); + } + + initRecommendationOptions() { + this.options = { searchQuery: "host", maxResults: 8 } + SettingsService.getValue('search.recommendation.mode') + .then((value) => { + if(value) { + this.options.searchQuery = value; + } + }); + SettingsService.getValue('search.recommendation.maxRows') + .then((value) => { + if(value) { + this.options.maxRows = Number(value); + } + }); } /** @@ -65,13 +83,10 @@ class RecommendationManager { let query = new SearchQuery('or'); query - .where( - query.field('host').equals(url.host), - query.field('url').contains(url.pathname.length > 1 ? url.host + url.pathname:url.host) - ) + .where(this.getFilterQuery(query, url)) .type('password') .score(0.3) - .limit(8) + .limit(this.options.maxRows) .sortBy('favorite') .sortBy('uses') .sortBy('shared') @@ -84,6 +99,39 @@ class RecommendationManager { } /** + * @param {SearchQuery} query + * @param {String} url + */ + getFilterQuery(query, url) { + let mode = this.options.searchQuery; + if(mode === 'domain') { + return query.field('host').contains(this.getSearchDomainFromHost(url.host)); + } else if(mode === 'host') { + return query.field('host').startsWith(url.host.split(':')[0]); + } else if(mode === 'hostport') { + return query.field('host').equals(url.host); + } else { + return query.field('url').equals(url.protocol + "//" + url.host + (url.pathname.length > 1 ? url.pathname: "")); + } + } + + + /** + * @param {String} host + */ + getSearchDomainFromHost(host) { + var regIPAdress = /((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))/; + if (regIPAdress.test(host)) { + return host; + } + else { + var domain = host.split(':')[0]; + if(domain === domain.split('.')[0]) return domain; + return domain.split('.').reverse()[1] + "." + domain.split('.').reverse()[0]; + } + } + + /** * * @param {Object} tab * @private @@ -113,4 +161,4 @@ class RecommendationManager { } } -export default new RecommendationManager();
\ No newline at end of file +export default new RecommendationManager(); diff --git a/src/js/Search/Query/Field/FieldFactory.js b/src/js/Search/Query/Field/FieldFactory.js index a37263d..fbece20 100644 --- a/src/js/Search/Query/Field/FieldFactory.js +++ b/src/js/Search/Query/Field/FieldFactory.js @@ -1,6 +1,7 @@ import FieldEquals from '@js/Search/Query/Field/FieldEquals'; import FieldContains from '@js/Search/Query/Field/FieldContains'; import FieldMatches from '@js/Search/Query/Field/FieldMatches'; +import FieldStartsWith from '@js/Search/Query/Field/FieldStartsWith'; import FieldIn from '@js/Search/Query/Field/FieldIn'; import FieldNotEquals from '@js/Search/Query/Field/FieldNotEquals'; import FieldNotIn from '@js/Search/Query/Field/FieldNotIn'; @@ -61,6 +62,21 @@ export default class FieldFactory { /** * + * @param {String} value + * @param {Number} [weight=null] + * @param {String} [name=null] + * @return {FieldContains} + */ + startsWith(value, weight = null, name = null) { + if(!name) { + name = this._name; + } + + return new FieldStartsWith(name, value, weight); + } + + /** + * * @param {String[]} value * @param {String} [name=null] * @return {FieldIn} diff --git a/src/js/Search/Query/Field/FieldStartsWith.js b/src/js/Search/Query/Field/FieldStartsWith.js new file mode 100644 index 0000000..c9c5b71 --- /dev/null +++ b/src/js/Search/Query/Field/FieldStartsWith.js @@ -0,0 +1,42 @@ +import AbstractField from '@js/Search/Query/Field/AbstractField'; + +export default class FieldStartsWith extends AbstractField { + + /** + * + * @param {String} name + * @param {String} value + * @param {Number} weight + */ + constructor(name, value, weight = 1) { + super(name, value); + + if(weight <= 0) weight = 1; + this._weight = weight; + } + + /** + * @inheritDoc + */ + evaluate(item) { + let values = this._getFieldValues(item); + + if(!values) return {passed: false}; + + let search = this._value.toLowerCase(), + checks = 0, + matches = 0; + + for(let value of values) { + checks++; + if(value.startsWith(search)) { + matches += value.split(search).length - 1; + } + } + + if(this._weight !== 1) matches *= this._weight; + if(matches > 0) return {matches, checks, passed: true}; + + return {checks, passed: false}; + } +}
\ No newline at end of file diff --git a/src/js/Settings/MasterSettingsProvider.js b/src/js/Settings/MasterSettingsProvider.js index bd68303..05c9b02 100644 --- a/src/js/Settings/MasterSettingsProvider.js +++ b/src/js/Settings/MasterSettingsProvider.js @@ -76,22 +76,32 @@ class MasterSettingsProvider { ], 'debug.localisation.enabled' : [ 'local.localisation.enabled' - ] + ], + 'search.recommendation.maxRows': [ + 'sync.search.recommendation.maxRows', + 'local.search.recommendation.maxRows' + ], + 'search.recommendation.mode': [ + 'sync.search.recommendation.mode', + 'local.search.recommendation.mode' + ], }; this._defaults = { - 'theme.custom' : null, - 'theme.current' : 'light', - 'server.default' : null, - 'paste.popup.close' : true, - 'paste.form.submit' : true, - 'paste.compromised.warning' : true, - 'paste.autofill' : false, - 'paste.basic-auth' : false, - 'popup.related.search' : true, - 'password.folder.private' : null, - 'notification.password.new' : true, - 'notification.password.update': true, - 'debug.localisation.enabled' : true + 'theme.custom' : null, + 'theme.current' : 'light', + 'server.default' : null, + 'paste.popup.close' : true, + 'paste.form.submit' : true, + 'paste.compromised.warning' : true, + 'paste.autofill' : false, + 'paste.basic-auth' : false, + 'popup.related.search' : true, + 'password.folder.private' : null, + 'notification.password.new' : true, + 'notification.password.update' : true, + 'debug.localisation.enabled' : true, + 'search.recommendation.mode' : 'host', + 'search.recommendation.maxRows': 8 }; } diff --git a/src/platform/generic/_locales/de/messages.json b/src/platform/generic/_locales/de/messages.json index cdf0d4c..61c00be 100644 --- a/src/platform/generic/_locales/de/messages.json +++ b/src/platform/generic/_locales/de/messages.json @@ -1176,5 +1176,47 @@ "DebugNoErrors" : { "message" : "Keine Fehlerberichte vorhanden", "description": "Message shown when no error reports exist instead of the error logs in the error logs section in the extension settings in the debug tab" + }, + "RecommendationSettings" : { + "message" : "Passwort Empfehlung", + "description": "Label of the section password recommendations in the extension settings." + }, + "RecommendationSettingsHelp" : { + "message" : "Beim Ändern dieser Einstellungen muss die Erweiterung neu gestartet werden. Entweder durch deaktivieren und aktivieren der Erweiterung oder mit einem Browser Neustart.", + "description": "Label of the help text for section password recommendations in the extension settings." + }, + "SettingsSearchRecommendationOption" : { + "message" : "Suche Passwort Empfehlung auf Basis von dieser Option.", + "description": "Label of the setting in the extension settings to define how password recommendations are searched." + }, + "LabelSearchRecommendationDomain" : { + "message" : "Domain", + "description": "Find password recommendations by domain. So on page mail.example.com you will see all passwords for the domain and sumdomains of example.com." + }, + "LabelSearchRecommendationHost" : { + "message" : "Host", + "description": "Find password recommendations by host. So on page mail.example.com you will see all passwords for the sumdomain mail e.g. (mail.example.com or mail.example.com:8443)." + }, + "LabelSearchRecommendationHostPort" : { + "message" : "Host + Port", + "description": "Find password recommendations for the specific server and port." + }, + "LabelSearchRecommendationExact" : { + "message" : "Exakte URL", + "description": "Find only passwords where th url matches exact to the current browser url." + }, + "SettingsSearchRecommendationMaxRows" : { + "message" : "Maximale Anzahl an Ergebnissen für die Passwort Empfehlung.", + "description": "Label of the setting in the extension settings to define the maximum number of results for the password recommendation." + }, + "SearchRecommendationMaxRowsNumber" : { + "message" : "$ROW$", + "description" : "Number of results that should be displayed for password recommendations.", + "placeholders": { + "row": { + "content": "$1", + "example": "One of 1, 5, 10, 15 or 20" + } + } } }
\ No newline at end of file diff --git a/src/platform/generic/_locales/en/messages.json b/src/platform/generic/_locales/en/messages.json index e3d3d45..c855735 100644 --- a/src/platform/generic/_locales/en/messages.json +++ b/src/platform/generic/_locales/en/messages.json @@ -1190,5 +1190,47 @@ "DebugNoErrors" : { "message" : "No errors in log", "description": "Message shown when no error reports exist instead of the error logs in the error logs section in the extension settings in the debug tab" + }, + "RecommendationSettings" : { + "message" : "Password recommendations", + "description": "Label of the section password recommendations in the extension settings." + }, + "RecommendationSettingsHelp" : { + "message" : "If you change the settings below you will need to restart the extension. This can be done by disabling/enabling the extension or with a browser restart.", + "description": "Label of the help text for section password recommendations in the extension settings." + }, + "SettingsSearchRecommendationOption" : { + "message" : "Search passwords based on the following option.", + "description": "Label of the setting in the extension settings to define how password recommendations are searched." + }, + "LabelSearchRecommendationDomain" : { + "message" : "Domain", + "description": "Find password recommendations by domain. So on page mail.example.com you will see all passwords for the domain and sumdomains of example.com." + }, + "LabelSearchRecommendationHost" : { + "message" : "Host", + "description": "Find password recommendations by host. So on page mail.example.com you will see all passwords for the sumdomain mail e.g. (mail.example.com or mail.example.com:8443)." + }, + "LabelSearchRecommendationHostPort" : { + "message" : "Host + Port", + "description": "Find password recommendations for the specific server and port." + }, + "LabelSearchRecommendationExact" : { + "message" : "Exact url", + "description": "Find only passwords where th url matches exact to the current browser url." + }, + "SettingsSearchRecommendationMaxRows" : { + "message" : "Maximum number of results for the password recommendation.", + "description": "Label of the setting in the extension settings to define the maximum number of results for the password recommendation." + }, + "SearchRecommendationMaxRowsNumber" : { + "message" : "$ROW$", + "description" : "Number of results that should be displayed for password recommendations.", + "placeholders": { + "row": { + "content": "$1", + "example": "One of 1, 5, 10, 15 or 20" + } + } } } diff --git a/src/vue/Components/Options/Settings.vue b/src/vue/Components/Options/Settings.vue index f6824a5..1eade48 100644 --- a/src/vue/Components/Options/Settings.vue +++ b/src/vue/Components/Options/Settings.vue @@ -39,6 +39,17 @@ <slider-field id="popup-related-search" v-model="relatedSearch"/> <translate tag="label" for="popup-related-search" say="SettingsPopupRelatedSearch"/> </div> + + <translate tag="h3" say="RecommendationSettings"/> + <translate tag="p" say="RecommendationSettingsHelp"/> + <div class="setting"> + <translate tag="label" for="search-recommendation-option" say="SettingsSearchRecommendationOption"/> + <select-field id="search-recommendation-option" :options="recommendationOptions" v-model="recSearchMode"/> + </div> + <div class="setting"> + <translate tag="label" for="search-recommendation-maxRows" say="SettingsSearchRecommendationMaxRows"/> + <select-field id="search-recommendation-maxRows" :options="recommendationMaxRows" v-model="recSearchRows"/> + </div> </div> </template> @@ -48,20 +59,23 @@ import SettingsService from '@js/Services/SettingsService'; import ToastService from '@js/Services/ToastService'; import SliderField from "@vue/Components/Form/SliderField"; + import SelectField from "@vue/Components/Form/SelectField"; import HelpText from "@vue/Components/Options/Setting/HelpText"; export default { - components: {HelpText, SliderField, Translate}, + components: {HelpText, SliderField, SelectField, Translate}, data() { return { - autoclose : false, - autosubmit : false, - autofill : false, - basicAuth : false, - compromised : false, - notifyPwNew : false, - relatedSearch : false, - notifyPwUpdate: false + autoclose : false, + autosubmit : false, + autofill : false, + basicAuth : false, + compromised : false, + notifyPwNew : false, + relatedSearch : false, + notifyPwUpdate : false, + recSearchMode : 'host', + recSearchRows : 8 }; }, @@ -69,6 +83,37 @@ this.loadData(); }, + computed: { + recommendationOptions() { + return [ + { + id : 'domain', + label: 'LabelSearchRecommendationDomain' + }, + { + id : 'host', + label: 'LabelSearchRecommendationHost' + }, + { + id : 'hostport', + label: 'LabelSearchRecommendationHostPort' + }, + { + id : 'exact', + label: 'LabelSearchRecommendationExact' + } + ]; + }, + recommendationMaxRows() { + var i = 1; + var result = []; + for(i =1; i <= 20; i++) { + result.push({id: i, label: ['SearchRecommendationMaxRowsNumber', i]}); + } + return result; + } + }, + methods: { loadData() { this.getSetting('paste.popup.close', 'autoclose'); @@ -79,6 +124,8 @@ this.getSetting('popup.related.search', 'relatedSearch'); this.getSetting('notification.password.new', 'notifyPwNew'); this.getSetting('notification.password.update', 'notifyPwUpdate'); + this.getSetting('search.recommendation.mode', 'recSearchMode'); + this.getSetting('search.recommendation.maxRows', 'recSearchRows'); }, async getSetting(name, variable) { try { @@ -138,6 +185,16 @@ if(oldValue !== null && value !== oldValue) { this.setSetting('notification.password.update', value); } + }, + recSearchMode(value, oldValue) { + if(oldValue !== null && value !== oldValue) { + this.setSetting('search.recommendation.mode', value); + } + }, + recSearchRows(value, oldValue) { + if(oldValue !== null && value !== oldValue) { + this.setSetting('search.recommendation.maxRows', value); + } } } }; @@ -149,6 +206,9 @@ h3 { margin : 1.5rem 1rem .5rem; } + p { + margin : 1.5rem 1rem .5rem; + } .setting { padding : 0.5rem 1rem; |