Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* 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
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// nsHttpNTLMAuth::GenerateCredentialsAsync runs GenerateCredentials on a
// background thread, so a channel cancelled while that is in flight makes
// NTLMGetNextTokenCompleteEvent::Cancel run on the main thread concurrently
// with the background thread filling in the result. Whichever side wins, the
// callback must run exactly once and the channel must end up aborted.
//
// test_bug1683176.js covers the same race for Negotiate; nothing covered it
// for NTLM.
"use strict";
const { HttpServer } = ChromeUtils.importESModule(
);
let httpserv;
let requestsSeen = 0;
function authHandler(metadata, response) {
requestsSeen++;
response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
response.setHeader("WWW-Authenticate", "NTLM", false);
response.write("challenge");
}
// The generic NTLM module reports the identity as invalid, so the channel
// prompts before it generates anything. Answer synchronously to keep the only
// asynchrony in the test the credential generation itself.
function AuthPrompt() {}
AuthPrompt.prototype = {
QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt2"]),
promptAuth(channel, level, authInfo) {
authInfo.username = "guest";
authInfo.password = "guest";
return true;
},
asyncPromptAuth() {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
},
};
function Requestor() {}
Requestor.prototype = {
QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]),
getInterface(iid) {
if (iid.equals(Ci.nsIAuthPrompt2)) {
if (!this.prompt) {
this.prompt = new AuthPrompt();
}
return this.prompt;
}
throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
},
prompt: null,
};
// A content principal, so that the auth prompt is not blocked the way it is
// for non-web-content triggered loads.
function makeChan(url) {
let principal = Services.scriptSecurityManager.createContentPrincipal(
Services.io.newURI(url),
{}
);
let chan = NetUtil.newChannel({
uri: url,
loadingPrincipal: principal,
securityFlags: Ci.nsILoadInfo.SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT,
contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
}).QueryInterface(Ci.nsIHttpChannel);
chan.notificationCallbacks = new Requestor();
return chan;
}
add_setup(async function setup() {
Services.prefs.setIntPref("network.auth.subresource-http-auth-allow", 2);
// Without this the Windows SSPI module handles the challenge using the
// logged-in user's credentials, which the test has no control over.
Services.prefs.setBoolPref("network.auth.force-generic-ntlm", true);
httpserv = new HttpServer();
httpserv.registerPathHandler("/auth", authHandler);
httpserv.start(-1);
registerCleanupFunction(async () => {
Services.prefs.clearUserPref("network.auth.subresource-http-auth-allow");
Services.prefs.clearUserPref("network.auth.force-generic-ntlm");
await httpserv.stop();
});
});
function cancelWhenSuspendedForAuth(chan) {
return new Promise(resolve => {
let topic = "http-on-transaction-suspended-authentication";
let cancelled = false;
let observer = {
QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
observe(aSubject, aTopic) {
if (aTopic != topic) {
return;
}
Services.obs.removeObserver(observer, topic);
cancelled = true;
aSubject.QueryInterface(Ci.nsIChannel).cancel(Cr.NS_BINDING_ABORTED);
},
};
Services.obs.addObserver(observer, topic);
chan.asyncOpen(
new ChannelListener(
() => {
if (!cancelled) {
Services.obs.removeObserver(observer, topic);
}
resolve(cancelled);
},
null,
CL_EXPECT_FAILURE
)
);
});
}
add_task(async function test_cancel_during_async_creds() {
let url = `http://localhost:${httpserv.identity.primaryPort}/auth`;
let chan = makeChan(url);
let cancelled = await cancelWhenSuspendedForAuth(chan);
Assert.ok(cancelled, "the transaction was suspended for authentication");
Assert.equal(chan.status, Cr.NS_BINDING_ABORTED, "channel was aborted");
Assert.equal(requestsSeen, 1, "no type 1 message was sent");
});