Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 import "package:expect/expect.dart"; | 5 import "package:expect/expect.dart"; |
| 6 import 'dart:async'; | 6 import 'dart:async'; |
| 7 import 'dart:crypto'; | 7 import 'dart:crypto'; |
| 8 import 'dart:io'; | 8 import 'dart:io'; |
| 9 import 'dart:isolate'; | 9 import 'dart:isolate'; |
| 10 import 'dart:uri'; | 10 import 'dart:uri'; |
| 11 import 'dart:utf'; | 11 import 'dart:utf'; |
| 12 | 12 |
| 13 class Server { | 13 class Server { |
| 14 HttpServer server; | 14 HttpServer server; |
| 15 bool passwordChanged = false; | 15 int unauthCount = 0; // Counter of the 401 responses. |
| 16 int successCount = 0; // Counter of the successful responses. | |
| 17 int nonceCount = 0; // Counter of use of current nonce. | |
| 16 var ha1; | 18 var ha1; |
| 17 | 19 |
| 18 static Future<Server> start(String algorithm, String qop) { | 20 static Future<Server> start(String algorithm, |
| 19 return new Server()._start(algorithm, qop); | 21 String qop, |
| 22 {int nonceStaleAfter, | |
| 23 bool useNextNonce: false}) { | |
|
Anders Johnsen
2013/05/03 12:07:35
indentation
| |
| 24 return new Server()._start(algorithm, qop, nonceStaleAfter, useNextNonce); | |
| 20 } | 25 } |
| 21 | 26 |
| 22 Future<Server> _start(String serverAlgorithm, String serverQop) { | 27 Future<Server> _start(String serverAlgorithm, |
| 28 String serverQop, | |
| 29 int nonceStaleAfter, | |
| 30 bool useNextNonce) { | |
| 31 Set ncs = new Set(); | |
| 23 // Calculate ha1. | 32 // Calculate ha1. |
| 24 String realm = "test"; | 33 String realm = "test"; |
| 25 String username = "dart"; | 34 String username = "dart"; |
| 26 String password = "password"; | 35 String password = "password"; |
| 27 var hasher = new MD5(); | 36 var hasher = new MD5(); |
| 28 hasher.add("${username}:${realm}:${password}".codeUnits); | 37 hasher.add("${username}:${realm}:${password}".codeUnits); |
| 29 ha1 = CryptoUtils.bytesToHex(hasher.close()); | 38 ha1 = CryptoUtils.bytesToHex(hasher.close()); |
| 30 | 39 |
| 40 var nonce = "12345678"; // No need for random nonce in test. | |
| 41 | |
| 31 var completer = new Completer(); | 42 var completer = new Completer(); |
| 32 HttpServer.bind("127.0.0.1", 0).then((s) { | 43 HttpServer.bind("127.0.0.1", 0).then((s) { |
| 33 server = s; | 44 server = s; |
| 34 server.listen((HttpRequest request) { | 45 server.listen((HttpRequest request) { |
| 35 // Just use a fixed nonce. | 46 sendUnauthorizedResponse(HttpResponse response, {stale: false}) { |
| 36 var nonce = "12345678"; | |
| 37 | |
| 38 sendUnauthorizedResponse(HttpResponse response) { | |
| 39 response.statusCode = HttpStatus.UNAUTHORIZED; | 47 response.statusCode = HttpStatus.UNAUTHORIZED; |
| 40 StringBuffer authHeader = new StringBuffer(); | 48 StringBuffer authHeader = new StringBuffer(); |
| 41 authHeader.write('Digest'); | 49 authHeader.write('Digest'); |
| 42 authHeader.write(', realm="$realm"'); | 50 authHeader.write(', realm="$realm"'); |
| 43 authHeader.write(', nonce="$nonce"'); | 51 authHeader.write(', nonce="$nonce"'); |
| 52 if (stale) authHeader.write(', stale="true"'); | |
| 44 if (serverAlgorithm != null) { | 53 if (serverAlgorithm != null) { |
| 45 authHeader.write(', algorithm=$serverAlgorithm'); | 54 authHeader.write(', algorithm=$serverAlgorithm'); |
| 46 } | 55 } |
| 47 authHeader.write(', domain="/digest/"'); | 56 authHeader.write(', domain="/digest/"'); |
| 48 if (serverQop != null) authHeader.write(', qop="$serverQop"'); | 57 if (serverQop != null) authHeader.write(', qop="$serverQop"'); |
| 49 response.headers.set(HttpHeaders.WWW_AUTHENTICATE, authHeader); | 58 response.headers.set(HttpHeaders.WWW_AUTHENTICATE, authHeader); |
| 59 unauthCount++; | |
| 50 } | 60 } |
| 51 | 61 |
| 52 var response = request.response; | 62 var response = request.response; |
| 53 if (request.headers[HttpHeaders.AUTHORIZATION] != null) { | 63 if (request.headers[HttpHeaders.AUTHORIZATION] != null) { |
| 54 Expect.equals(1, request.headers[HttpHeaders.AUTHORIZATION].length); | 64 Expect.equals(1, request.headers[HttpHeaders.AUTHORIZATION].length); |
| 55 String authorization = | 65 String authorization = |
| 56 request.headers[HttpHeaders.AUTHORIZATION][0]; | 66 request.headers[HttpHeaders.AUTHORIZATION][0]; |
| 57 HeaderValue header = | 67 HeaderValue header = |
| 58 new HeaderValue.fromString( | 68 new HeaderValue.fromString( |
| 59 authorization, parameterSeparator: ","); | 69 authorization, parameterSeparator: ","); |
| 60 if (header.value == "basic") { | 70 if (header.value == "basic") { |
| 61 sendUnauthorizedResponse(response); | 71 sendUnauthorizedResponse(response); |
| 72 } else if (!useNextNonce && nonceCount == nonceStaleAfter) { | |
| 73 nonce = "87654321"; | |
| 74 nonceCount = 0; | |
| 75 sendUnauthorizedResponse(response, stale: true); | |
| 62 } else { | 76 } else { |
| 63 var uri = header.parameters["uri"]; | 77 var uri = header.parameters["uri"]; |
| 64 var qop = header.parameters["qop"]; | 78 var qop = header.parameters["qop"]; |
| 65 var cnonce = header.parameters["cnonce"]; | 79 var cnonce = header.parameters["cnonce"]; |
| 66 var nc = header.parameters["nc"]; | 80 var nc = header.parameters["nc"]; |
| 67 Expect.equals("digest", header.value); | 81 Expect.equals("digest", header.value); |
| 68 Expect.equals("dart", header.parameters["username"]); | 82 Expect.equals("dart", header.parameters["username"]); |
| 69 Expect.equals(realm, header.parameters["realm"]); | 83 Expect.equals(realm, header.parameters["realm"]); |
| 70 Expect.equals("MD5", header.parameters["algorithm"]); | 84 Expect.equals("MD5", header.parameters["algorithm"]); |
| 71 Expect.equals(nonce, header.parameters["nonce"]); | 85 Expect.equals(nonce, header.parameters["nonce"]); |
| 72 Expect.equals(request.uri.path, uri); | 86 Expect.equals(request.uri.path, uri); |
| 73 if (qop != null) { | 87 if (qop != null) { |
| 74 // A server qop of auth-int is downgraded to none by the client. | 88 // A server qop of auth-int is downgraded to none by the client. |
| 75 Expect.equals("auth", serverQop); | 89 Expect.equals("auth", serverQop); |
| 76 Expect.equals("auth", header.parameters["qop"]); | 90 Expect.equals("auth", header.parameters["qop"]); |
| 77 Expect.isNotNull(cnonce); | 91 Expect.isNotNull(cnonce); |
| 78 Expect.isNotNull(nc); | 92 Expect.isNotNull(nc); |
| 93 Expect.isFalse(ncs.contains(nc)); | |
| 94 ncs.add(nc); | |
| 79 } else { | 95 } else { |
| 80 Expect.isNull(cnonce); | 96 Expect.isNull(cnonce); |
| 81 Expect.isNull(nc); | 97 Expect.isNull(nc); |
| 82 } | 98 } |
| 83 Expect.isNotNull(header.parameters["response"]); | 99 Expect.isNotNull(header.parameters["response"]); |
| 84 | 100 |
| 85 var hasher = new MD5(); | 101 var hasher = new MD5(); |
| 86 hasher.add("${request.method}:${uri}".codeUnits); | 102 hasher.add("${request.method}:${uri}".codeUnits); |
| 87 var ha2 = CryptoUtils.bytesToHex(hasher.close()); | 103 var ha2 = CryptoUtils.bytesToHex(hasher.close()); |
| 88 | 104 |
| 89 var x; | 105 var x; |
| 90 hasher = new MD5(); | 106 hasher = new MD5(); |
| 91 if (qop == null || qop == "" || qop == "none") { | 107 if (qop == null || qop == "" || qop == "none") { |
| 92 hasher.add("$ha1:${nonce}:$ha2".codeUnits); | 108 hasher.add("$ha1:${nonce}:$ha2".codeUnits); |
| 93 } else { | 109 } else { |
| 94 hasher.add("$ha1:${nonce}:${nc}:${cnonce}:${qop}:$ha2".codeUnits); | 110 hasher.add("$ha1:${nonce}:${nc}:${cnonce}:${qop}:$ha2".codeUnits); |
| 95 } | 111 } |
| 96 Expect.equals(CryptoUtils.bytesToHex(hasher.close()), | 112 Expect.equals(CryptoUtils.bytesToHex(hasher.close()), |
| 97 header.parameters["response"]); | 113 header.parameters["response"]); |
| 98 | 114 |
| 115 successCount++; | |
| 116 nonceCount++; | |
| 117 | |
| 99 // Add a bogus Authentication-Info for testing. | 118 // Add a bogus Authentication-Info for testing. |
| 100 var info = 'rspauth="77180d1ab3d6c9de084766977790f482", ' | 119 var info = 'rspauth="77180d1ab3d6c9de084766977790f482", ' |
| 101 'cnonce="8f971178", ' | 120 'cnonce="8f971178", ' |
| 102 'nc=000002c74, ' | 121 'nc=000002c74, ' |
| 103 'qop=auth'; | 122 'qop=auth'; |
| 123 if (useNextNonce && nonceCount == nonceStaleAfter) { | |
| 124 nonce = "abcdef01"; | |
| 125 info += ', nextnonce="$nonce"'; | |
| 126 } | |
| 104 response.headers.set("Authentication-Info", info); | 127 response.headers.set("Authentication-Info", info); |
| 105 } | 128 } |
| 106 } else { | 129 } else { |
| 107 sendUnauthorizedResponse(response); | 130 sendUnauthorizedResponse(response); |
| 108 } | 131 } |
| 109 response.close(); | 132 response.close(); |
| 110 }); | 133 }); |
| 111 completer.complete(this); | 134 completer.complete(this); |
| 112 }); | 135 }); |
| 113 return completer.future; | 136 return completer.future; |
| (...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 183 makeRequest( | 206 makeRequest( |
| 184 Uri.parse("http://127.0.0.1:${server.port}/digest"))); | 207 Uri.parse("http://127.0.0.1:${server.port}/digest"))); |
| 185 } | 208 } |
| 186 Future.wait(futures).then((_) { | 209 Future.wait(futures).then((_) { |
| 187 server.shutdown(); | 210 server.shutdown(); |
| 188 client.close(); | 211 client.close(); |
| 189 }); | 212 }); |
| 190 }); | 213 }); |
| 191 } | 214 } |
| 192 | 215 |
| 193 void testBasicAuthenticateCallback(String algorithm, String qop) { | 216 void testAuthenticateCallback(String algorithm, String qop) { |
| 194 Server.start(algorithm, qop).then((server) { | 217 Server.start(algorithm, qop).then((server) { |
| 195 HttpClient client = new HttpClient(); | 218 HttpClient client = new HttpClient(); |
| 196 | 219 |
| 197 client.authenticate = (Uri url, String scheme, String realm) { | 220 client.authenticate = (Uri url, String scheme, String realm) { |
| 198 Expect.equals("Digest", scheme); | 221 Expect.equals("Digest", scheme); |
| 199 Expect.equals("test", realm); | 222 Expect.equals("test", realm); |
| 200 Completer completer = new Completer(); | 223 Completer completer = new Completer(); |
| 201 new Timer(const Duration(milliseconds: 10), () { | 224 new Timer(const Duration(milliseconds: 10), () { |
| 202 client.addCredentials( | 225 client.addCredentials( |
| 203 Uri.parse("http://127.0.0.1:${server.port}/digest"), | 226 Uri.parse("http://127.0.0.1:${server.port}/digest"), |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 224 makeRequest( | 247 makeRequest( |
| 225 Uri.parse("http://127.0.0.1:${server.port}/digest"))); | 248 Uri.parse("http://127.0.0.1:${server.port}/digest"))); |
| 226 } | 249 } |
| 227 Future.wait(futures).then((_) { | 250 Future.wait(futures).then((_) { |
| 228 server.shutdown(); | 251 server.shutdown(); |
| 229 client.close(); | 252 client.close(); |
| 230 }); | 253 }); |
| 231 }); | 254 }); |
| 232 } | 255 } |
| 233 | 256 |
| 257 void testStaleNonce() { | |
| 258 Server.start("MD5", "auth", nonceStaleAfter: 2).then((server) { | |
| 259 HttpClient client = new HttpClient(); | |
| 260 | |
| 261 Future makeRequest(Uri url) { | |
| 262 return client.getUrl(url) | |
| 263 .then((HttpClientRequest request) => request.close()) | |
| 264 .then((HttpClientResponse response) { | |
| 265 Expect.equals(HttpStatus.OK, response.statusCode); | |
| 266 Expect.equals(1, response.headers["Authentication-Info"].length); | |
| 267 return response.fold(null, (x, y) {}); | |
| 268 }); | |
| 269 } | |
| 270 | |
| 271 Uri uri = Uri.parse("http://127.0.0.1:${server.port}/digest"); | |
| 272 var credentials = new HttpClientDigestCredentials("dart", "password"); | |
| 273 client.addCredentials(uri, "test", credentials); | |
| 274 | |
| 275 makeRequest(uri) | |
| 276 .then((_) => makeRequest(uri)) | |
| 277 .then((_) => makeRequest(uri)) | |
| 278 .then((_) => makeRequest(uri)) | |
| 279 .then((_) { | |
| 280 Expect.equals(2, server.unauthCount); | |
| 281 Expect.equals(4, server.successCount); | |
| 282 server.shutdown(); | |
| 283 client.close(); | |
| 284 }); | |
| 285 }); | |
| 286 } | |
| 287 | |
| 288 void testNextNonce() { | |
| 289 Server.start("MD5", | |
| 290 "auth", | |
| 291 nonceStaleAfter: 2, | |
| 292 useNextNonce: true).then((server) { | |
| 293 HttpClient client = new HttpClient(); | |
| 294 | |
| 295 Future makeRequest(Uri url) { | |
| 296 return client.getUrl(url) | |
| 297 .then((HttpClientRequest request) => request.close()) | |
| 298 .then((HttpClientResponse response) { | |
| 299 Expect.equals(HttpStatus.OK, response.statusCode); | |
| 300 Expect.equals(1, response.headers["Authentication-Info"].length); | |
| 301 return response.fold(null, (x, y) {}); | |
| 302 }); | |
| 303 } | |
| 304 | |
| 305 Uri uri = Uri.parse("http://127.0.0.1:${server.port}/digest"); | |
| 306 var credentials = new HttpClientDigestCredentials("dart", "password"); | |
| 307 client.addCredentials(uri, "test", credentials); | |
| 308 | |
| 309 makeRequest(uri) | |
| 310 .then((_) => makeRequest(uri)) | |
| 311 .then((_) => makeRequest(uri)) | |
| 312 .then((_) => makeRequest(uri)) | |
| 313 .then((_) { | |
| 314 Expect.equals(1, server.unauthCount); | |
| 315 Expect.equals(4, server.successCount); | |
| 316 server.shutdown(); | |
| 317 client.close(); | |
| 318 }); | |
| 319 }); | |
| 320 } | |
| 321 | |
| 234 // An Apache virtual directory configuration like this can be used for | 322 // An Apache virtual directory configuration like this can be used for |
| 235 // running the local server tests. | 323 // running the local server tests. |
| 236 // | 324 // |
| 237 // <Directory "/usr/local/prj/website/digest/"> | 325 // <Directory "/usr/local/prj/website/digest/"> |
| 238 // AllowOverride None | 326 // AllowOverride None |
| 239 // Order deny,allow | 327 // Order deny,allow |
| 240 // Deny from all | 328 // Deny from all |
| 241 // Allow from 127.0.0.0/255.0.0.0 ::1/128 | 329 // Allow from 127.0.0.0/255.0.0.0 ::1/128 |
| 242 // AuthType Digest | 330 // AuthType Digest |
| 243 // AuthName "test" | 331 // AuthName "test" |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 280 } | 368 } |
| 281 | 369 |
| 282 main() { | 370 main() { |
| 283 testNoCredentials(null, null); | 371 testNoCredentials(null, null); |
| 284 testNoCredentials("MD5", null); | 372 testNoCredentials("MD5", null); |
| 285 testNoCredentials("MD5", "auth"); | 373 testNoCredentials("MD5", "auth"); |
| 286 testCredentials(null, null); | 374 testCredentials(null, null); |
| 287 testCredentials("MD5", null); | 375 testCredentials("MD5", null); |
| 288 testCredentials("MD5", "auth"); | 376 testCredentials("MD5", "auth"); |
| 289 testCredentials("MD5", "auth-int"); | 377 testCredentials("MD5", "auth-int"); |
| 290 testBasicAuthenticateCallback(null, null); | 378 testAuthenticateCallback(null, null); |
| 291 testBasicAuthenticateCallback("MD5", null); | 379 testAuthenticateCallback("MD5", null); |
| 292 testBasicAuthenticateCallback("MD5", "auth"); | 380 testAuthenticateCallback("MD5", "auth"); |
| 293 testBasicAuthenticateCallback("MD5", "auth-int"); | 381 testAuthenticateCallback("MD5", "auth-int"); |
| 382 testStaleNonce(); | |
| 383 testNextNonce(); | |
| 294 // These teste are not normally run. They can be used for locally | 384 // These teste are not normally run. They can be used for locally |
| 295 // testing with another web server (e.g. Apache). | 385 // testing with another web server (e.g. Apache). |
| 296 //testLocalServerDigest(); | 386 //testLocalServerDigest(); |
| 297 } | 387 } |
| OLD | NEW |