mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-06-06 00:19:08 +00:00
377 lines
14 KiB
JavaScript
377 lines
14 KiB
JavaScript
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
|
/* vim: set ts=2 et sw=2 tw=80: */
|
|
/* Any copyright is dedicated to the Public Domain.
|
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
/**
|
|
* Provides infrastructure for automated login components tests.
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//// Globals
|
|
|
|
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "DownloadPaths",
|
|
"resource://gre/modules/DownloadPaths.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
|
|
"resource://gre/modules/FileUtils.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "OS",
|
|
"resource://gre/modules/osfile.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
|
"resource://gre/modules/Promise.jsm");
|
|
|
|
const LoginInfo =
|
|
Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
|
|
"nsILoginInfo", "init");
|
|
|
|
/**
|
|
* All the tests are implemented with add_task, this starts them automatically.
|
|
*/
|
|
function run_test()
|
|
{
|
|
do_get_profile();
|
|
run_next_test();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//// Global helpers
|
|
|
|
// Some of these functions are already implemented in other parts of the source
|
|
// tree, see bug 946708 about sharing more code.
|
|
|
|
// While the previous test file should have deleted all the temporary files it
|
|
// used, on Windows these might still be pending deletion on the physical file
|
|
// system. Thus, start from a new base number every time, to make a collision
|
|
// with a file that is still pending deletion highly unlikely.
|
|
let gFileCounter = Math.floor(Math.random() * 1000000);
|
|
|
|
/**
|
|
* Returns a reference to a temporary file, that is guaranteed not to exist, and
|
|
* to have never been created before.
|
|
*
|
|
* @param aLeafName
|
|
* Suggested leaf name for the file to be created.
|
|
*
|
|
* @return nsIFile pointing to a non-existent file in a temporary directory.
|
|
*
|
|
* @note It is not enough to delete the file if it exists, or to delete the file
|
|
* after calling nsIFile.createUnique, because on Windows the delete
|
|
* operation in the file system may still be pending, preventing a new
|
|
* file with the same name to be created.
|
|
*/
|
|
function getTempFile(aLeafName)
|
|
{
|
|
// Prepend a serial number to the extension in the suggested leaf name.
|
|
let [base, ext] = DownloadPaths.splitBaseNameAndExtension(aLeafName);
|
|
let leafName = base + "-" + gFileCounter + ext;
|
|
gFileCounter++;
|
|
|
|
// Get a file reference under the temporary directory for this test file.
|
|
let file = FileUtils.getFile("TmpD", [leafName]);
|
|
do_check_false(file.exists());
|
|
|
|
do_register_cleanup(function () {
|
|
if (file.exists()) {
|
|
file.remove(false);
|
|
}
|
|
});
|
|
|
|
return file;
|
|
}
|
|
|
|
/**
|
|
* Allows waiting for an observer notification once.
|
|
*
|
|
* @param aTopic
|
|
* Notification topic to observe.
|
|
*
|
|
* @return {Promise}
|
|
* @resolves The array [aSubject, aData] from the observed notification.
|
|
* @rejects Never.
|
|
*/
|
|
function promiseTopicObserved(aTopic)
|
|
{
|
|
let deferred = Promise.defer();
|
|
|
|
Services.obs.addObserver(
|
|
function PTO_observe(aSubject, aTopic, aData) {
|
|
Services.obs.removeObserver(PTO_observe, aTopic);
|
|
deferred.resolve([aSubject, aData]);
|
|
}, aTopic, false);
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
/**
|
|
* Returns a new XPCOM property bag with the provided properties.
|
|
*
|
|
* @param aProperties
|
|
* Each property of this object is copied to the property bag. This
|
|
* parameter can be omitted to return an empty property bag.
|
|
*
|
|
* @return A new property bag, that is an instance of nsIWritablePropertyBag,
|
|
* nsIWritablePropertyBag2, nsIPropertyBag, and nsIPropertyBag2.
|
|
*/
|
|
function newPropertyBag(aProperties)
|
|
{
|
|
let propertyBag = Cc["@mozilla.org/hash-property-bag;1"]
|
|
.createInstance(Ci.nsIWritablePropertyBag);
|
|
if (aProperties) {
|
|
for (let [name, value] of Iterator(aProperties)) {
|
|
propertyBag.setProperty(name, value);
|
|
}
|
|
}
|
|
return propertyBag.QueryInterface(Ci.nsIPropertyBag)
|
|
.QueryInterface(Ci.nsIPropertyBag2)
|
|
.QueryInterface(Ci.nsIWritablePropertyBag2);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//// Local helpers
|
|
|
|
const LoginTest = {
|
|
/**
|
|
* Forces the storage module to save all data, and the Login Manager service
|
|
* to replace the storage module with a newly initialized instance.
|
|
*/
|
|
reloadData: function ()
|
|
{
|
|
Services.obs.notifyObservers(null, "passwordmgr-storage-replace", null);
|
|
yield promiseTopicObserved("passwordmgr-storage-replace-complete");
|
|
},
|
|
|
|
/**
|
|
* Erases all the data stored by the Login Manager service.
|
|
*/
|
|
clearData: function ()
|
|
{
|
|
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: function (aExpectedLogins)
|
|
{
|
|
this.assertLoginListsEqual(Services.logins.getAllLogins(), aExpectedLogins);
|
|
},
|
|
|
|
/**
|
|
* Checks that the two provided arrays of nsILoginInfo have the same length,
|
|
* and every login in aExpectedLogins is also found in aActualLogins. The
|
|
* comparison uses the "equals" method of nsILoginInfo, that does not include
|
|
* nsILoginMetaInfo properties in the test.
|
|
*/
|
|
assertLoginListsEqual: function (aActual, aExpected)
|
|
{
|
|
do_check_eq(aExpected.length, aActual.length);
|
|
do_check_true(aExpected.every(e => aActual.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: function (aActual, aExpected)
|
|
{
|
|
Assert.deepEqual(aActual.sort(), aExpected.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: function (aTimeMs)
|
|
{
|
|
do_check_true(Math.abs(aTimeMs - Date.now()) < 30000);
|
|
}
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//// Predefined test data
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
const TestData = {
|
|
/**
|
|
* Returns a new nsILoginInfo for use with form submits.
|
|
*
|
|
* @param aModifications
|
|
* Each property of this object replaces the property of the same name
|
|
* in the returned nsILoginInfo or nsILoginMetaInfo.
|
|
*/
|
|
formLogin: function (aModifications)
|
|
{
|
|
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 (aModifications) {
|
|
for (let [name, value] of Iterator(aModifications)) {
|
|
loginInfo[name] = value;
|
|
}
|
|
}
|
|
return loginInfo;
|
|
},
|
|
|
|
/**
|
|
* Returns a new nsILoginInfo for use with HTTP authentication.
|
|
*
|
|
* @param aModifications
|
|
* Each property of this object replaces the property of the same name
|
|
* in the returned nsILoginInfo or nsILoginMetaInfo.
|
|
*/
|
|
authLogin: function (aModifications)
|
|
{
|
|
let loginInfo = new LoginInfo("http://www.example.org", null,
|
|
"The HTTP Realm", "the username",
|
|
"the password", "", "");
|
|
loginInfo.QueryInterface(Ci.nsILoginMetaInfo);
|
|
if (aModifications) {
|
|
for (let [name, value] of Iterator(aModifications)) {
|
|
loginInfo[name] = value;
|
|
}
|
|
}
|
|
return loginInfo;
|
|
},
|
|
|
|
/**
|
|
* Returns an array of typical nsILoginInfo that could be stored in the
|
|
* database.
|
|
*/
|
|
loginList: function ()
|
|
{
|
|
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", "", ""),
|
|
];
|
|
},
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//// Initialization functions common to all tests
|
|
|
|
add_task(function test_common_initialize()
|
|
{
|
|
// Before initializing the service for the first time, we should copy the key
|
|
// file required to decrypt the logins contained in the SQLite databases used
|
|
// by migration tests. This file is not required for the other tests.
|
|
yield OS.File.copy(do_get_file("data/key3.db").path,
|
|
OS.Path.join(OS.Constants.Path.profileDir, "key3.db"));
|
|
|
|
// Ensure that the service and the storage module are initialized.
|
|
yield Services.logins.initializationPromise;
|
|
|
|
// Ensure that every test file starts with an empty database.
|
|
LoginTest.clearData();
|
|
|
|
// Clean up after every test.
|
|
do_register_cleanup(() => LoginTest.clearData());
|
|
});
|