Bug 1188456 - Helper to deduplicate password manager logins. r=MattN

This commit is contained in:
Bernardo P. Rittmeyer 2015-07-30 00:46:06 -07:00
parent fb4976aa4c
commit a4456eaf0a
3 changed files with 127 additions and 0 deletions

View File

@ -259,6 +259,43 @@ this.LoginHelper = {
return newLogin;
},
/**
* Removes duplicates from a list of logins.
*
* @param {nsILoginInfo[]} logins
* A list of logins we want to deduplicate.
*
* @param {string[] = ["username", "password"]} uniqueKeys
* A list of login attributes to use as unique keys for the deduplication.
*
* @returns {nsILoginInfo[]} list of unique logins.
*/
dedupeLogins(logins, uniqueKeys = ["username", "password"]) {
const KEY_DELIMITER = ":";
// Generate a unique key string from a login.
function getKey(login, uniqueKeys) {
return uniqueKeys.reduce((prev, key) => prev + KEY_DELIMITER + login[key], "");
}
// We use a Map to easily lookup logins by their unique keys.
let loginsByKeys = new Map();
for (let login of logins) {
let key = getKey(login, uniqueKeys);
// If we find a more recently used login for the same key, replace the existing one.
if (loginsByKeys.has(key)) {
let loginDate = login.QueryInterface(Ci.nsILoginMetaInfo).timeLastUsed;
let storedLoginDate = loginsByKeys.get(key).QueryInterface(Ci.nsILoginMetaInfo).timeLastUsed;
if (loginDate < storedLoginDate) {
continue;
}
}
loginsByKeys.set(key, login);
}
// Return the map values in the form of an array.
return [...loginsByKeys.values()];
},
/**
* Open the password manager window.
*

View File

@ -17,6 +17,7 @@ const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/LoginRecipes.jsm");
Cu.import("resource://gre/modules/LoginHelper.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadPaths",
"resource://gre/modules/DownloadPaths.jsm");

View File

@ -52,6 +52,29 @@ function checkLoginInvalid(aLoginInfo, aExpectedError)
Services.logins.removeLogin(testLogin);
}
/**
* Verifies that two objects are not the same instance
* but have equal attributes.
*
* @param {Object} objectA
* An object to compare.
*
* @param {Object} objectB
* Another object to compare.
*
* @param {string[]} attributes
* Attributes to compare.
*
* @return true if all passed attributes are equal for both objects, false otherwise.
*/
function compareAttributes(objectA, objectB, attributes) {
// If it's the same object, we want to return false.
if (objectA == objectB) {
return false;
}
return attributes.every(attr => objectA[attr] == objectB[attr]);
}
////////////////////////////////////////////////////////////////////////////////
//// Tests
@ -295,3 +318,69 @@ add_task(function test_modifyLogin_nsIProperyBag()
LoginTestUtils.clearData();
});
/**
* Tests the login deduplication function.
*/
add_task(function test_deduplicate_logins() {
// Different key attributes combinations and the amount of unique
// results expected for the TestData login list.
let keyCombinations = [
{
keyset: ["username", "password"],
results: 13,
},
{
keyset: ["hostname", "username"],
results: 17,
},
{
keyset: ["hostname", "username", "password"],
results: 18,
},
{
keyset: ["hostname", "username", "password", "formSubmitURL"],
results: 22,
},
];
let logins = TestData.loginList();
for (let testCase of keyCombinations) {
// Deduplicate the logins using the current testcase keyset.
let deduped = LoginHelper.dedupeLogins(logins, testCase.keyset);
Assert.equal(deduped.length, testCase.results, "Correct amount of results.");
// Checks that every login after deduping is unique.
Assert.ok(deduped.every(loginA =>
deduped.every(loginB => !compareAttributes(loginA, loginB, testCase.keyset))
), "Every login is unique.");
}
});
/**
* Ensure that the login deduplication function keeps the most recent login.
*/
add_task(function test_deduplicate_keeps_most_recent() {
// Logins to deduplicate.
let logins = [
TestData.formLogin({timeLastUsed: Date.UTC(2004, 11, 4, 0, 0, 0)}),
TestData.formLogin({formSubmitURL: "http://example.com", timeLastUsed: Date.UTC(2015, 11, 4, 0, 0, 0)}),
];
// Deduplicate the logins.
let deduped = LoginHelper.dedupeLogins(logins);
Assert.equal(deduped.length, 1, "Deduplicated the logins array.");
// Verify that the remaining login have the most recent date.
let loginTimeLastUsed = deduped[0].QueryInterface(Ci.nsILoginMetaInfo).timeLastUsed;
Assert.equal(loginTimeLastUsed, Date.UTC(2015, 11, 4, 0, 0, 0), "Most recent login was kept.");
// Deduplicate the reverse logins array.
deduped = LoginHelper.dedupeLogins(logins.reverse());
Assert.equal(deduped.length, 1, "Deduplicated the reversed logins array.");
// Verify that the remaining login have the most recent date.
loginTimeLastUsed = deduped[0].QueryInterface(Ci.nsILoginMetaInfo).timeLastUsed;
Assert.equal(loginTimeLastUsed, Date.UTC(2015, 11, 4, 0, 0, 0), "Most recent login was kept.");
});