Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

"use strict";
AddonTestUtils.createAppInfo(
"xpcshell@tests.mozilla.org",
"XPCShell",
"42",
"42"
);
AddonTestUtils.init(this);
AddonTestUtils.overrideCertDB();
const HOSTS = new Set(["example.com"]);
const server = createHttpServer({ hosts: HOSTS });
const BASE_URL = "http://example.com";
// Save seen realms for cache checking.
let realms = new Set([]);
server.registerPathHandler("/authenticate.sjs", (request, response) => {
let url = new URL(`${BASE_URL}${request.path}?${request.queryString}`);
let realm = url.searchParams.get("realm") || "mochitest";
let proxy_realm = url.searchParams.get("proxy_realm");
function checkAuthorization() {
let expected_user = url.searchParams.get("user");
if (!expected_user) {
return true;
}
let expected_pass = url.searchParams.get("pass");
let actual_user, actual_pass;
let authHeader = request.getHeader("Authorization");
let match = /Basic (.+)/.exec(authHeader);
if (match.length != 2) {
throw new Error("Couldn't parse auth header: " + authHeader);
}
let userpass = atob(match[1]); // no atob() :-(
match = /(.*):(.*)/.exec(userpass);
if (match.length != 3) {
throw new Error("Couldn't decode auth header: " + userpass);
}
actual_user = match[1];
actual_pass = match[2];
return expected_user === actual_user && expected_pass === actual_pass;
}
response.setHeader("Content-Type", "text/plain; charset=UTF-8", false);
if (proxy_realm && !request.hasHeader("Proxy-Authorization")) {
// We're not testing anything that requires checking the proxy auth user/password.
response.setStatusLine("1.0", 407, "Proxy authentication required");
response.setHeader(
"Proxy-Authenticate",
`basic realm="${proxy_realm}"`,
true
);
response.write("proxy auth required");
} else if (
!(
realms.has(realm) &&
request.hasHeader("Authorization") &&
checkAuthorization()
)
) {
realms.add(realm);
response.setStatusLine(request.httpVersion, 401, "Authentication required");
response.setHeader("WWW-Authenticate", `basic realm="${realm}"`, true);
response.write("auth required");
} else {
response.setStatusLine(request.httpVersion, 200, "OK");
response.write("ok, got authorization");
}
});
function getExtension(bgConfig, { permissions = ["webRequestBlocking"] } = {}) {
function background(config) {
let path = config.path;
browser.webRequest.onBeforeRequest.addListener(
details => {
browser.test.log(
`onBeforeRequest called with ${details.requestId} ${details.url}`
);
browser.test.sendMessage("onBeforeRequest");
return (
config.onBeforeRequest.hasOwnProperty("result") &&
config.onBeforeRequest.result
);
},
{ urls: [path] },
config.onBeforeRequest.hasOwnProperty("extra")
? config.onBeforeRequest.extra
: []
);
browser.webRequest.onAuthRequired.addListener(
(details, asyncBlockingCb) => {
browser.test.log(
`onAuthRequired called with ${details.requestId} ${details.url}`
);
browser.test.assertEq(
config.realm,
details.realm,
"providing www authorization"
);
if (config.asyncBlocking) {
browser.test.assertEq(
"function",
typeof asyncBlockingCb,
"expect onAuthRequired to receive a callback parameter with asyncBlocking extra"
);
// Delay the asyncBlockingCb call.
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
setTimeout(() => {
browser.test.sendMessage("onAuthRequired");
asyncBlockingCb(
config.onAuthRequired.hasOwnProperty("result")
? config.onAuthRequired.result
: null
);
}, 100);
// NOTE: Return values from an asyncBlocking listeners are expected to be ignored.
// If this return value isn't ignored then the test case is expected to get stuck
// and fail with a timeout, and test logs to show that the request was cancelled.
return { cancel: true };
}
browser.test.assertEq(
undefined,
asyncBlockingCb,
"expect no onAuthRequired callback parameter without asyncBlocking extra"
);
browser.test.sendMessage("onAuthRequired");
return (
config.onAuthRequired.hasOwnProperty("result") &&
config.onAuthRequired.result
);
},
{ urls: [path] },
config.onAuthRequired.hasOwnProperty("extra")
? config.onAuthRequired.extra
: []
);
browser.webRequest.onCompleted.addListener(
details => {
browser.test.log(
`onCompleted called with ${details.requestId} ${details.url}`
);
browser.test.sendMessage("onCompleted");
},
{ urls: [path] }
);
browser.webRequest.onErrorOccurred.addListener(
details => {
browser.test.log(
`onErrorOccurred called with ${JSON.stringify(details)}`
);
browser.test.sendMessage("onErrorOccurred");
},
{ urls: [path] }
);
}
return ExtensionTestUtils.loadExtension({
manifest: {
permissions: [bgConfig.path, "webRequest", ...permissions],
},
background: `(${background})(${JSON.stringify(bgConfig)})`,
});
}
async function test_webRequest_auth(
permissions,
{ asyncBlocking = false } = {}
) {
let config = {
asyncBlocking,
path: `${BASE_URL}/*`,
realm: `webRequest_auth${Math.random()}`,
onBeforeRequest: {
extra: permissions.includes("webRequestBlocking") ? ["blocking"] : [],
},
onAuthRequired: {
extra: asyncBlocking ? ["asyncBlocking"] : ["blocking"],
result: {
authCredentials: {
username: "testuser",
password: "testpass",
},
},
},
};
let extension = getExtension(config, { permissions });
await extension.startup();
let requestUrl = `${BASE_URL}/authenticate.sjs?realm=${config.realm}`;
let contentPage = await ExtensionTestUtils.loadContentPage(requestUrl);
await Promise.all([
extension.awaitMessage("onBeforeRequest"),
extension.awaitMessage("onAuthRequired").then(() => {
return Promise.all([
extension.awaitMessage("onBeforeRequest"),
extension.awaitMessage("onCompleted"),
]);
}),
]);
await contentPage.close();
// Second time around to test cached credentials
contentPage = await ExtensionTestUtils.loadContentPage(requestUrl);
await Promise.all([
extension.awaitMessage("onBeforeRequest"),
extension.awaitMessage("onCompleted"),
]);
await contentPage.close();
await extension.unload();
}
add_task(async function test_webRequest_auth_with_webRequestBlocking() {
await test_webRequest_auth(["webRequestBlocking"]);
});
add_task(async function test_webRequest_auth_with_webRequestAuthProvider() {
await test_webRequest_auth(["webRequestAuthProvider"]);
});
add_task(async function test_webRequest_auth_with_asyncBlocking() {
info("Test asyncBlocking with webRequestBlocking permission");
await test_webRequest_auth(["webRequestBlocking"], { asyncBlocking: true });
info("Test asyncBlocking with webRequestAuthProvider permission");
await test_webRequest_auth(["webRequestAuthProvider"], {
asyncBlocking: true,
});
});
add_task(async function test_mutually_exclusive_asyncBlocking_and_blocking() {
const extension = ExtensionTestUtils.loadExtension({
manifest: {
permissions: [
"webRequest",
"webRequestAuthProvider",
],
},
background() {
try {
browser.webRequest.onAuthRequired.addListener(
() => {},
{ urls: ["https://example.org/*"] },
["blocking", "asyncBlocking"]
);
browser.test.fail(
"onAuthRequired.addListener should have raised an exception"
);
browser.test.notifyFail("add-listener-error");
} catch (err) {
browser.test.assertEq(
"'blocking' and 'asyncBlocking' are mutually exclusive",
err.message,
"Got the expected error when blocking and asyncBlocking are requested for the same listener"
);
browser.test.notifyPass("add-listener-error");
}
},
});
await extension.startup();
await extension.awaitFinish("add-listener-error");
await extension.unload();
});
add_task(async function test_webRequest_auth_cancelled() {
// Test that any auth listener can cancel.
let config = {
path: `${BASE_URL}/*`,
realm: `webRequest_auth${Math.random()}`,
onBeforeRequest: {
extra: ["blocking"],
},
onAuthRequired: {
extra: ["blocking"],
result: {
authCredentials: {
username: "testuser",
password: "testpass",
},
},
},
};
let ex1 = getExtension(config);
config.onAuthRequired.result = { cancel: true };
let ex2 = getExtension(config);
await ex1.startup();
await ex2.startup();
let requestUrl = `${BASE_URL}/authenticate.sjs?realm=${config.realm}`;
let contentPage = await ExtensionTestUtils.loadContentPage(requestUrl);
await Promise.all([
ex1.awaitMessage("onBeforeRequest"),
ex1.awaitMessage("onAuthRequired"),
ex1.awaitMessage("onErrorOccurred"),
ex2.awaitMessage("onBeforeRequest"),
ex2.awaitMessage("onAuthRequired"),
ex2.awaitMessage("onErrorOccurred"),
]);
await contentPage.close();
await ex1.unload();
await ex2.unload();
});
add_task(async function test_webRequest_auth_nonblocking() {
let config = {
path: `${BASE_URL}/*`,
realm: `webRequest_auth${Math.random()}`,
onBeforeRequest: {
extra: ["blocking"],
},
onAuthRequired: {
extra: ["blocking"],
result: {
authCredentials: {
username: "testuser",
password: "testpass",
},
},
},
};
let ex1 = getExtension(config);
// non-blocking ext tries to cancel but cannot.
delete config.onBeforeRequest.extra;
delete config.onAuthRequired.extra;
config.onAuthRequired.result = { cancel: true };
let ex2 = getExtension(config);
await ex1.startup();
await ex2.startup();
let requestUrl = `${BASE_URL}/authenticate.sjs?realm=${config.realm}`;
let contentPage = await ExtensionTestUtils.loadContentPage(requestUrl);
await Promise.all([
ex1.awaitMessage("onBeforeRequest"),
ex1.awaitMessage("onAuthRequired").then(() => {
return Promise.all([
ex1.awaitMessage("onBeforeRequest"),
ex1.awaitMessage("onCompleted"),
]);
}),
ex2.awaitMessage("onBeforeRequest"),
ex2.awaitMessage("onAuthRequired").then(() => {
return Promise.all([
ex2.awaitMessage("onBeforeRequest"),
ex2.awaitMessage("onCompleted"),
]);
}),
]);
await contentPage.close();
Services.obs.notifyObservers(null, "net:clear-active-logins");
await ex1.unload();
await ex2.unload();
});
add_task(async function test_webRequest_auth_blocking_noreturn() {
// The first listener is blocking but doesn't return anything. The second
// listener cancels the request.
let config = {
path: `${BASE_URL}/*`,
realm: `webRequest_auth${Math.random()}`,
onBeforeRequest: {
extra: ["blocking"],
},
onAuthRequired: {
extra: ["blocking"],
},
};
let ex1 = getExtension(config);
config.onAuthRequired.result = { cancel: true };
let ex2 = getExtension(config);
await ex1.startup();
await ex2.startup();
let requestUrl = `${BASE_URL}/authenticate.sjs?realm=${config.realm}`;
let contentPage = await ExtensionTestUtils.loadContentPage(requestUrl);
await Promise.all([
ex1.awaitMessage("onBeforeRequest"),
ex1.awaitMessage("onAuthRequired"),
ex1.awaitMessage("onErrorOccurred"),
ex2.awaitMessage("onBeforeRequest"),
ex2.awaitMessage("onAuthRequired"),
ex2.awaitMessage("onErrorOccurred"),
]);
await contentPage.close();
await ex1.unload();
await ex2.unload();
});
add_task(async function test_webRequest_duelingAuth() {
let config = {
path: `${BASE_URL}/*`,
realm: `webRequest_auth${Math.random()}`,
onBeforeRequest: {
extra: ["blocking"],
},
onAuthRequired: {
extra: ["blocking"],
},
};
let exNone = getExtension(config);
await exNone.startup();
let authCredentials = {
username: `testuser_da1${Math.random()}`,
password: `testpass_da1${Math.random()}`,
};
config.onAuthRequired.result = { authCredentials };
let ex1 = getExtension(config);
await ex1.startup();
config.onAuthRequired.result = {};
let exEmpty = getExtension(config);
await exEmpty.startup();
config.onAuthRequired.result = {
authCredentials: {
username: `testuser_da2${Math.random()}`,
password: `testpass_da2${Math.random()}`,
},
};
let ex2 = getExtension(config);
await ex2.startup();
let requestUrl = `${BASE_URL}/authenticate.sjs?realm=${config.realm}&user=${authCredentials.username}&pass=${authCredentials.password}`;
let contentPage = await ExtensionTestUtils.loadContentPage(requestUrl);
await Promise.all([
exNone.awaitMessage("onBeforeRequest"),
exNone.awaitMessage("onAuthRequired").then(() => {
return Promise.all([
exNone.awaitMessage("onBeforeRequest"),
exNone.awaitMessage("onCompleted"),
]);
}),
exEmpty.awaitMessage("onBeforeRequest"),
exEmpty.awaitMessage("onAuthRequired").then(() => {
return Promise.all([
exEmpty.awaitMessage("onBeforeRequest"),
exEmpty.awaitMessage("onCompleted"),
]);
}),
ex1.awaitMessage("onBeforeRequest"),
ex1.awaitMessage("onAuthRequired").then(() => {
return Promise.all([
ex1.awaitMessage("onBeforeRequest"),
ex1.awaitMessage("onCompleted"),
]);
}),
ex2.awaitMessage("onBeforeRequest"),
ex2.awaitMessage("onAuthRequired").then(() => {
return Promise.all([
ex2.awaitMessage("onBeforeRequest"),
ex2.awaitMessage("onCompleted"),
]);
}),
]);
await Promise.all([
await contentPage.close(),
exNone.unload(),
exEmpty.unload(),
ex1.unload(),
ex2.unload(),
]);
});
add_task(async function test_webRequest_auth_proxy() {
function background(permissionPath) {
let proxyOk = false;
browser.webRequest.onAuthRequired.addListener(
details => {
browser.test.log(
`handlingExt onAuthRequired called with ${details.requestId} ${details.url}`
);
if (details.isProxy) {
browser.test.succeed("providing proxy authorization");
proxyOk = true;
return { authCredentials: { username: "puser", password: "ppass" } };
}
browser.test.assertTrue(
proxyOk,
"providing www authorization after proxy auth"
);
browser.test.sendMessage("done");
return { authCredentials: { username: "auser", password: "apass" } };
},
{ urls: [permissionPath] },
["blocking"]
);
}
let handlingExt = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["webRequest", "webRequestBlocking", `${BASE_URL}/*`],
},
background: `(${background})("${BASE_URL}/*")`,
});
await handlingExt.startup();
let requestUrl = `${BASE_URL}/authenticate.sjs?realm=webRequest_auth${Math.random()}&proxy_realm=proxy_auth${Math.random()}`;
let contentPage = await ExtensionTestUtils.loadContentPage(requestUrl);
await handlingExt.awaitMessage("done");
await contentPage.close();
await handlingExt.unload();
});
add_task(async function test_asyncBlocking_onAuthRequired_startup_primed() {
function background() {
browser.webRequest.onAuthRequired.addListener(
(detais, asyncBlockingCb) => {
asyncBlockingCb({ cancel: true });
browser.test.sendMessage("onAuthRequired");
},
{ urls: ["http://example.com/*"] },
["asyncBlocking"]
);
browser.test.sendMessage("listener-added");
}
await AddonTestUtils.promiseStartupManager();
const extension = ExtensionTestUtils.loadExtension({
useAddonManager: "permanent",
manifest: {
background: { persistent: true },
permissions: [
"webRequest",
"webRequestAuthProvider",
],
},
background,
});
async function testTriggerPrimedListener() {
let requestUrl = `${BASE_URL}/authenticate.sjs?realm=webRequest${Math.random()}`;
let contentPagePromise = ExtensionTestUtils.loadContentPage(requestUrl);
info("Wait for the background script to be respawned");
await extension.awaitMessage("listener-added");
info("Wait for the onAuthRequired listener to have been called");
await extension.awaitMessage("onAuthRequired");
const contentPage = await contentPagePromise;
await contentPage.close();
}
await extension.startup();
await extension.awaitMessage("listener-added");
assertPersistentListeners(extension, "webRequest", "onAuthRequired", {
primed: false,
});
await AddonTestUtils.promiseRestartManager({ earlyStartup: false });
await extension.awaitStartup();
assertPersistentListeners(extension, "webRequest", "onAuthRequired", {
primed: true,
});
AddonTestUtils.notifyEarlyStartup();
await testTriggerPrimedListener();
Services.obs.notifyObservers(null, "net:clear-active-logins");
await extension.unload();
});