diff --git a/background/background.html b/background/background.html index 15de5a0..44ae262 100644 --- a/background/background.html +++ b/background/background.html @@ -2,8 +2,9 @@ - + + @@ -14,11 +15,13 @@ - + + + diff --git a/background/linked_site.js b/background/linked_site.js index 16e4a34..9fd550c 100644 --- a/background/linked_site.js +++ b/background/linked_site.js @@ -1,4 +1,4 @@ -var LinkedSite = function(Backbone) { +var LinkedSite = function(Backbone, _) { // LinkedSite constructor // data is: diff --git a/background/linked_site_collection.js b/background/linked_site_collection.js index 7cb1a5b..53a8a75 100644 --- a/background/linked_site_collection.js +++ b/background/linked_site_collection.js @@ -1,13 +1,24 @@ -var LinkedSiteCollection = function(Backbone, LinkedSite) { +var LinkedSiteCollection = function(Backbone, _, LinkedSite) { var LinkedSiteCollection = Backbone.Collection.extend({ model: LinkedSite, - initializeFromRealmLoginMap: function(logins) { - var realms = _.keys(logins); - realms.forEach((function(realm) { - this.add(linkedSites[realm]); - }).bind(this)); - }); + + parse: function(resp) { + return _.flatten(_.values(resp), true); + }, + + toJSON: function(options) { + var result = {}; + this.each(function(model) { + var realm = model.get("realm"); + if (!result[realm]) { + result[realm] = []; + } + result[realm].push(model.toJSON(options)); + }); + return result; + } + }); return LinkedSiteCollection; diff --git a/background/local_storage.js b/background/local_storage.js new file mode 100644 index 0000000..3eb3534 --- /dev/null +++ b/background/local_storage.js @@ -0,0 +1,263 @@ +/* +* storage.js +* +* +* Handles persisting user data via localStorage. +* +*/ + +// This handles the low-level localStorage +// TODO: handle errors +var LocalStorage = function() { + return { + getItem: function(key, callback) { + chrome.storage.local.get(key, function(storageObj) { + callback(storageObj[key]); + }); + }, + setItem: function(key, data, callback) { + var updatedObj = {}; + updatedObj[key] = data; + chrome.storage.local.set(updatedObj, callback); + }, + removeItem: function(key, callback) { + chrome.storage.local.remove(key, callback); + } + } +}; + +// 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) { + // Add header + var retVal = "hostname,username,password\n"; + fetchStoredData(function(userData) { + for (var item in _.keys(userData.logins)) { + for (var login in userData.logins[item]) { + retVal += login.hostname + ',' + login.username + + ',' + login.password + '\n'; + } + } + callback(retVal); + }); +} + +// Dump localStorage to CSV file, for debugging purposes. +function downloadExportDataFile() { + // Get entire content of localStorage + // NB: This contains all of the user's passwords in plaintext, as well as + // their PIN and not-so-useful flags like did_first_run. + getLoginsCSV(function(loginsCSV) { + // Turn storageObj into a blob + var blob = new window.Blob([loginsCSV], {type: 'text/csv'}); + + // Creates a link that opens the blob on the background page, + // and then clicks it. Cribbed from: + // http://stackoverflow.com/questions/4845215/making-a-chrome-extension-download-a-file + var a = document.createElement('a'); + a.href = window.URL.createObjectURL(blob); + a.download = 'passwords_dump.csv'; + a.style.display = 'none'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + delete a;// we don't need this anymore + }); +} diff --git a/background/main.js b/background/main.js index ae0fa3b..baf6bb5 100644 --- a/background/main.js +++ b/background/main.js @@ -6,7 +6,7 @@ * */ -initGombot(); +//initGombot(); var Gombot = {}; Gombot.Messaging = ChromeMessaging(); @@ -16,18 +16,38 @@ Gombot.Realms = Realms(Gombot.SiteConfigs, Gombot.TldService); Gombot.CapturedCredentialStorage = CapturedCredentialStorage(Gombot.Realms); Gombot.CommandHandler = CommandHandler(Gombot.Messaging, Gombot.CapturedCredentialStorage, Gombot.Realms); -Gombot.Storage = Storage(); -Gombot.User = User(Gombot.Storage); +Gombot.LocalStorage = LocalStorage(); +Gombot.Storage = Storage(Backbone, _, Gombot.LocalStorage); +Gombot.LinkedSite = LinkedSite(Backbone, _); +Gombot.LinkedSiteCollection = LinkedSiteCollection(Backbone, _, Gombot.LinkedSite); +Gombot.User = User(Backbone, _, Gombot.LinkedSiteCollection); + +//Gombot.LocalStorage = LocalSync(Backbone, _, Gombot.Storage); + + +var usersStore = new Gombot.Storage("users", function() { + Gombot.UserCollection = UserCollection(Backbone, _, Gombot.User, usersStore); + initGombot(); +}); + + +// var Users = new Gombot.UserCollection(); +// var currentUser; function initGombot() { + var users = new Gombot.UserCollection(); + users.fetch(); + // Users.fetch({ success: function(collection, response, options) { + // currentUser = collection + // }}); // Load blacklisted sites from localStorage // loadNeverSaveOnSites(); // Load PIN lock state from localStorage // loadLoginsLock(); - if (!User.firstRun.wasCompleted()) { - startFirstRunFlow(); - } + // if (!User.firstRun.wasCompleted()) { + // startFirstRunFlow(); + // } } // diff --git a/background/storage.js b/background/storage.js deleted file mode 100644 index 8e358e7..0000000 --- a/background/storage.js +++ /dev/null @@ -1,259 +0,0 @@ -/* -* storage.js -* -* -* Handles persisting user data via localStorage. -* -*/ - -// This handles the low-level localStorage -var Storage = function() { - return { - get: function(key, callback) { - chrome.storage.local.get(key, function(storageObj) { - callback(storageObj[key]); - }); - }, - set: function(key, data, callback) { - var updatedObj = {}; - updatedObj[key] = data; - chrome.storage.local.set(updatedObj, callback); - } - } -}; - -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 - }; -})(LLStorage); - -// // 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) { - // Add header - var retVal = "hostname,username,password\n"; - fetchStoredData(function(userData) { - for (var item in _.keys(userData.logins)) { - for (var login in userData.logins[item]) { - retVal += login.hostname + ',' + login.username - + ',' + login.password + '\n'; - } - } - callback(retVal); - }); -} - -// Dump localStorage to CSV file, for debugging purposes. -function downloadExportDataFile() { - // Get entire content of localStorage - // NB: This contains all of the user's passwords in plaintext, as well as - // their PIN and not-so-useful flags like did_first_run. - getLoginsCSV(function(loginsCSV) { - // Turn storageObj into a blob - var blob = new window.Blob([loginsCSV], {type: 'text/csv'}); - - // Creates a link that opens the blob on the background page, - // and then clicks it. Cribbed from: - // http://stackoverflow.com/questions/4845215/making-a-chrome-extension-download-a-file - var a = document.createElement('a'); - a.href = window.URL.createObjectURL(blob); - a.download = 'passwords_dump.csv'; - a.style.display = 'none'; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - delete a;// we don't need this anymore - }); -} diff --git a/background/user.js b/background/user.js index 2b5dcb0..16d3755 100644 --- a/background/user.js +++ b/background/user.js @@ -27,19 +27,20 @@ var User = function(Backbone, _, LinkedSiteCollection) { var User = Backbone.Model.extend({ defaults: { version: USER_DATA_VERSIONS[USER_DATA_VERSIONS.length-1], - pin: null + pin: null, + linkedSites: new LinkedSiteCollection() }, - initialize: function(args) { - var logins = args.logins || []; - delete args.logins; - this.set(args); - this.linkedSites = new LinkedSiteCollection(); - this.linkedSites.initalizeFromLoginMap(logins); + parse: function(resp) { + resp.linkedSites = new LinkedSiteCollection(resp.logins, { parse: true }); + delete resp.logins; + return resp; }, - toJSON: function() { - + toJSON: function(options) { + var result = Backbone.Model.prototype.toJSON.apply(this, arguments); + delete result.linkedSites; + return _.extend(result, { logins: this.get("linkedSites").toJSON(options) }); } }, diff --git a/background/user_collection.js b/background/user_collection.js new file mode 100644 index 0000000..eea99f2 --- /dev/null +++ b/background/user_collection.js @@ -0,0 +1,9 @@ +var UserCollection = function(Backbone, _, User, LocalStorage) { + + var UserCollection = Backbone.Collection.extend({ + model: User, + localStorage: LocalStorage + }); + + return UserCollection; +}; \ No newline at end of file diff --git a/lib/backbone.localStorage.js b/lib/backbone.localStorage.js new file mode 100644 index 0000000..76fea6d --- /dev/null +++ b/lib/backbone.localStorage.js @@ -0,0 +1,173 @@ +/** + * Backbone localStorage Adapter + * + * https://github.com/jeromegn/Backbone.localStorage + */ +var Storage = function(Backbone, _, Storage) { +return (function (root, factory) { + if (typeof define === "function" && define.amd) { + // AMD. Register as an anonymous module. + define(["underscore","backbone"], function(_, Backbone) { + // Use global variables if the locals is undefined. + return factory(_ || root._, Backbone || root.Backbone); + }); + } else { + // RequireJS isn't being used. Assume underscore and backbone is loaded in