diff options
Diffstat (limited to '')
-rw-r--r-- | toolkit/components/passwordmgr/test/mochitest/test_munged_values.html | 362 |
1 files changed, 362 insertions, 0 deletions
diff --git a/toolkit/components/passwordmgr/test/mochitest/test_munged_values.html b/toolkit/components/passwordmgr/test/mochitest/test_munged_values.html new file mode 100644 index 0000000000..d2fdf91d4a --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_munged_values.html @@ -0,0 +1,362 @@ +<!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, [html], function(contentHtml) { + 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, [html], function(contentHtml) { + 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, [html], function(contentHtml) { + 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, [html], function(contentHtml) { + 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> |