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 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. | |
Bob Nystrom
2012/11/16 19:53:30
Given this, maybe client should raise some sort of
nweiz
2012/11/17 01:06:27
That's a good idea. I've added a TODO to do so onc
| |
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 may | |
40 /// be specified via `scopes`. The scope strings are specific to the | |
Bob Nystrom
2012/11/16 19:53:30
Just me, but I'd probably italicize scopes and not
nweiz
2012/11/17 01:06:27
This shouldn't mention the field name at all. It w
| |
41 /// authorization server and may be 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. | |
71 factory Credentials.fromJson(String json) { | |
72 var parsed = JSON.parse(json); | |
73 var tokenEndpoint = parsed['tokenEndpoint']; | |
74 if (tokenEndpoint != null) tokenEndpoint = new Uri.fromString(tokenEndpoint) ; | |
Bob Nystrom
2012/11/16 19:53:30
Long line.
nweiz
2012/11/17 01:06:27
My kingdom for ||=.
| |
75 var expiration = parsed['expiration']; | |
76 if (expiration != null) { | |
77 expiration = new Date.fromMillisecondsSinceEpoch(expiration); | |
78 } | |
79 | |
80 return new Credentials( | |
81 parsed['accessToken'], | |
Bob Nystrom
2012/11/16 19:53:30
Throw a FormatException if this is missing?
nweiz
2012/11/17 01:06:27
Done. I've added a bunch more format checking here
| |
82 parsed['refreshToken'], | |
83 tokenEndpoint, | |
84 parsed['scopes'], | |
85 expiration); | |
86 } | |
87 | |
88 /// Serializes a set of credentials to JSON. Nothing is guaranteed about the | |
89 /// output except that it's valid JSON and compatible with | |
90 /// [Credentials.toJson]. | |
91 String toJson() => JSON.stringify({ | |
92 'accessToken': accessToken, | |
93 'refreshToken': refreshToken, | |
94 'tokenEndpoint': tokenEndpoint == null ? null : tokenEndpoint.toString(), | |
95 'scopes': scopes, | |
96 'expiration': expiration == null ? null : expiration.millisecondsSinceEpoch | |
97 }); | |
98 | |
99 /// Returns a new set of refreshed credentials. See [Client.identifier] and | |
100 /// [Client.secret] for explanations of those parameters. | |
101 /// | |
102 /// You may request different scopes than the default by passing in | |
103 /// `newScopes`. These must be a subset of [scopes]. | |
104 /// | |
105 /// This will throw a [StateError] if these credentials can't be refreshed, an | |
106 /// [AuthorizationException] if refreshing the credentials fails, or a | |
107 /// [FormatError] if the authorization server returns invalid responses. | |
108 Future<Credentials> refresh( | |
109 String identifier, | |
110 String secret, | |
111 {List<String> newScopes, | |
112 http.BaseClient httpClient}) { | |
113 var scopes = this.scopes; | |
114 if (newScopes != null) scopes = newScopes; | |
115 if (scopes == null) scopes = <String>[]; | |
116 if (httpClient == null) httpClient = new http.Client(); | |
117 | |
118 var startTime = new Date.now(); | |
119 return async.chain((_) { | |
120 if (refreshToken == null) { | |
121 throw new StateError("Can't refresh credentials without a refresh " | |
122 "token."); | |
123 } else if (tokenEndpoint == null) { | |
124 throw new StateError("Can't refresh credentials without a token " | |
125 "endpoint."); | |
126 } | |
127 | |
128 return httpClient.post(tokenEndpoint, fields: { | |
129 "grant_type": "refresh_token", | |
130 "refresh_token": refreshToken, | |
131 "scope": Strings.join(scopes, ' '), | |
132 // TODO(nweiz): the spec recommends that HTTP basic auth be used in | |
133 // preference to form parameters, but Google doesn't support that. Sho uld | |
Bob Nystrom
2012/11/16 19:53:30
Long line.
nweiz
2012/11/17 01:06:27
Done.
| |
134 // it be configurable? | |
135 "client_id": identifier, | |
136 "client_secret": secret | |
137 }); | |
138 }).transform((response) { | |
139 return handleAccessTokenResponse( | |
140 response, tokenEndpoint, startTime, scopes); | |
141 }); | |
142 } | |
143 } | |
OLD | NEW |