| 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 oauth2.credentials; | 5 library oauth2.credentials; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:collection'; |
| 8 import 'dart:convert'; | 9 import 'dart:convert'; |
| 9 | 10 |
| 10 import 'package:http/http.dart' as http; | 11 import 'package:http/http.dart' as http; |
| 11 | 12 |
| 12 import 'handle_access_token_response.dart'; | 13 import 'handle_access_token_response.dart'; |
| 14 import 'utils.dart'; |
| 13 | 15 |
| 14 /// Credentials that prove that a client is allowed to access a resource on the | 16 /// Credentials that prove that a client is allowed to access a resource on the |
| 15 /// resource owner's behalf. | 17 /// resource owner's behalf. |
| 16 /// | 18 /// |
| 17 /// These credentials are long-lasting and can be safely persisted across | 19 /// These credentials are long-lasting and can be safely persisted across |
| 18 /// multiple runs of the program. | 20 /// multiple runs of the program. |
| 19 /// | 21 /// |
| 20 /// Many authorization servers will attach an expiration date to a set of | 22 /// Many authorization servers will attach an expiration date to a set of |
| 21 /// credentials, along with a token that can be used to refresh the credentials | 23 /// credentials, along with a token that can be used to refresh the credentials |
| 22 /// once they've expired. The [Client] will automatically refresh its | 24 /// once they've expired. The [Client] will automatically refresh its |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 65 /// Whether it's possible to refresh these credentials. | 67 /// Whether it's possible to refresh these credentials. |
| 66 bool get canRefresh => refreshToken != null && tokenEndpoint != null; | 68 bool get canRefresh => refreshToken != null && tokenEndpoint != null; |
| 67 | 69 |
| 68 /// Creates a new set of credentials. | 70 /// Creates a new set of credentials. |
| 69 /// | 71 /// |
| 70 /// This class is usually not constructed directly; rather, it's accessed via | 72 /// This class is usually not constructed directly; rather, it's accessed via |
| 71 /// [Client.credentials] after a [Client] is created by | 73 /// [Client.credentials] after a [Client] is created by |
| 72 /// [AuthorizationCodeGrant]. Alternately, it may be loaded from a serialized | 74 /// [AuthorizationCodeGrant]. Alternately, it may be loaded from a serialized |
| 73 /// form via [Credentials.fromJson]. | 75 /// form via [Credentials.fromJson]. |
| 74 Credentials( | 76 Credentials( |
| 75 this.accessToken, | 77 this.accessToken, |
| 76 [this.refreshToken, | 78 {this.refreshToken, |
| 77 this.tokenEndpoint, | 79 this.tokenEndpoint, |
| 78 this.scopes, | 80 Iterable<String> scopes, |
| 79 this.expiration]); | 81 this.expiration}) |
| 82 : scopes = new UnmodifiableListView( |
| 83 // Explicitly type-annotate the list literal to work around |
| 84 // sdk#24202. |
| 85 scopes == null ? <String>[] : scopes.toList()); |
| 80 | 86 |
| 81 /// Loads a set of credentials from a JSON-serialized form. | 87 /// Loads a set of credentials from a JSON-serialized form. |
| 82 /// | 88 /// |
| 83 /// Throws a [FormatException] if the JSON is incorrectly formatted. | 89 /// Throws a [FormatException] if the JSON is incorrectly formatted. |
| 84 factory Credentials.fromJson(String json) { | 90 factory Credentials.fromJson(String json) { |
| 85 validate(condition, message) { | 91 validate(condition, message) { |
| 86 if (condition) return; | 92 if (condition) return; |
| 87 throw new FormatException( | 93 throw new FormatException( |
| 88 "Failed to load credentials: $message.\n\n$json"); | 94 "Failed to load credentials: $message.\n\n$json"); |
| 89 } | 95 } |
| (...skipping 29 matching lines...) Expand all Loading... |
| 119 } | 125 } |
| 120 var expiration = parsed['expiration']; | 126 var expiration = parsed['expiration']; |
| 121 if (expiration != null) { | 127 if (expiration != null) { |
| 122 validate(expiration is int, | 128 validate(expiration is int, |
| 123 'field "expiration" was not an int, was "$expiration"'); | 129 'field "expiration" was not an int, was "$expiration"'); |
| 124 expiration = new DateTime.fromMillisecondsSinceEpoch(expiration); | 130 expiration = new DateTime.fromMillisecondsSinceEpoch(expiration); |
| 125 } | 131 } |
| 126 | 132 |
| 127 return new Credentials( | 133 return new Credentials( |
| 128 parsed['accessToken'], | 134 parsed['accessToken'], |
| 129 parsed['refreshToken'], | 135 refreshToken: parsed['refreshToken'], |
| 130 tokenEndpoint, | 136 tokenEndpoint: tokenEndpoint, |
| 131 scopes, | 137 scopes: scopes, |
| 132 expiration); | 138 expiration: expiration); |
| 133 } | 139 } |
| 134 | 140 |
| 135 /// Serializes a set of credentials to JSON. | 141 /// Serializes a set of credentials to JSON. |
| 136 /// | 142 /// |
| 137 /// Nothing is guaranteed about the output except that it's valid JSON and | 143 /// Nothing is guaranteed about the output except that it's valid JSON and |
| 138 /// compatible with [Credentials.toJson]. | 144 /// compatible with [Credentials.toJson]. |
| 139 String toJson() => JSON.encode({ | 145 String toJson() => JSON.encode({ |
| 140 'accessToken': accessToken, | 146 'accessToken': accessToken, |
| 141 'refreshToken': refreshToken, | 147 'refreshToken': refreshToken, |
| 142 'tokenEndpoint': tokenEndpoint == null ? null : tokenEndpoint.toString(), | 148 'tokenEndpoint': tokenEndpoint == null ? null : tokenEndpoint.toString(), |
| 143 'scopes': scopes, | 149 'scopes': scopes, |
| 144 'expiration': expiration == null ? null : expiration.millisecondsSinceEpoch | 150 'expiration': expiration == null ? null : expiration.millisecondsSinceEpoch |
| 145 }); | 151 }); |
| 146 | 152 |
| 147 /// Returns a new set of refreshed credentials. | 153 /// Returns a new set of refreshed credentials. |
| 148 /// | 154 /// |
| 149 /// See [Client.identifier] and [Client.secret] for explanations of those | 155 /// See [Client.identifier] and [Client.secret] for explanations of those |
| 150 /// parameters. | 156 /// parameters. |
| 151 /// | 157 /// |
| 152 /// You may request different scopes than the default by passing in | 158 /// You may request different scopes than the default by passing in |
| 153 /// [newScopes]. These must be a subset of [scopes]. | 159 /// [newScopes]. These must be a subset of [scopes]. |
| 154 /// | 160 /// |
| 155 /// This will throw a [StateError] if these credentials can't be refreshed, an | 161 /// This throws an [ArgumentError] if [secret] is passed without [identifier], |
| 162 /// a [StateError] if these credentials can't be refreshed, an |
| 156 /// [AuthorizationException] if refreshing the credentials fails, or a | 163 /// [AuthorizationException] if refreshing the credentials fails, or a |
| 157 /// [FormatError] if the authorization server returns invalid responses. | 164 /// [FormatError] if the authorization server returns invalid responses. |
| 158 Future<Credentials> refresh( | 165 Future<Credentials> refresh( |
| 159 String identifier, | 166 {String identifier, |
| 160 String secret, | 167 String secret, |
| 161 {List<String> newScopes, | 168 Iterable<String> newScopes, |
| 162 http.Client httpClient}) async { | 169 bool basicAuth: true, |
| 170 http.Client httpClient}) async { |
| 163 var scopes = this.scopes; | 171 var scopes = this.scopes; |
| 164 if (newScopes != null) scopes = newScopes; | 172 if (newScopes != null) scopes = newScopes.toList(); |
| 165 if (scopes == null) scopes = <String>[]; | 173 if (scopes == null) scopes = []; |
| 166 if (httpClient == null) httpClient = new http.Client(); | 174 if (httpClient == null) httpClient = new http.Client(); |
| 167 | 175 |
| 176 if (identifier == null && secret != null) { |
| 177 throw new ArgumentError("secret may not be passed without identifier."); |
| 178 } |
| 179 |
| 168 var startTime = new DateTime.now(); | 180 var startTime = new DateTime.now(); |
| 169 if (refreshToken == null) { | 181 if (refreshToken == null) { |
| 170 throw new StateError("Can't refresh credentials without a refresh " | 182 throw new StateError("Can't refresh credentials without a refresh " |
| 171 "token."); | 183 "token."); |
| 172 } else if (tokenEndpoint == null) { | 184 } else if (tokenEndpoint == null) { |
| 173 throw new StateError("Can't refresh credentials without a token " | 185 throw new StateError("Can't refresh credentials without a token " |
| 174 "endpoint."); | 186 "endpoint."); |
| 175 } | 187 } |
| 176 | 188 |
| 177 var fields = { | 189 var headers = {}; |
| 190 |
| 191 var body = { |
| 178 "grant_type": "refresh_token", | 192 "grant_type": "refresh_token", |
| 179 "refresh_token": refreshToken, | 193 "refresh_token": refreshToken |
| 180 // TODO(nweiz): the spec recommends that HTTP basic auth be used in | |
| 181 // preference to form parameters, but Google doesn't support that. | |
| 182 // Should it be configurable? | |
| 183 "client_id": identifier, | |
| 184 "client_secret": secret | |
| 185 }; | 194 }; |
| 186 if (!scopes.isEmpty) fields["scope"] = scopes.join(' '); | 195 if (!scopes.isEmpty) body["scope"] = scopes.join(' '); |
| 187 | 196 |
| 188 var response = await httpClient.post(tokenEndpoint, body: fields); | 197 if (basicAuth && secret != null) { |
| 198 headers["Authorization"] = basicAuthHeader(identifier, secret); |
| 199 } else { |
| 200 if (identifier != null) body["client_id"] = identifier; |
| 201 if (secret != null) body["client_secret"] = secret; |
| 202 } |
| 203 |
| 204 var response = await httpClient.post(tokenEndpoint, |
| 205 headers: headers, body: body); |
| 189 var credentials = await handleAccessTokenResponse( | 206 var credentials = await handleAccessTokenResponse( |
| 190 response, tokenEndpoint, startTime, scopes); | 207 response, tokenEndpoint, startTime, scopes); |
| 191 | 208 |
| 192 // The authorization server may issue a new refresh token. If it doesn't, | 209 // The authorization server may issue a new refresh token. If it doesn't, |
| 193 // we should re-use the one we already have. | 210 // we should re-use the one we already have. |
| 194 if (credentials.refreshToken != null) return credentials; | 211 if (credentials.refreshToken != null) return credentials; |
| 195 return new Credentials( | 212 return new Credentials( |
| 196 credentials.accessToken, | 213 credentials.accessToken, |
| 197 this.refreshToken, | 214 refreshToken: this.refreshToken, |
| 198 credentials.tokenEndpoint, | 215 tokenEndpoint: credentials.tokenEndpoint, |
| 199 credentials.scopes, | 216 scopes: credentials.scopes, |
| 200 credentials.expiration); | 217 expiration: credentials.expiration); |
| 201 } | 218 } |
| 202 } | 219 } |
| OLD | NEW |