Chromium Code Reviews| Index: pkg/oauth2/lib/src/credentials.dart |
| diff --git a/pkg/oauth2/lib/src/credentials.dart b/pkg/oauth2/lib/src/credentials.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..3eaad8b411dcb9e585213b67f279bc1db2befb93 |
| --- /dev/null |
| +++ b/pkg/oauth2/lib/src/credentials.dart |
| @@ -0,0 +1,143 @@ |
| +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| +// for details. All rights reserved. Use of this source code is governed by a |
| +// BSD-style license that can be found in the LICENSE file. |
| + |
| +library credentials; |
| + |
| +import 'dart:json'; |
| +import 'dart:uri'; |
| + |
| +import '../../../http/lib/http.dart' as http; |
| +import 'handle_access_token_response.dart'; |
| +import 'utils.dart'; |
| + |
| +/// Credentials that prove that a client is allowed to access a resource on the |
| +/// resource owner's behalf. These credentials are long-lasting and can be |
| +/// safely persisted across multiple runs of the program. |
| +/// |
| +/// Many authorization servers will attach an expiration date to a set of |
| +/// credentials, along with a token that can be used to refresh the credentials |
| +/// once they've expired. The [Client] will automatically refresh its |
| +/// credentials when necessary. It's also possible to explicitly refresh them |
| +/// via [Client.refreshCredentials] or [Credentials.refresh]. |
| +/// |
| +/// Note that a given set of credentials can only be refreshed once, so be sure |
| +/// 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
|
| +class Credentials { |
| + /// The token that is sent to the resource server to prove the authorization |
| + /// of a client. |
| + final String accessToken; |
| + |
| + /// The token that is sent to the authorization server to refresh the |
| + /// credentials. This is optional. |
| + final String refreshToken; |
| + |
| + /// The URL of the authorization server endpoint that's used to refresh the |
| + /// credentials. This is optional. |
| + final Uri tokenEndpoint; |
| + |
| + /// The specific permissions being requested from the authorization server may |
| + /// 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
|
| + /// authorization server and may be found in its documentation. |
| + final List<String> scopes; |
| + |
| + /// The date at which these credentials will expire. This is likely to be a |
| + /// few seconds earlier than the server's idea of the expiration date. |
| + final Date expiration; |
| + |
| + /// Whether or not these credentials have expired. Note that it's possible the |
| + /// credentials will expire shortly after this is called. However, since the |
| + /// client's expiration date is kept a few seconds earlier than the server's, |
| + /// there should be enough leeway to rely on this. |
| + bool get isExpired => expiration != null && new Date.now() > expiration; |
| + |
| + /// Whether it's possible to refresh these credentials. |
| + bool get canRefresh => refreshToken != null && tokenEndpoint != null; |
| + |
| + /// Creates a new set of credentials. |
| + /// |
| + /// This class is usually not constructed directly; rather, it's accessed via |
| + /// [Client.credentials] after a [Client] is created by |
| + /// [AuthorizationCodeGrant]. Alternately, it may be loaded from a serialized |
| + /// form via [Credentials.fromJson]. |
| + Credentials( |
| + this.accessToken, |
| + [this.refreshToken, |
| + this.tokenEndpoint, |
| + this.scopes, |
| + this.expiration]); |
| + |
| + /// Loads a set of credentials from a JSON-serialized form. |
| + factory Credentials.fromJson(String json) { |
| + var parsed = JSON.parse(json); |
| + var tokenEndpoint = parsed['tokenEndpoint']; |
| + 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 ||=.
|
| + var expiration = parsed['expiration']; |
| + if (expiration != null) { |
| + expiration = new Date.fromMillisecondsSinceEpoch(expiration); |
| + } |
| + |
| + return new Credentials( |
| + 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
|
| + parsed['refreshToken'], |
| + tokenEndpoint, |
| + parsed['scopes'], |
| + expiration); |
| + } |
| + |
| + /// Serializes a set of credentials to JSON. Nothing is guaranteed about the |
| + /// output except that it's valid JSON and compatible with |
| + /// [Credentials.toJson]. |
| + String toJson() => JSON.stringify({ |
| + 'accessToken': accessToken, |
| + 'refreshToken': refreshToken, |
| + 'tokenEndpoint': tokenEndpoint == null ? null : tokenEndpoint.toString(), |
| + 'scopes': scopes, |
| + 'expiration': expiration == null ? null : expiration.millisecondsSinceEpoch |
| + }); |
| + |
| + /// Returns a new set of refreshed credentials. See [Client.identifier] and |
| + /// [Client.secret] for explanations of those parameters. |
| + /// |
| + /// You may request different scopes than the default by passing in |
| + /// `newScopes`. These must be a subset of [scopes]. |
| + /// |
| + /// This will throw a [StateError] if these credentials can't be refreshed, an |
| + /// [AuthorizationException] if refreshing the credentials fails, or a |
| + /// [FormatError] if the authorization server returns invalid responses. |
| + Future<Credentials> refresh( |
| + String identifier, |
| + String secret, |
| + {List<String> newScopes, |
| + http.BaseClient httpClient}) { |
| + var scopes = this.scopes; |
| + if (newScopes != null) scopes = newScopes; |
| + if (scopes == null) scopes = <String>[]; |
| + if (httpClient == null) httpClient = new http.Client(); |
| + |
| + var startTime = new Date.now(); |
| + return async.chain((_) { |
| + if (refreshToken == null) { |
| + throw new StateError("Can't refresh credentials without a refresh " |
| + "token."); |
| + } else if (tokenEndpoint == null) { |
| + throw new StateError("Can't refresh credentials without a token " |
| + "endpoint."); |
| + } |
| + |
| + return httpClient.post(tokenEndpoint, fields: { |
| + "grant_type": "refresh_token", |
| + "refresh_token": refreshToken, |
| + "scope": Strings.join(scopes, ' '), |
| + // TODO(nweiz): the spec recommends that HTTP basic auth be used in |
| + // preference to form parameters, but Google doesn't support that. Should |
|
Bob Nystrom
2012/11/16 19:53:30
Long line.
nweiz
2012/11/17 01:06:27
Done.
|
| + // it be configurable? |
| + "client_id": identifier, |
| + "client_secret": secret |
| + }); |
| + }).transform((response) { |
| + return handleAccessTokenResponse( |
| + response, tokenEndpoint, startTime, scopes); |
| + }); |
| + } |
| +} |