hopefully support for realms, but some problems on citi
This commit is contained in:
Родитель
28486ae16c
Коммит
cf78626e9a
9
TODO
9
TODO
|
@ -1,8 +1,12 @@
|
|||
get server sync going
|
||||
implement origins in capture, linking, filling, and configs
|
||||
need busy indicator on login page
|
||||
fix update_password in infobar_hooks.js
|
||||
figure out downloadExportDataFile function (does it work, is it exposed?)
|
||||
login success goes to successful install page
|
||||
start capturing login url and title
|
||||
seems to be race condition with citi realm (problems with capturing on accountonline.com and filling on https://creditcards.citi.com/)
|
||||
|
||||
|
||||
get multistage working
|
||||
HTTP auth capture/fill
|
||||
capturing and filling lone password fields
|
||||
tdameritrade may need some type like a human for lone password field on transfer page
|
||||
|
@ -11,4 +15,3 @@ bayareacurling has annoying change password page
|
|||
|
||||
consider supporting grabbing username from select elements (online.citibank.com when remember me is enabled). This site also has a dynamic username box when you say log in as another user
|
||||
|
||||
start capturing login url and title
|
||||
|
|
|
@ -4,8 +4,10 @@ var CapturedCredentialStorage = function(Realms) {
|
|||
|
||||
function mergeCredentials(newCredentials, source) {
|
||||
var oldCredentials = storage[source.id];
|
||||
if (!oldCredentials || newCredentials.realm !== oldCredentials.realm) {
|
||||
// TODO: revisit strict equality requirement here
|
||||
if (!oldCredentials || newCredentials.origin !== oldCredentials.origin) {
|
||||
storage[source.id] = newCredentials;
|
||||
//console.log("Storing credentials", storage[source.id]);
|
||||
return;
|
||||
}
|
||||
if (newCredentials.password) {
|
||||
|
@ -20,28 +22,24 @@ var CapturedCredentialStorage = function(Realms) {
|
|||
// Stores captured credentials
|
||||
// Input:
|
||||
// credentials: object with properties:
|
||||
// usernames: An array of { <name>: <username> } key/value pairs. The <name> is a
|
||||
// descriptive name for the <username>. In the common case where there is a
|
||||
// single username, the <name> will often be "username".
|
||||
// usernames: a username
|
||||
// password: a password
|
||||
// id: identifier for the credentials
|
||||
// source: object with properties:
|
||||
// id: identifier for the credential's source
|
||||
// url: url of the credential's source
|
||||
function setCredentials(credentials, source) {
|
||||
credentials.domain = new Uri(source.url).host();
|
||||
credentials.realm = Realms.getRealm(credentials.domain);
|
||||
credentials.origin = Realms.getOriginForUri(source.url);
|
||||
mergeCredentials(credentials, source);
|
||||
console.log("Storing credentials", storage[source.id]);
|
||||
}
|
||||
|
||||
function getCrendentials(credentials, source, callback) {
|
||||
callback(storage[source.id]);
|
||||
console.log("Getting credentials", storage[source.id]);
|
||||
//console.log("Getting credentials", storage[source.id]);
|
||||
}
|
||||
|
||||
function deleteCredentials(source) {
|
||||
console.log("Deleting credentials for tab", source.id)
|
||||
//console.log("Deleting credentials for tab", source.id)
|
||||
delete storage[source.id]
|
||||
}
|
||||
|
||||
|
|
|
@ -1,30 +1,35 @@
|
|||
var CommandHandler = function(Messaging, CapturedCredentialStorage, Realms) {
|
||||
function addLogin(message, sender) {
|
||||
var currentUser = Gombot.getCurrentUser();
|
||||
var notificationObj = message,
|
||||
tabID = sender.tab.id,
|
||||
username = notificationObj.username;
|
||||
var tabID = sender.tab.id,
|
||||
origin = message.origin,
|
||||
username = message.username,
|
||||
password = message.password;
|
||||
|
||||
// Check to see if the user disabled password saving on this site
|
||||
if (currentUser.get('disabledSites')[notificationObj.hostname] === 'all') {
|
||||
if (currentUser.get('disabledSites')[origin] === 'all') {
|
||||
return;
|
||||
}
|
||||
notificationObj.type = 'password_observed';
|
||||
message.type = 'password_observed';
|
||||
// Look for passwords in use on the current site
|
||||
var loginForSameUsername = currentUser.get('logins').find(function(login) {
|
||||
return login.get('hostname') === hostname &&
|
||||
login.get('username') === username;
|
||||
var loginForSavedUsername = currentUser.get('logins').find(function(login) {
|
||||
// TODO, FIXME: This assumes non-user-edited realms, meaning each realm will be an
|
||||
// array of 1 non-wildcarded realm
|
||||
return Realms.isOriginMemberOfRealm(origin,
|
||||
Realms.getRealmForOrigin(login.get('origins')[0])) &&
|
||||
login.get('username') === username;
|
||||
});
|
||||
if (loginForSameUsername) {
|
||||
if (loginForSameUsername.get("password") === password) {
|
||||
if (loginForSavedUsername) {
|
||||
if (loginForSavedUsername.get("password") === password) {
|
||||
// We're just logging into a site with an existing login. Bail.
|
||||
return;
|
||||
}
|
||||
else {
|
||||
// Prompt user to update password
|
||||
notificationObj.type = 'update_password';
|
||||
message.type = 'update_password';
|
||||
// If the existing login stored for this site was PIN locked,
|
||||
// make sure this new one will be also.
|
||||
notificationObj.pinLocked = loginForSameUsername.get("pinLocked");
|
||||
message.pinLocked = loginForSameUsername.get("pinLocked");
|
||||
}
|
||||
}
|
||||
if (currentUser && currentUser.keys) {
|
||||
|
@ -32,7 +37,7 @@ var CommandHandler = function(Messaging, CapturedCredentialStorage, Realms) {
|
|||
displayInfobar({
|
||||
notify: true,
|
||||
tabID: tabID,
|
||||
notification: notificationObj
|
||||
notification: message
|
||||
});
|
||||
} else {
|
||||
displayInfobar({
|
||||
|
@ -64,13 +69,13 @@ var CommandHandler = function(Messaging, CapturedCredentialStorage, Realms) {
|
|||
}
|
||||
|
||||
function getSavedCredentials(message, sender, callback) {
|
||||
var hostname = Realms.getRealm(sender.tab.url);
|
||||
var pageOrigin = Realms.getOriginForUri(sender.tab.url);
|
||||
var currentUser = Gombot.getCurrentUser();
|
||||
var logins = [];
|
||||
if (currentUser) {
|
||||
logins = currentUser.get('logins').filter(function(x) {
|
||||
return x.hostname === hostname;
|
||||
});
|
||||
logins = currentUser.get('logins').filter(function(login) {
|
||||
return Realms.isOriginMemberOfRealm(pageOrigin, Realms.getRealmForOrigin(login.get('origins')[0]));
|
||||
});
|
||||
}
|
||||
callback(logins);
|
||||
// Chrome requires that we return true if we plan to call a callback
|
||||
|
@ -93,6 +98,7 @@ var CommandHandler = function(Messaging, CapturedCredentialStorage, Realms) {
|
|||
return true;
|
||||
}
|
||||
|
||||
// probably will need to tweak this
|
||||
function getSiteConfig(message, sender, callback) {
|
||||
callback(Gombot.SiteConfigs[Gombot.TldService.getDomain(sender.tab.url)] || {});
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ function formatStoredLogin(login) {
|
|||
return {
|
||||
username: login.username,
|
||||
password: login.password,
|
||||
hostname: login.hostname,
|
||||
origins: [ login.origin ],
|
||||
|
||||
// Fields that may be missing
|
||||
title: login['title'] || '',
|
||||
|
@ -20,8 +20,6 @@ function formatStoredLogin(login) {
|
|||
};
|
||||
}
|
||||
|
||||
// Gombot.getCurrentUser().get('logins').filter(function(x) { return x.hostname == 'facebook.com'; } )
|
||||
|
||||
var infobarHooks = {
|
||||
'password_observed': function (notificationObj,infobarResponse) {
|
||||
console.log(notificationObj);
|
||||
|
@ -43,8 +41,8 @@ var infobarHooks = {
|
|||
break;
|
||||
|
||||
case 'never_for_this_site':
|
||||
var hostname = formattedLoginObj.hostname;
|
||||
currentUser.get('disabledSites')[hostname] = 'all';
|
||||
var origin = formattedLoginObj.origins[0];
|
||||
currentUser.get('disabledSites')[origin] = 'all';
|
||||
currentUser.save();
|
||||
break;
|
||||
|
||||
|
@ -71,6 +69,7 @@ var infobarHooks = {
|
|||
}
|
||||
}
|
||||
},
|
||||
// TODO: fix this
|
||||
'update_password': function(notificationObj,infobarResponse) {
|
||||
User.Logins.add(notificationObj.notification);
|
||||
}
|
||||
|
|
|
@ -26,203 +26,6 @@ var LocalStorage = function() {
|
|||
}
|
||||
};
|
||||
|
||||
// var User = (function(Storage){
|
||||
|
||||
// // In-memory storage for user data that gets synced.
|
||||
// var _logins = {};
|
||||
// var _neverAsk = {};
|
||||
// var _pin = null;
|
||||
|
||||
// // In-memory storasge for user data that's persisted to localStorage, but not synced.
|
||||
// var _didFirstRun = false;
|
||||
|
||||
// // The key for the document in localStorage which holds all of the user
|
||||
// // data synced with the server.
|
||||
// const SYNC_DOCUMENT_KEY = 'gombot_user_data';
|
||||
|
||||
// const DATA_VERSION = 'identity.mozilla.com/gombot/v1/userData';
|
||||
|
||||
// // NB: remove every call to fetchStoredData and updateStoredData before commit!
|
||||
|
||||
// function fetchStoredData(callback) {
|
||||
// var keysToFetch = [
|
||||
// SYNC_DOCUMENT_KEY,
|
||||
// 'did_first_run'
|
||||
// ];
|
||||
// chrome.storage.local.get(SYNC_DOCUMENT_KEY, function(storageObj) {
|
||||
// var userData = storageObj;//[SYNC_DOCUMENT_KEY];
|
||||
// if (userData === undefined) {
|
||||
// userData = {
|
||||
// version: DATA_VERSION,
|
||||
// logins: {},
|
||||
// pin: loginsLock.pin || null,
|
||||
// neverAsk: {}
|
||||
// };
|
||||
// }
|
||||
// callback(userData);
|
||||
// });
|
||||
// }
|
||||
|
||||
// // function updateStoredData(obj) {
|
||||
// // var updatedObj = {};
|
||||
// // updatedObj[SYNC_DOCUMENT_KEY] = obj;
|
||||
// // chrome.storage.local.set(updatedObj);
|
||||
// // }
|
||||
|
||||
// function saveToLocalStorage() {
|
||||
// Storage.set(SYNC_DOCUMENT_KEY, {
|
||||
// version: DATA_VERSION,
|
||||
// logins: _logins || {},
|
||||
// pin: _pin || null,
|
||||
// neverAsk: _neverAsk || {}
|
||||
// });
|
||||
// Storage.set('did_first_run',_didFirstRun);
|
||||
// }
|
||||
|
||||
// function loadFromLocalStorage() {
|
||||
// fetchStoredData(function(userData) {
|
||||
// _logins = userData[SYNC_DOCUMENT_KEY].logins;
|
||||
// _pin = userData[SYNC_DOCUMENT_KEY].logins;
|
||||
// _neverAsk = userData[SYNC_DOCUMENT_KEY].neverAsk;
|
||||
// _didFirstRun = userData['did_first_run'];
|
||||
// });
|
||||
// }
|
||||
|
||||
// // Load from localStorage into memory when extension starts.
|
||||
// loadFromLocalStorage();
|
||||
|
||||
// var userData = {};
|
||||
|
||||
// var loginsObj = (function() {
|
||||
// function formatStoredLogin(login) {
|
||||
// return {
|
||||
// username: login.username,
|
||||
// password: login.password,
|
||||
// hostname: login.hostname,
|
||||
//
|
||||
// // Fields that may be missing
|
||||
// title: login['title'] || '',
|
||||
// url: login['url'] || '',
|
||||
// pinLocked: login['pinLocked'] || false,
|
||||
// supplementalInformation: login['supplementalInformation'] || {}
|
||||
// };
|
||||
// }
|
||||
// return {
|
||||
// // Save a new login object to localStorage
|
||||
// add: function(newLogin) {
|
||||
// var loginObj = formatStoredLogin(newLogin);
|
||||
// // Filter for logins with the same username and hostname.
|
||||
// var existingLoginsForHost = _logins[newLogin.hostname] || [];
|
||||
// _logins[newLogin.hostname] =
|
||||
// existingLoginsForHost.filter(function(_login) {
|
||||
// return _login.username != loginObj.username;
|
||||
// });
|
||||
// _logins[newLogin.hostname].push(loginObj);
|
||||
// saveToLocalStorage();
|
||||
// },
|
||||
// // Takes a hostname and a callback, and passes it a list of login
|
||||
// // objects the user has saved for that domain.
|
||||
// getForHostname: function(hostname/*,callback*/) {
|
||||
// return (_logins[hostname] || []);
|
||||
// },
|
||||
|
||||
// // Mainly for debugging purposes.
|
||||
// deleteForHostname: function(hostname) {
|
||||
// delete _logins[hostname];
|
||||
// saveToLocalStorage();
|
||||
// }
|
||||
// };
|
||||
// })();
|
||||
|
||||
// var neverAskObj = (function() {
|
||||
// return {
|
||||
// // Add a hostname to the list of sites for which we never
|
||||
// // prompt to save passwords
|
||||
// add: function(siteHostname) {
|
||||
// if (!(siteHostname in _neverAsk)) {
|
||||
// _neverAsk[siteHostname] = 'all';
|
||||
// saveToLocalStorage();
|
||||
// }
|
||||
// },
|
||||
// // Takes a callback, and passes it a list of domains the user
|
||||
// // has elected to never save logins on.
|
||||
// get: function(/*callback*/) {
|
||||
// return _.keys(userData.neverAsk);
|
||||
// },
|
||||
// // Takes a hostname and a callback. Passes callback a boolean,
|
||||
// // indicating if the user *should* be asked about logins
|
||||
// // on that domain.
|
||||
// checkForHostname: function(hostname/*,callback*/) {
|
||||
// return !(hostname in this.get());
|
||||
// }
|
||||
// };
|
||||
// })();
|
||||
|
||||
// var firstRunObj = (function(){
|
||||
// return {
|
||||
// wasCompleted: function() {
|
||||
// // Takes a callback, and passes it a boolean indicating whether
|
||||
// // or not the user has completed the first run flow.
|
||||
// // Note that this will return true if the user started the first
|
||||
// // run flow but did not complete it.
|
||||
// return _didFirstRun;
|
||||
// },
|
||||
// setIfCompleted: function(firstRunFinished) {
|
||||
// //setIfDidFirstRun
|
||||
// // Set (or unset) the flag indicating the user has finished the first
|
||||
// // run flow.
|
||||
// _didFirstRun = firstRunFinished;
|
||||
// saveToLocalStorage();
|
||||
// }
|
||||
// };
|
||||
// })();
|
||||
|
||||
// var pinObj = (function() {
|
||||
// return {
|
||||
// validate: function(testPin) {
|
||||
// return testPin == _pin;
|
||||
// },
|
||||
// get: function() {
|
||||
// return _pin;
|
||||
// },
|
||||
// set: function(newPIN) {
|
||||
// _pin = newPIN || null;
|
||||
// saveToLocalStorage();
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// return {
|
||||
// Logins: loginsObj,
|
||||
// neverAsk: neverAskObj,
|
||||
// firstRun: firstRunObj,
|
||||
// PIN: pinObj
|
||||
// };
|
||||
// })(Gombot.Storage);
|
||||
|
||||
// // Takes a callback, and passes it a list of domains the user
|
||||
// // has elected to never save logins on.
|
||||
// function getNeverSaveOnSites(callback) {
|
||||
// fetchStoredData(function(userData) {
|
||||
// callback(_.keys(userData.neverAsk));
|
||||
// });
|
||||
// }
|
||||
|
||||
// // Takes a hostname and a callback, and passes it a list of login
|
||||
// // objects the user has saved for that domain.
|
||||
// function getLoginsForSite(hostname,callback) {
|
||||
// fetchStoredData(function(userData) {
|
||||
// callback(userData.logins[hostname] || []);
|
||||
// });
|
||||
// }
|
||||
|
||||
// // Mainly for debugging purposes.
|
||||
// function deleteLoginsForSite(hostname) {
|
||||
// fetchStoredData(function(userData) {
|
||||
// delete userData[hostname];
|
||||
// updateStoredData(userData);
|
||||
// });
|
||||
// }
|
||||
|
||||
// Returns a string of a comma separated value file containing the hostname, username,
|
||||
// and password for each login the user has saved.
|
||||
function getLoginsCSV(callback) {
|
||||
|
|
|
@ -24,7 +24,7 @@ var Gombot = {};
|
|||
Gombot.Messaging = ChromeMessaging();
|
||||
Gombot.TldService = TldService(Tld, Uri);
|
||||
Gombot.SiteConfigs = SiteConfigs || {};
|
||||
Gombot.Realms = Realms(Gombot.SiteConfigs, Gombot.TldService);
|
||||
Gombot.Realms = Realms(Gombot.SiteConfigs, Uri);
|
||||
Gombot.CapturedCredentialStorage = CapturedCredentialStorage(Gombot.Realms);
|
||||
Gombot.CommandHandler = CommandHandler(Gombot.Messaging, Gombot.CapturedCredentialStorage, Gombot.Realms);
|
||||
Gombot.LocalStorage = LocalStorage();
|
||||
|
@ -44,9 +44,9 @@ Gombot.User = User(Backbone, _, Gombot.LoginCredentialCollection);
|
|||
}
|
||||
})(Gombot);
|
||||
|
||||
var usersStore = new Gombot.Storage("users", function() {
|
||||
Gombot.UserCollection = UserCollection(Backbone, _, Gombot.User, usersStore);
|
||||
initGombot();
|
||||
new Gombot.Storage("users", function(store) {
|
||||
Gombot.UserCollection = UserCollection(Backbone, _, Gombot.User, store);
|
||||
initGombot();
|
||||
});
|
||||
|
||||
function initGombot() {
|
||||
|
|
|
@ -3,10 +3,9 @@ var LoginCredential = function(Backbone, _) {
|
|||
// LoginCredential constructor
|
||||
// data is:
|
||||
// {
|
||||
// "hostname": "www.mozilla.com",
|
||||
// "realm": "mozilla.com",
|
||||
// "origins": [ "https://www.mozilla.com" ],
|
||||
// "title": Mozilla,
|
||||
// "url": <full url to login page>,
|
||||
// "url": "https://www.mozilla.com/login",
|
||||
// "password": "grëën",
|
||||
// "pinLocked": false,
|
||||
// "username": "gömbottest",
|
||||
|
|
|
@ -1,30 +1,63 @@
|
|||
var Realms = function(SiteConfigs, TldService) {
|
||||
var Realms = function(SiteConfigs, Uri) {
|
||||
|
||||
var realmMap = {};
|
||||
|
||||
function buildRealmMap(siteConfigs) {
|
||||
var realms = Object.getOwnPropertyNames(siteConfigs);
|
||||
realmMap = {};
|
||||
var realms = siteConfigs.realms;
|
||||
|
||||
realms.forEach(function(realm) {
|
||||
var domains = siteConfigs[realm].domains;
|
||||
var origins = realm.origins;
|
||||
// presence of a domain field indicates it's a complex realm
|
||||
if (domains) {
|
||||
domains.forEach(function(domain) {
|
||||
realmMap[domain] = realm;
|
||||
if (origins) {
|
||||
origins.forEach(function(origin) {
|
||||
realmMap[origin] = origins;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getRealm(domain) {
|
||||
domain = TldService.getDomain(domain);
|
||||
return realmMap[domain] || domain;
|
||||
function getRealmForOrigin(origin) {
|
||||
var keys = Object.getOwnPropertyNames(realmMap);
|
||||
for (var i=0;i<keys.length;i++) {
|
||||
if (originsMatch(keys[i], origin)) {
|
||||
return realmMap[keys[i]];
|
||||
}
|
||||
}
|
||||
return [ origin ];
|
||||
}
|
||||
|
||||
// TODO: make more general to account for wildcards in origins
|
||||
function originsMatch(origin1, origin2 ) {
|
||||
return origin1 === origin2;
|
||||
}
|
||||
|
||||
function getOriginForUri(uriString) {
|
||||
var uri = new Uri(uriString),
|
||||
origin = uri.protocol()+"://"+uri.host(),
|
||||
port = uri.port();
|
||||
if (port) {
|
||||
origin += ":"+port;
|
||||
}
|
||||
return origin;
|
||||
}
|
||||
|
||||
// a realm is defined by an array of origins
|
||||
function isOriginMemberOfRealm(origin, origins) {
|
||||
var i;
|
||||
for (i=0;i<origins.length;i++) {
|
||||
if (originsMatch(origin,origins[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
buildRealmMap(SiteConfigs);
|
||||
|
||||
return {
|
||||
buildRealmMap: buildRealmMap,
|
||||
getRealm: getRealm
|
||||
getOriginForUri: getOriginForUri,
|
||||
isOriginMemberOfRealm: isOriginMemberOfRealm,
|
||||
getRealmForOrigin: getRealmForOrigin
|
||||
};
|
||||
}
|
|
@ -1,2 +1,2 @@
|
|||
// AUTOGENERATED FILE: edit 'site_configs.yml' instead and run 'build_site_configs.rb'
|
||||
var SiteConfigs = { "bankofamerica.com": { "multiStage": true, "un": "#id" }, "vanguard.com": { "multiStage": true, "un": "#USER" }, "citi.com": { "domains": [ "citibank.com", "accountonline.com", "citicards.com" ] }, "att.com": { "fakePasswordFill": "#password" }};
|
||||
var SiteConfigs = { "bankofamerica.com": { "multiStage": true, "un": "#id" }, "vanguard.com": { "multiStage": true, "un": "#USER" }, "citi.com": { "domains": [ "citibank.com", "accountonline.com", "citicards.com" ], "title": "Citi" }, "att.com": { "fakePasswordFill": "#password" }, "realms": [ { "origins": [ "https://online.citibank.com", "https://www.accountonline.com", "https://creditcards.citi.com" ], "title": "Citi" } ]};
|
|
@ -25,6 +25,8 @@
|
|||
#hulu.com:
|
||||
# clickOn: "input.inactive.dummy.user"
|
||||
|
||||
# CONFIGS
|
||||
|
||||
bankofamerica.com:
|
||||
multiStage: true
|
||||
un: "#id"
|
||||
|
@ -33,9 +35,11 @@ vanguard.com:
|
|||
multiStage: true
|
||||
un: "#USER"
|
||||
|
||||
citi.com: # change domains -> origins
|
||||
domains: ["citibank.com", "accountonline.com", "citicards.com"]
|
||||
title: "Citi"
|
||||
|
||||
att.com:
|
||||
fakePasswordFill: "#password"
|
||||
fakePasswordFill: "#password"
|
||||
|
||||
# REALMS
|
||||
|
||||
realms:
|
||||
- origins: ["https://online.citibank.com", "https://www.accountonline.com", "https://creditcards.citi.com" ]
|
||||
title: "Citi"
|
|
@ -32,7 +32,7 @@ function maybePromptToSaveCapturedCredentials() {
|
|||
if (!credentials || !credentials.password) return;
|
||||
var loginObj = {
|
||||
message: {
|
||||
hostname: credentials.realm,
|
||||
origin: credentials.origin,
|
||||
username: credentials.username,
|
||||
password: credentials.password
|
||||
},
|
||||
|
|
|
@ -40,7 +40,7 @@ Backbone.LocalStorage = window.Store = function(name, callback) {
|
|||
this.name = name;
|
||||
var cb = function(store) {
|
||||
this.records = (store && store.split(",")) || [];
|
||||
if (callback) callback();
|
||||
if (callback) callback(this);
|
||||
}
|
||||
this.localStorage().getItem(this.name, cb.bind(this));
|
||||
};
|
||||
|
|
Загрузка…
Ссылка в новой задаче