Bug 467463 - Login Manager should store a GUID for logins. r=gavin, r=sdwilsh.

--HG--
rename : toolkit/components/passwordmgr/test/unit/key3.db => toolkit/components/passwordmgr/test/unit/data/key3.db
This commit is contained in:
Justin Dolske 2009-01-12 16:26:48 -08:00
Родитель 28590e825e
Коммит 5bc73bc2fb
8 изменённых файлов: 379 добавлений и 65 удалений

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

@ -41,7 +41,7 @@
const Cc = Components.classes;
const Ci = Components.interfaces;
const DB_VERSION = 1; // The database schema version
const DB_VERSION = 2; // The database schema version
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
@ -101,6 +101,13 @@ LoginManagerStorage_mozStorage.prototype = {
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: {
@ -112,7 +119,8 @@ LoginManagerStorage_mozStorage.prototype = {
"usernameField TEXT NOT NULL," +
"passwordField TEXT NOT NULL," +
"encryptedUsername TEXT NOT NULL," +
"encryptedPassword TEXT NOT NULL",
"encryptedPassword TEXT NOT NULL," +
"guid TEXT",
moz_disabledHosts: "id INTEGER PRIMARY KEY," +
"hostname TEXT UNIQUE ON CONFLICT REPLACE",
@ -129,7 +137,11 @@ LoginManagerStorage_mozStorage.prototype = {
moz_logins_hostname_httpRealm_index: {
table: "moz_logins",
columns: ["hostname", "httpRealm"]
}
},
moz_logins_guid_index: {
table: "moz_logins",
columns: ["guid"]
}
}
},
_dbConnection : null, // The database connection
@ -221,7 +233,7 @@ LoginManagerStorage_mozStorage.prototype = {
this._initialized = true;
} catch (e) {
this.log("Initialization failed");
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);
@ -259,12 +271,16 @@ LoginManagerStorage_mozStorage.prototype = {
throw "User canceled master password entry, login not added.";
}
let guid = this._uuidService.generateUUID().toString();
let query =
"INSERT INTO moz_logins " +
"(hostname, httpRealm, formSubmitURL, usernameField, " +
"passwordField, encryptedUsername, encryptedPassword) " +
"passwordField, encryptedUsername, encryptedPassword, " +
"guid) " +
"VALUES (:hostname, :httpRealm, :formSubmitURL, :usernameField, " +
":passwordField, :encryptedUsername, :encryptedPassword)";
":passwordField, :encryptedUsername, :encryptedPassword, " +
":guid)";
let params = {
hostname: login.hostname,
@ -273,7 +289,8 @@ LoginManagerStorage_mozStorage.prototype = {
usernameField: login.usernameField,
passwordField: login.passwordField,
encryptedUsername: encUsername,
encryptedPassword: encPassword
encryptedPassword: encPassword,
guid: guid
};
let stmt;
@ -351,6 +368,7 @@ LoginManagerStorage_mozStorage.prototype = {
encryptedUsername: encUsername,
encryptedPassword: encPassword,
id: idToModify
// guid not changed
};
let stmt;
@ -950,8 +968,6 @@ LoginManagerStorage_mozStorage.prototype = {
//**************************************************************************//
// Database Creation & Access
// Hijacked from /toolkit/components/contentprefs/src/nsContentPrefService.js
// and modified to fit here. Look there for migration samples.
/*
* _dbCreateStatement
@ -961,21 +977,22 @@ LoginManagerStorage_mozStorage.prototype = {
* so that statements can be reused.
*/
_dbCreateStatement : function (query, params) {
let wrappedStmt = this._dbStmts[query];
// Memoize the statements
if (!this._dbStmts[query]) {
if (!wrappedStmt) {
this.log("Creating new statement for query: " + query);
let stmt = this._dbConnection.createStatement(query);
let wrappedStmt = Cc["@mozilla.org/storage/statement-wrapper;1"].
createInstance(Ci.mozIStorageStatementWrapper);
wrappedStmt = Cc["@mozilla.org/storage/statement-wrapper;1"].
createInstance(Ci.mozIStorageStatementWrapper);
wrappedStmt.initialize(stmt);
this._dbStmts[query] = wrappedStmt;
}
// Replace parameters, must be done 1 at a time
if (params)
for (let i in params)
this._dbStmts[query].params[i] = params[i];
return this._dbStmts[query];
wrappedStmt.params[i] = params[i];
return wrappedStmt;
},
@ -991,33 +1008,20 @@ LoginManagerStorage_mozStorage.prototype = {
let isFirstRun = false;
try {
this._dbConnection = this._storageService.openDatabase(this._signonsFile);
// schemaVersion will be 0 if the database has not been created yet
if (this._dbConnection.schemaVersion == 0) {
// 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 {
// Get the version of the schema in the file.
let version = this._dbConnection.schemaVersion;
// Try to migrate the schema in the database to the current schema used by
// the service.
if (version != DB_VERSION) {
try {
this._dbMigrate(version, DB_VERSION);
}
catch (e) {
this.log("Migration Failed");
throw(e);
}
}
} else if (version != DB_VERSION) {
this._dbMigrate(version);
}
} catch (e) {
} catch (e if e.result == Components.results.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
if (e.result == Components.results.NS_ERROR_FILE_CORRUPTED)
this._dbCleanup(true);
this._dbCleanup(true);
throw e;
// TODO handle migration failures
}
return isFirstRun;
},
@ -1054,24 +1058,153 @@ LoginManagerStorage_mozStorage.prototype = {
},
_dbMigrate : function (oldVersion, newVersion) {
this.log("Attempting to migrate from v" + oldVersion + "to v" + newVersion);
if (this["_dbMigrate" + oldVersion + "To" + newVersion]) {
this._dbConnection.beginTransaction();
_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",
Components.results.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...
this._dbConnection.beginTransaction();
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);
this._dbConnection.rollbackTransaction();
throw e;
}
this._dbConnection.schemaVersion = DB_VERSION;
this._dbConnection.commitTransaction();
this.log("DB migration completed.");
},
/*
* _dbMigrateToVersion2
*
* Version 2 adds a GUID column. Existing logins are assigned a random GUID.
*/
_dbMigrateToVersion2 : function () {
// Check to see if GUID column already exists.
let exists = true;
try {
let stmt = this._dbConnection.createStatement(
"SELECT guid FROM moz_logins");
// (no need to execute statement, if it compiled we're good)
stmt.finalize();
} catch (e) {
exists = false;
}
// Add the new column and index only if needed.
if (!exists) {
this._dbConnection.executeSimpleSQL(
"ALTER TABLE moz_logins ADD COLUMN guid TEXT");
this._dbConnection.executeSimpleSQL(
"CREATE INDEX IF NOT EXISTS " +
"moz_logins_guid_index ON moz_logins (guid)");
}
// Get a list of IDs for existing logins
let ids = [];
let query = "SELECT id FROM moz_logins WHERE guid isnull";
let stmt;
try {
stmt = this._dbCreateStatement(query);
while (stmt.step())
ids.push(stmt.row.id);
} catch (e) {
this.log("Failed getting IDs: " + e);
throw e;
} finally {
stmt.reset();
}
// Generate a GUID for each login and update the DB.
query = "UPDATE moz_logins SET guid = :guid WHERE id = :id";
for each (let id in ids) {
let params = {
id: id,
guid: this._uuidService.generateUUID().toString()
};
try {
this["_dbMigrate" + oldVersion + "To" + newVersion]();
this._dbConnection.schemaVersion = newVersion;
this._dbConnection.commitTransaction();
}
catch (e) {
this._dbConnection.rollbackTransaction();
stmt = this._dbCreateStatement(query, params);
stmt.execute();
} catch (e) {
this.log("Failed setting GUID: " + e);
throw e;
} finally {
stmt.reset();
}
}
else {
throw("no migrator function from version " + oldVersion +
" to version " + newVersion);
},
/*
* _dbAreExpectedColumnsPresent
*
* 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 " +
"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;
},

Двоичные данные
toolkit/components/passwordmgr/test/unit/data/signons-v1.sqlite Normal file

Двоичный файл не отображается.

Двоичные данные
toolkit/components/passwordmgr/test/unit/data/signons-v1v2.sqlite Normal file

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичные данные
toolkit/components/passwordmgr/test/unit/data/signons-v999.sqlite Normal file

Двоичный файл не отображается.

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

@ -227,9 +227,21 @@ const LoginTest = {
return storage;
},
openDB : function (filename) {
// nsIFile for the specified filename, in the profile dir.
var dbfile = PROFDIR.clone();
dbfile.append(filename);
var ss = Cc["@mozilla.org/storage/service;1"].
getService(Ci.mozIStorageService);
var dbConnection = ss.openDatabase(dbfile);
return dbConnection;
},
deleteFile : function (pathname, filename) {
var file = Cc["@mozilla.org/file/local;1"].
createInstance(Ci.nsILocalFile);
createInstance(Ci.nsILocalFile);
file.initWithPath(pathname);
file.append(filename);
// Suppress failures, this happens in the mozstorage tests on Windows
@ -239,8 +251,21 @@ const LoginTest = {
if (file.exists())
file.remove(false);
} catch (e) {}
}
},
// Copies a file from our test data directory to the unit test profile.
copyFile : function (filename) {
var file = DATADIR.clone();
file.append(filename);
var profileFile = PROFDIR.clone();
profileFile.append(filename);
if (profileFile.exists())
profileFile.remove(false);
file.copyTo(PROFDIR, filename);
}
};
@ -257,21 +282,14 @@ if (!profileDir) {
}
// nsIFiles...
var PROFDIR = profileDir;
var DATADIR = do_get_file("toolkit/components/passwordmgr/test/unit/data/" +
"signons-00.txt").parent;
// string versions...
var OUTDIR = PROFDIR.path;
var INDIR = do_get_file("toolkit/components/passwordmgr/test/unit/data/" +
"signons-00.txt").parent.path;
// Copy key3.db into the proper place, removing the file if it already exists.
// key3.db will be automatically created if it doesn't exist, so always
// replace it to ensure we have the key we need.
var keydb = do_get_file("toolkit/components/passwordmgr/test/unit/key3.db");
try {
var oldfile = profileDir.clone();
oldfile.append("key3.db");
if (oldfile.exists())
oldfile.remove(false);
} catch(e) { }
keydb.copyTo(profileDir, "key3.db");
var INDIR = DATADIR.path;
// Copy key3.db into the profile used for the unit tests. Need this so we can
// decrypt the encrypted logins stored in the various tests inputs.
LoginTest.copyFile("key3.db");

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

@ -0,0 +1,163 @@
/*
* Test suite for storage-mozStorage.js -- exercises schema version migration.
*
* This test interfaces directly with the mozStorage password storage module,
* bypassing the normal password manager usage.
*
* (Tests for importing from the legacy storage module are in test_storage_mozStorage_1.js)
*/
const STORAGE_TYPE = "mozStorage";
// Current schema version used by storage-mozStorage.js. This will need to be
// kept in sync with the version there (or else the tests fail).
const CURRENT_SCHEMA = 2;
function run_test() {
try {
var isGUID = /^\{[0-9a-f\d]{8}-[0-9a-f\d]{4}-[0-9a-f\d]{4}-[0-9a-f\d]{4}-[0-9a-f\d]{12}\}$/;
function getGUIDforID(conn, id) {
var stmt = conn.createStatement("SELECT guid from moz_logins WHERE id = " + id);
stmt.executeStep();
var guid = stmt.getString(0);
stmt.finalize();
return guid;
}
var storage;
var dbConnection;
var testnum = 0;
var testdesc = "Setup of nsLoginInfo test-users";
var nsLoginInfo = new Components.Constructor(
"@mozilla.org/login-manager/loginInfo;1",
Components.interfaces.nsILoginInfo);
do_check_true(nsLoginInfo != null);
var testuser1 = new nsLoginInfo;
testuser1.init("http://test.com", "http://test.com", null,
"testuser1", "testpass1", "u1", "p1");
var testuser1B = new nsLoginInfo;
testuser1B.init("http://test.com", "http://test.com", null,
"testuser1B", "testpass1B", "u1", "p1");
var testuser2 = new nsLoginInfo;
testuser2.init("http://test.org", "http://test.org", null,
"testuser2", "testpass2", "u2", "p2");
var testuser3 = new nsLoginInfo;
testuser3.init("http://test.gov", "http://test.gov", null,
"testuser3", "testpass3", "u3", "p3");
/* ========== 1 ========== */
testnum++;
testdesc = "Test downgrade from v999 storage"
LoginTest.copyFile("signons-v999.sqlite");
// Verify the schema version in the test file.
dbConnection = LoginTest.openDB("signons-v999.sqlite");
do_check_eq(999, dbConnection.schemaVersion);
dbConnection.close();
storage = LoginTest.reloadStorage(OUTDIR, "signons-v999.sqlite");
LoginTest.checkStorageData(storage, ["https://disabled.net"], [testuser1]);
// Check to make sure we downgraded the schema version.
dbConnection = LoginTest.openDB("signons-v999.sqlite");
do_check_eq(CURRENT_SCHEMA, dbConnection.schemaVersion);
dbConnection.close();
LoginTest.deleteFile(OUTDIR, "signons-v999.sqlite");
/* ========== 2 ========== */
testnum++;
testdesc = "Test downgrade from incompat v999 storage"
// This file has a testuser999/testpass999, but is missing an expected column
var origFile = PROFDIR.clone();
origFile.append("signons-v999-2.sqlite");
var failFile = PROFDIR.clone();
failFile.append("signons-v999-2.sqlite.corrupt");
// Make sure we always start clean in a clean state.
LoginTest.deleteFile(OUTDIR, "signons-v999-2.sqlite");
LoginTest.deleteFile(OUTDIR, "signons-v999-2.sqlite.corrupt");
do_check_false(origFile.exists());
do_check_false(failFile.exists());
LoginTest.copyFile("signons-v999-2.sqlite");
do_check_true(origFile.exists());
storage = LoginTest.reloadStorage(OUTDIR, "signons-v999-2.sqlite", /Initialization failed/);
// Check to ensure the DB file was renamed to .corrupt.
do_check_false(origFile.exists());
do_check_true(failFile.exists());
LoginTest.deleteFile(OUTDIR, "signons-v999-2.sqlite");
LoginTest.deleteFile(OUTDIR, "signons-v999-2.sqlite.corrupt");
/* ========== 3 ========== */
testnum++;
testdesc = "Test upgrade from v1 storage"
LoginTest.copyFile("signons-v1.sqlite");
// Sanity check the test file.
dbConnection = LoginTest.openDB("signons-v1.sqlite");
do_check_eq(1, dbConnection.schemaVersion);
dbConnection.close();
storage = LoginTest.reloadStorage(OUTDIR, "signons-v1.sqlite");
LoginTest.checkStorageData(storage, ["https://disabled.net"], [testuser1, testuser2]);
// Check to see that we added a GUIDs to the logins.
dbConnection = LoginTest.openDB("signons-v1.sqlite");
do_check_eq(CURRENT_SCHEMA, dbConnection.schemaVersion);
var guid = getGUIDforID(dbConnection, 1);
do_check_true(isGUID.test(guid));
guid = getGUIDforID(dbConnection, 2);
do_check_true(isGUID.test(guid));
dbConnection.close();
LoginTest.deleteFile(OUTDIR, "signons-v1.sqlite");
/* ========== 4 ========== */
testnum++;
testdesc = "Test upgrade v2->v1 storage";
// This is the case where a v2 DB has been accessed with v1 code, and now we
// are upgrading it again. Any logins added by the v1 code must be properly
// upgraded.
LoginTest.copyFile("signons-v1v2.sqlite");
// Sanity check the test file.
dbConnection = LoginTest.openDB("signons-v1v2.sqlite");
do_check_eq(1, dbConnection.schemaVersion);
dbConnection.close();
storage = LoginTest.reloadStorage(OUTDIR, "signons-v1v2.sqlite");
LoginTest.checkStorageData(storage, ["https://disabled.net"], [testuser1, testuser2, testuser3]);
// While we're here, try modifying a login, to ensure that doing so doesn't
// change the existing GUID.
storage.modifyLogin(testuser1, testuser1B);
LoginTest.checkStorageData(storage, ["https://disabled.net"], [testuser1B, testuser2, testuser3]);
// Check the GUIDs. Logins 1 and 2 should retain their original GUID, login 3
// should have one created (because it didn't have one previously).
dbConnection = LoginTest.openDB("signons-v1v2.sqlite");
do_check_eq(CURRENT_SCHEMA, dbConnection.schemaVersion);
guid = getGUIDforID(dbConnection, 1);
do_check_eq("{655c7358-f1d6-6446-adab-53f98ac5d80f}", guid);
guid = getGUIDforID(dbConnection, 2);
do_check_eq("{13d9bfdc-572a-4d4e-9436-68e9803e84c1}", guid);
guid = getGUIDforID(dbConnection, 3);
do_check_true(isGUID.test(guid));
dbConnection.close();
LoginTest.deleteFile(OUTDIR, "signons-v1v2.sqlite");
} catch (e) {
throw "FAILED in test #" + testnum + " -- " + testdesc + ": " + e;
}
};