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

Side by Side Diff: pkg/oauth2/lib/src/authorization_code_grant.dart

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

Powered by Google App Engine
This is Rietveld 408576698