OLD | NEW |
---|---|
(Empty) | |
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 | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 import "package:expect/expect.dart"; | |
6 import 'dart:async'; | |
7 import 'dart:crypto'; | |
8 import 'dart:io'; | |
9 import 'dart:isolate'; | |
10 import 'dart:uri'; | |
11 import 'dart:utf'; | |
12 | |
13 class Server { | |
14 HttpServer server; | |
15 bool passwordChanged = false; | |
16 var ha1; | |
17 | |
18 Future<Server> start(String serverAlgorithm, String serverQop) { | |
Anders Johnsen
2013/05/02 19:10:19
Nit. This method could be made static.
Søren Gjesse
2013/05/03 08:10:36
Done.
| |
19 // Calculate ha1. | |
20 String realm = "test"; | |
21 String username = "dart"; | |
22 String password = "password"; | |
23 var hasher = new MD5(); | |
24 hasher.add("${username}:${realm}:${password}".codeUnits); | |
25 ha1 = CryptoUtils.bytesToHex(hasher.close()); | |
26 | |
27 var completer = new Completer(); | |
28 HttpServer.bind("127.0.0.1", 0).then((s) { | |
29 server = s; | |
30 server.listen((HttpRequest request) { | |
31 // Just use a fixed nonce. | |
32 var nonce = "12345678"; | |
33 | |
34 sendUnauthorizedResponse(HttpResponse response) { | |
35 response.statusCode = HttpStatus.UNAUTHORIZED; | |
36 StringBuffer authHeader = new StringBuffer(); | |
37 authHeader.write('Digest'); | |
38 authHeader.write(', realm="$realm"'); | |
39 authHeader.write(', nonce="$nonce"'); | |
40 if (serverAlgorithm != null) { | |
41 authHeader.write(', algorithm=$serverAlgorithm'); | |
42 } | |
43 authHeader.write(', domain="/digest/"'); | |
44 if (serverQop != null) authHeader.write(', qop="$serverQop"'); | |
45 response.headers.set(HttpHeaders.WWW_AUTHENTICATE, authHeader); | |
46 } | |
47 | |
48 var response = request.response; | |
49 if (request.headers[HttpHeaders.AUTHORIZATION] != null) { | |
50 Expect.equals(1, request.headers[HttpHeaders.AUTHORIZATION].length); | |
51 String authorization = | |
52 request.headers[HttpHeaders.AUTHORIZATION][0]; | |
53 HeaderValue header = new HeaderValue.fromString(authorization, paramet erSeparator: ","); | |
54 if (header.value == "basic") { | |
55 sendUnauthorizedResponse(response); | |
56 } else { | |
57 var uri = header.parameters["uri"]; | |
58 var qop = header.parameters["qop"]; | |
59 var cnonce = header.parameters["cnonce"]; | |
60 var nc = header.parameters["nc"]; | |
61 Expect.equals("digest", header.value); | |
62 Expect.equals("dart", header.parameters["username"]); | |
63 Expect.equals(realm, header.parameters["realm"]); | |
64 Expect.equals("MD5", header.parameters["algorithm"]); | |
65 Expect.equals(nonce, header.parameters["nonce"]); | |
66 Expect.equals(request.uri.path, uri); | |
67 if (qop != null) { | |
68 // A server qop of auth-int is downgraded to none by the client. | |
69 Expect.equals("auth", serverQop); | |
70 Expect.equals("auth", header.parameters["qop"]); | |
71 Expect.isNotNull(cnonce); | |
72 Expect.isNotNull(nc); | |
73 } else { | |
74 Expect.isNull(cnonce); | |
75 Expect.isNull(nc); | |
76 } | |
77 Expect.isNotNull(header.parameters["response"]); | |
78 | |
79 var hasher = new MD5(); | |
80 hasher.add("${request.method}:${uri}".codeUnits); | |
81 var ha2 = CryptoUtils.bytesToHex(hasher.close()); | |
82 | |
83 var x; | |
84 hasher = new MD5(); | |
85 if (qop == null || qop == "" || qop == "none") { | |
86 hasher.add("$ha1:${nonce}:$ha2".codeUnits); | |
87 } else { | |
88 hasher.add("$ha1:${nonce}:${nc}:${cnonce}:${qop}:$ha2".codeUnits); | |
89 } | |
90 Expect.equals(CryptoUtils.bytesToHex(hasher.close()), | |
91 header.parameters["response"]); | |
92 | |
93 // Add a bogus Authentication-Info for testing. | |
94 var info = 'rspauth="77180d1ab3d6c9de084766977790f482", ' | |
95 'cnonce="8f971178", ' | |
96 'nc=000002c74, ' | |
97 'qop=auth'; | |
98 response.headers.set("Authentication-Info", info); | |
99 } | |
100 } else { | |
101 sendUnauthorizedResponse(response); | |
102 } | |
103 response.close(); | |
104 }); | |
105 completer.complete(this); | |
106 }); | |
107 return completer.future; | |
108 } | |
109 | |
110 void shutdown() { | |
111 server.close(); | |
112 } | |
113 | |
114 int get port => server.port; | |
115 } | |
116 | |
117 Future<Server> setupServer(String algorithm, String qop) { | |
118 return new Server().start(algorithm, qop); | |
119 } | |
120 | |
121 void testNoCredentials(String algorithm, String qop) { | |
122 setupServer(algorithm, qop).then((server) { | |
123 HttpClient client = new HttpClient(); | |
124 | |
125 // Add digest credentials which does not match the path requested. | |
126 client.addCredentials( | |
127 Uri.parse("http://127.0.0.1:${server.port}/xxx"), | |
128 "test", | |
129 new HttpClientDigestCredentials("dart", "password")); | |
130 | |
131 // Add basic credentials for the path requested. | |
132 client.addCredentials( | |
133 Uri.parse("http://127.0.0.1:${server.port}/digest"), | |
134 "test", | |
135 new HttpClientBasicCredentials("dart", "password")); | |
136 | |
137 Future makeRequest(Uri url) { | |
138 return client.getUrl(url) | |
139 .then((HttpClientRequest request) => request.close()) | |
140 .then((HttpClientResponse response) { | |
141 Expect.equals(HttpStatus.UNAUTHORIZED, response.statusCode); | |
142 return response.fold(null, (x, y) {}); | |
143 }); | |
144 } | |
145 | |
146 var futures = []; | |
147 for (int i = 0; i < 5; i++) { | |
148 futures.add( | |
149 makeRequest( | |
150 Uri.parse("http://127.0.0.1:${server.port}/digest"))); | |
151 } | |
152 Future.wait(futures).then((_) { | |
153 server.shutdown(); | |
154 client.close(); | |
155 }); | |
156 }); | |
157 } | |
158 | |
159 void testCredentials(String algorithm, String qop) { | |
160 setupServer(algorithm, qop).then((server) { | |
161 HttpClient client = new HttpClient(); | |
162 | |
163 Future makeRequest(Uri url) { | |
164 return client.getUrl(url) | |
165 .then((HttpClientRequest request) => request.close()) | |
166 .then((HttpClientResponse response) { | |
167 Expect.equals(HttpStatus.OK, response.statusCode); | |
168 Expect.equals(1, response.headers["Authentication-Info"].length); | |
169 return response.fold(null, (x, y) {}); | |
170 }); | |
171 } | |
172 | |
173 client.addCredentials( | |
174 Uri.parse("http://127.0.0.1:${server.port}/digest"), | |
175 "test", | |
176 new HttpClientDigestCredentials("dart", "password")); | |
177 | |
178 var futures = []; | |
179 for (int i = 0; i < 5; i++) { | |
180 futures.add( | |
181 makeRequest( | |
182 Uri.parse("http://127.0.0.1:${server.port}/digest"))); | |
183 } | |
184 Future.wait(futures).then((_) { | |
185 server.shutdown(); | |
186 client.close(); | |
187 }); | |
188 }); | |
189 } | |
190 | |
191 void testBasicAuthenticateCallback(String algorithm, String qop) { | |
192 setupServer(algorithm, qop).then((server) { | |
193 HttpClient client = new HttpClient(); | |
194 | |
195 client.authenticate = (Uri url, String scheme, String realm) { | |
196 Expect.equals("Digest", scheme); | |
197 Expect.equals("test", realm); | |
198 Completer completer = new Completer(); | |
199 new Timer(const Duration(milliseconds: 10), () { | |
200 client.addCredentials( | |
201 Uri.parse("http://127.0.0.1:${server.port}/digest"), | |
202 "test", | |
203 new HttpClientDigestCredentials("dart", "password")); | |
204 completer.complete(true); | |
205 }); | |
206 return completer.future; | |
207 }; | |
208 | |
209 Future makeRequest(Uri url) { | |
210 return client.getUrl(url) | |
211 .then((HttpClientRequest request) => request.close()) | |
212 .then((HttpClientResponse response) { | |
213 Expect.equals(HttpStatus.OK, response.statusCode); | |
214 Expect.equals(1, response.headers["Authentication-Info"].length); | |
215 return response.fold(null, (x, y) {}); | |
216 }); | |
217 } | |
218 | |
219 var futures = []; | |
220 for (int i = 0; i < 5; i++) { | |
221 futures.add( | |
222 makeRequest( | |
223 Uri.parse("http://127.0.0.1:${server.port}/digest"))); | |
224 } | |
225 Future.wait(futures).then((_) { | |
226 server.shutdown(); | |
227 client.close(); | |
228 }); | |
229 }); | |
230 } | |
231 | |
232 // An Apache virtual directory configuration like this can be used for | |
233 // running the local server tests. | |
234 // | |
235 // <Directory "/usr/local/prj/website/digest/"> | |
236 // AllowOverride None | |
237 // Order deny,allow | |
238 // Deny from all | |
239 // Allow from 127.0.0.0/255.0.0.0 ::1/128 | |
240 // AuthType Digest | |
241 // AuthName "test" | |
242 // AuthDigestDomain /digest/ | |
243 // AuthDigestProvider file | |
244 // AuthUserFile /usr/local/prj/apache/passwd/digest-passwd | |
245 // Require valid-user | |
246 // </Directory> | |
247 // | |
248 | |
249 void testLocalServerDigest() { | |
250 HttpClient client = new HttpClient(); | |
251 | |
252 Future makeRequest() { | |
253 return client.getUrl(Uri.parse("http://127.0.0.1/digest/test")) | |
254 .then((HttpClientRequest request) => request.close()) | |
255 .then((HttpClientResponse response) { | |
256 Expect.equals(HttpStatus.OK, response.statusCode); | |
257 return response.fold(null, (x, y) {}); | |
258 }); | |
259 } | |
260 | |
261 client.addCredentials( | |
262 Uri.parse("http://127.0.0.1/digest"), | |
263 "test", | |
264 new HttpClientDigestCredentials("dart", "password")); | |
265 | |
266 client.authenticate = (Uri url, String scheme, String realm) { | |
267 client.addCredentials( | |
268 Uri.parse("http://127.0.0.1/digest"), | |
269 "test", | |
270 new HttpClientDigestCredentials("dart", "password")); | |
271 return new Future.value(true); | |
272 }; | |
273 | |
274 next() { | |
275 makeRequest().then((_) => next()); | |
276 } | |
277 next(); | |
278 } | |
279 | |
280 main() { | |
281 testNoCredentials(null, null); | |
282 testNoCredentials("MD5", null); | |
283 testNoCredentials("MD5", "auth"); | |
284 testCredentials(null, null); | |
285 testCredentials("MD5", null); | |
286 testCredentials("MD5", "auth"); | |
287 testCredentials("MD5", "auth-int"); | |
288 testBasicAuthenticateCallback(null, null); | |
289 testBasicAuthenticateCallback("MD5", null); | |
290 testBasicAuthenticateCallback("MD5", "auth"); | |
291 testBasicAuthenticateCallback("MD5", "auth-int"); | |
292 // These teste are not normally run. They can be used for locally | |
293 // testing with another web server (e.g. Apache). | |
294 //testLocalServerDigest(); | |
295 } | |
OLD | NEW |