From bf457d622cbd68b1e6c190a0456f70f487fd2405 Mon Sep 17 00:00:00 2001 From: Maxim Baz Date: Sun, 24 Feb 2019 00:33:30 +0100 Subject: [PATCH] Store users' confirmation to fill foreign form in settings (#35) --- src/background.js | 78 ++++++++++++++++++++++++++++------------------- src/inject.js | 25 ++++++++++----- 2 files changed, 64 insertions(+), 39 deletions(-) diff --git a/src/background.js b/src/background.js index 219be12..ffd1329 100644 --- a/src/background.js +++ b/src/background.js @@ -10,7 +10,8 @@ var appID = "com.github.browserpass.native"; // default settings var defaultSettings = { gpgPath: null, - stores: {} + stores: {}, + foreignFills: {} }; var authListeners = {}; @@ -92,7 +93,7 @@ function saveRecent(settings, login, remove = false) { /** * Call injected form-fill code * - * @param object tab Target tab + * @param object settings Settings object * @param object fillRequest Fill request details * @param boolean allFrames Dispatch to all frames * @param boolean allowForeign Allow foreign-origin iframes @@ -100,7 +101,7 @@ function saveRecent(settings, login, remove = false) { * @return array list of filled fields */ async function dispatchFill( - tab, + settings, fillRequest, allFrames = false, allowForeign = false, @@ -108,36 +109,50 @@ async function dispatchFill( ) { fillRequest = Object.assign(deepCopy(fillRequest), { allowForeign: allowForeign, - allowNoSecret: allowNoSecret + allowNoSecret: allowNoSecret, + approvedForeign: settings.foreignFills[settings.host] }); - var filledFields = await chrome.tabs.executeScript(tab.id, { + var perFrameFillResults = await chrome.tabs.executeScript(settings.tab.id, { allFrames: allFrames, code: `window.browserpass.fillLogin(${JSON.stringify(fillRequest)});` }); - // simplify the list of filled fields - filledFields = filledFields - .reduce((fields, addFields) => fields.concat(addFields), []) - .reduce(function(fields, field) { - if (!fields.includes(field)) { - fields.push(field); + // merge fill resutls in a single object + var fillResult = perFrameFillResults.reduce( + function(merged, frameResult) { + if (typeof frameResult.foreignFill !== "undefined") { + merged.foreignFill = frameResult.foreignFill; } - return fields; - }, []); + for (var field in frameResult.filledFields) { + if (!merged.filledFields.includes(field)) { + merged.filledFields.push(field); + } + } + return merged; + }, + { filledFields: [] } + ); - return filledFields; + // if user answered a foreign-origin confirmation, + // store the answer in the settings + if (typeof fillResult.foreignFill !== "undefined") { + settings.foreignFills[settings.host] = fillResult.foreignFill; + saveSettings(settings); + } + + return fillResult.filledFields; } /** * Fill form fields * - * @param object tab Target tab - * @param object login Login object - * @param array fields List of fields to fill + * @param object settings Settings object + * @param object login Login object + * @param array fields List of fields to fill * @return array List of filled fields */ -async function fillFields(tab, login, fields) { +async function fillFields(settings, login, fields) { // check that required fields are present for (var field of fields) { if (login.fields[field] === null) { @@ -146,39 +161,43 @@ async function fillFields(tab, login, fields) { } // inject script - await chrome.tabs.executeScript(tab.id, { + await chrome.tabs.executeScript(settings.tab.id, { allFrames: true, file: "js/inject.dist.js" }); // build fill request var fillRequest = { - origin: new URL(tab.url).origin, + origin: new URL(settings.tab.url).origin, login: login, fields: fields }; // fill form via injected script - var filledFields = await dispatchFill(tab, fillRequest); + var filledFields = await dispatchFill(settings, fillRequest); // try again using same-origin frames if we couldn't fill a password field if (!filledFields.includes("secret")) { - filledFields = filledFields.concat(await dispatchFill(tab, fillRequest, true)); + filledFields = filledFields.concat(await dispatchFill(settings, fillRequest, true)); } // try again using all available frames if we couldn't fill a password field - if (!filledFields.includes("secret")) { - filledFields = filledFields.concat(await dispatchFill(tab, fillRequest, true, true)); + if (!filledFields.includes("secret") && settings.foreignFills[settings.host] !== false) { + filledFields = filledFields.concat(await dispatchFill(settings, fillRequest, true, true)); } // try again using same-origin frames, and don't require a password field if (!filledFields.length) { - filledFields = filledFields.concat(await dispatchFill(tab, fillRequest, true, false, true)); + filledFields = filledFields.concat( + await dispatchFill(settings, fillRequest, true, false, true) + ); } // try again using all available frames, and don't require a password field - if (!filledFields.length) { - filledFields = filledFields.concat(await dispatchFill(tab, fillRequest, true, true, true)); + if (!filledFields.length && settings.foreignFills[settings.host] !== false) { + filledFields = filledFields.concat( + await dispatchFill(settings, fillRequest, true, true, true) + ); } if (!filledFields.length) { @@ -384,10 +403,7 @@ async function handleMessage(settings, message, sendResponse) { case "fill": try { // dispatch initial fill request - var filledFields = await fillFields(settings.tab, message.login, [ - "login", - "secret" - ]); + var filledFields = await fillFields(settings, message.login, ["login", "secret"]); saveRecent(settings, message.login); // no need to check filledFields, because fillFields() already throws an error if empty diff --git a/src/inject.js b/src/inject.js index 0dabfc0..cc0ae39 100644 --- a/src/inject.js +++ b/src/inject.js @@ -74,29 +74,38 @@ * @since 3.0.0 * * @param object request Form fill request - * @return void + * @return object result of filling a form */ function fillLogin(request) { var autoSubmit = false; - var filledFields = []; + var result = { + filledFields: [], + foreignFill: undefined + }; // get the login form var loginForm = form(); // don't attempt to fill non-secret forms unless non-secret filling is allowed if (!find(PASSWORD_FIELDS, loginForm) && !request.allowNoSecret) { - return filledFields; + return result; } // ensure the origin is the same, or ask the user for permissions to continue if (window.location.origin !== request.origin) { + if (!request.allowForeign) { + return result; + } var message = "You have requested to fill login credentials into an embedded document from a " + "different origin than the main document in this tab. Do you wish to proceed?\n\n" + `Tab origin: ${request.origin}\n` + `Embedded origin: ${window.location.origin}`; - if (!request.allowForeign || !confirm(message)) { - return filledFields; + if (!request.approvedForeign) { + result.foreignFill = confirm(message); + if (!result.foreignFill) { + return result; + } } } @@ -105,7 +114,7 @@ request.fields.includes("login") && update(USERNAME_FIELDS, request.login.fields.login, loginForm) ) { - filledFields.push("login"); + result.filledFields.push("login"); } // fill secret field @@ -113,7 +122,7 @@ request.fields.includes("secret") && update(PASSWORD_FIELDS, request.login.fields.secret, loginForm) ) { - filledFields.push("secret"); + result.filledFields.push("secret"); } // check for multiple password fields in the login form @@ -153,7 +162,7 @@ } // finished filling things successfully - return filledFields; + return result; } /**