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