Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Test handling of possibly-manipulated username values</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="../../../satchel/test/satchel_common.js"></script>
<script src="pwmgr_common.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css" />
</head>
<body>
<script type="application/javascript">
const DEFAULT_ORIGIN = window.location.origin;
function removeAllUserFacingLoginsInParent() {
runInParent(function removeAllUserFacingLogins() {
Services.logins.removeAllUserFacingLogins();
});
}
async function add2logins() {
removeAllUserFacingLoginsInParent();
await addLoginsInParent([DEFAULT_ORIGIN, DEFAULT_ORIGIN, null, "real••••user", "pass1", "", ""], [DEFAULT_ORIGIN, DEFAULT_ORIGIN, null, "user2", "pass2", "", ""]);
}
async function addSingleLogin() {
removeAllUserFacingLoginsInParent();
await addLoginsInParent([DEFAULT_ORIGIN, DEFAULT_ORIGIN, null, "real••••user", "pass1", "", ""])
}
/**
* For any test including the character "!", generate a version of that test for every known munge
* character.
**/
function generateTestCases(test) {
const MUNGE_CHARS = ["*", ".", "•"];
const nothingToReplace = Object.values(test).every(value => typeof value !== "string" || !value.includes("!"));
if (nothingToReplace) {
return test;
};
return MUNGE_CHARS.map(char => {
const newTest = {};
for (const [propName, val] of Object.entries(test)) {
if (typeof val === "string") {
newTest[propName] = val.replace(/!/g, char);
} else {
newTest[propName] = val;
}
};
return newTest;
})};
const loadPromise = new Promise(resolve => {
document.addEventListener("DOMContentLoaded", () => {
resolve();
});
});
add_setup(async () => {
await setStoredLoginsAsync();
info("Waiting for page and window loads");
await loadPromise;
});
add_task(async function test_new_logins() {
const TEST_CASES = [
// ! is replaced with characters commonly used for munging
{
testName: "test_middle!MaskedUsername",
username: "so!!!ne",
expected: null,
},
{
testName: "test_start!MaskedUsername",
username: "!!!eone",
expected: null,
},
{
testName: "test_end!MaskedUsername",
username: "some!!!",
expected: null,
},
{
testName: "test_ok!Username",
username: "obelixand!",
expected: "obelixand!",
},
{
testName: "test_ok!Username2",
username: "!!username!!",
expected: "!!username!!",
},
{
// We should only consider a username munged if it repeats of the same character
testName: "test_combinedMungeCharacters",
username: "*.•*.•*.•*.•*.•*.•",
expected: "*.•*.•*.•*.•*.•*.•",
},
].flatMap(generateTestCases);
for (const tc of TEST_CASES) {
info("Starting testcase: " + JSON.stringify(tc));
// Create a new window for each test case, because if we instead try to use
// the same window and change the page using window.location, that will trigger
// an onLocationChange event, which can trigger unwanted FormSubmit outside of
// clicking the submit button in each test document.
const win = window.open("about:blank");
const html = `
<form id="form1" onsubmit="return false;">
<input type="text" name="uname" value="${tc.username}">
<input type="password" name="pword" value="thepassword">
<button type="submit" id="submitBtn">Submit</button>
</form>`;
await loadFormIntoWindow(DEFAULT_ORIGIN, html, win);
await SpecialPowers.spawn(win, [], function() {
const doc = this.content.document;
for (const field of doc.querySelectorAll("input")) {
const actualValue = field.value;
field.value = "";
SpecialPowers.wrap(field).setUserInput(actualValue);
}
});
await SpecialPowers.spawn(win, [tc], function(testcase) {
const doc = this.content.document;
Assert.equal(doc.querySelector("[name='uname']").value, testcase.username, "Checking for filled username");
});
// Check data sent via PasswordManager:onFormSubmit
const processedPromise = getSubmitMessage();
await SpecialPowers.spawn(win, [], function() {
this.content.document.getElementById("submitBtn").click();
});
const { data } = await processedPromise;
info("Got submitted result: " + JSON.stringify(data));
if (tc.expected === null) {
is(data.usernameField, tc.expected, "Check usernameField");
} else {
is(data.usernameField.value, tc.expected, "Check usernameField");
}
win.close();
await SimpleTest.promiseFocus(window);
}
});
add_task(async function test_no_save_dialog_when_password_is_fully_munged() {
const TEST_CASES = [
{
testName: "test_passFullyMungedBy!",
password: "!!!!!!!!!",
shouldShowPrompt: false,
},
{
testName: "test_passStartsMungedBy!",
password: "!!!!!!!butThenAPassword",
shouldShowPrompt: true,
},
{
testName: "test_passEndsMungedBy!",
password: "aRealPasswordAndThen!!!!!!!",
shouldShowPrompt: true,
},
{
testName: "test_passMostlyMungedBy!",
password: "!!!a!!!!",
shouldShowPrompt: true,
},
{
testName: "test_combinedMungedCharacters",
password: "*.•*.•*.•*.•",
shouldShowPrompt: true,
},
].flatMap(generateTestCases);
for (const tc of TEST_CASES) {
info("Starting testcase: " + tc.testName)
// Create a new window for each test case, because if we instead try to use
// the same window and change the page using window.location, that will trigger
// an onLocationChange event, which can trigger unwanted FormSubmit outside of
// clicking the submit button in each test document.
const win = window.open("about:blank");
const html = `
<form id="form1" onsubmit="return false;">
<input type="text" name="uname" value="username">
<input type="password" name="pword" value="${tc.password}">
<button type="submit" id="submitBtn">Submit</button>
</form>`;
await loadFormIntoWindow(DEFAULT_ORIGIN, html, win);
await SpecialPowers.spawn(win, [], function() {
const doc = this.content.document;
for (const field of doc.querySelectorAll("input")) {
const actualValue = field.value;
field.value = "";
SpecialPowers.wrap(field).setUserInput(actualValue);
}
});
await SpecialPowers.spawn(win, [tc], function(testcase) {
const doc = this.content.document;
Assert.equal(doc.querySelector("[name='pword']").value, testcase.password, "Checking for filled password");
});
const formSubmitListener = SpecialPowers.spawn(win, [], function() {
return new Promise(resolve => {
this.content.windowRoot.addEventListener(
"PasswordManager:ShowDoorhanger",
event => {
info(`PasswordManager:ShowDoorhanger called. Event: ${JSON.stringify(event)}`);
resolve(event.detail.messageSent);
}
);
});
});
await SpecialPowers.spawn(win, [], function() {
this.content.document.getElementById("submitBtn").click();
});
const dialogRequested = await formSubmitListener;
is(dialogRequested, tc.shouldShowPrompt, "Verify 'show save/update prompt' message sent to parent process");
win.close();
await SimpleTest.promiseFocus(window);
}
});
add_task(async function test_no_autofill_munged_username_matching_password() {
// run this test with 2 matching logins from this origin so we don't autofill
await add2logins();
const allLogins = await LoginManager.getAllLogins();
const matchingLogins = Array.prototype.filter.call(allLogins, l => l.origin == DEFAULT_ORIGIN);
is(matchingLogins.length, 2, "Expected number of matching logins");
const bulletLogin = matchingLogins.find(l => l.username == "real••••user");
ok(bulletLogin, "Found the real••••user login");
const timesUsed = bulletLogin.timesUsed;
const guid = bulletLogin.guid;
const win = window.open("about:blank");
const html =
`<form id="form1" onsubmit="return false;">
<input type="text" name="uname" value="">
<input type="password" name="pword" value="">
<button type="submit" id="submitBtn">Submit</button>
</form>`;
await loadFormIntoWindow(DEFAULT_ORIGIN, html, win);
await SpecialPowers.spawn(win, [], function() {
const doc = this.content.document;
for (const field of doc.querySelectorAll("input")) {
const actualValue = field.value;
field.value = "";
SpecialPowers.wrap(field).setUserInput(actualValue);
}
});
await SpecialPowers.spawn(win, [], function() {
const doc = this.content.document;
Assert.equal(doc.querySelector("[name='uname']").value, "", "Check username didn't get autofilled");
SpecialPowers.wrap(doc.querySelector("[name='uname']")).setUserInput("real••••user");
SpecialPowers.wrap(doc.querySelector("[name='pword']")).setUserInput("pass1");
});
// we shouldn't get the save password doorhanger...
const popupShownPromise = noPopupBy();
// Check data sent via PasswordManager:onFormSubmit
const processedPromise = getSubmitMessage();
await SpecialPowers.spawn(win, [], function() {
this.content.document.getElementById("submitBtn").click();
});
const { data } = await processedPromise;
info("Got submitted result: " + JSON.stringify(data));
is(data.usernameField, null, "Check usernameField");
const updatedLogins = await LoginManager.getAllLogins();
const updatedLogin = Array.prototype.find.call(updatedLogins, l => l.guid == guid);
ok(updatedLogin, "Got the login via guid");
is(updatedLogin.timesUsed, timesUsed + 1, "timesUsed was incremented");
await popupShownPromise;
win.close();
await SimpleTest.promiseFocus(window);
});
add_task(async function test_autofill_munged_username_matching_password() {
// only a single matching login so we autofill the username
await addSingleLogin();
const allLogins = await LoginManager.getAllLogins();
const matchingLogins = Array.prototype.filter.call(allLogins, l => l.origin == DEFAULT_ORIGIN);
is(matchingLogins.length, 1, "Expected number of matching logins");
info("matched login: " + matchingLogins[0].username);
const bulletLogin = matchingLogins.find(l => l.username == "real••••user");
ok(bulletLogin, "Found the real••••user login");
const timesUsed = bulletLogin.timesUsed;
const guid = bulletLogin.guid;
const win = window.open("about:blank");
const html =
`<form id="form1" onsubmit="return false;">
<input type="text" name="uname" value="">
<input type="password" name="pword" value="">
<button type="submit" id="submitBtn">Submit</button>
</form>`;
await loadFormIntoWindow(DEFAULT_ORIGIN, html, win);
await SpecialPowers.spawn(win, [], function() {
const doc = this.content.document;
for (const field of doc.querySelectorAll("input")) {
const actualValue = field.value;
field.value = "";
SpecialPowers.wrap(field).setUserInput(actualValue);
}
});
await SpecialPowers.spawn(win, [], function() {
const doc = this.content.document;
Assert.equal(doc.querySelector("[name='uname']").value, "real••••user", "Check username did get autofilled");
doc.querySelector("[name='pword']").setUserInput("pass1");
});
// we shouldn't get the save/update password doorhanger as it didn't change
const popupShownPromise = noPopupBy();
// Check data sent via PasswordManager:onFormSubmit
const processedPromise = getSubmitMessage();
await SpecialPowers.spawn(win, [], function() {
this.content.document.getElementById("submitBtn").click();
});
const { data } = await processedPromise;
info("Got submitted result: " + JSON.stringify(data));
is(data.usernameField, null, "Check usernameField");
const updatedLogins = await LoginManager.getAllLogins();
const updatedLogin = Array.prototype.find.call(updatedLogins, l => l.guid == guid);
ok(updatedLogin, "Got the login via guid");
is(updatedLogin.timesUsed, timesUsed + 1, "timesUsed was incremented");
await popupShownPromise;
win.close();
await SimpleTest.promiseFocus(window);
});
</script>
<p id="display"></p>
<div id="content">
</div>
<pre id="test"></pre>
</body>
</html>