mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1188456 - Helper to deduplicate password manager logins. r=MattN
This commit is contained in:
parent
fb4976aa4c
commit
a4456eaf0a
@ -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.
|
||||
*
|
||||
|
@ -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");
|
||||
|
@ -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.");
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user