test_ntlm_web_auth.js (7918B)
1 // Unit tests for a NTLM authenticated web server. 2 // 3 // Currently the tests do not determine whether the Authentication dialogs have 4 // been displayed. 5 // 6 7 "use strict"; 8 9 const { HttpServer } = ChromeUtils.importESModule( 10 "resource://testing-common/httpd.sys.mjs" 11 ); 12 13 ChromeUtils.defineLazyGetter(this, "URL", function () { 14 return "http://localhost:" + httpserver.identity.primaryPort; 15 }); 16 17 function AuthPrompt() {} 18 19 AuthPrompt.prototype = { 20 user: "guest", 21 pass: "guest", 22 23 QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt2"]), 24 25 promptAuth: function ap_promptAuth(channel, level, authInfo) { 26 authInfo.username = this.user; 27 authInfo.password = this.pass; 28 29 return true; 30 }, 31 32 asyncPromptAuth: function ap_async() { 33 throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); 34 }, 35 }; 36 37 function Requestor() {} 38 39 Requestor.prototype = { 40 QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]), 41 42 getInterface: function requestor_gi(iid) { 43 if (iid.equals(Ci.nsIAuthPrompt2)) { 44 // Allow the prompt to store state by caching it here 45 if (!this.prompt) { 46 this.prompt = new AuthPrompt(); 47 } 48 return this.prompt; 49 } 50 51 throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE); 52 }, 53 54 prompt: null, 55 }; 56 57 function makeChan(url, loadingUrl) { 58 var principal = Services.scriptSecurityManager.createContentPrincipal( 59 Services.io.newURI(loadingUrl), 60 {} 61 ); 62 return NetUtil.newChannel({ 63 uri: url, 64 loadingPrincipal: principal, 65 securityFlags: Ci.nsILoadInfo.SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT, 66 contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER, 67 }); 68 } 69 70 function TestListener() {} 71 TestListener.prototype.onStartRequest = function (request) { 72 // Need to do the instanceof to allow request.responseStatus 73 // to be read. 74 if (!(request instanceof Ci.nsIHttpChannel)) { 75 dump("Expecting an HTTP channel"); 76 } 77 78 Assert.equal(expectedResponse, request.responseStatus, "HTTP Status code"); 79 }; 80 TestListener.prototype.onStopRequest = function () { 81 Assert.equal(expectedRequests, requestsMade, "Number of requests made "); 82 83 if (current_test < tests.length - 1) { 84 current_test++; 85 tests[current_test](); 86 } else { 87 do_test_pending(); 88 httpserver.stop(do_test_finished); 89 } 90 91 do_test_finished(); 92 }; 93 TestListener.prototype.onDataAvaiable = function ( 94 request, 95 context, 96 stream, 97 offset, 98 count 99 ) { 100 read_stream(stream, count); 101 }; 102 103 // NTLM Messages, for the received type 1 and 3 messages only check that they 104 // are of the expected type. 105 const NTLM_TYPE1_PREFIX = "NTLM TlRMTVNTUAABAAAA"; 106 const NTLM_TYPE2_PREFIX = "NTLM TlRMTVNTUAACAAAA"; 107 const NTLM_TYPE3_PREFIX = "NTLM TlRMTVNTUAADAAAA"; 108 const NTLM_PREFIX_LEN = 21; 109 110 const NTLM_CHALLENGE = 111 NTLM_TYPE2_PREFIX + 112 "DAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAAR" + 113 "ABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUg" + 114 "BWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwB" + 115 "lAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA="; 116 117 // Web server responses for the happy path scenario. 118 // i.e. successful web server auth 119 // 120 function successfulAuth(metadata, response) { 121 let authorization; 122 let authPrefix; 123 switch (requestsMade) { 124 case 0: 125 // Web Server - Initial request 126 // Will respond with a 401 to start web server auth sequence 127 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); 128 response.setHeader("WWW-Authenticate", "NTLM", false); 129 break; 130 case 1: 131 // Web Server - Expecting a type 1 negotiate message from the client 132 authorization = metadata.getHeader("Authorization"); 133 authPrefix = authorization.substring(0, NTLM_PREFIX_LEN); 134 Assert.equal(NTLM_TYPE1_PREFIX, authPrefix, "Expecting a Type 1 message"); 135 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); 136 response.setHeader("WWW-Authenticate", NTLM_CHALLENGE, false); 137 break; 138 case 2: 139 // Web Server - Expecting a type 3 Authenticate message from the client 140 authorization = metadata.getHeader("Authorization"); 141 authPrefix = authorization.substring(0, NTLM_PREFIX_LEN); 142 Assert.equal(NTLM_TYPE3_PREFIX, authPrefix, "Expecting a Type 3 message"); 143 response.setStatusLine(metadata.httpVersion, 200, "Successful"); 144 break; 145 default: 146 // We should be authenticated and further requests are permitted 147 authorization = metadata.getHeader("Authorization"); 148 Assert.isnull(authorization); 149 response.setStatusLine(metadata.httpVersion, 200, "Successful"); 150 } 151 requestsMade++; 152 } 153 154 // web server responses simulating an unsuccessful web server auth 155 function failedAuth(metadata, response) { 156 let authorization; 157 let authPrefix; 158 switch (requestsMade) { 159 case 0: 160 // Web Server - First request return a 401 to start auth sequence 161 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); 162 response.setHeader("WWW-Authenticate", "NTLM", false); 163 break; 164 case 1: 165 // Web Server - Expecting a type 1 negotiate message from the client 166 authorization = metadata.getHeader("Authorization"); 167 authPrefix = authorization.substring(0, NTLM_PREFIX_LEN); 168 Assert.equal(NTLM_TYPE1_PREFIX, authPrefix, "Expecting a Type 1 message"); 169 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); 170 response.setHeader("WWW-Authenticate", NTLM_CHALLENGE, false); 171 break; 172 case 2: 173 // Web Server - Expecting a type 3 Authenticate message from the client 174 // Respond with a 401 to restart the auth sequence. 175 authorization = metadata.getHeader("Authorization"); 176 authPrefix = authorization.substring(0, NTLM_PREFIX_LEN); 177 Assert.equal(NTLM_TYPE3_PREFIX, authPrefix, "Expecting a Type 1 message"); 178 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); 179 break; 180 default: 181 // We should not get called past step 2 182 // Strictly speaking the connection should not be used again 183 // commented out for testing 184 // dump( "ERROR: NTLM Auth failed connection should not be reused"); 185 //Assert.fail(); 186 response.setHeader("WWW-Authenticate", "NTLM", false); 187 } 188 requestsMade++; 189 } 190 191 var tests = [test_happy_path, test_failed_auth]; 192 var current_test = 0; 193 194 var httpserver = null; 195 function run_test() { 196 httpserver = new HttpServer(); 197 httpserver.start(-1); 198 199 tests[0](); 200 } 201 202 var expectedRequests = 0; // Number of HTTP requests that are expected 203 var requestsMade = 0; // The number of requests that were made 204 var expectedResponse = 0; // The response code 205 // Note that any test failures in the HTTP handler 206 // will manifest as a 500 response code 207 208 // Common test setup 209 // Parameters: 210 // path - path component of the URL 211 // handler - http handler function for the httpserver 212 // requests - expected number oh http requests 213 // response - expected http response code 214 // clearCache - clear the authentication cache before running the test 215 function setupTest(path, handler, requests, response, clearCache) { 216 requestsMade = 0; 217 expectedRequests = requests; 218 expectedResponse = response; 219 220 // clear the auth cache if requested 221 if (clearCache) { 222 dump("Clearing auth cache"); 223 Cc["@mozilla.org/network/http-auth-manager;1"] 224 .getService(Ci.nsIHttpAuthManager) 225 .clearAll(); 226 } 227 228 var chan = makeChan(URL + path, URL); 229 httpserver.registerPathHandler(path, handler); 230 chan.notificationCallbacks = new Requestor(); 231 chan.asyncOpen(new TestListener()); 232 233 return chan; 234 } 235 236 // Happy code path 237 // Succesful web server auth. 238 function test_happy_path() { 239 dump("RUNNING TEST: test_happy_path"); 240 setupTest("/auth", successfulAuth, 3, 200, 1); 241 242 do_test_pending(); 243 } 244 245 // Unsuccessful web server sign on 246 function test_failed_auth() { 247 dump("RUNNING TEST: test_failed_auth"); 248 setupTest("/auth", failedAuth, 3, 401, 1); 249 250 do_test_pending(); 251 }