diff --git a/keepassxc-browser/_locales/en/messages.json b/keepassxc-browser/_locales/en/messages.json index 9bd0fcf..8637bf5 100644 --- a/keepassxc-browser/_locales/en/messages.json +++ b/keepassxc-browser/_locales/en/messages.json @@ -307,6 +307,26 @@ "message": "Please choose the credentials you want to update.", "description": "A popup message shown choosing what credentials user wants to update." }, + "popupRememberChooseGroup": { + "message": "Please choose the group you want to add the new credentials.", + "description": "A popup message shown choosing what group user wants to use for new credentials." + }, + "popupRememberErrorDefaultGroupNotFound": { + "message": "Error: Specified default group not found. Creating all necessary groups.", + "description": "Error message shown when default group set cannot be found." + }, + "popupRememberErrorCreatingNewGroup": { + "message": "New group(s) cannot be created.", + "description": "Error message shown when new groups cannot be created." + }, + "popupRememberErrorPasswordNotChanged": { + "message": "Error: Credentials not updated. The password has not been changed.", + "description": "Error message shown when credential password is not changed." + }, + "popupRememberErrorCannotSaveCredentials": { + "message": "Error: Credentials cannot be saved or updated.", + "description": "Error message shown when credentials cannot be saved or updated." + }, "popupLoginText": { "message": "Select the login information you would like to get entered into the page.", "description": "A popup message shown when one or multiple credentials are present." @@ -447,6 +467,22 @@ "message": "Maximum (Number of) Redirects:", "description": "RMaximum (Number of) Redirects options text." }, + "optionsLabelDefaultGroup": { + "message": "Default group for saving new passwords:", + "description": "Default group options text." + }, + "optionsDefaultGroupHelpText": { + "message": "Separate the group with slashes, for example: Group/ChildGroup.", + "description": "Default group help text." + }, + "optionsLabelDefaultGroupCheckboxText": { + "message": "Always ask where to save new credentials", + "description": "Default group checkbox help text." + }, + "optionsLabelDefaultGroupCheckboxHelpText": { + "message": "When enabled, the popup shows a group listing.", + "description": "Default checkbox explanation help text." + }, "optionsCheckboxUsePasswordGenerator": { "message": "Activate password generator.", "description": "Activate password generator checkbox text." diff --git a/keepassxc-browser/background/browserAction.js b/keepassxc-browser/background/browserAction.js index 4f6835d..c3e8b1b 100755 --- a/keepassxc-browser/background/browserAction.js +++ b/keepassxc-browser/background/browserAction.js @@ -167,13 +167,13 @@ browserAction.stackPop = function(tabId) { browserAction.stackPush = function(data, tabId) { const id = tabId || page.currentTabId; - browserAction.removeLevelFromStack(null, {'id': id}, data.level, '<=', true); + browserAction.removeLevelFromStack(null, { 'id': id }, data.level, '<=', true); page.tabs[id].stack.push(data); }; browserAction.stackUnshift = function(data, tabId) { const id = tabId || page.currentTabId; - browserAction.removeLevelFromStack(null, {'id': id}, data.level, '<=', true); + browserAction.removeLevelFromStack(null, { 'id': id }, data.level, '<=', true); page.tabs[id].stack.unshift(data); }; @@ -193,7 +193,7 @@ browserAction.removeRememberPopup = function(callback, tab, removeImmediately) { const currentMS = Date.now(); if (removeImmediately || (data.visibleForPageUpdates <= 0 && data.redirectOffset > 0)) { browserAction.stackPop(tab.id); - browserAction.show(null, {"id": tab.id}); + browserAction.show(null, { 'id': tab.id }); page.clearCredentials(tab.id); } else if (!isNaN(data.visibleForPageUpdates) && data.redirectOffset > 0 && currentMS >= data.redirectOffset) { data.visibleForPageUpdates -= 1; @@ -202,7 +202,7 @@ browserAction.removeRememberPopup = function(callback, tab, removeImmediately) { }; browserAction.setRememberPopup = function(tabId, username, password, url, usernameExists, credentialsList) { - browser.storage.local.get({'settings': {}}).then(function(item) { + browser.storage.local.get({ 'settings': {} }).then(function(item) { const settings = item.settings; // Don't show anything if the site is in the ignore @@ -233,7 +233,7 @@ browserAction.setRememberPopup = function(tabId, username, password, url, userna index: 0, counter: 0, max: 2, - icons: ['icon_remember_red_background_19x19.png', 'icon_remember_red_lock_19x19.png'] + icons: [ 'icon_remember_red_background_19x19.png', 'icon_remember_red_lock_19x19.png' ] }, icon: 'icon_remember_red_background_19x19.png', popup: 'popup_remember.html' diff --git a/keepassxc-browser/background/event.js b/keepassxc-browser/background/event.js index 921374f..3414888 100755 --- a/keepassxc-browser/background/event.js +++ b/keepassxc-browser/background/event.js @@ -284,8 +284,10 @@ kpxcEvent.messageHandlers = { 'add_credentials': keepass.addCredentials, 'associate': keepass.associate, 'check_update_keepassxc': kpxcEvent.onCheckUpdateKeePassXC, + 'create_new_group': keepass.createNewGroup, 'generate_password': keepass.generatePassword, 'get_connected_database': kpxcEvent.onGetConnectedDatabase, + 'get_database_groups': keepass.getDatabaseGroups, 'get_keepassxc_versions': kpxcEvent.onGetKeePassXCVersions, 'get_status': kpxcEvent.onGetStatus, 'get_tab_information': kpxcEvent.onGetTabInformation, diff --git a/keepassxc-browser/background/keepass.js b/keepassxc-browser/background/keepass.js index 8549fbc..4b18c4d 100755 --- a/keepassxc-browser/background/keepass.js +++ b/keepassxc-browser/background/keepass.js @@ -33,7 +33,9 @@ const kpActions = { CHANGE_PUBLIC_KEYS: 'change-public-keys', LOCK_DATABASE: 'lock-database', DATABASE_LOCKED: 'database-locked', - DATABASE_UNLOCKED: 'database-unlocked' + DATABASE_UNLOCKED: 'database-unlocked', + GET_DATABASE_GROUPS: 'get-database-groups', + CREATE_NEW_GROUP: 'create-new-group' }; const kpErrors = { @@ -127,11 +129,11 @@ keepass.sendNativeMessage = function(request, enableTimeout = false) { }); }; -keepass.addCredentials = function(callback, tab, username, password, url) { - keepass.updateCredentials(callback, tab, null, username, password, url); +keepass.addCredentials = function(callback, tab, username, password, url, group, groupUuid) { + keepass.updateCredentials(callback, tab, null, username, password, url, group, groupUuid); }; -keepass.updateCredentials = function(callback, tab, entryId, username, password, url) { +keepass.updateCredentials = function(callback, tab, entryId, username, password, url, group, groupUuid) { page.debug('keepass.updateCredentials(callback, {1}, {2}, {3}, [password], {4})', tab.id, entryId, username, url); if (tab && page.tabs[tab.id]) { page.tabs[tab.id].errorMessage = null; @@ -162,6 +164,11 @@ keepass.updateCredentials = function(callback, tab, entryId, username, password, messageData.uuid = entryId; } + if (group && groupUuid) { + messageData.group = group; + messageData.groupUuid = groupUuid; + } + const request = { action: kpAction, message: keepass.encrypt(messageData, nonce), @@ -678,6 +685,135 @@ keepass.lockDatabase = function(tab) { }); }; +keepass.getDatabaseGroups = function(callback, tab) { + keepass.testAssociation((taResponse) => { + if (!taResponse) { + browserAction.showDefault(null, tab); + callback([]); + return; + } + + if (tab && page.tabs[tab.id]) { + page.tabs[tab.id].errorMessage = null; + } + + if (!keepass.isConnected) { + callback([]); + return; + } + + let groups = []; + const kpAction = kpActions.GET_DATABASE_GROUPS; + const nonce = keepass.getNonce(); + const incrementedNonce = keepass.incrementedNonce(nonce); + + const messageData = { + action: kpAction + }; + + const request = { + action: kpAction, + message: keepass.encrypt(messageData, nonce), + nonce: nonce, + clientID: keepass.clientID + }; + + keepass.sendNativeMessage(request).then((response) => { + if (response.message && response.nonce) { + const res = keepass.decrypt(response.message, response.nonce); + if (!res) { + keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE); + callback([]); + return; + } + + const message = nacl.util.encodeUTF8(res); + const parsed = JSON.parse(message); + + if (keepass.verifyResponse(parsed, incrementedNonce)) { + groups = parsed.groups; + groups.defaultGroup = page.settings.defaultGroup; + groups.defaultGroupAlwaysAsk = page.settings.defaultGroupAlwaysAsk; + keepass.updateLastUsed(keepass.databaseHash); + callback(groups); + } else { + console.log('getDatabaseGroups rejected'); + callback([]); + } + } else if (response.error && response.errorCode) { + keepass.handleError(tab, response.errorCode, response.error); + callback([]); + } else { + browserAction.showDefault(null, tab); + callback([]); + } + }); + }, tab, false); +}; + +keepass.createNewGroup = function(callback, tab, groupName) { + keepass.testAssociation((taResponse) => { + if (!taResponse) { + browserAction.showDefault(null, tab); + callback([]); + return; + } + + if (tab && page.tabs[tab.id]) { + page.tabs[tab.id].errorMessage = null; + } + + if (!keepass.isConnected) { + callback([]); + return; + } + + const kpAction = kpActions.CREATE_NEW_GROUP; + const nonce = keepass.getNonce(); + const incrementedNonce = keepass.incrementedNonce(nonce); + + const messageData = { + action: kpAction, + groupName: groupName + }; + + const request = { + action: kpAction, + message: keepass.encrypt(messageData, nonce), + nonce: nonce, + clientID: keepass.clientID + }; + + keepass.sendNativeMessage(request).then((response) => { + if (response.message && response.nonce) { + const res = keepass.decrypt(response.message, response.nonce); + if (!res) { + keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE); + callback([]); + return; + } + + const message = nacl.util.encodeUTF8(res); + const parsed = JSON.parse(message); + + if (keepass.verifyResponse(parsed, incrementedNonce)) { + keepass.updateLastUsed(keepass.databaseHash); + callback(parsed); + } else { + console.log('getDatabaseGroups rejected'); + callback([]); + } + } else if (response.error && response.errorCode) { + keepass.handleError(tab, response.errorCode, response.error); + callback([]); + } else { + browserAction.showDefault(null, tab); + callback([]); + } + }); + }, tab, false); +}; + keepass.generateNewKeyPair = function() { keepass.keyPair = nacl.box.keyPair(); //console.log(nacl.util.encodeBase64(keepass.keyPair.publicKey) + ' ' + nacl.util.encodeBase64(keepass.keyPair.secretKey)); diff --git a/keepassxc-browser/background/page.js b/keepassxc-browser/background/page.js index 95c8884..4db0bc4 100755 --- a/keepassxc-browser/background/page.js +++ b/keepassxc-browser/background/page.js @@ -9,7 +9,9 @@ const defaultSettings = { autoRetrieveCredentials: true, showNotifications: true, showLoginNotifications: true, - saveDomainOnly: true + saveDomainOnly: true, + defaultGroup: '', + defaultGroupAlwaysAsk: false }; var page = {}; @@ -49,7 +51,13 @@ page.initSettings = function() { if (!('saveDomainOnly' in page.settings)) { page.settings.saveDomainOnly = defaultSettings.saveDomainOnly; } - browser.storage.local.set({'settings': page.settings}); + if (!('defaultGroup' in page.settings)) { + page.settings.defaultGroup = defaultSettings.defaultGroup; + } + if (!('defaultGroupAlwaysAsk' in page.settings)) { + page.settings.defaultGroupAlwaysAsk = defaultSettings.defaultGroupAlwaysAsk; + } + browser.storage.local.set({ 'settings': page.settings }); resolve(page.settings); }); }); diff --git a/keepassxc-browser/options/options.css b/keepassxc-browser/options/options.css index 59fcdcc..735a9f2 100644 --- a/keepassxc-browser/options/options.css +++ b/keepassxc-browser/options/options.css @@ -119,6 +119,10 @@ h2+hr { width: 75%; } +#defaultGroup { + width: 50%; +} + tr.clone { display: none; } diff --git a/keepassxc-browser/options/options.html b/keepassxc-browser/options/options.html index e364619..fe617df 100644 --- a/keepassxc-browser/options/options.html +++ b/keepassxc-browser/options/options.html @@ -87,6 +87,30 @@
+