// This file tests authentication prompt callbacks
// TODO NIT use do_check_eq(expected, actual) consistently, not sometimes eq(actual, expected)
"use strict";
const { HttpServer } = ChromeUtils.importESModule(
"resource://testing-common/httpd.sys.mjs"
);
// Turn off the authentication dialog blocking for this test.
Services.prefs.setIntPref(
"network.auth.subresource-http-auth-allow", 2);
ChromeUtils.defineLazyGetter(
this,
"URL",
function () {
return "http://localhost:" + httpserv.identity.primaryPort;
});
ChromeUtils.defineLazyGetter(
this,
"PORT",
function () {
return httpserv.identity.primaryPort;
});
const FLAG_RETURN_FALSE = 1 << 0;
const FLAG_WRONG_PASSWORD = 1 << 1;
const FLAG_BOGUS_USER = 1 << 2;
const FLAG_PREVIOUS_FAILED = 1 << 3;
const CROSS_ORIGIN = 1 << 4;
const FLAG_NO_REALM = 1 << 5;
const FLAG_NON_ASCII_USER_PASSWORD = 1 << 6;
const nsIAuthPrompt2 = Ci.nsIAuthPrompt2;
const nsIAuthInformation = Ci.nsIAuthInformation;
function AuthPrompt1(flags) {
this.flags = flags;
}
var initialChannelId = -1;
AuthPrompt1.prototype = {
user:
"guest",
pass:
"guest",
expectedRealm:
"secret",
QueryInterface: ChromeUtils.generateQI([
"nsIAuthPrompt"]),
prompt:
function ap1_prompt() {
do_throw(
"unexpected prompt call");
},
promptUsernameAndPassword:
function ap1_promptUP(
title,
text,
realm,
savePW,
user,
pw
) {
if (
this.flags & FLAG_NO_REALM) {
// Note that the realm here isn't actually the realm. it's a pw mgr key.
Assert.equal(URL +
" (" +
this.expectedRealm +
")", realm);
}
if (!(
this.flags & CROSS_ORIGIN)) {
if (!text.includes(
this.expectedRealm)) {
do_throw(
"Text must indicate the realm");
}
}
else if (text.includes(
this.expectedRealm)) {
do_throw(
"There should not be realm for cross origin");
}
if (!text.includes(
"localhost")) {
do_throw(
"Text must indicate the hostname");
}
if (!text.includes(String(PORT))) {
do_throw(
"Text must indicate the port");
}
if (text.includes(
"-1")) {
do_throw(
"Text must contain negative numbers");
}
if (
this.flags & FLAG_RETURN_FALSE) {
return false;
}
if (
this.flags & FLAG_BOGUS_USER) {
this.user =
"foo\nbar";
}
else if (
this.flags & FLAG_NON_ASCII_USER_PASSWORD) {
this.user =
"é";
}
user.value =
this.user;
if (
this.flags & FLAG_WRONG_PASSWORD) {
pw.value =
this.pass +
".wrong";
// Now clear the flag to avoid an infinite loop
this.flags &= ~FLAG_WRONG_PASSWORD;
}
else if (
this.flags & FLAG_NON_ASCII_USER_PASSWORD) {
pw.value =
"é";
}
else {
pw.value =
this.pass;
}
return true;
},
promptPassword:
function ap1_promptPW() {
do_throw(
"unexpected promptPassword call");
},
};
function AuthPrompt2(flags) {
this.flags = flags;
}
AuthPrompt2.prototype = {
user:
"guest",
pass:
"guest",
expectedRealm:
"secret",
QueryInterface: ChromeUtils.generateQI([
"nsIAuthPrompt2"]),
promptAuth:
function ap2_promptAuth(channel, level, authInfo) {
var isNTLM = channel.URI.pathQueryRef.includes(
"ntlm");
var isDigest = channel.URI.pathQueryRef.includes(
"digest");
if (isNTLM ||
this.flags & FLAG_NO_REALM) {
this.expectedRealm =
"";
// NTLM knows no realms
}
Assert.equal(
this.expectedRealm, authInfo.realm);
var expectedLevel =
isNTLM || isDigest
? nsIAuthPrompt2.LEVEL_PW_ENCRYPTED
: nsIAuthPrompt2.LEVEL_NONE;
Assert.equal(expectedLevel, level);
var expectedFlags = nsIAuthInformation.AUTH_HOST;
if (
this.flags & FLAG_PREVIOUS_FAILED) {
expectedFlags |= nsIAuthInformation.PREVIOUS_FAILED;
}
if (
this.flags & CROSS_ORIGIN) {
expectedFlags |= nsIAuthInformation.CROSS_ORIGIN_SUB_RESOURCE;
}
if (isNTLM) {
expectedFlags |= nsIAuthInformation.NEED_DOMAIN;
}
const kAllKnownFlags = 127;
// Don't fail test for newly added flags
Assert.equal(expectedFlags, authInfo.flags & kAllKnownFlags);
// eslint-disable-next-line no-nested-ternary
var expectedScheme = isNTLM ?
"ntlm" : isDigest ?
"digest" :
"basic";
Assert.equal(expectedScheme, authInfo.authenticationScheme);
// No passwords in the URL -> nothing should be prefilled
Assert.equal(authInfo.username,
"");
Assert.equal(authInfo.password,
"");
Assert.equal(authInfo.domain,
"");
if (
this.flags & FLAG_RETURN_FALSE) {
this.flags |= FLAG_PREVIOUS_FAILED;
return false;
}
if (
this.flags & FLAG_BOGUS_USER) {
this.user =
"foo\nbar";
}
else if (
this.flags & FLAG_NON_ASCII_USER_PASSWORD) {
this.user =
"é";
}
authInfo.username =
this.user;
if (
this.flags & FLAG_WRONG_PASSWORD) {
authInfo.password =
this.pass +
".wrong";
this.flags |= FLAG_PREVIOUS_FAILED;
// Now clear the flag to avoid an infinite loop
this.flags &= ~FLAG_WRONG_PASSWORD;
}
else if (
this.flags & FLAG_NON_ASCII_USER_PASSWORD) {
authInfo.password =
"é";
}
else {
authInfo.password =
this.pass;
this.flags &= ~FLAG_PREVIOUS_FAILED;
}
return true;
},
asyncPromptAuth:
function ap2_async(chan, cb, ctx, lvl, info) {
let self =
this;
executeSoon(
function () {
let ret = self.promptAuth(chan, lvl, info);
if (ret) {
cb.onAuthAvailable(ctx, info);
}
else {
cb.onAuthCancelled(ctx,
true);
}
});
},
};
function Requestor(flags, versions) {
this.flags = flags;
this.versions = versions;
}
Requestor.prototype = {
QueryInterface: ChromeUtils.generateQI([
"nsIInterfaceRequestor"]),
getInterface:
function requestor_gi(iid) {
if (
this.versions & 1 && iid.equals(Ci.nsIAuthPrompt)) {
// Allow the prompt to store state by caching it here
if (!
this.prompt1) {
this.prompt1 =
new AuthPrompt1(
this.flags);
}
return this.prompt1;
}
if (
this.versions & 2 && iid.equals(Ci.nsIAuthPrompt2)) {
// Allow the prompt to store state by caching it here
if (!
this.prompt2) {
this.prompt2 =
new AuthPrompt2(
this.flags);
}
return this.prompt2;
}
throw Components.Exception(
"", Cr.NS_ERROR_NO_INTERFACE);
},
prompt1:
null,
prompt2:
null,
};
function RealmTestRequestor() {
this.promptRealm =
"";
}
RealmTestRequestor.prototype = {
QueryInterface: ChromeUtils.generateQI([
"nsIInterfaceRequestor",
"nsIAuthPrompt2",
]),
getInterface:
function realmtest_interface(iid) {
if (iid.equals(Ci.nsIAuthPrompt2)) {
return this;
}
throw Components.Exception(
"", Cr.NS_ERROR_NO_INTERFACE);
},
promptAuth:
function realmtest_checkAuth(channel, level, authInfo) {
this.promptRealm = authInfo.realm;
return false;
},
asyncPromptAuth:
function realmtest_async() {
throw Components.Exception(
"", Cr.NS_ERROR_NOT_IMPLEMENTED);
},
};
var listener = {
expectedCode: -1,
// Uninitialized
nextTest: undefined,
expectRequestFail:
false,
onStartRequest:
function test_onStartR(request) {
try {
if (
!
this.expectRequestFail &&
!Components.isSuccessCode(request.status)
) {
do_throw(
"Channel should have a success code!");
}
if (!(request
instanceof Ci.nsIHttpChannel)) {
do_throw(
"Expecting an HTTP channel");
}
if (
Services.prefs.getBoolPref(
"network.auth.use_redirect_for_retries") &&
// we should skip redirect check if we do not expect to succeed
this.expectedCode == 200
) {
// ensure channel ids are initialized
Assert.notEqual(initialChannelId, -1);
// for each request we must use a unique channel ID.
// See Bug 1820807
var chan = request.QueryInterface(Ci.nsIIdentChannel);
Assert.notEqual(initialChannelId, chan.channelId);
}
Assert.equal(request.responseStatus,
this.expectedCode);
// The request should be succeeded if we expect 200
Assert.equal(request.requestSucceeded,
this.expectedCode == 200);
}
catch (e) {
do_throw(
"Unexpected exception: " + e);
}
throw Components.Exception(
"", Cr.NS_ERROR_ABORT);
},
onDataAvailable:
function test_ODA() {
do_throw(
"Should not get any data!");
},
onStopRequest:
function test_onStopR(request, status) {
Assert.equal(status, Cr.NS_ERROR_ABORT);
initialChannelId = -1;
this.nextTest();
},
};
let ChannelEventSink1 = {
_classDescription:
"WebRequest channel event sink",
_classID: Components.ID(
"115062f8-92f1-11e5-8b7f-08001110f7ec"),
_contractID:
"@mozilla.org/webrequest/channel-event-sink;1",
QueryInterface: ChromeUtils.generateQI([
"nsIChannelEventSink",
"nsIFactory"]),
init() {
Components.manager
.QueryInterface(Ci.nsIComponentRegistrar)
.registerFactory(
this._classID,
this._classDescription,
this._contractID,
this
);
},
register() {
Services.catMan.addCategoryEntry(
"net-channel-event-sinks",
this._contractID,
this._contractID,
false,
true
);
},
unregister() {
Services.catMan.deleteCategoryEntry(
"net-channel-event-sinks",
this._contractID,
false
);
},
// nsIChannelEventSink implementation
asyncOnChannelRedirect(oldChannel, newChannel, flags, redirectCallback) {
// Abort the redirection
redirectCallback.onRedirectVerifyCallback(Cr.NS_ERROR_ABORT);
},
// nsIFactory implementation
createInstance(iid) {
return this.QueryInterface(iid);
},
};
function makeChan(
url,
loadingUrl,
securityFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
contentPolicyType = Ci.nsIContentPolicy.TYPE_OTHER
) {
var principal = Services.scriptSecurityManager.createContentPrincipal(
Services.io.newURI(loadingUrl),
{}
);
return NetUtil.newChannel({
uri: url,
loadingPrincipal: principal,
securityFlags,
contentPolicyType,
});
}
var ChannelCreationObserver = {
QueryInterface: ChromeUtils.generateQI([
"nsIObserver"]),
observe(aSubject, aTopic) {
if (aTopic ==
"http-on-opening-request") {
initialChannelId = aSubject.QueryInterface(Ci.nsIIdentChannel).channelId;
}
},
};
var httpserv =
null;
function setup() {
httpserv =
new HttpServer();
httpserv.registerPathHandler(
"/auth", authHandler);
httpserv.registerPathHandler(
"/auth/stored/wrong/credentials/",
authHandlerWrongStoredCredentials
);
httpserv.registerPathHandler(
"/auth/ntlm/simple", authNtlmSimple);
httpserv.registerPathHandler(
"/auth/realm", authRealm);
httpserv.registerPathHandler(
"/auth/non_ascii", authNonascii);
httpserv.registerPathHandler(
"/auth/digest_md5", authDigestMD5);
httpserv.registerPathHandler(
"/auth/digest_md5sess", authDigestMD5sess);
httpserv.registerPathHandler(
"/auth/digest_sha256", authDigestSHA256);
httpserv.registerPathHandler(
"/auth/digest_sha256sess", authDigestSHA256sess);
httpserv.registerPathHandler(
"/auth/digest_sha256_md5", authDigestSHA256_MD5);
httpserv.registerPathHandler(
"/auth/digest_md5_sha256", authDigestMD5_SHA256);
httpserv.registerPathHandler(
"/auth/digest_md5_sha256_oneline",
authDigestMD5_SHA256_oneline
);
httpserv.registerPathHandler(
"/auth/short_digest", authShortDigest);
httpserv.registerPathHandler(
"/largeRealm", largeRealm);
httpserv.registerPathHandler(
"/largeDomain", largeDomain);
httpserv.registerPathHandler(
"/corp-coep", corpAndCoep);
httpserv.start(-1);
registerCleanupFunction(async () => {
await httpserv.stop();
});
Services.obs.addObserver(ChannelCreationObserver,
"http-on-opening-request");
}
setup();
async
function openAndListen(chan) {
await
new Promise(resolve => {
listener.nextTest = resolve;
chan.asyncOpen(listener);
});
Cc[
"@mozilla.org/network/http-auth-manager;1"]
.getService(Ci.nsIHttpAuthManager)
.clearAll();
}
async
function test_noauth() {
var chan = makeChan(URL +
"/auth", URL);
listener.expectedCode = 401;
// Unauthorized
await openAndListen(chan);
}
async
function test_returnfalse1() {
var chan = makeChan(URL +
"/auth", URL);
chan.notificationCallbacks =
new Requestor(FLAG_RETURN_FALSE, 1);
listener.expectedCode = 401;
// Unauthorized
await openAndListen(chan);
}
async
function test_wrongpw1() {
var chan = makeChan(URL +
"/auth", URL);
chan.notificationCallbacks =
new Requestor(FLAG_WRONG_PASSWORD, 1);
listener.expectedCode = 200;
// OK
await openAndListen(chan);
}
async
function test_prompt1() {
var chan = makeChan(URL +
"/auth", URL);
chan.notificationCallbacks =
new Requestor(0, 1);
listener.expectedCode = 200;
// OK
await openAndListen(chan);
}
async
function test_prompt1CrossOrigin() {
var chan = makeChan(URL +
"/auth",
"http://example.org");
chan.notificationCallbacks =
new Requestor(16, 1);
listener.expectedCode = 200;
// OK
await openAndListen(chan);
}
async
function test_prompt2CrossOrigin() {
var chan = makeChan(URL +
"/auth",
"http://example.org");
chan.notificationCallbacks =
new Requestor(16, 2);
listener.expectedCode = 200;
// OK
await openAndListen(chan);
}
async
function test_returnfalse2() {
var chan = makeChan(URL +
"/auth", URL);
chan.notificationCallbacks =
new Requestor(FLAG_RETURN_FALSE, 2);
listener.expectedCode = 401;
// Unauthorized
await openAndListen(chan);
}
async
function test_wrongpw2() {
var chan = makeChan(URL +
"/auth", URL);
chan.notificationCallbacks =
new Requestor(FLAG_WRONG_PASSWORD, 2);
listener.expectedCode = 200;
// OK
await openAndListen(chan);
}
var requestNum = 0;
var expectedRequestNum = 0;
async
function test_wrong_stored_passwd() {
// tests that we don't retry auth requests for incorrect custom credentials passed during channel creation
requestNum = 0;
expectedRequestNum = 1;
var chan = makeChan(URL +
"/auth/stored/wrong/credentials/", URL);
chan.nsIHttpChannel.setRequestHeader(
"Authorization",
"wrong_cred",
false);
chan.notificationCallbacks =
new Requestor(0, 1);
listener.expectedCode = 401;
// Unauthorized
await openAndListen(chan);
}
async
function test_prompt2() {
var chan = makeChan(URL +
"/auth", URL);
chan.notificationCallbacks =
new Requestor(0, 2);
listener.expectedCode = 200;
// OK
await openAndListen(chan);
}
async
function test_ntlm() {
var chan = makeChan(URL +
"/auth/ntlm/simple", URL);
chan.notificationCallbacks =
new Requestor(FLAG_RETURN_FALSE, 2);
listener.expectedCode = 401;
// Unauthorized
await openAndListen(chan);
}
async
function test_basicrealm() {
var chan = makeChan(URL +
"/auth/realm", URL);
let requestor =
new RealmTestRequestor();
chan.notificationCallbacks = requestor;
listener.expectedCode = 401;
// Unauthorized
await openAndListen(chan);
Assert.equal(requestor.promptRealm,
'"foo_bar');
}
async
function test_nonascii() {
var chan = makeChan(URL +
"/auth/non_ascii", URL);
chan.notificationCallbacks =
new Requestor(FLAG_NON_ASCII_USER_PASSWORD, 2);
listener.expectedCode = 200;
// OK
await openAndListen(chan);
}
async
function test_digest_noauth() {
var chan = makeChan(URL +
"/auth/digest_md5", URL);
// chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2);
listener.expectedCode = 401;
// Unauthorized
await openAndListen(chan);
}
async
function test_digest_md5() {
var chan = makeChan(URL +
"/auth/digest_md5", URL);
chan.notificationCallbacks =
new Requestor(0, 2);
listener.expectedCode = 200;
// OK
await openAndListen(chan);
}
add_task(
{ pref_set: [[
"network.auth.use_redirect_for_retries",
true]] },
async
function test_digest_md5_redirect_veto() {
ChannelEventSink1.init();
ChannelEventSink1.register();
var chan = makeChan(URL +
"/auth/digest_md5", URL);
chan.notificationCallbacks =
new Requestor(0, 1);
listener.expectedCode = 401;
// Unauthorized
listener.expectRequestFail =
true;
await openAndListen(chan);
ChannelEventSink1.unregister();
listener.expectRequestFail =
false;
}
);
async
function test_digest_md5sess() {
var chan = makeChan(URL +
"/auth/digest_md5sess", URL);
chan.notificationCallbacks =
new Requestor(0, 2);
listener.expectedCode = 200;
// OK
await openAndListen(chan);
}
async
function test_digest_sha256() {
var chan = makeChan(URL +
"/auth/digest_sha256", URL);
chan.notificationCallbacks =
new Requestor(0, 2);
listener.expectedCode = 200;
// OK
await openAndListen(chan);
}
async
function test_digest_sha256sess() {
var chan = makeChan(URL +
"/auth/digest_sha256sess", URL);
chan.notificationCallbacks =
new Requestor(0, 2);
listener.expectedCode = 200;
// OK
await openAndListen(chan);
}
async
function test_digest_sha256_md5() {
var chan = makeChan(URL +
"/auth/digest_sha256_md5", URL);
chan.notificationCallbacks =
new Requestor(0, 2);
listener.expectedCode = 200;
// OK
await openAndListen(chan);
}
async
function test_digest_md5_sha256() {
var chan = makeChan(URL +
"/auth/digest_md5_sha256", URL);
chan.notificationCallbacks =
new Requestor(0, 2);
listener.expectedCode = 200;
// OK
await openAndListen(chan);
}
async
function test_digest_md5_sha256_oneline() {
var chan = makeChan(URL +
"/auth/digest_md5_sha256_oneline", URL);
chan.notificationCallbacks =
new Requestor(0, 2);
listener.expectedCode = 200;
// OK
await openAndListen(chan);
}
async
function test_digest_bogus_user() {
var chan = makeChan(URL +
"/auth/digest_md5", URL);
chan.notificationCallbacks =
new Requestor(FLAG_BOGUS_USER, 2);
listener.expectedCode = 401;
// unauthorized
await openAndListen(chan);
}
// Test header "WWW-Authenticate: Digest" - bug 1338876.
async
function test_short_digest() {
var chan = makeChan(URL +
"/auth/short_digest", URL);
chan.notificationCallbacks =
new Requestor(FLAG_NO_REALM, 2);
listener.expectedCode = 401;
// OK
await openAndListen(chan);
}
// Test that COOP/COEP are processed even though asyncPromptAuth is cancelled.
async
function test_corp_coep() {
var chan = makeChan(
URL +
"/corp-coep",
URL,
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT,
Ci.nsIContentPolicy.TYPE_DOCUMENT
);
chan.notificationCallbacks =
new Requestor(FLAG_RETURN_FALSE, 2);
listener.expectedCode = 401;
// OK
await openAndListen(chan);
Assert.equal(
chan.getResponseHeader(
"cross-origin-embedder-policy"),
"require-corp"
);
Assert.equal(
chan.getResponseHeader(
"cross-origin-opener-policy"),
"same-origin"
);
}
// XXX(valentin): this makes tests fail if it's not run last. Why?
async
function test_nonascii_xhr() {
await
new Promise(resolve => {
let xhr =
new XMLHttpRequest();
xhr.open(
"GET", URL +
"/auth/non_ascii",
true,
"é",
"é");
xhr.onreadystatechange =
function () {
if (xhr.readyState == 4) {
Assert.equal(xhr.status, 200);
resolve();
xhr.onreadystatechange =
null;
}
};
xhr.send(
null);
});
}
let auth_tests = [
test_noauth,
test_returnfalse1,
test_wrongpw1,
test_wrong_stored_passwd,
test_prompt1,
test_prompt1CrossOrigin,
test_prompt2CrossOrigin,
test_returnfalse2,
test_wrongpw2,
test_prompt2,
test_ntlm,
test_basicrealm,
test_nonascii,
test_digest_noauth,
test_digest_md5,
test_digest_md5sess,
test_digest_sha256,
test_digest_sha256sess,
test_digest_sha256_md5,
test_digest_md5_sha256,
test_digest_md5_sha256_oneline,
test_digest_bogus_user,
test_short_digest,
test_corp_coep,
test_nonascii_xhr,
];
for (let auth_test of auth_tests) {
add_task(
{ pref_set: [[
"network.auth.use_redirect_for_retries",
false]] },
auth_test
);
}
for (let auth_test of auth_tests) {
add_task(
{ pref_set: [[
"network.auth.use_redirect_for_retries",
true]] },
auth_test
);
}
// PATH HANDLERS
// /auth
function authHandler(metadata, response) {
// btoa("guest:guest"), but that function is not available here
var expectedHeader =
"Basic Z3Vlc3Q6Z3Vlc3Q=";
var body;
if (
metadata.hasHeader(
"Authorization") &&
metadata.getHeader(
"Authorization") == expectedHeader
) {
response.setStatusLine(metadata.httpVersion, 200,
"OK, authorized");
response.setHeader(
"WWW-Authenticate",
'Basic realm="secret"',
false);
body =
"success";
}
else {
// didn't know guest:guest, failure
response.setStatusLine(metadata.httpVersion, 401,
"Unauthorized");
response.setHeader(
"WWW-Authenticate",
'Basic realm="secret"',
false);
body =
"failed";
}
response.bodyOutputStream.write(body, body.length);
}
function authHandlerWrongStoredCredentials(metadata, response) {
var body;
if (++requestNum > expectedRequestNum) {
response.setStatusLine(metadata.httpVersion, 500,
"");
}
else {
response.setStatusLine(
metadata.httpVersion,
401,
"Unauthorized" + requestNum
);
response.setHeader(
"WWW-Authenticate",
'Basic realm="secret"',
false);
}
body =
"failed";
response.bodyOutputStream.write(body, body.length);
}
// /auth/ntlm/simple
function authNtlmSimple(metadata, response) {
response.setStatusLine(metadata.httpVersion, 401,
"Unauthorized");
response.setHeader(
"WWW-Authenticate",
"NTLM" /* + ' realm="secret"' */,
false
);
var body =
"NOTE: This just sends an NTLM challenge, it never\n" +
"accepts the authentication. It also closes\n" +
"the connection after sending the challenge\n";
response.bodyOutputStream.write(body, body.length);
}
// /auth/realm
function authRealm(metadata, response) {
response.setStatusLine(metadata.httpVersion, 401,
"Unauthorized");
response.setHeader(
"WWW-Authenticate",
'Basic realm="\\"f\\oo_bar"',
false);
var body =
"success";
response.bodyOutputStream.write(body, body.length);
}
// /auth/nonAscii
function authNonascii(metadata, response) {
// btoa("é:é"), but that function is not available here
var expectedHeader =
"Basic w6k6w6k=";
var body;
if (
metadata.hasHeader(
"Authorization") &&
metadata.getHeader(
"Authorization") == expectedHeader
) {
response.setStatusLine(metadata.httpVersion, 200,
"OK, authorized");
response.setHeader(
"WWW-Authenticate",
'Basic realm="secret"',
false);
// Use correct XML syntax since this function is also used for testing XHR.
body =
"success";
}
else {
// didn't know é:é, failure
response.setStatusLine(metadata.httpVersion, 401,
"Unauthorized");
response.setHeader(
"WWW-Authenticate",
'Basic realm="secret"',
false);
body =
"failed";
}
response.bodyOutputStream.write(body, body.length);
}
function corpAndCoep(metadata, response) {
response.setStatusLine(metadata.httpVersion, 401,
"Unauthorized");
response.setHeader(
"cross-origin-embedder-policy",
"require-corp");
response.setHeader(
"cross-origin-opener-policy",
"same-origin");
response.setHeader(
"WWW-Authenticate",
'Basic realm="secret"',
false);
}
//
// Digest functions
//
function bytesFromString(str) {
return new TextEncoder().encode(str);
}
// return the two-digit hexadecimal code for a byte
function toHexString(charCode) {
return (
"0" + charCode.toString(16)).slice(-2);
}
function HMD5(str) {
var data = bytesFromString(str);
var ch = Cc[
"@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
ch.init(Ci.nsICryptoHash.MD5);
ch.update(data, data.length);
var hash = ch.finish(
false);
return Array.from(hash, (c, i) => toHexString(hash.charCodeAt(i))).join(
"");
}
function HSHA256(str) {
var data = bytesFromString(str);
var ch = Cc[
"@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
ch.init(Ci.nsICryptoHash.SHA256);
ch.update(data, data.length);
var hash = ch.finish(
false);
return Array.from(hash, (c, i) => toHexString(hash.charCodeAt(i))).join(
"");
}
//
// Digest handler
//
// /auth/digest
function authDigestMD5_helper(metadata, response, test_name) {
var nonce =
"6f93719059cf8d568005727f3250e798";
var opaque =
"1234opaque1234";
var body;
var send_401 = 0;
// check creds if we have them
if (metadata.hasHeader(
"Authorization")) {
var cnonceRE = /cnonce=
"(\w+)"/;
var responseRE = /response=
"(\w+)"/;
var usernameRE = /username=
"(\w+)"/;
var algorithmRE = /algorithm=([\w-]+)/;
var auth = metadata.getHeader(
"Authorization");
var cnonce = auth.match(cnonceRE)[1];
var clientDigest = auth.match(responseRE)[1];
var username = auth.match(usernameRE)[1];
var algorithm = auth.match(algorithmRE)[1];
var nc =
"00000001";
if (username !=
"guest") {
response.setStatusLine(metadata.httpVersion, 400,
"bad request");
body =
"should never get here";
}
else if (
algorithm !=
null &&
algorithm !=
"MD5" &&
algorithm !=
"MD5-sess"
) {
response.setStatusLine(metadata.httpVersion, 400,
"bad request");
body =
"Algorithm must be same as provided in WWW-Authenticate header";
}
else {
// see RFC2617 for the description of this calculation
var A1 =
"guest:secret:guest";
if (algorithm ==
"MD5-sess") {
A1 = [HMD5(A1), nonce, cnonce].join(
":");
}
var A2 =
"GET:/auth/" + test_name;
var noncebits = [nonce, nc, cnonce,
"auth", HMD5(A2)].join(
":");
var digest = HMD5([HMD5(A1), noncebits].join(
":"));
if (clientDigest == digest) {
response.setStatusLine(metadata.httpVersion, 200,
"OK, authorized");
body =
"success";
}
else {
send_401 = 1;
body =
"auth failed";
}
}
}
else {
// no header, send one
send_401 = 1;
body =
"failed, no header";
}
if (send_401) {
var authenticate_md5 =
'Digest realm="secret", domain="/", qop=auth,' +
'algorithm=MD5, nonce="' +
nonce +
'" opaque="' +
opaque +
'"';
var authenticate_md5sess =
'Digest realm="secret", domain="/", qop=auth,' +
'algorithm=MD5, nonce="' +
nonce +
'" opaque="' +
opaque +
'"';
if (test_name ==
"digest_md5") {
response.setHeader(
"WWW-Authenticate", authenticate_md5,
false);
}
else if (test_name ==
"digest_md5sess") {
response.setHeader(
"WWW-Authenticate", authenticate_md5sess,
false);
}
response.setStatusLine(metadata.httpVersion, 401,
"Unauthorized");
}
response.bodyOutputStream.write(body, body.length);
}
function authDigestMD5(metadata, response) {
authDigestMD5_helper(metadata, response,
"digest_md5");
}
function authDigestMD5sess(metadata, response) {
authDigestMD5_helper(metadata, response,
"digest_md5sess");
}
function authDigestSHA256_helper(metadata, response, test_name) {
var nonce =
"6f93719059cf8d568005727f3250e798";
var opaque =
"1234opaque1234";
var body;
var send_401 = 0;
// check creds if we have them
if (metadata.hasHeader(
"Authorization")) {
var cnonceRE = /cnonce=
"(\w+)"/;
var responseRE = /response=
"(\w+)"/;
var usernameRE = /username=
"(\w+)"/;
var algorithmRE = /algorithm=([\w-]+)/;
var auth = metadata.getHeader(
"Authorization");
var cnonce = auth.match(cnonceRE)[1];
var clientDigest = auth.match(responseRE)[1];
var username = auth.match(usernameRE)[1];
var algorithm = auth.match(algorithmRE)[1];
var nc =
"00000001";
if (username !=
"guest") {
response.setStatusLine(metadata.httpVersion, 400,
"bad request");
body =
"should never get here";
}
else if (algorithm !=
"SHA-256" && algorithm !=
"SHA-256-sess") {
response.setStatusLine(metadata.httpVersion, 400,
"bad request");
body =
"Algorithm must be same as provided in WWW-Authenticate header";
}
else {
// see RFC7616 for the description of this calculation
var A1 =
"guest:secret:guest";
if (algorithm ==
"SHA-256-sess") {
A1 = [HSHA256(A1), nonce, cnonce].join(
":");
}
var A2 =
"GET:/auth/" + test_name;
var noncebits = [nonce, nc, cnonce,
"auth", HSHA256(A2)].join(
":");
var digest = HSHA256([HSHA256(A1), noncebits].join(
":"));
if (clientDigest == digest) {
response.setStatusLine(metadata.httpVersion, 200,
"OK, authorized");
body =
"success";
}
else {
send_401 = 1;
body =
"auth failed";
}
}
}
else {
// no header, send one
send_401 = 1;
body =
"failed, no header";
}
if (send_401) {
var authenticate_sha256 =
'Digest realm="secret", domain="/", qop=auth, ' +
'algorithm=SHA-256, nonce="' +
nonce +
'", opaque="' +
opaque +
'"';
var authenticate_sha256sess =
'Digest realm="secret", domain="/", qop=auth, ' +
'algorithm=SHA-256-sess, nonce="' +
nonce +
'", opaque="' +
opaque +
'"';
var authenticate_md5 =
'Digest realm="secret", domain="/", qop=auth, ' +
'algorithm=MD5, nonce="' +
nonce +
'", opaque="' +
opaque +
'"';
if (test_name ==
"digest_sha256") {
response.setHeader(
"WWW-Authenticate", authenticate_sha256,
false);
}
else if (test_name ==
"digest_sha256sess") {
response.setHeader(
"WWW-Authenticate", authenticate_sha256sess,
false);
}
else if (test_name ==
"digest_md5_sha256") {
response.setHeader(
"WWW-Authenticate", authenticate_md5,
false);
response.setHeader(
"WWW-Authenticate", authenticate_sha256,
true);
}
else if (test_name ==
"digest_md5_sha256_oneline") {
response.setHeader(
"WWW-Authenticate",
authenticate_md5 +
" " + authenticate_sha256,
false
);
}
else if (test_name ==
"digest_sha256_md5") {
response.setHeader(
"WWW-Authenticate", authenticate_sha256,
false);
response.setHeader(
"WWW-Authenticate", authenticate_md5,
true);
}
response.setStatusLine(metadata.httpVersion, 401,
"Unauthorized");
}
response.bodyOutputStream.write(body, body.length);
}
function authDigestSHA256(metadata, response) {
authDigestSHA256_helper(metadata, response,
"digest_sha256");
}
function authDigestSHA256sess(metadata, response) {
authDigestSHA256_helper(metadata, response,
"digest_sha256sess");
}
function authDigestSHA256_MD5(metadata, response) {
authDigestSHA256_helper(metadata, response,
"digest_sha256_md5");
}
function authDigestMD5_SHA256(metadata, response) {
authDigestSHA256_helper(metadata, response,
"digest_md5_sha256");
}
function authDigestMD5_SHA256_oneline(metadata, response) {
authDigestSHA256_helper(metadata, response,
"digest_md5_sha256_oneline");
}
function authShortDigest(metadata, response) {
// no header, send one
response.setStatusLine(metadata.httpVersion, 401,
"Unauthorized");
response.setHeader(
"WWW-Authenticate",
"Digest",
false);
}
let buildLargePayload = (
function () {
let size = 33 * 1024;
let ret =
"";
return function () {
// Return cached value.
if (ret.length) {
return ret;
}
for (let i = 0; i < size; i++) {
ret +=
"a";
}
return ret;
};
})();
function largeRealm(metadata, response) {
// test > 32KB realm tokens
var body;
response.setStatusLine(metadata.httpVersion, 401,
"Unauthorized");
response.setHeader(
"WWW-Authenticate",
'Digest realm="' + buildLargePayload() +
'", domain="foo"'
);
body =
"need to authenticate";
response.bodyOutputStream.write(body, body.length);
}
function largeDomain(metadata, response) {
// test > 32KB domain tokens
var body;
response.setStatusLine(metadata.httpVersion, 401,
"Unauthorized");
response.setHeader(
"WWW-Authenticate",
'Digest realm="foo", domain="' + buildLargePayload() +
'"'
);
body =
"need to authenticate";
response.bodyOutputStream.write(body, body.length);
}
add_task(async
function test_large_realm() {
var chan = makeChan(URL +
"/largeRealm", URL);
listener.expectedCode = 401;
// Unauthorized
await openAndListen(chan);
});
add_task(async
function test_large_domain() {
var chan = makeChan(URL +
"/largeDomain", URL);
listener.expectedCode = 401;
// Unauthorized
await openAndListen(chan);
});
async
function add_parse_realm_testcase(testcase) {
httpserv.registerPathHandler(
"/parse_realm", (metadata, response) => {
response.setStatusLine(metadata.httpVersion, 401,
"Unauthorized");
response.setHeader(
"WWW-Authenticate", testcase.input,
false);
let body =
"failed";
response.bodyOutputStream.write(body, body.length);
});
let chan = makeChan(URL +
"/parse_realm", URL);
let requestor =
new RealmTestRequestor();
chan.notificationCallbacks = requestor;
listener.expectedCode = 401;
await openAndListen(chan);
Assert.equal(requestor.promptRealm, testcase.realm);
}
add_task(async
function simplebasic() {
await add_parse_realm_testcase({
input: `Basic realm=
"foo"`,
scheme: `Basic`,
realm: `foo`,
});
});
add_task(async
function simplebasiclf() {
await add_parse_realm_testcase({
input: `Basic\r\n realm=
"foo"`,
scheme: `Basic`,
realm: `foo`,
});
});
add_task(async
function simplebasicucase() {
await add_parse_realm_testcase({
input: `BASIC REALM=
"foo"`,
scheme: `Basic`,
realm: `foo`,
});
});
add_task(async
function simplebasictok() {
await add_parse_realm_testcase({
input: `Basic realm=foo`,
scheme: `Basic`,
realm: `foo`,
});
});
add_task(async
function simplebasictokbs() {
await add_parse_realm_testcase({
input: `Basic realm=\\f\\o\\o`,
scheme: `Basic`,
realm: `\\foo`,
});
});
add_task(async
function simplebasicsq() {
await add_parse_realm_testcase({
input: `Basic realm=
'foo'`,
scheme: `Basic`,
realm: `
'foo'`,
});
});
add_task(async
function simplebasicpct() {
await add_parse_realm_testcase({
input: `Basic realm=
"foo%20bar"`,
scheme: `Basic`,
realm: `foo%20bar`,
});
});
add_task(async
function simplebasiccomma() {
await add_parse_realm_testcase({
input: `Basic , realm=
"foo"`,
scheme: `Basic`,
realm: `foo`,
});
});
add_task(async
function simplebasiccomma2() {
await add_parse_realm_testcase({
input: `Basic, realm=
"foo"`,
scheme: `Basic`,
realm: ``,
});
});
add_task(async
function simplebasicnorealm() {
await add_parse_realm_testcase({
input: `Basic`,
scheme: `Basic`,
realm: ``,
});
});
add_task(async
function simplebasic2realms() {
await add_parse_realm_testcase({
input: `Basic realm=
"foo", realm=
"bar"`,
scheme: `Basic`,
realm: `foo`,
});
});
add_task(async
function simplebasicwsrealm() {
await add_parse_realm_testcase({
input: `Basic realm =
"foo"`,
scheme: `Basic`,
realm: `foo`,
});
});
add_task(async
function simplebasicrealmsqc() {
await add_parse_realm_testcase({
input: `Basic realm=
"\\f\\o\\o"`,
scheme: `Basic`,
realm: `foo`,
});
});
add_task(async
function simplebasicrealmsqc2() {
await add_parse_realm_testcase({
input: `Basic realm=
"\\"foo\\
""`,
scheme: `Basic`,
realm: `
"foo"`,
});
});
add_task(async
function simplebasicnewparam1() {
await add_parse_realm_testcase({
input: `Basic realm=
"foo", bar=
"xyz",, a=b,,,c=d`,
scheme: `Basic`,
realm: `foo`,
});
});
add_task(async
function simplebasicnewparam2() {
await add_parse_realm_testcase({
input: `Basic bar=
"xyz", realm=
"foo"`,
scheme: `Basic`,
realm: `foo`,
});
});
add_task(async
function simplebasicrealmiso88591() {
await add_parse_realm_testcase({
input: `Basic realm=
"foo-ä"`,
scheme: `Basic`,
realm: `foo-ä`,
});
});
add_task(async
function simplebasicrealmutf8() {
await add_parse_realm_testcase({
input: `Basic realm=
"foo-ä"`,
scheme: `Basic`,
realm: `foo-ä`,
});
});
add_task(async
function simplebasicrealmrfc2047() {
await add_parse_realm_testcase({
input: `Basic realm=
"=?ISO-8859-1?Q?foo-=E4?="`,
scheme: `Basic`,
realm: `=?ISO-8859-1?Q?foo-=E4?=`,
});
});
add_task(async
function multibasicunknown() {
await add_parse_realm_testcase({
input: `Basic realm=
"basic", Newauth realm=
"newauth"`,
scheme: `Basic`,
realm: `basic`,
});
});
add_task(async
function multibasicunknownnoparam() {
await add_parse_realm_testcase({
input: `Basic realm=
"basic", Newauth`,
scheme: `Basic`,
realm: `basic`,
});
});
add_task(async
function multibasicunknown2() {
await add_parse_realm_testcase({
input: `Newauth realm=
"newauth", Basic realm=
"basic"`,
scheme: `Basic`,
realm: `basic`,
});
});
add_task(async
function multibasicunknown2np() {
await add_parse_realm_testcase({
input: `Newauth, Basic realm=
"basic"`,
scheme: `Basic`,
realm: `basic`,
});
});
add_task(async
function multibasicunknown2mf() {
httpserv.registerPathHandler(
"/parse_realm", (metadata, response) => {
response.setStatusLine(metadata.httpVersion, 401,
"Unauthorized");
response.setHeader(
"WWW-Authenticate", `Newauth realm=
"newauth"`,
false);
response.setHeader(
"WWW-Authenticate", `Basic realm=
"basic"`,
false);
let body =
"failed";
response.bodyOutputStream.write(body, body.length);
});
let chan = makeChan(URL +
"/parse_realm", URL);
let requestor =
new RealmTestRequestor();
chan.notificationCallbacks = requestor;
listener.expectedCode = 401;
await openAndListen(chan);
Assert.equal(requestor.promptRealm,
"basic");
});
add_task(async
function multibasicempty() {
await add_parse_realm_testcase({
input: `,Basic realm=
"basic"`,
scheme: `Basic`,
realm: `basic`,
});
});
add_task(async
function multibasicqs() {
await add_parse_realm_testcase({
input: `Newauth realm=
"apps", type=1, title=
"Login to \"apps\
"", Basic realm=
"simple"`
,
scheme: `Basic`,
realm: `simple`,
});
});
add_task(async function multidisgscheme() {
await add_parse_realm_testcase({
input: `Newauth realm="Newauth Realm", basic=foo, Basic realm="Basic Realm"`,
scheme: `Basic`,
realm: `Basic Realm`,
});
});
add_task(async function unknown() {
await add_parse_realm_testcase({
input: `Newauth param="value"`,
scheme: `Basic`,
realm: ``,
});
});
add_task(async function parametersnotrequired() {
await add_parse_realm_testcase({ input: `A, B`, scheme: `Basic`, realm: `` });
});
add_task(async function disguisedrealm() {
await add_parse_realm_testcase({
input: `Basic foo="realm=nottherealm", realm="basic"`,
scheme: `Basic`,
realm: `basic`,
});
});
add_task(async function disguisedrealm2() {
await add_parse_realm_testcase({
input: `Basic nottherealm="nottherealm", realm="basic"`,
scheme: `Basic`,
realm: `basic`,
});
});
add_task(async function missingquote() {
await add_parse_realm_testcase({
input: `Basic realm="basic`,
scheme: `Basic`,
realm: `basic`,
});
});