1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-12-25 16:59:17 +01:00

detect login form submitted and show notification

This commit is contained in:
Kyle Spearrin 2016-12-30 02:09:54 -05:00
parent 54e8867ce7
commit 4bd34598b1
7 changed files with 403 additions and 124 deletions

View File

@ -1,4 +1,5 @@
var isBackground = true; var isBackground = true;
var loginsToAdd = [];
var i18nService = new i18nService(); var i18nService = new i18nService();
var constantsService = new ConstantsService(); var constantsService = new ConstantsService();
var utilsService = new UtilsService(); var utilsService = new UtilsService();
@ -56,15 +57,28 @@ chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
messageCurrentTab('closeOverlayPopup'); messageCurrentTab('closeOverlayPopup');
} }
else if (msg.command === 'bgOpenNotificationBar') { else if (msg.command === 'bgOpenNotificationBar') {
messageCurrentTab('openNotificationBar'); messageCurrentTab('openNotificationBar', msg.data);
} }
else if (msg.command === 'bgCloseNotificationBar') { else if (msg.command === 'bgCloseNotificationBar') {
messageCurrentTab('closeNotificationBar'); messageCurrentTab('closeNotificationBar');
} }
else if (msg.command === 'bgCollectPageDetails') {
collectPageDetailsForContentScript(sender.tab);
}
else if (msg.command === 'bgAddLogin') {
addLogin(msg.login, sender.tab);
}
else if (msg.command === 'collectPageDetailsResponse') { else if (msg.command === 'collectPageDetailsResponse') {
clearTimeout(autofillTimeout); // messageCurrentTab('openNotificationBar', { type: 'add', typeData: null });
pageDetailsToAutoFill.push({ frameId: sender.frameId, tabId: msg.tabId, details: msg.details }); if (msg.contentScript) {
autofillTimeout = setTimeout(autofillPage, 300); var forms = autofillService.getFormsWithPasswordFields(msg.details);
messageTab(msg.tabId, 'pageDetails', { details: msg.details, forms: forms });
}
else {
clearTimeout(autofillTimeout);
pageDetailsToAutoFill.push({ frameId: sender.frameId, tabId: msg.tabId, details: msg.details });
autofillTimeout = setTimeout(autofillPage, 300);
}
} }
}); });
@ -156,6 +170,7 @@ function buildContextMenu(callback) {
} }
chrome.tabs.onActivated.addListener(function (activeInfo) { chrome.tabs.onActivated.addListener(function (activeInfo) {
checkLoginsToAdd();
refreshBadgeAndMenu(); refreshBadgeAndMenu();
}); });
@ -165,6 +180,7 @@ chrome.tabs.onReplaced.addListener(function (addedTabId, removedTabId) {
return; return;
} }
onReplacedRan = true; onReplacedRan = true;
checkLoginsToAdd();
refreshBadgeAndMenu(); refreshBadgeAndMenu();
}); });
@ -174,6 +190,7 @@ chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
return; return;
} }
onUpdatedRan = true; onUpdatedRan = true;
checkLoginsToAdd();
refreshBadgeAndMenu(); refreshBadgeAndMenu();
}); });
@ -300,6 +317,60 @@ chrome.contextMenus.onClicked.addListener(function (info, tab) {
}); });
function messageCurrentTab(command, data) { function messageCurrentTab(command, data) {
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
var tabId = null;
if (tabs.length > 0) {
tabId = tabs[0].id;
}
else {
return;
}
messageTab(tabId, command, data);
});
}
function messageTab(tabId, command, data) {
if (!tabId) {
return;
}
var obj = {
command: command
};
if (data) {
obj['data'] = data;
}
chrome.tabs.sendMessage(tabId, obj);
}
function collectPageDetailsForContentScript(tab) {
chrome.tabs.sendMessage(tab.id, { command: 'collectPageDetails', tabId: tab.id, contentScript: true }, function () { });
}
function addLogin(login, tab) {
var loginDomain = tldjs.getDomain(login.url);
if (!loginDomain) {
return;
}
loginsToAdd.push({
username: login.username,
password: login.password,
name: loginDomain,
uri: login.url,
tabId: tab.id
});
checkLoginsToAdd();
}
function checkLoginsToAdd() {
if (!loginsToAdd.length) {
return;
}
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
var tabId = null; var tabId = null;
if (tabs.length > 0) { if (tabs.length > 0) {
@ -313,15 +384,17 @@ function messageCurrentTab(command, data) {
return; return;
} }
var obj = { var tabDomain = tldjs.getDomain(tabs[0].url);
command: command if (!tabDomain) {
}; return;
if (data) {
obj['data'] = data;
} }
chrome.tabs.sendMessage(tabId, obj); for (var i = 0; i < loginsToAdd.length; i++) {
if (loginsToAdd[i].tabId === tabId && loginsToAdd[i].name === tabDomain) {
messageTab(tabId, 'openNotificationBar', { type: 'add' });
break;
}
}
}); });
} }

