/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ /* * Shared functions generally available for testing login components. */ "use strict"; this.EXPORTED_SYMBOLS = [ "LoginTestUtils", ]; const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; Cu.import("resource://gre/modules/Promise.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/Task.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://testing-common/TestUtils.jsm"); const LoginInfo = Components.Constructor("@mozilla.org/login-manager/loginInfo;1", "nsILoginInfo", "init"); // For now, we need consumers to provide a reference to Assert.jsm. var Assert = null; this.LoginTestUtils = { set Assert(assert) { Assert = assert; // eslint-disable-line no-native-reassign }, /** * Forces the storage module to save all data, and the Login Manager service * to replace the storage module with a newly initialized instance. */ * reloadData() { Services.obs.notifyObservers(null, "passwordmgr-storage-replace", null); yield TestUtils.topicObserved("passwordmgr-storage-replace-complete"); }, /** * Erases all the data stored by the Login Manager service. */ clearData() { Services.logins.removeAllLogins(); for (let hostname of Services.logins.getAllDisabledHosts()) { Services.logins.setLoginSavingEnabled(hostname, true); } }, /** * Checks that the currently stored list of nsILoginInfo matches the provided * array. The comparison uses the "equals" method of nsILoginInfo, that does * not include nsILoginMetaInfo properties in the test. */ checkLogins(expectedLogins) { this.assertLoginListsEqual(Services.logins.getAllLogins(), expectedLogins); }, /** * Checks that the two provided arrays of nsILoginInfo have the same length, * and every login in "expected" is also found in "actual". The comparison * uses the "equals" method of nsILoginInfo, that does not include * nsILoginMetaInfo properties in the test. */ assertLoginListsEqual(actual, expected) { Assert.equal(expected.length, actual.length); Assert.ok(expected.every(e => actual.some(a => a.equals(e)))); }, /** * Checks that the two provided arrays of strings contain the same values, * maybe in a different order, case-sensitively. */ assertDisabledHostsEqual(actual, expected) { Assert.deepEqual(actual.sort(), expected.sort()); }, /** * Checks whether the given time, expressed as the number of milliseconds * since January 1, 1970, 00:00:00 UTC, falls within 30 seconds of now. */ assertTimeIsAboutNow(timeMs) { Assert.ok(Math.abs(timeMs - Date.now()) < 30000); }, }; /** * This object contains functions that return new instances of nsILoginInfo for * every call. The returned instances can be compared using their "equals" or * "matches" methods, or modified for the needs of the specific test being run. * * Any modification to the test data requires updating the tests accordingly, in * particular the search tests. */ this.LoginTestUtils.testData = { /** * Returns a new nsILoginInfo for use with form submits. * * @param modifications * Each property of this object replaces the property of the same name * in the returned nsILoginInfo or nsILoginMetaInfo. */ formLogin(modifications) { let loginInfo = new LoginInfo("http://www3.example.com", "http://www.example.com", null, "the username", "the password", "form_field_username", "form_field_password"); loginInfo.QueryInterface(Ci.nsILoginMetaInfo); if (modifications) { for (let [name, value] of Object.entries(modifications)) { loginInfo[name] = value; } } return loginInfo; }, /** * Returns a new nsILoginInfo for use with HTTP authentication. * * @param modifications * Each property of this object replaces the property of the same name * in the returned nsILoginInfo or nsILoginMetaInfo. */ authLogin(modifications) { let loginInfo = new LoginInfo("http://www.example.org", null, "The HTTP Realm", "the username", "the password", "", ""); loginInfo.QueryInterface(Ci.nsILoginMetaInfo); if (modifications) { for (let [name, value] of Object.entries(modifications)) { loginInfo[name] = value; } } return loginInfo; }, /** * Returns an array of typical nsILoginInfo that could be stored in the * database. */ loginList() { return [ // --- Examples of form logins (subdomains of example.com) --- // Simple form login with named fields for username and password. new LoginInfo("http://www.example.com", "http://www.example.com", null, "the username", "the password for www.example.com", "form_field_username", "form_field_password"), // Different schemes are treated as completely different sites. new LoginInfo("https://www.example.com", "https://www.example.com", null, "the username", "the password for https", "form_field_username", "form_field_password"), // Subdomains are treated as completely different sites. new LoginInfo("https://example.com", "https://example.com", null, "the username", "the password for example.com", "form_field_username", "form_field_password"), // Forms found on the same host, but with different hostnames in the // "action" attribute, are handled independently. new LoginInfo("http://www3.example.com", "http://www.example.com", null, "the username", "the password", "form_field_username", "form_field_password"), new LoginInfo("http://www3.example.com", "https://www.example.com", null, "the username", "the password", "form_field_username", "form_field_password"), new LoginInfo("http://www3.example.com", "http://example.com", null, "the username", "the password", "form_field_username", "form_field_password"), // It is not possible to store multiple passwords for the same username, // however multiple passwords can be stored when the usernames differ. // An empty username is a valid case and different from the others. new LoginInfo("http://www4.example.com", "http://www4.example.com", null, "username one", "password one", "form_field_username", "form_field_password"), new LoginInfo("http://www4.example.com", "http://www4.example.com", null, "username two", "password two", "form_field_username", "form_field_password"), new LoginInfo("http://www4.example.com", "http://www4.example.com", null, "", "password three", "form_field_username", "form_field_password"), // Username and passwords fields in forms may have no "name" attribute. new LoginInfo("http://www5.example.com", "http://www5.example.com", null, "multi username", "multi password", "", ""), // Forms with PIN-type authentication will typically have no username. new LoginInfo("http://www6.example.com", "http://www6.example.com", null, "", "12345", "", "form_field_password"), // --- Examples of authentication logins (subdomains of example.org) --- // Simple HTTP authentication login. new LoginInfo("http://www.example.org", null, "The HTTP Realm", "the username", "the password", "", ""), // Simple FTP authentication login. new LoginInfo("ftp://ftp.example.org", null, "ftp://ftp.example.org", "the username", "the password", "", ""), // Multiple HTTP authentication logins can be stored for different realms. new LoginInfo("http://www2.example.org", null, "The HTTP Realm", "the username", "the password", "", ""), new LoginInfo("http://www2.example.org", null, "The HTTP Realm Other", "the username other", "the password other", "", ""), // --- Both form and authentication logins (example.net) --- new LoginInfo("http://example.net", "http://example.net", null, "the username", "the password", "form_field_username", "form_field_password"), new LoginInfo("http://example.net", "http://www.example.net", null, "the username", "the password", "form_field_username", "form_field_password"), new LoginInfo("http://example.net", "http://www.example.net", null, "username two", "the password", "form_field_username", "form_field_password"), new LoginInfo("http://example.net", null, "The HTTP Realm", "the username", "the password", "", ""), new LoginInfo("http://example.net", null, "The HTTP Realm Other", "username two", "the password", "", ""), new LoginInfo("ftp://example.net", null, "ftp://example.net", "the username", "the password", "", ""), // --- Examples of logins added by extensions (chrome scheme) --- new LoginInfo("chrome://example_extension", null, "Example Login One", "the username", "the password one", "", ""), new LoginInfo("chrome://example_extension", null, "Example Login Two", "the username", "the password two", "", ""), ]; }, }; this.LoginTestUtils.recipes = { getRecipeParent() { let { LoginManagerParent } = Cu.import("resource://gre/modules/LoginManagerParent.jsm", {}); if (!LoginManagerParent.recipeParentPromise) { return null; } return LoginManagerParent.recipeParentPromise.then((recipeParent) => { return recipeParent; }); }, }; this.LoginTestUtils.masterPassword = { masterPassword: "omgsecret!", _set(enable) { let oldPW, newPW; if (enable) { oldPW = ""; newPW = this.masterPassword; } else { oldPW = this.masterPassword; newPW = ""; } let secmodDB = Cc["@mozilla.org/security/pkcs11moduledb;1"] .getService(Ci.nsIPKCS11ModuleDB); let slot = secmodDB.findSlotByName(""); if (!slot) { throw new Error("Can't find slot"); } // Set master password. Note that this does not log you in, so the next // invocation of pwmgr can trigger a MP prompt. let pk11db = Cc["@mozilla.org/security/pk11tokendb;1"] .getService(Ci.nsIPK11TokenDB); let token = pk11db.findTokenByName(""); if (slot.status == Ci.nsIPKCS11Slot.SLOT_UNINITIALIZED) { dump("MP initialized to " + newPW + "\n"); token.initPassword(newPW); } else { token.checkPassword(oldPW); dump("MP change from " + oldPW + " to " + newPW + "\n"); token.changePassword(oldPW, newPW); } }, enable() { this._set(true); }, disable() { this._set(false); }, };