summaryrefslogtreecommitdiffstats
path: root/toolkit/components/passwordmgr/storage-mozStorage.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/passwordmgr/storage-mozStorage.js')
-rw-r--r--toolkit/components/passwordmgr/storage-mozStorage.js1262
1 files changed, 0 insertions, 1262 deletions
diff --git a/toolkit/components/passwordmgr/storage-mozStorage.js b/toolkit/components/passwordmgr/storage-mozStorage.js
deleted file mode 100644
index 9da244f7a..000000000
--- a/toolkit/components/passwordmgr/storage-mozStorage.js
+++ /dev/null
@@ -1,1262 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
-const DB_VERSION = 6; // The database schema version
-const PERMISSION_SAVE_LOGINS = "login-saving";
-
-Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
-Components.utils.import("resource://gre/modules/Services.jsm");
-Components.utils.import("resource://gre/modules/Promise.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
- "resource://gre/modules/LoginHelper.jsm");
-
-/**
- * Object that manages a database transaction properly so consumers don't have
- * to worry about it throwing.
- *
- * @param aDatabase
- * The mozIStorageConnection to start a transaction on.
- */
-function Transaction(aDatabase) {
- this._db = aDatabase;
-
- this._hasTransaction = false;
- try {
- this._db.beginTransaction();
- this._hasTransaction = true;
- } catch (e) { /* om nom nom exceptions */ }
-}
-
-Transaction.prototype = {
- commit : function() {
- if (this._hasTransaction)
- this._db.commitTransaction();
- },
-
- rollback : function() {
- if (this._hasTransaction)
- this._db.rollbackTransaction();
- },
-};
-
-
-function LoginManagerStorage_mozStorage() { }
-
-LoginManagerStorage_mozStorage.prototype = {
-
- classID : Components.ID("{8c2023b9-175c-477e-9761-44ae7b549756}"),
- QueryInterface : XPCOMUtils.generateQI([Ci.nsILoginManagerStorage,
- Ci.nsIInterfaceRequestor]),
- getInterface : function(aIID) {
- if (aIID.equals(Ci.nsIVariant)) {
- // Allows unwrapping the JavaScript object for regression tests.
- return this;
- }
-
- if (aIID.equals(Ci.mozIStorageConnection)) {
- return this._dbConnection;
- }
-
- throw new Components.Exception("Interface not available", Cr.NS_ERROR_NO_INTERFACE);
- },
-
- __crypto : null, // nsILoginManagerCrypto service
- get _crypto() {
- if (!this.__crypto)
- this.__crypto = Cc["@mozilla.org/login-manager/crypto/SDR;1"].
- getService(Ci.nsILoginManagerCrypto);
- return this.__crypto;
- },
-
- __profileDir: null, // nsIFile for the user's profile dir
- get _profileDir() {
- if (!this.__profileDir)
- this.__profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
- return this.__profileDir;
- },
-
- __storageService: null, // Storage service for using mozStorage
- get _storageService() {
- if (!this.__storageService)
- this.__storageService = Cc["@mozilla.org/storage/service;1"].
- getService(Ci.mozIStorageService);
- return this.__storageService;
- },
-
- __uuidService: null,
- get _uuidService() {
- if (!this.__uuidService)
- this.__uuidService = Cc["@mozilla.org/uuid-generator;1"].
- getService(Ci.nsIUUIDGenerator);
- return this.__uuidService;
- },
-
-
- // The current database schema.
- _dbSchema: {
- tables: {
- moz_logins: "id INTEGER PRIMARY KEY," +
- "hostname TEXT NOT NULL," +
- "httpRealm TEXT," +
- "formSubmitURL TEXT," +
- "usernameField TEXT NOT NULL," +
- "passwordField TEXT NOT NULL," +
- "encryptedUsername TEXT NOT NULL," +
- "encryptedPassword TEXT NOT NULL," +
- "guid TEXT," +
- "encType INTEGER," +
- "timeCreated INTEGER," +
- "timeLastUsed INTEGER," +
- "timePasswordChanged INTEGER," +
- "timesUsed INTEGER",
- // Changes must be reflected in this._dbAreExpectedColumnsPresent(),
- // this._searchLogins(), and this.modifyLogin().
-
- moz_disabledHosts: "id INTEGER PRIMARY KEY," +
- "hostname TEXT UNIQUE ON CONFLICT REPLACE",
-
- moz_deleted_logins: "id INTEGER PRIMARY KEY," +
- "guid TEXT," +
- "timeDeleted INTEGER",
- },
- indices: {
- moz_logins_hostname_index: {
- table: "moz_logins",
- columns: ["hostname"]
- },
- moz_logins_hostname_formSubmitURL_index: {
- table: "moz_logins",
- columns: ["hostname", "formSubmitURL"]
- },
- moz_logins_hostname_httpRealm_index: {
- table: "moz_logins",
- columns: ["hostname", "httpRealm"]
- },
- moz_logins_guid_index: {
- table: "moz_logins",
- columns: ["guid"]
- },
- moz_logins_encType_index: {
- table: "moz_logins",
- columns: ["encType"]
- }
- }
- },
- _dbConnection : null, // The database connection
- _dbStmts : null, // Database statements for memoization
-
- _signonsFile : null, // nsIFile for "signons.sqlite"
-
-
- /*
- * Internal method used by regression tests only. It overrides the default
- * database location.
- */
- initWithFile : function(aDBFile) {
- if (aDBFile)
- this._signonsFile = aDBFile;
-
- this.initialize();
- },
-
-
- initialize : function () {
- this._dbStmts = {};
-
- let isFirstRun;
- try {
- // Force initialization of the crypto module.
- // See bug 717490 comment 17.
- this._crypto;
-
- // If initWithFile is calling us, _signonsFile may already be set.
- if (!this._signonsFile) {
- // Initialize signons.sqlite
- this._signonsFile = this._profileDir.clone();
- this._signonsFile.append("signons.sqlite");
- }
- this.log("Opening database at " + this._signonsFile.path);
-
- // Initialize the database (create, migrate as necessary)
- isFirstRun = this._dbInit();
-
- this._initialized = true;
-
- return Promise.resolve();
- } catch (e) {
- this.log("Initialization failed: " + e);
- // If the import fails on first run, we want to delete the db
- if (isFirstRun && e == "Import failed")
- this._dbCleanup(false);
- throw new Error("Initialization failed");
- }
- },
-
-
- /**
- * Internal method used by regression tests only. It is called before
- * replacing this storage module with a new instance.
- */
- terminate : function () {
- return Promise.resolve();
- },
-
-
- addLogin : function (login) {
- // Throws if there are bogus values.
- LoginHelper.checkLoginValues(login);
-
- let [encUsername, encPassword, encType] = this._encryptLogin(login);
-
- // Clone the login, so we don't modify the caller's object.
- let loginClone = login.clone();
-
- // Initialize the nsILoginMetaInfo fields, unless the caller gave us values
- loginClone.QueryInterface(Ci.nsILoginMetaInfo);
- if (loginClone.guid) {
- if (!this._isGuidUnique(loginClone.guid))
- throw new Error("specified GUID already exists");
- } else {
- loginClone.guid = this._uuidService.generateUUID().toString();
- }
-
- // Set timestamps
- let currentTime = Date.now();
- if (!loginClone.timeCreated)
- loginClone.timeCreated = currentTime;
- if (!loginClone.timeLastUsed)
- loginClone.timeLastUsed = currentTime;
- if (!loginClone.timePasswordChanged)
- loginClone.timePasswordChanged = currentTime;
- if (!loginClone.timesUsed)
- loginClone.timesUsed = 1;
-
- let query =
- "INSERT INTO moz_logins " +
- "(hostname, httpRealm, formSubmitURL, usernameField, " +
- "passwordField, encryptedUsername, encryptedPassword, " +
- "guid, encType, timeCreated, timeLastUsed, timePasswordChanged, " +
- "timesUsed) " +
- "VALUES (:hostname, :httpRealm, :formSubmitURL, :usernameField, " +
- ":passwordField, :encryptedUsername, :encryptedPassword, " +
- ":guid, :encType, :timeCreated, :timeLastUsed, " +
- ":timePasswordChanged, :timesUsed)";
-
- let params = {
- hostname: loginClone.hostname,
- httpRealm: loginClone.httpRealm,
- formSubmitURL: loginClone.formSubmitURL,
- usernameField: loginClone.usernameField,
- passwordField: loginClone.passwordField,
- encryptedUsername: encUsername,
- encryptedPassword: encPassword,
- guid: loginClone.guid,
- encType: encType,
- timeCreated: loginClone.timeCreated,
- timeLastUsed: loginClone.timeLastUsed,
- timePasswordChanged: loginClone.timePasswordChanged,
- timesUsed: loginClone.timesUsed
- };
-
- let stmt;
- try {
- stmt = this._dbCreateStatement(query, params);
- stmt.execute();
- } catch (e) {
- this.log("addLogin failed: " + e.name + " : " + e.message);
- throw new Error("Couldn't write to database, login not added.");
- } finally {
- if (stmt) {
- stmt.reset();
- }
- }
-
- // Send a notification that a login was added.
- LoginHelper.notifyStorageChanged("addLogin", loginClone);
- return loginClone;
- },
-
-
- removeLogin : function (login) {
- let [idToDelete, storedLogin] = this._getIdForLogin(login);
- if (!idToDelete)
- throw new Error("No matching logins");
-
- // Execute the statement & remove from DB
- let query = "DELETE FROM moz_logins WHERE id = :id";
- let params = { id: idToDelete };
- let stmt;
- let transaction = new Transaction(this._dbConnection);
- try {
- stmt = this._dbCreateStatement(query, params);
- stmt.execute();
- this.storeDeletedLogin(storedLogin);
- transaction.commit();
- } catch (e) {
- this.log("_removeLogin failed: " + e.name + " : " + e.message);
- transaction.rollback();
- throw new Error("Couldn't write to database, login not removed.");
- } finally {
- if (stmt) {
- stmt.reset();
- }
- }
- LoginHelper.notifyStorageChanged("removeLogin", storedLogin);
- },
-
- modifyLogin : function (oldLogin, newLoginData) {
- let [idToModify, oldStoredLogin] = this._getIdForLogin(oldLogin);
- if (!idToModify)
- throw new Error("No matching logins");
-
- let newLogin = LoginHelper.buildModifiedLogin(oldStoredLogin, newLoginData);
-
- // Check if the new GUID is duplicate.
- if (newLogin.guid != oldStoredLogin.guid &&
- !this._isGuidUnique(newLogin.guid)) {
- throw new Error("specified GUID already exists");
- }
-
- // Look for an existing entry in case key properties changed.
- if (!newLogin.matches(oldLogin, true)) {
- let logins = this.findLogins({}, newLogin.hostname,
- newLogin.formSubmitURL,
- newLogin.httpRealm);
-
- if (logins.some(login => newLogin.matches(login, true)))
- throw new Error("This login already exists.");
- }
-
- // Get the encrypted value of the username and password.
- let [encUsername, encPassword, encType] = this._encryptLogin(newLogin);
-
- let query =
- "UPDATE moz_logins " +
- "SET hostname = :hostname, " +
- "httpRealm = :httpRealm, " +
- "formSubmitURL = :formSubmitURL, " +
- "usernameField = :usernameField, " +
- "passwordField = :passwordField, " +
- "encryptedUsername = :encryptedUsername, " +
- "encryptedPassword = :encryptedPassword, " +
- "guid = :guid, " +
- "encType = :encType, " +
- "timeCreated = :timeCreated, " +
- "timeLastUsed = :timeLastUsed, " +
- "timePasswordChanged = :timePasswordChanged, " +
- "timesUsed = :timesUsed " +
- "WHERE id = :id";
-
- let params = {
- id: idToModify,
- hostname: newLogin.hostname,
- httpRealm: newLogin.httpRealm,
- formSubmitURL: newLogin.formSubmitURL,
- usernameField: newLogin.usernameField,
- passwordField: newLogin.passwordField,
- encryptedUsername: encUsername,
- encryptedPassword: encPassword,
- guid: newLogin.guid,
- encType: encType,
- timeCreated: newLogin.timeCreated,
- timeLastUsed: newLogin.timeLastUsed,
- timePasswordChanged: newLogin.timePasswordChanged,
- timesUsed: newLogin.timesUsed
- };
-
- let stmt;
- try {
- stmt = this._dbCreateStatement(query, params);
- stmt.execute();
- } catch (e) {
- this.log("modifyLogin failed: " + e.name + " : " + e.message);
- throw new Error("Couldn't write to database, login not modified.");
- } finally {
- if (stmt) {
- stmt.reset();
- }
- }
-
- LoginHelper.notifyStorageChanged("modifyLogin", [oldStoredLogin, newLogin]);
- },
-
-
- /**
- * Returns an array of nsILoginInfo.
- */
- getAllLogins : function (count) {
- let [logins, ids] = this._searchLogins({});
-
- // decrypt entries for caller.
- logins = this._decryptLogins(logins);
-
- this.log("_getAllLogins: returning " + logins.length + " logins.");
- if (count)
- count.value = logins.length; // needed for XPCOM
- return logins;
- },
-
-
- /**
- * Public wrapper around _searchLogins to convert the nsIPropertyBag to a
- * JavaScript object and decrypt the results.
- *
- * @return {nsILoginInfo[]} which are decrypted.
- */
- searchLogins : function(count, matchData) {
- let realMatchData = {};
- let options = {};
- // Convert nsIPropertyBag to normal JS object
- let propEnum = matchData.enumerator;
- while (propEnum.hasMoreElements()) {
- let prop = propEnum.getNext().QueryInterface(Ci.nsIProperty);
- switch (prop.name) {
- // Some property names aren't field names but are special options to affect the search.
- case "schemeUpgrades": {
- options[prop.name] = prop.value;
- break;
- }
- default: {
- realMatchData[prop.name] = prop.value;
- break;
- }
- }
- }
-
- let [logins, ids] = this._searchLogins(realMatchData, options);
-
- // Decrypt entries found for the caller.
- logins = this._decryptLogins(logins);
-
- count.value = logins.length; // needed for XPCOM
- return logins;
- },
-
-
- /**
- * Private method to perform arbitrary searches on any field. Decryption is
- * left to the caller.
- *
- * Returns [logins, ids] for logins that match the arguments, where logins
- * is an array of encrypted nsLoginInfo and ids is an array of associated
- * ids in the database.
- */
- _searchLogins : function (matchData, aOptions = {
- schemeUpgrades: false,
- }) {
- let conditions = [], params = {};
-
- for (let field in matchData) {
- let value = matchData[field];
- let condition = "";
- switch (field) {
- case "formSubmitURL":
- if (value != null) {
- // Historical compatibility requires this special case
- condition = "formSubmitURL = '' OR ";
- }
- // Fall through
- case "hostname":
- if (value != null) {
- condition += `${field} = :${field}`;
- params[field] = value;
- let valueURI;
- try {
- if (aOptions.schemeUpgrades && (valueURI = Services.io.newURI(value, null, null)) &&
- valueURI.scheme == "https") {
- condition += ` OR ${field} = :http${field}`;
- params["http" + field] = "http://" + valueURI.hostPort;
- }
- } catch (ex) {
- // newURI will throw for some values
- // but those URLs wouldn't support upgrades anyways.
- }
- break;
- }
- // Fall through
- // Normal cases.
- case "httpRealm":
- case "id":
- case "usernameField":
- case "passwordField":
- case "encryptedUsername":
- case "encryptedPassword":
- case "guid":
- case "encType":
- case "timeCreated":
- case "timeLastUsed":
- case "timePasswordChanged":
- case "timesUsed":
- if (value == null) {
- condition = field + " isnull";
- } else {
- condition = field + " = :" + field;
- params[field] = value;
- }
- break;
- // Fail if caller requests an unknown property.
- default:
- throw new Error("Unexpected field: " + field);
- }
- if (condition) {
- conditions.push(condition);
- }
- }
-
- // Build query
- let query = "SELECT * FROM moz_logins";
- if (conditions.length) {
- conditions = conditions.map(c => "(" + c + ")");
- query += " WHERE " + conditions.join(" AND ");
- }
-
- let stmt;
- let logins = [], ids = [];
- try {
- stmt = this._dbCreateStatement(query, params);
- // We can't execute as usual here, since we're iterating over rows
- while (stmt.executeStep()) {
- // Create the new nsLoginInfo object, push to array
- let login = Cc["@mozilla.org/login-manager/loginInfo;1"].
- createInstance(Ci.nsILoginInfo);
- login.init(stmt.row.hostname, stmt.row.formSubmitURL,
- stmt.row.httpRealm, stmt.row.encryptedUsername,
- stmt.row.encryptedPassword, stmt.row.usernameField,
- stmt.row.passwordField);
- // set nsILoginMetaInfo values
- login.QueryInterface(Ci.nsILoginMetaInfo);
- login.guid = stmt.row.guid;
- login.timeCreated = stmt.row.timeCreated;
- login.timeLastUsed = stmt.row.timeLastUsed;
- login.timePasswordChanged = stmt.row.timePasswordChanged;
- login.timesUsed = stmt.row.timesUsed;
- logins.push(login);
- ids.push(stmt.row.id);
- }
- } catch (e) {
- this.log("_searchLogins failed: " + e.name + " : " + e.message);
- } finally {
- if (stmt) {
- stmt.reset();
- }
- }
-
- this.log("_searchLogins: returning " + logins.length + " logins");
- return [logins, ids];
- },
-
- /**
- * Moves a login to the deleted logins table
- */
- storeDeletedLogin : function(aLogin) {
- let stmt = null;
- try {
- this.log("Storing " + aLogin.guid + " in deleted passwords\n");
- let query = "INSERT INTO moz_deleted_logins (guid, timeDeleted) VALUES (:guid, :timeDeleted)";
- let params = { guid: aLogin.guid,
- timeDeleted: Date.now() };
- let stmt = this._dbCreateStatement(query, params);
- stmt.execute();
- } catch (ex) {
- throw ex;
- } finally {
- if (stmt)
- stmt.reset();
- }
- },
-
-
- /**
- * Removes all logins from storage.
- */
- removeAllLogins : function () {
- this.log("Removing all logins");
- let query;
- let stmt;
- let transaction = new Transaction(this._dbConnection);
-
- // Disabled hosts kept, as one presumably doesn't want to erase those.
- // TODO: Add these items to the deleted items table once we've sorted
- // out the issues from bug 756701
- query = "DELETE FROM moz_logins";
- try {
- stmt = this._dbCreateStatement(query);
- stmt.execute();
- transaction.commit();
- } catch (e) {
- this.log("_removeAllLogins failed: " + e.name + " : " + e.message);
- transaction.rollback();
- throw new Error("Couldn't write to database");
- } finally {
- if (stmt) {
- stmt.reset();
- }
- }
-
- LoginHelper.notifyStorageChanged("removeAllLogins", null);
- },
-
-
- findLogins : function (count, hostname, formSubmitURL, httpRealm) {
- let loginData = {
- hostname: hostname,
- formSubmitURL: formSubmitURL,
- httpRealm: httpRealm
- };
- let matchData = { };
- for (let field of ["hostname", "formSubmitURL", "httpRealm"])
- if (loginData[field] != '')
- matchData[field] = loginData[field];
- let [logins, ids] = this._searchLogins(matchData);
-
- // Decrypt entries found for the caller.
- logins = this._decryptLogins(logins);
-
- this.log("_findLogins: returning " + logins.length + " logins");
- count.value = logins.length; // needed for XPCOM
- return logins;
- },
-
-
- countLogins : function (hostname, formSubmitURL, httpRealm) {
-
- let _countLoginsHelper = (hostname, formSubmitURL, httpRealm) => {
- // Do checks for null and empty strings, adjust conditions and params
- let [conditions, params] =
- this._buildConditionsAndParams(hostname, formSubmitURL, httpRealm);
-
- let query = "SELECT COUNT(1) AS numLogins FROM moz_logins";
- if (conditions.length) {
- conditions = conditions.map(c => "(" + c + ")");
- query += " WHERE " + conditions.join(" AND ");
- }
-
- let stmt, numLogins;
- try {
- stmt = this._dbCreateStatement(query, params);
- stmt.executeStep();
- numLogins = stmt.row.numLogins;
- } catch (e) {
- this.log("_countLogins failed: " + e.name + " : " + e.message);
- } finally {
- if (stmt) {
- stmt.reset();
- }
- }
- return numLogins;
- };
-
- let resultLogins = _countLoginsHelper(hostname, formSubmitURL, httpRealm);
- this.log("_countLogins: counted logins: " + resultLogins);
- return resultLogins;
- },
-
-
- get uiBusy() {
- return this._crypto.uiBusy;
- },
-
-
- get isLoggedIn() {
- return this._crypto.isLoggedIn;
- },
-
-
- /**
- * Returns an array with two items: [id, login]. If the login was not
- * found, both items will be null. The returned login contains the actual
- * stored login (useful for looking at the actual nsILoginMetaInfo values).
- */
- _getIdForLogin : function (login) {
- let matchData = { };
- for (let field of ["hostname", "formSubmitURL", "httpRealm"])
- if (login[field] != '')
- matchData[field] = login[field];
- let [logins, ids] = this._searchLogins(matchData);
-
- let id = null;
- let foundLogin = null;
-
- // The specified login isn't encrypted, so we need to ensure
- // the logins we're comparing with are decrypted. We decrypt one entry
- // at a time, lest _decryptLogins return fewer entries and screw up
- // indices between the two.
- for (let i = 0; i < logins.length; i++) {
- let [decryptedLogin] = this._decryptLogins([logins[i]]);
-
- if (!decryptedLogin || !decryptedLogin.equals(login))
- continue;
-
- // We've found a match, set id and break
- foundLogin = decryptedLogin;
- id = ids[i];
- break;
- }
-
- return [id, foundLogin];
- },
-
-
- /**
- * Adjusts the WHERE conditions and parameters for statements prior to the
- * statement being created. This fixes the cases where nulls are involved
- * and the empty string is supposed to be a wildcard match
- */
- _buildConditionsAndParams : function (hostname, formSubmitURL, httpRealm) {
- let conditions = [], params = {};
-
- if (hostname == null) {
- conditions.push("hostname isnull");
- } else if (hostname != '') {
- conditions.push("hostname = :hostname");
- params["hostname"] = hostname;
- }
-
- if (formSubmitURL == null) {
- conditions.push("formSubmitURL isnull");
- } else if (formSubmitURL != '') {
- conditions.push("formSubmitURL = :formSubmitURL OR formSubmitURL = ''");
- params["formSubmitURL"] = formSubmitURL;
- }
-
- if (httpRealm == null) {
- conditions.push("httpRealm isnull");
- } else if (httpRealm != '') {
- conditions.push("httpRealm = :httpRealm");
- params["httpRealm"] = httpRealm;
- }
-
- return [conditions, params];
- },
-
-
- /**
- * Checks to see if the specified GUID already exists.
- */
- _isGuidUnique : function (guid) {
- let query = "SELECT COUNT(1) AS numLogins FROM moz_logins WHERE guid = :guid";
- let params = { guid: guid };
-
- let stmt, numLogins;
- try {
- stmt = this._dbCreateStatement(query, params);
- stmt.executeStep();
- numLogins = stmt.row.numLogins;
- } catch (e) {
- this.log("_isGuidUnique failed: " + e.name + " : " + e.message);
- } finally {
- if (stmt) {
- stmt.reset();
- }
- }
-
- return (numLogins == 0);
- },
-
-
- /**
- * Returns the encrypted username, password, and encrypton type for the specified
- * login. Can throw if the user cancels a master password entry.
- */
- _encryptLogin : function (login) {
- let encUsername = this._crypto.encrypt(login.username);
- let encPassword = this._crypto.encrypt(login.password);
- let encType = this._crypto.defaultEncType;
-
- return [encUsername, encPassword, encType];
- },
-
-
- /**
- * Decrypts username and password fields in the provided array of
- * logins.
- *
- * The entries specified by the array will be decrypted, if possible.
- * An array of successfully decrypted logins will be returned. The return
- * value should be given to external callers (since still-encrypted
- * entries are useless), whereas internal callers generally don't want
- * to lose unencrypted entries (eg, because the user clicked Cancel
- * instead of entering their master password)
- */
- _decryptLogins : function (logins) {
- let result = [];
-
- for (let login of logins) {
- try {
- login.username = this._crypto.decrypt(login.username);
- login.password = this._crypto.decrypt(login.password);
- } catch (e) {
- // If decryption failed (corrupt entry?), just skip it.
- // Rethrow other errors (like canceling entry of a master pw)
- if (e.result == Cr.NS_ERROR_FAILURE)
- continue;
- throw e;
- }
- result.push(login);
- }
-
- return result;
- },
-
-
- // Database Creation & Access
-
- /**
- * Creates a statement, wraps it, and then does parameter replacement
- * Returns the wrapped statement for execution. Will use memoization
- * so that statements can be reused.
- */
- _dbCreateStatement : function (query, params) {
- let wrappedStmt = this._dbStmts[query];
- // Memoize the statements
- if (!wrappedStmt) {
- this.log("Creating new statement for query: " + query);
- wrappedStmt = this._dbConnection.createStatement(query);
- this._dbStmts[query] = wrappedStmt;
- }
- // Replace parameters, must be done 1 at a time
- if (params)
- for (let i in params)
- wrappedStmt.params[i] = params[i];
- return wrappedStmt;
- },
-
-
- /**
- * Attempts to initialize the database. This creates the file if it doesn't
- * exist, performs any migrations, etc. Return if this is the first run.
- */
- _dbInit : function () {
- this.log("Initializing Database");
- let isFirstRun = false;
- try {
- this._dbConnection = this._storageService.openDatabase(this._signonsFile);
- // Get the version of the schema in the file. It will be 0 if the
- // database has not been created yet.
- let version = this._dbConnection.schemaVersion;
- if (version == 0) {
- this._dbCreate();
- isFirstRun = true;
- } else if (version != DB_VERSION) {
- this._dbMigrate(version);
- }
- } catch (e) {
- if (e.result == Cr.NS_ERROR_FILE_CORRUPTED) {
- // Database is corrupted, so we backup the database, then throw
- // causing initialization to fail and a new db to be created next use
- this._dbCleanup(true);
- }
- throw e;
- }
-
- Services.obs.addObserver(this, "profile-before-change", false);
- return isFirstRun;
- },
-
- observe: function (subject, topic, data) {
- switch (topic) {
- case "profile-before-change":
- Services.obs.removeObserver(this, "profile-before-change");
- this._dbClose();
- break;
- }
- },
-
- _dbCreate: function () {
- this.log("Creating Database");
- this._dbCreateSchema();
- this._dbConnection.schemaVersion = DB_VERSION;
- },
-
-
- _dbCreateSchema : function () {
- this._dbCreateTables();
- this._dbCreateIndices();
- },
-
-
- _dbCreateTables : function () {
- this.log("Creating Tables");
- for (let name in this._dbSchema.tables)
- this._dbConnection.createTable(name, this._dbSchema.tables[name]);
- },
-
-
- _dbCreateIndices : function () {
- this.log("Creating Indices");
- for (let name in this._dbSchema.indices) {
- let index = this._dbSchema.indices[name];
- let statement = "CREATE INDEX IF NOT EXISTS " + name + " ON " + index.table +
- "(" + index.columns.join(", ") + ")";
- this._dbConnection.executeSimpleSQL(statement);
- }
- },
-
-
- _dbMigrate : function (oldVersion) {
- this.log("Attempting to migrate from version " + oldVersion);
-
- if (oldVersion > DB_VERSION) {
- this.log("Downgrading to version " + DB_VERSION);
- // User's DB is newer. Sanity check that our expected columns are
- // present, and if so mark the lower version and merrily continue
- // on. If the columns are borked, something is wrong so blow away
- // the DB and start from scratch. [Future incompatible upgrades
- // should swtich to a different table or file.]
-
- if (!this._dbAreExpectedColumnsPresent())
- throw Components.Exception("DB is missing expected columns",
- Cr.NS_ERROR_FILE_CORRUPTED);
-
- // Change the stored version to the current version. If the user
- // runs the newer code again, it will see the lower version number
- // and re-upgrade (to fixup any entries the old code added).
- this._dbConnection.schemaVersion = DB_VERSION;
- return;
- }
-
- // Upgrade to newer version...
-
- let transaction = new Transaction(this._dbConnection);
-
- try {
- for (let v = oldVersion + 1; v <= DB_VERSION; v++) {
- this.log("Upgrading to version " + v + "...");
- let migrateFunction = "_dbMigrateToVersion" + v;
- this[migrateFunction]();
- }
- } catch (e) {
- this.log("Migration failed: " + e);
- transaction.rollback();
- throw e;
- }
-
- this._dbConnection.schemaVersion = DB_VERSION;
- transaction.commit();
- this.log("DB migration completed.");
- },
-
-
- /**
- * Version 2 adds a GUID column. Existing logins are assigned a random GUID.
- */
- _dbMigrateToVersion2 : function () {
- // Check to see if GUID column already exists, add if needed
- let query;
- if (!this._dbColumnExists("guid")) {
- query = "ALTER TABLE moz_logins ADD COLUMN guid TEXT";
- this._dbConnection.executeSimpleSQL(query);
-
- query = "CREATE INDEX IF NOT EXISTS moz_logins_guid_index ON moz_logins (guid)";
- this._dbConnection.executeSimpleSQL(query);
- }
-
- // Get a list of IDs for existing logins
- let ids = [];
- query = "SELECT id FROM moz_logins WHERE guid isnull";
- let stmt;
- try {
- stmt = this._dbCreateStatement(query);
- while (stmt.executeStep())
- ids.push(stmt.row.id);
- } catch (e) {
- this.log("Failed getting IDs: " + e);
- throw e;
- } finally {
- if (stmt) {
- stmt.reset();
- }
- }
-
- // Generate a GUID for each login and update the DB.
- query = "UPDATE moz_logins SET guid = :guid WHERE id = :id";
- for (let id of ids) {
- let params = {
- id: id,
- guid: this._uuidService.generateUUID().toString()
- };
-
- try {
- stmt = this._dbCreateStatement(query, params);
- stmt.execute();
- } catch (e) {
- this.log("Failed setting GUID: " + e);
- throw e;
- } finally {
- if (stmt) {
- stmt.reset();
- }
- }
- }
- },
-
-
- /**
- * Version 3 adds a encType column.
- */
- _dbMigrateToVersion3 : function () {
- // Check to see if encType column already exists, add if needed
- let query;
- if (!this._dbColumnExists("encType")) {
- query = "ALTER TABLE moz_logins ADD COLUMN encType INTEGER";
- this._dbConnection.executeSimpleSQL(query);
-
- query = "CREATE INDEX IF NOT EXISTS " +
- "moz_logins_encType_index ON moz_logins (encType)";
- this._dbConnection.executeSimpleSQL(query);
- }
-
- // Get a list of existing logins
- let logins = [];
- let stmt;
- query = "SELECT id, encryptedUsername, encryptedPassword " +
- "FROM moz_logins WHERE encType isnull";
- try {
- stmt = this._dbCreateStatement(query);
- while (stmt.executeStep()) {
- let params = { id: stmt.row.id };
- // We will tag base64 logins correctly, but no longer support their use.
- if (stmt.row.encryptedUsername.charAt(0) == '~' ||
- stmt.row.encryptedPassword.charAt(0) == '~')
- params.encType = Ci.nsILoginManagerCrypto.ENCTYPE_BASE64;
- else
- params.encType = Ci.nsILoginManagerCrypto.ENCTYPE_SDR;
- logins.push(params);
- }
- } catch (e) {
- this.log("Failed getting logins: " + e);
- throw e;
- } finally {
- if (stmt) {
- stmt.reset();
- }
- }
-
- // Determine encryption type for each login and update the DB.
- query = "UPDATE moz_logins SET encType = :encType WHERE id = :id";
- for (let params of logins) {
- try {
- stmt = this._dbCreateStatement(query, params);
- stmt.execute();
- } catch (e) {
- this.log("Failed setting encType: " + e);
- throw e;
- } finally {
- if (stmt) {
- stmt.reset();
- }
- }
- }
- },
-
-
- /**
- * Version 4 adds timeCreated, timeLastUsed, timePasswordChanged,
- * and timesUsed columns
- */
- _dbMigrateToVersion4 : function () {
- let query;
- // Add the new columns, if needed.
- for (let column of ["timeCreated", "timeLastUsed", "timePasswordChanged", "timesUsed"]) {
- if (!this._dbColumnExists(column)) {
- query = "ALTER TABLE moz_logins ADD COLUMN " + column + " INTEGER";
- this._dbConnection.executeSimpleSQL(query);
- }
- }
-
- // Get a list of IDs for existing logins.
- let ids = [];
- let stmt;
- query = "SELECT id FROM moz_logins WHERE timeCreated isnull OR " +
- "timeLastUsed isnull OR timePasswordChanged isnull OR timesUsed isnull";
- try {
- stmt = this._dbCreateStatement(query);
- while (stmt.executeStep())
- ids.push(stmt.row.id);
- } catch (e) {
- this.log("Failed getting IDs: " + e);
- throw e;
- } finally {
- if (stmt) {
- stmt.reset();
- }
- }
-
- // Initialize logins with current time.
- query = "UPDATE moz_logins SET timeCreated = :initTime, timeLastUsed = :initTime, " +
- "timePasswordChanged = :initTime, timesUsed = 1 WHERE id = :id";
- let params = {
- id: null,
- initTime: Date.now()
- };
- for (let id of ids) {
- params.id = id;
- try {
- stmt = this._dbCreateStatement(query, params);
- stmt.execute();
- } catch (e) {
- this.log("Failed setting timestamps: " + e);
- throw e;
- } finally {
- if (stmt) {
- stmt.reset();
- }
- }
- }
- },
-
-
- /**
- * Version 5 adds the moz_deleted_logins table
- */
- _dbMigrateToVersion5 : function () {
- if (!this._dbConnection.tableExists("moz_deleted_logins")) {
- this._dbConnection.createTable("moz_deleted_logins", this._dbSchema.tables.moz_deleted_logins);
- }
- },
-
- /**
- * Version 6 migrates all the hosts from
- * moz_disabledHosts to the permission manager.
- */
- _dbMigrateToVersion6 : function () {
- let disabledHosts = [];
- let query = "SELECT hostname FROM moz_disabledHosts";
- let stmt;
-
- try {
- stmt = this._dbCreateStatement(query);
-
- while (stmt.executeStep()) {
- disabledHosts.push(stmt.row.hostname);
- }
-
- for (let host of disabledHosts) {
- try {
- let uri = Services.io.newURI(host, null, null);
- Services.perms.add(uri, PERMISSION_SAVE_LOGINS, Services.perms.DENY_ACTION);
- } catch (e) {
- Cu.reportError(e);
- }
- }
- } catch (e) {
- this.log(`_dbMigrateToVersion6 failed: ${e.name} : ${e.message}`);
- } finally {
- if (stmt) {
- stmt.reset();
- }
- }
-
- query = "DELETE FROM moz_disabledHosts";
- this._dbConnection.executeSimpleSQL(query);
- },
-
- /**
- * Sanity check to ensure that the columns this version of the code expects
- * are present in the DB we're using.
- */
- _dbAreExpectedColumnsPresent : function () {
- let query = "SELECT " +
- "id, " +
- "hostname, " +
- "httpRealm, " +
- "formSubmitURL, " +
- "usernameField, " +
- "passwordField, " +
- "encryptedUsername, " +
- "encryptedPassword, " +
- "guid, " +
- "encType, " +
- "timeCreated, " +
- "timeLastUsed, " +
- "timePasswordChanged, " +
- "timesUsed " +
- "FROM moz_logins";
- try {
- let stmt = this._dbConnection.createStatement(query);
- // (no need to execute statement, if it compiled we're good)
- stmt.finalize();
- } catch (e) {
- return false;
- }
-
- query = "SELECT " +
- "id, " +
- "hostname " +
- "FROM moz_disabledHosts";
- try {
- let stmt = this._dbConnection.createStatement(query);
- // (no need to execute statement, if it compiled we're good)
- stmt.finalize();
- } catch (e) {
- return false;
- }
-
- this.log("verified that expected columns are present in DB.");
- return true;
- },
-
-
- /**
- * Checks to see if the named column already exists.
- */
- _dbColumnExists : function (columnName) {
- let query = "SELECT " + columnName + " FROM moz_logins";
- try {
- let stmt = this._dbConnection.createStatement(query);
- // (no need to execute statement, if it compiled we're good)
- stmt.finalize();
- return true;
- } catch (e) {
- return false;
- }
- },
-
- _dbClose : function () {
- this.log("Closing the DB connection.");
- // Finalize all statements to free memory, avoid errors later
- for (let query in this._dbStmts) {
- let stmt = this._dbStmts[query];
- stmt.finalize();
- }
- this._dbStmts = {};
-
- if (this._dbConnection !== null) {
- try {
- this._dbConnection.close();
- } catch (e) {
- Components.utils.reportError(e);
- }
- }
- this._dbConnection = null;
- },
-
- /**
- * Called when database creation fails. Finalizes database statements,
- * closes the database connection, deletes the database file.
- */
- _dbCleanup : function (backup) {
- this.log("Cleaning up DB file - close & remove & backup=" + backup);
-
- // Create backup file
- if (backup) {
- let backupFile = this._signonsFile.leafName + ".corrupt";
- this._storageService.backupDatabaseFile(this._signonsFile, backupFile);
- }
-
- this._dbClose();
- this._signonsFile.remove(false);
- }
-
-}; // end of nsLoginManagerStorage_mozStorage implementation
-
-XPCOMUtils.defineLazyGetter(this.LoginManagerStorage_mozStorage.prototype, "log", () => {
- let logger = LoginHelper.createLogger("Login storage");
- return logger.log.bind(logger);
-});
-
-var component = [LoginManagerStorage_mozStorage];
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory(component);