View File

@ -90,7 +90,8 @@
chrome.runtime.sendMessage({ chrome.runtime.sendMessage({
command: 'collectPageDetailsResponse', command: 'collectPageDetailsResponse',
tabId: msg.tabId, tabId: msg.tabId,
details: pageDetailsObj details: pageDetailsObj,
contentScript: msg.contentScript ? true : false
}); });
sendResponse(); sendResponse();
return true; return true;

View File

@ -1,8 +1,14 @@
!(function () { !(function () {
var pageDetails = [],
formData = [];
chrome.runtime.sendMessage({
command: 'bgCollectPageDetails'
});
chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) { chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
if (msg.command === 'openNotificationBar') { if (msg.command === 'openNotificationBar') {
closeBar(); closeBar();
openBar(); openBar(msg.data.type, msg.data.typeData);
sendResponse(); sendResponse();
return true; return true;
} }
@ -11,11 +17,103 @@
sendResponse(); sendResponse();
return true; return true;
} }
else if (msg.command === 'pageDetails') {
console.log(msg.data);
pageDetails.push(msg.data.details);
watchForms(msg.data.forms);
sendResponse();
return true;
}
}); });
function openBar() { function watchForms(forms) {
if (!forms || !forms.length) {
return;
}
for (var i = 0; i < forms.length; i++) {
var form = null,
formId = forms[i].form ? forms[i].form.htmlID : null;
if (formId && formId !== '') {
form = document.getElementById(formId);
}
if (!form) {
var index = parseInt(forms[i].form.opid.split('__')[2]);
form = document.getElementsByTagName('form')[index];
}
if (form) {
forms[i].formElement = form;
formData.push(forms[i]);
form.addEventListener('submit', formSubmitted, false);
}
}
}
function formSubmitted(e) {
for (var i = 0; i < formData.length; i++) {
if (formData[i].formElement === e.target) {
var password = null,
username = null,
passwordId = formData[i].password ? formData[i].password.htmlID : null,
usernameId = formData[i].username ? formData[i].username.htmlID : null;
if (passwordId && passwordId !== '') {
password = document.getElementById(passwordId);
}
else if (formData[i].password) {
password = document.getElementsByTagName('input')[formData[i].password.elementNumber];
}
if (usernameId && usernameId !== '') {
username = document.getElementById(usernameId);
}
else if (formData[i].username) {
username = document.getElementsByTagName('input')[formData[i].username.elementNumber];
}
var login = {
username: username.value,
password: password.value,
url: document.URL
};
if (login.password && login.password !== '') {
chrome.runtime.sendMessage({
command: 'bgAddLogin',
login: login
});
break;
}
}
}
}
function openBar(type, typeData) {
var barPage = 'notification/bar.html';
switch (type) {
case 'info':
barPage = barPage + '?info=' + typeData.text;
break;
case 'warning':
barPage = barPage + '?warning=' + typeData.text;
break;
case 'error':
barPage = barPage + '?error=' + typeData.text;
break;
case 'success':
barPage = barPage + '?success=' + typeData.text;
break;
case 'add':
barPage = barPage + '?add=1';
default:
break;
}
var iframe = document.createElement('iframe'); var iframe = document.createElement('iframe');
iframe.src = chrome.extension.getURL('notification/bar.html'); iframe.src = chrome.extension.getURL(barPage);
iframe.style.cssText = 'height: 41px; width: 100%; border: 0;'; iframe.style.cssText = 'height: 41px; width: 100%; border: 0;';
var frameDiv = document.createElement('div'); var frameDiv = document.createElement('div');

54
src/notification/bar.css Normal file
View File

@ -0,0 +1,54 @@
body {
background-color: #ffffff;
padding: 0;
margin: 0;
height: 100%;
font-size: 14px;
line-height: 16px;
color: #333333;
font-family: Arial, Helvetica, sans-serif;
}
table {
width: 100%;
}
.outter-table > tbody > tr > td {
padding: 0 0 0 10px;
border-bottom: 1px solid #333333;
height: 40px;
}
.outter-table > tbody > tr > td:last-child {
padding-right: 10px;
}
.inner-table td {
padding: 0 10px 0 0;
}
.inner-table td:last-child {
padding: 0;
}
.inner-table td button {
margin-left: 5px;
}
img {
border: 0;
margin: 0;
padding: 0;
}
#logo {
width: 24px;
height: 24px;
display: block;
}
#close {
width: 18px;
height: 18px;
display: block;
}

