test_ntlm_authentication.js (7197B)
1 // This file tests authentication prompt callbacks 2 // TODO NIT use do_check_eq(expected, actual) consistently, not sometimes eq(actual, expected) 3 4 "use strict"; 5 6 const { HttpServer } = ChromeUtils.importESModule( 7 "resource://testing-common/httpd.sys.mjs" 8 ); 9 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); 13 14 ChromeUtils.defineLazyGetter(this, "URL", function () { 15 return "http://localhost:" + httpserv.identity.primaryPort; 16 }); 17 18 ChromeUtils.defineLazyGetter(this, "PORT", function () { 19 return httpserv.identity.primaryPort; 20 }); 21 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; 29 30 function AuthPrompt1(flags) { 31 this.flags = flags; 32 } 33 34 AuthPrompt1.prototype = { 35 user: "guest", 36 pass: "guest", 37 38 expectedRealm: "secret", 39 40 QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt"]), 41 42 prompt: function ap1_prompt() { 43 do_throw("unexpected prompt call"); 44 }, 45 46 promptUsernameAndPassword: function ap1_promptUP( 47 title, 48 text, 49 realm, 50 savePW, 51 user, 52 pw 53 ) { 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); 57 } 58 if (!(this.flags & CROSS_ORIGIN)) { 59 if (!text.includes(this.expectedRealm)) { 60 do_throw("Text must indicate the realm"); 61 } 62 } else if (text.includes(this.expectedRealm)) { 63 do_throw("There should not be realm for cross origin"); 64 } 65 if (!text.includes("localhost")) { 66 do_throw("Text must indicate the hostname"); 67 } 68 if (!text.includes(String(PORT))) { 69 do_throw("Text must indicate the port"); 70 } 71 if (text.includes("-1")) { 72 do_throw("Text must contain negative numbers"); 73 } 74 75 if (this.flags & FLAG_RETURN_FALSE) { 76 return false; 77 } 78 79 if (this.flags & FLAG_BOGUS_USER) { 80 this.user = "foo\nbar"; 81 } else if (this.flags & FLAG_NON_ASCII_USER_PASSWORD) { 82 this.user = "é"; 83 } 84 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) { 91 pw.value = "é"; 92 } else { 93 pw.value = this.pass; 94 } 95 return true; 96 }, 97 98 promptPassword: function ap1_promptPW() { 99 do_throw("unexpected promptPassword call"); 100 }, 101 }; 102 103 function AuthPrompt2(flags) { 104 this.flags = flags; 105 } 106 107 AuthPrompt2.prototype = { 108 user: "guest", 109 pass: "guest", 110 111 expectedRealm: "secret", 112 113 QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt2"]), 114 115 promptAuth: function ap2_promptAuth(channel, level, authInfo) { 116 authInfo.username = this.user; 117 authInfo.password = this.pass; 118 return true; 119 }, 120 121 asyncPromptAuth: function ap2_async() { 122 throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); 123 }, 124 }; 125 126 function Requestor(flags, versions) { 127 this.flags = flags; 128 this.versions = versions; 129 } 130 131 Requestor.prototype = { 132 QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]), 133 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 137 if (!this.prompt1) { 138 this.prompt1 = new AuthPrompt1(this.flags); 139 } 140 return this.prompt1; 141 } 142 if (this.versions & 2 && iid.equals(Ci.nsIAuthPrompt2)) { 143 // Allow the prompt to store state by caching it here 144 if (!this.prompt2) { 145 this.prompt2 = new AuthPrompt2(this.flags); 146 } 147 return this.prompt2; 148 } 149 150 throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE); 151 }, 152 153 prompt1: null, 154 prompt2: null, 155 }; 156 157 function RealmTestRequestor() {} 158 159 RealmTestRequestor.prototype = { 160 QueryInterface: ChromeUtils.generateQI([ 161 "nsIInterfaceRequestor", 162 "nsIAuthPrompt2", 163 ]), 164 165 getInterface: function realmtest_interface(iid) { 166 if (iid.equals(Ci.nsIAuthPrompt2)) { 167 return this; 168 } 169 170 throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE); 171 }, 172 173 promptAuth: function realmtest_checkAuth(channel, level, authInfo) { 174 Assert.equal(authInfo.realm, '"foo_bar'); 175 176 return false; 177 }, 178 179 asyncPromptAuth: function realmtest_async() { 180 throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); 181 }, 182 }; 183 184 function makeChan(url, loadingUrl) { 185 var principal = Services.scriptSecurityManager.createContentPrincipal( 186 Services.io.newURI(loadingUrl), 187 {} 188 ); 189 return NetUtil.newChannel({ 190 uri: url, 191 loadingPrincipal: principal, 192 securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, 193 contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER, 194 }); 195 } 196 197 // /auth/ntlm/simple 198 function authNtlmSimple(metadata, response) { 199 if (!metadata.hasHeader("Authorization")) { 200 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); 201 response.setHeader("WWW-Authenticate", "NTLM", false); 202 return; 203 } 204 205 let challenge = metadata.getHeader("Authorization"); 206 if (!challenge.startsWith("NTLM ")) { 207 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); 208 return; 209 } 210 211 let decoded = atob(challenge.substring(5)); 212 info(decoded); 213 214 if (!decoded.startsWith("NTLMSSP\0")) { 215 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); 216 return; 217 } 218 219 let isNegotiate = decoded.substring(8).startsWith("\x01\x00\x00\x00"); 220 let isAuthenticate = decoded.substring(8).startsWith("\x03\x00\x00\x00"); 221 222 if (isNegotiate) { 223 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); 224 response.setHeader( 225 "WWW-Authenticate", 226 "NTLM TlRMTVNTUAACAAAAAAAAAAAoAAABggAAASNFZ4mrze8AAAAAAAAAAAAAAAAAAAAA", 227 false 228 ); 229 return; 230 } 231 232 if (isAuthenticate) { 233 let body = "OK"; 234 response.bodyOutputStream.write(body, body.length); 235 return; 236 } 237 238 // Something else went wrong. 239 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); 240 } 241 242 let httpserv; 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); 246 247 httpserv = new HttpServer(); 248 httpserv.registerPathHandler("/auth/ntlm/simple", authNtlmSimple); 249 httpserv.start(-1); 250 251 registerCleanupFunction(async () => { 252 Services.prefs.clearUserPref("network.auth.force-generic-ntlm"); 253 Services.prefs.clearUserPref("network.auth.force-generic-ntlm-v1"); 254 255 await httpserv.stop(); 256 }); 257 258 var chan = makeChan(URL + "/auth/ntlm/simple", URL); 259 260 chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2); 261 let [req, buf] = await new Promise(resolve => { 262 chan.asyncOpen( 263 new ChannelListener((req1, buf1) => resolve([req1, buf1]), null) 264 ); 265 }); 266 Assert.ok(buf); 267 Assert.equal(req.QueryInterface(Ci.nsIHttpChannel).responseStatus, 200); 268 });