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

Side by Side Diff: pkg/oauth2/lib/src/credentials.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 credentials;
6
7 import 'dart:json';
8 import 'dart:uri';
9
10 import '../../../http/lib/http.dart' as http;
11 import 'handle_access_token_response.dart';
12 import 'utils.dart';
13
14 /// Credentials that prove that a client is allowed to access a resource on the
15 /// resource owner's behalf. These credentials are long-lasting and can be
16 /// safely persisted across multiple runs of the program.
17 ///
18 /// Many authorization servers will attach an expiration date to a set of
19 /// credentials, along with a token that can be used to refresh the credentials
20 /// once they've expired. The [Client] will automatically refresh its
21 /// credentials when necessary. It's also possible to explicitly refresh them
22 /// via [Client.refreshCredentials] or [Credentials.refresh].
23 ///
24 /// Note that a given set of credentials can only be refreshed once, so be sure
25 /// to save the refreshed credentials for future use.
26 class Credentials {
27 /// The token that is sent to the resource server to prove the authorization
28 /// of a client.
29 final String accessToken;
30
31 /// The token that is sent to the authorization server to refresh the
32 /// credentials. This is optional.
33 final String refreshToken;
34
35 /// The URL of the authorization server endpoint that's used to refresh the
36 /// credentials. This is optional.
37 final Uri tokenEndpoint;
38
39 /// The specific permissions being requested from the authorization server.
40 /// The scope strings are specific to the authorization server and may be
41 /// found in its documentation.
42 final List<String> scopes;
43
44 /// The date at which these credentials will expire. This is likely to be a
45 /// few seconds earlier than the server's idea of the expiration date.
46 final Date expiration;
47
48 /// Whether or not these credentials have expired. Note that it's possible the
49 /// credentials will expire shortly after this is called. However, since the
50 /// client's expiration date is kept a few seconds earlier than the server's,
51 /// there should be enough leeway to rely on this.
52 bool get isExpired => expiration != null && new Date.now() > expiration;
53
54 /// Whether it's possible to refresh these credentials.
55 bool get canRefresh => refreshToken != null && tokenEndpoint != null;
56
57 /// Creates a new set of credentials.
58 ///
59 /// This class is usually not constructed directly; rather, it's accessed via
60 /// [Client.credentials] after a [Client] is created by
61 /// [AuthorizationCodeGrant]. Alternately, it may be loaded from a serialized
62 /// form via [Credentials.fromJson].
63 Credentials(
64 this.accessToken,
65 [this.refreshToken,
66 this.tokenEndpoint,
67 this.scopes,
68 this.expiration]);
69
70 /// Loads a set of credentials from a JSON-serialized form. Throws
71 /// [FormatException] if the JSON is incorrectly formatted.
72 factory Credentials.fromJson(String json) {
73 void validate(bool condition, String message) {
74 if (condition) return;
75 throw new FormatException(
76 "Failed to load credentials: $message.\n\n$json");
77 }
78
79 var parsed;
80 try {
81 parsed = JSON.parse(json);
82 } catch (e) {
83 validate(false, 'invalid JSON');
84 }
85
86 validate(parsed is Map, 'was not a JSON map');
87 validate(parsed.containsKey('accessToken'),
88 'did not contain required field "accessToken"');
89 validate(parsed['accessToken'] is String,
90 'required field "accessToken" was not a string, was '
91 '${parsed["accessToken"]}');
92
93
94 for (var stringField in ['refreshToken', 'tokenEndpoint']) {
95 var value = parsed[stringField];
96 validate(value == null || value is String,
97 'field "$stringField" was not a string, was "$value"');
98 }
99
100 var scopes = parsed['scopes'];
101 validate(scopes == null || scopes is List,
102 'field "scopes" was not a list, was "$scopes"');
103
104 var tokenEndpoint = parsed['tokenEndpoint'];
105 if (tokenEndpoint != null) {
106 tokenEndpoint = new Uri.fromString(tokenEndpoint);
107 }
108 var expiration = parsed['expiration'];
109 if (expiration != null) {
110 validate(expiration is int,
111 'field "expiration" was not an int, was "$expiration"');
112 expiration = new Date.fromMillisecondsSinceEpoch(expiration);
113 }
114
115 return new Credentials(
116 parsed['accessToken'],
117 parsed['refreshToken'],
118 tokenEndpoint,
119 scopes,
120 expiration);
121 }
122
123 /// Serializes a set of credentials to JSON. Nothing is guaranteed about the
124 /// output except that it's valid JSON and compatible with
125 /// [Credentials.toJson].
126 String toJson() => JSON.stringify({
127 'accessToken': accessToken,
128 'refreshToken': refreshToken,
129 'tokenEndpoint': tokenEndpoint == null ? null : tokenEndpoint.toString(),
130 'scopes': scopes,
131 'expiration': expiration == null ? null : expiration.millisecondsSinceEpoch
132 });
133
134 /// Returns a new set of refreshed credentials. See [Client.identifier] and
135 /// [Client.secret] for explanations of those parameters.
136 ///
137 /// You may request different scopes than the default by passing in
138 /// [newScopes]. These must be a subset of [scopes].
139 ///
140 /// This will throw a [StateError] if these credentials can't be refreshed, an
141 /// [AuthorizationException] if refreshing the credentials fails, or a
142 /// [FormatError] if the authorization server returns invalid responses.
143 Future<Credentials> refresh(
144 String identifier,
145 String secret,
146 {List<String> newScopes,
147 http.BaseClient httpClient}) {
148 var scopes = this.scopes;
149 if (newScopes != null) scopes = newScopes;
150 if (scopes == null) scopes = <String>[];
151 if (httpClient == null) httpClient = new http.Client();
152
153 var startTime = new Date.now();
154 return async.chain((_) {
155 if (refreshToken == null) {
156 throw new StateError("Can't refresh credentials without a refresh "
157 "token.");
158 } else if (tokenEndpoint == null) {
159 throw new StateError("Can't refresh credentials without a token "
160 "endpoint.");
161 }
162
163 return httpClient.post(tokenEndpoint, fields: {
164 "grant_type": "refresh_token",
165 "refresh_token": refreshToken,
166 "scope": Strings.join(scopes, ' '),
167 // TODO(nweiz): the spec recommends that HTTP basic auth be used in
168 // preference to form parameters, but Google doesn't support that.
169 // Should it be configurable?
170 "client_id": identifier,
171 "client_secret": secret
172 });
173 }).transform((response) {
174 return handleAccessTokenResponse(
175 response, tokenEndpoint, startTime, scopes);
176 });
177 }
178 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698