diff --git a/toolkit/components/passwordmgr/public/Makefile.in b/toolkit/components/passwordmgr/public/Makefile.in index f39adf01658..9b43a72cfab 100644 --- a/toolkit/components/passwordmgr/public/Makefile.in +++ b/toolkit/components/passwordmgr/public/Makefile.in @@ -46,6 +46,7 @@ XPIDL_MODULE = loginmgr XPIDLSRCS = \ nsILoginInfo.idl \ + nsILoginMetaInfo.idl \ nsILoginManager.idl \ nsILoginManagerStorage.idl \ nsILoginManagerPrompter.idl \ diff --git a/toolkit/components/passwordmgr/public/nsILoginInfo.idl b/toolkit/components/passwordmgr/public/nsILoginInfo.idl index b905cf61270..d6d045a1d56 100644 --- a/toolkit/components/passwordmgr/public/nsILoginInfo.idl +++ b/toolkit/components/passwordmgr/public/nsILoginInfo.idl @@ -37,7 +37,7 @@ #include "nsISupports.idl" -[scriptable, uuid(9c87a9bd-bf8b-4fae-bdb8-70513b2877df)] +[scriptable, uuid(c41b7dff-6b9b-42fe-b78d-113051facb05)] /** * An object containing information for a login stored by the @@ -134,6 +134,15 @@ interface nsILoginInfo : nsISupports { * If true, ignore the password when checking for match. */ boolean matches(in nsILoginInfo aLoginInfo, in boolean ignorePassword); + + /** + * Create an identical copy of the login, duplicating all of the login's + * nsILoginInfo and nsILoginMetaInfo properties. + * + * This allows code to be forwards-compatible, when additional properties + * are added to nsILoginMetaInfo (or nsILoginInfo) in the future. + */ + nsILoginInfo clone(); }; %{C++ diff --git a/toolkit/components/passwordmgr/public/nsILoginManager.idl b/toolkit/components/passwordmgr/public/nsILoginManager.idl index b1424b4472c..a1b776c0649 100644 --- a/toolkit/components/passwordmgr/public/nsILoginManager.idl +++ b/toolkit/components/passwordmgr/public/nsILoginManager.idl @@ -43,7 +43,7 @@ interface nsIAutoCompleteResult; interface nsIDOMHTMLInputElement; interface nsIDOMHTMLFormElement; -[scriptable, uuid(04dbfa30-4238-11dd-ae16-0800200c9a66)] +[scriptable, uuid(9c78bfc1-422b-4f4f-ba09-f7eb3c4e72b2)] interface nsILoginManager : nsISupports { @@ -52,6 +52,10 @@ interface nsILoginManager : nsISupports { * * @param aLogin * The login to be added. + * + * Default values for the login's nsILoginMetaInfo properties will be + * created. However, if the caller specifies non-default values, they will + * be used instead. */ void addLogin(in nsILoginInfo aLogin); @@ -61,6 +65,9 @@ interface nsILoginManager : nsISupports { * * @param aLogin * The login to be removed. + * + * The specified login must exactly match a stored login. However, the + * values of any nsILoginMetaInfo properties are ignored. */ void removeLogin(in nsILoginInfo aLogin); @@ -68,10 +75,20 @@ interface nsILoginManager : nsISupports { /** * Modify an existing login in the login manager. * - * @param aLogin + * @param oldLogin * The login to be modified. + * @param newLoginData + * The new login values (either a nsILoginInfo or nsIProperyBag) + * + * If newLoginData is a nsILoginInfo, all of the old login's nsILoginInfo + * properties are changed to the values from newLoginData (but the old + * login's nsILoginMetaInfo properties are unmodified). + * + * If newLoginData is a nsIPropertyBag, only the specified properties + * will be changed. The nsILoginMetaInfo properties of oldLogin can be + * changed in this manner. */ - void modifyLogin(in nsILoginInfo oldLogin, in nsILoginInfo newLogin); + void modifyLogin(in nsILoginInfo oldLogin, in nsISupports newLoginData); /** diff --git a/toolkit/components/passwordmgr/public/nsILoginManagerStorage.idl b/toolkit/components/passwordmgr/public/nsILoginManagerStorage.idl index a6ba829681c..bdc1c1fe8fc 100644 --- a/toolkit/components/passwordmgr/public/nsILoginManagerStorage.idl +++ b/toolkit/components/passwordmgr/public/nsILoginManagerStorage.idl @@ -40,7 +40,7 @@ interface nsIFile; interface nsILoginInfo; -[scriptable, uuid(dec624d1-18ea-40ea-8ca5-69002153c8b8)] +[scriptable, uuid(199ebbff-4656-4a18-8da9-9401c64619f9)] /* * NOTE: This interface is intended to be implemented by modules @@ -71,30 +71,47 @@ interface nsILoginManagerStorage : nsISupports { /** - * Store a new login. + * Store a new login in the storage module. * * @param aLogin * The login to be added. + * + * Default values for the login's nsILoginMetaInfo properties will be + * created. However, if the caller specifies non-default values, they will + * be used instead. */ void addLogin(in nsILoginInfo aLogin); /** - * Remove a login from the login manager. + * Remove a login from the storage module. * * @param aLogin * The login to be removed. + * + * The specified login must exactly match a stored login. However, the + * values of any nsILoginMetaInfo properties are ignored. */ void removeLogin(in nsILoginInfo aLogin); /** - * Modify an existing login in the login manager. + * Modify an existing login in the storage module. * - * @param aLogin + * @param oldLogin * The login to be modified. + * @param newLoginData + * The new login values (either a nsILoginInfo or nsIProperyBag) + * + * If newLoginData is a nsILoginInfo, all of the old login's nsILoginInfo + * properties are changed to the values from newLoginData (but the old + * login's nsILoginMetaInfo properties are unmodified). + * + * If newLoginData is a nsIPropertyBag, only the specified properties + * will be changed. The nsILoginMetaInfo properties of oldLogin can be + * changed in this manner. */ - void modifyLogin(in nsILoginInfo oldLogin, in nsILoginInfo newLogin); + void modifyLogin(in nsILoginInfo oldLogin, in nsISupports newLoginData); /** diff --git a/toolkit/components/passwordmgr/public/nsILoginMetaInfo.idl b/toolkit/components/passwordmgr/public/nsILoginMetaInfo.idl new file mode 100644 index 00000000000..adbf02ed324 --- /dev/null +++ b/toolkit/components/passwordmgr/public/nsILoginMetaInfo.idl @@ -0,0 +1,62 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is Mozilla Corporation. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Justin Dolske (original author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + + +#include "nsISupports.idl" + +[scriptable, uuid(867407d5-10e0-43a0-bc81-a324740534ca)] + +/** + * An object containing metainfo for a login stored by the login manager. + * + * Code using login manager can generally ignore this interface. When adding + * logins, default value will be created. When modifying logins, these + * properties will be unchanged unless a change is explicitly requested [by + * using modifyLogin() with a nsIPropertyBag]. When deleting a login or + * comparing logins, these properties are ignored. + */ +interface nsILoginMetaInfo : nsISupports { + /** + * The GUID to uniquely identify the login. This can be any arbitrary + * string, but a format as created by nsIUUIDGenerator is recommended. + * For example, "{d4e1a1f6-5ea0-40ee-bff5-da57982f21cf}" + * + * addLogin will generate a random value unless a value is provided. + * + * addLogin and modifyLogin will throw if the GUID already exists. + */ + attribute AString guid; +}; diff --git a/toolkit/components/passwordmgr/src/nsLoginInfo.js b/toolkit/components/passwordmgr/src/nsLoginInfo.js index 133c7d48fea..3fd10bf1af2 100644 --- a/toolkit/components/passwordmgr/src/nsLoginInfo.js +++ b/toolkit/components/passwordmgr/src/nsLoginInfo.js @@ -47,7 +47,7 @@ nsLoginInfo.prototype = { classDescription : "LoginInfo", contractID : "@mozilla.org/login-manager/loginInfo;1", classID : Components.ID("{0f2f347c-1e4f-40cc-8efd-792dea70a85e}"), - QueryInterface: XPCOMUtils.generateQI([Ci.nsILoginInfo]), + QueryInterface: XPCOMUtils.generateQI([Ci.nsILoginInfo, Ci.nsILoginMetaInfo]), // Allow storage-Legacy.js to get at the JS object so it can // slap on a few extra properties for internal use. @@ -55,6 +55,10 @@ nsLoginInfo.prototype = { return this; }, + // + // nsILoginInfo interfaces... + // + hostname : null, formSubmitURL : null, httpRealm : null, @@ -105,7 +109,27 @@ nsLoginInfo.prototype = { return false; return true; - } + }, + + clone : function() { + let clone = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + clone.init(this.hostname, this.formSubmitURL, this.httpRealm, + this.username, this.password, + this.usernameField, this.passwordField); + + // Copy nsILoginMetaInfo props + clone.QueryInterface(Ci.nsILoginMetaInfo); + clone.guid = this.guid; + + return clone; + }, + + // + // nsILoginMetaInfo interfaces... + // + + guid : null }; // end of nsLoginInfo implementation diff --git a/toolkit/components/passwordmgr/src/storage-Legacy.js b/toolkit/components/passwordmgr/src/storage-Legacy.js index ec67909775c..a35c19649d9 100644 --- a/toolkit/components/passwordmgr/src/storage-Legacy.js +++ b/toolkit/components/passwordmgr/src/storage-Legacy.js @@ -311,6 +311,9 @@ LoginManagerStorage_legacy.prototype = { * */ modifyLogin : function (oldLogin, newLogin) { + if (newLogin instanceof Ci.nsIPropertyBag) + throw "legacy modifyLogin with propertybag not implemented."; + newLogin.QueryInterface(Ci.nsILoginInfo); // Throws if there are bogus values. this._checkLoginValues(newLogin); diff --git a/toolkit/components/passwordmgr/src/storage-mozStorage.js b/toolkit/components/passwordmgr/src/storage-mozStorage.js index 1ed3c41baf3..72070a139d3 100644 --- a/toolkit/components/passwordmgr/src/storage-mozStorage.js +++ b/toolkit/components/passwordmgr/src/storage-mozStorage.js @@ -271,7 +271,17 @@ LoginManagerStorage_mozStorage.prototype = { throw "User canceled master password entry, login not added."; } - let guid = this._uuidService.generateUUID().toString(); + // 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 "specified GUID already exists"; + } else { + loginClone.guid = this._uuidService.generateUUID().toString(); + } let query = "INSERT INTO moz_logins " + @@ -283,14 +293,14 @@ LoginManagerStorage_mozStorage.prototype = { ":guid)"; let params = { - hostname: login.hostname, - httpRealm: login.httpRealm, - formSubmitURL: login.formSubmitURL, - usernameField: login.usernameField, - passwordField: login.passwordField, + hostname: loginClone.hostname, + httpRealm: loginClone.httpRealm, + formSubmitURL: loginClone.formSubmitURL, + usernameField: loginClone.usernameField, + passwordField: loginClone.passwordField, encryptedUsername: encUsername, encryptedPassword: encPassword, - guid: guid + guid: loginClone.guid }; let stmt; @@ -311,7 +321,7 @@ LoginManagerStorage_mozStorage.prototype = { * */ removeLogin : function (login) { - let idToDelete = this._getIdForLogin(login); + let [idToDelete, storedLogin] = this._getIdForLogin(login); if (!idToDelete) throw "No matching logins"; @@ -335,13 +345,60 @@ LoginManagerStorage_mozStorage.prototype = { * modifyLogin * */ - modifyLogin : function (oldLogin, newLogin) { - // Throws if there are bogus values. - this._checkLoginValues(newLogin); - - let idToModify = this._getIdForLogin(oldLogin); + modifyLogin : function (oldLogin, newLoginData) { + let [idToModify, oldStoredLogin] = this._getIdForLogin(oldLogin); if (!idToModify) throw "No matching logins"; + oldStoredLogin.QueryInterface(Ci.nsILoginMetaInfo); + + let newLogin; + if (newLoginData instanceof Ci.nsILoginInfo) { + // Clone the existing login to get its nsILoginMetaInfo, then init it + // with the replacement nsILoginInfo data from the new login. + newLogin = oldStoredLogin.clone(); + newLogin.init(newLoginData.hostname, + newLoginData.formSubmitURL, newLoginData.httpRealm, + newLoginData.username, newLoginData.password, + newLoginData.usernameField, newLoginData.passwordField); + newLogin.QueryInterface(Ci.nsILoginMetaInfo); + } else if (newLoginData instanceof Ci.nsIPropertyBag) { + // Clone the existing login, along with all its properties. + newLogin = oldStoredLogin.clone(); + newLogin.QueryInterface(Ci.nsILoginMetaInfo); + + let propEnum = newLoginData.enumerator; + while (propEnum.hasMoreElements()) { + let prop = propEnum.getNext().QueryInterface(Ci.nsIProperty); + switch (prop.name) { + // nsILoginInfo properties... + case "hostname": + case "httpRealm": + case "formSubmitURL": + case "username": + case "password": + case "usernameField": + case "passwordField": + newLogin[prop.name] = prop.value; + break; + + // nsILoginMetaInfo properties... + case "guid": + newLogin.guid = prop.value; + if (!this._isGuidUnique(newLogin.guid)) + throw "specified GUID already exists"; + break; + + // Fail if caller requests setting an unknown property. + default: + throw "Unexpected propertybag item: " + prop.name; + } + } + } else { + throw "newLoginData needs an expected interface!"; + } + + // Throws if there are bogus values. + this._checkLoginValues(newLogin); // Get the encrypted value of the username and password. let [encUsername, encPassword, userCanceled] = this._encryptLogin(newLogin); @@ -356,10 +413,12 @@ LoginManagerStorage_mozStorage.prototype = { "usernameField = :usernameField, " + "passwordField = :passwordField, " + "encryptedUsername = :encryptedUsername, " + - "encryptedPassword = :encryptedPassword " + + "encryptedPassword = :encryptedPassword, " + + "guid = :guid " + "WHERE id = :id"; let params = { + id: idToModify, hostname: newLogin.hostname, httpRealm: newLogin.httpRealm, formSubmitURL: newLogin.formSubmitURL, @@ -367,8 +426,7 @@ LoginManagerStorage_mozStorage.prototype = { passwordField: newLogin.passwordField, encryptedUsername: encUsername, encryptedPassword: encPassword, - id: idToModify - // guid not changed + guid: newLogin.guid }; let stmt; @@ -552,13 +610,15 @@ LoginManagerStorage_mozStorage.prototype = { /* * _getIdForLogin * - * Returns the |id| for the specified login, or null if the login was not - * found. + * 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 [logins, ids] = this._queryLogins(login.hostname, login.formSubmitURL, login.httpRealm); 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 @@ -575,11 +635,12 @@ LoginManagerStorage_mozStorage.prototype = { continue; // We've found a match, set id and break + foundLogin = decryptedLogin; id = ids[i]; break; } - return id; + return [id, foundLogin]; }, @@ -614,6 +675,9 @@ LoginManagerStorage_mozStorage.prototype = { 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; logins.push(login); ids.push(stmt.row.id); } @@ -758,6 +822,30 @@ LoginManagerStorage_mozStorage.prototype = { }, + /* + * _isGuidUnique + * + * 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.step(); + numLogins = stmt.row.numLogins; + } catch (e) { + this.log("_isGuidUnique failed: " + e.name + " : " + e.message); + } finally { + stmt.reset(); + } + + return (numLogins == 0); + }, + + /* * _importLegacySignons * diff --git a/toolkit/components/passwordmgr/test/unit/test_storage_mozStorage_6.js b/toolkit/components/passwordmgr/test/unit/test_storage_mozStorage_6.js new file mode 100644 index 00000000000..ba006b3c3d7 --- /dev/null +++ b/toolkit/components/passwordmgr/test/unit/test_storage_mozStorage_6.js @@ -0,0 +1,262 @@ +/* + * Test suite for storage-mozStorage.js + * + * This test interfaces directly with the mozStorage password storage module, + * bypassing the normal password manager usage. + * + */ + + +const STORAGE_TYPE = "mozStorage"; + +function run_test() { + +try { + +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.QueryInterface(Ci.nsILoginMetaInfo); +testuser1.init("http://testhost1", "", null, + "dummydude", "itsasecret", "put_user_here", "put_pw_here"); +var guid1; + +var testuser2 = new nsLoginInfo; +testuser2.init("http://testhost2", "", null, + "dummydude2", "itsasecret2", "put_user2_here", "put_pw2_here"); +testuser2.QueryInterface(Ci.nsILoginMetaInfo); +var guid2 = "{12345678-abcd-1234-abcd-987654321000}"; +testuser2.guid = guid2; + +var testuser3 = new nsLoginInfo; +testuser3.QueryInterface(Ci.nsILoginMetaInfo); +testuser3.init("http://testhost3", "", null, + "dummydude3", "itsasecret3", "put_user3_here", "put_pw3_here"); +var guid3 = "{99999999-abcd-9999-abcd-999999999999}"; + +// This login is different than testuser2, except it has the same guid. +var testuser2dupeguid = new nsLoginInfo; +testuser2dupeguid.QueryInterface(Ci.nsILoginMetaInfo); +testuser2dupeguid.init("http://dupe-testhost2", "", null, + "dupe-dummydude2", "dupe-itsasecret2", "put_user2_here", "put_pw2_here"); +testuser2dupeguid.QueryInterface(Ci.nsILoginMetaInfo); +testuser2dupeguid.guid = guid2; + +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}\}$/; + +/* ========== 1 ========== */ +var testnum = 1; +var testdesc = "Initial connection to storage module" + +LoginTest.deleteFile(OUTDIR, "signons-unittest6.sqlite"); + +var storage; +storage = LoginTest.initStorage(INDIR, "signons-empty.txt", OUTDIR, "signons-unittest6.sqlite"); +var logins = storage.getAllLogins({}); +do_check_eq(logins.length, 0, "Checking for no initial logins"); +var disabledHosts = storage.getAllDisabledHosts({}); +do_check_eq(disabledHosts.length, 0, "Checking for no initial disabled hosts"); + + +/* ========== 2 ========== */ +testnum++; +testdesc = "add user1 w/o guid"; + +storage.addLogin(testuser1); +LoginTest.checkStorageData(storage, [], [testuser1]); + +// Check guid +do_check_eq(testuser1.guid, null, "caller's login shouldn't be modified"); +logins = storage.findLogins({}, "http://testhost1", "", null); +do_check_eq(logins.length, 1, "expecting 1 login"); +logins[0].QueryInterface(Ci.nsILoginMetaInfo); +do_check_true(isGUID.test(logins[0].guid), "testuser1 guid is set"); +guid1 = logins[0].guid; + +/* ========== 3 ========== */ +testnum++; +testdesc = "add user2 WITH guid"; + +storage.addLogin(testuser2); +LoginTest.checkStorageData(storage, [], [testuser1, testuser2]); + +// Check guid +do_check_eq(testuser2.guid, guid2, "caller's login shouldn't be modified"); +logins = storage.findLogins({}, "http://testhost2", "", null); +do_check_eq(logins.length, 1, "expecting 1 login"); +logins[0].QueryInterface(Ci.nsILoginMetaInfo); +do_check_true(isGUID.test(logins[0].guid), "testuser2 guid is set"); +do_check_eq(logins[0].guid, guid2, "checking guid2"); + +/* ========== 4 ========== */ +testnum++; +testdesc = "add user3 w/o guid"; + +storage.addLogin(testuser3); +LoginTest.checkStorageData(storage, [], [testuser1, testuser2, testuser3]); +logins = storage.findLogins({}, "http://testhost3", "", null); +do_check_eq(logins.length, 1, "expecting 1 login"); +logins[0].QueryInterface(Ci.nsILoginMetaInfo); +do_check_true(isGUID.test(logins[0].guid), "testuser3 guid is set"); +do_check_neq(logins[0].guid, guid3, "testuser3 guid is different"); + +/* ========== 5 ========== */ +testnum++; +testdesc = "(don't) modify user1"; + +// When newlogin.guid is blank, the GUID shouldn't be changed. +testuser1.guid = ""; +storage.modifyLogin(testuser1, testuser1); + +// Check it +do_check_eq(testuser1.guid, "", "caller's login shouldn't be modified"); +logins = storage.findLogins({}, "http://testhost1", "", null); +do_check_eq(logins.length, 1, "expecting 1 login"); +logins[0].QueryInterface(Ci.nsILoginMetaInfo); +do_check_eq(logins[0].guid, guid1, "checking guid1"); + +/* ========== 6 ========== */ +testnum++; +testdesc = "modify user3"; + +// change the GUID to our known value +var propbag = Cc["@mozilla.org/hash-property-bag;1"]. + createInstance(Ci.nsIWritablePropertyBag); +propbag.setProperty("guid", guid3); +storage.modifyLogin(testuser3, propbag); + +// Check it +logins = storage.findLogins({}, "http://testhost3", "", null); +do_check_eq(logins.length, 1, "expecting 1 login"); +logins[0].QueryInterface(Ci.nsILoginMetaInfo); +do_check_eq(logins[0].guid, guid3, "checking guid3"); + +/* ========== 7 ========== */ +testnum++; +testdesc = "try adding a duplicate guid"; + +var ex = null; +try { + storage.addLogin(testuser2dupeguid); +} catch (e) { + ex = e; +} +do_check_true(/specified GUID already exists/.test(ex), "ensuring exception thrown when adding duplicate GUID"); +LoginTest.checkStorageData(storage, [], [testuser1, testuser2, testuser3]); + +/* ========== 8 ========== */ +testnum++; +testdesc = "try modifing to a duplicate guid"; + +propbag = Cc["@mozilla.org/hash-property-bag;1"]. + createInstance(Ci.nsIWritablePropertyBag); +propbag.setProperty("guid", testuser2dupeguid.guid); + +ex = null; +try { + storage.modifyLogin(testuser1, propbag); +} catch (e) { + ex = e; +} +do_check_true(/specified GUID already exists/.test(ex), "ensuring exception thrown when modifying to duplicate GUID"); +LoginTest.checkStorageData(storage, [], [testuser1, testuser2, testuser3]); + + +/* ========== 9 ========== */ +testnum++; +testdesc = "check propertybag nulls/empty strings"; + +// Set formSubmitURL to a null, and usernameField to a empty-string. +do_check_eq(testuser3.formSubmitURL, ""); +do_check_eq(testuser3.httpRealm, null); +do_check_eq(testuser3.usernameField, "put_user3_here"); +propbag = Cc["@mozilla.org/hash-property-bag;1"]. + createInstance(Ci.nsIWritablePropertyBag); +propbag.setProperty("formSubmitURL", null); +propbag.setProperty("httpRealm", "newRealm"); +propbag.setProperty("usernameField", ""); + +storage.modifyLogin(testuser3, propbag); + +// Fixup testuser3 to match the new values. +testuser3.formSubmitURL = null; +testuser3.httpRealm = "newRealm"; +testuser3.usernameField = ""; +LoginTest.checkStorageData(storage, [], [testuser1, testuser2, testuser3]); + + +/* ========== 10 ========== */ +testnum++; +testdesc = "[reinit storage, look for expected guids]"; + +storage = LoginTest.reloadStorage(OUTDIR, "signons-unittest6.sqlite"); +LoginTest.checkStorageData(storage, [], [testuser1, testuser2, testuser3]); + +logins = storage.findLogins({}, "http://testhost1", "", null); +do_check_eq(logins.length, 1, "expecting 1 login"); +logins[0].QueryInterface(Ci.nsILoginMetaInfo); +do_check_eq(logins[0].guid, guid1, "checking guid1"); + +logins = storage.findLogins({}, "http://testhost2", "", null); +do_check_eq(logins.length, 1, "expecting 1 login"); +logins[0].QueryInterface(Ci.nsILoginMetaInfo); +do_check_eq(logins[0].guid, guid2, "checking guid2"); + +logins = storage.findLogins({}, "http://testhost3", null, "newRealm"); +do_check_eq(logins.length, 1, "expecting 1 login"); +logins[0].QueryInterface(Ci.nsILoginMetaInfo); +do_check_eq(logins[0].guid, guid3, "checking guid3"); + + +/* ========== 11 ========== */ +testnum++; +testdesc = "login w/o nsILoginMetaInfo impl"; + +var wonkyDelegate = new nsLoginInfo; +wonkyDelegate.init("http://wonky", null, "wonkyness", + "wonkyuser", "wonkypass", "u", "p"); + +var wonkyLogin = { + QueryInterface : function (iid) { + var interfaces = [Ci.nsILoginInfo, Ci.nsISupports]; + if (!interfaces.some( function(v) { return iid.equals(v) })) + throw Components.results.NS_ERROR_NO_INTERFACE; + return this; + }, + hostname: wonkyDelegate.hostname, + formSubmitURL: wonkyDelegate.formSubmitURL, + httpRealm: wonkyDelegate.httpRealm, + username: wonkyDelegate.username, + password: wonkyDelegate.password, + usernameField: wonkyDelegate.usernameField, + passwordField: wonkyDelegate.passwordField, + equals: wonkyDelegate.equals, + matches: wonkyDelegate.matches, + clone: wonkyDelegate.clone +}; + +storage.addLogin(wonkyLogin); +LoginTest.checkStorageData(storage, [], [testuser1, testuser2, testuser3, wonkyLogin]); + +logins = storage.findLogins({}, "http://wonky", null, ""); +do_check_eq(logins.length, 1, "expecting 1 login"); +logins[0].QueryInterface(Ci.nsILoginMetaInfo); +do_check_true(isGUID.test(logins[0].guid), "wonky guid is set"); + +storage.modifyLogin(wonkyLogin, wonkyLogin); +LoginTest.checkStorageData(storage, [], [testuser1, testuser2, testuser3, wonkyLogin]); +storage.removeLogin(wonkyLogin); +LoginTest.checkStorageData(storage, [], [testuser1, testuser2, testuser3]); + + +LoginTest.deleteFile(OUTDIR, "signons-unittest6.sqlite"); + +} catch (e) { + throw "FAILED in test #" + testnum + " -- " + testdesc + ": " + e; +} +};