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 oauth2.client; | 5 library oauth2.client; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:async'; |
8 | 8 |
9 import 'package:http/http.dart' as http; | 9 import 'package:http/http.dart' as http; |
| 10 import 'package:http_parser/http_parser.dart'; |
10 | 11 |
11 import 'authorization_exception.dart'; | 12 import 'authorization_exception.dart'; |
12 import 'credentials.dart'; | 13 import 'credentials.dart'; |
13 import 'expiration_exception.dart'; | 14 import 'expiration_exception.dart'; |
14 import 'utils.dart'; | 15 import 'utils.dart'; |
15 | 16 |
16 // TODO(nweiz): Add an onCredentialsRefreshed event once we have some event | 17 // TODO(nweiz): Add an onCredentialsRefreshed event once we have some event |
17 // infrastructure. | 18 // infrastructure. |
18 /// An OAuth2 client. | 19 /// An OAuth2 client. |
19 /// | 20 /// |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
62 final String secret; | 63 final String secret; |
63 | 64 |
64 /// The credentials this client uses to prove to the resource server that it's | 65 /// The credentials this client uses to prove to the resource server that it's |
65 /// authorized. | 66 /// authorized. |
66 /// | 67 /// |
67 /// This may change from request to request as the credentials expire and the | 68 /// This may change from request to request as the credentials expire and the |
68 /// client refreshes them automatically. | 69 /// client refreshes them automatically. |
69 Credentials get credentials => _credentials; | 70 Credentials get credentials => _credentials; |
70 Credentials _credentials; | 71 Credentials _credentials; |
71 | 72 |
| 73 /// Whether to use HTTP Basic authentication for authorizing the client. |
| 74 final bool _basicAuth; |
| 75 |
72 /// The underlying HTTP client. | 76 /// The underlying HTTP client. |
73 http.Client _httpClient; | 77 http.Client _httpClient; |
74 | 78 |
75 /// Creates a new client from a pre-existing set of credentials. | 79 /// Creates a new client from a pre-existing set of credentials. |
76 /// | 80 /// |
77 /// When authorizing a client for the first time, you should use | 81 /// When authorizing a client for the first time, you should use |
78 /// [AuthorizationCodeGrant] instead of constructing a [Client] directly. | 82 /// [AuthorizationCodeGrant] instead of constructing a [Client] directly. |
79 /// | 83 /// |
80 /// [httpClient] is the underlying client that this forwards requests to after | 84 /// [httpClient] is the underlying client that this forwards requests to after |
81 /// adding authorization credentials to them. | 85 /// adding authorization credentials to them. |
82 Client( | 86 /// |
83 this.identifier, | 87 /// Throws an [ArgumentError] if [secret] is passed without [identifier]. |
84 this.secret, | 88 Client(this._credentials, {this.identifier, this.secret, |
85 this._credentials, | 89 bool basicAuth: true, http.Client httpClient}) |
86 {http.Client httpClient}) | 90 : _basicAuth = basicAuth, |
87 : _httpClient = httpClient == null ? new http.Client() : httpClient; | 91 _httpClient = httpClient == null ? new http.Client() : httpClient { |
| 92 if (identifier == null && secret != null) { |
| 93 throw new ArgumentError("secret may not be passed without identifier."); |
| 94 } |
| 95 } |
88 | 96 |
89 /// Sends an HTTP request with OAuth2 authorization credentials attached. | 97 /// Sends an HTTP request with OAuth2 authorization credentials attached. |
90 /// | 98 /// |
91 /// This will also automatically refresh this client's [Credentials] before | 99 /// This will also automatically refresh this client's [Credentials] before |
92 /// sending the request if necessary. | 100 /// sending the request if necessary. |
93 Future<http.StreamedResponse> send(http.BaseRequest request) async { | 101 Future<http.StreamedResponse> send(http.BaseRequest request) async { |
94 if (credentials.isExpired) { | 102 if (credentials.isExpired) { |
95 if (!credentials.canRefresh) throw new ExpirationException(credentials); | 103 if (!credentials.canRefresh) throw new ExpirationException(credentials); |
96 await refreshCredentials(); | 104 await refreshCredentials(); |
97 } | 105 } |
98 | 106 |
99 request.headers['authorization'] = "Bearer ${credentials.accessToken}"; | 107 request.headers['authorization'] = "Bearer ${credentials.accessToken}"; |
100 var response = await _httpClient.send(request); | 108 var response = await _httpClient.send(request); |
101 | 109 |
102 if (response.statusCode != 401) return response; | 110 if (response.statusCode != 401) return response; |
103 if (!response.headers.containsKey('www-authenticate')) return response; | 111 if (!response.headers.containsKey('www-authenticate')) return response; |
104 | 112 |
105 var authenticate; | 113 var challenges; |
106 try { | 114 try { |
107 authenticate = new AuthenticateHeader.parse( | 115 challenges = AuthenticationChallenge.parseHeader( |
108 response.headers['www-authenticate']); | 116 response.headers['www-authenticate']); |
109 } on FormatException catch (_) { | 117 } on FormatException catch (_) { |
110 return response; | 118 return response; |
111 } | 119 } |
112 | 120 |
113 if (authenticate.scheme != 'bearer') return response; | 121 var challenge = challenges.firstWhere( |
| 122 (challenge) => challenge.scheme == 'bearer', orElse: () => null); |
| 123 if (challenge == null) return response; |
114 | 124 |
115 var params = authenticate.parameters; | 125 var params = challenge.parameters; |
116 if (!params.containsKey('error')) return response; | 126 if (!params.containsKey('error')) return response; |
117 | 127 |
118 throw new AuthorizationException( | 128 throw new AuthorizationException( |
119 params['error'], params['error_description'], | 129 params['error'], params['error_description'], |
120 params['error_uri'] == null ? null : Uri.parse(params['error_uri'])); | 130 params['error_uri'] == null ? null : Uri.parse(params['error_uri'])); |
121 } | 131 } |
122 | 132 |
123 /// Explicitly refreshes this client's credentials. Returns this client. | 133 /// Explicitly refreshes this client's credentials. Returns this client. |
124 /// | 134 /// |
125 /// This will throw a [StateError] if the [Credentials] can't be refreshed, an | 135 /// This will throw a [StateError] if the [Credentials] can't be refreshed, an |
126 /// [AuthorizationException] if refreshing the credentials fails, or a | 136 /// [AuthorizationException] if refreshing the credentials fails, or a |
127 /// [FormatError] if the authorization server returns invalid responses. | 137 /// [FormatError] if the authorization server returns invalid responses. |
128 /// | 138 /// |
129 /// You may request different scopes than the default by passing in | 139 /// You may request different scopes than the default by passing in |
130 /// [newScopes]. These must be a subset of the scopes in the | 140 /// [newScopes]. These must be a subset of the scopes in the |
131 /// [Credentials.scopes] field of [Client.credentials]. | 141 /// [Credentials.scopes] field of [Client.credentials]. |
132 Future<Client> refreshCredentials([List<String> newScopes]) async { | 142 Future<Client> refreshCredentials([List<String> newScopes]) async { |
133 if (!credentials.canRefresh) { | 143 if (!credentials.canRefresh) { |
134 var prefix = "OAuth credentials"; | 144 var prefix = "OAuth credentials"; |
135 if (credentials.isExpired) prefix = "$prefix have expired and"; | 145 if (credentials.isExpired) prefix = "$prefix have expired and"; |
136 throw new StateError("$prefix can't be refreshed."); | 146 throw new StateError("$prefix can't be refreshed."); |
137 } | 147 } |
138 | 148 |
139 _credentials = await credentials.refresh( | 149 _credentials = await credentials.refresh( |
140 identifier, secret, | 150 identifier: identifier, |
141 newScopes: newScopes, httpClient: _httpClient); | 151 secret: secret, |
| 152 newScopes: newScopes, |
| 153 basicAuth: _basicAuth, |
| 154 httpClient: _httpClient); |
142 | 155 |
143 return this; | 156 return this; |
144 } | 157 } |
145 | 158 |
146 /// Closes this client and its underlying HTTP client. | 159 /// Closes this client and its underlying HTTP client. |
147 void close() { | 160 void close() { |
148 if (_httpClient != null) _httpClient.close(); | 161 if (_httpClient != null) _httpClient.close(); |
149 _httpClient = null; | 162 _httpClient = null; |
150 } | 163 } |
151 } | 164 } |
OLD | NEW |