From cf78626e9a98206e9886908ffa6731ff02e11272 Mon Sep 17 00:00:00 2001 From: Chris Karlof Date: Mon, 14 Jan 2013 22:09:40 -0800 Subject: [PATCH] hopefully support for realms, but some problems on citi --- TODO | 9 +- background/captured_credential_storage.js | 16 +- background/command_handler.js | 40 +++-- background/infobar_hooks.js | 9 +- background/local_storage.js | 197 ---------------------- background/main.js | 8 +- background/models/login_credential.js | 5 +- background/realms.js | 55 ++++-- background/site_configs.js | 2 +- background/site_configs.yml | 14 +- content_scripts/main.js | 2 +- lib/backbone.localStorage.js | 2 +- 12 files changed, 102 insertions(+), 257 deletions(-) diff --git a/TODO b/TODO index 27d7838..f0ce7a7 100644 --- a/TODO +++ b/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 diff --git a/background/captured_credential_storage.js b/background/captured_credential_storage.js index a55f5e1..9b38cf8 100644 --- a/background/captured_credential_storage.js +++ b/background/captured_credential_storage.js @@ -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 { : } key/value pairs. The is a - // descriptive name for the . In the common case where there is a - // single username, the 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] } diff --git a/background/command_handler.js b/background/command_handler.js index 487ec27..ef5bac4 100644 --- a/background/command_handler.js +++ b/background/command_handler.js @@ -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)] || {}); } diff --git a/background/infobar_hooks.js b/background/infobar_hooks.js index 47651f2..f5a681b 100644 --- a/background/infobar_hooks.js +++ b/background/infobar_hooks.js @@ -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); } diff --git a/background/local_storage.js b/background/local_storage.js index 5a6c76a..099ff50 100644 --- a/background/local_storage.js +++ b/background/local_storage.js @@ -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) { diff --git a/background/main.js b/background/main.js index b6cbdb6..3451f19 100644 --- a/background/main.js +++ b/background/main.js @@ -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() { diff --git a/background/models/login_credential.js b/background/models/login_credential.js index ccb8cbd..455098f 100644 --- a/background/models/login_credential.js +++ b/background/models/login_credential.js @@ -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": , + // "url": "https://www.mozilla.com/login", // "password": "grëën", // "pinLocked": false, // "username": "gömbottest", diff --git a/background/realms.js b/background/realms.js index eb5a2e3..4edd8df 100644 --- a/background/realms.js +++ b/background/realms.js @@ -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 origins - domains: ["citibank.com", "accountonline.com", "citicards.com"] - title: "Citi" - att.com: - fakePasswordFill: "#password" \ No newline at end of file + fakePasswordFill: "#password" + +# REALMS + +realms: + - origins: ["https://online.citibank.com", "https://www.accountonline.com", "https://creditcards.citi.com" ] + title: "Citi" \ No newline at end of file diff --git a/content_scripts/main.js b/content_scripts/main.js index 9cceba1..4bcd314 100644 --- a/content_scripts/main.js +++ b/content_scripts/main.js @@ -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 }, diff --git a/lib/backbone.localStorage.js b/lib/backbone.localStorage.js index 025b816..46474eb 100644 --- a/lib/backbone.localStorage.js +++ b/lib/backbone.localStorage.js @@ -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)); };