1 // This file tests authentication prompt callbacks
2 // TODO NIT use do_check_eq(expected, actual) consistently, not sometimes eq(actual, expected)
6 const { HttpServer } = ChromeUtils.importESModule(
7 "resource://testing-common/httpd.sys.mjs"
10 // Turn off the authentication dialog blocking for this test.
11 var prefs = Services.prefs;
12 prefs.setIntPref("network.auth.subresource-http-auth-allow", 2);
14 ChromeUtils.defineLazyGetter(this, "URL", function () {
15 return "http://localhost:" + httpserv.identity.primaryPort;
18 ChromeUtils.defineLazyGetter(this, "PORT", function () {
19 return httpserv.identity.primaryPort;
22 const FLAG_RETURN_FALSE = 1 << 0;
23 const FLAG_WRONG_PASSWORD = 1 << 1;
24 const FLAG_BOGUS_USER = 1 << 2;
25 // const FLAG_PREVIOUS_FAILED = 1 << 3;
26 const CROSS_ORIGIN = 1 << 4;
27 const FLAG_NO_REALM = 1 << 5;
28 const FLAG_NON_ASCII_USER_PASSWORD = 1 << 6;
30 function AuthPrompt1(flags) {
34 AuthPrompt1.prototype = {
38 expectedRealm: "secret",
40 QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt"]),
42 prompt: function ap1_prompt() {
43 do_throw("unexpected prompt call");
46 promptUsernameAndPassword: function ap1_promptUP(
54 if (this.flags & FLAG_NO_REALM) {
55 // Note that the realm here isn't actually the realm. it's a pw mgr key.
56 Assert.equal(URL + " (" + this.expectedRealm + ")", realm);
58 if (!(this.flags & CROSS_ORIGIN)) {
59 if (!text.includes(this.expectedRealm)) {
60 do_throw("Text must indicate the realm");
62 } else if (text.includes(this.expectedRealm)) {
63 do_throw("There should not be realm for cross origin");
65 if (!text.includes("localhost")) {
66 do_throw("Text must indicate the hostname");
68 if (!text.includes(String(PORT))) {
69 do_throw("Text must indicate the port");
71 if (text.includes("-1")) {
72 do_throw("Text must contain negative numbers");
75 if (this.flags & FLAG_RETURN_FALSE) {
79 if (this.flags & FLAG_BOGUS_USER) {
80 this.user = "foo\nbar";
81 } else if (this.flags & FLAG_NON_ASCII_USER_PASSWORD) {
85 user.value = this.user;
86 if (this.flags & FLAG_WRONG_PASSWORD) {
87 pw.value = this.pass + ".wrong";
88 // Now clear the flag to avoid an infinite loop
89 this.flags &= ~FLAG_WRONG_PASSWORD;
90 } else if (this.flags & FLAG_NON_ASCII_USER_PASSWORD) {
98 promptPassword: function ap1_promptPW() {
99 do_throw("unexpected promptPassword call");
103 function AuthPrompt2(flags) {
107 AuthPrompt2.prototype = {
111 expectedRealm: "secret",
113 QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt2"]),
115 promptAuth: function ap2_promptAuth(channel, level, authInfo) {
116 authInfo.username = this.user;
117 authInfo.password = this.pass;
121 asyncPromptAuth: function ap2_async() {
122 throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
126 function Requestor(flags, versions) {
128 this.versions = versions;
131 Requestor.prototype = {
132 QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]),
134 getInterface: function requestor_gi(iid) {
135 if (this.versions & 1 && iid.equals(Ci.nsIAuthPrompt)) {
136 // Allow the prompt to store state by caching it here
138 this.prompt1 = new AuthPrompt1(this.flags);
142 if (this.versions & 2 && iid.equals(Ci.nsIAuthPrompt2)) {
143 // Allow the prompt to store state by caching it here
145 this.prompt2 = new AuthPrompt2(this.flags);
150 throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
157 function RealmTestRequestor() {}
159 RealmTestRequestor.prototype = {
160 QueryInterface: ChromeUtils.generateQI([
161 "nsIInterfaceRequestor",
165 getInterface: function realmtest_interface(iid) {
166 if (iid.equals(Ci.nsIAuthPrompt2)) {
170 throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
173 promptAuth: function realmtest_checkAuth(channel, level, authInfo) {
174 Assert.equal(authInfo.realm, '"foo_bar');
179 asyncPromptAuth: function realmtest_async() {
180 throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
184 function makeChan(url, loadingUrl) {
185 var principal = Services.scriptSecurityManager.createContentPrincipal(
186 Services.io.newURI(loadingUrl),
189 return NetUtil.newChannel({
191 loadingPrincipal: principal,
192 securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
193 contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
198 function authNtlmSimple(metadata, response) {
199 if (!metadata.hasHeader("Authorization")) {
200 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
201 response.setHeader("WWW-Authenticate", "NTLM", false);
205 let challenge = metadata.getHeader("Authorization");
206 if (!challenge.startsWith("NTLM ")) {
207 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
211 let decoded = atob(challenge.substring(5));
214 if (!decoded.startsWith("NTLMSSP\0")) {
215 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
219 let isNegotiate = decoded.substring(8).startsWith("\x01\x00\x00\x00");
220 let isAuthenticate = decoded.substring(8).startsWith("\x03\x00\x00\x00");
223 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
226 "NTLM TlRMTVNTUAACAAAAAAAAAAAoAAABggAAASNFZ4mrze8AAAAAAAAAAAAAAAAAAAAA",
232 if (isAuthenticate) {
234 response.bodyOutputStream.write(body, body.length);
238 // Something else went wrong.
239 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
243 add_task(async function test_ntlm() {
244 Services.prefs.setBoolPref("network.auth.force-generic-ntlm", true);
245 Services.prefs.setBoolPref("network.auth.force-generic-ntlm-v1", true);
247 httpserv = new HttpServer();
248 httpserv.registerPathHandler("/auth/ntlm/simple", authNtlmSimple);
251 registerCleanupFunction(async () => {
252 Services.prefs.clearUserPref("network.auth.force-generic-ntlm");
253 Services.prefs.clearUserPref("network.auth.force-generic-ntlm-v1");
255 await httpserv.stop();
258 var chan = makeChan(URL + "/auth/ntlm/simple", URL);
260 chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2);
261 let [req, buf] = await new Promise(resolve => {
263 new ChannelListener((req1, buf1) => resolve([req1, buf1]), null)
267 Assert.equal(req.QueryInterface(Ci.nsIHttpChannel).responseStatus, 200);