View File

@ -3,74 +3,41 @@
<head> <head>
<title></title> <title></title>
<meta charset="utf-8" /> <meta charset="utf-8" />
<style> <link rel="stylesheet" type="text/css" href="bar.css" />
body {
background-color: #ffffff;
padding: 0;
margin: 0;
height: 100%;
font-size: 14px;
line-height: 20px;
color: #333333;
font-family: Arial, Helvetica, sans-serif;
}
table {
width: 100%;
}
table td {
height: 40px;
padding: 0 0 0 10px;
border-bottom: 1px solid #333333;
}
table td:last-child {
padding-right: 10px;
}
img {
border: 0;
margin: 0;
padding: 0;
}
#logo {
width: 24px;
height: 24px;
display: block;
}
#content {
}
#close {
width: 18px;
height: 18px;
display: block;
}
</style>
<script>
!(function () {
});
</script>
</head> </head>
<body> <body>
<table cellpadding="0" cellspacing="0"> <table class="outter-table" cellpadding="0" cellspacing="0">
<tr> <tbody>
<td width="24"> <tr>
<img id="logo" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAclBMVEUAAAAzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzOkbymMAAAAJXRSTlMAAQIICQsMGBkaKkJYWWl0dXeMjpWXm6WmtcXM0dPr7fH3+fv9KIjKuwAAAIRJREFUKM/dzUkCgkAMBdHfCArOIypBW4S6/xVdNCqDJ7BWSd4iknKG5ZIkMW4AdzMzs+cI1mG3f4N6ADfqMJR9cFAG2PVhDtsAWQ8igyyAzl3Yw6W9K/YfcAfw8RuUVS1MrlCl+pZ6gM3iAX6mbknR/ikS9XOrBmiWTqOmR05T/SzqLi/Z3CbrA3nnNwAAAABJRU5ErkJggg==" /> <td width="24">
</td> <img id="logo" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAclBMVEUAAAAzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzOkbymMAAAAJXRSTlMAAQIICQsMGBkaKkJYWWl0dXeMjpWXm6WmtcXM0dPr7fH3+fv9KIjKuwAAAIRJREFUKM/dzUkCgkAMBdHfCArOIypBW4S6/xVdNCqDJ7BWSd4iknKG5ZIkMW4AdzMzs+cI1mG3f4N6ADfqMJR9cFAG2PVhDtsAWQ8igyyAzl3Yw6W9K/YfcAfw8RuUVS1MrlCl+pZ6gM3iAX6mbknR/ikS9XOrBmiWTqOmR05T/SzqLi/Z3CbrA3nnNwAAAABJRU5ErkJggg==" />
<td id="content"> </td>
This is the notification bar <td id="content"></td>
</td> <td align="right" width="18">
<td align="right" width="18"> <a href="#" title="Close" id="close-button">
<a href="#" title="Close"> <img id="close" alt="X" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAMAAABhEH5lAAAAWlBMVEUAAAAzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMHgWvyAAAAHXRSTlMAAgMFCA4RLTE0NVlrbG97naCotbe5z+Lk5ujr/TBmF6UAAAB+SURBVBhXXc7JEsJQCETRm3l2ikZj0v//my7ol0rJBjhFAUB+X2oimnUEyF/SFtZKugKzZGslSSOlZAvRCpNtiKwGuLiOaOHPQs6WBB6W4ZA+Dfm/kxx2Elvnxvu2mmKPsk93F7K3Jf1yg+prCXtmQPXZO9+eNGcAZIUFSuAHidYU7gWlVOAAAAAASUVORK5CYII=" />
<img id="close" alt="X" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAMAAABhEH5lAAAAWlBMVEUAAAAzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMHgWvyAAAAHXRSTlMAAgMFCA4RLTE0NVlrbG97naCotbe5z+Lk5ujr/TBmF6UAAAB+SURBVBhXXc7JEsJQCETRm3l2ikZj0v//my7ol0rJBjhFAUB+X2oimnUEyF/SFtZKugKzZGslSSOlZAvRCpNtiKwGuLiOaOHPQs6WBB6W4ZA+Dfm/kxx2Elvnxvu2mmKPsk93F7K3Jf1yg+prCXtmQPXZO9+eNGcAZIUFSuAHidYU7gWlVOAAAAAASUVORK5CYII=" /> </a>
</a> </td>
</td> </tr>
</tr> </tbody>
</table> </table>
<div id="templates" style="display: none;">
<table class="inner-table" cellpadding="0" cellspacing="0" id="template-add">
<tbody>
<tr>
<td>Should bitwarden remember this password for you?</td>
<td align="right" width="200">
<button class="add-save">Save Site</button>
<button class="add-never">Never</button>
</td>
</tr>
</tbody>
</table>
<div id="template-alert">
This is an alert.
</div>
</div>
<script src="../lib/jquery/jquery.js"></script>
<script src="bar.js"></script>
</body> </body>
</html> </html>

