1 // This file tests authentication prompt callbacks
3 do_import_script("netwerk/test/httpserver/httpd.js");
5 const FLAG_RETURN_FALSE
= 1 << 0;
6 const FLAG_WRONG_PASSWORD
= 1 << 1;
8 const nsIAuthPrompt2
= Components
.interfaces
.nsIAuthPrompt2
;
9 const nsIAuthInformation
= Components
.interfaces
.nsIAuthInformation
;
12 function AuthPrompt1(flags
) {
16 AuthPrompt1
.prototype = {
20 expectedRealm
: "secret",
22 QueryInterface
: function authprompt_qi(iid
) {
23 if (iid
.equals(Components
.interfaces
.nsISupports
) ||
24 iid
.equals(Components
.interfaces
.nsIAuthPrompt
))
26 throw Components
.results
.NS_ERROR_NO_INTERFACE
;
29 prompt
: function ap1_prompt(title
, text
, realm
, save
, defaultText
, result
) {
30 do_throw("unexpected prompt call");
33 promptUsernameAndPassword
:
34 function ap1_promptUP(title
, text
, realm
, savePW
, user
, pw
)
36 // Note that the realm here isn't actually the realm. it's a pw mgr key.
37 do_check_eq("localhost:4444 (" + this.expectedRealm
+ ")", realm
);
38 if (text
.indexOf(this.expectedRealm
) == -1)
39 do_throw("Text must indicate the realm");
40 if (text
.indexOf("localhost") == -1)
41 do_throw("Text must indicate the hostname");
42 if (text
.indexOf("4444") == -1)
43 do_throw("Text must indicate the port");
44 if (text
.indexOf("-1") != -1)
45 do_throw("Text must contain negative numbers");
47 if (this.flags
& FLAG_RETURN_FALSE
)
50 user
.value
= this.user
;
51 if (this.flags
& FLAG_WRONG_PASSWORD
) {
52 pw
.value
= this.pass
+ ".wrong";
53 // Now clear the flag to avoid an infinite loop
54 this.flags
&= ~FLAG_WRONG_PASSWORD
;
61 promptPassword
: function ap1_promptPW(title
, text
, realm
, save
, pwd
) {
62 do_throw("unexpected promptPassword call");
67 function AuthPrompt2(flags
) {
71 AuthPrompt2
.prototype = {
75 expectedRealm
: "secret",
77 QueryInterface
: function authprompt2_qi(iid
) {
78 if (iid
.equals(Components
.interfaces
.nsISupports
) ||
79 iid
.equals(Components
.interfaces
.nsIAuthPrompt2
))
81 throw Components
.results
.NS_ERROR_NO_INTERFACE
;
85 function ap2_promptAuth(channel
, level
, authInfo
)
87 var isNTLM
= channel
.URI
.path
.indexOf("ntlm") != -1;
88 var isDigest
= channel
.URI
.path
.indexOf("digest") != -1;
91 this.expectedRealm
= ""; // NTLM knows no realms
93 do_check_eq(this.expectedRealm
, authInfo
.realm
);
95 var expectedLevel
= (isNTLM
|| isDigest
) ?
96 nsIAuthPrompt2
.LEVEL_PW_ENCRYPTED
:
97 nsIAuthPrompt2
.LEVEL_NONE
;
98 do_check_eq(expectedLevel
, level
);
100 var expectedFlags
= nsIAuthInformation
.AUTH_HOST
;
103 expectedFlags
|= nsIAuthInformation
.NEED_DOMAIN
;
105 do_check_eq(expectedFlags
, authInfo
.flags
);
107 var expectedScheme
= isNTLM
? "ntlm" : isDigest
? "digest" : "basic";
108 do_check_eq(expectedScheme
, authInfo
.authenticationScheme
);
110 // No passwords in the URL -> nothing should be prefilled
111 do_check_eq(authInfo
.username
, "");
112 do_check_eq(authInfo
.password
, "");
113 do_check_eq(authInfo
.domain
, "");
115 if (this.flags
& FLAG_RETURN_FALSE
)
118 authInfo
.username
= this.user
;
119 if (this.flags
& FLAG_WRONG_PASSWORD
) {
120 authInfo
.password
= this.pass
+ ".wrong";
121 // Now clear the flag to avoid an infinite loop
122 this.flags
&= ~FLAG_WRONG_PASSWORD
;
124 authInfo
.password
= this.pass
;
129 asyncPromptAuth
: function ap2_async(chan
, cb
, ctx
, lvl
, info
) {
130 do_throw("not implemented yet")
134 function Requestor(flags
, versions
, username
) {
136 this.versions
= versions
;
137 this.username
= username
;
140 Requestor
.prototype = {
141 QueryInterface
: function requestor_qi(iid
) {
142 if (iid
.equals(Components
.interfaces
.nsISupports
) ||
143 iid
.equals(Components
.interfaces
.nsIInterfaceRequestor
))
145 throw Components
.results
.NS_ERROR_NO_INTERFACE
;
148 getInterface
: function requestor_gi(iid
) {
149 if (this.versions
& 1 &&
150 iid
.equals(Components
.interfaces
.nsIAuthPrompt
)) {
151 // Allow the prompt to store state by caching it here
153 this.prompt1
= new AuthPrompt1(this.flags
);
156 if (this.versions
& 2 &&
157 iid
.equals(Components
.interfaces
.nsIAuthPrompt2
)) {
158 // Allow the prompt to store state by caching it here
160 this.prompt2
= new AuthPrompt2(this.flags
);
162 this.prompt2
.user
= this.username
;
166 throw Components
.results
.NS_ERROR_NO_INTERFACE
;
173 function RealmTestRequestor() {}
175 RealmTestRequestor
.prototype = {
176 QueryInterface
: function realmtest_qi(iid
) {
177 if (iid
.equals(Components
.interfaces
.nsISupports
) ||
178 iid
.equals(Components
.interfaces
.nsIInterfaceRequestor
) ||
179 iid
.equals(Components
.interfaces
.nsIAuthPrompt2
))
181 throw Components
.results
.NS_ERROR_NO_INTERFACE
;
184 getInterface
: function realmtest_interface(iid
) {
185 if (iid
.equals(Components
.interfaces
.nsIAuthPrompt2
))
188 throw Components
.results
.NS_ERROR_NO_INTERFACE
;
191 promptAuth
: function realmtest_checkAuth(channel
, level
, authInfo
) {
192 do_check_eq(authInfo
.realm
, '\\"foo_bar');
197 asyncPromptAuth
: function realmtest_async(chan
, cb
, ctx
, lvl
, info
) {
198 do_throw("not implemented yet");
203 expectedCode
: 401, // Unauthorized
205 onStartRequest
: function test_onStartR(request
, ctx
) {
207 if (!Components
.isSuccessCode(request
.status
))
208 do_throw("Channel should have a success code!");
210 if (!(request
instanceof Components
.interfaces
.nsIHttpChannel
))
211 do_throw("Expecting an HTTP channel");
213 do_check_eq(request
.responseStatus
, this.expectedCode
);
214 // The request should be succeeded iff we expect 200
215 do_check_eq(request
.requestSucceeded
, this.expectedCode
== 200);
218 do_throw("Unexpected exception: " + e
);
221 throw Components
.results
.NS_ERROR_ABORT
;
224 onDataAvailable
: function test_ODA() {
225 do_throw("Should not get any data!");
228 onStopRequest
: function test_onStopR(request
, ctx
, status
) {
229 do_check_eq(status
, Components
.results
.NS_ERROR_ABORT
);
231 if (current_test
< (tests
.length
- 1)) {
232 // First, gotta clear the auth cache
233 Components
.classes
["@mozilla.org/network/http-auth-manager;1"]
234 .getService(Components
.interfaces
.nsIHttpAuthManager
)
238 tests
[current_test
]();
247 function makeChan(url
) {
248 var ios
= Components
.classes
["@mozilla.org/network/io-service;1"]
249 .getService(Components
.interfaces
.nsIIOService
);
250 var chan
= ios
.newChannel(url
, null, null)
251 .QueryInterface(Components
.interfaces
.nsIHttpChannel
);
256 var tests
= [test_noauth
, test_returnfalse1
, test_wrongpw1
, test_prompt1
,
257 test_returnfalse2
, test_wrongpw2
, test_prompt2
, test_ntlm
,
258 test_auth
, test_digest_noauth
, test_digest
,
259 test_digest_bogus_user
];
261 var current_test
= 0;
265 function run_test() {
266 httpserv
= new nsHttpServer();
268 httpserv
.registerPathHandler("/auth", authHandler
);
269 httpserv
.registerPathHandler("/auth/ntlm/simple", authNtlmSimple
);
270 httpserv
.registerPathHandler("/auth/realm", authRealm
);
271 httpserv
.registerPathHandler("/auth/digest", authDigest
);
273 httpserv
.start(4444);
278 function test_noauth() {
279 var chan
= makeChan("http://localhost:4444/auth");
281 listener
.expectedCode
= 401; // Unauthorized
282 chan
.asyncOpen(listener
, null);
287 function test_returnfalse1() {
288 var chan
= makeChan("http://localhost:4444/auth");
290 chan
.notificationCallbacks
= new Requestor(FLAG_RETURN_FALSE
, 1);
291 listener
.expectedCode
= 401; // Unauthorized
292 chan
.asyncOpen(listener
, null);
297 function test_wrongpw1() {
298 var chan
= makeChan("http://localhost:4444/auth");
300 chan
.notificationCallbacks
= new Requestor(FLAG_WRONG_PASSWORD
, 1);
301 listener
.expectedCode
= 200; // OK
302 chan
.asyncOpen(listener
, null);
307 function test_prompt1() {
308 var chan
= makeChan("http://localhost:4444/auth");
310 chan
.notificationCallbacks
= new Requestor(0, 1);
311 listener
.expectedCode
= 200; // OK
312 chan
.asyncOpen(listener
, null);
317 function test_returnfalse2() {
318 var chan
= makeChan("http://localhost:4444/auth");
320 chan
.notificationCallbacks
= new Requestor(FLAG_RETURN_FALSE
, 2);
321 listener
.expectedCode
= 401; // Unauthorized
322 chan
.asyncOpen(listener
, null);
327 function test_wrongpw2() {
328 var chan
= makeChan("http://localhost:4444/auth");
330 chan
.notificationCallbacks
= new Requestor(FLAG_WRONG_PASSWORD
, 2);
331 listener
.expectedCode
= 200; // OK
332 chan
.asyncOpen(listener
, null);
337 function test_prompt2() {
338 var chan
= makeChan("http://localhost:4444/auth");
340 chan
.notificationCallbacks
= new Requestor(0, 2);
341 listener
.expectedCode
= 200; // OK
342 chan
.asyncOpen(listener
, null);
347 function test_ntlm() {
348 var chan
= makeChan("http://localhost:4444/auth/ntlm/simple");
350 chan
.notificationCallbacks
= new Requestor(FLAG_RETURN_FALSE
, 2);
351 listener
.expectedCode
= 401; // Unauthorized
352 chan
.asyncOpen(listener
, null);
357 function test_auth() {
358 var chan
= makeChan("http://localhost:4444/auth/realm");
360 chan
.notificationCallbacks
= new RealmTestRequestor();
361 listener
.expectedCode
= 401; // Unauthorized
362 chan
.asyncOpen(listener
, null);
367 function test_digest_noauth() {
368 var chan
= makeChan("http://localhost:4444/auth/digest");
370 //chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2);
371 listener
.expectedCode
= 401; // Unauthorized
372 chan
.asyncOpen(listener
, null);
377 function test_digest() {
378 var chan
= makeChan("http://localhost:4444/auth/digest");
380 chan
.notificationCallbacks
= new Requestor(0, 2);
381 listener
.expectedCode
= 200; // OK
382 chan
.asyncOpen(listener
, null);
387 function test_digest_bogus_user() {
388 var chan
= makeChan("http://localhost:4444/auth/digest");
389 chan
.notificationCallbacks
= new Requestor(0, 2, "foo\nbar");
390 listener
.expectedCode
= 401; // unauthorized
391 chan
.asyncOpen(listener
, null);
399 function authHandler(metadata
, response
) {
400 // btoa("guest:guest"), but that function is not available here
401 var expectedHeader
= "Basic Z3Vlc3Q6Z3Vlc3Q=";
404 if (metadata
.hasHeader("Authorization") &&
405 metadata
.getHeader("Authorization") == expectedHeader
)
407 response
.setStatusLine(metadata
.httpVersion
, 200, "OK, authorized");
408 response
.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
414 // didn't know guest:guest, failure
415 response
.setStatusLine(metadata
.httpVersion
, 401, "Unauthorized");
416 response
.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
421 response
.bodyOutputStream
.write(body
, body
.length
);
425 function authNtlmSimple(metadata
, response
) {
426 response
.setStatusLine(metadata
.httpVersion
, 401, "Unauthorized");
427 response
.setHeader("WWW-Authenticate", "NTLM" /* + ' realm="secret"' */, false);
429 var body
= "NOTE: This just sends an NTLM challenge, it never\n" +
430 "accepts the authentication. It also closes\n" +
431 "the connection after sending the challenge\n";
434 response
.bodyOutputStream
.write(body
, body
.length
);
438 function authRealm(metadata
, response
) {
439 response
.setStatusLine(metadata
.httpVersion
, 401, "Unauthorized");
440 response
.setHeader("WWW-Authenticate", 'Basic realm="\\"foo_bar"', false);
441 var body
= "success";
443 response
.bodyOutputStream
.write(body
, body
.length
);
449 function bytesFromString(str
) {
451 Components
.classes
["@mozilla.org/intl/scriptableunicodeconverter"]
452 .createInstance(Components
.interfaces
.nsIScriptableUnicodeConverter
);
453 converter
.charset
= "UTF-8";
455 var data
= converter
.convertToByteArray(str
, result
);
459 // return the two-digit hexadecimal code for a byte
460 function toHexString(charCode
) {
461 return ("0" + charCode
.toString(16)).slice(-2);
465 var data
= bytesFromString(str
);
466 var ch
= Components
.classes
["@mozilla.org/security/hash;1"]
467 .createInstance(Components
.interfaces
.nsICryptoHash
);
468 ch
.init(Components
.interfaces
.nsICryptoHash
.MD5
);
469 ch
.update(data
, data
.length
);
470 var hash
= ch
.finish(false);
471 return [toHexString(hash
.charCodeAt(i
)) for (i
in hash
)].join("");
478 function authDigest(metadata
, response
) {
479 var nonce
= "6f93719059cf8d568005727f3250e798";
480 var opaque
= "1234opaque1234";
481 var cnonceRE
= /cnonce="(\w+)"/;
482 var responseRE
= /response="(\w+)"/;
483 var usernameRE
= /username="(\w+)"/;
484 var authenticate
= 'Digest realm="secret", domain="/", qop=auth,' +
485 'algorithm=MD5, nonce="' + nonce
+ '" opaque="' +
488 // check creds if we have them
489 if (metadata
.hasHeader("Authorization")) {
490 var auth
= metadata
.getHeader("Authorization");
491 var cnonce
= (auth
.match(cnonceRE
))[1];
492 var clientDigest
= (auth
.match(responseRE
))[1];
493 var username
= (auth
.match(usernameRE
))[1];
496 if (username
!= "guest") {
497 response
.setStatusLine(metadata
.httpVersion
, 400, "bad request");
498 body
= "should never get here";
500 // see RFC2617 for the description of this calculation
501 var A1
= "guest:secret:guest";
502 var A2
= "GET:/auth/digest";
503 var noncebits
= [nonce
, nc
, cnonce
, "auth", H(A2
)].join(":");
504 var digest
= H([H(A1
), noncebits
].join(":"));
506 if (clientDigest
== digest
) {
507 response
.setStatusLine(metadata
.httpVersion
, 200, "OK, authorized");
510 response
.setStatusLine(metadata
.httpVersion
, 401, "Unauthorized");
511 response
.setHeader("WWW-Authenticate", authenticate
, false);
512 body
= "auth failed";
516 // no header, send one
517 response
.setStatusLine(metadata
.httpVersion
, 401, "Unauthorized");
518 response
.setHeader("WWW-Authenticate", authenticate
, false);
519 body
= "failed, no header";
522 response
.bodyOutputStream
.write(body
, body
.length
);