Revision control
Copy as Markdown
Other Tools
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
// Tests what a directory keeps of the credentials typed into the password
// prompt. The server accepting them is what counts, not what it then makes
// of the request.
const { HttpServer } = ChromeUtils.importESModule(
);
var { MockRegistrar } = ChromeUtils.importESModule(
);
const REALM = "test";
const USERNAME = "bob";
const PASSWORD = "bob's password";
const STALE_USERNAME = "robert";
let server;
let origin;
/** What the server answers once it has accepted the credentials. */
let acceptedResponse = { status: 207, statusText: "Multi-Status" };
/** How the password prompt is answered, and how often it was shown. */
let passwordPrompt = { username: USERNAME, password: PASSWORD, remember: true };
/** Answers the password prompt instead of opening a window. */
const windowWatcher = {
QueryInterface: ChromeUtils.generateQI(["nsIWindowWatcher"]),
activeWindow: null,
openWindow(parent, url, name, features, args) {
Assert.equal(
url,
"the password prompt should be the window that opens"
);
const bag = args.QueryInterface(Ci.nsIWritablePropertyBag);
passwordPrompt.count++;
passwordPrompt.checkLabel = bag.getProperty("checkLabel");
bag.setProperty("user", passwordPrompt.username);
bag.setProperty("pass", passwordPrompt.password);
bag.setProperty("checked", passwordPrompt.remember);
bag.setProperty("ok", true);
},
};
/**
* Demands the credentials, then answers with whatever `acceptedResponse` says.
*
* @param {nsIHttpRequest} request - The received HTTP request.
* @param {nsIHttpResponse} response - The HTTP response to populate.
*/
function requestHandler(request, response) {
const expected = `Basic ${btoa(`${USERNAME}:${PASSWORD}`)}`;
if (
!request.hasHeader("Authorization") ||
request.getHeader("Authorization") != expected
) {
response.setStatusLine("1.1", 401, "Unauthorized");
response.setHeader("WWW-Authenticate", `Basic realm="${REALM}"`);
return;
}
response.setStatusLine(
"1.1",
acceptedResponse.status,
acceptedResponse.statusText
);
response.setHeader("Content-Type", "text/xml");
}
/**
* Create a directory pointing at the server, with a username that is no
* longer the right one.
*
* @param {string} dirPrefId - Pref ID of the new directory.
* @returns {CardDAVDirectory}
*/
function createDirectory(dirPrefId) {
const directory = new CardDAVDirectory();
directory._dirPrefId = dirPrefId;
directory._uid = dirPrefId;
directory.__prefBranch = Services.prefs.getBranch(
`ldap_2.servers.${dirPrefId}.`
);
directory.__prefBranch.setStringPref("carddav.url", `${origin}/`);
directory.__prefBranch.setStringPref("carddav.username", STALE_USERNAME);
return directory;
}
/**
* Forget every saved login and reset what the server and the prompt do. The
* credentials of an earlier task live on in the HTTP auth cache, which would
* spare the next one the password prompt.
*/
async function reset() {
await Services.logins.removeAllLoginsAsync();
Cc["@mozilla.org/network/http-auth-manager;1"]
.getService(Ci.nsIHttpAuthManager)
.clearAll();
acceptedResponse = { status: 207, statusText: "Multi-Status" };
passwordPrompt = {
username: USERNAME,
password: PASSWORD,
remember: true,
count: 0,
};
}
/**
* Check the logins saved for the server.
*
* @param {object[]} expected - Zero or one {username, password} object.
*/
async function assertSavedLogins(expected) {
const logins = await Services.logins.searchLoginsAsync({ origin });
Assert.deepEqual(
logins.map(login => ({
username: login.username,
password: login.password,
})),
expected,
"the right credentials should be saved for the server"
);
for (const login of logins) {
Assert.equal(
login.httpRealm,
REALM,
"the login should be saved for the realm the server named"
);
}
}
add_setup(async function () {
server = new HttpServer();
server.registerPathHandler("/", requestHandler);
server.start(-1);
const cid = MockRegistrar.register(
"@mozilla.org/embedcomp/window-watcher;1",
windowWatcher
);
registerCleanupFunction(async function () {
MockRegistrar.unregister(cid);
await new Promise(resolve => server.stop(resolve));
});
});
/** The server accepts the credentials and answers as expected. */
add_task(async function testExpectedStatus() {
await reset();
const directory = createDirectory("expectedStatus");
const response = await directory._makeRequest("", {
expectedStatuses: [207],
});
Assert.equal(response.status, 207, "the request should have succeeded");
Assert.equal(
passwordPrompt.count,
1,
"the user should have been prompted once"
);
Assert.ok(
passwordPrompt.checkLabel,
"the prompt should have offered to remember the password"
);
await assertSavedLogins([{ username: USERNAME, password: PASSWORD }]);
Assert.equal(
directory.getStringValue("carddav.username", ""),
USERNAME,
"the directory should have taken over the username that was typed"
);
});
/**
* The server accepts the credentials but refuses the request for some other
* reason. The credentials are good and must be kept.
*/
add_task(async function testUnexpectedStatus() {
await reset();
acceptedResponse = { status: 412, statusText: "Precondition Failed" };
const directory = createDirectory("unexpectedStatus");
await Assert.rejects(
directory._makeRequest("", { expectedStatuses: [207] }),
/Incorrect response from server: 412 Precondition Failed/,
"the request should have failed on the unexpected status"
);
Assert.equal(
passwordPrompt.count,
1,
"the user should have been prompted once"
);
await assertSavedLogins([{ username: USERNAME, password: PASSWORD }]);
Assert.equal(
directory.getStringValue("carddav.username", ""),
USERNAME,
"the directory should have taken over the username that was typed"
);
});
/** The server rejects the credentials. Nothing may be kept. */
add_task(async function testRejectedCredentials() {
await reset();
passwordPrompt.password = "not the password";
const directory = createDirectory("rejectedCredentials");
await Assert.rejects(
directory._makeRequest("", { expectedStatuses: [207] }),
/Authorization failure/,
"the request should have failed on the credentials"
);
Assert.equal(
passwordPrompt.count,
1,
"the user should have been prompted once"
);
await assertSavedLogins([]);
Assert.equal(
directory.getStringValue("carddav.username", ""),
STALE_USERNAME,
"the directory should have kept the username it had"
);
});
/** The user doesn't want the password remembered. */
add_task(async function testPasswordNotRemembered() {
await reset();
acceptedResponse = { status: 412, statusText: "Precondition Failed" };
passwordPrompt.remember = false;
const directory = createDirectory("passwordNotRemembered");
await Assert.rejects(
directory._makeRequest("", { expectedStatuses: [207] }),
/Incorrect response from server: 412 Precondition Failed/,
"the request should have failed on the unexpected status"
);
await assertSavedLogins([]);
});