mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 23:31:56 +00:00
Bug 589628 - Password manager should support subdomains with the same password. r=MattN
Completed by Sam Foster. Differential Revision: https://phabricator.services.mozilla.com/D26389 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
967bc2a754
commit
7a0d2657fd
@ -1748,6 +1748,8 @@ pref("signon.schemeUpgrades", true);
|
||||
pref("signon.privateBrowsingCapture.enabled", true);
|
||||
pref("signon.showAutoCompleteFooter", true);
|
||||
pref("signon.management.page.enabled", false);
|
||||
pref("signon.showAutoCompleteOrigins", true);
|
||||
pref("signon.includeOtherSubdomainsInLookup", true);
|
||||
|
||||
// Enable the "Simplify Page" feature in Print Preview. This feature
|
||||
// is disabled by default in toolkit.
|
||||
|
@ -4732,6 +4732,7 @@ pref("signon.storeWhenAutocompleteOff", true);
|
||||
pref("signon.debug", false);
|
||||
pref("signon.recipes.path", "chrome://passwordmgr/content/recipes.json");
|
||||
pref("signon.schemeUpgrades", false);
|
||||
pref("signon.includeOtherSubdomainsInLookup", false);
|
||||
// This temporarily prevents the master password to reprompt for autocomplete.
|
||||
pref("signon.masterPasswordReprompt.timeout_ms", 900000); // 15 Minutes
|
||||
pref("signon.showAutoCompleteFooter", false);
|
||||
|
@ -253,6 +253,7 @@ var LoginHelper = {
|
||||
isOriginMatching(aLoginOrigin, aSearchOrigin, aOptions = {
|
||||
schemeUpgrades: false,
|
||||
acceptWildcardMatch: false,
|
||||
acceptDifferentSubdomains: false,
|
||||
}) {
|
||||
if (aLoginOrigin == aSearchOrigin) {
|
||||
return true;
|
||||
@ -266,18 +267,30 @@ var LoginHelper = {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (aOptions.schemeUpgrades) {
|
||||
try {
|
||||
let loginURI = Services.io.newURI(aLoginOrigin);
|
||||
let searchURI = Services.io.newURI(aSearchOrigin);
|
||||
if (loginURI.scheme == "http" && searchURI.scheme == "https" &&
|
||||
loginURI.hostPort == searchURI.hostPort) {
|
||||
try {
|
||||
let loginURI = Services.io.newURI(aLoginOrigin);
|
||||
let searchURI = Services.io.newURI(aSearchOrigin);
|
||||
let schemeMatches = loginURI.scheme == "http" && searchURI.scheme == "https";
|
||||
|
||||
if (aOptions.acceptDifferentSubdomains) {
|
||||
let loginBaseDomain = Services.eTLD.getBaseDomain(loginURI);
|
||||
let searchBaseDomain = Services.eTLD.getBaseDomain(searchURI);
|
||||
if (loginBaseDomain == searchBaseDomain &&
|
||||
(loginURI.scheme == searchURI.scheme ||
|
||||
(aOptions.schemeUpgrades && schemeMatches))) {
|
||||
return true;
|
||||
}
|
||||
} catch (ex) {
|
||||
// newURI will throw for some values e.g. chrome://FirefoxAccounts
|
||||
return false;
|
||||
}
|
||||
|
||||
if (aOptions.schemeUpgrades &&
|
||||
loginURI.host == searchURI.host &&
|
||||
schemeMatches && loginURI.port == searchURI.port) {
|
||||
return true;
|
||||
}
|
||||
} catch (ex) {
|
||||
// newURI will throw for some values e.g. chrome://FirefoxAccounts
|
||||
// uri.host and uri.port will throw for some values e.g. javascript:
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -447,6 +460,59 @@ var LoginHelper = {
|
||||
return newLogin;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove http: logins when there is an https: login with the same username and hostPort.
|
||||
* Sort order is preserved.
|
||||
*
|
||||
* @param {nsILoginInfo[]} logins
|
||||
* A list of logins we want to process for shadowing.
|
||||
* @returns {nsILoginInfo[]} A subset of of the passed logins.
|
||||
*/
|
||||
shadowHTTPLogins(logins) {
|
||||
/**
|
||||
* Map a (hostPort, username) to a boolean indicating whether `logins`
|
||||
* contains an https: login for that combo.
|
||||
*/
|
||||
let hasHTTPSByHostPortUsername = new Map();
|
||||
for (let login of logins) {
|
||||
let key = this.getUniqueKeyForLogin(login, ["hostPort", "username"]);
|
||||
let hasHTTPSlogin = hasHTTPSByHostPortUsername.get(key) || false;
|
||||
let loginURI = Services.io.newURI(login.hostname);
|
||||
hasHTTPSByHostPortUsername.set(key, loginURI.scheme == "https" || hasHTTPSlogin);
|
||||
}
|
||||
|
||||
return logins.filter((login) => {
|
||||
let key = this.getUniqueKeyForLogin(login, ["hostPort", "username"]);
|
||||
let loginURI = Services.io.newURI(login.hostname);
|
||||
if (loginURI.scheme == "http" && hasHTTPSByHostPortUsername.get(key)) {
|
||||
// If this is an http: login and we have an https: login for the
|
||||
// (hostPort, username) combo then remove it.
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate a unique key string from a login.
|
||||
* @param {nsILoginInfo} login
|
||||
* @param {string[]} uniqueKeys containing nsILoginInfo attribute names or "hostPort"
|
||||
* @returns {string} to use as a key in a Map
|
||||
*/
|
||||
getUniqueKeyForLogin(login, uniqueKeys) {
|
||||
const KEY_DELIMITER = ":";
|
||||
return uniqueKeys.reduce((prev, key) => {
|
||||
let val = null;
|
||||
if (key == "hostPort") {
|
||||
val = Services.io.newURI(login.hostname).hostPort;
|
||||
} else {
|
||||
val = login[key];
|
||||
}
|
||||
|
||||
return prev + KEY_DELIMITER + val;
|
||||
}, "");
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes duplicates from a list of logins while preserving the sort order.
|
||||
*
|
||||
@ -476,11 +542,15 @@ var LoginHelper = {
|
||||
resolveBy = ["timeLastUsed"],
|
||||
preferredOrigin = undefined,
|
||||
preferredFormActionOrigin = undefined) {
|
||||
const KEY_DELIMITER = ":";
|
||||
|
||||
if (!preferredOrigin && resolveBy.includes("scheme")) {
|
||||
throw new Error("dedupeLogins: `preferredOrigin` is required in order to " +
|
||||
"prefer schemes which match it.");
|
||||
if (!preferredOrigin) {
|
||||
if (resolveBy.includes("scheme")) {
|
||||
throw new Error("dedupeLogins: `preferredOrigin` is required in order to " +
|
||||
"prefer schemes which match it.");
|
||||
}
|
||||
if (resolveBy.includes("subdomain")) {
|
||||
throw new Error("dedupeLogins: `preferredOrigin` is required in order to " +
|
||||
"prefer subdomains which match it.");
|
||||
}
|
||||
}
|
||||
|
||||
let preferredOriginScheme;
|
||||
@ -500,11 +570,6 @@ var LoginHelper = {
|
||||
// We use a Map to easily lookup logins by their unique keys.
|
||||
let loginsByKeys = new Map();
|
||||
|
||||
// Generate a unique key string from a login.
|
||||
function getKey(login, uniqueKeys) {
|
||||
return uniqueKeys.reduce((prev, key) => prev + KEY_DELIMITER + login[key], "");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {bool} whether `login` is preferred over its duplicate (considering `uniqueKeys`)
|
||||
* `existingLogin`.
|
||||
@ -556,6 +621,21 @@ var LoginHelper = {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "subdomain": {
|
||||
// Replace the existing login only if the new login is an exact match on the host.
|
||||
let existingLoginURI = Services.io.newURI(existingLogin.hostname);
|
||||
let newLoginURI = Services.io.newURI(login.hostname);
|
||||
let preferredOriginURI = Services.io.newURI(preferredOrigin);
|
||||
if (existingLoginURI.hostPort != preferredOriginURI.hostPort &&
|
||||
newLoginURI.hostPort == preferredOriginURI.hostPort) {
|
||||
return true;
|
||||
}
|
||||
if (existingLoginURI.host != preferredOriginURI.host &&
|
||||
newLoginURI.host == preferredOriginURI.host) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "timeLastUsed":
|
||||
case "timePasswordChanged": {
|
||||
// If we find a more recent login for the same key, replace the existing one.
|
||||
@ -577,7 +657,7 @@ var LoginHelper = {
|
||||
}
|
||||
|
||||
for (let login of logins) {
|
||||
let key = getKey(login, uniqueKeys);
|
||||
let key = this.getUniqueKeyForLogin(login, uniqueKeys);
|
||||
|
||||
if (loginsByKeys.has(key)) {
|
||||
if (!isLoginPreferred(loginsByKeys.get(key), login)) {
|
||||
|
@ -1332,16 +1332,38 @@ var LoginManagerContent = {
|
||||
}
|
||||
|
||||
if (!userTriggered) {
|
||||
// Only autofill logins that match the form's action. In the above code
|
||||
// Only autofill logins that match the form's action and hostname. In the above code
|
||||
// we have attached autocomplete for logins that don't match the form action.
|
||||
let loginOrigin = LoginHelper.getLoginOrigin(form.ownerDocument.documentURI);
|
||||
let formActionOrigin = LoginHelper.getFormActionOrigin(form);
|
||||
foundLogins = foundLogins.filter(l => {
|
||||
return LoginHelper.isOriginMatching(l.formSubmitURL,
|
||||
LoginHelper.getFormActionOrigin(form),
|
||||
{
|
||||
schemeUpgrades: LoginHelper.schemeUpgrades,
|
||||
acceptWildcardMatch: true,
|
||||
});
|
||||
let formActionMatches = LoginHelper.isOriginMatching(l.formSubmitURL,
|
||||
formActionOrigin,
|
||||
{
|
||||
schemeUpgrades: LoginHelper.schemeUpgrades,
|
||||
acceptWildcardMatch: true,
|
||||
acceptDifferentSubdomains: false,
|
||||
});
|
||||
let formOriginMatches = LoginHelper.isOriginMatching(l.hostname,
|
||||
loginOrigin,
|
||||
{
|
||||
schemeUpgrades: LoginHelper.schemeUpgrades,
|
||||
acceptWildcardMatch: true,
|
||||
acceptDifferentSubdomains: false,
|
||||
});
|
||||
return formActionMatches && formOriginMatches;
|
||||
});
|
||||
|
||||
// Since the logins are already filtered now to only match the origin and formAction,
|
||||
// dedupe to just the username since remaining logins may have different schemes.
|
||||
foundLogins = LoginHelper.dedupeLogins(foundLogins,
|
||||
["username"],
|
||||
[
|
||||
"scheme",
|
||||
"timePasswordChanged",
|
||||
],
|
||||
loginOrigin,
|
||||
formActionOrigin);
|
||||
}
|
||||
|
||||
// Nothing to do if we have no matching logins available.
|
||||
|
@ -25,6 +25,9 @@ XPCOMUtils.defineLazyGetter(this, "log", () => {
|
||||
return logger.log.bind(logger);
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(this, "INCLUDE_OTHER_SUBDOMAINS_IN_LOOKUP",
|
||||
"signon.includeOtherSubdomainsInLookup", false);
|
||||
|
||||
var EXPORTED_SYMBOLS = [ "LoginManagerParent" ];
|
||||
|
||||
var LoginManagerParent = {
|
||||
@ -51,11 +54,17 @@ var LoginManagerParent = {
|
||||
// to avoid spamming master password prompts on autocomplete searches.
|
||||
_lastMPLoginCancelled: Math.NEGATIVE_INFINITY,
|
||||
|
||||
_searchAndDedupeLogins(formOrigin, actionOrigin, {looseActionOriginMatch} = {}) {
|
||||
_searchAndDedupeLogins(formOrigin,
|
||||
actionOrigin,
|
||||
{
|
||||
looseActionOriginMatch,
|
||||
acceptDifferentSubdomains,
|
||||
} = {}) {
|
||||
let logins;
|
||||
let matchData = {
|
||||
hostname: formOrigin,
|
||||
schemeUpgrades: LoginHelper.schemeUpgrades,
|
||||
acceptDifferentSubdomains,
|
||||
};
|
||||
if (!looseActionOriginMatch) {
|
||||
matchData.formSubmitURL = actionOrigin;
|
||||
@ -73,13 +82,15 @@ var LoginManagerParent = {
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Dedupe so the length checks below still make sense with scheme upgrades.
|
||||
logins = LoginHelper.shadowHTTPLogins(logins);
|
||||
|
||||
let resolveBy = [
|
||||
"actionOrigin",
|
||||
"scheme",
|
||||
"subdomain",
|
||||
"timePasswordChanged",
|
||||
];
|
||||
return LoginHelper.dedupeLogins(logins, ["username"], resolveBy, formOrigin, actionOrigin);
|
||||
return LoginHelper.dedupeLogins(logins, ["username", "password"], resolveBy, formOrigin, actionOrigin);
|
||||
},
|
||||
|
||||
// Listeners are added in BrowserGlue.jsm on desktop
|
||||
@ -241,8 +252,13 @@ var LoginManagerParent = {
|
||||
return;
|
||||
}
|
||||
|
||||
// Autocomplete results do not need to match actionOrigin.
|
||||
let logins = this._searchAndDedupeLogins(formOrigin, actionOrigin, {looseActionOriginMatch: true});
|
||||
// Autocomplete results do not need to match actionOrigin or exact hostname.
|
||||
let logins = this._searchAndDedupeLogins(formOrigin,
|
||||
actionOrigin,
|
||||
{
|
||||
looseActionOriginMatch: true,
|
||||
acceptDifferentSubdomains: INCLUDE_OTHER_SUBDOMAINS_IN_LOOKUP,
|
||||
});
|
||||
|
||||
log("sendLoginDataToChild:", logins.length, "deduped logins");
|
||||
// Convert the array of nsILoginInfo to vanilla JS objects since nsILoginInfo
|
||||
@ -297,8 +313,13 @@ var LoginManagerParent = {
|
||||
} else {
|
||||
log("Creating new autocomplete search result.");
|
||||
|
||||
// Autocomplete results do not need to match actionOrigin.
|
||||
logins = this._searchAndDedupeLogins(formOrigin, actionOrigin, {looseActionOriginMatch: true});
|
||||
// Autocomplete results do not need to match actionOrigin or exact hostname.
|
||||
logins = this._searchAndDedupeLogins(formOrigin,
|
||||
actionOrigin,
|
||||
{
|
||||
looseActionOriginMatch: true,
|
||||
acceptDifferentSubdomains: INCLUDE_OTHER_SUBDOMAINS_IN_LOOKUP,
|
||||
});
|
||||
}
|
||||
|
||||
let matchingLogins = logins.filter(function(fullMatch) {
|
||||
|
@ -271,6 +271,7 @@ this.LoginManagerStorage_json.prototype = {
|
||||
for (let prop of matchData.enumerator) {
|
||||
switch (prop.name) {
|
||||
// Some property names aren't field names but are special options to affect the search.
|
||||
case "acceptDifferentSubdomains":
|
||||
case "schemeUpgrades": {
|
||||
options[prop.name] = prop.value;
|
||||
break;
|
||||
@ -300,6 +301,7 @@ this.LoginManagerStorage_json.prototype = {
|
||||
*/
|
||||
_searchLogins(matchData, aOptions = {
|
||||
schemeUpgrades: false,
|
||||
acceptDifferentSubdomains: false,
|
||||
}) {
|
||||
this._store.ensureDataReady();
|
||||
|
||||
|
@ -420,6 +420,7 @@ LoginManagerStorage_mozStorage.prototype = {
|
||||
for (let prop of matchData.enumerator) {
|
||||
switch (prop.name) {
|
||||
// Some property names aren't field names but are special options to affect the search.
|
||||
case "acceptDifferentSubdomains":
|
||||
case "schemeUpgrades": {
|
||||
options[prop.name] = prop.value;
|
||||
break;
|
||||
@ -450,6 +451,7 @@ LoginManagerStorage_mozStorage.prototype = {
|
||||
*/
|
||||
_searchLogins(matchData, aOptions = {
|
||||
schemeUpgrades: false,
|
||||
acceptDifferentSubdomains: false,
|
||||
}) {
|
||||
let conditions = [], params = {};
|
||||
|
||||
|
@ -16,6 +16,7 @@ support-files =
|
||||
../browser/form_basic.html
|
||||
../browser/formless_basic.html
|
||||
../browser/form_cross_origin_secure_action.html
|
||||
../browser/form_same_origin_action.html
|
||||
auth2/authenticate.sjs
|
||||
pwmgr_common.js
|
||||
pwmgr_common_parent.js
|
||||
@ -47,12 +48,17 @@ skip-if = toolkit == 'android' # bug 1533965
|
||||
[test_autofill_different_formSubmitURL.html]
|
||||
scheme = https
|
||||
skip-if = toolkit == 'android' # Bug 1259768
|
||||
[test_autofill_different_subdomain.html]
|
||||
scheme = https
|
||||
skip-if = toolkit == 'android' # Bug 1259768
|
||||
[test_autofill_from_bfcache.html]
|
||||
scheme = https
|
||||
skip-if = toolkit == 'android' # bug 1527403
|
||||
[test_autofill_highlight.html]
|
||||
scheme = https
|
||||
skip-if = toolkit == 'android' # Bug 1531185
|
||||
[test_autofill_https_downgrade.html]
|
||||
scheme = http # we need http to test handling of https logins on http forms
|
||||
[test_autofill_https_upgrade.html]
|
||||
skip-if = toolkit == 'android' # Bug 1259768
|
||||
[test_autofill_sandboxed.html]
|
||||
@ -71,6 +77,9 @@ skip-if = toolkit == 'android' # autocomplete
|
||||
[test_basic_form_autocomplete.html]
|
||||
skip-if = toolkit == 'android' || (os == 'linux' && debug) # android:autocomplete, linux: bug 1538955
|
||||
scheme = https
|
||||
[test_basic_form_autocomplete_subdomain.html]
|
||||
skip-if = toolkit == 'android' # android:autocomplete.
|
||||
scheme = https
|
||||
[test_basic_form_autocomplete_formSubmitURL.html]
|
||||
skip-if = toolkit == 'android' # android:autocomplete.
|
||||
scheme = https
|
||||
|
@ -29,7 +29,7 @@ runInParent(function addLogins() {
|
||||
|
||||
// Same as above but HTTP instead of HTTPS (to test de-duping)
|
||||
let login2 = new nsLoginInfo("http://example.org", "http://example.org", null,
|
||||
"name1", "passHTTP", "uname", "pword");
|
||||
"name1", "pass1", "uname", "pword");
|
||||
|
||||
// Different HTTP login to upgrade with secure formSubmitURL
|
||||
let login3 = new nsLoginInfo("http://example.org", "https://example.org", null,
|
||||
@ -67,10 +67,13 @@ function restoreForm() {
|
||||
uname.focus();
|
||||
}
|
||||
|
||||
add_task(async function setup() {
|
||||
const HTTPS_FORM_URL = "https://example.org/tests/toolkit/components/passwordmgr/test/mochitest/form_basic.html";
|
||||
const HTTP_FORM_URL = "http://example.org/tests/toolkit/components/passwordmgr/test/mochitest/form_basic.html";
|
||||
|
||||
async function setup(formUrl = HTTPS_FORM_URL) {
|
||||
await SpecialPowers.pushPrefEnv({"set": [["signon.schemeUpgrades", true]]});
|
||||
|
||||
iframe.src = "https://example.org/tests/toolkit/components/passwordmgr/test/mochitest/form_basic.html";
|
||||
iframe.src = formUrl;
|
||||
await new Promise(resolve => {
|
||||
iframe.addEventListener("load", function() {
|
||||
resolve();
|
||||
@ -81,6 +84,33 @@ add_task(async function setup() {
|
||||
hostname = iframeDoc.documentURIObject.host;
|
||||
uname = iframeDoc.getElementById("form-basic-username");
|
||||
pword = iframeDoc.getElementById("form-basic-password");
|
||||
}
|
||||
|
||||
add_task(async function test_autocomplete_http() {
|
||||
info("test_autocomplete_http, setup with " + HTTP_FORM_URL);
|
||||
await setup(HTTP_FORM_URL);
|
||||
|
||||
let logins = LoginManager.getAllLogins();
|
||||
info("got logins: " + logins.map(l => l.hostname));
|
||||
// from a HTTP page, look for matching logins, we should never offer a login with an HTTPS scheme
|
||||
// we're expecting just login2 as a match
|
||||
// Make sure initial form is empty.
|
||||
checkLoginForm(uname, "", pword, "");
|
||||
// Trigger autocomplete popup
|
||||
restoreForm();
|
||||
let popupState = await getPopupState();
|
||||
is(popupState.open, false, "Check popup is initially closed");
|
||||
let shownPromise = promiseACShown();
|
||||
synthesizeKey("KEY_ArrowDown");
|
||||
let results = await shownPromise;
|
||||
info("got results: " + results.join(", "));
|
||||
popupState = await getPopupState();
|
||||
is(popupState.selectedIndex, -1, "Check no entries are selected");
|
||||
checkAutoCompleteResults(results, ["name1"], "http://example.org", "initial");
|
||||
});
|
||||
|
||||
add_task(async function https_setup() {
|
||||
await setup(HTTPS_FORM_URL);
|
||||
});
|
||||
|
||||
add_task(async function test_empty_first_entry() {
|
||||
|
@ -0,0 +1,104 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test autofill on an HTTPS page using logins with different eTLD+1</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/AddTask.js"></script>
|
||||
<script type="text/javascript" src="pwmgr_common.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
const MISSING_ACTION_PATH = TESTS_DIR + "mochitest/form_basic.html";
|
||||
|
||||
const chromeScript = runChecksAfterCommonInit(false);
|
||||
|
||||
let nsLoginInfo = SpecialPowers.wrap(SpecialPowers.Components).Constructor("@mozilla.org/login-manager/loginInfo;1",
|
||||
SpecialPowers.Ci.nsILoginInfo,
|
||||
"init");
|
||||
</script>
|
||||
<p id="display"></p>
|
||||
|
||||
<!-- we presumably can't hide the content for this test. -->
|
||||
<div id="content">
|
||||
<iframe></iframe>
|
||||
</div>
|
||||
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
let iframe = SpecialPowers.wrap(document.getElementsByTagName("iframe")[0]);
|
||||
|
||||
function checkIframeLoginForm(expectedUsername, expectedPassword) {
|
||||
let iframeDoc = iframe.contentDocument;
|
||||
let uname = iframeDoc.getElementById("form-basic-username");
|
||||
let pword = iframeDoc.getElementById("form-basic-password");
|
||||
checkLoginForm(uname, expectedUsername, pword, expectedPassword);
|
||||
}
|
||||
async function prepareLoginsAndProcessForm(url, logins = []) {
|
||||
LoginManager.removeAllLogins();
|
||||
|
||||
let dates = Date.now();
|
||||
for (let login of logins) {
|
||||
SpecialPowers.do_QueryInterface(login, SpecialPowers.Ci.nsILoginMetaInfo);
|
||||
// Force all dates to be the same so they don't affect things like deduping.
|
||||
login.timeCreated = login.timePasswordChanged = login.timeLastUsed = dates;
|
||||
LoginManager.addLogin(login);
|
||||
}
|
||||
|
||||
iframe.src = url;
|
||||
await promiseFormsProcessed();
|
||||
}
|
||||
|
||||
add_task(async function test_login_with_different_subdomain_shouldnt_autofill_wildcard_formSubmitURL() {
|
||||
await prepareLoginsAndProcessForm("https://example.com" + MISSING_ACTION_PATH, [
|
||||
new nsLoginInfo("https://foobar.example.com", "", null,
|
||||
"name2", "pass2", "uname", "pword"),
|
||||
]);
|
||||
|
||||
checkIframeLoginForm("", "");
|
||||
});
|
||||
|
||||
add_task(async function test_login_with_different_subdomain_shouldnt_autofill_same_domain_formSubmitURL() {
|
||||
await prepareLoginsAndProcessForm("https://example.com" + MISSING_ACTION_PATH, [
|
||||
new nsLoginInfo("https://foobar.example.com", "https://example.com", null,
|
||||
"name2", "pass2", "uname", "pword"),
|
||||
]);
|
||||
|
||||
checkIframeLoginForm("", "");
|
||||
});
|
||||
|
||||
add_task(async function test_matching_logins_with_different_subdomain_and_matching_domain_should_autofill() {
|
||||
await prepareLoginsAndProcessForm("https://example.com" + MISSING_ACTION_PATH, [
|
||||
new nsLoginInfo("https://example.com", "https://example.com", null,
|
||||
"name2", "pass2", "uname", "pword"),
|
||||
new nsLoginInfo("https://old.example.com", "https://example.com", null,
|
||||
"name2", "pass2", "uname", "pword"),
|
||||
]);
|
||||
|
||||
checkIframeLoginForm("name2", "pass2");
|
||||
});
|
||||
|
||||
add_task(async function test_login_with_different_subdomain_shouldnt_autofill_different_subdomain_formSubmitURL() {
|
||||
await prepareLoginsAndProcessForm("https://example.com" + MISSING_ACTION_PATH, [
|
||||
new nsLoginInfo("https://foobar.example.com", "https://foobar.example.com", null,
|
||||
"name2", "pass2", "uname", "pword"),
|
||||
]);
|
||||
|
||||
checkIframeLoginForm("", "");
|
||||
});
|
||||
|
||||
add_task(async function test_login_with_different_subdomain_shouldnt_autofill_different_domain_formSubmitURL() {
|
||||
await prepareLoginsAndProcessForm("https://example.com" + MISSING_ACTION_PATH, [
|
||||
new nsLoginInfo("https://foobar.example.com", "https://another.domain", null,
|
||||
"name2", "pass2", "uname", "pword"),
|
||||
]);
|
||||
|
||||
checkIframeLoginForm("", "");
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,112 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test we don't autofill on an HTTP page using HTTPS logins</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script type="text/javascript" src="pwmgr_common.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
const MISSING_ACTION_PATH = TESTS_DIR + "mochitest/form_basic.html";
|
||||
const SAME_ORIGIN_ACTION_PATH = TESTS_DIR + "mochitest/form_same_origin_action.html";
|
||||
|
||||
const chromeScript = runChecksAfterCommonInit(false);
|
||||
|
||||
let nsLoginInfo = SpecialPowers.wrap(SpecialPowers.Components).Constructor("@mozilla.org/login-manager/loginInfo;1",
|
||||
SpecialPowers.Ci.nsILoginInfo,
|
||||
"init");
|
||||
</script>
|
||||
<p id="display"></p>
|
||||
|
||||
<!-- we presumably can't hide the content for this test. -->
|
||||
<div id="content">
|
||||
<iframe></iframe>
|
||||
</div>
|
||||
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
let iframe = SpecialPowers.wrap(document.getElementsByTagName("iframe")[0]);
|
||||
|
||||
async function prepareAndProcessForm(url, login) {
|
||||
iframe.src = url;
|
||||
info("prepareAndProcessForm, assigned iframe.src: " + url);
|
||||
await promiseFormsProcessed();
|
||||
}
|
||||
|
||||
async function checkFormsWithLogin(formUrls, login, expectedUsername, expectedPassword) {
|
||||
LoginManager.removeAllLogins();
|
||||
LoginManager.addLogin(login);
|
||||
|
||||
for (let url of formUrls) {
|
||||
info("start test_checkNoAutofillOnDowngrade w. url: " + url);
|
||||
|
||||
await prepareAndProcessForm(url);
|
||||
info("form was processed");
|
||||
let iframeDoc = iframe.contentDocument;
|
||||
let uname = iframeDoc.getElementById("form-basic-username");
|
||||
let pword = iframeDoc.getElementById("form-basic-password");
|
||||
info("checking form, uname: " + uname.value);
|
||||
is(uname.value, expectedUsername, `username ${expectedUsername ? "filled" : "not filled"} on ${url}`);
|
||||
is(pword.value, expectedPassword, `password ${expectedPassword ? "filled" : "not filled"} on ${url}`);
|
||||
}
|
||||
}
|
||||
|
||||
add_task(async function setup() {
|
||||
await SpecialPowers.pushPrefEnv({"set": [["signon.schemeUpgrades", true]]});
|
||||
});
|
||||
|
||||
add_task(async function test_sanityCheckHTTPS() {
|
||||
let login = new nsLoginInfo("https://example.com", "https://example.com", null,
|
||||
"name1", "pass1", "uname", "pword");
|
||||
|
||||
await checkFormsWithLogin([
|
||||
`https://example.com${MISSING_ACTION_PATH}`,
|
||||
`https://example.com${SAME_ORIGIN_ACTION_PATH}`,
|
||||
], login, "name1", "pass1");
|
||||
});
|
||||
|
||||
add_task(async function test_checkNoAutofillOnDowngrade() {
|
||||
let login = new nsLoginInfo("https://example.com", "https://example.com", null,
|
||||
"name1", "pass1", "uname", "pword");
|
||||
await checkFormsWithLogin([
|
||||
`http://example.com${MISSING_ACTION_PATH}`,
|
||||
`http://example.com${SAME_ORIGIN_ACTION_PATH}`,
|
||||
], login, "", "");
|
||||
});
|
||||
|
||||
add_task(async function test_checkNoAutofillOnDowngradeSubdomain() {
|
||||
let login = new nsLoginInfo("https://sub.example.com", "https://example.com", null,
|
||||
"name1", "pass1", "uname", "pword");
|
||||
todo(false, "await promiseFormsProcessed timesout when test is run with scheme=https");
|
||||
await checkFormsWithLogin([
|
||||
`http://example.com${MISSING_ACTION_PATH}`,
|
||||
`http://example.com${SAME_ORIGIN_ACTION_PATH}`,
|
||||
], login, "", "");
|
||||
});
|
||||
|
||||
|
||||
add_task(async function test_checkNoAutofillOnDowngradeDifferentPort() {
|
||||
let login = new nsLoginInfo("https://example.com:8080", "https://example.com", null,
|
||||
"name1", "pass1", "uname", "pword");
|
||||
await checkFormsWithLogin([
|
||||
`http://example.com${MISSING_ACTION_PATH}`,
|
||||
`http://example.com${SAME_ORIGIN_ACTION_PATH}`,
|
||||
], login, "", "");
|
||||
});
|
||||
|
||||
add_task(async function test_checkNoAutofillOnDowngradeSubdomainDifferentPort() {
|
||||
let login = new nsLoginInfo("https://sub.example.com:8080", "https://example.com", null,
|
||||
"name1", "pass1", "uname", "pword");
|
||||
await checkFormsWithLogin([
|
||||
`https://example.com${MISSING_ACTION_PATH}`,
|
||||
`https://example.com${SAME_ORIGIN_ACTION_PATH}`,
|
||||
], login, "", "");
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -2,7 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test autocomplete on an HTTPS page using upgraded HTTP logins</title>
|
||||
<title>Test autofill on an HTTPS page using upgraded HTTP logins</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script type="text/javascript" src="pwmgr_common.js"></script>
|
||||
|
@ -0,0 +1,151 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test that logins with non-exact match hostname appear in autocomplete dropdown</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/AddTask.js"></script>
|
||||
<script type="text/javascript" src="../../../satchel/test/satchel_common.js"></script>
|
||||
<script type="text/javascript" src="pwmgr_common.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
Login Manager test: logins with non-exact match hostname appear in autocomplete dropdown
|
||||
|
||||
<script>
|
||||
var setupScript = runInParent(function setup() {
|
||||
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
// Create some logins just for this form, since we'll be deleting them.
|
||||
let nsLoginInfo = Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
|
||||
Ci.nsILoginInfo, "init");
|
||||
assert.ok(nsLoginInfo != null, "nsLoginInfo constructor");
|
||||
|
||||
let login1 = new nsLoginInfo("https://old.example.com", "https://old.example.com", null,
|
||||
"dsdu1", "dsdp1", "uname", "pword");
|
||||
let login1httpDiffPass = new nsLoginInfo("http://old.example.com", "https://old.example.com", null,
|
||||
"dsdu1", "dsdp1new", "uname", "pword");
|
||||
let login2 = new nsLoginInfo("https://example.com", "https://example.com", null,
|
||||
"dsdu1", "dsdp1", "uname", "pword");
|
||||
let login2httpSamePass = new nsLoginInfo("http://example.com", "https://example.com", null,
|
||||
"dsdu1", "dsdp1", "uname", "pword");
|
||||
let login3 = new nsLoginInfo("https://new.example.com", "https://new.example.com", null,
|
||||
"dsdu1", "dsdp1prime", "uname", "pword");
|
||||
|
||||
// `login1httpDiffPass` and `login2httpSamePass` should never be visible on https: versions of
|
||||
// *.example.com since the login is for http: and an https: login exists for this username.
|
||||
Services.logins.addLogin(login1httpDiffPass);
|
||||
Services.logins.addLogin(login2httpSamePass);
|
||||
|
||||
Services.logins.addLogin(login1);
|
||||
Services.logins.addLogin(login2);
|
||||
Services.logins.addLogin(login3);
|
||||
|
||||
addMessageListener("getDateString", () => {
|
||||
let dateAndTimeFormatter = new Services.intl.DateTimeFormat(undefined, { dateStyle: "medium" });
|
||||
return dateAndTimeFormatter.format(new Date());
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<p id="display"></p>
|
||||
|
||||
<!-- we presumably can't hide the content for this test. -->
|
||||
<div id="content">
|
||||
|
||||
<!-- form1 tests multiple matching logins -->
|
||||
<form id="form1" action="https://new.example.com/formtest.js" onsubmit="return false;">
|
||||
<input type="text" name="uname">
|
||||
<input type="password" name="pword">
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
var uname = $_(1, "uname");
|
||||
var pword = $_(1, "pword");
|
||||
|
||||
// Restore the form to the default state.
|
||||
function restoreForm() {
|
||||
uname.value = "";
|
||||
pword.value = "";
|
||||
uname.focus();
|
||||
}
|
||||
|
||||
function spinEventLoop() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
add_task(async function setup() {
|
||||
listenForUnexpectedPopupShown();
|
||||
});
|
||||
|
||||
add_task(async function test_form1_initial_empty() {
|
||||
await SimpleTest.promiseFocus(window);
|
||||
|
||||
// Make sure initial form is empty.
|
||||
checkLoginForm(uname, "", pword, "");
|
||||
let popupState = await getPopupState();
|
||||
is(popupState.open, false, "Check popup is initially closed");
|
||||
});
|
||||
|
||||
/* For this testcase, there exists two logins for this origin
|
||||
* on different subdomains but with different passwords. Both logins
|
||||
* should appear in the autocomplete popup.
|
||||
*/
|
||||
add_task(async function test_form1_menu_shows_two_logins_same_usernames_for_different_subdomain() {
|
||||
await SimpleTest.promiseFocus(window);
|
||||
// Trigger autocomplete popup
|
||||
restoreForm();
|
||||
let shownPromise = promiseACShown();
|
||||
synthesizeKey("KEY_ArrowDown"); // open
|
||||
let results = await shownPromise;
|
||||
|
||||
let popupState = await getPopupState();
|
||||
is(popupState.selectedIndex, -1, "Check no entries are selected upon opening");
|
||||
|
||||
// The logins are added "today" and since they are duplicates, the date that they were last
|
||||
// changed will be appended.
|
||||
let dateString = setupScript.sendSyncMessage("getDateString");
|
||||
let username = `dsdu1 (${dateString})`;
|
||||
|
||||
checkAutoCompleteResults(results, [username, username], "example.com", "Check all menuitems are displayed correctly.");
|
||||
|
||||
synthesizeKey("KEY_ArrowDown"); // first item
|
||||
checkLoginForm(uname, "", pword, ""); // value shouldn't update just by selecting
|
||||
|
||||
synthesizeKey("KEY_Enter");
|
||||
await promiseFormsProcessed();
|
||||
todo_is(pword.value, "dsdp1",
|
||||
`Bug 1166113: The password should match the login that was selected.
|
||||
From bug 499649 when there are two logins with the same case we just
|
||||
blindly select the last one always. We should instead remember which
|
||||
login was selected from the dropdown and use that login's password
|
||||
when possible`);
|
||||
checkLoginForm(uname, "dsdu1", pword, "dsdp1prime");
|
||||
|
||||
restoreForm();
|
||||
|
||||
shownPromise = promiseACShown();
|
||||
synthesizeKey("KEY_ArrowDown"); // open
|
||||
await shownPromise;
|
||||
|
||||
synthesizeKey("KEY_ArrowDown"); // first item
|
||||
synthesizeKey("KEY_ArrowDown"); // second item
|
||||
checkLoginForm(uname, "", pword, ""); // value shouldn't update just by selecting
|
||||
|
||||
synthesizeKey("KEY_Enter");
|
||||
await promiseFormsProcessed();
|
||||
todo_is(pword.value, "dsdp1",
|
||||
`Bug 1166113: The password should match the login that was selected.
|
||||
From bug 499649 when there are two logins with the same case we just
|
||||
blindly select the last one always. We should instead remember which
|
||||
login was selected from the dropdown and use that login's password
|
||||
when possible`);
|
||||
checkLoginForm(uname, "dsdu1", pword, "dsdp1prime");
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -1,4 +1,4 @@
|
||||
/*
|
||||
/**
|
||||
* Test LoginHelper.dedupeLogins
|
||||
*/
|
||||
|
||||
@ -15,12 +15,34 @@ const DOMAIN1_HTTP_TO_HTTP_U2_P2 = TestData.formLogin({
|
||||
password: "password two",
|
||||
username: "username two",
|
||||
});
|
||||
const DOMAIN1_HTTPS_TO_HTTPS_U1_P1 = TestData.formLogin({
|
||||
const DOMAIN2_HTTP_TO_HTTP_U2_P2 = TestData.formLogin({
|
||||
hostname: "http://www4.example.com",
|
||||
formSubmitURL: "http://www4.example.com",
|
||||
password: "password two",
|
||||
username: "username two",
|
||||
});
|
||||
|
||||
const DOMAIN1_HTTPS_TO_HTTP_U1_P1 = TestData.formLogin({
|
||||
formSubmitURL: "http://www.example.com",
|
||||
hostname: "https://www3.example.com",
|
||||
timePasswordChanged: 4000,
|
||||
timeLastUsed: 1000,
|
||||
});
|
||||
const DOMAIN1_HTTPS_TO_HTTPS_U1_P1 = TestData.formLogin({
|
||||
formSubmitURL: "https://www.example.com",
|
||||
hostname: "https://www3.example.com",
|
||||
timePasswordChanged: 4000,
|
||||
timeLastUsed: 1000,
|
||||
});
|
||||
|
||||
const DOMAIN1_HTTPS_TO_HTTPS_U1_P2 = TestData.formLogin({
|
||||
formSubmitURL: "https://www.example.com",
|
||||
hostname: "https://www3.example.com",
|
||||
password: "password two",
|
||||
timePasswordChanged: 4000,
|
||||
timeLastUsed: 1000,
|
||||
});
|
||||
|
||||
const DOMAIN1_HTTPS_TO_EMPTY_U1_P1 = TestData.formLogin({
|
||||
formSubmitURL: "",
|
||||
hostname: "https://www3.example.com",
|
||||
@ -35,6 +57,32 @@ const DOMAIN1_HTTP_AUTH = TestData.authLogin({
|
||||
const DOMAIN1_HTTPS_AUTH = TestData.authLogin({
|
||||
hostname: "https://www3.example.com",
|
||||
});
|
||||
const DOMAIN1_HTTPS_LOGIN = TestData.formLogin({
|
||||
hostname: "https://www3.example.com",
|
||||
formSubmitURL: "https://www3.example.com",
|
||||
});
|
||||
const DOMAIN1_HTTP_LOGIN = TestData.formLogin({
|
||||
hostname: "http://www3.example.com",
|
||||
formSubmitURL: "http://www3.example.com",
|
||||
});
|
||||
const DOMAIN1_HTTPS_NONSTANDARD_PORT1 = TestData.formLogin({
|
||||
hostname: "https://www3.example.com:8001",
|
||||
formSubmitURL: "https://www3.example.com:8001",
|
||||
});
|
||||
const DOMAIN1_HTTPS_NONSTANDARD_PORT2 = TestData.formLogin({
|
||||
hostname: "https://www3.example.com:8008",
|
||||
formSubmitURL: "https://www3.example.com:8008",
|
||||
});
|
||||
const DOMAIN2_HTTPS_LOGIN = TestData.formLogin({
|
||||
hostname: "https://www4.example.com",
|
||||
formSubmitURL: "https://www4.example.com",
|
||||
});
|
||||
const DOMAIN2_HTTPS_TO_HTTPS_U2_P2 = TestData.formLogin({
|
||||
hostname: "https://www4.example.com",
|
||||
formSubmitURL: "https://www4.example.com",
|
||||
password: "password two",
|
||||
username: "username two",
|
||||
});
|
||||
|
||||
|
||||
add_task(function test_dedupeLogins() {
|
||||
@ -64,21 +112,21 @@ add_task(function test_dedupeLogins() {
|
||||
[
|
||||
"same un+pw, different scheme",
|
||||
[DOMAIN1_HTTP_TO_HTTP_U1_P1],
|
||||
[DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_HTTPS_U1_P1],
|
||||
[DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_HTTP_U1_P1],
|
||||
undefined,
|
||||
[],
|
||||
],
|
||||
[
|
||||
"same un+pw, different scheme, reverse order",
|
||||
[DOMAIN1_HTTPS_TO_HTTPS_U1_P1],
|
||||
[DOMAIN1_HTTPS_TO_HTTPS_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
|
||||
[DOMAIN1_HTTPS_TO_HTTP_U1_P1],
|
||||
[DOMAIN1_HTTPS_TO_HTTP_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
|
||||
undefined,
|
||||
[],
|
||||
],
|
||||
[
|
||||
"same un+pw, different scheme, include hostname",
|
||||
[DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_HTTPS_U1_P1],
|
||||
[DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_HTTPS_U1_P1],
|
||||
[DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_HTTP_U1_P1],
|
||||
[DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_HTTP_U1_P1],
|
||||
["hostname", "username", "password"],
|
||||
[],
|
||||
],
|
||||
@ -118,68 +166,68 @@ add_task(function test_dedupeLogins() {
|
||||
|
||||
|
||||
add_task(async function test_dedupeLogins_resolveBy() {
|
||||
Assert.ok(DOMAIN1_HTTP_TO_HTTP_U1_P1.timeLastUsed > DOMAIN1_HTTPS_TO_HTTPS_U1_P1.timeLastUsed,
|
||||
Assert.ok(DOMAIN1_HTTP_TO_HTTP_U1_P1.timeLastUsed > DOMAIN1_HTTPS_TO_HTTP_U1_P1.timeLastUsed,
|
||||
"Sanity check timeLastUsed difference");
|
||||
Assert.ok(DOMAIN1_HTTP_TO_HTTP_U1_P1.timePasswordChanged < DOMAIN1_HTTPS_TO_HTTPS_U1_P1.timePasswordChanged,
|
||||
Assert.ok(DOMAIN1_HTTP_TO_HTTP_U1_P1.timePasswordChanged < DOMAIN1_HTTPS_TO_HTTP_U1_P1.timePasswordChanged,
|
||||
"Sanity check timePasswordChanged difference");
|
||||
|
||||
let testcases = [
|
||||
[
|
||||
"default resolveBy is timeLastUsed",
|
||||
[DOMAIN1_HTTP_TO_HTTP_U1_P1],
|
||||
[DOMAIN1_HTTPS_TO_HTTPS_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
|
||||
[DOMAIN1_HTTPS_TO_HTTP_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
|
||||
],
|
||||
[
|
||||
"default resolveBy is timeLastUsed, reversed input",
|
||||
[DOMAIN1_HTTP_TO_HTTP_U1_P1],
|
||||
[DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_HTTPS_U1_P1],
|
||||
[DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_HTTP_U1_P1],
|
||||
],
|
||||
[
|
||||
"resolveBy timeLastUsed + timePasswordChanged",
|
||||
[DOMAIN1_HTTP_TO_HTTP_U1_P1],
|
||||
[DOMAIN1_HTTPS_TO_HTTPS_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
|
||||
[DOMAIN1_HTTPS_TO_HTTP_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
|
||||
undefined,
|
||||
["timeLastUsed", "timePasswordChanged"],
|
||||
],
|
||||
[
|
||||
"resolveBy timeLastUsed + timePasswordChanged, reversed input",
|
||||
[DOMAIN1_HTTP_TO_HTTP_U1_P1],
|
||||
[DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_HTTPS_U1_P1],
|
||||
[DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_HTTP_U1_P1],
|
||||
undefined,
|
||||
["timeLastUsed", "timePasswordChanged"],
|
||||
],
|
||||
[
|
||||
"resolveBy timePasswordChanged",
|
||||
[DOMAIN1_HTTPS_TO_HTTPS_U1_P1],
|
||||
[DOMAIN1_HTTPS_TO_HTTPS_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
|
||||
[DOMAIN1_HTTPS_TO_HTTP_U1_P1],
|
||||
[DOMAIN1_HTTPS_TO_HTTP_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
|
||||
undefined,
|
||||
["timePasswordChanged"],
|
||||
],
|
||||
[
|
||||
"resolveBy timePasswordChanged, reversed",
|
||||
[DOMAIN1_HTTPS_TO_HTTPS_U1_P1],
|
||||
[DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_HTTPS_U1_P1],
|
||||
[DOMAIN1_HTTPS_TO_HTTP_U1_P1],
|
||||
[DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_HTTP_U1_P1],
|
||||
undefined,
|
||||
["timePasswordChanged"],
|
||||
],
|
||||
[
|
||||
"resolveBy timePasswordChanged + timeLastUsed",
|
||||
[DOMAIN1_HTTPS_TO_HTTPS_U1_P1],
|
||||
[DOMAIN1_HTTPS_TO_HTTPS_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
|
||||
[DOMAIN1_HTTPS_TO_HTTP_U1_P1],
|
||||
[DOMAIN1_HTTPS_TO_HTTP_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
|
||||
undefined,
|
||||
["timePasswordChanged", "timeLastUsed"],
|
||||
],
|
||||
[
|
||||
"resolveBy timePasswordChanged + timeLastUsed, reversed",
|
||||
[DOMAIN1_HTTPS_TO_HTTPS_U1_P1],
|
||||
[DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_HTTPS_U1_P1],
|
||||
[DOMAIN1_HTTPS_TO_HTTP_U1_P1],
|
||||
[DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_HTTP_U1_P1],
|
||||
undefined,
|
||||
["timePasswordChanged", "timeLastUsed"],
|
||||
],
|
||||
[
|
||||
"resolveBy scheme + timePasswordChanged, prefer HTTP",
|
||||
[DOMAIN1_HTTP_TO_HTTP_U1_P1],
|
||||
[DOMAIN1_HTTPS_TO_HTTPS_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
|
||||
[DOMAIN1_HTTPS_TO_HTTP_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
|
||||
undefined,
|
||||
["scheme", "timePasswordChanged"],
|
||||
DOMAIN1_HTTP_TO_HTTP_U1_P1.hostname,
|
||||
@ -187,26 +235,26 @@ add_task(async function test_dedupeLogins_resolveBy() {
|
||||
[
|
||||
"resolveBy scheme + timePasswordChanged, prefer HTTP, reversed input",
|
||||
[DOMAIN1_HTTP_TO_HTTP_U1_P1],
|
||||
[DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_HTTPS_U1_P1],
|
||||
[DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_HTTP_U1_P1],
|
||||
undefined,
|
||||
["scheme", "timePasswordChanged"],
|
||||
DOMAIN1_HTTP_TO_HTTP_U1_P1.hostname,
|
||||
],
|
||||
[
|
||||
"resolveBy scheme + timePasswordChanged, prefer HTTPS",
|
||||
[DOMAIN1_HTTPS_TO_HTTPS_U1_P1],
|
||||
[DOMAIN1_HTTPS_TO_HTTPS_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
|
||||
[DOMAIN1_HTTPS_TO_HTTP_U1_P1],
|
||||
[DOMAIN1_HTTPS_TO_HTTP_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
|
||||
undefined,
|
||||
["scheme", "timePasswordChanged"],
|
||||
DOMAIN1_HTTPS_TO_HTTPS_U1_P1.hostname,
|
||||
DOMAIN1_HTTPS_TO_HTTP_U1_P1.hostname,
|
||||
],
|
||||
[
|
||||
"resolveBy scheme + timePasswordChanged, prefer HTTPS, reversed input",
|
||||
[DOMAIN1_HTTPS_TO_HTTPS_U1_P1],
|
||||
[DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_HTTPS_U1_P1],
|
||||
[DOMAIN1_HTTPS_TO_HTTP_U1_P1],
|
||||
[DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_HTTP_U1_P1],
|
||||
undefined,
|
||||
["scheme", "timePasswordChanged"],
|
||||
DOMAIN1_HTTPS_TO_HTTPS_U1_P1.hostname,
|
||||
DOMAIN1_HTTPS_TO_HTTP_U1_P1.hostname,
|
||||
],
|
||||
[
|
||||
"resolveBy scheme HTTP auth",
|
||||
@ -226,10 +274,63 @@ add_task(async function test_dedupeLogins_resolveBy() {
|
||||
],
|
||||
[
|
||||
"resolveBy scheme, empty form submit URL",
|
||||
[DOMAIN1_HTTPS_TO_HTTPS_U1_P1],
|
||||
[DOMAIN1_HTTPS_TO_HTTPS_U1_P1, DOMAIN1_HTTPS_TO_EMPTY_U1_P1],
|
||||
[DOMAIN1_HTTPS_TO_HTTP_U1_P1],
|
||||
[DOMAIN1_HTTPS_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_EMPTY_U1_P1],
|
||||
undefined,
|
||||
["scheme"],
|
||||
DOMAIN1_HTTPS_TO_HTTP_U1_P1.hostname,
|
||||
],
|
||||
[
|
||||
"resolveBy subdomain, different subdomains, same login, subdomain1 preferred",
|
||||
[DOMAIN1_HTTPS_LOGIN],
|
||||
[DOMAIN1_HTTPS_LOGIN, DOMAIN2_HTTPS_LOGIN],
|
||||
undefined,
|
||||
["subdomain"],
|
||||
DOMAIN1_HTTPS_LOGIN.hostname,
|
||||
],
|
||||
[
|
||||
"resolveBy subdomain, different subdomains, same login, subdomain2 preferred",
|
||||
[DOMAIN2_HTTPS_LOGIN],
|
||||
[DOMAIN1_HTTPS_LOGIN, DOMAIN2_HTTPS_LOGIN],
|
||||
undefined,
|
||||
["subdomain"],
|
||||
DOMAIN2_HTTPS_LOGIN.hostname,
|
||||
],
|
||||
[
|
||||
"resolveBy subdomain, same subdomain, different schemes",
|
||||
[DOMAIN1_HTTPS_LOGIN],
|
||||
[DOMAIN1_HTTPS_LOGIN, DOMAIN1_HTTP_LOGIN],
|
||||
undefined,
|
||||
["subdomain"],
|
||||
DOMAIN1_HTTPS_LOGIN.hostname,
|
||||
],
|
||||
[
|
||||
"resolveBy subdomain, same subdomain, different ports",
|
||||
[DOMAIN1_HTTPS_LOGIN],
|
||||
[DOMAIN1_HTTPS_LOGIN, DOMAIN1_HTTPS_NONSTANDARD_PORT1, DOMAIN1_HTTPS_NONSTANDARD_PORT2],
|
||||
undefined,
|
||||
["subdomain"],
|
||||
DOMAIN1_HTTPS_LOGIN.hostname,
|
||||
],
|
||||
[
|
||||
"resolveBy subdomain, same subdomain, different schemes, different ports",
|
||||
[DOMAIN1_HTTPS_LOGIN],
|
||||
[DOMAIN1_HTTPS_LOGIN, DOMAIN1_HTTPS_NONSTANDARD_PORT1, DOMAIN1_HTTPS_NONSTANDARD_PORT2],
|
||||
undefined,
|
||||
["subdomain"],
|
||||
DOMAIN1_HTTPS_AUTH.hostname,
|
||||
],
|
||||
[
|
||||
"resolveBy matching _searchAndDedupeLogins, prefer https: scheme over http: in primary and subdomains",
|
||||
// expected:
|
||||
[DOMAIN1_HTTPS_TO_HTTPS_U1_P1, DOMAIN2_HTTPS_TO_HTTPS_U2_P2],
|
||||
// logins:
|
||||
[DOMAIN1_HTTP_TO_HTTP_U1_P1, DOMAIN1_HTTPS_TO_HTTPS_U1_P1, DOMAIN2_HTTP_TO_HTTP_U2_P2, DOMAIN2_HTTPS_TO_HTTPS_U2_P2],
|
||||
// uniqueKeys:
|
||||
undefined,
|
||||
// resolveBy:
|
||||
["actionOrigin", "scheme", "subdomain", "timePasswordChanged"],
|
||||
// preferredOrigin:
|
||||
DOMAIN1_HTTPS_TO_HTTPS_U1_P1.hostname,
|
||||
],
|
||||
];
|
||||
@ -238,6 +339,7 @@ add_task(async function test_dedupeLogins_resolveBy() {
|
||||
let description = tc.shift();
|
||||
let expected = tc.shift();
|
||||
let actual = LoginHelper.dedupeLogins(...tc);
|
||||
info(`'${description}' actual:\n ${JSON.stringify(actual, null, 2)}`);
|
||||
Assert.strictEqual(actual.length, expected.length, `Check: ${description}`);
|
||||
for (let [i, login] of expected.entries()) {
|
||||
Assert.strictEqual(actual[i], login, `Check index ${i}`);
|
||||
@ -250,21 +352,21 @@ add_task(async function test_dedupeLogins_preferredOriginMissing() {
|
||||
[
|
||||
"resolveBy scheme + timePasswordChanged, missing preferredOrigin",
|
||||
/preferredOrigin/,
|
||||
[DOMAIN1_HTTPS_TO_HTTPS_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
|
||||
[DOMAIN1_HTTPS_TO_HTTP_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
|
||||
undefined,
|
||||
["scheme", "timePasswordChanged"],
|
||||
],
|
||||
[
|
||||
"resolveBy timePasswordChanged + scheme, missing preferredOrigin",
|
||||
/preferredOrigin/,
|
||||
[DOMAIN1_HTTPS_TO_HTTPS_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
|
||||
[DOMAIN1_HTTPS_TO_HTTP_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
|
||||
undefined,
|
||||
["timePasswordChanged", "scheme"],
|
||||
],
|
||||
[
|
||||
"resolveBy scheme + timePasswordChanged, empty preferredOrigin",
|
||||
/preferredOrigin/,
|
||||
[DOMAIN1_HTTPS_TO_HTTPS_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
|
||||
[DOMAIN1_HTTPS_TO_HTTP_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
|
||||
undefined,
|
||||
["scheme", "timePasswordChanged"],
|
||||
"",
|
||||
|
@ -29,6 +29,19 @@ add_task(function test_isOriginMatching() {
|
||||
[false, "http://example.com:8080", "https://example.com", { schemeUpgrades: true }], // port mismatch
|
||||
[false, "http://example.com", "https://example.com:8443", { schemeUpgrades: true }], // port mismatch
|
||||
[false, "http://sub.example.com", "http://example.com", { schemeUpgrades: true }],
|
||||
[true, "http://sub.example.com", "http://example.com", { acceptDifferentSubdomains: true }],
|
||||
[true, "http://sub.sub.example.com", "http://example.com", { acceptDifferentSubdomains: true }],
|
||||
[true, "http://example.com", "http://sub.example.com", { acceptDifferentSubdomains: true }],
|
||||
[true, "http://example.com", "http://sub.sub.example.com", { acceptDifferentSubdomains: true }],
|
||||
[false, "https://sub.example.com", "http://example.com", { acceptDifferentSubdomains: true, schemeUpgrades: true }],
|
||||
[true, "http://sub.example.com", "https://example.com", { acceptDifferentSubdomains: true, schemeUpgrades: true }],
|
||||
[true, "http://sub.example.com", "http://example.com:8081", { acceptDifferentSubdomains: true }],
|
||||
[false, "http://sub.example.com", "http://sub.example.mozilla.com", { acceptDifferentSubdomains: true }],
|
||||
// signon.includeOtherSubdomainsInLookup allows acceptDifferentSubdomains to be false
|
||||
[false, "http://sub.example.com", "http://example.com", { acceptDifferentSubdomains: false }],
|
||||
[false, "http://sub.sub.example.com", "http://example.com", { acceptDifferentSubdomains: false }],
|
||||
[false, "http://sub.example.com", "http://example.com:8081", { acceptDifferentSubdomains: false }],
|
||||
[false, "http://sub.example.com", "http://sub.example.mozilla.com", { acceptDifferentSubdomains: false }],
|
||||
];
|
||||
for (let tc of testcases) {
|
||||
let expected = tc.shift();
|
||||
|
@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Test LoginHelper.shadowHTTPLogins
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const DOMAIN1_HTTP_TO_HTTP_U1_P1 = TestData.formLogin({});
|
||||
const DOMAIN1_HTTP_TO_HTTP_U2_P1 = TestData.formLogin({
|
||||
username: "user2",
|
||||
});
|
||||
const DOMAIN1_HTTPS_TO_HTTPS_U1_P1 = TestData.formLogin({
|
||||
hostname: "https://www3.example.com",
|
||||
formSubmitURL: "https://login.example.com",
|
||||
});
|
||||
const DOMAIN1_HTTPS_TO_HTTPS_U1_P2 = TestData.formLogin({
|
||||
hostname: "https://www3.example.com",
|
||||
formSubmitURL: "https://login.example.com",
|
||||
password: "password two",
|
||||
});
|
||||
const DOMAIN1_HTTP_TO_HTTP_U1_P2 = TestData.formLogin({
|
||||
password: "password two",
|
||||
});
|
||||
const DOMAIN1_HTTP_TO_HTTP_U1_P1_DIFFERENT_PORT = TestData.formLogin({
|
||||
hostname: "http://www3.example.com:8080",
|
||||
});
|
||||
const DOMAIN2_HTTP_TO_HTTP_U1_P1 = TestData.formLogin({
|
||||
hostname: "http://different.example.com",
|
||||
});
|
||||
const DOMAIN2_HTTPS_TO_HTTPS_U1_P1 = TestData.formLogin({
|
||||
hostname: "https://different.example.com",
|
||||
formSubmitURL: "https://login.example.com",
|
||||
});
|
||||
|
||||
add_task(function test_shadowHTTPLogins() {
|
||||
let testcases = [
|
||||
{
|
||||
description: "same hostPort, same username, different scheme",
|
||||
logins: [DOMAIN1_HTTPS_TO_HTTPS_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1],
|
||||
expected: [DOMAIN1_HTTPS_TO_HTTPS_U1_P1],
|
||||
},
|
||||
{
|
||||
description: "different passwords, different scheme",
|
||||
logins: [DOMAIN1_HTTPS_TO_HTTPS_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P2],
|
||||
expected: [DOMAIN1_HTTPS_TO_HTTPS_U1_P1],
|
||||
},
|
||||
{
|
||||
description: "both https, same username, different password",
|
||||
logins: [DOMAIN1_HTTPS_TO_HTTPS_U1_P1, DOMAIN1_HTTPS_TO_HTTPS_U1_P2],
|
||||
expected: [DOMAIN1_HTTPS_TO_HTTPS_U1_P1, DOMAIN1_HTTPS_TO_HTTPS_U1_P2],
|
||||
},
|
||||
{
|
||||
description: "same hostname, different port, different scheme",
|
||||
logins: [DOMAIN1_HTTPS_TO_HTTPS_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1_DIFFERENT_PORT],
|
||||
expected: [DOMAIN1_HTTPS_TO_HTTPS_U1_P1, DOMAIN1_HTTP_TO_HTTP_U1_P1_DIFFERENT_PORT],
|
||||
},
|
||||
{
|
||||
description: "different hostname, different scheme",
|
||||
logins: [DOMAIN1_HTTPS_TO_HTTPS_U1_P1, DOMAIN2_HTTP_TO_HTTP_U1_P1],
|
||||
expected: [DOMAIN1_HTTPS_TO_HTTPS_U1_P1, DOMAIN2_HTTP_TO_HTTP_U1_P1],
|
||||
},
|
||||
{
|
||||
description: "different username, different scheme",
|
||||
logins: [DOMAIN1_HTTPS_TO_HTTPS_U1_P1, DOMAIN1_HTTP_TO_HTTP_U2_P1],
|
||||
expected: [DOMAIN1_HTTPS_TO_HTTPS_U1_P1, DOMAIN1_HTTP_TO_HTTP_U2_P1],
|
||||
},
|
||||
];
|
||||
|
||||
for (let tc of testcases) {
|
||||
info(tc.description);
|
||||
let actual = LoginHelper.shadowHTTPLogins(tc.logins);
|
||||
Assert.strictEqual(actual.length, tc.expected.length, `Check result length`);
|
||||
for (let [i, login] of tc.expected.entries()) {
|
||||
Assert.strictEqual(actual[i], login, `Check index ${i}`);
|
||||
}
|
||||
}
|
||||
});
|
@ -49,5 +49,6 @@ skip-if = os == "android" # Not packaged/used on Android
|
||||
[test_recipes_add.js]
|
||||
[test_recipes_content.js]
|
||||
[test_search_schemeUpgrades.js]
|
||||
[test_shadowHTTPLogins.js]
|
||||
[test_storage.js]
|
||||
[test_telemetry.js]
|
||||
|
Loading…
Reference in New Issue
Block a user