This commit is contained in:
Chris Karlof 2013-01-11 17:52:34 -08:00
Родитель d5993a17ca
Коммит 2db5f5515d
10 изменённых файлов: 506 добавлений и 285 удалений

Просмотреть файл

@ -2,8 +2,9 @@
<script src="../lib/jquery.js"></script>
<script src="../lib/jsuri.js"></script>
<script src="../lib/tldjs.js"></script>
<script src="../lib/backbone-min.js"></script>
<script src="../lib/underscore.js"></script>
<script src="../lib/backbone-min.js"></script>
<script src="../lib/backbone.localStorage.js"></script>
<script src="../infobar/manager.js"></script>
<script src="util.js"></script>
<script src="tld_service.js"></script>
@ -14,11 +15,13 @@
<script src="first_run.js"></script>
<script src="chrome_messaging.js"></script>
<script src="command_handler.js"></script>
<script src="storage.js"></script>
<script src="local_storage.js"></script>
<script src="captured_credential_storage.js"></script>
<script src="site_configs.js"></script>
<script src="linked_site.js"></script>
<script src="linked_site_collection.js"></script>
<script src="user.js"></script>
<script src="user_collection.js"></script>
<script src="main.js"></script>
<body>
<!-- Invisible <textarea> used to copy data onto the clipboard -->

Просмотреть файл

@ -1,4 +1,4 @@
var LinkedSite = function(Backbone) {
var LinkedSite = function(Backbone, _) {
// LinkedSite constructor
// data is:

Просмотреть файл

@ -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;

263
background/local_storage.js Normal file
Просмотреть файл

@ -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
});
}

Просмотреть файл

@ -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();
// }
}
//

Просмотреть файл

@ -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
});
}

Просмотреть файл

@ -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) });
}
},

Просмотреть файл

@ -0,0 +1,9 @@
var UserCollection = function(Backbone, _, User, LocalStorage) {
var UserCollection = Backbone.Collection.extend({
model: User,
localStorage: LocalStorage
});
return UserCollection;
};

Просмотреть файл

@ -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 <script> tags
return factory(_, Backbone);
}
}(this, function(_, Backbone) {
// A simple module to replace `Backbone.sync` with *localStorage*-based
// persistence. Models are given GUIDS, and saved into a JSON object. Simple
// as that.
// Hold reference to Underscore.js and Backbone.js in the closure in order
// to make things work even if they are removed from the global namespace
// Generate four random hex digits.
function S4() {
return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
};
// Generate a pseudo-GUID by concatenating random hexadecimal.
function guid() {
return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
};
// Our Store is represented by a single JS object in *localStorage*. Create it
// with a meaningful name, like the name you'd give a table.
// window.Store is deprectated, use Backbone.LocalStorage instead
Backbone.LocalStorage = window.Store = function(name, callback) {
this.name = name;
var cb = function(store) {
this.records = (store && store.split(",")) || [];
if (callback) callback();
}
this.localStorage().getItem(this.name, cb.bind(this));
};
_.extend(Backbone.LocalStorage.prototype, {
// Save the current state of the **Store** to *localStorage*.
save: function(callback) {
this.localStorage().setItem(this.name, this.records.join(","), callback);
},
// Add a model, giving it a (hopefully)-unique GUID, if it doesn't already
// have an id of it's own.
create: function(model, callback) {
if (!model.id) {
model.id = guid();
model.set(model.idAttribute, model.id);
}
var cb = _.after(2, function() {
callback(model.toJSON());
});
this.localStorage().setItem(this.name+"-"+model.id, JSON.stringify(model), cb);
this.records.push(model.id.toString());
this.save(cb);
return;
},
// Update a model by replacing its copy in `this.data`.
update: function(model, callback) {
var cb = _.after(2, function() {
callback(model.toJSON());
});
this.localStorage().setItem(this.name+"-"+model.id, JSON.stringify(model), cb);
if (!_.include(this.records, model.id.toString())) this.records.push(model.id.toString()); this.save(cb);
return;
},
// Retrieve a model from `this.data` by id.
find: function(model, callback) {
this.localStorage().getItem(this.name+"-"+model.id, function(resp) { callback(this.jsonData(resp)); });
return;
},
// Return the array of all models currently in storage.
findAll: function(callback) {
var result = [];
var allFetched = _.after(this.records.length, function() { console.log(result); callback(_(result).chain().compact().value()); });
var itemFetched = function(resp) {
console.log("READ", resp);
result.push(this.jsonData(resp));
allFetched();
}
_(this.records)
.map(function(id){return this.localStorage().getItem(this.name+"-"+id, itemFetched.bind(this));}, this);
},
// Delete a model from `this.data`, returning it.
destroy: function(model, callback) {
var cb = _.after(2, function() {
callback(model);
});
this.localStorage().removeItem(this.name+"-"+model.id, cb);
this.records = _.reject(this.records, function(record_id){return record_id == model.id.toString();});
this.save(cb);
return;
},
localStorage: function() {
return Storage;
},
// fix for "illegal access" error on Android when JSON.parse is passed null
jsonData: function (data) {
return data && JSON.parse(data);
}
});
// localSync delegate to the model or collection's
// *localStorage* property, which should be an instance of `Store`.
// window.Store.sync and Backbone.localSync is deprectated, use Backbone.LocalStorage.sync instead
Backbone.LocalStorage.sync = window.Store.sync = Backbone.localSync = function(method, model, options) {
var store = model.localStorage || model.collection.localStorage;
var resp, syncDfd = $.Deferred && $.Deferred(); //If $ is having Deferred - use it.
var callback = function(resp) {
if (resp) {
if (options && options.success) options.success(resp);
if (syncDfd) syncDfd.resolve();
} else {
if (options && options.error) options.error("Record not found");
if (syncDfd) syncDfd.reject();
}
// add compatibility with $.ajax
// always execute callback for success and error
if (options && options.complete) options.complete(resp);
};
switch (method) {
case "read": model.id != undefined ? store.find(model, callback) : store.findAll(callback); break;
case "create": store.create(model, callback); break;
case "update": store.update(model, callback); break;
case "delete": store.destroy(model, callback); break;
}
return syncDfd && syncDfd.promise();
};
Backbone.ajaxSync = Backbone.sync;
Backbone.getSyncMethod = function(model) {
if(model.localStorage || (model.collection && model.collection.localStorage))
{
return Backbone.localSync;
}
return Backbone.ajaxSync;
};
// Override 'Backbone.sync' to default to localSync,
// the original 'Backbone.sync' is still available in 'Backbone.ajaxSync'
Backbone.sync = function(method, model, options) {
return Backbone.getSyncMethod(model).apply(this, [method, model, options]);
};
return Backbone.LocalStorage;
}));
};

2
server

@ -1 +1 @@
Subproject commit 1b7aa4ce43356f268a5866ed9adecbcfcdfaab4d
Subproject commit cee80f7e61d6caf31373c495377df78a3508372c