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:
Jared Wein 2019-05-25 05:08:16 +00:00
parent 967bc2a754
commit 7a0d2657fd
17 changed files with 800 additions and 72 deletions

View File

@ -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.

View File

@ -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);

View File

@ -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)) {

View File

@ -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.

View File

@ -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) {

View File

@ -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();

View File

@ -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 = {};

View File

@ -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

View File

@ -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() {

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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"],
"",

View File

@ -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();

View File

@ -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}`);
}
}
});

View File

@ -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]