Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(6)

Side by Side Diff: sdk/lib/io/http_impl.dart

Issue 14740015: Implementation of HTTP digest authentication (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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 part of dart.io; 5 part of dart.io;
6 6
7 class _HttpIncoming extends Stream<List<int>> { 7 class _HttpIncoming extends Stream<List<int>> {
8 final int _transferLength; 8 final int _transferLength;
9 final Completer _dataCompleter = new Completer(); 9 final Completer _dataCompleter = new Completer();
10 Stream<List<int>> _stream; 10 Stream<List<int>> _stream;
(...skipping 256 matching lines...) Expand 10 before | Expand all | Expand 10 after
267 bool get _shouldAuthenticate { 267 bool get _shouldAuthenticate {
268 // Only try to authenticate if there is a challenge in the response. 268 // Only try to authenticate if there is a challenge in the response.
269 List<String> challenge = headers[HttpHeaders.WWW_AUTHENTICATE]; 269 List<String> challenge = headers[HttpHeaders.WWW_AUTHENTICATE];
270 return statusCode == HttpStatus.UNAUTHORIZED && 270 return statusCode == HttpStatus.UNAUTHORIZED &&
271 challenge != null && challenge.length == 1; 271 challenge != null && challenge.length == 1;
272 } 272 }
273 273
274 Future<HttpClientResponse> _authenticate() { 274 Future<HttpClientResponse> _authenticate() {
275 Future<HttpClientResponse> retryWithCredentials(_Credentials cr) { 275 Future<HttpClientResponse> retryWithCredentials(_Credentials cr) {
276 if (cr != null) { 276 if (cr != null) {
277 // TODO(sgjesse): Support digest. 277 if (cr.scheme != _AuthenticationScheme.UNKNOWN) {
Anders Johnsen 2013/05/02 19:10:19 Merge ifs.
Søren Gjesse 2013/05/03 08:10:36 Done.
278 if (cr.scheme == _AuthenticationScheme.BASIC) {
279 // Drain body and retry. 278 // Drain body and retry.
280 return fold(null, (x, y) {}).then((_) { 279 return fold(null, (x, y) {}).then((_) {
281 return _httpClient._openUrlFromRequest(_httpRequest.method, 280 return _httpClient._openUrlFromRequest(_httpRequest.method,
282 _httpRequest.uri, 281 _httpRequest.uri,
283 _httpRequest) 282 _httpRequest)
284 .then((request) => request.close()); 283 .then((request) => request.close());
285 }); 284 });
286 } 285 }
287 } 286 }
288 287
289 // Fall through to here to perform normal response handling if 288 // Fall through to here to perform normal response handling if
290 // there is no sensible authorization handling. 289 // there is no sensible authorization handling.
291 return new Future.value(this); 290 return new Future.value(this);
292 } 291 }
293 292
294 List<String> challenge = headers[HttpHeaders.WWW_AUTHENTICATE]; 293 List<String> challenge = headers[HttpHeaders.WWW_AUTHENTICATE];
295 assert(challenge != null || challenge.length == 1); 294 assert(challenge != null || challenge.length == 1);
296 _HeaderValue header = 295 _HeaderValue header =
297 new _HeaderValue.fromString(challenge[0], parameterSeparator: ","); 296 new _HeaderValue.fromString(challenge[0], parameterSeparator: ",");
298 _AuthenticationScheme scheme = 297 _AuthenticationScheme scheme =
299 new _AuthenticationScheme.fromString(header.value); 298 new _AuthenticationScheme.fromString(header.value);
300 String realm = header.parameters["realm"]; 299 String realm = header.parameters["realm"];
301 300
302 // See if any credentials are available. 301 // See if any matching credentials are available.
303 _Credentials cr = _httpClient._findCredentials(_httpRequest.uri, scheme); 302 _Credentials cr = _httpClient._findCredentials(_httpRequest.uri, scheme);
303 if (cr != null) {
304 if (cr.scheme == _AuthenticationScheme.BASIC && !cr.used) {
305 // If credentials found prepare for retrying the request.
306 return retryWithCredentials(cr);
307 }
304 308
305 if (cr != null && !cr.used) { 309 // Digest authentication only supports the MD5 algorithm.
306 // If credentials found prepare for retrying the request. 310 if (cr.scheme == _AuthenticationScheme.DIGEST &&
Anders Johnsen 2013/05/02 19:10:19 No used check? If missing, maybe move it to line 3
Søren Gjesse 2013/05/03 08:10:36 The checking for whether the nonce is set works li
307 return retryWithCredentials(cr); 311 (header.parameters["algorithm"] == null ||
Anders Johnsen 2013/05/02 19:10:19 != String, or do we always know it's either null o
Søren Gjesse 2013/05/03 08:10:36 Done.
Søren Gjesse 2013/05/03 08:10:36 The HeaderValue only adds Strings to the map.
312 header.parameters["algorithm"].toLowerCase() == "md5")) {
313 if (cr.nonce == null) {
314 // Set up authentication state.
315 cr.nonce = header.parameters["nonce"];
316 cr.algorithm = "MD5";
317 cr.qop = header.parameters["qop"];
318 cr.nonceCount = 0;
319 }
320 // If credentials found prepare for retrying the request.
321 return retryWithCredentials(cr);
322 }
308 } 323 }
309 324
310 // Ask for more credentials if none found or the one found has 325 // Ask for more credentials if none found or the one found has
311 // already been used. If it has already been used it must now be 326 // already been used. If it has already been used it must now be
312 // invalid and is removed. 327 // invalid and is removed.
313 if (cr != null) { 328 if (cr != null) {
314 _httpClient._removeCredentials(cr); 329 _httpClient._removeCredentials(cr);
315 cr = null; 330 cr = null;
316 } 331 }
317 if (_httpClient._authenticate != null) { 332 if (_httpClient._authenticate != null) {
(...skipping 1623 matching lines...) Expand 10 before | Expand all | Expand 10 after
1941 if (this == BASIC) return "Basic"; 1956 if (this == BASIC) return "Basic";
1942 if (this == DIGEST) return "Digest"; 1957 if (this == DIGEST) return "Digest";
1943 return "Unknown"; 1958 return "Unknown";
1944 } 1959 }
1945 1960
1946 final int _scheme; 1961 final int _scheme;
1947 } 1962 }
1948 1963
1949 1964
1950 class _Credentials { 1965 class _Credentials {
1951 _Credentials(this.uri, this.realm, this.credentials); 1966 _Credentials(this.uri, this.realm, this.credentials) {
1967 // Calculate the H(A1) value once.
1968 var hasher = new MD5();
1969 hasher.add(credentials.username.codeUnits);
Anders Johnsen 2013/05/02 19:10:19 Should we do any String encoding here, and below?
Søren Gjesse 2013/05/03 08:10:36 Added UTF-8 encoding and moved the coment from lin
1970 hasher.add([_CharCode.COLON]);
1971 hasher.add(realm.codeUnits);
1972 hasher.add([_CharCode.COLON]);
1973 hasher.add(credentials.password.codeUnits);
1974 ha1 = CryptoUtils.bytesToHex(hasher.close());
1975 }
1952 1976
1953 _AuthenticationScheme get scheme => credentials.scheme; 1977 _AuthenticationScheme get scheme => credentials.scheme;
1954 1978
1955 bool applies(Uri uri, _AuthenticationScheme scheme) { 1979 bool applies(Uri uri, _AuthenticationScheme scheme) {
1956 if (scheme != null && credentials.scheme != scheme) return false; 1980 if (scheme != null && credentials.scheme != scheme) return false;
1957 if (uri.domain != this.uri.domain) return false; 1981 if (uri.domain != this.uri.domain) return false;
1958 int thisPort = 1982 int thisPort =
1959 this.uri.port == 0 ? HttpClient.DEFAULT_HTTP_PORT : this.uri.port; 1983 this.uri.port == 0 ? HttpClient.DEFAULT_HTTP_PORT : this.uri.port;
1960 int otherPort = uri.port == 0 ? HttpClient.DEFAULT_HTTP_PORT : uri.port; 1984 int otherPort = uri.port == 0 ? HttpClient.DEFAULT_HTTP_PORT : uri.port;
1961 if (otherPort != thisPort) return false; 1985 if (otherPort != thisPort) return false;
1962 return uri.path.startsWith(this.uri.path); 1986 return uri.path.startsWith(this.uri.path);
1963 } 1987 }
1964 1988
1965 void authorize(HttpClientRequest request) { 1989 void authorize(HttpClientRequest request) {
1990 // Digest credentials cannot be used without a nonce from the
1991 // server.
1992 if (credentials.scheme == _AuthenticationScheme.DIGEST &&
1993 nonce == null) {
1994 return;
1995 }
1966 credentials.authorize(this, request); 1996 credentials.authorize(this, request);
1967 used = true; 1997 used = true;
1968 } 1998 }
1969 1999
1970 bool used = false; 2000 bool used = false;
1971 Uri uri; 2001 Uri uri;
1972 String realm; 2002 String realm;
1973 _HttpClientCredentials credentials; 2003 _HttpClientCredentials credentials;
1974 2004
1975 // Digest specific fields. 2005 // Digest specific fields.
2006 String ha1;
1976 String nonce; 2007 String nonce;
1977 String algorithm; 2008 String algorithm;
1978 String qop; 2009 String qop;
2010 int nonceCount;
1979 } 2011 }
1980 2012
1981 2013
1982 class _ProxyCredentials { 2014 class _ProxyCredentials {
1983 _ProxyCredentials(this.host, this.port, this.realm, this.credentials); 2015 _ProxyCredentials(this.host, this.port, this.realm, this.credentials);
1984 2016
1985 _AuthenticationScheme get scheme => credentials.scheme; 2017 _AuthenticationScheme get scheme => credentials.scheme;
1986 2018
1987 bool applies(_Proxy proxy, _AuthenticationScheme scheme) { 2019 bool applies(_Proxy proxy, _AuthenticationScheme scheme) {
1988 return proxy.host == host && proxy.port == port; 2020 return proxy.host == host && proxy.port == port;
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
2040 2072
2041 2073
2042 class _HttpClientDigestCredentials 2074 class _HttpClientDigestCredentials
2043 extends _HttpClientCredentials 2075 extends _HttpClientCredentials
2044 implements HttpClientDigestCredentials { 2076 implements HttpClientDigestCredentials {
2045 _HttpClientDigestCredentials(this.username, 2077 _HttpClientDigestCredentials(this.username,
2046 this.password); 2078 this.password);
2047 2079
2048 _AuthenticationScheme get scheme => _AuthenticationScheme.DIGEST; 2080 _AuthenticationScheme get scheme => _AuthenticationScheme.DIGEST;
2049 2081
2082 String authorization(_Credentials credentials, HttpClientRequest request) {
2083 // There is no mentioning of username/password encoding in RFC
2084 // 2617. However there is an open draft for adding an additional
2085 // accept-charset parameter to the WWW-Authenticate and
2086 // Proxy-Authenticate headers, see
2087 // http://tools.ietf.org/html/draft-reschke-basicauth-enc-06. For
2088 // now always use UTF-8 encoding.
2089 MD5 hasher = new MD5();
2090 hasher.add(request.method.codeUnits);
2091 hasher.add([_CharCode.COLON]);
2092 hasher.add(request.uri.path.codeUnits);
2093 var ha2 = CryptoUtils.bytesToHex(hasher.close());
2094
2095 String qop;
2096 String cnonce;
2097 String nc;
2098 var x;
2099 hasher = new MD5();
2100 hasher.add(credentials.ha1.codeUnits);
2101 hasher.add([_CharCode.COLON]);
2102 if (credentials.qop == "auth") {
2103 qop = credentials.qop;
2104 cnonce = CryptoUtils.bytesToHex(_IOCrypto.getRandomBytes(4));
2105 nc = (++credentials.nonceCount).toRadixString(16);
2106 nc = "00000000".substring(0, 8 - nc.length + 1) + nc;
2107 hasher.add(credentials.nonce.codeUnits);
2108 hasher.add([_CharCode.COLON]);
2109 hasher.add(nc.codeUnits);
2110 hasher.add([_CharCode.COLON]);
2111 hasher.add(cnonce.codeUnits);
2112 hasher.add([_CharCode.COLON]);
2113 hasher.add(credentials.qop.codeUnits);
2114 hasher.add([_CharCode.COLON]);
2115 hasher.add(ha2.codeUnits);
2116 } else {
2117 hasher.add(credentials.nonce.codeUnits);
2118 hasher.add([_CharCode.COLON]);
2119 hasher.add(ha2.codeUnits);
2120 }
2121 var response = CryptoUtils.bytesToHex(hasher.close());
2122
2123 StringBuffer buffer = new StringBuffer();
2124 buffer.write('Digest ');
2125 buffer.write('username="$username"');
2126 buffer.write(', realm="${credentials.realm}"');
2127 buffer.write(', nonce="${credentials.nonce}"');
2128 buffer.write(', uri="${request.uri.path}"');
2129 buffer.write(', algorithm="${credentials.algorithm}"');
2130 if (qop == "auth") {
2131 buffer.write(', qop="$qop"');
2132 buffer.write(', cnonce="$cnonce"');
2133 buffer.write(', nc="$nc"');
2134 }
2135 buffer.write(', response="$response"');
2136 return buffer.toString();
2137 }
2138
2050 void authorize(_Credentials credentials, HttpClientRequest request) { 2139 void authorize(_Credentials credentials, HttpClientRequest request) {
2051 // TODO(sgjesse): Implement!!! 2140 request.headers.set(HttpHeaders.AUTHORIZATION, authorization(credentials, re quest));
2052 throw new UnsupportedError("Digest authentication not yet supported");
2053 } 2141 }
2054 2142
2055 void authorizeProxy(_ProxyCredentials credentials, 2143 void authorizeProxy(_ProxyCredentials credentials,
2056 HttpClientRequest request) { 2144 HttpClientRequest request) {
2057 // TODO(sgjesse): Implement!!! 2145 // TODO(sgjesse): Implement!!!
2058 throw new UnsupportedError("Digest authentication not yet supported"); 2146 throw new UnsupportedError("Digest authentication not yet supported");
2059 } 2147 }
2060 2148
2061 String username; 2149 String username;
2062 String password; 2150 String password;
2063 } 2151 }
2064 2152
2065 2153
2066 class _RedirectInfo implements RedirectInfo { 2154 class _RedirectInfo implements RedirectInfo {
2067 const _RedirectInfo(int this.statusCode, 2155 const _RedirectInfo(int this.statusCode,
2068 String this.method, 2156 String this.method,
2069 Uri this.location); 2157 Uri this.location);
2070 final int statusCode; 2158 final int statusCode;
2071 final String method; 2159 final String method;
2072 final Uri location; 2160 final Uri location;
2073 } 2161 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698