| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library authorization_code_grant; | |
| 6 | |
| 7 import 'dart:async'; | |
| 8 | |
| 9 import 'package:http/http.dart' as http; | |
| 10 | |
| 11 import 'client.dart'; | |
| 12 import 'authorization_exception.dart'; | |
| 13 import 'handle_access_token_response.dart'; | |
| 14 import 'utils.dart'; | |
| 15 | |
| 16 /// A class for obtaining credentials via an [authorization code grant][]. This | |
| 17 /// method of authorization involves sending the resource owner to the | |
| 18 /// authorization server where they will authorize the client. They're then | |
| 19 /// redirected back to your server, along with an authorization code. This is | |
| 20 /// used to obtain [Credentials] and create a fully-authorized [Client]. | |
| 21 /// | |
| 22 /// To use this class, you must first call [getAuthorizationUrl] to get the URL | |
| 23 /// to which to redirect the resource owner. Then once they've been redirected | |
| 24 /// back to your application, call [handleAuthorizationResponse] or | |
| 25 /// [handleAuthorizationCode] to process the authorization server's response and | |
| 26 /// construct a [Client]. | |
| 27 /// | |
| 28 /// [authorization code grant]: http://tools.ietf.org/html/draft-ietf-oauth-v2-3
1#section-4.1 | |
| 29 class AuthorizationCodeGrant { | |
| 30 /// An enum value for [_state] indicating that [getAuthorizationUrl] has not | |
| 31 /// yet been called for this grant. | |
| 32 static const _INITIAL_STATE = 0; | |
| 33 | |
| 34 // An enum value for [_state] indicating that [getAuthorizationUrl] has been | |
| 35 // called but neither [handleAuthorizationResponse] nor | |
| 36 // [handleAuthorizationCode] has been called. | |
| 37 static const _AWAITING_RESPONSE_STATE = 1; | |
| 38 | |
| 39 // An enum value for [_state] indicating that [getAuthorizationUrl] and either | |
| 40 // [handleAuthorizationResponse] or [handleAuthorizationCode] have been | |
| 41 // called. | |
| 42 static const _FINISHED_STATE = 2; | |
| 43 | |
| 44 /// The client identifier for this client. The authorization server will issue | |
| 45 /// each client a separate client identifier and secret, which allows the | |
| 46 /// server to tell which client is accessing it. Some servers may also have an | |
| 47 /// anonymous identifier/secret pair that any client may use. | |
| 48 /// | |
| 49 /// This is usually global to the program using this library. | |
| 50 final String identifier; | |
| 51 | |
| 52 /// The client secret for this client. The authorization server will issue | |
| 53 /// each client a separate client identifier and secret, which allows the | |
| 54 /// server to tell which client is accessing it. Some servers may also have an | |
| 55 /// anonymous identifier/secret pair that any client may use. | |
| 56 /// | |
| 57 /// This is usually global to the program using this library. | |
| 58 /// | |
| 59 /// Note that clients whose source code or binary executable is readily | |
| 60 /// available may not be able to make sure the client secret is kept a secret. | |
| 61 /// This is fine; OAuth2 servers generally won't rely on knowing with | |
| 62 /// certainty that a client is who it claims to be. | |
| 63 final String secret; | |
| 64 | |
| 65 /// A URL provided by the authorization server that serves as the base for the | |
| 66 /// URL that the resource owner will be redirected to to authorize this | |
| 67 /// client. This will usually be listed in the authorization server's | |
| 68 /// OAuth2 API documentation. | |
| 69 final Uri authorizationEndpoint; | |
| 70 | |
| 71 /// A URL provided by the authorization server that this library uses to | |
| 72 /// obtain long-lasting credentials. This will usually be listed in the | |
| 73 /// authorization server's OAuth2 API documentation. | |
| 74 final Uri tokenEndpoint; | |
| 75 | |
| 76 /// The HTTP client used to make HTTP requests. | |
| 77 http.Client _httpClient; | |
| 78 | |
| 79 /// The URL to which the resource owner will be redirected after they | |
| 80 /// authorize this client with the authorization server. | |
| 81 Uri _redirectEndpoint; | |
| 82 | |
| 83 /// The scopes that the client is requesting access to. | |
| 84 List<String> _scopes; | |
| 85 | |
| 86 /// An opaque string that users of this library may specify that will be | |
| 87 /// included in the response query parameters. | |
| 88 String _stateString; | |
| 89 | |
| 90 /// The current state of the grant object. One of [_INITIAL_STATE], | |
| 91 /// [_AWAITING_RESPONSE_STATE], or [_FINISHED_STATE]. | |
| 92 int _state = _INITIAL_STATE; | |
| 93 | |
| 94 /// Creates a new grant. | |
| 95 /// | |
| 96 /// [httpClient] is used for all HTTP requests made by this grant, as well as | |
| 97 /// those of the [Client] is constructs. | |
| 98 AuthorizationCodeGrant( | |
| 99 this.identifier, | |
| 100 this.secret, | |
| 101 this.authorizationEndpoint, | |
| 102 this.tokenEndpoint, | |
| 103 {http.Client httpClient}) | |
| 104 : _httpClient = httpClient == null ? new http.Client() : httpClient; | |
| 105 | |
| 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 | |
| 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 | |
| 110 /// [handleAuthorizationResponse]. | |
| 111 /// | |
| 112 /// The specific permissions being requested from the authorization server may | |
| 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 | |
| 115 /// may not be granted access to every scope you request; you may check the | |
| 116 /// [Credentials.scopes] field of [Client.credentials] to see which scopes you | |
| 117 /// were granted. | |
| 118 /// | |
| 119 /// An opaque [state] string may also be passed that will be present in the | |
| 120 /// query parameters provided to the redirect URL. | |
| 121 /// | |
| 122 /// It is a [StateError] to call this more than once. | |
| 123 Uri getAuthorizationUrl(Uri redirect, | |
| 124 {List<String> scopes: const <String>[], String state}) { | |
| 125 if (_state != _INITIAL_STATE) { | |
| 126 throw new StateError('The authorization URL has already been generated.'); | |
| 127 } | |
| 128 _state = _AWAITING_RESPONSE_STATE; | |
| 129 | |
| 130 this._redirectEndpoint = redirect; | |
| 131 this._scopes = scopes; | |
| 132 this._stateString = state; | |
| 133 var parameters = { | |
| 134 "response_type": "code", | |
| 135 "client_id": this.identifier, | |
| 136 "redirect_uri": redirect.toString() | |
| 137 }; | |
| 138 | |
| 139 if (state != null) parameters['state'] = state; | |
| 140 if (!scopes.isEmpty) parameters['scope'] = scopes.join(' '); | |
| 141 | |
| 142 return addQueryParameters(this.authorizationEndpoint, parameters); | |
| 143 } | |
| 144 | |
| 145 /// Processes the query parameters added to a redirect from the authorization | |
| 146 /// server. Note that this "response" is not an HTTP response, but rather the | |
| 147 /// data passed to a server controlled by the client as query parameters on | |
| 148 /// the redirect URL. | |
| 149 /// | |
| 150 /// It is a [StateError] to call this more than once, to call it before | |
| 151 /// [getAuthorizationUrl] is called, or to call it after | |
| 152 /// [handleAuthorizationCode] is called. | |
| 153 /// | |
| 154 /// Throws [FormatError] if [parameters] is invalid according to the OAuth2 | |
| 155 /// spec or if the authorization server otherwise provides invalid responses. | |
| 156 /// If `state` was passed to [getAuthorizationUrl], this will throw a | |
| 157 /// [FormatError] if the `state` parameter doesn't match the original value. | |
| 158 /// | |
| 159 /// Throws [AuthorizationException] if the authorization fails. | |
| 160 Future<Client> handleAuthorizationResponse(Map<String, String> parameters) { | |
| 161 return async.then((_) { | |
| 162 if (_state == _INITIAL_STATE) { | |
| 163 throw new StateError( | |
| 164 'The authorization URL has not yet been generated.'); | |
| 165 } else if (_state == _FINISHED_STATE) { | |
| 166 throw new StateError( | |
| 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']}".'); | |
| 180 } | |
| 181 } | |
| 182 | |
| 183 if (parameters.containsKey('error')) { | |
| 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 } | |
| 197 | |
| 198 /// Processes an authorization code directly. Usually | |
| 199 /// [handleAuthorizationResponse] is preferable to this method, since it | |
| 200 /// 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 | |
| 202 /// application, in which case this method must be used. | |
| 203 /// | |
| 204 /// It is a [StateError] to call this more than once, to call it before | |
| 205 /// [getAuthorizationUrl] is called, or to call it after | |
| 206 /// [handleAuthorizationCode] is called. | |
| 207 /// | |
| 208 /// Throws [FormatError] if the authorization server provides invalid | |
| 209 /// responses while retrieving credentials. | |
| 210 /// | |
| 211 /// Throws [AuthorizationException] if the authorization fails. | |
| 212 Future<Client> handleAuthorizationCode(String authorizationCode) { | |
| 213 return async.then((_) { | |
| 214 if (_state == _INITIAL_STATE) { | |
| 215 throw new StateError( | |
| 216 'The authorization URL has not yet been generated.'); | |
| 217 } else if (_state == _FINISHED_STATE) { | |
| 218 throw new StateError( | |
| 219 'The authorization code has already been received.'); | |
| 220 } | |
| 221 _state = _FINISHED_STATE; | |
| 222 | |
| 223 return _handleAuthorizationCode(authorizationCode); | |
| 224 }); | |
| 225 } | |
| 226 | |
| 227 /// This works just like [handleAuthorizationCode], except it doesn't validate | |
| 228 /// the state beforehand. | |
| 229 Future<Client> _handleAuthorizationCode(String authorizationCode) { | |
| 230 var startTime = new DateTime.now(); | |
| 231 return _httpClient.post(this.tokenEndpoint, body: { | |
| 232 "grant_type": "authorization_code", | |
| 233 "code": authorizationCode, | |
| 234 "redirect_uri": this._redirectEndpoint.toString(), | |
| 235 // TODO(nweiz): the spec recommends that HTTP basic auth be used in | |
| 236 // preference to form parameters, but Google doesn't support that. Should | |
| 237 // it be configurable? | |
| 238 "client_id": this.identifier, | |
| 239 "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 }); | |
| 246 } | |
| 247 | |
| 248 /// Closes the grant and frees its resources. | |
| 249 /// | |
| 250 /// 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 | |
| 252 /// continue using the client. | |
| 253 void close() { | |
| 254 if (_httpClient != null) _httpClient.close(); | |
| 255 _httpClient = null; | |
| 256 } | |
| 257 } | |
| OLD | NEW |