Store users' confirmation to fill foreign form in settings (#35)

This commit is contained in:
Maxim Baz
2019-02-24 00:33:30 +01:00
committed by GitHub
parent 64c95daa62
commit bf457d622c
2 changed files with 64 additions and 39 deletions

View File

@@ -10,7 +10,8 @@ var appID = "com.github.browserpass.native";
// default settings // default settings
var defaultSettings = { var defaultSettings = {
gpgPath: null, gpgPath: null,
stores: {} stores: {},
foreignFills: {}
}; };
var authListeners = {}; var authListeners = {};
@@ -92,7 +93,7 @@ function saveRecent(settings, login, remove = false) {
/** /**
* Call injected form-fill code * Call injected form-fill code
* *
* @param object tab Target tab * @param object settings Settings object
* @param object fillRequest Fill request details * @param object fillRequest Fill request details
* @param boolean allFrames Dispatch to all frames * @param boolean allFrames Dispatch to all frames
* @param boolean allowForeign Allow foreign-origin iframes * @param boolean allowForeign Allow foreign-origin iframes
@@ -100,7 +101,7 @@ function saveRecent(settings, login, remove = false) {
* @return array list of filled fields * @return array list of filled fields
*/ */
async function dispatchFill( async function dispatchFill(
tab, settings,
fillRequest, fillRequest,
allFrames = false, allFrames = false,
allowForeign = false, allowForeign = false,
@@ -108,36 +109,50 @@ async function dispatchFill(
) { ) {
fillRequest = Object.assign(deepCopy(fillRequest), { fillRequest = Object.assign(deepCopy(fillRequest), {
allowForeign: allowForeign, 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, allFrames: allFrames,
code: `window.browserpass.fillLogin(${JSON.stringify(fillRequest)});` code: `window.browserpass.fillLogin(${JSON.stringify(fillRequest)});`
}); });
// simplify the list of filled fields // merge fill resutls in a single object
filledFields = filledFields var fillResult = perFrameFillResults.reduce(
.reduce((fields, addFields) => fields.concat(addFields), []) function(merged, frameResult) {
.reduce(function(fields, field) { if (typeof frameResult.foreignFill !== "undefined") {
if (!fields.includes(field)) { merged.foreignFill = frameResult.foreignFill;
fields.push(field);
} }
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 * Fill form fields
* *
* @param object tab Target tab * @param object settings Settings object
* @param object login Login object * @param object login Login object
* @param array fields List of fields to fill * @param array fields List of fields to fill
* @return array List of filled fields * @return array List of filled fields
*/ */
async function fillFields(tab, login, fields) { async function fillFields(settings, login, fields) {
// check that required fields are present // check that required fields are present
for (var field of fields) { for (var field of fields) {
if (login.fields[field] === null) { if (login.fields[field] === null) {
@@ -146,39 +161,43 @@ async function fillFields(tab, login, fields) {
} }
// inject script // inject script
await chrome.tabs.executeScript(tab.id, { await chrome.tabs.executeScript(settings.tab.id, {
allFrames: true, allFrames: true,
file: "js/inject.dist.js" file: "js/inject.dist.js"
}); });
// build fill request // build fill request
var fillRequest = { var fillRequest = {
origin: new URL(tab.url).origin, origin: new URL(settings.tab.url).origin,
login: login, login: login,
fields: fields fields: fields
}; };
// fill form via injected script // 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 // try again using same-origin frames if we couldn't fill a password field
if (!filledFields.includes("secret")) { 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 // try again using all available frames if we couldn't fill a password field
if (!filledFields.includes("secret")) { if (!filledFields.includes("secret") && settings.foreignFills[settings.host] !== false) {
filledFields = filledFields.concat(await dispatchFill(tab, fillRequest, true, true)); filledFields = filledFields.concat(await dispatchFill(settings, fillRequest, true, true));
} }
// try again using same-origin frames, and don't require a password field // try again using same-origin frames, and don't require a password field
if (!filledFields.length) { 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 // try again using all available frames, and don't require a password field
if (!filledFields.length) { if (!filledFields.length && settings.foreignFills[settings.host] !== false) {
filledFields = filledFields.concat(await dispatchFill(tab, fillRequest, true, true, true)); filledFields = filledFields.concat(
await dispatchFill(settings, fillRequest, true, true, true)
);
} }
if (!filledFields.length) { if (!filledFields.length) {
@@ -384,10 +403,7 @@ async function handleMessage(settings, message, sendResponse) {
case "fill": case "fill":
try { try {
// dispatch initial fill request // dispatch initial fill request
var filledFields = await fillFields(settings.tab, message.login, [ var filledFields = await fillFields(settings, message.login, ["login", "secret"]);
"login",
"secret"
]);
saveRecent(settings, message.login); saveRecent(settings, message.login);
// no need to check filledFields, because fillFields() already throws an error if empty // no need to check filledFields, because fillFields() already throws an error if empty

View File

@@ -74,29 +74,38 @@
* @since 3.0.0 * @since 3.0.0
* *
* @param object request Form fill request * @param object request Form fill request
* @return void * @return object result of filling a form
*/ */
function fillLogin(request) { function fillLogin(request) {
var autoSubmit = false; var autoSubmit = false;
var filledFields = []; var result = {
filledFields: [],
foreignFill: undefined
};
// get the login form // get the login form
var loginForm = form(); var loginForm = form();
// don't attempt to fill non-secret forms unless non-secret filling is allowed // don't attempt to fill non-secret forms unless non-secret filling is allowed
if (!find(PASSWORD_FIELDS, loginForm) && !request.allowNoSecret) { 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 // ensure the origin is the same, or ask the user for permissions to continue
if (window.location.origin !== request.origin) { if (window.location.origin !== request.origin) {
if (!request.allowForeign) {
return result;
}
var message = var message =
"You have requested to fill login credentials into an embedded document from a " + "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" + "different origin than the main document in this tab. Do you wish to proceed?\n\n" +
`Tab origin: ${request.origin}\n` + `Tab origin: ${request.origin}\n` +
`Embedded origin: ${window.location.origin}`; `Embedded origin: ${window.location.origin}`;
if (!request.allowForeign || !confirm(message)) { if (!request.approvedForeign) {
return filledFields; result.foreignFill = confirm(message);
if (!result.foreignFill) {
return result;
}
} }
} }
@@ -105,7 +114,7 @@
request.fields.includes("login") && request.fields.includes("login") &&
update(USERNAME_FIELDS, request.login.fields.login, loginForm) update(USERNAME_FIELDS, request.login.fields.login, loginForm)
) { ) {
filledFields.push("login"); result.filledFields.push("login");
} }
// fill secret field // fill secret field
@@ -113,7 +122,7 @@
request.fields.includes("secret") && request.fields.includes("secret") &&
update(PASSWORD_FIELDS, request.login.fields.secret, loginForm) update(PASSWORD_FIELDS, request.login.fields.secret, loginForm)
) { ) {
filledFields.push("secret"); result.filledFields.push("secret");
} }
// check for multiple password fields in the login form // check for multiple password fields in the login form
@@ -153,7 +162,7 @@
} }
// finished filling things successfully // finished filling things successfully
return filledFields; return result;
} }
/** /**