OLD | NEW |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, 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 library client; | 5 library client; |
6 | 6 |
7 import 'dart:uri'; | 7 import 'dart:uri'; |
8 | 8 |
9 import '../../../http/lib/http.dart' as http; | 9 import '../../../http/lib/http.dart' as http; |
10 | 10 |
| 11 import 'authorization_exception.dart'; |
11 import 'credentials.dart'; | 12 import 'credentials.dart'; |
12 import 'expiration_exception.dart'; | 13 import 'expiration_exception.dart'; |
13 import 'utils.dart'; | 14 import 'utils.dart'; |
14 | 15 |
15 // TODO(nweiz): Add an onCredentialsRefreshed event once we have some event | 16 // TODO(nweiz): Add an onCredentialsRefreshed event once we have some event |
16 // infrastructure. | 17 // infrastructure. |
17 /// An OAuth2 client. This acts as a drop-in replacement for an [http.Client], | 18 /// An OAuth2 client. This acts as a drop-in replacement for an [http.Client], |
18 /// while sending OAuth2 authorization credentials along with each request. | 19 /// while sending OAuth2 authorization credentials along with each request. |
19 /// | 20 /// |
20 /// The client also automatically refreshes its credentials if possible. When it | 21 /// The client also automatically refreshes its credentials if possible. When it |
21 /// makes a request, if its credentials are expired, it will first refresh them. | 22 /// makes a request, if its credentials are expired, it will first refresh them. |
22 /// This means that any request may throw an [AuthorizationException] if the | 23 /// This means that any request may throw an [AuthorizationException] if the |
23 /// refresh is not authorized for some reason, a [FormatException] if the | 24 /// refresh is not authorized for some reason, a [FormatException] if the |
24 /// authorization server provides ill-formatted responses, or an | 25 /// authorization server provides ill-formatted responses, or an |
25 /// [ExpirationException] if the credentials are expired and can't be refreshed. | 26 /// [ExpirationException] if the credentials are expired and can't be refreshed. |
26 /// | 27 /// |
27 /// Currently this client doesn't attempt to identify errors from the resource | 28 /// The client will also throw an [AuthorizationException] if the resource |
28 /// server that are caused by authentication failure. However, it may throw | 29 /// server returns a 401 response with a WWW-Authenticate header indicating that |
29 /// [AuthorizationException]s for such errors in the future. | 30 /// the current credentials are invalid. |
30 /// | 31 /// |
31 /// If you already have a set of [Credentials], you can construct a [Client] | 32 /// If you already have a set of [Credentials], you can construct a [Client] |
32 /// directly. However, in order to first obtain the credentials, you must | 33 /// directly. However, in order to first obtain the credentials, you must |
33 /// authorize. At the time of writing, the only authorization method this | 34 /// authorize. At the time of writing, the only authorization method this |
34 /// library supports is [AuthorizationCodeGrant]. | 35 /// library supports is [AuthorizationCodeGrant]. |
35 class Client extends http.BaseClient { | 36 class Client extends http.BaseClient { |
36 /// The client identifier for this client. The authorization server will issue | 37 /// The client identifier for this client. The authorization server will issue |
37 /// each client a separate client identifier and secret, which allows the | 38 /// each client a separate client identifier and secret, which allows the |
38 /// server to tell which client is accessing it. Some servers may also have an | 39 /// server to tell which client is accessing it. Some servers may also have an |
39 /// anonymous identifier/secret pair that any client may use. | 40 /// anonymous identifier/secret pair that any client may use. |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
80 /// will also automatically refresh this client's [Credentials] before sending | 81 /// will also automatically refresh this client's [Credentials] before sending |
81 /// the request if necessary. | 82 /// the request if necessary. |
82 Future<http.StreamedResponse> send(http.BaseRequest request) { | 83 Future<http.StreamedResponse> send(http.BaseRequest request) { |
83 return async.chain((_) { | 84 return async.chain((_) { |
84 if (!credentials.isExpired) return new Future.immediate(null); | 85 if (!credentials.isExpired) return new Future.immediate(null); |
85 if (!credentials.canRefresh) throw new ExpirationException(credentials); | 86 if (!credentials.canRefresh) throw new ExpirationException(credentials); |
86 return refreshCredentials(); | 87 return refreshCredentials(); |
87 }).chain((_) { | 88 }).chain((_) { |
88 request.headers['authorization'] = "Bearer ${credentials.accessToken}"; | 89 request.headers['authorization'] = "Bearer ${credentials.accessToken}"; |
89 return _httpClient.send(request); | 90 return _httpClient.send(request); |
| 91 }).transform((response) { |
| 92 if (response.statusCode != 401 || |
| 93 !response.headers.containsKey('www-authenticate')) { |
| 94 return response; |
| 95 } |
| 96 |
| 97 var authenticate; |
| 98 try { |
| 99 authenticate = parseAuthenticateHeader( |
| 100 response.headers['www-authenticate']); |
| 101 } on FormatException catch (e) { |
| 102 return response; |
| 103 } |
| 104 |
| 105 if (authenticate.first != 'bearer') return response; |
| 106 |
| 107 var params = authenticate.last; |
| 108 if (!params.containsKey('error')) return response; |
| 109 |
| 110 throw new AuthorizationException( |
| 111 params['error'], params['error_description'], params['error_uri']); |
90 }); | 112 }); |
91 // TODO(nweiz): parse 401 errors that are caused by OAuth errors here. | |
92 } | 113 } |
93 | 114 |
94 /// Explicitly refreshes this client's credentials. Returns this client. | 115 /// Explicitly refreshes this client's credentials. Returns this client. |
95 /// | 116 /// |
96 /// This will throw a [StateError] if the [Credentials] can't be refreshed, an | 117 /// This will throw a [StateError] if the [Credentials] can't be refreshed, an |
97 /// [AuthorizationException] if refreshing the credentials fails, or a | 118 /// [AuthorizationException] if refreshing the credentials fails, or a |
98 /// [FormatError] if the authorization server returns invalid responses. | 119 /// [FormatError] if the authorization server returns invalid responses. |
99 /// | 120 /// |
100 /// You may request different scopes than the default by passing in | 121 /// You may request different scopes than the default by passing in |
101 /// [newScopes]. These must be a subset of the scopes in the | 122 /// [newScopes]. These must be a subset of the scopes in the |
(...skipping 13 matching lines...) Expand all Loading... |
115 return this; | 136 return this; |
116 }); | 137 }); |
117 } | 138 } |
118 | 139 |
119 /// Closes this client and its underlying HTTP client. | 140 /// Closes this client and its underlying HTTP client. |
120 void close() { | 141 void close() { |
121 if (_httpClient != null) _httpClient.close(); | 142 if (_httpClient != null) _httpClient.close(); |
122 _httpClient = null; | 143 _httpClient = null; |
123 } | 144 } |
124 } | 145 } |
OLD | NEW |