Chromium Code Reviews| 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 authorization_code_grant; | 5 library authorization_code_grant; |
| 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 | 10 |
| (...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 89 | 89 |
| 90 /// The current state of the grant object. One of [_INITIAL_STATE], | 90 /// The current state of the grant object. One of [_INITIAL_STATE], |
| 91 /// [_AWAITING_RESPONSE_STATE], or [_FINISHED_STATE]. | 91 /// [_AWAITING_RESPONSE_STATE], or [_FINISHED_STATE]. |
| 92 int _state = _INITIAL_STATE; | 92 int _state = _INITIAL_STATE; |
| 93 | 93 |
| 94 /// Creates a new grant. | 94 /// Creates a new grant. |
| 95 /// | 95 /// |
| 96 /// [httpClient] is used for all HTTP requests made by this grant, as well as | 96 /// [httpClient] is used for all HTTP requests made by this grant, as well as |
| 97 /// those of the [Client] is constructs. | 97 /// those of the [Client] is constructs. |
| 98 AuthorizationCodeGrant( | 98 AuthorizationCodeGrant( |
| 99 this.identifier, | 99 this.identifier, |
| 100 this.secret, | 100 this.secret, |
| 101 this.authorizationEndpoint, | 101 this.authorizationEndpoint, |
| 102 this.tokenEndpoint, | 102 this.tokenEndpoint, |
| 103 {http.Client httpClient}) | 103 {http.Client httpClient}) |
| 104 : _httpClient = httpClient == null ? new http.Client() : httpClient; | 104 : _httpClient = httpClient == null ? new http.Client() : httpClient; |
| 105 | 105 |
| 106 /// Returns the URL to which the resource owner should be redirected to | 106 /// Returns the URL to which the resource owner should be redirected to |
| 107 /// authorize this client. The resource owner will then be redirected to | 107 /// authorize this client. The resource owner will then be redirected to |
| 108 /// [redirect], which should point to a server controlled by the client. This | 108 /// [redirect], which should point to a server controlled by the client. This |
| 109 /// redirect will have additional query parameters that should be passed to | 109 /// redirect will have additional query parameters that should be passed to |
| 110 /// [handleAuthorizationResponse]. | 110 /// [handleAuthorizationResponse]. |
| 111 /// | 111 /// |
| 112 /// The specific permissions being requested from the authorization server may | 112 /// The specific permissions being requested from the authorization server may |
| 113 /// be specified via [scopes]. The scope strings are specific to the | 113 /// be specified via [scopes]. The scope strings are specific to the |
| 114 /// authorization server and may be found in its documentation. Note that you | 114 /// authorization server and may be found in its documentation. Note that you |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 150 /// It is a [StateError] to call this more than once, to call it before | 150 /// It is a [StateError] to call this more than once, to call it before |
| 151 /// [getAuthorizationUrl] is called, or to call it after | 151 /// [getAuthorizationUrl] is called, or to call it after |
| 152 /// [handleAuthorizationCode] is called. | 152 /// [handleAuthorizationCode] is called. |
| 153 /// | 153 /// |
| 154 /// Throws [FormatError] if [parameters] is invalid according to the OAuth2 | 154 /// Throws [FormatError] if [parameters] is invalid according to the OAuth2 |
| 155 /// spec or if the authorization server otherwise provides invalid responses. | 155 /// spec or if the authorization server otherwise provides invalid responses. |
| 156 /// If `state` was passed to [getAuthorizationUrl], this will throw a | 156 /// If `state` was passed to [getAuthorizationUrl], this will throw a |
| 157 /// [FormatError] if the `state` parameter doesn't match the original value. | 157 /// [FormatError] if the `state` parameter doesn't match the original value. |
| 158 /// | 158 /// |
| 159 /// Throws [AuthorizationException] if the authorization fails. | 159 /// Throws [AuthorizationException] if the authorization fails. |
| 160 Future<Client> handleAuthorizationResponse(Map<String, String> parameters) { | 160 Future<Client> handleAuthorizationResponse(Map<String, String> parameters) |
| 161 return async.then((_) { | 161 async { |
| 162 if (_state == _INITIAL_STATE) { | 162 if (_state == _INITIAL_STATE) { |
| 163 throw new StateError( | 163 throw new StateError( |
| 164 'The authorization URL has not yet been generated.'); | 164 'The authorization URL has not yet been generated.'); |
| 165 } else if (_state == _FINISHED_STATE) { | 165 } else if (_state == _FINISHED_STATE) { |
|
Bob Nystrom
2015/08/24 21:36:51
Nit, but the "else" isn't useful.
nweiz
2015/08/24 23:58:34
I like using "else"s when they make the code terse
| |
| 166 throw new StateError( | 166 throw new StateError( |
| 167 'The authorization code has already been received.'); | 167 'The authorization code has already been received.'); |
| 168 } | |
| 169 _state = _FINISHED_STATE; | |
| 170 | |
| 171 if (_stateString != null) { | |
| 172 if (!parameters.containsKey('state')) { | |
| 173 throw new FormatException('Invalid OAuth response for ' | |
| 174 '"$authorizationEndpoint": parameter "state" expected to be ' | |
| 175 '"$_stateString", was missing.'); | |
| 176 } else if (parameters['state'] != _stateString) { | |
| 177 throw new FormatException('Invalid OAuth response for ' | |
| 178 '"$authorizationEndpoint": parameter "state" expected to be ' | |
| 179 '"$_stateString", was "${parameters['state']}".'); | |
| 168 } | 180 } |
| 169 _state = _FINISHED_STATE; | 181 } |
| 170 | 182 |
| 171 if (_stateString != null) { | 183 if (parameters.containsKey('error')) { |
| 172 if (!parameters.containsKey('state')) { | 184 var description = parameters['error_description']; |
| 173 throw new FormatException('Invalid OAuth response for ' | 185 var uriString = parameters['error_uri']; |
| 174 '"$authorizationEndpoint": parameter "state" expected to be ' | 186 var uri = uriString == null ? null : Uri.parse(uriString); |
| 175 '"$_stateString", was missing.'); | 187 throw new AuthorizationException(parameters['error'], description, uri); |
| 176 } else if (parameters['state'] != _stateString) { | 188 } else if (!parameters.containsKey('code')) { |
| 177 throw new FormatException('Invalid OAuth response for ' | 189 throw new FormatException('Invalid OAuth response for ' |
| 178 '"$authorizationEndpoint": parameter "state" expected to be ' | 190 '"$authorizationEndpoint": did not contain required parameter ' |
| 179 '"$_stateString", was "${parameters['state']}".'); | 191 '"code".'); |
| 180 } | 192 } |
| 181 } | |
| 182 | 193 |
| 183 if (parameters.containsKey('error')) { | 194 return await _handleAuthorizationCode(parameters['code']); |
| 184 var description = parameters['error_description']; | |
| 185 var uriString = parameters['error_uri']; | |
| 186 var uri = uriString == null ? null : Uri.parse(uriString); | |
| 187 throw new AuthorizationException(parameters['error'], description, uri); | |
| 188 } else if (!parameters.containsKey('code')) { | |
| 189 throw new FormatException('Invalid OAuth response for ' | |
| 190 '"$authorizationEndpoint": did not contain required parameter ' | |
| 191 '"code".'); | |
| 192 } | |
| 193 | |
| 194 return _handleAuthorizationCode(parameters['code']); | |
| 195 }); | |
| 196 } | 195 } |
| 197 | 196 |
| 198 /// Processes an authorization code directly. Usually | 197 /// Processes an authorization code directly. Usually |
| 199 /// [handleAuthorizationResponse] is preferable to this method, since it | 198 /// [handleAuthorizationResponse] is preferable to this method, since it |
| 200 /// validates all of the query parameters. However, some authorization servers | 199 /// validates all of the query parameters. However, some authorization servers |
| 201 /// allow the user to copy and paste an authorization code into a command-line | 200 /// allow the user to copy and paste an authorization code into a command-line |
| 202 /// application, in which case this method must be used. | 201 /// application, in which case this method must be used. |
| 203 /// | 202 /// |
| 204 /// It is a [StateError] to call this more than once, to call it before | 203 /// It is a [StateError] to call this more than once, to call it before |
| 205 /// [getAuthorizationUrl] is called, or to call it after | 204 /// [getAuthorizationUrl] is called, or to call it after |
| 206 /// [handleAuthorizationCode] is called. | 205 /// [handleAuthorizationCode] is called. |
| 207 /// | 206 /// |
| 208 /// Throws [FormatError] if the authorization server provides invalid | 207 /// Throws [FormatError] if the authorization server provides invalid |
| 209 /// responses while retrieving credentials. | 208 /// responses while retrieving credentials. |
| 210 /// | 209 /// |
| 211 /// Throws [AuthorizationException] if the authorization fails. | 210 /// Throws [AuthorizationException] if the authorization fails. |
| 212 Future<Client> handleAuthorizationCode(String authorizationCode) { | 211 Future<Client> handleAuthorizationCode(String authorizationCode) async { |
| 213 return async.then((_) { | 212 if (_state == _INITIAL_STATE) { |
| 214 if (_state == _INITIAL_STATE) { | 213 throw new StateError( |
| 215 throw new StateError( | 214 'The authorization URL has not yet been generated.'); |
| 216 'The authorization URL has not yet been generated.'); | 215 } else if (_state == _FINISHED_STATE) { |
| 217 } else if (_state == _FINISHED_STATE) { | 216 throw new StateError( |
| 218 throw new StateError( | 217 'The authorization code has already been received.'); |
| 219 'The authorization code has already been received.'); | 218 } |
| 220 } | 219 _state = _FINISHED_STATE; |
| 221 _state = _FINISHED_STATE; | |
| 222 | 220 |
| 223 return _handleAuthorizationCode(authorizationCode); | 221 return await _handleAuthorizationCode(authorizationCode); |
| 224 }); | |
| 225 } | 222 } |
| 226 | 223 |
| 227 /// This works just like [handleAuthorizationCode], except it doesn't validate | 224 /// This works just like [handleAuthorizationCode], except it doesn't validate |
| 228 /// the state beforehand. | 225 /// the state beforehand. |
| 229 Future<Client> _handleAuthorizationCode(String authorizationCode) { | 226 Future<Client> _handleAuthorizationCode(String authorizationCode) async { |
| 230 var startTime = new DateTime.now(); | 227 var startTime = new DateTime.now(); |
| 231 return _httpClient.post(this.tokenEndpoint, body: { | 228 var response = await _httpClient.post(this.tokenEndpoint, body: { |
| 232 "grant_type": "authorization_code", | 229 "grant_type": "authorization_code", |
| 233 "code": authorizationCode, | 230 "code": authorizationCode, |
| 234 "redirect_uri": this._redirectEndpoint.toString(), | 231 "redirect_uri": this._redirectEndpoint.toString(), |
| 235 // TODO(nweiz): the spec recommends that HTTP basic auth be used in | 232 // TODO(nweiz): the spec recommends that HTTP basic auth be used in |
| 236 // preference to form parameters, but Google doesn't support that. Should | 233 // preference to form parameters, but Google doesn't support that. Should |
| 237 // it be configurable? | 234 // it be configurable? |
| 238 "client_id": this.identifier, | 235 "client_id": this.identifier, |
| 239 "client_secret": this.secret | 236 "client_secret": this.secret |
| 240 }).then((response) { | |
| 241 var credentials = handleAccessTokenResponse( | |
| 242 response, tokenEndpoint, startTime, _scopes); | |
| 243 return new Client( | |
| 244 this.identifier, this.secret, credentials, httpClient: _httpClient); | |
| 245 }); | 237 }); |
| 238 | |
| 239 var credentials = handleAccessTokenResponse( | |
| 240 response, tokenEndpoint, startTime, _scopes); | |
| 241 return new Client( | |
| 242 this.identifier, this.secret, credentials, httpClient: _httpClient); | |
| 246 } | 243 } |
| 247 | 244 |
| 248 /// Closes the grant and frees its resources. | 245 /// Closes the grant and frees its resources. |
| 249 /// | 246 /// |
| 250 /// This will close the underlying HTTP client, which is shared by the | 247 /// This will close the underlying HTTP client, which is shared by the |
| 251 /// [Client] created by this grant, so it's not safe to close the grant and | 248 /// [Client] created by this grant, so it's not safe to close the grant and |
| 252 /// continue using the client. | 249 /// continue using the client. |
| 253 void close() { | 250 void close() { |
| 254 if (_httpClient != null) _httpClient.close(); | 251 if (_httpClient != null) _httpClient.close(); |
| 255 _httpClient = null; | 252 _httpClient = null; |
| 256 } | 253 } |
| 257 } | 254 } |
| OLD | NEW |