Allow filling KPH attributes from context menu

Co-authored-by: varjolintu <sami.vanttinen@protonmail.com>
This commit is contained in:
Bart Nagel 2020-09-27 12:14:08 +03:00 committed by Jonathan White
parent 376510c3f8
commit 08380827c2
5 changed files with 93 additions and 2 deletions

View file

@ -27,6 +27,10 @@
"message": "Show Password Generator",
"description": "Show password generator text."
},
"contextMenuFillAttribute": {
"message": "Fill attribute…",
"description": "Context menu parent item for filling a custom attribute."
},
"multipleCredentialsDetected": {
"message": "HTTP authentication with multiple credentials detected. Click on the extension icon to choose the correct one.",
"description": "Notification when HTTP Authentication has multiple credentials detected."

View file

@ -236,5 +236,6 @@ kpxcEvent.messageHandlers = {
'update_credentials': keepass.updateCredentials,
'username_field_detected': kpxcEvent.onUsernameFieldDetected,
'save_settings': kpxcEvent.onSaveSettings,
'update_available_keepassxc': kpxcEvent.onUpdateAvailableKeePassXC
'update_available_keepassxc': kpxcEvent.onUpdateAvailableKeePassXC,
'update_context_menu': page.updateContextMenu
};

View file

@ -107,6 +107,7 @@ const contextMenuItems = [
{ title: tr('contextMenuFillUsernameAndPassword'), action: 'fill_username_password' },
{ title: tr('contextMenuFillPassword'), action: 'fill_password' },
{ title: tr('contextMenuFillTOTP'), action: 'fill_totp' },
{ title: tr('contextMenuFillAttribute'), id: 'fill_attribute', visible: false },
{ title: tr('contextMenuShowPasswordGenerator'), action: 'show_password_generator' },
{ title: tr('contextMenuSaveCredentials'), action: 'remember_credentials' }
];
@ -122,6 +123,8 @@ for (const item of contextMenuItems) {
browser.contextMenus.create({
title: item.title,
contexts: menuContexts,
visible: item.visible,
id: item.id,
onclick: (info, tab) => {
browser.tabs.sendMessage(tab.id, {
action: item.action

View file

@ -24,6 +24,7 @@ const defaultSettings = {
};
var page = {};
page.attributeMenuItemIds = [];
page.blockedTabs = [];
page.currentRequest = {};
page.currentTabId = -1;
@ -170,7 +171,14 @@ page.initSitePreferences = async function() {
await browser.storage.local.set({ 'settings': page.settings });
};
page.switchTab = function(tab) {
page.switchTab = async function(tab) {
browser.contextMenus.update('fill_attribute', { visible: false });
if (page.tabs[tab.id]
&& page.tabs[tab.id].credentials.length > 0
&& page.tabs[tab.id].credentials.some(e => e.stringFields && e.stringFields.length > 0)) {
await page.updateContextMenu(tab, page.tabs[tab.id].credentials);
}
browserAction.showDefault(tab);
browser.tabs.sendMessage(tab.id, { action: 'activated_tab' }).catch((e) => {
console.log('Cannot send activated_tab message: ', e);
@ -203,6 +211,8 @@ page.clearLogins = function(tabId) {
page.tabs[tabId].loginList = [];
page.currentRequest = {};
page.passwordFilled = false;
browser.contextMenus.update('fill_attribute', { visible: false });
};
// Clear all logins from all pages and update the content scripts
@ -234,6 +244,7 @@ page.createTabEntry = function(tabId) {
};
page.clearSubmittedCredentials();
browser.contextMenus.update('fill_attribute', { visible: false });
};
page.removePageInformationFromNotExistingTabs = async function() {
@ -312,3 +323,51 @@ page.setSubmitted = async function(tab, args = []) {
const [ submitted, username, password, url, oldCredentials ] = args;
page.setSubmittedCredentials(submitted, username, password, url, oldCredentials, tab.id);
};
// Update context menu for attribute filling
page.updateContextMenu = async function(tab, credentials) {
// Remove any old attribute items
while (page.attributeMenuItemIds.length) {
browser.contextMenus.remove(page.attributeMenuItemIds.pop());
}
// Set parent item visibility
browser.contextMenus.update('fill_attribute', { visible: true });
// Add any new attribute items
for (const cred of credentials) {
if (!cred.stringFields) {
continue;
}
for (const attribute of cred.stringFields) {
// Show username inside [] if there are KPH attributes inside multiple credentials
const attributeName = Object.keys(attribute)[0].slice(5);
const finalName = credentials.length > 1
? `[${cred.login}] ${attributeName}`
: attributeName;
page.attributeMenuItemIds.push(createContextMenuItem({
action: 'fill_attribute',
args: attribute,
parentId: 'fill_attribute',
title: finalName
}));
}
}
};
const createContextMenuItem = function({action, args, ...options}) {
return browser.contextMenus.create({
contexts: menuContexts,
onclick: (info, tab) => {
browser.tabs.sendMessage(tab.id, {
action: action,
args: args
}).catch((err) => {
console.log(err);
});
},
...options
});
};

View file

@ -720,6 +720,21 @@ kpxc.detectDatabaseChange = async function(response) {
}
};
// Fill selected attribute from the context menu
kpxc.fillAttributeToActiveElementWith = async function(attr) {
const el = document.activeElement;
if (el.nodeName !== 'INPUT' || kpxc.credentials.length === 0) {
return;
}
const value = Object.values(attr);
if (!value || value.length === 0) {
return;
}
kpxc.setValue(el, value[0]);
};
// Fill requested from the context menu. Active element is used for combination detection
kpxc.fillInFromActiveElement = async function(passOnly = false) {
if (kpxc.credentials.length === 0) {
@ -1292,6 +1307,12 @@ kpxc.retrieveCredentialsCallback = async function(credentials) {
await kpxc.prepareCredentials();
}
// Update fill_attribute context menu if String Fields are available
const stringFieldsFound = credentials.some(e => e.stringFields && e.stringFields.length > 0);
if (stringFieldsFound) {
await sendMessage('update_context_menu', credentials);
}
// Retrieve submitted credentials if available
const creds = await sendMessage('page_get_submitted');
if (creds && creds.submitted) {
@ -1761,6 +1782,9 @@ browser.runtime.onMessage.addListener(async function(req, sender) {
} else if (req.action === 'fill_totp') {
await kpxc.receiveCredentialsIfNecessary();
kpxc.fillFromTOTP();
} else if (req.action === 'fill_attribute' && req.args) {
await kpxc.receiveCredentialsIfNecessary();
kpxc.fillAttributeToActiveElementWith(req.args);
} else if (req.action === 'ignore_site') {
kpxc.ignoreSite(req.args);
} else if (req.action === 'redetect_fields') {