48
src/notification/bar.js Normal file
View File

@ -0,0 +1,48 @@
$(function () {
var content = document.getElementById('content'),
template_add = document.getElementById('template-add'),
template_alert = document.getElementById('template-alert');
if (getQueryVariable('add')) {
setContent(template_add);
var add = $('#template-add-clone'),
addButton = $('#template-add-clone.add-save'),
neverButton = $('#template-add-clone.add-never');
}
else if (getQueryVariable('info')) {
setContent(template_alert);
$('#template-alert-clone').text(getQueryVariable('info'));
}
$('#close-button').click(function (e) {
e.preventDefault();
chrome.runtime.sendMessage({
command: 'bgCloseNotificationBar'
});
});
function getQueryVariable(variable) {
var query = window.location.search.substring(1);
var vars = query.split('&');
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split('=');
if (pair[0] == variable) {
return pair[1];
}
}
return null;
}
function setContent(element) {
while (content.firstChild) {
content.removeChild(content.firstChild);
}
var newElement = element.cloneNode(true);
newElement.id = newElement.id + '-clone';
content.appendChild(newElement);
}
});

View File

@ -23,31 +23,10 @@ function initAutofill() {
pf = null, pf = null,
username = null; username = null;
function loadPasswordFields(canBeHidden) { passwordFields = loadPasswordFields(pageDetails, false);
for (var i = 0; i < pageDetails.fields.length; i++) {
if (pageDetails.fields[i].type === 'password' && (canBeHidden || pageDetails.fields[i].viewable)) {
passwordFields.push(pageDetails.fields[i]);
}
}
}
loadPasswordFields(false);
if (!passwordFields.length) { if (!passwordFields.length) {
// not able to find any viewable password fields. maybe there are some "hidden" ones? // not able to find any viewable password fields. maybe there are some "hidden" ones?
loadPasswordFields(true); passwordFields = loadPasswordFields(pageDetails, true);
}
function findUsernameField(passwordField, canBeHidden) {
for (var i = 0; i < pageDetails.fields.length; i++) {
var f = pageDetails.fields[i];
if (f.form === passwordField.form && (canBeHidden || f.viewable)
&& (f.type === 'text' || f.type === 'email' || f.type === 'tel')
&& f.elementNumber < passwordField.elementNumber) {
return f;
}
}
return null;
} }
for (var formKey in pageDetails.forms) { for (var formKey in pageDetails.forms) {
@ -63,11 +42,11 @@ function initAutofill() {
passwords.push(pf); passwords.push(pf);
if (fillUsername) { if (fillUsername) {
username = findUsernameField(pf, false); username = findUsernameField(pageDetails, pf, false);
if (!username) { if (!username) {
// not able to find any viewable username fields. maybe there are some "hidden" ones? // not able to find any viewable username fields. maybe there are some "hidden" ones?
username = findUsernameField(pf, true); username = findUsernameField(pageDetails, pf, true);
} }
if (username) { if (username) {
@ -77,22 +56,6 @@ function initAutofill() {
} }
} }
function findUsernameFieldWithoutForm(passwordField, canBeHidden) {
var usernameField = null;
for (var i = 0; i < pageDetails.fields.length; i++) {
var f = pageDetails.fields[i];
if (f.elementNumber > passwordField.elementNumber) {
break;
}
if ((canBeHidden || f.viewable) && (f.type === 'text' || f.type === 'email' || f.type === 'tel')) {
usernameField = f;
}
}
return usernameField;
}
if (passwordFields.length && !passwords.length) { if (passwordFields.length && !passwords.length) {
// The page does not have any forms with password fields. Use the first password field on the page and the // The page does not have any forms with password fields. Use the first password field on the page and the
// input field just before it as the username. // input field just before it as the username.
@ -101,11 +64,11 @@ function initAutofill() {
passwords.push(pf); passwords.push(pf);
if (fillUsername && pf.elementNumber > 0) { if (fillUsername && pf.elementNumber > 0) {
username = findUsernameFieldWithoutForm(pf, false); username = findUsernameFieldWithoutForm(pageDetails, pf, false);
if (!username) { if (!username) {
// not able to find any viewable username fields. maybe there are some "hidden" ones? // not able to find any viewable username fields. maybe there are some "hidden" ones?
username = findUsernameFieldWithoutForm(pf, true); username = findUsernameFieldWithoutForm(pageDetails, pf, true);
} }
if (username) { if (username) {
@ -130,4 +93,79 @@ function initAutofill() {
return fillScript; return fillScript;
}; };
AutofillService.prototype.getFormsWithPasswordFields = function (pageDetails) {
var passwordFields = [],
formData = [];
passwordFields = loadPasswordFields(pageDetails, false);
if (!passwordFields.length) {
// not able to find any viewable password fields. maybe there are some "hidden" ones?
passwordFields = loadPasswordFields(pageDetails, true);
}
if (passwordFields.length) {
for (var formKey in pageDetails.forms) {
for (var i = 0; i < passwordFields.length; i++) {
var pf = passwordFields[i];
if (formKey === pf.form) {
var uf = findUsernameField(pageDetails, pf, false);
if (!uf) {
// not able to find any viewable username fields. maybe there are some "hidden" ones?
uf = findUsernameField(pageDetails, pf, true);
}
formData.push({
form: pageDetails.forms[formKey],
password: pf,
username: uf
});
break;
}
}
}
}
return formData;
};
function loadPasswordFields(pageDetails, canBeHidden) {
var arr = [];
for (var i = 0; i < pageDetails.fields.length; i++) {
if (pageDetails.fields[i].type === 'password' && (canBeHidden || pageDetails.fields[i].viewable)) {
arr.push(pageDetails.fields[i]);
}
}
return arr;
}
function findUsernameField(pageDetails, passwordField, canBeHidden) {
for (var i = 0; i < pageDetails.fields.length; i++) {
var f = pageDetails.fields[i];
if (f.form === passwordField.form && (canBeHidden || f.viewable)
&& (f.type === 'text' || f.type === 'email' || f.type === 'tel')
&& f.elementNumber < passwordField.elementNumber) {
return f;
}
}
return null;
}
function findUsernameFieldWithoutForm(pageDetails, passwordField, canBeHidden) {
var usernameField = null;
for (var i = 0; i < pageDetails.fields.length; i++) {
var f = pageDetails.fields[i];
if (f.elementNumber > passwordField.elementNumber) {
break;
}
if ((canBeHidden || f.viewable) && (f.type === 'text' || f.type === 'email' || f.type === 'tel')) {
usernameField = f;
}
}
return usernameField;
}
}; };