From 4a2cbe7d0fb0658f1de7fd7636cac3c0b6070f46 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Mon, 19 Nov 2012 22:23:33 +0000 Subject: [PATCH 001/159] Add a package for authenticating via OAuth2. Review URL: https://codereview.chromium.org//11420025 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@15115 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/oauth2.dart | 109 ++++++++ .../lib/src/authorization_code_grant.dart | 258 ++++++++++++++++++ .../lib/src/authorization_exception.dart | 36 +++ pkgs/oauth2/lib/src/client.dart | 125 +++++++++ pkgs/oauth2/lib/src/credentials.dart | 190 +++++++++++++ pkgs/oauth2/lib/src/expiration_exception.dart | 20 ++ .../lib/src/handle_access_token_response.dart | 143 ++++++++++ pkgs/oauth2/lib/src/utils.dart | 73 +++++ .../test/authorization_code_grant_test.dart | 196 +++++++++++++ pkgs/oauth2/test/client_test.dart | 120 ++++++++ pkgs/oauth2/test/credentials_test.dart | 174 ++++++++++++ .../handle_access_token_response_test.dart | 191 +++++++++++++ pkgs/oauth2/test/utils.dart | 82 ++++++ 13 files changed, 1717 insertions(+) create mode 100644 pkgs/oauth2/lib/oauth2.dart create mode 100644 pkgs/oauth2/lib/src/authorization_code_grant.dart create mode 100644 pkgs/oauth2/lib/src/authorization_exception.dart create mode 100644 pkgs/oauth2/lib/src/client.dart create mode 100644 pkgs/oauth2/lib/src/credentials.dart create mode 100644 pkgs/oauth2/lib/src/expiration_exception.dart create mode 100644 pkgs/oauth2/lib/src/handle_access_token_response.dart create mode 100644 pkgs/oauth2/lib/src/utils.dart create mode 100644 pkgs/oauth2/test/authorization_code_grant_test.dart create mode 100644 pkgs/oauth2/test/client_test.dart create mode 100644 pkgs/oauth2/test/credentials_test.dart create mode 100644 pkgs/oauth2/test/handle_access_token_response_test.dart create mode 100644 pkgs/oauth2/test/utils.dart diff --git a/pkgs/oauth2/lib/oauth2.dart b/pkgs/oauth2/lib/oauth2.dart new file mode 100644 index 000000000..a7e9307d5 --- /dev/null +++ b/pkgs/oauth2/lib/oauth2.dart @@ -0,0 +1,109 @@ +// 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. + +/// A client library for authenticating with a remote service via OAuth2 on +/// behalf of a user, and making authorized HTTP requests with the user's OAuth2 +/// credentials. +/// +/// OAuth2 allows a client (the program using this library) to access and +/// manipulate a resource that's owned by a resource owner (the end user) and +/// lives on a remote server. The client directs the resource owner to an +/// authorization server (usually but not always the same as the server that +/// hosts the resource), where the resource owner tells the authorization server +/// to give the client an access token. This token serves as proof that the +/// client has permission to access resources on behalf of the resource owner. +/// +/// OAuth2 provides several different methods for the client to obtain +/// authorization. At the time of writing, this library only supports the +/// [AuthorizationCodeGrant] method, but further methods may be added in the +/// future. The following example uses this method to authenticate, and assumes +/// that the library is being used by a server-side application. +/// +/// import 'dart:io' +/// import 'dart:uri' +/// import 'package:oauth2/oauth2.dart' as oauth2; +/// +/// // These URLs are endpoints that are provided by the authorization +/// // server. They're usually included in the server's documentation of its +/// // OAuth2 API. +/// final authorizationEndpoint = +/// new Uri.fromString("http://example.com/oauth2/authorization"); +/// final tokenEndpoint = +/// new Uri.fromString("http://example.com/oauth2/token"); +/// +/// // The authorization server will issue each client a separate client +/// // identifier and secret, which allows the server to tell which client +/// // is accessing it. Some servers may also have an anonymous +/// // identifier/secret pair that any client may use. +/// // +/// // Note that clients whose source code or binary executable is readily +/// // available may not be able to make sure the client secret is kept a +/// // secret. This is fine; OAuth2 servers generally won't rely on knowing +/// // with certainty that a client is who it claims to be. +/// final identifier = "my client identifier"; +/// final secret = "my client secret"; +/// +/// // This is a URL on your application's server. The authorization server +/// // will redirect the resource owner here once they've authorized the +/// // client. The redirection will include the authorization code in the +/// // query parameters. +/// final redirectUrl = new Uri.fromString( +/// "http://my-site.com/oauth2-redirect"); +/// +/// var credentialsFile = new File("~/.myapp/credentials.json"); +/// return credentialsFile.exists().chain((exists) { +/// // If the OAuth2 credentials have already been saved from a previous +/// // run, we just want to reload them. +/// if (exists) { +/// return credentialsFile.readAsText().transform((json) { +/// var credentials = new oauth2.Credentials.fromJson(json); +/// return new oauth2.Client(identifier, secret, credentials); +/// }); +/// } +/// +/// // If we don't have OAuth2 credentials yet, we need to get the +/// // resource owner to authorize us. We're assuming here that we're a +/// // command-line application. +/// var grant = new oauth2.AuthorizationCodeGrant( +/// identifier, secret, authorizationEndpoint, tokenEndpoint); +/// +/// // Redirect the resource owner to the authorization URL. This will be +/// // a URL on the authorization server (authorizationEndpoint with some +/// // additional query parameters). Once the resource owner has +/// // authorized, they'll be redirected to `redirectUrl` with an +/// // authorization code. +/// // +/// // `redirect` is an imaginary function that redirects the resource +/// // owner's browser. +/// return redirect(grant.getAuthorizationUrl(redirectUrl)).chain((_) { +/// // Another imaginary function that listens for a request to +/// // `redirectUrl`. +/// return listen(redirectUrl); +/// }).transform((request) { +/// // Once the user is redirected to `redirectUrl`, pass the query +/// // parameters to the AuthorizationCodeGrant. It will validate them +/// // and extract the authorization code to create a new Client. +/// return grant.handleAuthorizationResponse(request.queryParameters); +/// }) +/// }).chain((client) { +/// // Once you have a Client, you can use it just like any other HTTP +/// // client. +/// return client.read("http://example.com/protected-resources.txt") +/// .transform((result) { +/// // Once we're done with the client, save the credentials file. This +/// // ensures that if the credentials were automatically refreshed +/// // while using the client, the new credentials are available for the +/// // next run of the program. +/// return credentialsFile.open(FileMode.WRITE).chain((file) { +/// return file.writeString(client.credentials.toJson()); +/// }).chain((file) => file.close()).transform((_) => result); +/// }); +/// }).then(print); +library oauth2; + +export 'src/authorization_code_grant.dart'; +export 'src/client.dart'; +export 'src/credentials.dart'; +export 'src/authorization_exception.dart'; +export 'src/expiration_exception.dart'; diff --git a/pkgs/oauth2/lib/src/authorization_code_grant.dart b/pkgs/oauth2/lib/src/authorization_code_grant.dart new file mode 100644 index 000000000..b4c50d966 --- /dev/null +++ b/pkgs/oauth2/lib/src/authorization_code_grant.dart @@ -0,0 +1,258 @@ +// 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 authorization_code_grant; + +import 'dart:uri'; + +// TODO(nweiz): This should be a "package:" import. See issue 6745. +import '../../../http/lib/http.dart' as http; + +import 'client.dart'; +import 'authorization_exception.dart'; +import 'handle_access_token_response.dart'; +import 'utils.dart'; + +/// A class for obtaining credentials via an [authorization code grant][]. This +/// method of authorization involves sending the resource owner to the +/// authorization server where they will authorize the client. They're then +/// redirected back to your server, along with an authorization code. This is +/// used to obtain [Credentials] and create a fully-authorized [Client]. +/// +/// To use this class, you must first call [getAuthorizationUrl] to get the URL +/// to which to redirect the resource owner. Then once they've been redirected +/// back to your application, call [handleAuthorizationResponse] or +/// [handleAuthorizationCode] to process the authorization server's response and +/// construct a [Client]. +/// +/// [authorization code grant]: http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-4.1 +class AuthorizationCodeGrant { + /// An enum value for [_state] indicating that [getAuthorizationUrl] has not + /// yet been called for this grant. + static const _INITIAL_STATE = 0; + + // An enum value for [_state] indicating that [getAuthorizationUrl] has been + // called but neither [handleAuthorizationResponse] nor + // [handleAuthorizationCode] has been called. + static const _AWAITING_RESPONSE_STATE = 1; + + // An enum value for [_state] indicating that [getAuthorizationUrl] and either + // [handleAuthorizationResponse] or [handleAuthorizationCode] have been + // called. + static const _FINISHED_STATE = 2; + + /// The client identifier for this client. The authorization server will issue + /// each client a separate client identifier and secret, which allows the + /// server to tell which client is accessing it. Some servers may also have an + /// anonymous identifier/secret pair that any client may use. + /// + /// This is usually global to the program using this library. + final String identifier; + + /// The client secret for this client. The authorization server will issue + /// each client a separate client identifier and secret, which allows the + /// server to tell which client is accessing it. Some servers may also have an + /// anonymous identifier/secret pair that any client may use. + /// + /// This is usually global to the program using this library. + /// + /// Note that clients whose source code or binary executable is readily + /// available may not be able to make sure the client secret is kept a secret. + /// This is fine; OAuth2 servers generally won't rely on knowing with + /// certainty that a client is who it claims to be. + final String secret; + + /// A URL provided by the authorization server that serves as the base for the + /// URL that the resource owner will be redirected to to authorize this + /// client. This will usually be listed in the authorization server's + /// OAuth2 API documentation. + final Uri authorizationEndpoint; + + /// A URL provided by the authorization server that this library uses to + /// obtain long-lasting credentials. This will usually be listed in the + /// authorization server's OAuth2 API documentation. + final Uri tokenEndpoint; + + /// The HTTP client used to make HTTP requests. + http.BaseClient _httpClient; + + /// The URL to which the resource owner will be redirected after they + /// authorize this client with the authorization server. + Uri _redirectEndpoint; + + /// The scopes that the client is requesting access to. + List _scopes; + + /// An opaque string that users of this library may specify that will be + /// included in the response query parameters. + String _stateString; + + /// The current state of the grant object. One of [_INITIAL_STATE], + /// [_AWAITING_RESPONSE_STATE], or [_FINISHED_STATE]. + int _state = _INITIAL_STATE; + + /// Creates a new grant. + /// + /// [httpClient] is used for all HTTP requests made by this grant, as well as + /// those of the [Client] is constructs. + AuthorizationCodeGrant( + this.identifier, + this.secret, + this.authorizationEndpoint, + this.tokenEndpoint, + {http.BaseClient httpClient}) + : _httpClient = httpClient == null ? new http.Client() : httpClient; + + /// Returns the URL to which the resource owner should be redirected to + /// authorize this client. The resource owner will then be redirected to + /// [redirect], which should point to a server controlled by the client. This + /// redirect will have additional query parameters that should be passed to + /// [handleAuthorizationResponse]. + /// + /// The specific permissions being requested from the authorization server may + /// be specified via [scopes]. The scope strings are specific to the + /// authorization server and may be found in its documentation. Note that you + /// may not be granted access to every scope you request; you may check the + /// [Credentials.scopes] field of [Client.credentials] to see which scopes you + /// were granted. + /// + /// An opaque [state] string may also be passed that will be present in the + /// query parameters provided to the redirect URL. + /// + /// It is a [StateError] to call this more than once. + Uri getAuthorizationUrl(Uri redirect, + {List scopes: const [], String state}) { + if (_state != _INITIAL_STATE) { + throw new StateError('The authorization URL has already been generated.'); + } + _state = _AWAITING_RESPONSE_STATE; + + this._redirectEndpoint = redirect; + this._scopes = scopes; + this._stateString = state; + var parameters = { + "response_type": "code", + "client_id": this.identifier, + "redirect_uri": redirect.toString() + }; + + if (state != null) parameters['state'] = state; + if (!scopes.isEmpty) parameters['scope'] = Strings.join(scopes, ' '); + + return addQueryParameters(this.authorizationEndpoint, parameters); + } + + /// Processes the query parameters added to a redirect from the authorization + /// server. Note that this "response" is not an HTTP response, but rather the + /// data passed to a server controlled by the client as query parameters on + /// the redirect URL. + /// + /// It is a [StateError] to call this more than once, to call it before + /// [getAuthorizationUrl] is called, or to call it after + /// [handleAuthorizationCode] is called. + /// + /// Throws [FormatError] if [parameters] is invalid according to the OAuth2 + /// spec or if the authorization server otherwise provides invalid responses. + /// If `state` was passed to [getAuthorizationUrl], this will throw a + /// [FormatError] if the `state` parameter doesn't match the original value. + /// + /// Throws [AuthorizationException] if the authorization fails. + Future handleAuthorizationResponse(Map parameters) { + return async.chain((_) { + if (_state == _INITIAL_STATE) { + throw new StateError( + 'The authorization URL has not yet been generated.'); + } else if (_state == _FINISHED_STATE) { + throw new StateError( + 'The authorization code has already been received.'); + } + _state = _FINISHED_STATE; + + if (_stateString != null) { + if (!parameters.containsKey('state')) { + throw new FormatException('Invalid OAuth response for ' + '"$authorizationEndpoint": parameter "state" expected to be ' + '"$_stateString", was missing.'); + } else if (parameters['state'] != _stateString) { + throw new FormatException('Invalid OAuth response for ' + '"$authorizationEndpoint": parameter "state" expected to be ' + '"$_stateString", was "${parameters['state']}".'); + } + } + + if (parameters.containsKey('error')) { + var description = parameters['error_description']; + var uriString = parameters['error_uri']; + var uri = uriString == null ? null : new Uri.fromString(uriString); + throw new AuthorizationException(parameters['error'], description, uri); + } else if (!parameters.containsKey('code')) { + throw new FormatException('Invalid OAuth response for ' + '"$authorizationEndpoint": did not contain required parameter ' + '"code".'); + } + + return _handleAuthorizationCode(parameters['code']); + }); + } + + /// Processes an authorization code directly. Usually + /// [handleAuthorizationResponse] is preferable to this method, since it + /// validates all of the query parameters. However, some authorization servers + /// allow the user to copy and paste an authorization code into a command-line + /// application, in which case this method must be used. + /// + /// It is a [StateError] to call this more than once, to call it before + /// [getAuthorizationUrl] is called, or to call it after + /// [handleAuthorizationCode] is called. + /// + /// Throws [FormatError] if the authorization server provides invalid + /// responses while retrieving credentials. + /// + /// Throws [AuthorizationException] if the authorization fails. + Future handleAuthorizationCode(String authorizationCode) { + return async.chain((_) { + if (_state == _INITIAL_STATE) { + throw new StateError( + 'The authorization URL has not yet been generated.'); + } else if (_state == _FINISHED_STATE) { + throw new StateError( + 'The authorization code has already been received.'); + } + _state = _FINISHED_STATE; + + return _handleAuthorizationCode(authorizationCode); + }); + } + + /// This works just like [handleAuthorizationCode], except it doesn't validate + /// the state beforehand. + Future _handleAuthorizationCode(String authorizationCode) { + var startTime = new Date.now(); + return _httpClient.post(this.tokenEndpoint, fields: { + "grant_type": "authorization_code", + "code": authorizationCode, + "redirect_uri": this._redirectEndpoint.toString(), + // TODO(nweiz): the spec recommends that HTTP basic auth be used in + // preference to form parameters, but Google doesn't support that. Should + // it be configurable? + "client_id": this.identifier, + "client_secret": this.secret + }).transform((response) { + var credentials = handleAccessTokenResponse( + response, tokenEndpoint, startTime, _scopes); + return new Client( + this.identifier, this.secret, credentials, httpClient: _httpClient); + }); + } + + /// Closes the grant and frees its resources. + /// + /// This will close the underlying HTTP client, which is shared by the + /// [Client] created by this grant, so it's not safe to close the grant and + /// continue using the client. + void close() { + if (_httpClient != null) _httpClient.close(); + _httpClient = null; + } +} diff --git a/pkgs/oauth2/lib/src/authorization_exception.dart b/pkgs/oauth2/lib/src/authorization_exception.dart new file mode 100644 index 000000000..d48c3b16d --- /dev/null +++ b/pkgs/oauth2/lib/src/authorization_exception.dart @@ -0,0 +1,36 @@ +// 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 authorization_exception; + +import 'dart:io'; + +/// An exception raised when OAuth2 authorization fails. +class AuthorizationException implements Exception { + /// The name of the error. Possible names are enumerated in [the spec][]. + /// + /// [the spec]: http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-5.2 + final String error; + + /// The description of the error, provided by the server. Defaults to null. + final String description; + + /// A URI for a page that describes the error in more detail, provided by the + /// server. Defaults to null. + final Uri uri; + + /// Creates an AuthorizationException. + AuthorizationException(this.error, this.description, this.uri); + + /// Provides a string description of the AuthorizationException. + String toString() { + var header = 'OAuth authorization error ($error)'; + if (description != null) { + header = '$header: $description'; + } else if (uri != null) { + header = '$header: $uri'; + } + return '$header.'; + } +} diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart new file mode 100644 index 000000000..dc71e4047 --- /dev/null +++ b/pkgs/oauth2/lib/src/client.dart @@ -0,0 +1,125 @@ +// 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 client; + +import 'dart:uri'; + +import '../../../http/lib/http.dart' as http; + +import 'credentials.dart'; +import 'expiration_exception.dart'; +import 'utils.dart'; + +// TODO(nweiz): Add an onCredentialsRefreshed event once we have some event +// infrastructure. +/// An OAuth2 client. This acts as a drop-in replacement for an +/// [http.BaseClient], while sending OAuth2 authorization credentials along with +/// each request. +/// +/// The client also automatically refreshes its credentials if possible. When it +/// makes a request, if its credentials are expired, it will first refresh them. +/// This means that any request may throw an [AuthorizationException] if the +/// refresh is not authorized for some reason, a [FormatException] if the +/// authorization server provides ill-formatted responses, or an +/// [ExpirationException] if the credentials are expired and can't be refreshed. +/// +/// Currently this client doesn't attempt to identify errors from the resource +/// server that are caused by authentication failure. However, it may throw +/// [AuthorizationException]s for such errors in the future. +/// +/// If you already have a set of [Credentials], you can construct a [Client] +/// directly. However, in order to first obtain the credentials, you must +/// authorize. At the time of writing, the only authorization method this +/// library supports is [AuthorizationCodeGrant]. +class Client extends http.BaseClient { + /// The client identifier for this client. The authorization server will issue + /// each client a separate client identifier and secret, which allows the + /// server to tell which client is accessing it. Some servers may also have an + /// anonymous identifier/secret pair that any client may use. + /// + /// This is usually global to the program using this library. + final String identifier; + + /// The client secret for this client. The authorization server will issue + /// each client a separate client identifier and secret, which allows the + /// server to tell which client is accessing it. Some servers may also have an + /// anonymous identifier/secret pair that any client may use. + /// + /// This is usually global to the program using this library. + /// + /// Note that clients whose source code or binary executable is readily + /// available may not be able to make sure the client secret is kept a secret. + /// This is fine; OAuth2 servers generally won't rely on knowing with + /// certainty that a client is who it claims to be. + final String secret; + + /// The credentials this client uses to prove to the resource server that it's + /// authorized. This may change from request to request as the credentials + /// expire and the client refreshes them automatically. + Credentials get credentials => _credentials; + Credentials _credentials; + + /// The underlying HTTP client. + http.BaseClient _httpClient; + + /// Creates a new client from a pre-existing set of credentials. When + /// authorizing a client for the first time, you should use + /// [AuthorizationCodeGrant] instead of constructing a [Client] directly. + /// + /// [httpClient] is the underlying client that this forwards requests to after + /// adding authorization credentials to them. + Client( + this.identifier, + this.secret, + this._credentials, + {http.BaseClient httpClient}) + : _httpClient = httpClient == null ? new http.Client() : httpClient; + + /// Sends an HTTP request with OAuth2 authorization credentials attached. This + /// will also automatically refresh this client's [Credentials] before sending + /// the request if necessary. + Future send(http.BaseRequest request) { + return async.chain((_) { + if (!credentials.isExpired) return new Future.immediate(null); + if (!credentials.canRefresh) throw new ExpirationException(credentials); + return refreshCredentials(); + }).chain((_) { + request.headers['authorization'] = "Bearer ${credentials.accessToken}"; + return _httpClient.send(request); + }); + // TODO(nweiz): parse 401 errors that are caused by OAuth errors here. + } + + /// Explicitly refreshes this client's credentials. Returns this client. + /// + /// This will throw a [StateError] if the [Credentials] can't be refreshed, an + /// [AuthorizationException] if refreshing the credentials fails, or a + /// [FormatError] if the authorization server returns invalid responses. + /// + /// You may request different scopes than the default by passing in + /// [newScopes]. These must be a subset of the scopes in the + /// [Credentials.scopes] field of [Client.credentials]. + Future refreshCredentials([List newScopes]) { + return async.chain((_) { + if (!credentials.canRefresh) { + var prefix = "OAuth credentials"; + if (credentials.isExpired) prefix = "$prefix have expired and"; + throw new StateError("$prefix can't be refreshed."); + } + + return credentials.refresh(identifier, secret, + newScopes: newScopes, httpClient: _httpClient); + }).transform((credentials) { + _credentials = credentials; + return this; + }); + } + + /// Closes this client and its underlying HTTP client. + void close() { + if (_httpClient != null) _httpClient.close(); + _httpClient = null; + } +} diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart new file mode 100644 index 000000000..f0939c9c2 --- /dev/null +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -0,0 +1,190 @@ +// 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. +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. + /// The scope strings are specific to the authorization server and may be + /// found in its documentation. + final List 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. Throws + /// [FormatException] if the JSON is incorrectly formatted. + factory Credentials.fromJson(String json) { + void validate(bool condition, String message) { + if (condition) return; + throw new FormatException( + "Failed to load credentials: $message.\n\n$json"); + } + + var parsed; + try { + parsed = JSON.parse(json); + } catch (e) { + validate(false, 'invalid JSON'); + } + + validate(parsed is Map, 'was not a JSON map'); + validate(parsed.containsKey('accessToken'), + 'did not contain required field "accessToken"'); + validate(parsed['accessToken'] is String, + 'required field "accessToken" was not a string, was ' + '${parsed["accessToken"]}'); + + + for (var stringField in ['refreshToken', 'tokenEndpoint']) { + var value = parsed[stringField]; + validate(value == null || value is String, + 'field "$stringField" was not a string, was "$value"'); + } + + var scopes = parsed['scopes']; + validate(scopes == null || scopes is List, + 'field "scopes" was not a list, was "$scopes"'); + + var tokenEndpoint = parsed['tokenEndpoint']; + if (tokenEndpoint != null) { + tokenEndpoint = new Uri.fromString(tokenEndpoint); + } + var expiration = parsed['expiration']; + if (expiration != null) { + validate(expiration is int, + 'field "expiration" was not an int, was "$expiration"'); + expiration = new Date.fromMillisecondsSinceEpoch(expiration); + } + + return new Credentials( + parsed['accessToken'], + parsed['refreshToken'], + tokenEndpoint, + 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 refresh( + String identifier, + String secret, + {List newScopes, + http.BaseClient httpClient}) { + var scopes = this.scopes; + if (newScopes != null) scopes = newScopes; + if (scopes == null) scopes = []; + 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."); + } + + var fields = { + "grant_type": "refresh_token", + "refresh_token": refreshToken, + // TODO(nweiz): the spec recommends that HTTP basic auth be used in + // preference to form parameters, but Google doesn't support that. + // Should it be configurable? + "client_id": identifier, + "client_secret": secret + }; + if (!scopes.isEmpty) fields["scope"] = Strings.join(scopes, ' '); + + return httpClient.post(tokenEndpoint, fields: fields); + }).transform((response) { + return handleAccessTokenResponse( + response, tokenEndpoint, startTime, scopes); + }).transform((credentials) { + // The authorization server may issue a new refresh token. If it doesn't, + // we should re-use the one we already have. + if (credentials.refreshToken != null) return credentials; + return new Credentials( + credentials.accessToken, + this.refreshToken, + credentials.tokenEndpoint, + credentials.scopes, + credentials.expiration); + }); + } +} diff --git a/pkgs/oauth2/lib/src/expiration_exception.dart b/pkgs/oauth2/lib/src/expiration_exception.dart new file mode 100644 index 000000000..ac15559c3 --- /dev/null +++ b/pkgs/oauth2/lib/src/expiration_exception.dart @@ -0,0 +1,20 @@ +// 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 expiration_exception; + +import 'dart:io'; + +/// An exception raised when attempting to use expired OAuth2 credentials. +class ExpirationException implements Exception { + /// The expired credentials. + final Credentials credentials; + + /// Creates an ExpirationException. + ExpirationException(this.credentials); + + /// Provides a string description of the ExpirationException. + String toString() => + "OAuth2 credentials have expired and can't be refreshed."; +} diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart new file mode 100644 index 000000000..af94f911f --- /dev/null +++ b/pkgs/oauth2/lib/src/handle_access_token_response.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 handle_access_token_response; + +import 'dart:io'; +import 'dart:json'; +import 'dart:uri'; + +import '../../../http/lib/http.dart' as http; + +import 'credentials.dart'; +import 'authorization_exception.dart'; + +/// The amount of time, in seconds, to add as a "grace period" for credential +/// expiration. This allows credential expiration checks to remain valid for a +/// reasonable amount of time. +const _EXPIRATION_GRACE = 10; + +/// Handles a response from the authorization server that contains an access +/// token. This response format is common across several different components of +/// the OAuth2 flow. +Credentials handleAccessTokenResponse( + http.Response response, + Uri tokenEndpoint, + Date startTime, + List scopes) { + if (response.statusCode != 200) _handleErrorResponse(response, tokenEndpoint); + + void validate(bool condition, String message) => + _validate(response, tokenEndpoint, condition, message); + + var contentType = response.headers['content-type']; + if (contentType != null) { + contentType = new ContentType.fromString(contentType); + } + validate(contentType != null && contentType.value == "application/json", + 'content-type was "$contentType", expected "application/json"'); + + var parameters; + try { + parameters = JSON.parse(response.body); + } catch (e) { + validate(false, 'invalid JSON'); + } + + for (var requiredParameter in ['access_token', 'token_type']) { + validate(parameters.containsKey(requiredParameter), + 'did not contain required parameter "$requiredParameter"'); + validate(parameters[requiredParameter] is String, + 'required parameter "$requiredParameter" was not a string, was ' + '"${parameters[requiredParameter]}"'); + } + + // TODO(nweiz): support the "mac" token type + // (http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01) + validate(parameters['token_type'].toLowerCase() == 'bearer', + '"$tokenEndpoint": unknown token type "${parameters['token_type']}"'); + + var expiresIn = parameters['expires_in']; + validate(expiresIn == null || expiresIn is int, + 'parameter "expires_in" was not an int, was "$expiresIn"'); + + for (var name in ['refresh_token', 'scope']) { + var value = parameters[name]; + validate(value == null || value is String, + 'parameter "$name" was not a string, was "$value"'); + } + + var scope = parameters['scope']; + if (scope != null) scopes = scope.split(" "); + + var expiration = expiresIn == null ? null : + startTime.add(new Duration(seconds: expiresIn - _EXPIRATION_GRACE)); + + return new Credentials( + parameters['access_token'], + parameters['refresh_token'], + tokenEndpoint, + scopes, + expiration); +} + +/// Throws the appropriate exception for an error response from the +/// authorization server. +void _handleErrorResponse(http.Response response, Uri tokenEndpoint) { + void validate(bool condition, String message) => + _validate(response, tokenEndpoint, condition, message); + + // OAuth2 mandates a 400 or 401 response code for access token error + // responses. If it's not a 400 reponse, the server is either broken or + // off-spec. + if (response.statusCode != 400 && response.statusCode != 401) { + var reason = ''; + if (response.reasonPhrase != null && !response.reasonPhrase.isEmpty) { + ' ${response.reasonPhrase}'; + } + throw new FormatException('OAuth request for "$tokenEndpoint" failed ' + 'with status ${response.statusCode}$reason.\n\n${response.body}'); + } + + var contentType = response.headers['content-type']; + if (contentType != null) { + contentType = new ContentType.fromString(contentType); + } + validate(contentType != null && contentType.value == "application/json", + 'content-type was "$contentType", expected "application/json"'); + + var parameters; + try { + parameters = JSON.parse(response.body); + } catch (e) { + validate(false, 'invalid JSON'); + } + + validate(parameters.containsKey('error'), + 'did not contain required parameter "error"'); + validate(parameters["error"] is String, + 'required parameter "error" was not a string, was ' + '"${parameters["error"]}"'); + + for (var name in ['error_description', 'error_uri']) { + var value = parameters[name]; + validate(value == null || value is String, + 'parameter "$name" was not a string, was "$value"'); + } + + var description = parameters['error_description']; + var uriString = parameters['error_uri']; + var uri = uriString == null ? null : new Uri.fromString(uriString); + throw new AuthorizationException(parameters['error'], description, uri); +} + +void _validate( + http.Response response, + Uri tokenEndpoint, + bool condition, + String message) { + if (condition) return; + throw new FormatException('Invalid OAuth response for "$tokenEndpoint": ' + '$message.\n\n${response.body}'); +} diff --git a/pkgs/oauth2/lib/src/utils.dart b/pkgs/oauth2/lib/src/utils.dart new file mode 100644 index 000000000..583a5bc44 --- /dev/null +++ b/pkgs/oauth2/lib/src/utils.dart @@ -0,0 +1,73 @@ +// 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 utils; + +import 'dart:uri'; +import 'dart:isolate'; +import 'dart:crypto'; + +/// Adds additional query parameters to [url], overwriting the original +/// parameters if a name conflict occurs. +Uri addQueryParameters(Uri url, Map parameters) { + var queryMap = queryToMap(url.query); + mapAddAll(queryMap, parameters); + return url.resolve("?${mapToQuery(queryMap)}"); +} + +/// Convert a URL query string (or `application/x-www-form-urlencoded` body) +/// into a [Map] from parameter names to values. +Map queryToMap(String queryList) { + var map = {}; + for (var pair in queryList.split("&")) { + var split = split1(pair, "="); + if (split.isEmpty) continue; + var key = urlDecode(split[0]); + var value = split.length > 1 ? urlDecode(split[1]) : ""; + map[key] = value; + } + return map; +} + +/// Convert a [Map] from parameter names to values to a URL query string. +String mapToQuery(Map map) { + var pairs = >[]; + map.forEach((key, value) { + key = encodeUriComponent(key); + value = (value == null || value.isEmpty) ? null : encodeUriComponent(value); + pairs.add([key, value]); + }); + return Strings.join(pairs.map((pair) { + if (pair[1] == null) return pair[0]; + return "${pair[0]}=${pair[1]}"; + }), "&"); +} + +/// Add all key/value pairs from [source] to [destination], overwriting any +/// pre-existing values. +void mapAddAll(Map destination, Map source) => + source.forEach((key, value) => destination[key] = value); + +/// Decode a URL-encoded string. Unlike [decodeUriComponent], this includes +/// replacing `+` with ` `. +String urlDecode(String encoded) => + decodeUriComponent(encoded.replaceAll("+", " ")); + +/// Like [String.split], but only splits on the first occurrence of the pattern. +/// This will always return a list of two elements or fewer. +List split1(String toSplit, String pattern) { + if (toSplit.isEmpty) return []; + + var index = toSplit.indexOf(pattern); + if (index == -1) return [toSplit]; + return [toSplit.substring(0, index), + toSplit.substring(index + pattern.length)]; +} + +/// Returns a [Future] that asynchronously completes to `null`. +Future get async { + var completer = new Completer(); + new Timer(0, (_) => completer.complete(null)); + return completer.future; +} diff --git a/pkgs/oauth2/test/authorization_code_grant_test.dart b/pkgs/oauth2/test/authorization_code_grant_test.dart new file mode 100644 index 000000000..ad2c0ba3d --- /dev/null +++ b/pkgs/oauth2/test/authorization_code_grant_test.dart @@ -0,0 +1,196 @@ +// 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 authorization_code_grant_test; + +import 'dart:io'; +import 'dart:json'; +import 'dart:uri'; + +import '../../unittest/lib/unittest.dart'; +import '../../http/lib/http.dart' as http; +import '../../http/lib/testing.dart'; +import '../lib/oauth2.dart' as oauth2; +import 'utils.dart'; + +final redirectUrl = new Uri.fromString('http://example.com/redirect'); + +ExpectClient client; + +AuthorizationCodeGrant grant; + +void createGrant() { + client = new ExpectClient(); + grant = new oauth2.AuthorizationCodeGrant( + 'identifier', + 'secret', + new Uri.fromString('https://example.com/authorization'), + new Uri.fromString('https://example.com/token'), + httpClient: client); +} + +void main() { + group('.getAuthorizationUrl', () { + setUp(createGrant); + + test('builds the correct URL', () { + expect(grant.getAuthorizationUrl(redirectUrl).toString(), + equals('https://example.com/authorization' + '?response_type=code' + '&client_id=identifier' + '&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect')); + }); + + test('builds the correct URL with scopes', () { + var authorizationUrl = grant.getAuthorizationUrl( + redirectUrl, scopes: ['scope', 'other/scope']); + expect(authorizationUrl.toString(), + equals('https://example.com/authorization' + '?response_type=code' + '&client_id=identifier' + '&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect' + '&scope=scope%20other%2Fscope')); + }); + + test('builds the correct URL with state', () { + var authorizationUrl = grant.getAuthorizationUrl( + redirectUrl, state: 'state'); + expect(authorizationUrl.toString(), + equals('https://example.com/authorization' + '?response_type=code' + '&client_id=identifier' + '&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect' + '&state=state')); + }); + + test('merges with existing query parameters', () { + grant = new oauth2.AuthorizationCodeGrant( + 'identifier', + 'secret', + new Uri.fromString('https://example.com/authorization?query=value'), + new Uri.fromString('https://example.com/token'), + httpClient: client); + + var authorizationUrl = grant.getAuthorizationUrl(redirectUrl); + expect(authorizationUrl.toString(), + equals('https://example.com/authorization' + '?query=value' + '&response_type=code' + '&client_id=identifier' + '&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect')); + }); + + test("can't be called twice", () { + grant.getAuthorizationUrl(redirectUrl); + expect(() => grant.getAuthorizationUrl(redirectUrl), throwsStateError); + }); + }); + + group('.handleAuthorizationResponse', () { + setUp(createGrant); + + test("can't be called before .getAuthorizationUrl", () { + expect(grant.handleAuthorizationResponse({}), throwsStateError); + }); + + test("can't be called twice", () { + grant.getAuthorizationUrl(redirectUrl); + grant.handleAuthorizationResponse({'code': 'auth code'}); + expect(grant.handleAuthorizationResponse({'code': 'auth code'}), + throwsStateError); + }); + + test('must have a state parameter if the authorization URL did', () { + grant.getAuthorizationUrl(redirectUrl, state: 'state'); + expect(grant.handleAuthorizationResponse({'code': 'auth code'}), + throwsFormatException); + }); + + test('must have the same state parameter the authorization URL did', () { + grant.getAuthorizationUrl(redirectUrl, state: 'state'); + expect(grant.handleAuthorizationResponse({ + 'code': 'auth code', + 'state': 'other state' + }), throwsFormatException); + }); + + test('must have a code parameter', () { + grant.getAuthorizationUrl(redirectUrl); + expect(grant.handleAuthorizationResponse({}), throwsFormatException); + }); + + test('with an error parameter throws an AuthorizationException', () { + grant.getAuthorizationUrl(redirectUrl); + expect(grant.handleAuthorizationResponse({'error': 'invalid_request'}), + throwsAuthorizationException); + }); + + test('sends an authorization code request', () { + grant.getAuthorizationUrl(redirectUrl); + client.expectRequest((request) { + expect(request.method, equals('POST')); + expect(request.url.toString(), equals(grant.tokenEndpoint.toString())); + expect(request.bodyFields, equals({ + 'grant_type': 'authorization_code', + 'code': 'auth code', + 'redirect_uri': redirectUrl.toString(), + 'client_id': 'identifier', + 'client_secret': 'secret' + })); + + return new Future.immediate(new http.Response(JSON.stringify({ + 'access_token': 'access token', + 'token_type': 'bearer', + }), 200, headers: {'content-type': 'application/json'})); + }); + + expect(grant.handleAuthorizationResponse({'code': 'auth code'}), + completion(predicate((client) { + expect(client.credentials.accessToken, equals('access token')); + return true; + }))); + }); + }); + + group('.handleAuthorizationCode', () { + setUp(createGrant); + + test("can't be called before .getAuthorizationUrl", () { + expect(grant.handleAuthorizationCode('auth code'), throwsStateError); + }); + + test("can't be called twice", () { + grant.getAuthorizationUrl(redirectUrl); + grant.handleAuthorizationCode('auth code'); + expect(grant.handleAuthorizationCode('auth code'), + throwsStateError); + }); + + test('sends an authorization code request', () { + grant.getAuthorizationUrl(redirectUrl); + client.expectRequest((request) { + expect(request.method, equals('POST')); + expect(request.url.toString(), equals(grant.tokenEndpoint.toString())); + expect(request.bodyFields, equals({ + 'grant_type': 'authorization_code', + 'code': 'auth code', + 'redirect_uri': redirectUrl.toString(), + 'client_id': 'identifier', + 'client_secret': 'secret' + })); + + return new Future.immediate(new http.Response(JSON.stringify({ + 'access_token': 'access token', + 'token_type': 'bearer', + }), 200, headers: {'content-type': 'application/json'})); + }); + + expect(grant.handleAuthorizationCode('auth code'), + completion(predicate((client) { + expect(client.credentials.accessToken, equals('access token')); + return true; + }))); + }); + }); +} diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart new file mode 100644 index 000000000..94b792038 --- /dev/null +++ b/pkgs/oauth2/test/client_test.dart @@ -0,0 +1,120 @@ +// 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 client_test; + +import 'dart:io'; +import 'dart:json'; +import 'dart:uri'; + +import '../../unittest/lib/unittest.dart'; +import '../../http/lib/http.dart' as http; +import '../lib/oauth2.dart' as oauth2; +import 'utils.dart'; + +final Uri requestUri = new Uri.fromString("http://example.com/resource"); + +final Uri tokenEndpoint = new Uri.fromString('http://example.com/token'); + +ExpectClient httpClient; + +void createHttpClient() { + httpClient = new ExpectClient(); +} + +void main() { + group('with expired credentials', () { + setUp(createHttpClient); + + test("that can't be refreshed throws an ExpirationException on send", () { + var expiration = new Date.now().subtract(new Duration(hours: 1)); + var credentials = new oauth2.Credentials( + 'access token', null, null, [], expiration); + var client = new oauth2.Client('identifier', 'secret', credentials, + httpClient: httpClient); + + expect(client.get(requestUri), throwsExpirationException); + }); + + test("that can be refreshed refreshes the credentials and sends the " + "request", () { + var expiration = new Date.now().subtract(new Duration(hours: 1)); + var credentials = new oauth2.Credentials( + 'access token', 'refresh token', tokenEndpoint, [], expiration); + var client = new oauth2.Client('identifier', 'secret', credentials, + httpClient: httpClient); + + httpClient.expectRequest((request) { + expect(request.method, equals('POST')); + expect(request.url.toString(), equals(tokenEndpoint.toString())); + return new Future.immediate(new http.Response(JSON.stringify({ + 'access_token': 'new access token', + 'token_type': 'bearer' + }), 200, headers: {'content-type': 'application/json'})); + }); + + httpClient.expectRequest((request) { + expect(request.method, equals('GET')); + expect(request.url.toString(), equals(requestUri.toString())); + expect(request.headers['authorization'], + equals('Bearer new access token')); + + return new Future.immediate(new http.Response('good job', 200)); + }); + + expect(client.read(requestUri).transform((_) { + expect(client.credentials.accessToken, equals('new access token')); + }), completes); + }); + }); + + group('with valid credentials', () { + setUp(createHttpClient); + + test("sends a request with bearer authorization", () { + var credentials = new oauth2.Credentials('access token'); + var client = new oauth2.Client('identifier', 'secret', credentials, + httpClient: httpClient); + + httpClient.expectRequest((request) { + expect(request.method, equals('GET')); + expect(request.url.toString(), equals(requestUri.toString())); + expect(request.headers['authorization'], + equals('Bearer access token')); + + return new Future.immediate(new http.Response('good job', 200)); + }); + + expect(client.read(requestUri), completion(equals('good job'))); + }); + + test("can manually refresh the credentials", () { + var credentials = new oauth2.Credentials( + 'access token', 'refresh token', tokenEndpoint); + var client = new oauth2.Client('identifier', 'secret', credentials, + httpClient: httpClient); + + httpClient.expectRequest((request) { + expect(request.method, equals('POST')); + expect(request.url.toString(), equals(tokenEndpoint.toString())); + return new Future.immediate(new http.Response(JSON.stringify({ + 'access_token': 'new access token', + 'token_type': 'bearer' + }), 200, headers: {'content-type': 'application/json'})); + }); + + expect(client.refreshCredentials().transform((_) { + expect(client.credentials.accessToken, equals('new access token')); + }), completes); + }); + + test("without a refresh token can't manually refresh the credentials", () { + var credentials = new oauth2.Credentials('access token'); + var client = new oauth2.Client('identifier', 'secret', credentials, + httpClient: httpClient); + + expect(client.refreshCredentials(), throwsStateError); + }); + }); +} diff --git a/pkgs/oauth2/test/credentials_test.dart b/pkgs/oauth2/test/credentials_test.dart new file mode 100644 index 000000000..5faf2d04c --- /dev/null +++ b/pkgs/oauth2/test/credentials_test.dart @@ -0,0 +1,174 @@ +// 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_test; + +import 'dart:io'; +import 'dart:json'; +import 'dart:uri'; + +import '../../unittest/lib/unittest.dart'; +import '../../http/lib/http.dart' as http; +import '../lib/oauth2.dart' as oauth2; +import 'utils.dart'; + +final Uri tokenEndpoint = new Uri.fromString('http://example.com/token'); + +ExpectClient httpClient; + +void main() { + setUp(() => httpClient = new ExpectClient()); + + test('is not expired if no expiration exists', () { + var credentials = new oauth2.Credentials('access token'); + expect(credentials.isExpired, isFalse); + }); + + test('is not expired if the expiration is in the future', () { + var expiration = new Date.now().add(new Duration(hours: 1)); + var credentials = new oauth2.Credentials( + 'access token', null, null, null, expiration); + expect(credentials.isExpired, isFalse); + }); + + test('is expired if the expiration is in the past', () { + var expiration = new Date.now().subtract(new Duration(hours: 1)); + var credentials = new oauth2.Credentials( + 'access token', null, null, null, expiration); + expect(credentials.isExpired, isTrue); + }); + + test("can't refresh without a refresh token", () { + var credentials = new oauth2.Credentials( + 'access token', null, tokenEndpoint); + expect(credentials.canRefresh, false); + expect(credentials.refresh('identifier', 'secret', httpClient: httpClient), + throwsStateError); + }); + + test("can't refresh without a token endpoint", () { + var credentials = new oauth2.Credentials('access token', 'refresh token'); + expect(credentials.canRefresh, false); + expect(credentials.refresh('identifier', 'secret', httpClient: httpClient), + throwsStateError); + }); + + test("can refresh with a refresh token and a token endpoint", () { + var credentials = new oauth2.Credentials( + 'access token', 'refresh token', tokenEndpoint, ['scope1', 'scope2']); + expect(credentials.canRefresh, true); + + httpClient.expectRequest((request) { + expect(request.method, equals('POST')); + expect(request.url.toString(), equals(tokenEndpoint.toString())); + expect(request.bodyFields, equals({ + "grant_type": "refresh_token", + "refresh_token": "refresh token", + "scope": "scope1 scope2", + "client_id": "identifier", + "client_secret": "secret" + })); + + return new Future.immediate(new http.Response(JSON.stringify({ + 'access_token': 'new access token', + 'token_type': 'bearer', + 'refresh_token': 'new refresh token' + }), 200, headers: {'content-type': 'application/json'})); + }); + + + expect(credentials.refresh('identifier', 'secret', httpClient: httpClient) + .transform((credentials) { + expect(credentials.accessToken, equals('new access token')); + expect(credentials.refreshToken, equals('new refresh token')); + }), completes); + }); + + test("uses the old refresh token if a new one isn't provided", () { + var credentials = new oauth2.Credentials( + 'access token', 'refresh token', tokenEndpoint); + expect(credentials.canRefresh, true); + + httpClient.expectRequest((request) { + expect(request.method, equals('POST')); + expect(request.url.toString(), equals(tokenEndpoint.toString())); + expect(request.bodyFields, equals({ + "grant_type": "refresh_token", + "refresh_token": "refresh token", + "client_id": "identifier", + "client_secret": "secret" + })); + + return new Future.immediate(new http.Response(JSON.stringify({ + 'access_token': 'new access token', + 'token_type': 'bearer' + }), 200, headers: {'content-type': 'application/json'})); + }); + + + expect(credentials.refresh('identifier', 'secret', httpClient: httpClient) + .transform((credentials) { + expect(credentials.accessToken, equals('new access token')); + expect(credentials.refreshToken, equals('refresh token')); + }), completes); + }); + + group("fromJson", () { + oauth2.Credentials fromMap(Map map) => + new oauth2.Credentials.fromJson(JSON.stringify(map)); + + test("should load the same credentials from toJson", () { + var expiration = new Date.now().subtract(new Duration(hours: 1)); + var credentials = new oauth2.Credentials( + 'access token', 'refresh token', tokenEndpoint, ['scope1', 'scope2'], + expiration); + var reloaded = new oauth2.Credentials.fromJson(credentials.toJson()); + + expect(reloaded.accessToken, equals(credentials.accessToken)); + expect(reloaded.refreshToken, equals(credentials.refreshToken)); + expect(reloaded.tokenEndpoint.toString(), + equals(credentials.tokenEndpoint.toString())); + expect(reloaded.scopes, equals(credentials.scopes)); + expect(reloaded.expiration, equals(credentials.expiration)); + }); + + test("should throw a FormatException for invalid JSON", () { + expect(() => new oauth2.Credentials.fromJson("foo bar"), + throwsFormatException); + }); + + test("should throw a FormatException for JSON that's not a map", () { + expect(() => new oauth2.Credentials.fromJson("null"), + throwsFormatException); + }); + + test("should throw a FormatException if there is no accessToken", () { + expect(() => fromMap({}), throwsFormatException); + }); + + test("should throw a FormatException if accessToken is not a string", () { + expect(() => fromMap({"accessToken": 12}), throwsFormatException); + }); + + test("should throw a FormatException if refreshToken is not a string", () { + expect(() => fromMap({"accessToken": "foo", "refreshToken": 12}), + throwsFormatException); + }); + + test("should throw a FormatException if tokenEndpoint is not a string", () { + expect(() => fromMap({"accessToken": "foo", "tokenEndpoint": 12}), + throwsFormatException); + }); + + test("should throw a FormatException if scopes is not a list", () { + expect(() => fromMap({"accessToken": "foo", "scopes": 12}), + throwsFormatException); + }); + + test("should throw a FormatException if expiration is not an int", () { + expect(() => fromMap({"accessToken": "foo", "expiration": "12"}), + throwsFormatException); + }); + }); +} diff --git a/pkgs/oauth2/test/handle_access_token_response_test.dart b/pkgs/oauth2/test/handle_access_token_response_test.dart new file mode 100644 index 000000000..d5fed3a18 --- /dev/null +++ b/pkgs/oauth2/test/handle_access_token_response_test.dart @@ -0,0 +1,191 @@ +// 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 handle_access_token_response_test; + +import 'dart:io'; +import 'dart:json'; +import 'dart:uri'; + +import '../../unittest/lib/unittest.dart'; +import '../../http/lib/http.dart' as http; +import '../lib/oauth2.dart' as oauth2; +import '../lib/src/handle_access_token_response.dart'; +import 'utils.dart'; + +final Uri tokenEndpoint = new Uri.fromString("https://example.com/token"); + +final Date startTime = new Date.now(); + +Credentials handle(http.Response response) => + handleAccessTokenResponse(response, tokenEndpoint, startTime, ["scope"]); + +void main() { + group('an error response', () { + oauth2.Credentials handleError( + {String body: '{"error": "invalid_request"}', + int statusCode: 400, + Map headers: + const {"content-type": "application/json"}}) => + handle(new http.Response(body, statusCode, headers: headers)); + + test('causes an AuthorizationException', () { + expect(() => handleError(), throwsAuthorizationException); + }); + + test('with a 401 code causes an AuthorizationException', () { + expect(() => handleError(statusCode: 401), throwsAuthorizationException); + }); + + test('with an unexpected code causes a FormatException', () { + expect(() => handleError(statusCode: 500), + throwsFormatException); + }); + + test('with no content-type causes a FormatException', () { + expect(() => handleError(headers: {}), throwsFormatException); + }); + + test('with a non-JSON content-type causes a FormatException', () { + expect(() => handleError(headers: { + 'content-type': 'text/plain' + }), throwsFormatException); + }); + + test('with a JSON content-type and charset causes an ' + 'AuthorizationException', () { + expect(() => handleError(headers: { + 'content-type': 'application/json; charset=UTF-8' + }), throwsAuthorizationException); + }); + + test('with invalid JSON causes a FormatException', () { + expect(() => handleError(body: 'not json'), + throwsFormatException); + }); + + test('with a non-string error causes a FormatException', () { + expect(() => handleError(body: '{"error": 12}'), + throwsFormatException); + }); + + test('with a non-string error_description causes a FormatException', () { + expect(() => handleError(body: JSON.stringify({ + "error": "invalid_request", + "error_description": 12 + })), throwsFormatException); + }); + + test('with a non-string error_uri causes a FormatException', () { + expect(() => handleError(body: JSON.stringify({ + "error": "invalid_request", + "error_uri": 12 + })), throwsFormatException); + }); + + test('with a string error_description causes a AuthorizationException', () { + expect(() => handleError(body: JSON.stringify({ + "error": "invalid_request", + "error_description": "description" + })), throwsAuthorizationException); + }); + + test('with a string error_uri causes a AuthorizationException', () { + expect(() => handleError(body: JSON.stringify({ + "error": "invalid_request", + "error_uri": "http://example.com/error" + })), throwsAuthorizationException); + }); + }); + + group('a success response', () { + oauth2.Credentials handleSuccess( + {String contentType: "application/json", + String accessToken: 'access token', + String tokenType: 'bearer', + String expiresIn, + String refreshToken, + String scope}) { + return handle(new http.Response(JSON.stringify({ + 'access_token': accessToken, + 'token_type': tokenType, + 'expires_in': expiresIn, + 'refresh_token': refreshToken, + 'scope': scope + }), 200, headers: {'content-type': contentType})); + } + + test('returns the correct credentials', () { + var credentials = handleSuccess(); + expect(credentials.accessToken, equals('access token')); + expect(credentials.tokenEndpoint.toString(), + equals(tokenEndpoint.toString())); + }); + + test('with no content-type causes a FormatException', () { + expect(() => handleSuccess(contentType: null), throwsFormatException); + }); + + test('with a non-JSON content-type causes a FormatException', () { + expect(() => handleSuccess(contentType: 'text/plain'), + throwsFormatException); + }); + + test('with a JSON content-type and charset returns the correct ' + 'credentials', () { + var credentials = handleSuccess( + contentType: 'application/json; charset=UTF-8'); + expect(credentials.accessToken, equals('access token')); + }); + + test('with a null access token throws a FormatException', () { + expect(() => handleSuccess(accessToken: null), throwsFormatException); + }); + + test('with a non-string access token throws a FormatException', () { + expect(() => handleSuccess(accessToken: 12), throwsFormatException); + }); + + test('with a null token type throws a FormatException', () { + expect(() => handleSuccess(tokenType: null), throwsFormatException); + }); + + test('with a non-string token type throws a FormatException', () { + expect(() => handleSuccess(tokenType: 12), throwsFormatException); + }); + + test('with a non-"bearer" token type throws a FormatException', () { + expect(() => handleSuccess(tokenType: "mac"), throwsFormatException); + }); + + test('with a non-int expires-in throws a FormatException', () { + expect(() => handleSuccess(expiresIn: "whenever"), throwsFormatException); + }); + + test('with expires-in sets the expiration to ten seconds earlier than the ' + 'server says', () { + var credentials = handleSuccess(expiresIn: 100); + expect(credentials.expiration.millisecondsSinceEpoch, + startTime.millisecondsSinceEpoch + 90 * 1000); + }); + + test('with a non-string refresh token throws a FormatException', () { + expect(() => handleSuccess(refreshToken: 12), throwsFormatException); + }); + + test('with a refresh token sets the refresh token', () { + var credentials = handleSuccess(refreshToken: "refresh me"); + expect(credentials.refreshToken, equals("refresh me")); + }); + + test('with a non-string scope throws a FormatException', () { + expect(() => handleSuccess(scope: 12), throwsFormatException); + }); + + test('with a scope sets the scopes', () { + var credentials = handleSuccess(scope: "scope1 scope2"); + expect(credentials.scopes, equals(["scope1", "scope2"])); + }); + }); +} diff --git a/pkgs/oauth2/test/utils.dart b/pkgs/oauth2/test/utils.dart new file mode 100644 index 000000000..631ea9547 --- /dev/null +++ b/pkgs/oauth2/test/utils.dart @@ -0,0 +1,82 @@ +// 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 utils; + +import '../../unittest/lib/unittest.dart'; +import '../../http/lib/http.dart' as http; +import '../../http/lib/testing.dart'; +import '../lib/oauth2.dart' as oauth2; + +class ExpectClient extends MockClient { + final Queue _handlers; + + ExpectClient._(MockClientHandler fn) + : _handlers = new Queue(), + super(fn); + + factory ExpectClient() { + var client; + client = new ExpectClient._((request) => + client._handleRequest(request)); + return client; + } + + void expectRequest(MockClientHandler fn) { + var completer = new Completer(); + expect(completer.future, completes); + + _handlers.add((request) { + completer.complete(null); + return fn(request); + }); + } + + Future _handleRequest(http.Request request) { + if (_handlers.isEmpty) { + return new Future.immediate(new http.Response('not found', 404)); + } else { + return _handlers.removeFirst()(request); + } + } +} + +// TODO(nweiz): remove this once it's built in to unittest +/// A matcher for StateErrors. +const isStateError = const _StateError(); + +/// A matcher for functions that throw StateError. +const Matcher throwsStateError = + const Throws(isStateError); + +class _StateError extends TypeMatcher { + const _StateError() : super("StateError"); + bool matches(item, MatchState matchState) => item is StateError; +} + +/// A matcher for AuthorizationExceptions. +const isAuthorizationException = const _AuthorizationException(); + +/// A matcher for functions that throw AuthorizationException. +const Matcher throwsAuthorizationException = + const Throws(isAuthorizationException); + +class _AuthorizationException extends TypeMatcher { + const _AuthorizationException() : super("AuthorizationException"); + bool matches(item, MatchState matchState) => + item is oauth2.AuthorizationException; +} + +/// A matcher for ExpirationExceptions. +const isExpirationException = const _ExpirationException(); + +/// A matcher for functions that throw ExpirationException. +const Matcher throwsExpirationException = + const Throws(isExpirationException); + +class _ExpirationException extends TypeMatcher { + const _ExpirationException() : super("ExpirationException"); + bool matches(item, MatchState matchState) => + item is oauth2.ExpirationException; +} From 763ffb4b7dd9a42dea72b5498b92e9d185704f0a Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Mon, 19 Nov 2012 22:38:44 +0000 Subject: [PATCH 002/159] Fix OAuth2 tests, and disable them on non-dart:io platforms. TBR Review URL: https://codereview.chromium.org//11412083 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@15116 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/src/authorization_exception.dart | 1 + pkgs/oauth2/lib/src/expiration_exception.dart | 2 ++ pkgs/oauth2/test/authorization_code_grant_test.dart | 2 +- .../test/handle_access_token_response_test.dart | 12 ++++++------ 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/pkgs/oauth2/lib/src/authorization_exception.dart b/pkgs/oauth2/lib/src/authorization_exception.dart index d48c3b16d..1062aa987 100644 --- a/pkgs/oauth2/lib/src/authorization_exception.dart +++ b/pkgs/oauth2/lib/src/authorization_exception.dart @@ -5,6 +5,7 @@ library authorization_exception; import 'dart:io'; +import 'dart:uri'; /// An exception raised when OAuth2 authorization fails. class AuthorizationException implements Exception { diff --git a/pkgs/oauth2/lib/src/expiration_exception.dart b/pkgs/oauth2/lib/src/expiration_exception.dart index ac15559c3..8c8ad1f64 100644 --- a/pkgs/oauth2/lib/src/expiration_exception.dart +++ b/pkgs/oauth2/lib/src/expiration_exception.dart @@ -6,6 +6,8 @@ library expiration_exception; import 'dart:io'; +import 'credentials.dart'; + /// An exception raised when attempting to use expired OAuth2 credentials. class ExpirationException implements Exception { /// The expired credentials. diff --git a/pkgs/oauth2/test/authorization_code_grant_test.dart b/pkgs/oauth2/test/authorization_code_grant_test.dart index ad2c0ba3d..c4ccc216f 100644 --- a/pkgs/oauth2/test/authorization_code_grant_test.dart +++ b/pkgs/oauth2/test/authorization_code_grant_test.dart @@ -18,7 +18,7 @@ final redirectUrl = new Uri.fromString('http://example.com/redirect'); ExpectClient client; -AuthorizationCodeGrant grant; +oauth2.AuthorizationCodeGrant grant; void createGrant() { client = new ExpectClient(); diff --git a/pkgs/oauth2/test/handle_access_token_response_test.dart b/pkgs/oauth2/test/handle_access_token_response_test.dart index d5fed3a18..4757dc1b1 100644 --- a/pkgs/oauth2/test/handle_access_token_response_test.dart +++ b/pkgs/oauth2/test/handle_access_token_response_test.dart @@ -18,7 +18,7 @@ final Uri tokenEndpoint = new Uri.fromString("https://example.com/token"); final Date startTime = new Date.now(); -Credentials handle(http.Response response) => +oauth2.Credentials handle(http.Response response) => handleAccessTokenResponse(response, tokenEndpoint, startTime, ["scope"]); void main() { @@ -102,11 +102,11 @@ void main() { group('a success response', () { oauth2.Credentials handleSuccess( {String contentType: "application/json", - String accessToken: 'access token', - String tokenType: 'bearer', - String expiresIn, - String refreshToken, - String scope}) { + accessToken: 'access token', + tokenType: 'bearer', + expiresIn, + refreshToken, + scope}) { return handle(new http.Response(JSON.stringify({ 'access_token': accessToken, 'token_type': tokenType, From d64fe176fd9045556b5969f2c2619691ba45b823 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Mon, 19 Nov 2012 23:17:00 +0000 Subject: [PATCH 003/159] Add TODOs about narrowing JSON catch clauses to the OAuth2 package. Review URL: https://codereview.chromium.org//11308110 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@15120 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/src/credentials.dart | 1 + pkgs/oauth2/lib/src/handle_access_token_response.dart | 2 ++ 2 files changed, 3 insertions(+) diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index f0939c9c2..1a358273e 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -80,6 +80,7 @@ class Credentials { try { parsed = JSON.parse(json); } catch (e) { + // TODO(nweiz): narrow this catch clause once issue 6775 is fixed. validate(false, 'invalid JSON'); } diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart index af94f911f..cb08d8fd2 100644 --- a/pkgs/oauth2/lib/src/handle_access_token_response.dart +++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart @@ -42,6 +42,7 @@ Credentials handleAccessTokenResponse( try { parameters = JSON.parse(response.body); } catch (e) { + // TODO(nweiz): narrow this catch clause once issue 6775 is fixed. validate(false, 'invalid JSON'); } @@ -111,6 +112,7 @@ void _handleErrorResponse(http.Response response, Uri tokenEndpoint) { try { parameters = JSON.parse(response.body); } catch (e) { + // TODO(nweiz): narrow this catch clause once issue 6775 is fixed. validate(false, 'invalid JSON'); } From d61b3a3332d6aaaa21f3c30cca4f8fad7f35e42e Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Mon, 19 Nov 2012 23:54:31 +0000 Subject: [PATCH 004/159] Refactor http.Client and http.BaseClient to allow http.Client in type annotations. Review URL: https://codereview.chromium.org//11308111 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@15124 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/src/authorization_code_grant.dart | 4 ++-- pkgs/oauth2/lib/src/client.dart | 9 ++++----- pkgs/oauth2/lib/src/credentials.dart | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/pkgs/oauth2/lib/src/authorization_code_grant.dart b/pkgs/oauth2/lib/src/authorization_code_grant.dart index b4c50d966..dcb36d06f 100644 --- a/pkgs/oauth2/lib/src/authorization_code_grant.dart +++ b/pkgs/oauth2/lib/src/authorization_code_grant.dart @@ -75,7 +75,7 @@ class AuthorizationCodeGrant { final Uri tokenEndpoint; /// The HTTP client used to make HTTP requests. - http.BaseClient _httpClient; + http.Client _httpClient; /// The URL to which the resource owner will be redirected after they /// authorize this client with the authorization server. @@ -101,7 +101,7 @@ class AuthorizationCodeGrant { this.secret, this.authorizationEndpoint, this.tokenEndpoint, - {http.BaseClient httpClient}) + {http.Client httpClient}) : _httpClient = httpClient == null ? new http.Client() : httpClient; /// Returns the URL to which the resource owner should be redirected to diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart index dc71e4047..7322afac4 100644 --- a/pkgs/oauth2/lib/src/client.dart +++ b/pkgs/oauth2/lib/src/client.dart @@ -14,9 +14,8 @@ import 'utils.dart'; // TODO(nweiz): Add an onCredentialsRefreshed event once we have some event // infrastructure. -/// An OAuth2 client. This acts as a drop-in replacement for an -/// [http.BaseClient], while sending OAuth2 authorization credentials along with -/// each request. +/// An OAuth2 client. This acts as a drop-in replacement for an [http.Client], +/// while sending OAuth2 authorization credentials along with each request. /// /// The client also automatically refreshes its credentials if possible. When it /// makes a request, if its credentials are expired, it will first refresh them. @@ -62,7 +61,7 @@ class Client extends http.BaseClient { Credentials _credentials; /// The underlying HTTP client. - http.BaseClient _httpClient; + http.Client _httpClient; /// Creates a new client from a pre-existing set of credentials. When /// authorizing a client for the first time, you should use @@ -74,7 +73,7 @@ class Client extends http.BaseClient { this.identifier, this.secret, this._credentials, - {http.BaseClient httpClient}) + {http.Client httpClient}) : _httpClient = httpClient == null ? new http.Client() : httpClient; /// Sends an HTTP request with OAuth2 authorization credentials attached. This diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index 1a358273e..89af77711 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -145,7 +145,7 @@ class Credentials { String identifier, String secret, {List newScopes, - http.BaseClient httpClient}) { + http.Client httpClient}) { var scopes = this.scopes; if (newScopes != null) scopes = newScopes; if (scopes == null) scopes = []; From 581bf27dc6d1c5a5ea20b5023529e76fe081683c Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Wed, 28 Nov 2012 19:35:59 +0000 Subject: [PATCH 005/159] Add http and oauth2 as SDK packages. Review URL: https://codereview.chromium.org//11419207 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@15466 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/pubspec.yaml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 pkgs/oauth2/pubspec.yaml diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml new file mode 100644 index 000000000..477d211fa --- /dev/null +++ b/pkgs/oauth2/pubspec.yaml @@ -0,0 +1,7 @@ +name: oauth2 +description: A composable, Future-based API for making HTTP requests. +dependencies: + unittest: + sdk: unittest + http: + sdk: http From 3ab7e4fe28a46e1ed39776f0686ea950ea6ed2a6 Mon Sep 17 00:00:00 2001 From: "dgrove@google.com" Date: Fri, 30 Nov 2012 21:16:01 +0000 Subject: [PATCH 006/159] Add authors and homepages to packages. Review URL: https://codereview.chromium.org//11418267 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@15601 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/pubspec.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index 477d211fa..ae3d11df8 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,10 @@ name: oauth2 -description: A composable, Future-based API for making HTTP requests. +author: "Dart Team " +homepage: http://www.dartlang.org +description: > + A client library for authenticating with a remote service via OAuth2 on + behalf of a user, and making authorized HTTP requests with the user's + OAuth2 credentials. dependencies: unittest: sdk: unittest From ff0b1120b02bbb435f4c5298e812d2ce284cf537 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Tue, 4 Dec 2012 22:50:12 +0000 Subject: [PATCH 007/159] Make the oauth2 lib handle OAuth2 401 errors. BUG=6813 Review URL: https://codereview.chromium.org//11316325 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@15713 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/src/client.dart | 29 +++++++-- pkgs/oauth2/lib/src/utils.dart | 46 +++++++++++++ pkgs/oauth2/test/client_test.dart | 105 ++++++++++++++++++++++++++++++ pkgs/oauth2/test/utils_test.dart | 91 ++++++++++++++++++++++++++ 4 files changed, 267 insertions(+), 4 deletions(-) create mode 100644 pkgs/oauth2/test/utils_test.dart diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart index 7322afac4..eb7b0623b 100644 --- a/pkgs/oauth2/lib/src/client.dart +++ b/pkgs/oauth2/lib/src/client.dart @@ -8,6 +8,7 @@ import 'dart:uri'; import '../../../http/lib/http.dart' as http; +import 'authorization_exception.dart'; import 'credentials.dart'; import 'expiration_exception.dart'; import 'utils.dart'; @@ -24,9 +25,9 @@ import 'utils.dart'; /// authorization server provides ill-formatted responses, or an /// [ExpirationException] if the credentials are expired and can't be refreshed. /// -/// Currently this client doesn't attempt to identify errors from the resource -/// server that are caused by authentication failure. However, it may throw -/// [AuthorizationException]s for such errors in the future. +/// The client will also throw an [AuthorizationException] if the resource +/// server returns a 401 response with a WWW-Authenticate header indicating that +/// the current credentials are invalid. /// /// If you already have a set of [Credentials], you can construct a [Client] /// directly. However, in order to first obtain the credentials, you must @@ -87,8 +88,28 @@ class Client extends http.BaseClient { }).chain((_) { request.headers['authorization'] = "Bearer ${credentials.accessToken}"; return _httpClient.send(request); + }).transform((response) { + if (response.statusCode != 401 || + !response.headers.containsKey('www-authenticate')) { + return response; + } + + var authenticate; + try { + authenticate = parseAuthenticateHeader( + response.headers['www-authenticate']); + } on FormatException catch (e) { + return response; + } + + if (authenticate.first != 'bearer') return response; + + var params = authenticate.last; + if (!params.containsKey('error')) return response; + + throw new AuthorizationException( + params['error'], params['error_description'], params['error_uri']); }); - // TODO(nweiz): parse 401 errors that are caused by OAuth errors here. } /// Explicitly refreshes this client's credentials. Returns this client. diff --git a/pkgs/oauth2/lib/src/utils.dart b/pkgs/oauth2/lib/src/utils.dart index 583a5bc44..59428b3c6 100644 --- a/pkgs/oauth2/lib/src/utils.dart +++ b/pkgs/oauth2/lib/src/utils.dart @@ -65,6 +65,52 @@ List split1(String toSplit, String pattern) { toSplit.substring(index + pattern.length)]; } +/// A WWW-Authenticate header value, parsed as per [RFC 2617][]. +/// +/// [RFC 2617]: http://tools.ietf.org/html/rfc2617 +class AuthenticateHeader { + final String scheme; + final Map parameters; + + AuthenticateHeader(this.scheme, this.parameters); + + /// Parses a header string. Throws a [FormatException] if the header is + /// invalid. + factory AuthenticateHeader.parse(String header) { + var split = split1(header, ' '); + if (split.length == 0) { + throw new FormatException('Invalid WWW-Authenticate header: "$header"'); + } else if (split.length == 1 || split[1].trim().isEmpty) { + return new AuthenticateHeader(split[0].toLowerCase(), {}); + } + var scheme = split[0].toLowerCase(); + var paramString = split[1]; + + // From http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html. + var tokenChar = r'[^\0-\x1F()<>@,;:\\"/\[\]?={} \t\x7F]'; + var quotedStringChar = r'(?:[^\0-\x1F\x7F"]|\\.)'; + var regexp = new RegExp('^ *($tokenChar+)="($quotedStringChar*)" *(, *)?'); + + var parameters = {}; + var match; + do { + match = regexp.firstMatch(paramString); + if (match == null) { + throw new FormatException('Invalid WWW-Authenticate header: "$header"'); + } + + paramString = paramString.substring(match.end); + parameters[match.group(1).toLowerCase()] = match.group(2); + } while (match.group(3) != null); + + if (!paramString.trim().isEmpty) { + throw new FormatException('Invalid WWW-Authenticate header: "$header"'); + } + + return new AuthenticateHeader(scheme, parameters); + } +} + /// Returns a [Future] that asynchronously completes to `null`. Future get async { var completer = new Completer(); diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart index 94b792038..fba14a626 100644 --- a/pkgs/oauth2/test/client_test.dart +++ b/pkgs/oauth2/test/client_test.dart @@ -117,4 +117,109 @@ void main() { expect(client.refreshCredentials(), throwsStateError); }); }); + + group('with invalid credentials', () { + setUp(createHttpClient); + + test('throws an AuthorizationException for a 401 response', () { + var credentials = new oauth2.Credentials('access token'); + var client = new oauth2.Client('identifier', 'secret', credentials, + httpClient: httpClient); + + httpClient.expectRequest((request) { + expect(request.method, equals('GET')); + expect(request.url.toString(), equals(requestUri.toString())); + expect(request.headers['authorization'], + equals('Bearer access token')); + + var authenticate = 'Bearer error="invalid_token", error_description=' + '"Something is terribly wrong."'; + return new Future.immediate(new http.Response('bad job', 401, + headers: {'www-authenticate': authenticate})); + }); + + expect(client.read(requestUri), throwsAuthorizationException); + }); + + test('passes through a 401 response without www-authenticate', () { + var credentials = new oauth2.Credentials('access token'); + var client = new oauth2.Client('identifier', 'secret', credentials, + httpClient: httpClient); + + httpClient.expectRequest((request) { + expect(request.method, equals('GET')); + expect(request.url.toString(), equals(requestUri.toString())); + expect(request.headers['authorization'], + equals('Bearer access token')); + + return new Future.immediate(new http.Response('bad job', 401)); + }); + + expect( + client.get(requestUri).transform((response) => response.statusCode), + completion(equals(401))); + }); + + test('passes through a 401 response with invalid www-authenticate', () { + var credentials = new oauth2.Credentials('access token'); + var client = new oauth2.Client('identifier', 'secret', credentials, + httpClient: httpClient); + + httpClient.expectRequest((request) { + expect(request.method, equals('GET')); + expect(request.url.toString(), equals(requestUri.toString())); + expect(request.headers['authorization'], + equals('Bearer access token')); + + var authenticate = 'Bearer error="invalid_token", error_description=' + '"Something is terribly wrong.", '; + return new Future.immediate(new http.Response('bad job', 401, + headers: {'www-authenticate': authenticate})); + }); + + expect( + client.get(requestUri).transform((response) => response.statusCode), + completion(equals(401))); + }); + + test('passes through a 401 response with non-bearer www-authenticate', () { + var credentials = new oauth2.Credentials('access token'); + var client = new oauth2.Client('identifier', 'secret', credentials, + httpClient: httpClient); + + httpClient.expectRequest((request) { + expect(request.method, equals('GET')); + expect(request.url.toString(), equals(requestUri.toString())); + expect(request.headers['authorization'], + equals('Bearer access token')); + + return new Future.immediate(new http.Response('bad job', 401, + headers: {'www-authenticate': 'Digest'})); + }); + + expect( + client.get(requestUri).transform((response) => response.statusCode), + completion(equals(401))); + }); + + test('passes through a 401 response with non-OAuth2 www-authenticate', () { + var credentials = new oauth2.Credentials('access token'); + var client = new oauth2.Client('identifier', 'secret', credentials, + httpClient: httpClient); + + httpClient.expectRequest((request) { + expect(request.method, equals('GET')); + expect(request.url.toString(), equals(requestUri.toString())); + expect(request.headers['authorization'], + equals('Bearer access token')); + + return new Future.immediate(new http.Response('bad job', 401, + headers: {'www-authenticate': 'Bearer'})); + }); + + expect( + client.get(requestUri).transform((response) => response.statusCode), + completion(equals(401))); + }); + }); } diff --git a/pkgs/oauth2/test/utils_test.dart b/pkgs/oauth2/test/utils_test.dart new file mode 100644 index 000000000..d76e2b001 --- /dev/null +++ b/pkgs/oauth2/test/utils_test.dart @@ -0,0 +1,91 @@ +// 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 utils_test; + +import '../../unittest/lib/unittest.dart'; +import '../lib/src/utils.dart'; + + +void main() { + group('AuthenticateHeader', () { + test("parses a scheme", () { + var header = new AuthenticateHeader.parse('bearer'); + expect(header.scheme, equals('bearer')); + expect(header.parameters, equals({})); + }); + + test("lower-cases the scheme", () { + var header = new AuthenticateHeader.parse('BeaRer'); + expect(header.scheme, equals('bearer')); + expect(header.parameters, equals({})); + }); + + test("parses a scheme with trailing whitespace", () { + var header = new AuthenticateHeader.parse('bearer '); + expect(header.scheme, equals('bearer')); + expect(header.parameters, equals({})); + }); + + test("parses a scheme with one param", () { + var header = new AuthenticateHeader.parse('bearer foo="bar"'); + expect(header.scheme, equals('bearer')); + expect(header.parameters, equals({'foo': 'bar'})); + }); + + test("parses a scheme with several params", () { + var header = new AuthenticateHeader.parse( + 'bearer foo="bar", bar="baz" ,baz="qux"'); + expect(header.scheme, equals('bearer')); + expect(header.parameters, equals({ + 'foo': 'bar', + 'bar': 'baz', + 'baz': 'qux' + })); + }); + + test("lower-cases parameter names but not values", () { + var header = new AuthenticateHeader.parse('bearer FoO="bAr"'); + expect(header.scheme, equals('bearer')); + expect(header.parameters, equals({'foo': 'bAr'})); + }); + + test("allows empty values", () { + var header = new AuthenticateHeader.parse('bearer foo=""'); + expect(header.scheme, equals('bearer')); + expect(header.parameters, equals({'foo': ''})); + }); + + test("won't parse an empty string", () { + expect(() => new AuthenticateHeader.parse(''), + throwsFormatException); + }); + + test("won't parse a token without a value", () { + expect(() => new AuthenticateHeader.parse('bearer foo'), + throwsFormatException); + + expect(() => new AuthenticateHeader.parse('bearer foo='), + throwsFormatException); + }); + + test("won't parse a token without a value", () { + expect(() => new AuthenticateHeader.parse('bearer foo'), + throwsFormatException); + + expect(() => new AuthenticateHeader.parse('bearer foo='), + throwsFormatException); + }); + + test("won't parse a trailing comma", () { + expect(() => new AuthenticateHeader.parse('bearer foo="bar",'), + throwsFormatException); + }); + + test("won't parse a multiple params without a comma", () { + expect(() => new AuthenticateHeader.parse('bearer foo="bar" bar="baz"'), + throwsFormatException); + }); + }); +} From b4fb691c5d5c0ec206a609ea39c74967221130de Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Wed, 5 Dec 2012 19:47:32 +0000 Subject: [PATCH 008/159] Handle OAuth2 AuthorizationExceptions in pub. BUG=6950 Review URL: https://codereview.chromium.org//11416352 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@15747 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/src/client.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart index eb7b0623b..a397c00fc 100644 --- a/pkgs/oauth2/lib/src/client.dart +++ b/pkgs/oauth2/lib/src/client.dart @@ -96,15 +96,15 @@ class Client extends http.BaseClient { var authenticate; try { - authenticate = parseAuthenticateHeader( + authenticate = new AuthenticateHeader.parse( response.headers['www-authenticate']); } on FormatException catch (e) { return response; } - if (authenticate.first != 'bearer') return response; + if (authenticate.scheme != 'bearer') return response; - var params = authenticate.last; + var params = authenticate.parameters; if (!params.containsKey('error')) return response; throw new AuthorizationException( From 0fff24e5c9da2c1c6deeccbc3f092305b217892e Mon Sep 17 00:00:00 2001 From: "sgjesse@google.com" Date: Fri, 21 Dec 2012 09:50:05 +0000 Subject: [PATCH 009/159] Reapply "Fix URI encoding/decoding of + and space"" Updated test expectations for some tests for pkg/http, pkg/oauth2 and pub to match the change. TBR=lrn@google.com Review URL: https://codereview.chromium.org//11659009 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@16426 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/test/authorization_code_grant_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/oauth2/test/authorization_code_grant_test.dart b/pkgs/oauth2/test/authorization_code_grant_test.dart index c4ccc216f..2b80cf454 100644 --- a/pkgs/oauth2/test/authorization_code_grant_test.dart +++ b/pkgs/oauth2/test/authorization_code_grant_test.dart @@ -50,7 +50,7 @@ void main() { '?response_type=code' '&client_id=identifier' '&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect' - '&scope=scope%20other%2Fscope')); + '&scope=scope+other%2Fscope')); }); test('builds the correct URL with state', () { From a5e687cc762f23c86668b22444fa3183298017f9 Mon Sep 17 00:00:00 2001 From: "floitsch@google.com" Date: Mon, 7 Jan 2013 11:23:16 +0000 Subject: [PATCH 010/159] Big merge from experimental to bleeding edge. Review URL: https://codereview.chromium.org//11783009 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@16687 260f80e4-7a28-3924-810f-c04153c831b5 --- .../lib/src/authorization_code_grant.dart | 7 ++-- pkgs/oauth2/lib/src/client.dart | 11 +++--- pkgs/oauth2/lib/src/credentials.dart | 9 +++-- .../lib/src/handle_access_token_response.dart | 2 +- pkgs/oauth2/lib/src/utils.dart | 9 ++--- .../test/authorization_code_grant_test.dart | 38 ++++++++++++------- pkgs/oauth2/test/client_test.dart | 12 +++--- pkgs/oauth2/test/credentials_test.dart | 19 ++++++---- .../handle_access_token_response_test.dart | 2 +- pkgs/oauth2/test/utils.dart | 2 + 10 files changed, 65 insertions(+), 46 deletions(-) diff --git a/pkgs/oauth2/lib/src/authorization_code_grant.dart b/pkgs/oauth2/lib/src/authorization_code_grant.dart index dcb36d06f..5e9cbd2f6 100644 --- a/pkgs/oauth2/lib/src/authorization_code_grant.dart +++ b/pkgs/oauth2/lib/src/authorization_code_grant.dart @@ -4,6 +4,7 @@ library authorization_code_grant; +import 'dart:async'; import 'dart:uri'; // TODO(nweiz): This should be a "package:" import. See issue 6745. @@ -159,7 +160,7 @@ class AuthorizationCodeGrant { /// /// Throws [AuthorizationException] if the authorization fails. Future handleAuthorizationResponse(Map parameters) { - return async.chain((_) { + return async.then((_) { if (_state == _INITIAL_STATE) { throw new StateError( 'The authorization URL has not yet been generated.'); @@ -211,7 +212,7 @@ class AuthorizationCodeGrant { /// /// Throws [AuthorizationException] if the authorization fails. Future handleAuthorizationCode(String authorizationCode) { - return async.chain((_) { + return async.then((_) { if (_state == _INITIAL_STATE) { throw new StateError( 'The authorization URL has not yet been generated.'); @@ -238,7 +239,7 @@ class AuthorizationCodeGrant { // it be configurable? "client_id": this.identifier, "client_secret": this.secret - }).transform((response) { + }).then((response) { var credentials = handleAccessTokenResponse( response, tokenEndpoint, startTime, _scopes); return new Client( diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart index a397c00fc..fd315bb3f 100644 --- a/pkgs/oauth2/lib/src/client.dart +++ b/pkgs/oauth2/lib/src/client.dart @@ -4,6 +4,7 @@ library client; +import 'dart:async'; import 'dart:uri'; import '../../../http/lib/http.dart' as http; @@ -81,14 +82,14 @@ class Client extends http.BaseClient { /// will also automatically refresh this client's [Credentials] before sending /// the request if necessary. Future send(http.BaseRequest request) { - return async.chain((_) { + return async.then((_) { if (!credentials.isExpired) return new Future.immediate(null); if (!credentials.canRefresh) throw new ExpirationException(credentials); return refreshCredentials(); - }).chain((_) { + }).then((_) { request.headers['authorization'] = "Bearer ${credentials.accessToken}"; return _httpClient.send(request); - }).transform((response) { + }).then((response) { if (response.statusCode != 401 || !response.headers.containsKey('www-authenticate')) { return response; @@ -122,7 +123,7 @@ class Client extends http.BaseClient { /// [newScopes]. These must be a subset of the scopes in the /// [Credentials.scopes] field of [Client.credentials]. Future refreshCredentials([List newScopes]) { - return async.chain((_) { + return async.then((_) { if (!credentials.canRefresh) { var prefix = "OAuth credentials"; if (credentials.isExpired) prefix = "$prefix have expired and"; @@ -131,7 +132,7 @@ class Client extends http.BaseClient { return credentials.refresh(identifier, secret, newScopes: newScopes, httpClient: _httpClient); - }).transform((credentials) { + }).then((credentials) { _credentials = credentials; return this; }); diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index 89af77711..12a0be8fd 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -4,7 +4,8 @@ library credentials; -import 'dart:json'; +import 'dart:async'; +import 'dart:json' as JSON; import 'dart:uri'; import '../../../http/lib/http.dart' as http; @@ -152,7 +153,7 @@ class Credentials { if (httpClient == null) httpClient = new http.Client(); var startTime = new Date.now(); - return async.chain((_) { + return async.then((_) { if (refreshToken == null) { throw new StateError("Can't refresh credentials without a refresh " "token."); @@ -173,10 +174,10 @@ class Credentials { if (!scopes.isEmpty) fields["scope"] = Strings.join(scopes, ' '); return httpClient.post(tokenEndpoint, fields: fields); - }).transform((response) { + }).then((response) { return handleAccessTokenResponse( response, tokenEndpoint, startTime, scopes); - }).transform((credentials) { + }).then((credentials) { // The authorization server may issue a new refresh token. If it doesn't, // we should re-use the one we already have. if (credentials.refreshToken != null) return credentials; diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart index cb08d8fd2..b80532d3f 100644 --- a/pkgs/oauth2/lib/src/handle_access_token_response.dart +++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart @@ -5,7 +5,7 @@ library handle_access_token_response; import 'dart:io'; -import 'dart:json'; +import 'dart:json' as JSON; import 'dart:uri'; import '../../../http/lib/http.dart' as http; diff --git a/pkgs/oauth2/lib/src/utils.dart b/pkgs/oauth2/lib/src/utils.dart index 59428b3c6..eb57e5626 100644 --- a/pkgs/oauth2/lib/src/utils.dart +++ b/pkgs/oauth2/lib/src/utils.dart @@ -4,6 +4,7 @@ library utils; +import 'dart:async'; import 'dart:uri'; import 'dart:isolate'; import 'dart:crypto'; @@ -38,7 +39,7 @@ String mapToQuery(Map map) { value = (value == null || value.isEmpty) ? null : encodeUriComponent(value); pairs.add([key, value]); }); - return Strings.join(pairs.map((pair) { + return Strings.join(pairs.mappedBy((pair) { if (pair[1] == null) return pair[0]; return "${pair[0]}=${pair[1]}"; }), "&"); @@ -112,8 +113,4 @@ class AuthenticateHeader { } /// Returns a [Future] that asynchronously completes to `null`. -Future get async { - var completer = new Completer(); - new Timer(0, (_) => completer.complete(null)); - return completer.future; -} +Future get async => new Future.delayed(0, () => null); diff --git a/pkgs/oauth2/test/authorization_code_grant_test.dart b/pkgs/oauth2/test/authorization_code_grant_test.dart index 2b80cf454..e896dca25 100644 --- a/pkgs/oauth2/test/authorization_code_grant_test.dart +++ b/pkgs/oauth2/test/authorization_code_grant_test.dart @@ -30,6 +30,12 @@ void createGrant() { httpClient: client); } +void expectFutureThrows(future, predicate) { + future.catchError(expectAsync1((AsyncError e) { + expect(predicate(e.error), isTrue); + })); +} + void main() { group('.getAuthorizationUrl', () { setUp(createGrant); @@ -83,7 +89,8 @@ void main() { test("can't be called twice", () { grant.getAuthorizationUrl(redirectUrl); - expect(() => grant.getAuthorizationUrl(redirectUrl), throwsStateError); + expectFutureThrows(grant.getAuthorizationUrl(redirectUrl), + (e) => e is StateError); }); }); @@ -91,39 +98,44 @@ void main() { setUp(createGrant); test("can't be called before .getAuthorizationUrl", () { - expect(grant.handleAuthorizationResponse({}), throwsStateError); + expectFutureThrows(grant.handleAuthorizationResponse({}), + (e) => e is StateError); }); test("can't be called twice", () { grant.getAuthorizationUrl(redirectUrl); grant.handleAuthorizationResponse({'code': 'auth code'}); - expect(grant.handleAuthorizationResponse({'code': 'auth code'}), - throwsStateError); + expectFutureThrows( + grant.handleAuthorizationResponse({'code': 'auth code'}), + (e) => e is StateError); }); test('must have a state parameter if the authorization URL did', () { grant.getAuthorizationUrl(redirectUrl, state: 'state'); - expect(grant.handleAuthorizationResponse({'code': 'auth code'}), - throwsFormatException); + expectFutureThrows( + grant.handleAuthorizationResponse({'code': 'auth code'}), + (e) => e is FormatException); }); test('must have the same state parameter the authorization URL did', () { grant.getAuthorizationUrl(redirectUrl, state: 'state'); - expect(grant.handleAuthorizationResponse({ + expectFutureThrows(grant.handleAuthorizationResponse({ 'code': 'auth code', 'state': 'other state' - }), throwsFormatException); + }), (e) => e is FormatException); }); test('must have a code parameter', () { grant.getAuthorizationUrl(redirectUrl); - expect(grant.handleAuthorizationResponse({}), throwsFormatException); + expectFutureThrows(grant.handleAuthorizationResponse({}), + (e) => e is FormatException); }); test('with an error parameter throws an AuthorizationException', () { grant.getAuthorizationUrl(redirectUrl); - expect(grant.handleAuthorizationResponse({'error': 'invalid_request'}), - throwsAuthorizationException); + expectFutureThrows( + grant.handleAuthorizationResponse({'error': 'invalid_request'}), + (e) => e is AuthorizationException); }); test('sends an authorization code request', () { @@ -163,8 +175,8 @@ void main() { test("can't be called twice", () { grant.getAuthorizationUrl(redirectUrl); grant.handleAuthorizationCode('auth code'); - expect(grant.handleAuthorizationCode('auth code'), - throwsStateError); + expectFutureThrows(grant.handleAuthorizationCode('auth code'), + (e) => e is StateError); }); test('sends an authorization code request', () { diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart index fba14a626..af8e7e474 100644 --- a/pkgs/oauth2/test/client_test.dart +++ b/pkgs/oauth2/test/client_test.dart @@ -63,7 +63,7 @@ void main() { return new Future.immediate(new http.Response('good job', 200)); }); - expect(client.read(requestUri).transform((_) { + expect(client.read(requestUri).then((_) { expect(client.credentials.accessToken, equals('new access token')); }), completes); }); @@ -104,7 +104,7 @@ void main() { }), 200, headers: {'content-type': 'application/json'})); }); - expect(client.refreshCredentials().transform((_) { + expect(client.refreshCredentials().then((_) { expect(client.credentials.accessToken, equals('new access token')); }), completes); }); @@ -156,7 +156,7 @@ void main() { }); expect( - client.get(requestUri).transform((response) => response.statusCode), + client.get(requestUri).then((response) => response.statusCode), completion(equals(401))); }); @@ -178,7 +178,7 @@ void main() { }); expect( - client.get(requestUri).transform((response) => response.statusCode), + client.get(requestUri).then((response) => response.statusCode), completion(equals(401))); }); @@ -198,7 +198,7 @@ void main() { }); expect( - client.get(requestUri).transform((response) => response.statusCode), + client.get(requestUri).then((response) => response.statusCode), completion(equals(401))); }); @@ -218,7 +218,7 @@ void main() { }); expect( - client.get(requestUri).transform((response) => response.statusCode), + client.get(requestUri).then((response) => response.statusCode), completion(equals(401))); }); }); diff --git a/pkgs/oauth2/test/credentials_test.dart b/pkgs/oauth2/test/credentials_test.dart index 5faf2d04c..57e31a149 100644 --- a/pkgs/oauth2/test/credentials_test.dart +++ b/pkgs/oauth2/test/credentials_test.dart @@ -4,8 +4,9 @@ library credentials_test; +import 'dart:async'; import 'dart:io'; -import 'dart:json'; +import 'dart:json' as JSON; import 'dart:uri'; import '../../unittest/lib/unittest.dart'; @@ -43,15 +44,19 @@ void main() { var credentials = new oauth2.Credentials( 'access token', null, tokenEndpoint); expect(credentials.canRefresh, false); - expect(credentials.refresh('identifier', 'secret', httpClient: httpClient), - throwsStateError); + credentials.refresh('identifier', 'secret', httpClient: httpClient) + .catchError(expectAsync1((e) { + expect(e.error is StateError, isTrue); + })); }); test("can't refresh without a token endpoint", () { var credentials = new oauth2.Credentials('access token', 'refresh token'); expect(credentials.canRefresh, false); - expect(credentials.refresh('identifier', 'secret', httpClient: httpClient), - throwsStateError); + credentials.refresh('identifier', 'secret', httpClient: httpClient) + .catchError(expectAsync1((e) { + expect(e.error is StateError, isTrue); + })); }); test("can refresh with a refresh token and a token endpoint", () { @@ -79,7 +84,7 @@ void main() { expect(credentials.refresh('identifier', 'secret', httpClient: httpClient) - .transform((credentials) { + .then((credentials) { expect(credentials.accessToken, equals('new access token')); expect(credentials.refreshToken, equals('new refresh token')); }), completes); @@ -108,7 +113,7 @@ void main() { expect(credentials.refresh('identifier', 'secret', httpClient: httpClient) - .transform((credentials) { + .then((credentials) { expect(credentials.accessToken, equals('new access token')); expect(credentials.refreshToken, equals('refresh token')); }), completes); diff --git a/pkgs/oauth2/test/handle_access_token_response_test.dart b/pkgs/oauth2/test/handle_access_token_response_test.dart index 4757dc1b1..48465f204 100644 --- a/pkgs/oauth2/test/handle_access_token_response_test.dart +++ b/pkgs/oauth2/test/handle_access_token_response_test.dart @@ -5,7 +5,7 @@ library handle_access_token_response_test; import 'dart:io'; -import 'dart:json'; +import 'dart:json' as JSON; import 'dart:uri'; import '../../unittest/lib/unittest.dart'; diff --git a/pkgs/oauth2/test/utils.dart b/pkgs/oauth2/test/utils.dart index 631ea9547..cf5bff26f 100644 --- a/pkgs/oauth2/test/utils.dart +++ b/pkgs/oauth2/test/utils.dart @@ -4,6 +4,8 @@ library utils; +import 'dart:async'; + import '../../unittest/lib/unittest.dart'; import '../../http/lib/http.dart' as http; import '../../http/lib/testing.dart'; From a85b692790c191c02741995be03870200fc26698 Mon Sep 17 00:00:00 2001 From: "ajohnsen@google.com" Date: Mon, 7 Jan 2013 11:59:49 +0000 Subject: [PATCH 011/159] Fix tests for VM in checked mode. BUG= Review URL: https://codereview.chromium.org//11801008 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@16689 260f80e4-7a28-3924-810f-c04153c831b5 --- .../test/authorization_code_grant_test.dart | 27 +++++++++++-------- pkgs/oauth2/test/client_test.dart | 18 ++++++++++--- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/pkgs/oauth2/test/authorization_code_grant_test.dart b/pkgs/oauth2/test/authorization_code_grant_test.dart index e896dca25..7614bcd5e 100644 --- a/pkgs/oauth2/test/authorization_code_grant_test.dart +++ b/pkgs/oauth2/test/authorization_code_grant_test.dart @@ -4,8 +4,9 @@ library authorization_code_grant_test; +import 'dart:async'; import 'dart:io'; -import 'dart:json'; +import 'dart:json' as JSON; import 'dart:uri'; import '../../unittest/lib/unittest.dart'; @@ -89,8 +90,7 @@ void main() { test("can't be called twice", () { grant.getAuthorizationUrl(redirectUrl); - expectFutureThrows(grant.getAuthorizationUrl(redirectUrl), - (e) => e is StateError); + expect(() => grant.getAuthorizationUrl(redirectUrl), throwsStateError); }); }); @@ -104,7 +104,9 @@ void main() { test("can't be called twice", () { grant.getAuthorizationUrl(redirectUrl); - grant.handleAuthorizationResponse({'code': 'auth code'}); + expectFutureThrows( + grant.handleAuthorizationResponse({'code': 'auth code'}), + (e) => e is FormatException); expectFutureThrows( grant.handleAuthorizationResponse({'code': 'auth code'}), (e) => e is StateError); @@ -135,7 +137,7 @@ void main() { grant.getAuthorizationUrl(redirectUrl); expectFutureThrows( grant.handleAuthorizationResponse({'error': 'invalid_request'}), - (e) => e is AuthorizationException); + (e) => e is oauth2.AuthorizationException); }); test('sends an authorization code request', () { @@ -157,11 +159,10 @@ void main() { }), 200, headers: {'content-type': 'application/json'})); }); - expect(grant.handleAuthorizationResponse({'code': 'auth code'}), - completion(predicate((client) { + grant.handleAuthorizationResponse({'code': 'auth code'}) + .then(expectAsync1((client) { expect(client.credentials.accessToken, equals('access token')); - return true; - }))); + })); }); }); @@ -169,12 +170,16 @@ void main() { setUp(createGrant); test("can't be called before .getAuthorizationUrl", () { - expect(grant.handleAuthorizationCode('auth code'), throwsStateError); + expectFutureThrows( + grant.handleAuthorizationCode('auth code'), + (e) => e is StateError); }); test("can't be called twice", () { grant.getAuthorizationUrl(redirectUrl); - grant.handleAuthorizationCode('auth code'); + expectFutureThrows( + grant.handleAuthorizationCode('auth code'), + (e) => e is FormatException); expectFutureThrows(grant.handleAuthorizationCode('auth code'), (e) => e is StateError); }); diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart index af8e7e474..6ad7e48b3 100644 --- a/pkgs/oauth2/test/client_test.dart +++ b/pkgs/oauth2/test/client_test.dart @@ -4,8 +4,9 @@ library client_test; +import 'dart:async'; import 'dart:io'; -import 'dart:json'; +import 'dart:json' as JSON; import 'dart:uri'; import '../../unittest/lib/unittest.dart'; @@ -23,6 +24,12 @@ void createHttpClient() { httpClient = new ExpectClient(); } +void expectFutureThrows(future, predicate) { + future.catchError(expectAsync1((AsyncError e) { + expect(predicate(e.error), isTrue); + })); +} + void main() { group('with expired credentials', () { setUp(createHttpClient); @@ -34,7 +41,8 @@ void main() { var client = new oauth2.Client('identifier', 'secret', credentials, httpClient: httpClient); - expect(client.get(requestUri), throwsExpirationException); + expectFutureThrows(client.get(requestUri), + (e) => e is oauth2.ExpirationException); }); test("that can be refreshed refreshes the credentials and sends the " @@ -114,7 +122,8 @@ void main() { var client = new oauth2.Client('identifier', 'secret', credentials, httpClient: httpClient); - expect(client.refreshCredentials(), throwsStateError); + expectFutureThrows(client.refreshCredentials(), + (e) => e is StateError); }); }); @@ -138,7 +147,8 @@ void main() { headers: {'www-authenticate': authenticate})); }); - expect(client.read(requestUri), throwsAuthorizationException); + expectFutureThrows(client.read(requestUri), + (e) => e is oauth2.AuthorizationException); }); test('passes through a 401 response without www-authenticate', () { From 7129feb0ae7224530cc6185505318537f029f44b Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Wed, 9 Jan 2013 00:18:13 +0000 Subject: [PATCH 012/159] Fix the OAuth2 documentation to use then rather than chain/transform. Review URL: https://codereview.chromium.org//11826010 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@16828 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/oauth2.dart | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pkgs/oauth2/lib/oauth2.dart b/pkgs/oauth2/lib/oauth2.dart index a7e9307d5..cc0bb990d 100644 --- a/pkgs/oauth2/lib/oauth2.dart +++ b/pkgs/oauth2/lib/oauth2.dart @@ -52,11 +52,11 @@ /// "http://my-site.com/oauth2-redirect"); /// /// var credentialsFile = new File("~/.myapp/credentials.json"); -/// return credentialsFile.exists().chain((exists) { +/// return credentialsFile.exists().then((exists) { /// // If the OAuth2 credentials have already been saved from a previous /// // run, we just want to reload them. /// if (exists) { -/// return credentialsFile.readAsText().transform((json) { +/// return credentialsFile.readAsText().then((json) { /// var credentials = new oauth2.Credentials.fromJson(json); /// return new oauth2.Client(identifier, secret, credentials); /// }); @@ -76,28 +76,28 @@ /// // /// // `redirect` is an imaginary function that redirects the resource /// // owner's browser. -/// return redirect(grant.getAuthorizationUrl(redirectUrl)).chain((_) { +/// return redirect(grant.getAuthorizationUrl(redirectUrl)).then((_) { /// // Another imaginary function that listens for a request to /// // `redirectUrl`. /// return listen(redirectUrl); -/// }).transform((request) { +/// }).then((request) { /// // Once the user is redirected to `redirectUrl`, pass the query /// // parameters to the AuthorizationCodeGrant. It will validate them /// // and extract the authorization code to create a new Client. /// return grant.handleAuthorizationResponse(request.queryParameters); /// }) -/// }).chain((client) { +/// }).then((client) { /// // Once you have a Client, you can use it just like any other HTTP /// // client. /// return client.read("http://example.com/protected-resources.txt") -/// .transform((result) { +/// .then((result) { /// // Once we're done with the client, save the credentials file. This /// // ensures that if the credentials were automatically refreshed /// // while using the client, the new credentials are available for the /// // next run of the program. -/// return credentialsFile.open(FileMode.WRITE).chain((file) { +/// return credentialsFile.open(FileMode.WRITE).then((file) { /// return file.writeString(client.credentials.toJson()); -/// }).chain((file) => file.close()).transform((_) => result); +/// }).then((file) => file.close()).then((_) => result); /// }); /// }).then(print); library oauth2; From b0f13021dfbffa5ef77fdd236e5fa69ed9daa1c5 Mon Sep 17 00:00:00 2001 From: "floitsch@google.com" Date: Mon, 21 Jan 2013 13:34:02 +0000 Subject: [PATCH 013/159] Move some core classes to collection library. Review URL: https://codereview.chromium.org//11867024 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@17352 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/test/utils.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/pkgs/oauth2/test/utils.dart b/pkgs/oauth2/test/utils.dart index cf5bff26f..2b6c1a7aa 100644 --- a/pkgs/oauth2/test/utils.dart +++ b/pkgs/oauth2/test/utils.dart @@ -5,6 +5,7 @@ library utils; import 'dart:async'; +import 'dart:collection' show Queue; import '../../unittest/lib/unittest.dart'; import '../../http/lib/http.dart' as http; From a8d911f85348a3ec63f4ba9c3b66c9562feeafaf Mon Sep 17 00:00:00 2001 From: "hausner@google.com" Date: Tue, 22 Jan 2013 16:48:00 +0000 Subject: [PATCH 014/159] Stop supporting map literals with 1 type argument To be checked in next week Tue (Jan 22) Review URL: https://codereview.chromium.org//12021022 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@17410 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/src/utils.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/oauth2/lib/src/utils.dart b/pkgs/oauth2/lib/src/utils.dart index eb57e5626..085fa2f5c 100644 --- a/pkgs/oauth2/lib/src/utils.dart +++ b/pkgs/oauth2/lib/src/utils.dart @@ -20,7 +20,7 @@ Uri addQueryParameters(Uri url, Map parameters) { /// Convert a URL query string (or `application/x-www-form-urlencoded` body) /// into a [Map] from parameter names to values. Map queryToMap(String queryList) { - var map = {}; + var map = {}; for (var pair in queryList.split("&")) { var split = split1(pair, "="); if (split.isEmpty) continue; From 65bb5818e66f190f16d255938bcda0b42075c676 Mon Sep 17 00:00:00 2001 From: "hausner@google.com" Date: Tue, 22 Jan 2013 17:31:29 +0000 Subject: [PATCH 015/159] Fix map literals from change 17410 Remove any type arguments from map literals that had one type argument before change 17410. Review URL: https://codereview.chromium.org//12045024 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@17412 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/src/utils.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/oauth2/lib/src/utils.dart b/pkgs/oauth2/lib/src/utils.dart index 085fa2f5c..85a9f6796 100644 --- a/pkgs/oauth2/lib/src/utils.dart +++ b/pkgs/oauth2/lib/src/utils.dart @@ -20,7 +20,7 @@ Uri addQueryParameters(Uri url, Map parameters) { /// Convert a URL query string (or `application/x-www-form-urlencoded` body) /// into a [Map] from parameter names to values. Map queryToMap(String queryList) { - var map = {}; + var map = {}; for (var pair in queryList.split("&")) { var split = split1(pair, "="); if (split.isEmpty) continue; From 43062014f306c2bc97755485951790dfad18d709 Mon Sep 17 00:00:00 2001 From: "floitsch@google.com" Date: Thu, 24 Jan 2013 12:16:37 +0000 Subject: [PATCH 016/159] Rename Date to DateTime. BUG=http://dartbug.com/1424 Review URL: https://codereview.chromium.org//11770004 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@17549 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/src/authorization_code_grant.dart | 2 +- pkgs/oauth2/lib/src/credentials.dart | 8 ++++---- pkgs/oauth2/lib/src/handle_access_token_response.dart | 2 +- pkgs/oauth2/test/client_test.dart | 4 ++-- pkgs/oauth2/test/credentials_test.dart | 6 +++--- pkgs/oauth2/test/handle_access_token_response_test.dart | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/pkgs/oauth2/lib/src/authorization_code_grant.dart b/pkgs/oauth2/lib/src/authorization_code_grant.dart index 5e9cbd2f6..abd0d2e84 100644 --- a/pkgs/oauth2/lib/src/authorization_code_grant.dart +++ b/pkgs/oauth2/lib/src/authorization_code_grant.dart @@ -229,7 +229,7 @@ class AuthorizationCodeGrant { /// This works just like [handleAuthorizationCode], except it doesn't validate /// the state beforehand. Future _handleAuthorizationCode(String authorizationCode) { - var startTime = new Date.now(); + var startTime = new DateTime.now(); return _httpClient.post(this.tokenEndpoint, fields: { "grant_type": "authorization_code", "code": authorizationCode, diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index 12a0be8fd..d476e78f9 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -44,13 +44,13 @@ class Credentials { /// 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; + final DateTime 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; + bool get isExpired => expiration != null && new DateTime.now() > expiration; /// Whether it's possible to refresh these credentials. bool get canRefresh => refreshToken != null && tokenEndpoint != null; @@ -111,7 +111,7 @@ class Credentials { if (expiration != null) { validate(expiration is int, 'field "expiration" was not an int, was "$expiration"'); - expiration = new Date.fromMillisecondsSinceEpoch(expiration); + expiration = new DateTime.fromMillisecondsSinceEpoch(expiration); } return new Credentials( @@ -152,7 +152,7 @@ class Credentials { if (scopes == null) scopes = []; if (httpClient == null) httpClient = new http.Client(); - var startTime = new Date.now(); + var startTime = new DateTime.now(); return async.then((_) { if (refreshToken == null) { throw new StateError("Can't refresh credentials without a refresh " diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart index b80532d3f..d1fedea73 100644 --- a/pkgs/oauth2/lib/src/handle_access_token_response.dart +++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart @@ -24,7 +24,7 @@ const _EXPIRATION_GRACE = 10; Credentials handleAccessTokenResponse( http.Response response, Uri tokenEndpoint, - Date startTime, + DateTime startTime, List scopes) { if (response.statusCode != 200) _handleErrorResponse(response, tokenEndpoint); diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart index 6ad7e48b3..fd5e5b4ac 100644 --- a/pkgs/oauth2/test/client_test.dart +++ b/pkgs/oauth2/test/client_test.dart @@ -35,7 +35,7 @@ void main() { setUp(createHttpClient); test("that can't be refreshed throws an ExpirationException on send", () { - var expiration = new Date.now().subtract(new Duration(hours: 1)); + var expiration = new DateTime.now().subtract(new Duration(hours: 1)); var credentials = new oauth2.Credentials( 'access token', null, null, [], expiration); var client = new oauth2.Client('identifier', 'secret', credentials, @@ -47,7 +47,7 @@ void main() { test("that can be refreshed refreshes the credentials and sends the " "request", () { - var expiration = new Date.now().subtract(new Duration(hours: 1)); + var expiration = new DateTime.now().subtract(new Duration(hours: 1)); var credentials = new oauth2.Credentials( 'access token', 'refresh token', tokenEndpoint, [], expiration); var client = new oauth2.Client('identifier', 'secret', credentials, diff --git a/pkgs/oauth2/test/credentials_test.dart b/pkgs/oauth2/test/credentials_test.dart index 57e31a149..e3f24dab8 100644 --- a/pkgs/oauth2/test/credentials_test.dart +++ b/pkgs/oauth2/test/credentials_test.dart @@ -27,14 +27,14 @@ void main() { }); test('is not expired if the expiration is in the future', () { - var expiration = new Date.now().add(new Duration(hours: 1)); + var expiration = new DateTime.now().add(new Duration(hours: 1)); var credentials = new oauth2.Credentials( 'access token', null, null, null, expiration); expect(credentials.isExpired, isFalse); }); test('is expired if the expiration is in the past', () { - var expiration = new Date.now().subtract(new Duration(hours: 1)); + var expiration = new DateTime.now().subtract(new Duration(hours: 1)); var credentials = new oauth2.Credentials( 'access token', null, null, null, expiration); expect(credentials.isExpired, isTrue); @@ -124,7 +124,7 @@ void main() { new oauth2.Credentials.fromJson(JSON.stringify(map)); test("should load the same credentials from toJson", () { - var expiration = new Date.now().subtract(new Duration(hours: 1)); + var expiration = new DateTime.now().subtract(new Duration(hours: 1)); var credentials = new oauth2.Credentials( 'access token', 'refresh token', tokenEndpoint, ['scope1', 'scope2'], expiration); diff --git a/pkgs/oauth2/test/handle_access_token_response_test.dart b/pkgs/oauth2/test/handle_access_token_response_test.dart index 48465f204..6bfb7da06 100644 --- a/pkgs/oauth2/test/handle_access_token_response_test.dart +++ b/pkgs/oauth2/test/handle_access_token_response_test.dart @@ -16,7 +16,7 @@ import 'utils.dart'; final Uri tokenEndpoint = new Uri.fromString("https://example.com/token"); -final Date startTime = new Date.now(); +final DateTime startTime = new DateTime.now(); oauth2.Credentials handle(http.Response response) => handleAccessTokenResponse(response, tokenEndpoint, startTime, ["scope"]); From d6aeb0735a98383f9b967c7bfe8936f8ec73a86c Mon Sep 17 00:00:00 2001 From: "floitsch@google.com" Date: Thu, 24 Jan 2013 13:26:18 +0000 Subject: [PATCH 017/159] Rename new Uri.fromString to Uri.parse. Review URL: https://codereview.chromium.org//12052038 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@17562 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/oauth2.dart | 6 +++--- pkgs/oauth2/lib/src/authorization_code_grant.dart | 2 +- pkgs/oauth2/lib/src/credentials.dart | 2 +- pkgs/oauth2/lib/src/handle_access_token_response.dart | 2 +- pkgs/oauth2/test/authorization_code_grant_test.dart | 10 +++++----- pkgs/oauth2/test/client_test.dart | 4 ++-- pkgs/oauth2/test/credentials_test.dart | 2 +- .../oauth2/test/handle_access_token_response_test.dart | 2 +- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/pkgs/oauth2/lib/oauth2.dart b/pkgs/oauth2/lib/oauth2.dart index cc0bb990d..4dfaadd21 100644 --- a/pkgs/oauth2/lib/oauth2.dart +++ b/pkgs/oauth2/lib/oauth2.dart @@ -28,9 +28,9 @@ /// // server. They're usually included in the server's documentation of its /// // OAuth2 API. /// final authorizationEndpoint = -/// new Uri.fromString("http://example.com/oauth2/authorization"); +/// Uri.parse("http://example.com/oauth2/authorization"); /// final tokenEndpoint = -/// new Uri.fromString("http://example.com/oauth2/token"); +/// Uri.parse("http://example.com/oauth2/token"); /// /// // The authorization server will issue each client a separate client /// // identifier and secret, which allows the server to tell which client @@ -48,7 +48,7 @@ /// // will redirect the resource owner here once they've authorized the /// // client. The redirection will include the authorization code in the /// // query parameters. -/// final redirectUrl = new Uri.fromString( +/// final redirectUrl = Uri.parse( /// "http://my-site.com/oauth2-redirect"); /// /// var credentialsFile = new File("~/.myapp/credentials.json"); diff --git a/pkgs/oauth2/lib/src/authorization_code_grant.dart b/pkgs/oauth2/lib/src/authorization_code_grant.dart index abd0d2e84..b361f2158 100644 --- a/pkgs/oauth2/lib/src/authorization_code_grant.dart +++ b/pkgs/oauth2/lib/src/authorization_code_grant.dart @@ -185,7 +185,7 @@ class AuthorizationCodeGrant { if (parameters.containsKey('error')) { var description = parameters['error_description']; var uriString = parameters['error_uri']; - var uri = uriString == null ? null : new Uri.fromString(uriString); + var uri = uriString == null ? null : Uri.parse(uriString); throw new AuthorizationException(parameters['error'], description, uri); } else if (!parameters.containsKey('code')) { throw new FormatException('Invalid OAuth response for ' diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index d476e78f9..17cd41049 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -105,7 +105,7 @@ class Credentials { var tokenEndpoint = parsed['tokenEndpoint']; if (tokenEndpoint != null) { - tokenEndpoint = new Uri.fromString(tokenEndpoint); + tokenEndpoint = Uri.parse(tokenEndpoint); } var expiration = parsed['expiration']; if (expiration != null) { diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart index d1fedea73..644324734 100644 --- a/pkgs/oauth2/lib/src/handle_access_token_response.dart +++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart @@ -130,7 +130,7 @@ void _handleErrorResponse(http.Response response, Uri tokenEndpoint) { var description = parameters['error_description']; var uriString = parameters['error_uri']; - var uri = uriString == null ? null : new Uri.fromString(uriString); + var uri = uriString == null ? null : Uri.parse(uriString); throw new AuthorizationException(parameters['error'], description, uri); } diff --git a/pkgs/oauth2/test/authorization_code_grant_test.dart b/pkgs/oauth2/test/authorization_code_grant_test.dart index 7614bcd5e..0bc3433a8 100644 --- a/pkgs/oauth2/test/authorization_code_grant_test.dart +++ b/pkgs/oauth2/test/authorization_code_grant_test.dart @@ -15,7 +15,7 @@ import '../../http/lib/testing.dart'; import '../lib/oauth2.dart' as oauth2; import 'utils.dart'; -final redirectUrl = new Uri.fromString('http://example.com/redirect'); +final redirectUrl = Uri.parse('http://example.com/redirect'); ExpectClient client; @@ -26,8 +26,8 @@ void createGrant() { grant = new oauth2.AuthorizationCodeGrant( 'identifier', 'secret', - new Uri.fromString('https://example.com/authorization'), - new Uri.fromString('https://example.com/token'), + Uri.parse('https://example.com/authorization'), + Uri.parse('https://example.com/token'), httpClient: client); } @@ -75,8 +75,8 @@ void main() { grant = new oauth2.AuthorizationCodeGrant( 'identifier', 'secret', - new Uri.fromString('https://example.com/authorization?query=value'), - new Uri.fromString('https://example.com/token'), + Uri.parse('https://example.com/authorization?query=value'), + Uri.parse('https://example.com/token'), httpClient: client); var authorizationUrl = grant.getAuthorizationUrl(redirectUrl); diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart index fd5e5b4ac..b875de9e5 100644 --- a/pkgs/oauth2/test/client_test.dart +++ b/pkgs/oauth2/test/client_test.dart @@ -14,9 +14,9 @@ import '../../http/lib/http.dart' as http; import '../lib/oauth2.dart' as oauth2; import 'utils.dart'; -final Uri requestUri = new Uri.fromString("http://example.com/resource"); +final Uri requestUri = Uri.parse("http://example.com/resource"); -final Uri tokenEndpoint = new Uri.fromString('http://example.com/token'); +final Uri tokenEndpoint = Uri.parse('http://example.com/token'); ExpectClient httpClient; diff --git a/pkgs/oauth2/test/credentials_test.dart b/pkgs/oauth2/test/credentials_test.dart index e3f24dab8..e6ad2a987 100644 --- a/pkgs/oauth2/test/credentials_test.dart +++ b/pkgs/oauth2/test/credentials_test.dart @@ -14,7 +14,7 @@ import '../../http/lib/http.dart' as http; import '../lib/oauth2.dart' as oauth2; import 'utils.dart'; -final Uri tokenEndpoint = new Uri.fromString('http://example.com/token'); +final Uri tokenEndpoint = Uri.parse('http://example.com/token'); ExpectClient httpClient; diff --git a/pkgs/oauth2/test/handle_access_token_response_test.dart b/pkgs/oauth2/test/handle_access_token_response_test.dart index 6bfb7da06..3e95ddc52 100644 --- a/pkgs/oauth2/test/handle_access_token_response_test.dart +++ b/pkgs/oauth2/test/handle_access_token_response_test.dart @@ -14,7 +14,7 @@ import '../lib/oauth2.dart' as oauth2; import '../lib/src/handle_access_token_response.dart'; import 'utils.dart'; -final Uri tokenEndpoint = new Uri.fromString("https://example.com/token"); +final Uri tokenEndpoint = Uri.parse("https://example.com/token"); final DateTime startTime = new DateTime.now(); From b518b2b71e1b2d895a4512b20c48e4d1f0739f62 Mon Sep 17 00:00:00 2001 From: "sigmund@google.com" Date: Tue, 29 Jan 2013 21:47:05 +0000 Subject: [PATCH 018/159] Fix build for throwsStateError: remove duplicate definitions in other tests. Review URL: https://codereview.chromium.org//12088056 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@17812 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/test/utils.dart | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/pkgs/oauth2/test/utils.dart b/pkgs/oauth2/test/utils.dart index 2b6c1a7aa..093565897 100644 --- a/pkgs/oauth2/test/utils.dart +++ b/pkgs/oauth2/test/utils.dart @@ -45,19 +45,6 @@ class ExpectClient extends MockClient { } } -// TODO(nweiz): remove this once it's built in to unittest -/// A matcher for StateErrors. -const isStateError = const _StateError(); - -/// A matcher for functions that throw StateError. -const Matcher throwsStateError = - const Throws(isStateError); - -class _StateError extends TypeMatcher { - const _StateError() : super("StateError"); - bool matches(item, MatchState matchState) => item is StateError; -} - /// A matcher for AuthorizationExceptions. const isAuthorizationException = const _AuthorizationException(); From 4730fe021ef8084486b95c02bf32e7a47e5c49ee Mon Sep 17 00:00:00 2001 From: "lrn@google.com" Date: Thu, 31 Jan 2013 12:37:13 +0000 Subject: [PATCH 019/159] Rename mappedBy to map. Retain a deprecated mappedBy for now. Change return type of mappedBy, skip and take on List to Iterable. BUG= http://dartbug.com/8063 BUG= http://dartbug.com/8064 BUG= http://dartbug.com/6739 BUG= http://dartbug.com/7982 Review URL: https://codereview.chromium.org//12086062 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@17899 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/src/utils.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/oauth2/lib/src/utils.dart b/pkgs/oauth2/lib/src/utils.dart index 85a9f6796..bcff58999 100644 --- a/pkgs/oauth2/lib/src/utils.dart +++ b/pkgs/oauth2/lib/src/utils.dart @@ -39,7 +39,7 @@ String mapToQuery(Map map) { value = (value == null || value.isEmpty) ? null : encodeUriComponent(value); pairs.add([key, value]); }); - return Strings.join(pairs.mappedBy((pair) { + return Strings.join(pairs.map((pair) { if (pair[1] == null) return pair[0]; return "${pair[0]}=${pair[1]}"; }), "&"); From 3ea8cb9fc9f22467c79e75416136433580c43102 Mon Sep 17 00:00:00 2001 From: "floitsch@google.com" Date: Thu, 31 Jan 2013 14:02:27 +0000 Subject: [PATCH 020/159] Revert "Rename mappedBy to map." This reverts commit 17899. Review URL: https://codereview.chromium.org//12087103 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@17907 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/src/utils.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/oauth2/lib/src/utils.dart b/pkgs/oauth2/lib/src/utils.dart index bcff58999..85a9f6796 100644 --- a/pkgs/oauth2/lib/src/utils.dart +++ b/pkgs/oauth2/lib/src/utils.dart @@ -39,7 +39,7 @@ String mapToQuery(Map map) { value = (value == null || value.isEmpty) ? null : encodeUriComponent(value); pairs.add([key, value]); }); - return Strings.join(pairs.map((pair) { + return Strings.join(pairs.mappedBy((pair) { if (pair[1] == null) return pair[0]; return "${pair[0]}=${pair[1]}"; }), "&"); From 835c5dd207d03ecd004d9130cc0af3fb6c74aff5 Mon Sep 17 00:00:00 2001 From: "lrn@google.com" Date: Thu, 31 Jan 2013 15:12:56 +0000 Subject: [PATCH 021/159] Reapply "Rename mappedBy to map." This reverts commit r17907. TBR. Review URL: https://codereview.chromium.org//12090093 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@17918 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/src/utils.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/oauth2/lib/src/utils.dart b/pkgs/oauth2/lib/src/utils.dart index 85a9f6796..bcff58999 100644 --- a/pkgs/oauth2/lib/src/utils.dart +++ b/pkgs/oauth2/lib/src/utils.dart @@ -39,7 +39,7 @@ String mapToQuery(Map map) { value = (value == null || value.isEmpty) ? null : encodeUriComponent(value); pairs.add([key, value]); }); - return Strings.join(pairs.mappedBy((pair) { + return Strings.join(pairs.map((pair) { if (pair[1] == null) return pair[0]; return "${pair[0]}=${pair[1]}"; }), "&"); From 7b6ab44a01bc2ff8cfa48fbc305797ccfb0605b4 Mon Sep 17 00:00:00 2001 From: "floitsch@google.com" Date: Wed, 13 Feb 2013 19:31:22 +0000 Subject: [PATCH 022/159] Change Future.delayed to take a Duration. Review URL: https://codereview.chromium.org//12224081 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@18471 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/src/utils.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkgs/oauth2/lib/src/utils.dart b/pkgs/oauth2/lib/src/utils.dart index bcff58999..83fe2b701 100644 --- a/pkgs/oauth2/lib/src/utils.dart +++ b/pkgs/oauth2/lib/src/utils.dart @@ -113,4 +113,5 @@ class AuthenticateHeader { } /// Returns a [Future] that asynchronously completes to `null`. -Future get async => new Future.delayed(0, () => null); +Future get async => new Future.delayed(const Duration(milliseconds: 0), + () => null); From 7728aeb4524c9947dba02f0e9c447801a2de91f6 Mon Sep 17 00:00:00 2001 From: "floitsch@google.com" Date: Tue, 19 Feb 2013 13:57:03 +0000 Subject: [PATCH 023/159] Remove deprecated Strings class. Review URL: https://codereview.chromium.org//12295014 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@18686 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/src/authorization_code_grant.dart | 2 +- pkgs/oauth2/lib/src/credentials.dart | 2 +- pkgs/oauth2/lib/src/utils.dart | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkgs/oauth2/lib/src/authorization_code_grant.dart b/pkgs/oauth2/lib/src/authorization_code_grant.dart index b361f2158..b1995795d 100644 --- a/pkgs/oauth2/lib/src/authorization_code_grant.dart +++ b/pkgs/oauth2/lib/src/authorization_code_grant.dart @@ -139,7 +139,7 @@ class AuthorizationCodeGrant { }; if (state != null) parameters['state'] = state; - if (!scopes.isEmpty) parameters['scope'] = Strings.join(scopes, ' '); + if (!scopes.isEmpty) parameters['scope'] = scopes.join(' '); return addQueryParameters(this.authorizationEndpoint, parameters); } diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index 17cd41049..26112c63a 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -171,7 +171,7 @@ class Credentials { "client_id": identifier, "client_secret": secret }; - if (!scopes.isEmpty) fields["scope"] = Strings.join(scopes, ' '); + if (!scopes.isEmpty) fields["scope"] = scopes.join(' '); return httpClient.post(tokenEndpoint, fields: fields); }).then((response) { diff --git a/pkgs/oauth2/lib/src/utils.dart b/pkgs/oauth2/lib/src/utils.dart index 83fe2b701..f69ac8b19 100644 --- a/pkgs/oauth2/lib/src/utils.dart +++ b/pkgs/oauth2/lib/src/utils.dart @@ -39,10 +39,10 @@ String mapToQuery(Map map) { value = (value == null || value.isEmpty) ? null : encodeUriComponent(value); pairs.add([key, value]); }); - return Strings.join(pairs.map((pair) { + return pairs.map((pair) { if (pair[1] == null) return pair[0]; return "${pair[0]}=${pair[1]}"; - }), "&"); + }).join("&"); } /// Add all key/value pairs from [source] to [destination], overwriting any From a0c707180b5d2257a46f6b5038e6f32a426c8061 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Thu, 28 Feb 2013 23:08:27 +0000 Subject: [PATCH 024/159] Make oauth2 warning-clean. Review URL: https://codereview.chromium.org//12379021 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@19262 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/src/credentials.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index 26112c63a..acc569025 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -50,7 +50,8 @@ class Credentials { /// 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 DateTime.now() > expiration; + bool get isExpired => expiration != null && + new DateTime.now().isAfter(expiration); /// Whether it's possible to refresh these credentials. bool get canRefresh => refreshToken != null && tokenEndpoint != null; From 79112f40450235a56f15d31ff8d3bd123d9e980a Mon Sep 17 00:00:00 2001 From: "dgrove@google.com" Date: Wed, 6 Mar 2013 17:17:05 +0000 Subject: [PATCH 025/159] Import packages outside oauth2 via ../../.../pkg/ . This allows packages to have their imports rewritten and uploaded to pub. Review URL: https://codereview.chromium.org//12543004 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@19566 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/src/authorization_code_grant.dart | 2 +- pkgs/oauth2/lib/src/client.dart | 2 +- pkgs/oauth2/lib/src/credentials.dart | 2 +- pkgs/oauth2/lib/src/handle_access_token_response.dart | 2 +- pkgs/oauth2/test/authorization_code_grant_test.dart | 6 +++--- pkgs/oauth2/test/client_test.dart | 4 ++-- pkgs/oauth2/test/credentials_test.dart | 4 ++-- pkgs/oauth2/test/handle_access_token_response_test.dart | 4 ++-- pkgs/oauth2/test/utils.dart | 6 +++--- pkgs/oauth2/test/utils_test.dart | 2 +- 10 files changed, 17 insertions(+), 17 deletions(-) diff --git a/pkgs/oauth2/lib/src/authorization_code_grant.dart b/pkgs/oauth2/lib/src/authorization_code_grant.dart index b1995795d..342c726bb 100644 --- a/pkgs/oauth2/lib/src/authorization_code_grant.dart +++ b/pkgs/oauth2/lib/src/authorization_code_grant.dart @@ -8,7 +8,7 @@ import 'dart:async'; import 'dart:uri'; // TODO(nweiz): This should be a "package:" import. See issue 6745. -import '../../../http/lib/http.dart' as http; +import '../../../../pkg/http/lib/http.dart' as http; import 'client.dart'; import 'authorization_exception.dart'; diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart index fd315bb3f..6d4d04227 100644 --- a/pkgs/oauth2/lib/src/client.dart +++ b/pkgs/oauth2/lib/src/client.dart @@ -7,7 +7,7 @@ library client; import 'dart:async'; import 'dart:uri'; -import '../../../http/lib/http.dart' as http; +import '../../../../pkg/http/lib/http.dart' as http; import 'authorization_exception.dart'; import 'credentials.dart'; diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index acc569025..bd9ab0468 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -8,7 +8,7 @@ import 'dart:async'; import 'dart:json' as JSON; import 'dart:uri'; -import '../../../http/lib/http.dart' as http; +import '../../../../pkg/http/lib/http.dart' as http; import 'handle_access_token_response.dart'; import 'utils.dart'; diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart index 644324734..9f9e86777 100644 --- a/pkgs/oauth2/lib/src/handle_access_token_response.dart +++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart @@ -8,7 +8,7 @@ import 'dart:io'; import 'dart:json' as JSON; import 'dart:uri'; -import '../../../http/lib/http.dart' as http; +import '../../../../pkg/http/lib/http.dart' as http; import 'credentials.dart'; import 'authorization_exception.dart'; diff --git a/pkgs/oauth2/test/authorization_code_grant_test.dart b/pkgs/oauth2/test/authorization_code_grant_test.dart index 0bc3433a8..ad784a548 100644 --- a/pkgs/oauth2/test/authorization_code_grant_test.dart +++ b/pkgs/oauth2/test/authorization_code_grant_test.dart @@ -9,9 +9,9 @@ import 'dart:io'; import 'dart:json' as JSON; import 'dart:uri'; -import '../../unittest/lib/unittest.dart'; -import '../../http/lib/http.dart' as http; -import '../../http/lib/testing.dart'; +import '../../../pkg/unittest/lib/unittest.dart'; +import '../../../pkg/http/lib/http.dart' as http; +import '../../../pkg/http/lib/testing.dart'; import '../lib/oauth2.dart' as oauth2; import 'utils.dart'; diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart index b875de9e5..a815a22e5 100644 --- a/pkgs/oauth2/test/client_test.dart +++ b/pkgs/oauth2/test/client_test.dart @@ -9,8 +9,8 @@ import 'dart:io'; import 'dart:json' as JSON; import 'dart:uri'; -import '../../unittest/lib/unittest.dart'; -import '../../http/lib/http.dart' as http; +import '../../../pkg/unittest/lib/unittest.dart'; +import '../../../pkg/http/lib/http.dart' as http; import '../lib/oauth2.dart' as oauth2; import 'utils.dart'; diff --git a/pkgs/oauth2/test/credentials_test.dart b/pkgs/oauth2/test/credentials_test.dart index e6ad2a987..e21b301c8 100644 --- a/pkgs/oauth2/test/credentials_test.dart +++ b/pkgs/oauth2/test/credentials_test.dart @@ -9,8 +9,8 @@ import 'dart:io'; import 'dart:json' as JSON; import 'dart:uri'; -import '../../unittest/lib/unittest.dart'; -import '../../http/lib/http.dart' as http; +import '../../../pkg/unittest/lib/unittest.dart'; +import '../../../pkg/http/lib/http.dart' as http; import '../lib/oauth2.dart' as oauth2; import 'utils.dart'; diff --git a/pkgs/oauth2/test/handle_access_token_response_test.dart b/pkgs/oauth2/test/handle_access_token_response_test.dart index 3e95ddc52..e39ee5642 100644 --- a/pkgs/oauth2/test/handle_access_token_response_test.dart +++ b/pkgs/oauth2/test/handle_access_token_response_test.dart @@ -8,8 +8,8 @@ import 'dart:io'; import 'dart:json' as JSON; import 'dart:uri'; -import '../../unittest/lib/unittest.dart'; -import '../../http/lib/http.dart' as http; +import '../../../pkg/unittest/lib/unittest.dart'; +import '../../../pkg/http/lib/http.dart' as http; import '../lib/oauth2.dart' as oauth2; import '../lib/src/handle_access_token_response.dart'; import 'utils.dart'; diff --git a/pkgs/oauth2/test/utils.dart b/pkgs/oauth2/test/utils.dart index 093565897..04b194eff 100644 --- a/pkgs/oauth2/test/utils.dart +++ b/pkgs/oauth2/test/utils.dart @@ -7,9 +7,9 @@ library utils; import 'dart:async'; import 'dart:collection' show Queue; -import '../../unittest/lib/unittest.dart'; -import '../../http/lib/http.dart' as http; -import '../../http/lib/testing.dart'; +import '../../../pkg/unittest/lib/unittest.dart'; +import '../../../pkg/http/lib/http.dart' as http; +import '../../../pkg/http/lib/testing.dart'; import '../lib/oauth2.dart' as oauth2; class ExpectClient extends MockClient { diff --git a/pkgs/oauth2/test/utils_test.dart b/pkgs/oauth2/test/utils_test.dart index d76e2b001..2fa4b734d 100644 --- a/pkgs/oauth2/test/utils_test.dart +++ b/pkgs/oauth2/test/utils_test.dart @@ -4,7 +4,7 @@ library utils_test; -import '../../unittest/lib/unittest.dart'; +import '../../../pkg/unittest/lib/unittest.dart'; import '../lib/src/utils.dart'; From 1a1012e87dd54f6f6f8715d99c583a64f262c6f5 Mon Sep 17 00:00:00 2001 From: "kevmoo@j832.com" Date: Wed, 20 Mar 2013 19:41:10 +0000 Subject: [PATCH 026/159] Moved pkg pubspecs to use dev_dependencies where appropriate BUG=https://code.google.com/p/dart/issues/detail?id=9309 Review URL: https://codereview.chromium.org//12962003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@20285 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/pubspec.yaml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index ae3d11df8..f531f467e 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -6,7 +6,6 @@ description: > behalf of a user, and making authorized HTTP requests with the user's OAuth2 credentials. dependencies: - unittest: - sdk: unittest - http: - sdk: http + http: any +dev_dependencies: + unittest: any From 62c5e73e2fdeb0603770253806e9726653d7ddbd Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Thu, 21 Mar 2013 18:50:59 +0000 Subject: [PATCH 027/159] Fix an outdated method call in example code for oauth2. BUG=7675 Review URL: https://codereview.chromium.org//12787015 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@20341 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/oauth2.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/oauth2/lib/oauth2.dart b/pkgs/oauth2/lib/oauth2.dart index 4dfaadd21..7fdb20555 100644 --- a/pkgs/oauth2/lib/oauth2.dart +++ b/pkgs/oauth2/lib/oauth2.dart @@ -56,7 +56,7 @@ /// // If the OAuth2 credentials have already been saved from a previous /// // run, we just want to reload them. /// if (exists) { -/// return credentialsFile.readAsText().then((json) { +/// return credentialsFile.readAsString().then((json) { /// var credentials = new oauth2.Credentials.fromJson(json); /// return new oauth2.Client(identifier, secret, credentials); /// }); From b245c2367ffa3099efdd4dc537c4074723d30996 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Thu, 28 Mar 2013 20:27:28 +0000 Subject: [PATCH 028/159] Switch pkg packages, pub, and dartdoc to use package: imports. This also changes the SDK layout by replacing the "pkg" directory, which contained the full source of all the packages needed by pub and dartdoc, with a "packages" directory that contains only their lib directories. This directory is used as the package root for pub and dartdoc when run from the SDK. BUG=6745 Review URL: https://codereview.chromium.org//12782016 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@20640 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/src/authorization_code_grant.dart | 3 +-- pkgs/oauth2/lib/src/client.dart | 2 +- pkgs/oauth2/lib/src/credentials.dart | 3 ++- pkgs/oauth2/lib/src/handle_access_token_response.dart | 2 +- pkgs/oauth2/test/authorization_code_grant_test.dart | 9 +++++---- pkgs/oauth2/test/client_test.dart | 7 ++++--- pkgs/oauth2/test/credentials_test.dart | 7 ++++--- pkgs/oauth2/test/handle_access_token_response_test.dart | 9 +++++---- pkgs/oauth2/test/utils.dart | 8 ++++---- pkgs/oauth2/test/utils_test.dart | 5 ++--- 10 files changed, 29 insertions(+), 26 deletions(-) diff --git a/pkgs/oauth2/lib/src/authorization_code_grant.dart b/pkgs/oauth2/lib/src/authorization_code_grant.dart index 342c726bb..900dfc536 100644 --- a/pkgs/oauth2/lib/src/authorization_code_grant.dart +++ b/pkgs/oauth2/lib/src/authorization_code_grant.dart @@ -7,8 +7,7 @@ library authorization_code_grant; import 'dart:async'; import 'dart:uri'; -// TODO(nweiz): This should be a "package:" import. See issue 6745. -import '../../../../pkg/http/lib/http.dart' as http; +import 'package:http/http.dart' as http; import 'client.dart'; import 'authorization_exception.dart'; diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart index 6d4d04227..afb59e4c7 100644 --- a/pkgs/oauth2/lib/src/client.dart +++ b/pkgs/oauth2/lib/src/client.dart @@ -7,7 +7,7 @@ library client; import 'dart:async'; import 'dart:uri'; -import '../../../../pkg/http/lib/http.dart' as http; +import 'package:http/http.dart' as http; import 'authorization_exception.dart'; import 'credentials.dart'; diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index bd9ab0468..420dc4934 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -8,7 +8,8 @@ import 'dart:async'; import 'dart:json' as JSON; import 'dart:uri'; -import '../../../../pkg/http/lib/http.dart' as http; +import 'package:http/http.dart' as http; + import 'handle_access_token_response.dart'; import 'utils.dart'; diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart index 9f9e86777..65e706e6a 100644 --- a/pkgs/oauth2/lib/src/handle_access_token_response.dart +++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart @@ -8,7 +8,7 @@ import 'dart:io'; import 'dart:json' as JSON; import 'dart:uri'; -import '../../../../pkg/http/lib/http.dart' as http; +import 'package:http/http.dart' as http; import 'credentials.dart'; import 'authorization_exception.dart'; diff --git a/pkgs/oauth2/test/authorization_code_grant_test.dart b/pkgs/oauth2/test/authorization_code_grant_test.dart index ad784a548..85c055b02 100644 --- a/pkgs/oauth2/test/authorization_code_grant_test.dart +++ b/pkgs/oauth2/test/authorization_code_grant_test.dart @@ -9,10 +9,11 @@ import 'dart:io'; import 'dart:json' as JSON; import 'dart:uri'; -import '../../../pkg/unittest/lib/unittest.dart'; -import '../../../pkg/http/lib/http.dart' as http; -import '../../../pkg/http/lib/testing.dart'; -import '../lib/oauth2.dart' as oauth2; +import 'package:unittest/unittest.dart'; +import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; +import 'package:oauth2/oauth2.dart' as oauth2; + import 'utils.dart'; final redirectUrl = Uri.parse('http://example.com/redirect'); diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart index a815a22e5..7049122ba 100644 --- a/pkgs/oauth2/test/client_test.dart +++ b/pkgs/oauth2/test/client_test.dart @@ -9,9 +9,10 @@ import 'dart:io'; import 'dart:json' as JSON; import 'dart:uri'; -import '../../../pkg/unittest/lib/unittest.dart'; -import '../../../pkg/http/lib/http.dart' as http; -import '../lib/oauth2.dart' as oauth2; +import 'package:http/http.dart' as http; +import 'package:oauth2/oauth2.dart' as oauth2; +import 'package:unittest/unittest.dart'; + import 'utils.dart'; final Uri requestUri = Uri.parse("http://example.com/resource"); diff --git a/pkgs/oauth2/test/credentials_test.dart b/pkgs/oauth2/test/credentials_test.dart index e21b301c8..8e40a4a17 100644 --- a/pkgs/oauth2/test/credentials_test.dart +++ b/pkgs/oauth2/test/credentials_test.dart @@ -9,9 +9,10 @@ import 'dart:io'; import 'dart:json' as JSON; import 'dart:uri'; -import '../../../pkg/unittest/lib/unittest.dart'; -import '../../../pkg/http/lib/http.dart' as http; -import '../lib/oauth2.dart' as oauth2; +import 'package:http/http.dart' as http; +import 'package:oauth2/oauth2.dart' as oauth2; +import 'package:unittest/unittest.dart'; + import 'utils.dart'; final Uri tokenEndpoint = Uri.parse('http://example.com/token'); diff --git a/pkgs/oauth2/test/handle_access_token_response_test.dart b/pkgs/oauth2/test/handle_access_token_response_test.dart index e39ee5642..772f2631e 100644 --- a/pkgs/oauth2/test/handle_access_token_response_test.dart +++ b/pkgs/oauth2/test/handle_access_token_response_test.dart @@ -8,10 +8,11 @@ import 'dart:io'; import 'dart:json' as JSON; import 'dart:uri'; -import '../../../pkg/unittest/lib/unittest.dart'; -import '../../../pkg/http/lib/http.dart' as http; -import '../lib/oauth2.dart' as oauth2; -import '../lib/src/handle_access_token_response.dart'; +import 'package:http/http.dart' as http; +import 'package:oauth2/oauth2.dart' as oauth2; +import 'package:oauth2/src/handle_access_token_response.dart'; +import 'package:unittest/unittest.dart'; + import 'utils.dart'; final Uri tokenEndpoint = Uri.parse("https://example.com/token"); diff --git a/pkgs/oauth2/test/utils.dart b/pkgs/oauth2/test/utils.dart index 04b194eff..b874cc4e7 100644 --- a/pkgs/oauth2/test/utils.dart +++ b/pkgs/oauth2/test/utils.dart @@ -7,10 +7,10 @@ library utils; import 'dart:async'; import 'dart:collection' show Queue; -import '../../../pkg/unittest/lib/unittest.dart'; -import '../../../pkg/http/lib/http.dart' as http; -import '../../../pkg/http/lib/testing.dart'; -import '../lib/oauth2.dart' as oauth2; +import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; +import 'package:oauth2/oauth2.dart' as oauth2; +import 'package:unittest/unittest.dart'; class ExpectClient extends MockClient { final Queue _handlers; diff --git a/pkgs/oauth2/test/utils_test.dart b/pkgs/oauth2/test/utils_test.dart index 2fa4b734d..145e99bb1 100644 --- a/pkgs/oauth2/test/utils_test.dart +++ b/pkgs/oauth2/test/utils_test.dart @@ -4,9 +4,8 @@ library utils_test; -import '../../../pkg/unittest/lib/unittest.dart'; -import '../lib/src/utils.dart'; - +import 'package:oauth2/src/utils.dart'; +import 'package:unittest/unittest.dart'; void main() { group('AuthenticateHeader', () { From 01e987531ddc3981247750fa2e1c4b93db9c791a Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Mon, 8 Apr 2013 19:39:30 +0000 Subject: [PATCH 029/159] Normalize packages' library names. This makes the library names play nicer with dartdoc. Review URL: https://codereview.chromium.org//13674024 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@21106 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/src/client.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart index afb59e4c7..0d422afd5 100644 --- a/pkgs/oauth2/lib/src/client.dart +++ b/pkgs/oauth2/lib/src/client.dart @@ -2,7 +2,7 @@ // 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 client; +library oauth2_client; import 'dart:async'; import 'dart:uri'; From abfb4b0b06f9fe8949d1f3be2e272db54d6374dd Mon Sep 17 00:00:00 2001 From: "floitsch@google.com" Date: Mon, 15 Apr 2013 18:58:32 +0000 Subject: [PATCH 030/159] Remove AsyncError with Expando. Review URL: https://codereview.chromium.org//14251006 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@21498 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/test/authorization_code_grant_test.dart | 4 ++-- pkgs/oauth2/test/client_test.dart | 4 ++-- pkgs/oauth2/test/credentials_test.dart | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pkgs/oauth2/test/authorization_code_grant_test.dart b/pkgs/oauth2/test/authorization_code_grant_test.dart index 85c055b02..dead690f7 100644 --- a/pkgs/oauth2/test/authorization_code_grant_test.dart +++ b/pkgs/oauth2/test/authorization_code_grant_test.dart @@ -33,8 +33,8 @@ void createGrant() { } void expectFutureThrows(future, predicate) { - future.catchError(expectAsync1((AsyncError e) { - expect(predicate(e.error), isTrue); + future.catchError(expectAsync1((error) { + expect(predicate(error), isTrue); })); } diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart index 7049122ba..434ea95fc 100644 --- a/pkgs/oauth2/test/client_test.dart +++ b/pkgs/oauth2/test/client_test.dart @@ -26,8 +26,8 @@ void createHttpClient() { } void expectFutureThrows(future, predicate) { - future.catchError(expectAsync1((AsyncError e) { - expect(predicate(e.error), isTrue); + future.catchError(expectAsync1((error) { + expect(predicate(error), isTrue); })); } diff --git a/pkgs/oauth2/test/credentials_test.dart b/pkgs/oauth2/test/credentials_test.dart index 8e40a4a17..1ee5b7597 100644 --- a/pkgs/oauth2/test/credentials_test.dart +++ b/pkgs/oauth2/test/credentials_test.dart @@ -46,8 +46,8 @@ void main() { 'access token', null, tokenEndpoint); expect(credentials.canRefresh, false); credentials.refresh('identifier', 'secret', httpClient: httpClient) - .catchError(expectAsync1((e) { - expect(e.error is StateError, isTrue); + .catchError(expectAsync1((error) { + expect(error is StateError, isTrue); })); }); @@ -55,8 +55,8 @@ void main() { var credentials = new oauth2.Credentials('access token', 'refresh token'); expect(credentials.canRefresh, false); credentials.refresh('identifier', 'secret', httpClient: httpClient) - .catchError(expectAsync1((e) { - expect(e.error is StateError, isTrue); + .catchError(expectAsync1((error) { + expect(error is StateError, isTrue); })); }); From 70969367518f1632a20174e8406755f03b99543d Mon Sep 17 00:00:00 2001 From: "floitsch@google.com" Date: Mon, 15 Apr 2013 21:24:27 +0000 Subject: [PATCH 031/159] Refactor Future constructors. BUG= Review URL: https://codereview.chromium.org//14070010 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@21517 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/src/client.dart | 2 +- .../test/authorization_code_grant_test.dart | 4 ++-- pkgs/oauth2/test/client_test.dart | 18 +++++++++--------- pkgs/oauth2/test/credentials_test.dart | 4 ++-- pkgs/oauth2/test/utils.dart | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart index 0d422afd5..ec8dda5f2 100644 --- a/pkgs/oauth2/lib/src/client.dart +++ b/pkgs/oauth2/lib/src/client.dart @@ -83,7 +83,7 @@ class Client extends http.BaseClient { /// the request if necessary. Future send(http.BaseRequest request) { return async.then((_) { - if (!credentials.isExpired) return new Future.immediate(null); + if (!credentials.isExpired) return new Future.value(); if (!credentials.canRefresh) throw new ExpirationException(credentials); return refreshCredentials(); }).then((_) { diff --git a/pkgs/oauth2/test/authorization_code_grant_test.dart b/pkgs/oauth2/test/authorization_code_grant_test.dart index dead690f7..22be3cfa7 100644 --- a/pkgs/oauth2/test/authorization_code_grant_test.dart +++ b/pkgs/oauth2/test/authorization_code_grant_test.dart @@ -154,7 +154,7 @@ void main() { 'client_secret': 'secret' })); - return new Future.immediate(new http.Response(JSON.stringify({ + return new Future.value(new http.Response(JSON.stringify({ 'access_token': 'access token', 'token_type': 'bearer', }), 200, headers: {'content-type': 'application/json'})); @@ -198,7 +198,7 @@ void main() { 'client_secret': 'secret' })); - return new Future.immediate(new http.Response(JSON.stringify({ + return new Future.value(new http.Response(JSON.stringify({ 'access_token': 'access token', 'token_type': 'bearer', }), 200, headers: {'content-type': 'application/json'})); diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart index 434ea95fc..bac054854 100644 --- a/pkgs/oauth2/test/client_test.dart +++ b/pkgs/oauth2/test/client_test.dart @@ -57,7 +57,7 @@ void main() { httpClient.expectRequest((request) { expect(request.method, equals('POST')); expect(request.url.toString(), equals(tokenEndpoint.toString())); - return new Future.immediate(new http.Response(JSON.stringify({ + return new Future.value(new http.Response(JSON.stringify({ 'access_token': 'new access token', 'token_type': 'bearer' }), 200, headers: {'content-type': 'application/json'})); @@ -69,7 +69,7 @@ void main() { expect(request.headers['authorization'], equals('Bearer new access token')); - return new Future.immediate(new http.Response('good job', 200)); + return new Future.value(new http.Response('good job', 200)); }); expect(client.read(requestUri).then((_) { @@ -92,7 +92,7 @@ void main() { expect(request.headers['authorization'], equals('Bearer access token')); - return new Future.immediate(new http.Response('good job', 200)); + return new Future.value(new http.Response('good job', 200)); }); expect(client.read(requestUri), completion(equals('good job'))); @@ -107,7 +107,7 @@ void main() { httpClient.expectRequest((request) { expect(request.method, equals('POST')); expect(request.url.toString(), equals(tokenEndpoint.toString())); - return new Future.immediate(new http.Response(JSON.stringify({ + return new Future.value(new http.Response(JSON.stringify({ 'access_token': 'new access token', 'token_type': 'bearer' }), 200, headers: {'content-type': 'application/json'})); @@ -144,7 +144,7 @@ void main() { var authenticate = 'Bearer error="invalid_token", error_description=' '"Something is terribly wrong."'; - return new Future.immediate(new http.Response('bad job', 401, + return new Future.value(new http.Response('bad job', 401, headers: {'www-authenticate': authenticate})); }); @@ -163,7 +163,7 @@ void main() { expect(request.headers['authorization'], equals('Bearer access token')); - return new Future.immediate(new http.Response('bad job', 401)); + return new Future.value(new http.Response('bad job', 401)); }); expect( @@ -184,7 +184,7 @@ void main() { var authenticate = 'Bearer error="invalid_token", error_description=' '"Something is terribly wrong.", '; - return new Future.immediate(new http.Response('bad job', 401, + return new Future.value(new http.Response('bad job', 401, headers: {'www-authenticate': authenticate})); }); @@ -204,7 +204,7 @@ void main() { expect(request.headers['authorization'], equals('Bearer access token')); - return new Future.immediate(new http.Response('bad job', 401, + return new Future.value(new http.Response('bad job', 401, headers: {'www-authenticate': 'Digest'})); }); @@ -224,7 +224,7 @@ void main() { expect(request.headers['authorization'], equals('Bearer access token')); - return new Future.immediate(new http.Response('bad job', 401, + return new Future.value(new http.Response('bad job', 401, headers: {'www-authenticate': 'Bearer'})); }); diff --git a/pkgs/oauth2/test/credentials_test.dart b/pkgs/oauth2/test/credentials_test.dart index 1ee5b7597..652d3940b 100644 --- a/pkgs/oauth2/test/credentials_test.dart +++ b/pkgs/oauth2/test/credentials_test.dart @@ -76,7 +76,7 @@ void main() { "client_secret": "secret" })); - return new Future.immediate(new http.Response(JSON.stringify({ + return new Future.value(new http.Response(JSON.stringify({ 'access_token': 'new access token', 'token_type': 'bearer', 'refresh_token': 'new refresh token' @@ -106,7 +106,7 @@ void main() { "client_secret": "secret" })); - return new Future.immediate(new http.Response(JSON.stringify({ + return new Future.value(new http.Response(JSON.stringify({ 'access_token': 'new access token', 'token_type': 'bearer' }), 200, headers: {'content-type': 'application/json'})); diff --git a/pkgs/oauth2/test/utils.dart b/pkgs/oauth2/test/utils.dart index b874cc4e7..9198c8025 100644 --- a/pkgs/oauth2/test/utils.dart +++ b/pkgs/oauth2/test/utils.dart @@ -38,7 +38,7 @@ class ExpectClient extends MockClient { Future _handleRequest(http.Request request) { if (_handlers.isEmpty) { - return new Future.immediate(new http.Response('not found', 404)); + return new Future.value(new http.Response('not found', 404)); } else { return _handlers.removeFirst()(request); } From 52b141609d4fa5ea99540d1e28555bbdfe8b2c8b Mon Sep 17 00:00:00 2001 From: "sethladd@google.com" Date: Fri, 19 Apr 2013 20:51:10 +0000 Subject: [PATCH 032/159] add installation instructions to pkg packages BUG= Review URL: https://codereview.chromium.org//14188048 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@21770 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/oauth2.dart | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pkgs/oauth2/lib/oauth2.dart b/pkgs/oauth2/lib/oauth2.dart index 7fdb20555..592589d90 100644 --- a/pkgs/oauth2/lib/oauth2.dart +++ b/pkgs/oauth2/lib/oauth2.dart @@ -6,6 +6,19 @@ /// behalf of a user, and making authorized HTTP requests with the user's OAuth2 /// credentials. /// +/// ## Installing ## +/// +/// Use [pub][] to install this package. Add the following to your +/// `pubspec.yaml` file. +/// +/// dependencies: +/// oauth2: any +/// +/// Then run `pub install`. +/// +/// For more information, see the +/// [oauth2 package on pub.dartlang.org][pkg]. +/// /// OAuth2 allows a client (the program using this library) to access and /// manipulate a resource that's owned by a resource owner (the end user) and /// lives on a remote server. The client directs the resource owner to an @@ -100,6 +113,9 @@ /// }).then((file) => file.close()).then((_) => result); /// }); /// }).then(print); +/// +/// [pub]: http://pub.dartlang.org +/// [pkg]: http://pub.dartlang.org/packages/oauth2 library oauth2; export 'src/authorization_code_grant.dart'; From ce7dcf31255249548fd60709420a4d2fd0a6c142 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Tue, 30 Apr 2013 21:11:00 +0000 Subject: [PATCH 033/159] Adding Uri.parse to AuthorizationException BUG=https://code.google.com/p/dart/issues/detail?id=10050 R=nweiz@google.com Review URL: https://codereview.chromium.org//14135014 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@22199 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/src/client.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart index ec8dda5f2..62a06cad1 100644 --- a/pkgs/oauth2/lib/src/client.dart +++ b/pkgs/oauth2/lib/src/client.dart @@ -109,7 +109,8 @@ class Client extends http.BaseClient { if (!params.containsKey('error')) return response; throw new AuthorizationException( - params['error'], params['error_description'], params['error_uri']); + params['error'], params['error_description'], + Uri.parse(params['error_uri'])); }); } From 889ff236546b3af163b907f16b3334392026d299 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Tue, 30 Apr 2013 21:25:04 +0000 Subject: [PATCH 034/159] Fix a broken oauth2 test. TBR Review URL: https://codereview.chromium.org//14694002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@22203 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/src/client.dart | 2 +- pkgs/oauth2/test/client_test.dart | 17 +++++------------ 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart index 62a06cad1..62e228844 100644 --- a/pkgs/oauth2/lib/src/client.dart +++ b/pkgs/oauth2/lib/src/client.dart @@ -110,7 +110,7 @@ class Client extends http.BaseClient { throw new AuthorizationException( params['error'], params['error_description'], - Uri.parse(params['error_uri'])); + params['error_uri'] == null ? null : Uri.parse(params['error_uri'])); }); } diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart index bac054854..7a05e9905 100644 --- a/pkgs/oauth2/test/client_test.dart +++ b/pkgs/oauth2/test/client_test.dart @@ -25,12 +25,6 @@ void createHttpClient() { httpClient = new ExpectClient(); } -void expectFutureThrows(future, predicate) { - future.catchError(expectAsync1((error) { - expect(predicate(error), isTrue); - })); -} - void main() { group('with expired credentials', () { setUp(createHttpClient); @@ -42,8 +36,8 @@ void main() { var client = new oauth2.Client('identifier', 'secret', credentials, httpClient: httpClient); - expectFutureThrows(client.get(requestUri), - (e) => e is oauth2.ExpirationException); + expect(client.get(requestUri), + throwsA(new isInstanceOf())); }); test("that can be refreshed refreshes the credentials and sends the " @@ -123,8 +117,7 @@ void main() { var client = new oauth2.Client('identifier', 'secret', credentials, httpClient: httpClient); - expectFutureThrows(client.refreshCredentials(), - (e) => e is StateError); + expect(client.refreshCredentials(), throwsA(isStateError)); }); }); @@ -148,8 +141,8 @@ void main() { headers: {'www-authenticate': authenticate})); }); - expectFutureThrows(client.read(requestUri), - (e) => e is oauth2.AuthorizationException); + expect(client.read(requestUri), + throwsA(new isInstanceOf())); }); test('passes through a 401 response without www-authenticate', () { From 6c0df1d0a002865a2b0722c836e2fe0fc949a9c1 Mon Sep 17 00:00:00 2001 From: "sgjesse@google.com" Date: Mon, 6 May 2013 07:30:02 +0000 Subject: [PATCH 035/159] Change fromString constructor to parse static method on HeaderValue and ContentType. R=ajohnsen@google.com BUG= Review URL: https://codereview.chromium.org//14914002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@22406 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/src/handle_access_token_response.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart index 65e706e6a..c82c818ab 100644 --- a/pkgs/oauth2/lib/src/handle_access_token_response.dart +++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart @@ -33,7 +33,7 @@ Credentials handleAccessTokenResponse( var contentType = response.headers['content-type']; if (contentType != null) { - contentType = new ContentType.fromString(contentType); + contentType = ContentType.parse(contentType); } validate(contentType != null && contentType.value == "application/json", 'content-type was "$contentType", expected "application/json"'); @@ -103,7 +103,7 @@ void _handleErrorResponse(http.Response response, Uri tokenEndpoint) { var contentType = response.headers['content-type']; if (contentType != null) { - contentType = new ContentType.fromString(contentType); + contentType = ContentType.parse(contentType); } validate(contentType != null && contentType.value == "application/json", 'content-type was "$contentType", expected "application/json"'); From 4e753cad478fc963a5f3eab632d560196d9025f1 Mon Sep 17 00:00:00 2001 From: "sgjesse@google.com" Date: Tue, 28 May 2013 13:35:01 +0000 Subject: [PATCH 036/159] Merge the dart:uri library into dart:core and update the Uri class This merges the dart:uri library into dart:core removing the dart:uri library. Besides moving the library the Url class has been changed. * Removed existing Uri constructor as it was equivalent with Uri.parse * Remamed constructor Uri.fromComponents to Uri * Moved toplevel function encodeUriComponent to static method Uri.encodeComponent * Moved toplevel function decodeUriComponent to static method Uri.decodeComponent * Moved toplevel function encodeUri to static method Uri.encodeFull * Moved toplevel function decodeUri to static method Uri.decodeFull * Rename domain to host * Added static methods Uri.encodeQueryComponent and Uri.decodeQueryComponent * Added support for path generation and splitting * Added support for query generation and splitting * Added some level of normalization R=floitsch@google.com, lrn@google.com, nweiz@google.com, scheglov@google.com BUG= Review URL: https://codereview.chromium.org//16019002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@23266 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/oauth2.dart | 1 - pkgs/oauth2/lib/src/authorization_code_grant.dart | 1 - pkgs/oauth2/lib/src/authorization_exception.dart | 1 - pkgs/oauth2/lib/src/client.dart | 1 - pkgs/oauth2/lib/src/credentials.dart | 1 - pkgs/oauth2/lib/src/handle_access_token_response.dart | 1 - pkgs/oauth2/lib/src/utils.dart | 11 ++++++----- pkgs/oauth2/test/authorization_code_grant_test.dart | 1 - pkgs/oauth2/test/client_test.dart | 1 - pkgs/oauth2/test/credentials_test.dart | 1 - .../test/handle_access_token_response_test.dart | 1 - 11 files changed, 6 insertions(+), 15 deletions(-) diff --git a/pkgs/oauth2/lib/oauth2.dart b/pkgs/oauth2/lib/oauth2.dart index 592589d90..96bffcf1e 100644 --- a/pkgs/oauth2/lib/oauth2.dart +++ b/pkgs/oauth2/lib/oauth2.dart @@ -34,7 +34,6 @@ /// that the library is being used by a server-side application. /// /// import 'dart:io' -/// import 'dart:uri' /// import 'package:oauth2/oauth2.dart' as oauth2; /// /// // These URLs are endpoints that are provided by the authorization diff --git a/pkgs/oauth2/lib/src/authorization_code_grant.dart b/pkgs/oauth2/lib/src/authorization_code_grant.dart index 900dfc536..266812e2c 100644 --- a/pkgs/oauth2/lib/src/authorization_code_grant.dart +++ b/pkgs/oauth2/lib/src/authorization_code_grant.dart @@ -5,7 +5,6 @@ library authorization_code_grant; import 'dart:async'; -import 'dart:uri'; import 'package:http/http.dart' as http; diff --git a/pkgs/oauth2/lib/src/authorization_exception.dart b/pkgs/oauth2/lib/src/authorization_exception.dart index 1062aa987..d48c3b16d 100644 --- a/pkgs/oauth2/lib/src/authorization_exception.dart +++ b/pkgs/oauth2/lib/src/authorization_exception.dart @@ -5,7 +5,6 @@ library authorization_exception; import 'dart:io'; -import 'dart:uri'; /// An exception raised when OAuth2 authorization fails. class AuthorizationException implements Exception { diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart index 62e228844..d473be19a 100644 --- a/pkgs/oauth2/lib/src/client.dart +++ b/pkgs/oauth2/lib/src/client.dart @@ -5,7 +5,6 @@ library oauth2_client; import 'dart:async'; -import 'dart:uri'; import 'package:http/http.dart' as http; diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index 420dc4934..b02f4705d 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -6,7 +6,6 @@ library credentials; import 'dart:async'; import 'dart:json' as JSON; -import 'dart:uri'; import 'package:http/http.dart' as http; diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart index c82c818ab..5e5f6e279 100644 --- a/pkgs/oauth2/lib/src/handle_access_token_response.dart +++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart @@ -6,7 +6,6 @@ library handle_access_token_response; import 'dart:io'; import 'dart:json' as JSON; -import 'dart:uri'; import 'package:http/http.dart' as http; diff --git a/pkgs/oauth2/lib/src/utils.dart b/pkgs/oauth2/lib/src/utils.dart index f69ac8b19..b47adcbf2 100644 --- a/pkgs/oauth2/lib/src/utils.dart +++ b/pkgs/oauth2/lib/src/utils.dart @@ -5,7 +5,6 @@ library utils; import 'dart:async'; -import 'dart:uri'; import 'dart:isolate'; import 'dart:crypto'; @@ -35,8 +34,10 @@ Map queryToMap(String queryList) { String mapToQuery(Map map) { var pairs = >[]; map.forEach((key, value) { - key = encodeUriComponent(key); - value = (value == null || value.isEmpty) ? null : encodeUriComponent(value); + key = Uri.encodeQueryComponent(key); + value = (value == null || value.isEmpty) + ? null + : Uri.encodeQueryComponent(value); pairs.add([key, value]); }); return pairs.map((pair) { @@ -50,10 +51,10 @@ String mapToQuery(Map map) { void mapAddAll(Map destination, Map source) => source.forEach((key, value) => destination[key] = value); -/// Decode a URL-encoded string. Unlike [decodeUriComponent], this includes +/// Decode a URL-encoded string. Unlike [Uri.decodeComponent], this includes /// replacing `+` with ` `. String urlDecode(String encoded) => - decodeUriComponent(encoded.replaceAll("+", " ")); + Uri.decodeComponent(encoded.replaceAll("+", " ")); /// Like [String.split], but only splits on the first occurrence of the pattern. /// This will always return a list of two elements or fewer. diff --git a/pkgs/oauth2/test/authorization_code_grant_test.dart b/pkgs/oauth2/test/authorization_code_grant_test.dart index 22be3cfa7..0d4a5fc0e 100644 --- a/pkgs/oauth2/test/authorization_code_grant_test.dart +++ b/pkgs/oauth2/test/authorization_code_grant_test.dart @@ -7,7 +7,6 @@ library authorization_code_grant_test; import 'dart:async'; import 'dart:io'; import 'dart:json' as JSON; -import 'dart:uri'; import 'package:unittest/unittest.dart'; import 'package:http/http.dart' as http; diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart index 7a05e9905..d55eef7dc 100644 --- a/pkgs/oauth2/test/client_test.dart +++ b/pkgs/oauth2/test/client_test.dart @@ -7,7 +7,6 @@ library client_test; import 'dart:async'; import 'dart:io'; import 'dart:json' as JSON; -import 'dart:uri'; import 'package:http/http.dart' as http; import 'package:oauth2/oauth2.dart' as oauth2; diff --git a/pkgs/oauth2/test/credentials_test.dart b/pkgs/oauth2/test/credentials_test.dart index 652d3940b..768b85a12 100644 --- a/pkgs/oauth2/test/credentials_test.dart +++ b/pkgs/oauth2/test/credentials_test.dart @@ -7,7 +7,6 @@ library credentials_test; import 'dart:async'; import 'dart:io'; import 'dart:json' as JSON; -import 'dart:uri'; import 'package:http/http.dart' as http; import 'package:oauth2/oauth2.dart' as oauth2; diff --git a/pkgs/oauth2/test/handle_access_token_response_test.dart b/pkgs/oauth2/test/handle_access_token_response_test.dart index 772f2631e..45860abb2 100644 --- a/pkgs/oauth2/test/handle_access_token_response_test.dart +++ b/pkgs/oauth2/test/handle_access_token_response_test.dart @@ -6,7 +6,6 @@ library handle_access_token_response_test; import 'dart:io'; import 'dart:json' as JSON; -import 'dart:uri'; import 'package:http/http.dart' as http; import 'package:oauth2/oauth2.dart' as oauth2; From 276f12ef90b54e447cbd037e925d4d6d435d621d Mon Sep 17 00:00:00 2001 From: "sgjesse@google.com" Date: Wed, 29 May 2013 07:29:29 +0000 Subject: [PATCH 037/159] Remove library dart:crypto Moved the contnet of dart:crypto to the package pkg/crypto. R=dgrove@google.com, floitsch@google.com, iposva@google.com, nweiz@google.com BUG= Review URL: https://codereview.chromium.org//15820008 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@23322 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/src/utils.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkgs/oauth2/lib/src/utils.dart b/pkgs/oauth2/lib/src/utils.dart index b47adcbf2..d0e5f114e 100644 --- a/pkgs/oauth2/lib/src/utils.dart +++ b/pkgs/oauth2/lib/src/utils.dart @@ -6,7 +6,8 @@ library utils; import 'dart:async'; import 'dart:isolate'; -import 'dart:crypto'; + +import "package:crypto/crypto.dart"; /// Adds additional query parameters to [url], overwriting the original /// parameters if a name conflict occurs. From 671e0c6345a84f7c5ba29077abad5993d13157b0 Mon Sep 17 00:00:00 2001 From: "sgjesse@google.com" Date: Thu, 30 May 2013 13:39:24 +0000 Subject: [PATCH 038/159] Remove the HttpRequest.queryParameters getter The parsed query parameters are now now available from the Uri object. TBR=whesse@google.com BUG=http://dartbug/2641 Review URL: https://codereview.chromium.org//15688013 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@23413 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/oauth2.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/oauth2/lib/oauth2.dart b/pkgs/oauth2/lib/oauth2.dart index 96bffcf1e..cca66abd6 100644 --- a/pkgs/oauth2/lib/oauth2.dart +++ b/pkgs/oauth2/lib/oauth2.dart @@ -96,7 +96,7 @@ /// // Once the user is redirected to `redirectUrl`, pass the query /// // parameters to the AuthorizationCodeGrant. It will validate them /// // and extract the authorization code to create a new Client. -/// return grant.handleAuthorizationResponse(request.queryParameters); +/// return grant.handleAuthorizationResponse(request.uri.queryParameters); /// }) /// }).then((client) { /// // Once you have a Client, you can use it just like any other HTTP From ab8b8d3ecb579823c436b3329fc39b59e4e9ebe9 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Mon, 10 Jun 2013 21:21:15 +0000 Subject: [PATCH 039/159] Stop working around issue 2644. R=rnystrom@google.com Review URL: https://codereview.chromium.org//16756002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@23829 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/src/utils.dart | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pkgs/oauth2/lib/src/utils.dart b/pkgs/oauth2/lib/src/utils.dart index d0e5f114e..759357e78 100644 --- a/pkgs/oauth2/lib/src/utils.dart +++ b/pkgs/oauth2/lib/src/utils.dart @@ -13,7 +13,7 @@ import "package:crypto/crypto.dart"; /// parameters if a name conflict occurs. Uri addQueryParameters(Uri url, Map parameters) { var queryMap = queryToMap(url.query); - mapAddAll(queryMap, parameters); + queryMap.addAll(parameters); return url.resolve("?${mapToQuery(queryMap)}"); } @@ -47,11 +47,6 @@ String mapToQuery(Map map) { }).join("&"); } -/// Add all key/value pairs from [source] to [destination], overwriting any -/// pre-existing values. -void mapAddAll(Map destination, Map source) => - source.forEach((key, value) => destination[key] = value); - /// Decode a URL-encoded string. Unlike [Uri.decodeComponent], this includes /// replacing `+` with ` `. String urlDecode(String encoded) => From e25cea7bc17c12cbb05391630b679c16b7d1531c Mon Sep 17 00:00:00 2001 From: "gram@google.com" Date: Fri, 14 Jun 2013 19:46:16 +0000 Subject: [PATCH 040/159] Change MatchState to Map. R=sigmund@google.com Review URL: https://codereview.chromium.org//17090006 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@24046 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/test/utils.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/test/utils.dart b/pkgs/oauth2/test/utils.dart index 9198c8025..fddcd372e 100644 --- a/pkgs/oauth2/test/utils.dart +++ b/pkgs/oauth2/test/utils.dart @@ -54,7 +54,7 @@ const Matcher throwsAuthorizationException = class _AuthorizationException extends TypeMatcher { const _AuthorizationException() : super("AuthorizationException"); - bool matches(item, MatchState matchState) => + bool matches(item, Map matchState) => item is oauth2.AuthorizationException; } @@ -67,6 +67,6 @@ const Matcher throwsExpirationException = class _ExpirationException extends TypeMatcher { const _ExpirationException() : super("ExpirationException"); - bool matches(item, MatchState matchState) => + bool matches(item, Map matchState) => item is oauth2.ExpirationException; } From a1d0ae9aee4659d37d9cae9ca022f68608b980b6 Mon Sep 17 00:00:00 2001 From: "kevmoo@j832.com" Date: Tue, 6 Aug 2013 20:42:04 +0000 Subject: [PATCH 041/159] pkg: analysis aided cleanup Removed a lot of warnings and hints when opening many pkg projects in the editor R=gram@google.com Review URL: https://codereview.chromium.org//22284003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@25831 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/src/authorization_exception.dart | 2 -- pkgs/oauth2/lib/src/expiration_exception.dart | 2 -- pkgs/oauth2/lib/src/utils.dart | 3 --- pkgs/oauth2/test/authorization_code_grant_test.dart | 2 -- pkgs/oauth2/test/client_test.dart | 1 - pkgs/oauth2/test/credentials_test.dart | 5 ++--- pkgs/oauth2/test/handle_access_token_response_test.dart | 1 - 7 files changed, 2 insertions(+), 14 deletions(-) diff --git a/pkgs/oauth2/lib/src/authorization_exception.dart b/pkgs/oauth2/lib/src/authorization_exception.dart index d48c3b16d..2a19cd11c 100644 --- a/pkgs/oauth2/lib/src/authorization_exception.dart +++ b/pkgs/oauth2/lib/src/authorization_exception.dart @@ -4,8 +4,6 @@ library authorization_exception; -import 'dart:io'; - /// An exception raised when OAuth2 authorization fails. class AuthorizationException implements Exception { /// The name of the error. Possible names are enumerated in [the spec][]. diff --git a/pkgs/oauth2/lib/src/expiration_exception.dart b/pkgs/oauth2/lib/src/expiration_exception.dart index 8c8ad1f64..9829de1e1 100644 --- a/pkgs/oauth2/lib/src/expiration_exception.dart +++ b/pkgs/oauth2/lib/src/expiration_exception.dart @@ -4,8 +4,6 @@ library expiration_exception; -import 'dart:io'; - import 'credentials.dart'; /// An exception raised when attempting to use expired OAuth2 credentials. diff --git a/pkgs/oauth2/lib/src/utils.dart b/pkgs/oauth2/lib/src/utils.dart index 759357e78..12a429e19 100644 --- a/pkgs/oauth2/lib/src/utils.dart +++ b/pkgs/oauth2/lib/src/utils.dart @@ -5,9 +5,6 @@ library utils; import 'dart:async'; -import 'dart:isolate'; - -import "package:crypto/crypto.dart"; /// Adds additional query parameters to [url], overwriting the original /// parameters if a name conflict occurs. diff --git a/pkgs/oauth2/test/authorization_code_grant_test.dart b/pkgs/oauth2/test/authorization_code_grant_test.dart index 0d4a5fc0e..473fee176 100644 --- a/pkgs/oauth2/test/authorization_code_grant_test.dart +++ b/pkgs/oauth2/test/authorization_code_grant_test.dart @@ -5,12 +5,10 @@ library authorization_code_grant_test; import 'dart:async'; -import 'dart:io'; import 'dart:json' as JSON; import 'package:unittest/unittest.dart'; import 'package:http/http.dart' as http; -import 'package:http/testing.dart'; import 'package:oauth2/oauth2.dart' as oauth2; import 'utils.dart'; diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart index d55eef7dc..922302dc7 100644 --- a/pkgs/oauth2/test/client_test.dart +++ b/pkgs/oauth2/test/client_test.dart @@ -5,7 +5,6 @@ library client_test; import 'dart:async'; -import 'dart:io'; import 'dart:json' as JSON; import 'package:http/http.dart' as http; diff --git a/pkgs/oauth2/test/credentials_test.dart b/pkgs/oauth2/test/credentials_test.dart index 768b85a12..6a873f27c 100644 --- a/pkgs/oauth2/test/credentials_test.dart +++ b/pkgs/oauth2/test/credentials_test.dart @@ -5,7 +5,6 @@ library credentials_test; import 'dart:async'; -import 'dart:io'; import 'dart:json' as JSON; import 'package:http/http.dart' as http; @@ -82,7 +81,7 @@ void main() { }), 200, headers: {'content-type': 'application/json'})); }); - + expect(credentials.refresh('identifier', 'secret', httpClient: httpClient) .then((credentials) { expect(credentials.accessToken, equals('new access token')); @@ -111,7 +110,7 @@ void main() { }), 200, headers: {'content-type': 'application/json'})); }); - + expect(credentials.refresh('identifier', 'secret', httpClient: httpClient) .then((credentials) { expect(credentials.accessToken, equals('new access token')); diff --git a/pkgs/oauth2/test/handle_access_token_response_test.dart b/pkgs/oauth2/test/handle_access_token_response_test.dart index 45860abb2..10e6f75e2 100644 --- a/pkgs/oauth2/test/handle_access_token_response_test.dart +++ b/pkgs/oauth2/test/handle_access_token_response_test.dart @@ -4,7 +4,6 @@ library handle_access_token_response_test; -import 'dart:io'; import 'dart:json' as JSON; import 'package:http/http.dart' as http; From 8d01b263e13858c95692e0ecb93c19b271e9e4fb Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Mon, 26 Aug 2013 17:50:00 +0000 Subject: [PATCH 042/159] Stop working around issue 6775. R=rnystrom@google.com BUG= Review URL: https://codereview.chromium.org//23093023 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@26664 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/src/credentials.dart | 3 +-- pkgs/oauth2/lib/src/handle_access_token_response.dart | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index b02f4705d..5bbec169d 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -81,8 +81,7 @@ class Credentials { var parsed; try { parsed = JSON.parse(json); - } catch (e) { - // TODO(nweiz): narrow this catch clause once issue 6775 is fixed. + } on FormatException catch (e) { validate(false, 'invalid JSON'); } diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart index 5e5f6e279..c14b8d1e2 100644 --- a/pkgs/oauth2/lib/src/handle_access_token_response.dart +++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart @@ -40,8 +40,7 @@ Credentials handleAccessTokenResponse( var parameters; try { parameters = JSON.parse(response.body); - } catch (e) { - // TODO(nweiz): narrow this catch clause once issue 6775 is fixed. + } on FormatException catch (e) { validate(false, 'invalid JSON'); } @@ -110,8 +109,7 @@ void _handleErrorResponse(http.Response response, Uri tokenEndpoint) { var parameters; try { parameters = JSON.parse(response.body); - } catch (e) { - // TODO(nweiz): narrow this catch clause once issue 6775 is fixed. + } on FormatException catch (e) { validate(false, 'invalid JSON'); } From 7f72fce72ee5c68091e2b36adba6f3ba276927ae Mon Sep 17 00:00:00 2001 From: "floitsch@google.com" Date: Wed, 28 Aug 2013 14:05:11 +0000 Subject: [PATCH 043/159] Remove usage of dart:json. R=jmesserly@google.com, lrn@google.com, nweiz@google.com, rnystrom@google.com Review URL: https://codereview.chromium.org//23596007 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@26789 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/src/credentials.dart | 6 +++--- .../oauth2/lib/src/handle_access_token_response.dart | 6 +++--- pkgs/oauth2/test/authorization_code_grant_test.dart | 6 +++--- pkgs/oauth2/test/client_test.dart | 6 +++--- pkgs/oauth2/test/credentials_test.dart | 8 ++++---- .../test/handle_access_token_response_test.dart | 12 ++++++------ 6 files changed, 22 insertions(+), 22 deletions(-) diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index 5bbec169d..13d33204c 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -5,7 +5,7 @@ library credentials; import 'dart:async'; -import 'dart:json' as JSON; +import 'dart:convert'; import 'package:http/http.dart' as http; @@ -80,7 +80,7 @@ class Credentials { var parsed; try { - parsed = JSON.parse(json); + parsed = JSON.decode(json); } on FormatException catch (e) { validate(false, 'invalid JSON'); } @@ -125,7 +125,7 @@ class Credentials { /// 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({ + String toJson() => JSON.encode({ 'accessToken': accessToken, 'refreshToken': refreshToken, 'tokenEndpoint': tokenEndpoint == null ? null : tokenEndpoint.toString(), diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart index c14b8d1e2..129f25919 100644 --- a/pkgs/oauth2/lib/src/handle_access_token_response.dart +++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart @@ -5,7 +5,7 @@ library handle_access_token_response; import 'dart:io'; -import 'dart:json' as JSON; +import 'dart:convert'; import 'package:http/http.dart' as http; @@ -39,7 +39,7 @@ Credentials handleAccessTokenResponse( var parameters; try { - parameters = JSON.parse(response.body); + parameters = JSON.decode(response.body); } on FormatException catch (e) { validate(false, 'invalid JSON'); } @@ -108,7 +108,7 @@ void _handleErrorResponse(http.Response response, Uri tokenEndpoint) { var parameters; try { - parameters = JSON.parse(response.body); + parameters = JSON.decode(response.body); } on FormatException catch (e) { validate(false, 'invalid JSON'); } diff --git a/pkgs/oauth2/test/authorization_code_grant_test.dart b/pkgs/oauth2/test/authorization_code_grant_test.dart index 473fee176..85c54e758 100644 --- a/pkgs/oauth2/test/authorization_code_grant_test.dart +++ b/pkgs/oauth2/test/authorization_code_grant_test.dart @@ -5,7 +5,7 @@ library authorization_code_grant_test; import 'dart:async'; -import 'dart:json' as JSON; +import 'dart:convert'; import 'package:unittest/unittest.dart'; import 'package:http/http.dart' as http; @@ -151,7 +151,7 @@ void main() { 'client_secret': 'secret' })); - return new Future.value(new http.Response(JSON.stringify({ + return new Future.value(new http.Response(JSON.encode({ 'access_token': 'access token', 'token_type': 'bearer', }), 200, headers: {'content-type': 'application/json'})); @@ -195,7 +195,7 @@ void main() { 'client_secret': 'secret' })); - return new Future.value(new http.Response(JSON.stringify({ + return new Future.value(new http.Response(JSON.encode({ 'access_token': 'access token', 'token_type': 'bearer', }), 200, headers: {'content-type': 'application/json'})); diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart index 922302dc7..56dd78975 100644 --- a/pkgs/oauth2/test/client_test.dart +++ b/pkgs/oauth2/test/client_test.dart @@ -5,7 +5,7 @@ library client_test; import 'dart:async'; -import 'dart:json' as JSON; +import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:oauth2/oauth2.dart' as oauth2; @@ -49,7 +49,7 @@ void main() { httpClient.expectRequest((request) { expect(request.method, equals('POST')); expect(request.url.toString(), equals(tokenEndpoint.toString())); - return new Future.value(new http.Response(JSON.stringify({ + return new Future.value(new http.Response(JSON.encode({ 'access_token': 'new access token', 'token_type': 'bearer' }), 200, headers: {'content-type': 'application/json'})); @@ -99,7 +99,7 @@ void main() { httpClient.expectRequest((request) { expect(request.method, equals('POST')); expect(request.url.toString(), equals(tokenEndpoint.toString())); - return new Future.value(new http.Response(JSON.stringify({ + return new Future.value(new http.Response(JSON.encode({ 'access_token': 'new access token', 'token_type': 'bearer' }), 200, headers: {'content-type': 'application/json'})); diff --git a/pkgs/oauth2/test/credentials_test.dart b/pkgs/oauth2/test/credentials_test.dart index 6a873f27c..f3e303b9a 100644 --- a/pkgs/oauth2/test/credentials_test.dart +++ b/pkgs/oauth2/test/credentials_test.dart @@ -5,7 +5,7 @@ library credentials_test; import 'dart:async'; -import 'dart:json' as JSON; +import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:oauth2/oauth2.dart' as oauth2; @@ -74,7 +74,7 @@ void main() { "client_secret": "secret" })); - return new Future.value(new http.Response(JSON.stringify({ + return new Future.value(new http.Response(JSON.encode({ 'access_token': 'new access token', 'token_type': 'bearer', 'refresh_token': 'new refresh token' @@ -104,7 +104,7 @@ void main() { "client_secret": "secret" })); - return new Future.value(new http.Response(JSON.stringify({ + return new Future.value(new http.Response(JSON.encode({ 'access_token': 'new access token', 'token_type': 'bearer' }), 200, headers: {'content-type': 'application/json'})); @@ -120,7 +120,7 @@ void main() { group("fromJson", () { oauth2.Credentials fromMap(Map map) => - new oauth2.Credentials.fromJson(JSON.stringify(map)); + new oauth2.Credentials.fromJson(JSON.encode(map)); test("should load the same credentials from toJson", () { var expiration = new DateTime.now().subtract(new Duration(hours: 1)); diff --git a/pkgs/oauth2/test/handle_access_token_response_test.dart b/pkgs/oauth2/test/handle_access_token_response_test.dart index 10e6f75e2..a99d8eaa7 100644 --- a/pkgs/oauth2/test/handle_access_token_response_test.dart +++ b/pkgs/oauth2/test/handle_access_token_response_test.dart @@ -4,7 +4,7 @@ library handle_access_token_response_test; -import 'dart:json' as JSON; +import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:oauth2/oauth2.dart' as oauth2; @@ -70,28 +70,28 @@ void main() { }); test('with a non-string error_description causes a FormatException', () { - expect(() => handleError(body: JSON.stringify({ + expect(() => handleError(body: JSON.encode({ "error": "invalid_request", "error_description": 12 })), throwsFormatException); }); test('with a non-string error_uri causes a FormatException', () { - expect(() => handleError(body: JSON.stringify({ + expect(() => handleError(body: JSON.encode({ "error": "invalid_request", "error_uri": 12 })), throwsFormatException); }); test('with a string error_description causes a AuthorizationException', () { - expect(() => handleError(body: JSON.stringify({ + expect(() => handleError(body: JSON.encode({ "error": "invalid_request", "error_description": "description" })), throwsAuthorizationException); }); test('with a string error_uri causes a AuthorizationException', () { - expect(() => handleError(body: JSON.stringify({ + expect(() => handleError(body: JSON.encode({ "error": "invalid_request", "error_uri": "http://example.com/error" })), throwsAuthorizationException); @@ -106,7 +106,7 @@ void main() { expiresIn, refreshToken, scope}) { - return handle(new http.Response(JSON.stringify({ + return handle(new http.Response(JSON.encode({ 'access_token': accessToken, 'token_type': tokenType, 'expires_in': expiresIn, From ee69a262ecb72305d74e208070f93db802db8c8e Mon Sep 17 00:00:00 2001 From: "jmesserly@google.com" Date: Wed, 6 Nov 2013 03:27:58 +0000 Subject: [PATCH 044/159] add versions and constraints for packages and samples - all packages at 0.9.0, except "analyzer" which had a version already - dependencies at ">=0.9.0 <0.10.0" except analyzer is ">=0.10.0 <0.11.0" - sdk constraint ">=1.0.0 <2.0.0" R=sigmund@google.com Review URL: https://codereview.chromium.org//59763006 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@29957 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/pubspec.yaml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index f531f467e..b2c9358f5 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,4 +1,5 @@ name: oauth2 +version: 0.9.0 author: "Dart Team " homepage: http://www.dartlang.org description: > @@ -6,6 +7,8 @@ description: > behalf of a user, and making authorized HTTP requests with the user's OAuth2 credentials. dependencies: - http: any + http: ">=0.9.0 <0.10.0" dev_dependencies: - unittest: any + unittest: ">=0.9.0 <0.10.0" +environment: + sdk: ">=1.0.0 <2.0.0" From d36a0840c711c2590275bb8b268de9116cb476da Mon Sep 17 00:00:00 2001 From: "ajohnsen@google.com" Date: Wed, 6 Nov 2013 09:09:18 +0000 Subject: [PATCH 045/159] Revert "add versions and constraints for packages and samples" This is currently blocking us from testing samples. BUG= R=kasperl@google.com Review URL: https://codereview.chromium.org//59513007 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@29960 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/pubspec.yaml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index b2c9358f5..f531f467e 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,4 @@ name: oauth2 -version: 0.9.0 author: "Dart Team " homepage: http://www.dartlang.org description: > @@ -7,8 +6,6 @@ description: > behalf of a user, and making authorized HTTP requests with the user's OAuth2 credentials. dependencies: - http: ">=0.9.0 <0.10.0" + http: any dev_dependencies: - unittest: ">=0.9.0 <0.10.0" -environment: - sdk: ">=1.0.0 <2.0.0" + unittest: any From 507e3599681a80d851706f4490e4248045e1d1e7 Mon Sep 17 00:00:00 2001 From: "dgrove@google.com" Date: Wed, 6 Nov 2013 18:28:22 +0000 Subject: [PATCH 046/159] Re-land r29957 (add versions and constraints for packages and samples), with SDK constraints bumped from 1.0.0 to 0.8.10+6 . R=ricow@google.com, sigmund@google.com Review URL: https://codereview.chromium.org//62473002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@29986 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/pubspec.yaml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index f531f467e..4d6fda671 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,4 +1,5 @@ name: oauth2 +version: 0.9.0 author: "Dart Team " homepage: http://www.dartlang.org description: > @@ -6,6 +7,8 @@ description: > behalf of a user, and making authorized HTTP requests with the user's OAuth2 credentials. dependencies: - http: any + http: ">=0.9.0 <0.10.0" dev_dependencies: - unittest: any + unittest: ">=0.9.0 <0.10.0" +environment: + sdk: ">=0.8.10+6 <2.0.0" From d276e746b9b2f7efb657b90095ab257ec57735af Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Tue, 12 Nov 2013 01:59:39 +0000 Subject: [PATCH 047/159] Add utility methods for sending non-form data in pkg/http. R=rnystrom@google.com BUG=8380 Review URL: https://codereview.chromium.org//65583004 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@30183 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/src/authorization_code_grant.dart | 2 +- pkgs/oauth2/lib/src/credentials.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/lib/src/authorization_code_grant.dart b/pkgs/oauth2/lib/src/authorization_code_grant.dart index 266812e2c..c879e8ca1 100644 --- a/pkgs/oauth2/lib/src/authorization_code_grant.dart +++ b/pkgs/oauth2/lib/src/authorization_code_grant.dart @@ -228,7 +228,7 @@ class AuthorizationCodeGrant { /// the state beforehand. Future _handleAuthorizationCode(String authorizationCode) { var startTime = new DateTime.now(); - return _httpClient.post(this.tokenEndpoint, fields: { + return _httpClient.post(this.tokenEndpoint, body: { "grant_type": "authorization_code", "code": authorizationCode, "redirect_uri": this._redirectEndpoint.toString(), diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index 13d33204c..ce4e06454 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -173,7 +173,7 @@ class Credentials { }; if (!scopes.isEmpty) fields["scope"] = scopes.join(' '); - return httpClient.post(tokenEndpoint, fields: fields); + return httpClient.post(tokenEndpoint, body: fields); }).then((response) { return handleAccessTokenResponse( response, tokenEndpoint, startTime, scopes); From f065c0d916eaecd076459ea115d7ccf30de18e2d Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Thu, 14 Nov 2013 23:52:37 +0000 Subject: [PATCH 048/159] Mention that OAuth2 currently only works with dart:io. R=rnystrom@google.com BUG=7445 Review URL: https://codereview.chromium.org//61393004 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@30296 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index 4d6fda671..b0d701d88 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -5,7 +5,7 @@ homepage: http://www.dartlang.org description: > A client library for authenticating with a remote service via OAuth2 on behalf of a user, and making authorized HTTP requests with the user's - OAuth2 credentials. + OAuth2 credentials. Currently only works with dart:io. dependencies: http: ">=0.9.0 <0.10.0" dev_dependencies: From 737df1a332ec9cabee13cb96abab0b48277dba4c Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Mon, 6 Jan 2014 23:55:51 +0000 Subject: [PATCH 049/159] Update oauth2's dependency on http. OAuth2 was upgraded to support the new HTTP API prior to the new conventions for package versioning, and the upgraded version hasn't been released yet. R=rnystrom@google.com BUG=15809 Review URL: https://codereview.chromium.org//98163005 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@31523 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index b0d701d88..74ee7710b 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,5 @@ name: oauth2 -version: 0.9.0 +version: 0.9.1 author: "Dart Team " homepage: http://www.dartlang.org description: > @@ -7,7 +7,7 @@ description: > behalf of a user, and making authorized HTTP requests with the user's OAuth2 credentials. Currently only works with dart:io. dependencies: - http: ">=0.9.0 <0.10.0" + http: ">=0.9.2 <0.10.0" dev_dependencies: unittest: ">=0.9.0 <0.10.0" environment: From 0e24ded8ff281306e0565e89ca76f9dc95cc7b3e Mon Sep 17 00:00:00 2001 From: "kevmoo@google.com" Date: Mon, 13 Jan 2014 18:07:45 +0000 Subject: [PATCH 050/159] pkg/unittest: added LICENSE R=rnystrom@google.com Review URL: https://codereview.chromium.org//135343002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@31750 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/LICENSE | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 pkgs/oauth2/LICENSE diff --git a/pkgs/oauth2/LICENSE b/pkgs/oauth2/LICENSE new file mode 100644 index 000000000..5c60afea3 --- /dev/null +++ b/pkgs/oauth2/LICENSE @@ -0,0 +1,26 @@ +Copyright 2014, the Dart project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From d7e89d9140e8ba48f003bc8c04f1317e33540bdb Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Tue, 25 Feb 2014 01:29:16 +0000 Subject: [PATCH 051/159] Added text/javascript as a possible content-type response to the _validate method Some api provider like dropbox core api use text/javascript instead application/json as content-type response BUG= R=nweiz@google.com Review URL: https://codereview.chromium.org//177093006 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@33001 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/lib/src/handle_access_token_response.dart | 7 ++++++- pkgs/oauth2/test/handle_access_token_response_test.dart | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart index 129f25919..11a6fb9fe 100644 --- a/pkgs/oauth2/lib/src/handle_access_token_response.dart +++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart @@ -34,7 +34,12 @@ Credentials handleAccessTokenResponse( if (contentType != null) { contentType = ContentType.parse(contentType); } - validate(contentType != null && contentType.value == "application/json", + + // The spec requires a content-type of application/json, but some endpoints + // (e.g. Dropbox) serve it as text/javascript instead. + validate(contentType != null && + (contentType.value == "application/json" || + contentType.value == "text/javascript"), 'content-type was "$contentType", expected "application/json"'); var parameters; diff --git a/pkgs/oauth2/test/handle_access_token_response_test.dart b/pkgs/oauth2/test/handle_access_token_response_test.dart index a99d8eaa7..c36286847 100644 --- a/pkgs/oauth2/test/handle_access_token_response_test.dart +++ b/pkgs/oauth2/test/handle_access_token_response_test.dart @@ -138,6 +138,11 @@ void main() { expect(credentials.accessToken, equals('access token')); }); + test('with a JavScript content-type returns the correct credentials', () { + var credentials = handleSuccess(contentType: 'text/javascript'); + expect(credentials.accessToken, equals('access token')); + }); + test('with a null access token throws a FormatException', () { expect(() => handleSuccess(accessToken: null), throwsFormatException); }); From f72894440608c9b4664a09aefbaf9cfce5903c6d Mon Sep 17 00:00:00 2001 From: "kevmoo@google.com" Date: Thu, 6 Mar 2014 00:13:44 +0000 Subject: [PATCH 052/159] pkg/oauth2 bringing tests into 2014 R=nweiz@google.com Review URL: https://codereview.chromium.org//187843006 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@33357 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/pubspec.yaml | 12 ++--- .../test/authorization_code_grant_test.dart | 53 +++++++------------ pkgs/oauth2/test/client_test.dart | 6 +-- pkgs/oauth2/test/credentials_test.dart | 14 +++-- .../handle_access_token_response_test.dart | 9 ++-- 5 files changed, 35 insertions(+), 59 deletions(-) diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index 74ee7710b..c3dad6e00 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,14 +1,14 @@ name: oauth2 -version: 0.9.1 -author: "Dart Team " +version: 0.9.2-dev +author: Dart Team homepage: http://www.dartlang.org description: > A client library for authenticating with a remote service via OAuth2 on behalf of a user, and making authorized HTTP requests with the user's OAuth2 credentials. Currently only works with dart:io. +environment: + sdk: '>=1.0.0 <2.0.0' dependencies: - http: ">=0.9.2 <0.10.0" + http: '>=0.9.2 <0.10.0' dev_dependencies: - unittest: ">=0.9.0 <0.10.0" -environment: - sdk: ">=0.8.10+6 <2.0.0" + unittest: '>=0.9.0 <0.11.0' diff --git a/pkgs/oauth2/test/authorization_code_grant_test.dart b/pkgs/oauth2/test/authorization_code_grant_test.dart index 85c54e758..2c3265e21 100644 --- a/pkgs/oauth2/test/authorization_code_grant_test.dart +++ b/pkgs/oauth2/test/authorization_code_grant_test.dart @@ -29,12 +29,6 @@ void createGrant() { httpClient: client); } -void expectFutureThrows(future, predicate) { - future.catchError(expectAsync1((error) { - expect(predicate(error), isTrue); - })); -} - void main() { group('.getAuthorizationUrl', () { setUp(createGrant); @@ -96,46 +90,41 @@ void main() { setUp(createGrant); test("can't be called before .getAuthorizationUrl", () { - expectFutureThrows(grant.handleAuthorizationResponse({}), - (e) => e is StateError); + expect(grant.handleAuthorizationResponse({}), throwsStateError); }); test("can't be called twice", () { grant.getAuthorizationUrl(redirectUrl); - expectFutureThrows( - grant.handleAuthorizationResponse({'code': 'auth code'}), - (e) => e is FormatException); - expectFutureThrows( - grant.handleAuthorizationResponse({'code': 'auth code'}), - (e) => e is StateError); + expect(grant.handleAuthorizationResponse({'code': 'auth code'}), + throwsFormatException); + expect(grant.handleAuthorizationResponse({'code': 'auth code'}), + throwsStateError); }); test('must have a state parameter if the authorization URL did', () { grant.getAuthorizationUrl(redirectUrl, state: 'state'); - expectFutureThrows( - grant.handleAuthorizationResponse({'code': 'auth code'}), - (e) => e is FormatException); + expect(grant.handleAuthorizationResponse({'code': 'auth code'}), + throwsFormatException); }); test('must have the same state parameter the authorization URL did', () { grant.getAuthorizationUrl(redirectUrl, state: 'state'); - expectFutureThrows(grant.handleAuthorizationResponse({ + expect(grant.handleAuthorizationResponse({ 'code': 'auth code', 'state': 'other state' - }), (e) => e is FormatException); + }), throwsFormatException); }); test('must have a code parameter', () { grant.getAuthorizationUrl(redirectUrl); - expectFutureThrows(grant.handleAuthorizationResponse({}), - (e) => e is FormatException); + expect(grant.handleAuthorizationResponse({}), throwsFormatException); }); test('with an error parameter throws an AuthorizationException', () { grant.getAuthorizationUrl(redirectUrl); - expectFutureThrows( + expect( grant.handleAuthorizationResponse({'error': 'invalid_request'}), - (e) => e is oauth2.AuthorizationException); + throwsA((e) => e is oauth2.AuthorizationException)); }); test('sends an authorization code request', () { @@ -157,10 +146,9 @@ void main() { }), 200, headers: {'content-type': 'application/json'})); }); - grant.handleAuthorizationResponse({'code': 'auth code'}) - .then(expectAsync1((client) { - expect(client.credentials.accessToken, equals('access token')); - })); + expect(grant.handleAuthorizationResponse({'code': 'auth code'}) + .then((client) => client.credentials.accessToken), + completion(equals('access token'))); }); }); @@ -168,18 +156,13 @@ void main() { setUp(createGrant); test("can't be called before .getAuthorizationUrl", () { - expectFutureThrows( - grant.handleAuthorizationCode('auth code'), - (e) => e is StateError); + expect(grant.handleAuthorizationCode('auth code'), throwsStateError); }); test("can't be called twice", () { grant.getAuthorizationUrl(redirectUrl); - expectFutureThrows( - grant.handleAuthorizationCode('auth code'), - (e) => e is FormatException); - expectFutureThrows(grant.handleAuthorizationCode('auth code'), - (e) => e is StateError); + expect(grant.handleAuthorizationCode('auth code'), throwsFormatException); + expect(grant.handleAuthorizationCode('auth code'), throwsStateError); }); test('sends an authorization code request', () { diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart index 56dd78975..ad552c003 100644 --- a/pkgs/oauth2/test/client_test.dart +++ b/pkgs/oauth2/test/client_test.dart @@ -81,8 +81,7 @@ void main() { httpClient.expectRequest((request) { expect(request.method, equals('GET')); expect(request.url.toString(), equals(requestUri.toString())); - expect(request.headers['authorization'], - equals('Bearer access token')); + expect(request.headers['authorization'], equals('Bearer access token')); return new Future.value(new http.Response('good job', 200)); }); @@ -130,8 +129,7 @@ void main() { httpClient.expectRequest((request) { expect(request.method, equals('GET')); expect(request.url.toString(), equals(requestUri.toString())); - expect(request.headers['authorization'], - equals('Bearer access token')); + expect(request.headers['authorization'], equals('Bearer access token')); var authenticate = 'Bearer error="invalid_token", error_description=' '"Something is terribly wrong."'; diff --git a/pkgs/oauth2/test/credentials_test.dart b/pkgs/oauth2/test/credentials_test.dart index f3e303b9a..2ce9ec414 100644 --- a/pkgs/oauth2/test/credentials_test.dart +++ b/pkgs/oauth2/test/credentials_test.dart @@ -43,19 +43,17 @@ void main() { var credentials = new oauth2.Credentials( 'access token', null, tokenEndpoint); expect(credentials.canRefresh, false); - credentials.refresh('identifier', 'secret', httpClient: httpClient) - .catchError(expectAsync1((error) { - expect(error is StateError, isTrue); - })); + + expect(credentials.refresh('identifier', 'secret', httpClient: httpClient), + throwsStateError); }); test("can't refresh without a token endpoint", () { var credentials = new oauth2.Credentials('access token', 'refresh token'); expect(credentials.canRefresh, false); - credentials.refresh('identifier', 'secret', httpClient: httpClient) - .catchError(expectAsync1((error) { - expect(error is StateError, isTrue); - })); + + expect(credentials.refresh('identifier', 'secret', httpClient: httpClient), + throwsStateError); }); test("can refresh with a refresh token and a token endpoint", () { diff --git a/pkgs/oauth2/test/handle_access_token_response_test.dart b/pkgs/oauth2/test/handle_access_token_response_test.dart index c36286847..d97ef3860 100644 --- a/pkgs/oauth2/test/handle_access_token_response_test.dart +++ b/pkgs/oauth2/test/handle_access_token_response_test.dart @@ -38,8 +38,7 @@ void main() { }); test('with an unexpected code causes a FormatException', () { - expect(() => handleError(statusCode: 500), - throwsFormatException); + expect(() => handleError(statusCode: 500), throwsFormatException); }); test('with no content-type causes a FormatException', () { @@ -60,13 +59,11 @@ void main() { }); test('with invalid JSON causes a FormatException', () { - expect(() => handleError(body: 'not json'), - throwsFormatException); + expect(() => handleError(body: 'not json'), throwsFormatException); }); test('with a non-string error causes a FormatException', () { - expect(() => handleError(body: '{"error": 12}'), - throwsFormatException); + expect(() => handleError(body: '{"error": 12}'), throwsFormatException); }); test('with a non-string error_description causes a FormatException', () { From 54c66a751e41116b3d17153674feb158d0af61a8 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Mon, 17 Mar 2014 22:14:21 +0000 Subject: [PATCH 053/159] Make BaseRequest.contentType use null rather than -1 as a flag value. BUG= R=rnystrom@google.com Review URL: https://codereview.chromium.org//196423017 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@33781 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index c3dad6e00..61c4f52ef 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -9,6 +9,6 @@ description: > environment: sdk: '>=1.0.0 <2.0.0' dependencies: - http: '>=0.9.2 <0.10.0' + http: '>=0.9.2 <0.11.0' dev_dependencies: unittest: '>=0.9.0 <0.11.0' From f351897fea8cb9248403d971bab7051e5d4157ff Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Wed, 26 Mar 2014 22:18:10 +0000 Subject: [PATCH 054/159] Move the oauth2 library documentation to a README. BUG=17772 R=sethladd@google.com Review URL: https://codereview.chromium.org//213083003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@34454 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/CHANGELOG.md | 5 ++ pkgs/oauth2/README.md | 100 +++++++++++++++++++++++++++++++ pkgs/oauth2/lib/oauth2.dart | 113 ------------------------------------ pkgs/oauth2/pubspec.yaml | 2 +- 4 files changed, 106 insertions(+), 114 deletions(-) create mode 100644 pkgs/oauth2/CHANGELOG.md create mode 100644 pkgs/oauth2/README.md diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md new file mode 100644 index 000000000..d3deb954e --- /dev/null +++ b/pkgs/oauth2/CHANGELOG.md @@ -0,0 +1,5 @@ +# 0.9.2 + +* Expand the dependency on the HTTP package to include 0.10.x. + +* Add a README file. diff --git a/pkgs/oauth2/README.md b/pkgs/oauth2/README.md new file mode 100644 index 000000000..72aff2a8e --- /dev/null +++ b/pkgs/oauth2/README.md @@ -0,0 +1,100 @@ +A client library for authenticating with a remote service via OAuth2 on +behalf of a user, and making authorized HTTP requests with the user's OAuth2 +credentials. Currently this only works where `dart:io` is available. + +OAuth2 allows a client (the program using this library) to access and +manipulate a resource that's owned by a resource owner (the end user) and +lives on a remote server. The client directs the resource owner to an +authorization server (usually but not always the same as the server that +hosts the resource), where the resource owner tells the authorization server +to give the client an access token. This token serves as proof that the +client has permission to access resources on behalf of the resource owner. + +OAuth2 provides several different methods for the client to obtain +authorization. At the time of writing, this library only supports the +[AuthorizationCodeGrant][] method, but further methods may be added in the +future. The following example uses this method to authenticate, and assumes +that the library is being used by a server-side application. + +[AuthorizationCodeGrant]: https://api.dartlang.org/apidocs/channels/stable/#oauth2/oauth2.AuthorizationCodeGrant + +```dart +import 'dart:io' +import 'package:oauth2/oauth2.dart' as oauth2; + +// These URLs are endpoints that are provided by the authorization +// server. They're usually included in the server's documentation of its +// OAuth2 API. +final authorizationEndpoint = + Uri.parse("http://example.com/oauth2/authorization"); +final tokenEndpoint = + Uri.parse("http://example.com/oauth2/token"); + +// The authorization server will issue each client a separate client +// identifier and secret, which allows the server to tell which client +// is accessing it. Some servers may also have an anonymous +// identifier/secret pair that any client may use. +// +// Note that clients whose source code or binary executable is readily +// available may not be able to make sure the client secret is kept a +// secret. This is fine; OAuth2 servers generally won't rely on knowing +// with certainty that a client is who it claims to be. +final identifier = "my client identifier"; +final secret = "my client secret"; + +// This is a URL on your application's server. The authorization server +// will redirect the resource owner here once they've authorized the +// client. The redirection will include the authorization code in the +// query parameters. +final redirectUrl = Uri.parse("http://my-site.com/oauth2-redirect"); + +var credentialsFile = new File("~/.myapp/credentials.json"); +return credentialsFile.exists().then((exists) { + // If the OAuth2 credentials have already been saved from a previous + // run, we just want to reload them. + if (exists) { + return credentialsFile.readAsString().then((json) { + var credentials = new oauth2.Credentials.fromJson(json); + return new oauth2.Client(identifier, secret, credentials); + }); + } + + // If we don't have OAuth2 credentials yet, we need to get the + // resource owner to authorize us. We're assuming here that we're a + // command-line application. + var grant = new oauth2.AuthorizationCodeGrant( + identifier, secret, authorizationEndpoint, tokenEndpoint); + + // Redirect the resource owner to the authorization URL. This will be + // a URL on the authorization server (authorizationEndpoint with some + // additional query parameters). Once the resource owner has + // authorized, they'll be redirected to `redirectUrl` with an + // authorization code. + // + // `redirect` is an imaginary function that redirects the resource + // owner's browser. + return redirect(grant.getAuthorizationUrl(redirectUrl)).then((_) { + // Another imaginary function that listens for a request to + // `redirectUrl`. + return listen(redirectUrl); + }).then((request) { + // Once the user is redirected to `redirectUrl`, pass the query + // parameters to the AuthorizationCodeGrant. It will validate them + // and extract the authorization code to create a new Client. + return grant.handleAuthorizationResponse(request.uri.queryParameters); + }) +}).then((client) { + // Once you have a Client, you can use it just like any other HTTP + // client. + return client.read("http://example.com/protected-resources.txt") + .then((result) { + // Once we're done with the client, save the credentials file. This + // ensures that if the credentials were automatically refreshed + // while using the client, the new credentials are available for the + // next run of the program. + return credentialsFile.open(FileMode.WRITE).then((file) { + return file.writeString(client.credentials.toJson()); + }).then((file) => file.close()).then((_) => result); + }); +}).then(print); +``` diff --git a/pkgs/oauth2/lib/oauth2.dart b/pkgs/oauth2/lib/oauth2.dart index cca66abd6..cb3c59232 100644 --- a/pkgs/oauth2/lib/oauth2.dart +++ b/pkgs/oauth2/lib/oauth2.dart @@ -2,119 +2,6 @@ // 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. -/// A client library for authenticating with a remote service via OAuth2 on -/// behalf of a user, and making authorized HTTP requests with the user's OAuth2 -/// credentials. -/// -/// ## Installing ## -/// -/// Use [pub][] to install this package. Add the following to your -/// `pubspec.yaml` file. -/// -/// dependencies: -/// oauth2: any -/// -/// Then run `pub install`. -/// -/// For more information, see the -/// [oauth2 package on pub.dartlang.org][pkg]. -/// -/// OAuth2 allows a client (the program using this library) to access and -/// manipulate a resource that's owned by a resource owner (the end user) and -/// lives on a remote server. The client directs the resource owner to an -/// authorization server (usually but not always the same as the server that -/// hosts the resource), where the resource owner tells the authorization server -/// to give the client an access token. This token serves as proof that the -/// client has permission to access resources on behalf of the resource owner. -/// -/// OAuth2 provides several different methods for the client to obtain -/// authorization. At the time of writing, this library only supports the -/// [AuthorizationCodeGrant] method, but further methods may be added in the -/// future. The following example uses this method to authenticate, and assumes -/// that the library is being used by a server-side application. -/// -/// import 'dart:io' -/// import 'package:oauth2/oauth2.dart' as oauth2; -/// -/// // These URLs are endpoints that are provided by the authorization -/// // server. They're usually included in the server's documentation of its -/// // OAuth2 API. -/// final authorizationEndpoint = -/// Uri.parse("http://example.com/oauth2/authorization"); -/// final tokenEndpoint = -/// Uri.parse("http://example.com/oauth2/token"); -/// -/// // The authorization server will issue each client a separate client -/// // identifier and secret, which allows the server to tell which client -/// // is accessing it. Some servers may also have an anonymous -/// // identifier/secret pair that any client may use. -/// // -/// // Note that clients whose source code or binary executable is readily -/// // available may not be able to make sure the client secret is kept a -/// // secret. This is fine; OAuth2 servers generally won't rely on knowing -/// // with certainty that a client is who it claims to be. -/// final identifier = "my client identifier"; -/// final secret = "my client secret"; -/// -/// // This is a URL on your application's server. The authorization server -/// // will redirect the resource owner here once they've authorized the -/// // client. The redirection will include the authorization code in the -/// // query parameters. -/// final redirectUrl = Uri.parse( -/// "http://my-site.com/oauth2-redirect"); -/// -/// var credentialsFile = new File("~/.myapp/credentials.json"); -/// return credentialsFile.exists().then((exists) { -/// // If the OAuth2 credentials have already been saved from a previous -/// // run, we just want to reload them. -/// if (exists) { -/// return credentialsFile.readAsString().then((json) { -/// var credentials = new oauth2.Credentials.fromJson(json); -/// return new oauth2.Client(identifier, secret, credentials); -/// }); -/// } -/// -/// // If we don't have OAuth2 credentials yet, we need to get the -/// // resource owner to authorize us. We're assuming here that we're a -/// // command-line application. -/// var grant = new oauth2.AuthorizationCodeGrant( -/// identifier, secret, authorizationEndpoint, tokenEndpoint); -/// -/// // Redirect the resource owner to the authorization URL. This will be -/// // a URL on the authorization server (authorizationEndpoint with some -/// // additional query parameters). Once the resource owner has -/// // authorized, they'll be redirected to `redirectUrl` with an -/// // authorization code. -/// // -/// // `redirect` is an imaginary function that redirects the resource -/// // owner's browser. -/// return redirect(grant.getAuthorizationUrl(redirectUrl)).then((_) { -/// // Another imaginary function that listens for a request to -/// // `redirectUrl`. -/// return listen(redirectUrl); -/// }).then((request) { -/// // Once the user is redirected to `redirectUrl`, pass the query -/// // parameters to the AuthorizationCodeGrant. It will validate them -/// // and extract the authorization code to create a new Client. -/// return grant.handleAuthorizationResponse(request.uri.queryParameters); -/// }) -/// }).then((client) { -/// // Once you have a Client, you can use it just like any other HTTP -/// // client. -/// return client.read("http://example.com/protected-resources.txt") -/// .then((result) { -/// // Once we're done with the client, save the credentials file. This -/// // ensures that if the credentials were automatically refreshed -/// // while using the client, the new credentials are available for the -/// // next run of the program. -/// return credentialsFile.open(FileMode.WRITE).then((file) { -/// return file.writeString(client.credentials.toJson()); -/// }).then((file) => file.close()).then((_) => result); -/// }); -/// }).then(print); -/// -/// [pub]: http://pub.dartlang.org -/// [pkg]: http://pub.dartlang.org/packages/oauth2 library oauth2; export 'src/authorization_code_grant.dart'; diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index 61c4f52ef..ba9d309f4 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,5 @@ name: oauth2 -version: 0.9.2-dev +version: 0.9.2 author: Dart Team homepage: http://www.dartlang.org description: > From 07b8fd14ad2d18f3610783c2ccc59b484f06865b Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Thu, 3 Apr 2014 18:29:14 +0000 Subject: [PATCH 055/159] Rip out dart:io from pkg/http wherever possible. This uses the http_parser package instead of dart:io's ContentType, ByteConversionSink instead of ByteBuffer, and a new ClientException type instead of HttpException. This still uses dart:io for the default Client implementation and for [MultipartFile.fromPath]. R=rnystrom@google.com Review URL: https://codereview.chromium.org//216603010 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@34716 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/CHANGELOG.md | 4 ++++ .../lib/src/handle_access_token_response.dart | 16 ++++++---------- pkgs/oauth2/pubspec.yaml | 5 +++-- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md index d3deb954e..da65ce661 100644 --- a/pkgs/oauth2/CHANGELOG.md +++ b/pkgs/oauth2/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.9.3 + +* Update the `http` dependency. + # 0.9.2 * Expand the dependency on the HTTP package to include 0.10.x. diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart index 11a6fb9fe..576636265 100644 --- a/pkgs/oauth2/lib/src/handle_access_token_response.dart +++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart @@ -4,10 +4,10 @@ library handle_access_token_response; -import 'dart:io'; import 'dart:convert'; import 'package:http/http.dart' as http; +import 'package:http_parser/http_parser.dart'; import 'credentials.dart'; import 'authorization_exception.dart'; @@ -31,15 +31,13 @@ Credentials handleAccessTokenResponse( _validate(response, tokenEndpoint, condition, message); var contentType = response.headers['content-type']; - if (contentType != null) { - contentType = ContentType.parse(contentType); - } + if (contentType != null) contentType = new MediaType.parse(contentType); // The spec requires a content-type of application/json, but some endpoints // (e.g. Dropbox) serve it as text/javascript instead. validate(contentType != null && - (contentType.value == "application/json" || - contentType.value == "text/javascript"), + (contentType.mimeType == "application/json" || + contentType.mimeType == "text/javascript"), 'content-type was "$contentType", expected "application/json"'); var parameters; @@ -105,10 +103,8 @@ void _handleErrorResponse(http.Response response, Uri tokenEndpoint) { } var contentType = response.headers['content-type']; - if (contentType != null) { - contentType = ContentType.parse(contentType); - } - validate(contentType != null && contentType.value == "application/json", + if (contentType != null) contentType = new MediaType.parse(contentType); + validate(contentType != null && contentType.mimeType == "application/json", 'content-type was "$contentType", expected "application/json"'); var parameters; diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index ba9d309f4..2e61c5899 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,5 @@ name: oauth2 -version: 0.9.2 +version: 0.9.3-dev author: Dart Team homepage: http://www.dartlang.org description: > @@ -9,6 +9,7 @@ description: > environment: sdk: '>=1.0.0 <2.0.0' dependencies: - http: '>=0.9.2 <0.11.0' + http: '>=0.11.0-dev <0.12.0' + http_parser: '>=0.0.0 <0.1.0' dev_dependencies: unittest: '>=0.9.0 <0.11.0' From f3edda7bb18f935c3b94d6bf4fa82863d037d9a2 Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Mon, 5 May 2014 20:25:13 +0000 Subject: [PATCH 056/159] Mark oauth2 as running outside of dart:io contexts. R=rnystrom@google.com Review URL: https://codereview.chromium.org//269053003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@35763 260f80e4-7a28-3924-810f-c04153c831b5 --- pkgs/oauth2/CHANGELOG.md | 3 +++ pkgs/oauth2/README.md | 2 +- pkgs/oauth2/pubspec.yaml | 6 +++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md index da65ce661..fc869c940 100644 --- a/pkgs/oauth2/CHANGELOG.md +++ b/pkgs/oauth2/CHANGELOG.md @@ -2,6 +2,9 @@ * Update the `http` dependency. +* Since `http` 0.11.0 now works in non-`dart:io` contexts, `oauth2` does as + well. + # 0.9.2 * Expand the dependency on the HTTP package to include 0.10.x. diff --git a/pkgs/oauth2/README.md b/pkgs/oauth2/README.md index 72aff2a8e..52b724c9d 100644 --- a/pkgs/oauth2/README.md +++ b/pkgs/oauth2/README.md @@ -1,6 +1,6 @@ A client library for authenticating with a remote service via OAuth2 on behalf of a user, and making authorized HTTP requests with the user's OAuth2 -credentials. Currently this only works where `dart:io` is available. +credentials. OAuth2 allows a client (the program using this library) to access and manipulate a resource that's owned by a resource owner (the end user) and diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index 2e61c5899..7a1311e9d 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,15 +1,15 @@ name: oauth2 -version: 0.9.3-dev +version: 0.9.3 author: Dart Team homepage: http://www.dartlang.org description: > A client library for authenticating with a remote service via OAuth2 on behalf of a user, and making authorized HTTP requests with the user's - OAuth2 credentials. Currently only works with dart:io. + OAuth2 credentials. environment: sdk: '>=1.0.0 <2.0.0' dependencies: - http: '>=0.11.0-dev <0.12.0' + http: '>=0.11.0 <0.12.0' http_parser: '>=0.0.0 <0.1.0' dev_dependencies: unittest: '>=0.9.0 <0.11.0' From b70cc14f87d009eedd8a96d31fb167fd6c205f8a Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 17 Dec 2014 13:52:55 -0800 Subject: [PATCH 057/159] Add gitignore, status, and codereview files. --- pkgs/oauth2/.gitignore | 14 ++++++++++++++ pkgs/oauth2/.status | 3 +++ pkgs/oauth2/codereview.settings | 3 +++ 3 files changed, 20 insertions(+) create mode 100644 pkgs/oauth2/.gitignore create mode 100644 pkgs/oauth2/.status create mode 100644 pkgs/oauth2/codereview.settings diff --git a/pkgs/oauth2/.gitignore b/pkgs/oauth2/.gitignore new file mode 100644 index 000000000..388eff0ba --- /dev/null +++ b/pkgs/oauth2/.gitignore @@ -0,0 +1,14 @@ +# Don’t commit the following directories created by pub. +.buildlog +.pub/ +build/ +packages + +# Or the files created by dart2js. +*.dart.js +*.js_ +*.js.deps +*.js.map + +# Include when developing application packages. +pubspec.lock \ No newline at end of file diff --git a/pkgs/oauth2/.status b/pkgs/oauth2/.status new file mode 100644 index 000000000..e9f2b0049 --- /dev/null +++ b/pkgs/oauth2/.status @@ -0,0 +1,3 @@ +# Copyright (c) 2014, 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. diff --git a/pkgs/oauth2/codereview.settings b/pkgs/oauth2/codereview.settings new file mode 100644 index 000000000..0b7a5a8f9 --- /dev/null +++ b/pkgs/oauth2/codereview.settings @@ -0,0 +1,3 @@ +CODE_REVIEW_SERVER: http://codereview.chromium.org/ +VIEW_VC: https://github.com/dart-lang/oauth2/commit/ +CC_LIST: reviews@dartlang.org \ No newline at end of file From 29b3bb56f2221db1815400ae4979c413d9093624 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 17 Dec 2014 15:19:57 -0800 Subject: [PATCH 058/159] Update the pubspec's homepage link. --- pkgs/oauth2/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index 7a1311e9d..bedc1e323 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,7 +1,7 @@ name: oauth2 version: 0.9.3 author: Dart Team -homepage: http://www.dartlang.org +homepage: http://github.com/dart-lang/oauth2 description: > A client library for authenticating with a remote service via OAuth2 on behalf of a user, and making authorized HTTP requests with the user's From c16164dc6f9dfb77a9bc6e4391fc7a0bb639c402 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 16 Jul 2015 13:27:20 -0700 Subject: [PATCH 059/159] Upgrade to the new test runner. R=rnystrom@google.com Review URL: https://codereview.chromium.org//1230713003 . --- pkgs/oauth2/.gitignore | 1 + pkgs/oauth2/.status | 3 --- pkgs/oauth2/.test_config | 3 +++ pkgs/oauth2/pubspec.yaml | 4 ++-- pkgs/oauth2/test/authorization_code_grant_test.dart | 2 +- pkgs/oauth2/test/client_test.dart | 2 +- pkgs/oauth2/test/credentials_test.dart | 2 +- pkgs/oauth2/test/handle_access_token_response_test.dart | 2 +- pkgs/oauth2/test/utils.dart | 2 +- pkgs/oauth2/test/utils_test.dart | 2 +- 10 files changed, 12 insertions(+), 11 deletions(-) delete mode 100644 pkgs/oauth2/.status create mode 100644 pkgs/oauth2/.test_config diff --git a/pkgs/oauth2/.gitignore b/pkgs/oauth2/.gitignore index 388eff0ba..7dbf0350d 100644 --- a/pkgs/oauth2/.gitignore +++ b/pkgs/oauth2/.gitignore @@ -3,6 +3,7 @@ .pub/ build/ packages +.packages # Or the files created by dart2js. *.dart.js diff --git a/pkgs/oauth2/.status b/pkgs/oauth2/.status deleted file mode 100644 index e9f2b0049..000000000 --- a/pkgs/oauth2/.status +++ /dev/null @@ -1,3 +0,0 @@ -# Copyright (c) 2014, 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. diff --git a/pkgs/oauth2/.test_config b/pkgs/oauth2/.test_config new file mode 100644 index 000000000..412fc5c5c --- /dev/null +++ b/pkgs/oauth2/.test_config @@ -0,0 +1,3 @@ +{ + "test_package": true +} \ No newline at end of file diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index bedc1e323..31c0b5062 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,5 @@ name: oauth2 -version: 0.9.3 +version: 0.9.4-dev author: Dart Team homepage: http://github.com/dart-lang/oauth2 description: > @@ -12,4 +12,4 @@ dependencies: http: '>=0.11.0 <0.12.0' http_parser: '>=0.0.0 <0.1.0' dev_dependencies: - unittest: '>=0.9.0 <0.11.0' + test: '>=0.12.0 <0.13.0' diff --git a/pkgs/oauth2/test/authorization_code_grant_test.dart b/pkgs/oauth2/test/authorization_code_grant_test.dart index 2c3265e21..24ff59c16 100644 --- a/pkgs/oauth2/test/authorization_code_grant_test.dart +++ b/pkgs/oauth2/test/authorization_code_grant_test.dart @@ -7,7 +7,7 @@ library authorization_code_grant_test; import 'dart:async'; import 'dart:convert'; -import 'package:unittest/unittest.dart'; +import 'package:test/test.dart'; import 'package:http/http.dart' as http; import 'package:oauth2/oauth2.dart' as oauth2; diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart index ad552c003..11a3bf84a 100644 --- a/pkgs/oauth2/test/client_test.dart +++ b/pkgs/oauth2/test/client_test.dart @@ -9,7 +9,7 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:oauth2/oauth2.dart' as oauth2; -import 'package:unittest/unittest.dart'; +import 'package:test/test.dart'; import 'utils.dart'; diff --git a/pkgs/oauth2/test/credentials_test.dart b/pkgs/oauth2/test/credentials_test.dart index 2ce9ec414..94b5445ea 100644 --- a/pkgs/oauth2/test/credentials_test.dart +++ b/pkgs/oauth2/test/credentials_test.dart @@ -9,7 +9,7 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:oauth2/oauth2.dart' as oauth2; -import 'package:unittest/unittest.dart'; +import 'package:test/test.dart'; import 'utils.dart'; diff --git a/pkgs/oauth2/test/handle_access_token_response_test.dart b/pkgs/oauth2/test/handle_access_token_response_test.dart index d97ef3860..4d822626c 100644 --- a/pkgs/oauth2/test/handle_access_token_response_test.dart +++ b/pkgs/oauth2/test/handle_access_token_response_test.dart @@ -9,7 +9,7 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:oauth2/oauth2.dart' as oauth2; import 'package:oauth2/src/handle_access_token_response.dart'; -import 'package:unittest/unittest.dart'; +import 'package:test/test.dart'; import 'utils.dart'; diff --git a/pkgs/oauth2/test/utils.dart b/pkgs/oauth2/test/utils.dart index fddcd372e..bee6bf3d2 100644 --- a/pkgs/oauth2/test/utils.dart +++ b/pkgs/oauth2/test/utils.dart @@ -10,7 +10,7 @@ import 'dart:collection' show Queue; import 'package:http/http.dart' as http; import 'package:http/testing.dart'; import 'package:oauth2/oauth2.dart' as oauth2; -import 'package:unittest/unittest.dart'; +import 'package:test/test.dart'; class ExpectClient extends MockClient { final Queue _handlers; diff --git a/pkgs/oauth2/test/utils_test.dart b/pkgs/oauth2/test/utils_test.dart index 145e99bb1..0dd0348ab 100644 --- a/pkgs/oauth2/test/utils_test.dart +++ b/pkgs/oauth2/test/utils_test.dart @@ -5,7 +5,7 @@ library utils_test; import 'package:oauth2/src/utils.dart'; -import 'package:unittest/unittest.dart'; +import 'package:test/test.dart'; void main() { group('AuthenticateHeader', () { From f88677d3ddbf197823a91e07d1fb1f8efdb86c2e Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Mon, 24 Aug 2015 16:58:43 -0700 Subject: [PATCH 060/159] Async-ify. R=rnystrom@google.com Review URL: https://codereview.chromium.org//1308163004 . --- pkgs/oauth2/README.md | 87 +++++++------- .../lib/src/authorization_code_grant.dart | 111 +++++++++--------- pkgs/oauth2/lib/src/client.dart | 83 ++++++------- pkgs/oauth2/lib/src/credentials.dart | 67 +++++------ pkgs/oauth2/lib/src/utils.dart | 4 - pkgs/oauth2/pubspec.yaml | 2 +- pkgs/oauth2/test/client_test.dart | 41 +++---- pkgs/oauth2/test/credentials_test.dart | 22 ++-- 8 files changed, 200 insertions(+), 217 deletions(-) diff --git a/pkgs/oauth2/README.md b/pkgs/oauth2/README.md index 52b724c9d..23be5ff20 100644 --- a/pkgs/oauth2/README.md +++ b/pkgs/oauth2/README.md @@ -48,53 +48,60 @@ final secret = "my client secret"; // query parameters. final redirectUrl = Uri.parse("http://my-site.com/oauth2-redirect"); -var credentialsFile = new File("~/.myapp/credentials.json"); -return credentialsFile.exists().then((exists) { - // If the OAuth2 credentials have already been saved from a previous - // run, we just want to reload them. +/// A file in which the users credentials are stored persistently. If the server +/// issues a refresh token allowing the client to refresh outdated credentials, +/// these may be valid indefinitely, meaning the user never has to +/// re-authenticate. +final credentialsFile = new File("~/.myapp/credentials.json"); + +/// Either load an OAuth2 client from saved credentials or authenticate a new +/// one. +Future getClient() async { + var exists = await credentialsFile.exists(); + + // If the OAuth2 credentials have already been saved from a previous run, we + // just want to reload them. if (exists) { - return credentialsFile.readAsString().then((json) { - var credentials = new oauth2.Credentials.fromJson(json); - return new oauth2.Client(identifier, secret, credentials); - }); + var credentials = new oauth2.Credentials.fromJson( + await credentialsFile.readAsString()); + return new oauth2.Client(identifier, secret, credentials); } - // If we don't have OAuth2 credentials yet, we need to get the - // resource owner to authorize us. We're assuming here that we're a - // command-line application. + // If we don't have OAuth2 credentials yet, we need to get the resource owner + // to authorize us. We're assuming here that we're a command-line application. var grant = new oauth2.AuthorizationCodeGrant( identifier, secret, authorizationEndpoint, tokenEndpoint); - // Redirect the resource owner to the authorization URL. This will be - // a URL on the authorization server (authorizationEndpoint with some - // additional query parameters). Once the resource owner has - // authorized, they'll be redirected to `redirectUrl` with an - // authorization code. + // Redirect the resource owner to the authorization URL. This will be a URL on + // the authorization server (authorizationEndpoint with some additional query + // parameters). Once the resource owner has authorized, they'll be redirected + // to `redirectUrl` with an authorization code. // // `redirect` is an imaginary function that redirects the resource // owner's browser. - return redirect(grant.getAuthorizationUrl(redirectUrl)).then((_) { - // Another imaginary function that listens for a request to - // `redirectUrl`. - return listen(redirectUrl); - }).then((request) { - // Once the user is redirected to `redirectUrl`, pass the query - // parameters to the AuthorizationCodeGrant. It will validate them - // and extract the authorization code to create a new Client. - return grant.handleAuthorizationResponse(request.uri.queryParameters); - }) -}).then((client) { - // Once you have a Client, you can use it just like any other HTTP - // client. - return client.read("http://example.com/protected-resources.txt") - .then((result) { - // Once we're done with the client, save the credentials file. This - // ensures that if the credentials were automatically refreshed - // while using the client, the new credentials are available for the - // next run of the program. - return credentialsFile.open(FileMode.WRITE).then((file) { - return file.writeString(client.credentials.toJson()); - }).then((file) => file.close()).then((_) => result); - }); -}).then(print); + await redirect(grant.getAuthorizationUrl(redirectUrl)); + + // Another imaginary function that listens for a request to `redirectUrl`. + var request = await listen(redirectUrl); + + // Once the user is redirected to `redirectUrl`, pass the query parameters to + // the AuthorizationCodeGrant. It will validate them and extract the + // authorization code to create a new Client. + return await grant.handleAuthorizationResponse(request.uri.queryParameters); +} + +main() async { + var client = await loadClient(); + + // Once you have a Client, you can use it just like any other HTTP client. + var result = client.read("http://example.com/protected-resources.txt"); + + // Once we're done with the client, save the credentials file. This ensures + // that if the credentials were automatically refreshed while using the + // client, the new credentials are available for the next run of the + // program. + await credentialsFile.writeAsString(client.credentials.toJson()); + + print(result); +} ``` diff --git a/pkgs/oauth2/lib/src/authorization_code_grant.dart b/pkgs/oauth2/lib/src/authorization_code_grant.dart index c879e8ca1..9ec4a274d 100644 --- a/pkgs/oauth2/lib/src/authorization_code_grant.dart +++ b/pkgs/oauth2/lib/src/authorization_code_grant.dart @@ -96,12 +96,12 @@ class AuthorizationCodeGrant { /// [httpClient] is used for all HTTP requests made by this grant, as well as /// those of the [Client] is constructs. AuthorizationCodeGrant( - this.identifier, - this.secret, - this.authorizationEndpoint, - this.tokenEndpoint, - {http.Client httpClient}) - : _httpClient = httpClient == null ? new http.Client() : httpClient; + this.identifier, + this.secret, + this.authorizationEndpoint, + this.tokenEndpoint, + {http.Client httpClient}) + : _httpClient = httpClient == null ? new http.Client() : httpClient; /// Returns the URL to which the resource owner should be redirected to /// authorize this client. The resource owner will then be redirected to @@ -157,42 +157,41 @@ class AuthorizationCodeGrant { /// [FormatError] if the `state` parameter doesn't match the original value. /// /// Throws [AuthorizationException] if the authorization fails. - Future handleAuthorizationResponse(Map parameters) { - return async.then((_) { - if (_state == _INITIAL_STATE) { - throw new StateError( - 'The authorization URL has not yet been generated.'); - } else if (_state == _FINISHED_STATE) { - throw new StateError( - 'The authorization code has already been received.'); - } - _state = _FINISHED_STATE; - - if (_stateString != null) { - if (!parameters.containsKey('state')) { - throw new FormatException('Invalid OAuth response for ' - '"$authorizationEndpoint": parameter "state" expected to be ' - '"$_stateString", was missing.'); - } else if (parameters['state'] != _stateString) { - throw new FormatException('Invalid OAuth response for ' - '"$authorizationEndpoint": parameter "state" expected to be ' - '"$_stateString", was "${parameters['state']}".'); - } - } + Future handleAuthorizationResponse(Map parameters) + async { + if (_state == _INITIAL_STATE) { + throw new StateError( + 'The authorization URL has not yet been generated.'); + } else if (_state == _FINISHED_STATE) { + throw new StateError( + 'The authorization code has already been received.'); + } + _state = _FINISHED_STATE; - if (parameters.containsKey('error')) { - var description = parameters['error_description']; - var uriString = parameters['error_uri']; - var uri = uriString == null ? null : Uri.parse(uriString); - throw new AuthorizationException(parameters['error'], description, uri); - } else if (!parameters.containsKey('code')) { + if (_stateString != null) { + if (!parameters.containsKey('state')) { throw new FormatException('Invalid OAuth response for ' - '"$authorizationEndpoint": did not contain required parameter ' - '"code".'); + '"$authorizationEndpoint": parameter "state" expected to be ' + '"$_stateString", was missing.'); + } else if (parameters['state'] != _stateString) { + throw new FormatException('Invalid OAuth response for ' + '"$authorizationEndpoint": parameter "state" expected to be ' + '"$_stateString", was "${parameters['state']}".'); } + } - return _handleAuthorizationCode(parameters['code']); - }); + if (parameters.containsKey('error')) { + var description = parameters['error_description']; + var uriString = parameters['error_uri']; + var uri = uriString == null ? null : Uri.parse(uriString); + throw new AuthorizationException(parameters['error'], description, uri); + } else if (!parameters.containsKey('code')) { + throw new FormatException('Invalid OAuth response for ' + '"$authorizationEndpoint": did not contain required parameter ' + '"code".'); + } + + return await _handleAuthorizationCode(parameters['code']); } /// Processes an authorization code directly. Usually @@ -209,26 +208,24 @@ class AuthorizationCodeGrant { /// responses while retrieving credentials. /// /// Throws [AuthorizationException] if the authorization fails. - Future handleAuthorizationCode(String authorizationCode) { - return async.then((_) { - if (_state == _INITIAL_STATE) { - throw new StateError( - 'The authorization URL has not yet been generated.'); - } else if (_state == _FINISHED_STATE) { - throw new StateError( - 'The authorization code has already been received.'); - } - _state = _FINISHED_STATE; + Future handleAuthorizationCode(String authorizationCode) async { + if (_state == _INITIAL_STATE) { + throw new StateError( + 'The authorization URL has not yet been generated.'); + } else if (_state == _FINISHED_STATE) { + throw new StateError( + 'The authorization code has already been received.'); + } + _state = _FINISHED_STATE; - return _handleAuthorizationCode(authorizationCode); - }); + return await _handleAuthorizationCode(authorizationCode); } /// This works just like [handleAuthorizationCode], except it doesn't validate /// the state beforehand. - Future _handleAuthorizationCode(String authorizationCode) { + Future _handleAuthorizationCode(String authorizationCode) async { var startTime = new DateTime.now(); - return _httpClient.post(this.tokenEndpoint, body: { + var response = await _httpClient.post(this.tokenEndpoint, body: { "grant_type": "authorization_code", "code": authorizationCode, "redirect_uri": this._redirectEndpoint.toString(), @@ -237,12 +234,12 @@ class AuthorizationCodeGrant { // it be configurable? "client_id": this.identifier, "client_secret": this.secret - }).then((response) { - var credentials = handleAccessTokenResponse( - response, tokenEndpoint, startTime, _scopes); - return new Client( - this.identifier, this.secret, credentials, httpClient: _httpClient); }); + + var credentials = handleAccessTokenResponse( + response, tokenEndpoint, startTime, _scopes); + return new Client( + this.identifier, this.secret, credentials, httpClient: _httpClient); } /// Closes the grant and frees its resources. diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart index d473be19a..60495a0ab 100644 --- a/pkgs/oauth2/lib/src/client.dart +++ b/pkgs/oauth2/lib/src/client.dart @@ -80,37 +80,34 @@ class Client extends http.BaseClient { /// Sends an HTTP request with OAuth2 authorization credentials attached. This /// will also automatically refresh this client's [Credentials] before sending /// the request if necessary. - Future send(http.BaseRequest request) { - return async.then((_) { - if (!credentials.isExpired) return new Future.value(); + Future send(http.BaseRequest request) async { + if (credentials.isExpired) { if (!credentials.canRefresh) throw new ExpirationException(credentials); - return refreshCredentials(); - }).then((_) { - request.headers['authorization'] = "Bearer ${credentials.accessToken}"; - return _httpClient.send(request); - }).then((response) { - if (response.statusCode != 401 || - !response.headers.containsKey('www-authenticate')) { - return response; - } - - var authenticate; - try { - authenticate = new AuthenticateHeader.parse( - response.headers['www-authenticate']); - } on FormatException catch (e) { - return response; - } - - if (authenticate.scheme != 'bearer') return response; - - var params = authenticate.parameters; - if (!params.containsKey('error')) return response; - - throw new AuthorizationException( - params['error'], params['error_description'], - params['error_uri'] == null ? null : Uri.parse(params['error_uri'])); - }); + await refreshCredentials(); + } + + request.headers['authorization'] = "Bearer ${credentials.accessToken}"; + var response = await _httpClient.send(request); + + if (response.statusCode != 401) return response; + if (!response.headers.containsKey('www-authenticate')) return response; + + var authenticate; + try { + authenticate = new AuthenticateHeader.parse( + response.headers['www-authenticate']); + } on FormatException catch (e) { + return response; + } + + if (authenticate.scheme != 'bearer') return response; + + var params = authenticate.parameters; + if (!params.containsKey('error')) return response; + + throw new AuthorizationException( + params['error'], params['error_description'], + params['error_uri'] == null ? null : Uri.parse(params['error_uri'])); } /// Explicitly refreshes this client's credentials. Returns this client. @@ -122,20 +119,18 @@ class Client extends http.BaseClient { /// You may request different scopes than the default by passing in /// [newScopes]. These must be a subset of the scopes in the /// [Credentials.scopes] field of [Client.credentials]. - Future refreshCredentials([List newScopes]) { - return async.then((_) { - if (!credentials.canRefresh) { - var prefix = "OAuth credentials"; - if (credentials.isExpired) prefix = "$prefix have expired and"; - throw new StateError("$prefix can't be refreshed."); - } - - return credentials.refresh(identifier, secret, - newScopes: newScopes, httpClient: _httpClient); - }).then((credentials) { - _credentials = credentials; - return this; - }); + Future refreshCredentials([List newScopes]) async { + if (!credentials.canRefresh) { + var prefix = "OAuth credentials"; + if (credentials.isExpired) prefix = "$prefix have expired and"; + throw new StateError("$prefix can't be refreshed."); + } + + _credentials = await credentials.refresh( + identifier, secret, + newScopes: newScopes, httpClient: _httpClient); + + return this; } /// Closes this client and its underlying HTTP client. diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index ce4e06454..88b1f5d11 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -146,47 +146,44 @@ class Credentials { String identifier, String secret, {List newScopes, - http.Client httpClient}) { + http.Client httpClient}) async { var scopes = this.scopes; if (newScopes != null) scopes = newScopes; if (scopes == null) scopes = []; if (httpClient == null) httpClient = new http.Client(); var startTime = new DateTime.now(); - return async.then((_) { - 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."); - } - - var fields = { - "grant_type": "refresh_token", - "refresh_token": refreshToken, - // TODO(nweiz): the spec recommends that HTTP basic auth be used in - // preference to form parameters, but Google doesn't support that. - // Should it be configurable? - "client_id": identifier, - "client_secret": secret - }; - if (!scopes.isEmpty) fields["scope"] = scopes.join(' '); - - return httpClient.post(tokenEndpoint, body: fields); - }).then((response) { - return handleAccessTokenResponse( + 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."); + } + + var fields = { + "grant_type": "refresh_token", + "refresh_token": refreshToken, + // TODO(nweiz): the spec recommends that HTTP basic auth be used in + // preference to form parameters, but Google doesn't support that. + // Should it be configurable? + "client_id": identifier, + "client_secret": secret + }; + if (!scopes.isEmpty) fields["scope"] = scopes.join(' '); + + var response = await httpClient.post(tokenEndpoint, body: fields); + var credentials = await handleAccessTokenResponse( response, tokenEndpoint, startTime, scopes); - }).then((credentials) { - // The authorization server may issue a new refresh token. If it doesn't, - // we should re-use the one we already have. - if (credentials.refreshToken != null) return credentials; - return new Credentials( - credentials.accessToken, - this.refreshToken, - credentials.tokenEndpoint, - credentials.scopes, - credentials.expiration); - }); + + // The authorization server may issue a new refresh token. If it doesn't, + // we should re-use the one we already have. + if (credentials.refreshToken != null) return credentials; + return new Credentials( + credentials.accessToken, + this.refreshToken, + credentials.tokenEndpoint, + credentials.scopes, + credentials.expiration); } } diff --git a/pkgs/oauth2/lib/src/utils.dart b/pkgs/oauth2/lib/src/utils.dart index 12a429e19..b29262580 100644 --- a/pkgs/oauth2/lib/src/utils.dart +++ b/pkgs/oauth2/lib/src/utils.dart @@ -105,7 +105,3 @@ class AuthenticateHeader { return new AuthenticateHeader(scheme, parameters); } } - -/// Returns a [Future] that asynchronously completes to `null`. -Future get async => new Future.delayed(const Duration(milliseconds: 0), - () => null); diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index 31c0b5062..e40265aa5 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -7,7 +7,7 @@ description: > behalf of a user, and making authorized HTTP requests with the user's OAuth2 credentials. environment: - sdk: '>=1.0.0 <2.0.0' + sdk: '>=1.9.0 <2.0.0' dependencies: http: '>=0.11.0 <0.12.0' http_parser: '>=0.0.0 <0.1.0' diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart index 11a3bf84a..7115095c0 100644 --- a/pkgs/oauth2/test/client_test.dart +++ b/pkgs/oauth2/test/client_test.dart @@ -39,7 +39,7 @@ void main() { }); test("that can be refreshed refreshes the credentials and sends the " - "request", () { + "request", () async { var expiration = new DateTime.now().subtract(new Duration(hours: 1)); var credentials = new oauth2.Credentials( 'access token', 'refresh token', tokenEndpoint, [], expiration); @@ -64,9 +64,8 @@ void main() { return new Future.value(new http.Response('good job', 200)); }); - expect(client.read(requestUri).then((_) { - expect(client.credentials.accessToken, equals('new access token')); - }), completes); + await client.read(requestUri); + expect(client.credentials.accessToken, equals('new access token')); }); }); @@ -89,7 +88,7 @@ void main() { expect(client.read(requestUri), completion(equals('good job'))); }); - test("can manually refresh the credentials", () { + test("can manually refresh the credentials", () async { var credentials = new oauth2.Credentials( 'access token', 'refresh token', tokenEndpoint); var client = new oauth2.Client('identifier', 'secret', credentials, @@ -104,9 +103,8 @@ void main() { }), 200, headers: {'content-type': 'application/json'})); }); - expect(client.refreshCredentials().then((_) { - expect(client.credentials.accessToken, equals('new access token')); - }), completes); + await client.refreshCredentials(); + expect(client.credentials.accessToken, equals('new access token')); }); test("without a refresh token can't manually refresh the credentials", () { @@ -141,7 +139,7 @@ void main() { throwsA(new isInstanceOf())); }); - test('passes through a 401 response without www-authenticate', () { + test('passes through a 401 response without www-authenticate', () async { var credentials = new oauth2.Credentials('access token'); var client = new oauth2.Client('identifier', 'secret', credentials, httpClient: httpClient); @@ -155,12 +153,11 @@ void main() { return new Future.value(new http.Response('bad job', 401)); }); - expect( - client.get(requestUri).then((response) => response.statusCode), - completion(equals(401))); + expect((await client.get(requestUri)).statusCode, equals(401)); }); - test('passes through a 401 response with invalid www-authenticate', () { + test('passes through a 401 response with invalid www-authenticate', + () async { var credentials = new oauth2.Credentials('access token'); var client = new oauth2.Client('identifier', 'secret', credentials, httpClient: httpClient); @@ -177,12 +174,11 @@ void main() { headers: {'www-authenticate': authenticate})); }); - expect( - client.get(requestUri).then((response) => response.statusCode), - completion(equals(401))); + expect((await client.get(requestUri)).statusCode, equals(401)); }); - test('passes through a 401 response with non-bearer www-authenticate', () { + test('passes through a 401 response with non-bearer www-authenticate', + () async { var credentials = new oauth2.Credentials('access token'); var client = new oauth2.Client('identifier', 'secret', credentials, httpClient: httpClient); @@ -197,12 +193,11 @@ void main() { headers: {'www-authenticate': 'Digest'})); }); - expect( - client.get(requestUri).then((response) => response.statusCode), - completion(equals(401))); + expect((await client.get(requestUri)).statusCode, equals(401)); }); - test('passes through a 401 response with non-OAuth2 www-authenticate', () { + test('passes through a 401 response with non-OAuth2 www-authenticate', + () async { var credentials = new oauth2.Credentials('access token'); var client = new oauth2.Client('identifier', 'secret', credentials, httpClient: httpClient); @@ -217,9 +212,7 @@ void main() { headers: {'www-authenticate': 'Bearer'})); }); - expect( - client.get(requestUri).then((response) => response.statusCode), - completion(equals(401))); + expect((await client.get(requestUri)).statusCode, equals(401)); }); }); } diff --git a/pkgs/oauth2/test/credentials_test.dart b/pkgs/oauth2/test/credentials_test.dart index 94b5445ea..3bd44d301 100644 --- a/pkgs/oauth2/test/credentials_test.dart +++ b/pkgs/oauth2/test/credentials_test.dart @@ -56,7 +56,7 @@ void main() { throwsStateError); }); - test("can refresh with a refresh token and a token endpoint", () { + test("can refresh with a refresh token and a token endpoint", () async { var credentials = new oauth2.Credentials( 'access token', 'refresh token', tokenEndpoint, ['scope1', 'scope2']); expect(credentials.canRefresh, true); @@ -80,14 +80,13 @@ void main() { }); - expect(credentials.refresh('identifier', 'secret', httpClient: httpClient) - .then((credentials) { - expect(credentials.accessToken, equals('new access token')); - expect(credentials.refreshToken, equals('new refresh token')); - }), completes); + credentials = await credentials.refresh('identifier', 'secret', + httpClient: httpClient); + expect(credentials.accessToken, equals('new access token')); + expect(credentials.refreshToken, equals('new refresh token')); }); - test("uses the old refresh token if a new one isn't provided", () { + test("uses the old refresh token if a new one isn't provided", () async { var credentials = new oauth2.Credentials( 'access token', 'refresh token', tokenEndpoint); expect(credentials.canRefresh, true); @@ -109,11 +108,10 @@ void main() { }); - expect(credentials.refresh('identifier', 'secret', httpClient: httpClient) - .then((credentials) { - expect(credentials.accessToken, equals('new access token')); - expect(credentials.refreshToken, equals('refresh token')); - }), completes); + credentials = await credentials.refresh('identifier', 'secret', + httpClient: httpClient); + expect(credentials.accessToken, equals('new access token')); + expect(credentials.refreshToken, equals('refresh token')); }); group("fromJson", () { From 4ecf7876c9fe84c5a9af6db72167314443c8028a Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Tue, 25 Aug 2015 15:04:18 -0700 Subject: [PATCH 061/159] Modernize the style. R=rnystrom@google.com Review URL: https://codereview.chromium.org//1311323002 . --- .../lib/src/authorization_code_grant.dart | 124 +++++++++++------- .../lib/src/authorization_exception.dart | 16 ++- pkgs/oauth2/lib/src/client.dart | 48 ++++--- pkgs/oauth2/lib/src/credentials.dart | 55 +++++--- pkgs/oauth2/lib/src/expiration_exception.dart | 4 +- .../lib/src/handle_access_token_response.dart | 31 +++-- pkgs/oauth2/lib/src/utils.dart | 49 +------ .../test/authorization_code_grant_test.dart | 34 ++--- pkgs/oauth2/test/client_test.dart | 17 +-- pkgs/oauth2/test/credentials_test.dart | 5 +- pkgs/oauth2/test/utils.dart | 2 - pkgs/oauth2/test/utils_test.dart | 2 - 12 files changed, 187 insertions(+), 200 deletions(-) diff --git a/pkgs/oauth2/lib/src/authorization_code_grant.dart b/pkgs/oauth2/lib/src/authorization_code_grant.dart index 9ec4a274d..e3bb64538 100644 --- a/pkgs/oauth2/lib/src/authorization_code_grant.dart +++ b/pkgs/oauth2/lib/src/authorization_code_grant.dart @@ -2,7 +2,7 @@ // 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 authorization_code_grant; +library oauth2.authorization_code_grant; import 'dart:async'; @@ -13,8 +13,9 @@ import 'authorization_exception.dart'; import 'handle_access_token_response.dart'; import 'utils.dart'; -/// A class for obtaining credentials via an [authorization code grant][]. This -/// method of authorization involves sending the resource owner to the +/// A class for obtaining credentials via an [authorization code grant][]. +/// +/// This method of authorization involves sending the resource owner to the /// authorization server where they will authorize the client. They're then /// redirected back to your server, along with an authorization code. This is /// used to obtain [Credentials] and create a fully-authorized [Client]. @@ -27,32 +28,22 @@ import 'utils.dart'; /// /// [authorization code grant]: http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-4.1 class AuthorizationCodeGrant { - /// An enum value for [_state] indicating that [getAuthorizationUrl] has not - /// yet been called for this grant. - static const _INITIAL_STATE = 0; - - // An enum value for [_state] indicating that [getAuthorizationUrl] has been - // called but neither [handleAuthorizationResponse] nor - // [handleAuthorizationCode] has been called. - static const _AWAITING_RESPONSE_STATE = 1; - - // An enum value for [_state] indicating that [getAuthorizationUrl] and either - // [handleAuthorizationResponse] or [handleAuthorizationCode] have been - // called. - static const _FINISHED_STATE = 2; - - /// The client identifier for this client. The authorization server will issue - /// each client a separate client identifier and secret, which allows the - /// server to tell which client is accessing it. Some servers may also have an - /// anonymous identifier/secret pair that any client may use. + /// The client identifier for this client. + /// + /// The authorization server will issue each client a separate client + /// identifier and secret, which allows the server to tell which client is + /// accessing it. Some servers may also have an anonymous identifier/secret + /// pair that any client may use. /// /// This is usually global to the program using this library. final String identifier; - /// The client secret for this client. The authorization server will issue - /// each client a separate client identifier and secret, which allows the - /// server to tell which client is accessing it. Some servers may also have an - /// anonymous identifier/secret pair that any client may use. + /// The client secret for this client. + /// + /// The authorization server will issue each client a separate client + /// identifier and secret, which allows the server to tell which client is + /// accessing it. Some servers may also have an anonymous identifier/secret + /// pair that any client may use. /// /// This is usually global to the program using this library. /// @@ -64,13 +55,17 @@ class AuthorizationCodeGrant { /// A URL provided by the authorization server that serves as the base for the /// URL that the resource owner will be redirected to to authorize this - /// client. This will usually be listed in the authorization server's - /// OAuth2 API documentation. + /// client. + /// + /// This will usually be listed in the authorization server's OAuth2 API + /// documentation. final Uri authorizationEndpoint; /// A URL provided by the authorization server that this library uses to - /// obtain long-lasting credentials. This will usually be listed in the - /// authorization server's OAuth2 API documentation. + /// obtain long-lasting credentials. + /// + /// This will usually be listed in the authorization server's OAuth2 API + /// documentation. final Uri tokenEndpoint; /// The HTTP client used to make HTTP requests. @@ -87,9 +82,8 @@ class AuthorizationCodeGrant { /// included in the response query parameters. String _stateString; - /// The current state of the grant object. One of [_INITIAL_STATE], - /// [_AWAITING_RESPONSE_STATE], or [_FINISHED_STATE]. - int _state = _INITIAL_STATE; + /// The current state of the grant object. + _State _state = _State.initial; /// Creates a new grant. /// @@ -104,9 +98,11 @@ class AuthorizationCodeGrant { : _httpClient = httpClient == null ? new http.Client() : httpClient; /// Returns the URL to which the resource owner should be redirected to - /// authorize this client. The resource owner will then be redirected to - /// [redirect], which should point to a server controlled by the client. This - /// redirect will have additional query parameters that should be passed to + /// authorize this client. + /// + /// The resource owner will then be redirected to [redirect], which should + /// point to a server controlled by the client. This redirect will have + /// additional query parameters that should be passed to /// [handleAuthorizationResponse]. /// /// The specific permissions being requested from the authorization server may @@ -122,10 +118,10 @@ class AuthorizationCodeGrant { /// It is a [StateError] to call this more than once. Uri getAuthorizationUrl(Uri redirect, {List scopes: const [], String state}) { - if (_state != _INITIAL_STATE) { + if (_state != _State.initial) { throw new StateError('The authorization URL has already been generated.'); } - _state = _AWAITING_RESPONSE_STATE; + _state = _State.awaitingResponse; this._redirectEndpoint = redirect; this._scopes = scopes; @@ -143,9 +139,11 @@ class AuthorizationCodeGrant { } /// Processes the query parameters added to a redirect from the authorization - /// server. Note that this "response" is not an HTTP response, but rather the - /// data passed to a server controlled by the client as query parameters on - /// the redirect URL. + /// server. + /// + /// Note that this "response" is not an HTTP response, but rather the data + /// passed to a server controlled by the client as query parameters on the + /// redirect URL. /// /// It is a [StateError] to call this more than once, to call it before /// [getAuthorizationUrl] is called, or to call it after @@ -159,14 +157,14 @@ class AuthorizationCodeGrant { /// Throws [AuthorizationException] if the authorization fails. Future handleAuthorizationResponse(Map parameters) async { - if (_state == _INITIAL_STATE) { + if (_state == _State.initial) { throw new StateError( 'The authorization URL has not yet been generated.'); - } else if (_state == _FINISHED_STATE) { + } else if (_state == _State.finished) { throw new StateError( 'The authorization code has already been received.'); } - _state = _FINISHED_STATE; + _state = _State.finished; if (_stateString != null) { if (!parameters.containsKey('state')) { @@ -194,11 +192,12 @@ class AuthorizationCodeGrant { return await _handleAuthorizationCode(parameters['code']); } - /// Processes an authorization code directly. Usually - /// [handleAuthorizationResponse] is preferable to this method, since it - /// validates all of the query parameters. However, some authorization servers - /// allow the user to copy and paste an authorization code into a command-line - /// application, in which case this method must be used. + /// Processes an authorization code directly. + /// + /// Usually [handleAuthorizationResponse] is preferable to this method, since + /// it validates all of the query parameters. However, some authorization + /// servers allow the user to copy and paste an authorization code into a + /// command-line application, in which case this method must be used. /// /// It is a [StateError] to call this more than once, to call it before /// [getAuthorizationUrl] is called, or to call it after @@ -209,14 +208,14 @@ class AuthorizationCodeGrant { /// /// Throws [AuthorizationException] if the authorization fails. Future handleAuthorizationCode(String authorizationCode) async { - if (_state == _INITIAL_STATE) { + if (_state == _State.initial) { throw new StateError( 'The authorization URL has not yet been generated.'); - } else if (_state == _FINISHED_STATE) { + } else if (_state == _State.finished) { throw new StateError( 'The authorization code has already been received.'); } - _state = _FINISHED_STATE; + _state = _State.finished; return await _handleAuthorizationCode(authorizationCode); } @@ -252,3 +251,26 @@ class AuthorizationCodeGrant { _httpClient = null; } } + +/// States that [AuthorizationCodeGrant] can be in. +class _State { + /// [AuthorizationCodeGrant.getAuthorizationUrl] has not yet been called for + /// this grant. + static const initial = const _State("initial"); + + // [AuthorizationCodeGrant.getAuthorizationUrl] has been called but neither + // [AuthorizationCodeGrant.handleAuthorizationResponse] nor + // [AuthorizationCodeGrant.handleAuthorizationCode] has been called. + static const awaitingResponse = const _State("awaiting response"); + + // [AuthorizationCodeGrant.getAuthorizationUrl] and either + // [AuthorizationCodeGrant.handleAuthorizationResponse] or + // [AuthorizationCodeGrant.handleAuthorizationCode] have been called. + static const finished = const _State("finished"); + + final String _name; + + const _State(this._name); + + String toString() => _name; +} diff --git a/pkgs/oauth2/lib/src/authorization_exception.dart b/pkgs/oauth2/lib/src/authorization_exception.dart index 2a19cd11c..838d0011f 100644 --- a/pkgs/oauth2/lib/src/authorization_exception.dart +++ b/pkgs/oauth2/lib/src/authorization_exception.dart @@ -2,20 +2,26 @@ // 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 authorization_exception; +library oauth2.authorization_exception; /// An exception raised when OAuth2 authorization fails. class AuthorizationException implements Exception { - /// The name of the error. Possible names are enumerated in [the spec][]. + /// The name of the error. + /// + /// Possible names are enumerated in [the spec][]. /// /// [the spec]: http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-5.2 final String error; - /// The description of the error, provided by the server. Defaults to null. + /// The description of the error, provided by the server. + /// + /// May be `null` if the server provided no description. final String description; - /// A URI for a page that describes the error in more detail, provided by the - /// server. Defaults to null. + /// A URL for a page that describes the error in more detail, provided by the + /// server. + /// + /// May be `null` if the server provided no URL. final Uri uri; /// Creates an AuthorizationException. diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart index 60495a0ab..371eed309 100644 --- a/pkgs/oauth2/lib/src/client.dart +++ b/pkgs/oauth2/lib/src/client.dart @@ -2,7 +2,7 @@ // 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 oauth2_client; +library oauth2.client; import 'dart:async'; @@ -15,8 +15,10 @@ import 'utils.dart'; // TODO(nweiz): Add an onCredentialsRefreshed event once we have some event // infrastructure. -/// An OAuth2 client. This acts as a drop-in replacement for an [http.Client], -/// while sending OAuth2 authorization credentials along with each request. +/// An OAuth2 client. +/// +/// This acts as a drop-in replacement for an [http.Client], while sending +/// OAuth2 authorization credentials along with each request. /// /// The client also automatically refreshes its credentials if possible. When it /// makes a request, if its credentials are expired, it will first refresh them. @@ -34,18 +36,22 @@ import 'utils.dart'; /// authorize. At the time of writing, the only authorization method this /// library supports is [AuthorizationCodeGrant]. class Client extends http.BaseClient { - /// The client identifier for this client. The authorization server will issue - /// each client a separate client identifier and secret, which allows the - /// server to tell which client is accessing it. Some servers may also have an - /// anonymous identifier/secret pair that any client may use. + /// The client identifier for this client. + /// + /// The authorization server will issue each client a separate client + /// identifier and secret, which allows the server to tell which client is + /// accessing it. Some servers may also have an anonymous identifier/secret + /// pair that any client may use. /// /// This is usually global to the program using this library. final String identifier; - /// The client secret for this client. The authorization server will issue - /// each client a separate client identifier and secret, which allows the - /// server to tell which client is accessing it. Some servers may also have an - /// anonymous identifier/secret pair that any client may use. + /// The client secret for this client. + /// + /// The authorization server will issue each client a separate client + /// identifier and secret, which allows the server to tell which client is + /// accessing it. Some servers may also have an anonymous identifier/secret + /// pair that any client may use. /// /// This is usually global to the program using this library. /// @@ -56,16 +62,19 @@ class Client extends http.BaseClient { final String secret; /// The credentials this client uses to prove to the resource server that it's - /// authorized. This may change from request to request as the credentials - /// expire and the client refreshes them automatically. + /// authorized. + /// + /// This may change from request to request as the credentials expire and the + /// client refreshes them automatically. Credentials get credentials => _credentials; Credentials _credentials; /// The underlying HTTP client. http.Client _httpClient; - /// Creates a new client from a pre-existing set of credentials. When - /// authorizing a client for the first time, you should use + /// Creates a new client from a pre-existing set of credentials. + /// + /// When authorizing a client for the first time, you should use /// [AuthorizationCodeGrant] instead of constructing a [Client] directly. /// /// [httpClient] is the underlying client that this forwards requests to after @@ -77,9 +86,10 @@ class Client extends http.BaseClient { {http.Client httpClient}) : _httpClient = httpClient == null ? new http.Client() : httpClient; - /// Sends an HTTP request with OAuth2 authorization credentials attached. This - /// will also automatically refresh this client's [Credentials] before sending - /// the request if necessary. + /// Sends an HTTP request with OAuth2 authorization credentials attached. + /// + /// This will also automatically refresh this client's [Credentials] before + /// sending the request if necessary. Future send(http.BaseRequest request) async { if (credentials.isExpired) { if (!credentials.canRefresh) throw new ExpirationException(credentials); @@ -96,7 +106,7 @@ class Client extends http.BaseClient { try { authenticate = new AuthenticateHeader.parse( response.headers['www-authenticate']); - } on FormatException catch (e) { + } on FormatException catch (_) { return response; } diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index 88b1f5d11..37e711487 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -2,7 +2,7 @@ // 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; +library oauth2.credentials; import 'dart:async'; import 'dart:convert'; @@ -10,11 +10,12 @@ import 'dart:convert'; import 'package:http/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. +/// 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 @@ -30,26 +31,34 @@ class Credentials { final String accessToken; /// The token that is sent to the authorization server to refresh the - /// credentials. This is optional. + /// credentials. + /// + /// This may be `null`, indicating that the credentials can't be refreshed. final String refreshToken; /// The URL of the authorization server endpoint that's used to refresh the - /// credentials. This is optional. + /// credentials. + /// + /// This may be `null`, indicating that the credentials can't be refreshed. final Uri tokenEndpoint; /// The specific permissions being requested from the authorization server. + /// /// The scope strings are specific to the authorization server and may be /// found in its documentation. final List 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. + /// 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 DateTime 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. + /// 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 DateTime.now().isAfter(expiration); @@ -69,10 +78,11 @@ class Credentials { this.scopes, this.expiration]); - /// Loads a set of credentials from a JSON-serialized form. Throws - /// [FormatException] if the JSON is incorrectly formatted. + /// Loads a set of credentials from a JSON-serialized form. + /// + /// Throws a [FormatException] if the JSON is incorrectly formatted. factory Credentials.fromJson(String json) { - void validate(bool condition, String message) { + validate(condition, message) { if (condition) return; throw new FormatException( "Failed to load credentials: $message.\n\n$json"); @@ -81,7 +91,7 @@ class Credentials { var parsed; try { parsed = JSON.decode(json); - } on FormatException catch (e) { + } on FormatException catch (_) { validate(false, 'invalid JSON'); } @@ -122,9 +132,10 @@ class Credentials { 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]. + /// 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.encode({ 'accessToken': accessToken, 'refreshToken': refreshToken, @@ -133,8 +144,10 @@ class Credentials { 'expiration': expiration == null ? null : expiration.millisecondsSinceEpoch }); - /// Returns a new set of refreshed credentials. See [Client.identifier] and - /// [Client.secret] for explanations of those parameters. + /// 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]. diff --git a/pkgs/oauth2/lib/src/expiration_exception.dart b/pkgs/oauth2/lib/src/expiration_exception.dart index 9829de1e1..f684e6ae5 100644 --- a/pkgs/oauth2/lib/src/expiration_exception.dart +++ b/pkgs/oauth2/lib/src/expiration_exception.dart @@ -2,7 +2,7 @@ // 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 expiration_exception; +library oauth2.expiration_exception; import 'credentials.dart'; @@ -16,5 +16,5 @@ class ExpirationException implements Exception { /// Provides a string description of the ExpirationException. String toString() => - "OAuth2 credentials have expired and can't be refreshed."; + "OAuth2 credentials have expired and can't be refreshed."; } diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart index 576636265..0065f0c67 100644 --- a/pkgs/oauth2/lib/src/handle_access_token_response.dart +++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart @@ -2,7 +2,7 @@ // 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 handle_access_token_response; +library oauth2.handle_access_token_response; import 'dart:convert'; @@ -12,14 +12,17 @@ import 'package:http_parser/http_parser.dart'; import 'credentials.dart'; import 'authorization_exception.dart'; -/// The amount of time, in seconds, to add as a "grace period" for credential -/// expiration. This allows credential expiration checks to remain valid for a -/// reasonable amount of time. -const _EXPIRATION_GRACE = 10; +/// The amount of time to add as a "grace period" for credential expiration. +/// +/// This allows credential expiration checks to remain valid for a reasonable +/// amount of time. +const _expirationGrace = const Duration(seconds: 10); /// Handles a response from the authorization server that contains an access -/// token. This response format is common across several different components of -/// the OAuth2 flow. +/// token. +/// +/// This response format is common across several different components of the +/// OAuth2 flow. Credentials handleAccessTokenResponse( http.Response response, Uri tokenEndpoint, @@ -27,8 +30,8 @@ Credentials handleAccessTokenResponse( List scopes) { if (response.statusCode != 200) _handleErrorResponse(response, tokenEndpoint); - void validate(bool condition, String message) => - _validate(response, tokenEndpoint, condition, message); + validate(condition, message) => + _validate(response, tokenEndpoint, condition, message); var contentType = response.headers['content-type']; if (contentType != null) contentType = new MediaType.parse(contentType); @@ -43,7 +46,7 @@ Credentials handleAccessTokenResponse( var parameters; try { parameters = JSON.decode(response.body); - } on FormatException catch (e) { + } on FormatException catch (_) { validate(false, 'invalid JSON'); } @@ -74,7 +77,7 @@ Credentials handleAccessTokenResponse( if (scope != null) scopes = scope.split(" "); var expiration = expiresIn == null ? null : - startTime.add(new Duration(seconds: expiresIn - _EXPIRATION_GRACE)); + startTime.add(new Duration(seconds: expiresIn) - _expirationGrace); return new Credentials( parameters['access_token'], @@ -87,8 +90,8 @@ Credentials handleAccessTokenResponse( /// Throws the appropriate exception for an error response from the /// authorization server. void _handleErrorResponse(http.Response response, Uri tokenEndpoint) { - void validate(bool condition, String message) => - _validate(response, tokenEndpoint, condition, message); + validate(condition, message) => + _validate(response, tokenEndpoint, condition, message); // OAuth2 mandates a 400 or 401 response code for access token error // responses. If it's not a 400 reponse, the server is either broken or @@ -110,7 +113,7 @@ void _handleErrorResponse(http.Response response, Uri tokenEndpoint) { var parameters; try { parameters = JSON.decode(response.body); - } on FormatException catch (e) { + } on FormatException catch (_) { validate(false, 'invalid JSON'); } diff --git a/pkgs/oauth2/lib/src/utils.dart b/pkgs/oauth2/lib/src/utils.dart index b29262580..734c58e26 100644 --- a/pkgs/oauth2/lib/src/utils.dart +++ b/pkgs/oauth2/lib/src/utils.dart @@ -2,57 +2,18 @@ // 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 utils; - -import 'dart:async'; +library oauth2.utils; /// Adds additional query parameters to [url], overwriting the original /// parameters if a name conflict occurs. -Uri addQueryParameters(Uri url, Map parameters) { - var queryMap = queryToMap(url.query); - queryMap.addAll(parameters); - return url.resolve("?${mapToQuery(queryMap)}"); -} - -/// Convert a URL query string (or `application/x-www-form-urlencoded` body) -/// into a [Map] from parameter names to values. -Map queryToMap(String queryList) { - var map = {}; - for (var pair in queryList.split("&")) { - var split = split1(pair, "="); - if (split.isEmpty) continue; - var key = urlDecode(split[0]); - var value = split.length > 1 ? urlDecode(split[1]) : ""; - map[key] = value; - } - return map; -} - -/// Convert a [Map] from parameter names to values to a URL query string. -String mapToQuery(Map map) { - var pairs = >[]; - map.forEach((key, value) { - key = Uri.encodeQueryComponent(key); - value = (value == null || value.isEmpty) - ? null - : Uri.encodeQueryComponent(value); - pairs.add([key, value]); - }); - return pairs.map((pair) { - if (pair[1] == null) return pair[0]; - return "${pair[0]}=${pair[1]}"; - }).join("&"); -} - -/// Decode a URL-encoded string. Unlike [Uri.decodeComponent], this includes -/// replacing `+` with ` `. -String urlDecode(String encoded) => - Uri.decodeComponent(encoded.replaceAll("+", " ")); +Uri addQueryParameters(Uri url, Map parameters) => url.replace( + queryParameters: new Map.from(url.queryParameters)..addAll(parameters)); /// Like [String.split], but only splits on the first occurrence of the pattern. +/// /// This will always return a list of two elements or fewer. List split1(String toSplit, String pattern) { - if (toSplit.isEmpty) return []; + if (toSplit.isEmpty) return []; var index = toSplit.indexOf(pattern); if (index == -1) return [toSplit]; diff --git a/pkgs/oauth2/test/authorization_code_grant_test.dart b/pkgs/oauth2/test/authorization_code_grant_test.dart index 24ff59c16..deaad5c14 100644 --- a/pkgs/oauth2/test/authorization_code_grant_test.dart +++ b/pkgs/oauth2/test/authorization_code_grant_test.dart @@ -2,8 +2,6 @@ // 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 authorization_code_grant_test; - import 'dart:async'; import 'dart:convert'; @@ -15,24 +13,20 @@ import 'utils.dart'; final redirectUrl = Uri.parse('http://example.com/redirect'); -ExpectClient client; - -oauth2.AuthorizationCodeGrant grant; - -void createGrant() { - client = new ExpectClient(); - grant = new oauth2.AuthorizationCodeGrant( - 'identifier', - 'secret', - Uri.parse('https://example.com/authorization'), - Uri.parse('https://example.com/token'), - httpClient: client); -} - void main() { - group('.getAuthorizationUrl', () { - setUp(createGrant); + var client; + var grant; + setUp(() { + client = new ExpectClient(); + grant = new oauth2.AuthorizationCodeGrant( + 'identifier', + 'secret', + Uri.parse('https://example.com/authorization'), + Uri.parse('https://example.com/token'), + httpClient: client); + }); + group('.getAuthorizationUrl', () { test('builds the correct URL', () { expect(grant.getAuthorizationUrl(redirectUrl).toString(), equals('https://example.com/authorization' @@ -87,8 +81,6 @@ void main() { }); group('.handleAuthorizationResponse', () { - setUp(createGrant); - test("can't be called before .getAuthorizationUrl", () { expect(grant.handleAuthorizationResponse({}), throwsStateError); }); @@ -153,8 +145,6 @@ void main() { }); group('.handleAuthorizationCode', () { - setUp(createGrant); - test("can't be called before .getAuthorizationUrl", () { expect(grant.handleAuthorizationCode('auth code'), throwsStateError); }); diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart index 7115095c0..969787a94 100644 --- a/pkgs/oauth2/test/client_test.dart +++ b/pkgs/oauth2/test/client_test.dart @@ -2,8 +2,6 @@ // 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 client_test; - import 'dart:async'; import 'dart:convert'; @@ -17,16 +15,11 @@ final Uri requestUri = Uri.parse("http://example.com/resource"); final Uri tokenEndpoint = Uri.parse('http://example.com/token'); -ExpectClient httpClient; - -void createHttpClient() { - httpClient = new ExpectClient(); -} - void main() { - group('with expired credentials', () { - setUp(createHttpClient); + var httpClient; + setUp(() => httpClient = new ExpectClient()); + group('with expired credentials', () { test("that can't be refreshed throws an ExpirationException on send", () { var expiration = new DateTime.now().subtract(new Duration(hours: 1)); var credentials = new oauth2.Credentials( @@ -70,8 +63,6 @@ void main() { }); group('with valid credentials', () { - setUp(createHttpClient); - test("sends a request with bearer authorization", () { var credentials = new oauth2.Credentials('access token'); var client = new oauth2.Client('identifier', 'secret', credentials, @@ -117,8 +108,6 @@ void main() { }); group('with invalid credentials', () { - setUp(createHttpClient); - test('throws an AuthorizationException for a 401 response', () { var credentials = new oauth2.Credentials('access token'); var client = new oauth2.Client('identifier', 'secret', credentials, diff --git a/pkgs/oauth2/test/credentials_test.dart b/pkgs/oauth2/test/credentials_test.dart index 3bd44d301..d72614f01 100644 --- a/pkgs/oauth2/test/credentials_test.dart +++ b/pkgs/oauth2/test/credentials_test.dart @@ -2,8 +2,6 @@ // 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_test; - import 'dart:async'; import 'dart:convert'; @@ -15,9 +13,8 @@ import 'utils.dart'; final Uri tokenEndpoint = Uri.parse('http://example.com/token'); -ExpectClient httpClient; - void main() { + var httpClient; setUp(() => httpClient = new ExpectClient()); test('is not expired if no expiration exists', () { diff --git a/pkgs/oauth2/test/utils.dart b/pkgs/oauth2/test/utils.dart index bee6bf3d2..5bdf78a07 100644 --- a/pkgs/oauth2/test/utils.dart +++ b/pkgs/oauth2/test/utils.dart @@ -2,8 +2,6 @@ // 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 utils; - import 'dart:async'; import 'dart:collection' show Queue; diff --git a/pkgs/oauth2/test/utils_test.dart b/pkgs/oauth2/test/utils_test.dart index 0dd0348ab..54c2da58e 100644 --- a/pkgs/oauth2/test/utils_test.dart +++ b/pkgs/oauth2/test/utils_test.dart @@ -2,8 +2,6 @@ // 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 utils_test; - import 'package:oauth2/src/utils.dart'; import 'package:test/test.dart'; From 4868e9093a651d7048bc05b5ce2cf66132adb202 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 26 Aug 2015 13:44:16 -0700 Subject: [PATCH 062/159] Code review changes BUG= Review URL: https://codereview.chromium.org//1304363004 . --- pkgs/oauth2/CHANGELOG.md | 32 ++++ pkgs/oauth2/README.md | 6 +- .../lib/src/authorization_code_grant.dart | 59 +++++-- pkgs/oauth2/lib/src/client.dart | 37 +++-- pkgs/oauth2/lib/src/credentials.dart | 75 +++++---- .../lib/src/handle_access_token_response.dart | 8 +- pkgs/oauth2/lib/src/utils.dart | 63 +------- pkgs/oauth2/pubspec.yaml | 4 +- .../test/authorization_code_grant_test.dart | 76 ++++++++- pkgs/oauth2/test/client_test.dart | 55 +++++-- pkgs/oauth2/test/credentials_test.dart | 152 ++++++++++++++++-- pkgs/oauth2/test/utils_test.dart | 88 ---------- 12 files changed, 411 insertions(+), 244 deletions(-) delete mode 100644 pkgs/oauth2/test/utils_test.dart diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md index fc869c940..686b6acac 100644 --- a/pkgs/oauth2/CHANGELOG.md +++ b/pkgs/oauth2/CHANGELOG.md @@ -1,3 +1,35 @@ +# 1.0.0 + +## Breaking changes + +* Requests that use client authentication, such as the + `AuthorizationCodeGrant`'s access token request and `Credentials`' refresh + request, now use HTTP Basic authentication by default. This form of + authentication is strongly recommended by the OAuth 2.0 spec. The new + `basicAuth` parameter may be set to `false` to force form-based authentication + for servers that require it. + +* `new AuthorizationCodeGrant()` now takes `secret` as an optional named + argument rather than a required argument. This matches the OAuth 2.0 spec, + which says that a client secret is only required for confidential clients. + +* `new Client()` and `Credentials.refresh()` now take both `identifier` and + `secret` as optional named arguments rather than required arguments. This + matches the OAuth 2.0 spec, which says that the server may choose not to + require client authentication for some flows. + +* `new Credentials()` now takes named arguments rather than optional positional + arguments. + +## Non-breaking changes + +* The `scopes` argument to `AuthorizationCodeGrant.getAuthorizationUrl()` and + `new Credentials()` and the `newScopes` argument to `Credentials.refresh` now + take an `Iterable` rather than just a `List`. + +* The `scopes` argument to `AuthorizationCodeGrant.getAuthorizationUrl()` now + defaults to `null` rather than `const []`. + # 0.9.3 * Update the `http` dependency. diff --git a/pkgs/oauth2/README.md b/pkgs/oauth2/README.md index 23be5ff20..7e390cd4c 100644 --- a/pkgs/oauth2/README.md +++ b/pkgs/oauth2/README.md @@ -64,13 +64,15 @@ Future getClient() async { if (exists) { var credentials = new oauth2.Credentials.fromJson( await credentialsFile.readAsString()); - return new oauth2.Client(identifier, secret, credentials); + return new oauth2.Client(credentials, + identifier: identifier, secret: secret); } // If we don't have OAuth2 credentials yet, we need to get the resource owner // to authorize us. We're assuming here that we're a command-line application. var grant = new oauth2.AuthorizationCodeGrant( - identifier, secret, authorizationEndpoint, tokenEndpoint); + identifier, authorizationEndpoint, tokenEndpoint, + secret: secret); // Redirect the resource owner to the authorization URL. This will be a URL on // the authorization server (authorizationEndpoint with some additional query diff --git a/pkgs/oauth2/lib/src/authorization_code_grant.dart b/pkgs/oauth2/lib/src/authorization_code_grant.dart index e3bb64538..7a73d44ab 100644 --- a/pkgs/oauth2/lib/src/authorization_code_grant.dart +++ b/pkgs/oauth2/lib/src/authorization_code_grant.dart @@ -68,6 +68,9 @@ class AuthorizationCodeGrant { /// documentation. final Uri tokenEndpoint; + /// Whether to use HTTP Basic authentication for authorizing the client. + final bool _basicAuth; + /// The HTTP client used to make HTTP requests. http.Client _httpClient; @@ -87,15 +90,23 @@ class AuthorizationCodeGrant { /// Creates a new grant. /// + /// If [basicAuth] is `true` (the default), the client credentials are sent to + /// the server using using HTTP Basic authentication as defined in [RFC 2617]. + /// Otherwise, they're included in the request body. Note that the latter form + /// is not recommended by the OAuth 2.0 spec, and should only be used if the + /// server doesn't support Basic authentication. + /// + /// [RFC 2617]: https://tools.ietf.org/html/rfc2617 + /// /// [httpClient] is used for all HTTP requests made by this grant, as well as /// those of the [Client] is constructs. AuthorizationCodeGrant( this.identifier, - this.secret, this.authorizationEndpoint, this.tokenEndpoint, - {http.Client httpClient}) - : _httpClient = httpClient == null ? new http.Client() : httpClient; + {this.secret, bool basicAuth: true, http.Client httpClient}) + : _basicAuth = basicAuth, + _httpClient = httpClient == null ? new http.Client() : httpClient; /// Returns the URL to which the resource owner should be redirected to /// authorize this client. @@ -116,13 +127,19 @@ class AuthorizationCodeGrant { /// query parameters provided to the redirect URL. /// /// It is a [StateError] to call this more than once. - Uri getAuthorizationUrl(Uri redirect, - {List scopes: const [], String state}) { + Uri getAuthorizationUrl(Uri redirect, {Iterable scopes, + String state}) { if (_state != _State.initial) { throw new StateError('The authorization URL has already been generated.'); } _state = _State.awaitingResponse; + if (scopes == null) { + scopes = []; + } else { + scopes = scopes.toList(); + } + this._redirectEndpoint = redirect; this._scopes = scopes; this._stateString = state; @@ -224,21 +241,35 @@ class AuthorizationCodeGrant { /// the state beforehand. Future _handleAuthorizationCode(String authorizationCode) async { var startTime = new DateTime.now(); - var response = await _httpClient.post(this.tokenEndpoint, body: { + + var headers = {}; + + var body = { "grant_type": "authorization_code", "code": authorizationCode, - "redirect_uri": this._redirectEndpoint.toString(), - // TODO(nweiz): the spec recommends that HTTP basic auth be used in - // preference to form parameters, but Google doesn't support that. Should - // it be configurable? - "client_id": this.identifier, - "client_secret": this.secret - }); + "redirect_uri": this._redirectEndpoint.toString() + }; + + if (_basicAuth && secret != null) { + headers["Authorization"] = basicAuthHeader(identifier, secret); + } else { + // The ID is required for this request any time basic auth isn't being + // used, even if there's no actual client authentication to be done. + body["client_id"] = identifier; + if (secret != null) body["client_secret"] = secret; + } + + var response = await _httpClient.post(this.tokenEndpoint, + headers: headers, body: body); var credentials = handleAccessTokenResponse( response, tokenEndpoint, startTime, _scopes); return new Client( - this.identifier, this.secret, credentials, httpClient: _httpClient); + credentials, + identifier: this.identifier, + secret: this.secret, + basicAuth: _basicAuth, + httpClient: _httpClient); } /// Closes the grant and frees its resources. diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart index 371eed309..adf53d526 100644 --- a/pkgs/oauth2/lib/src/client.dart +++ b/pkgs/oauth2/lib/src/client.dart @@ -7,6 +7,7 @@ library oauth2.client; import 'dart:async'; import 'package:http/http.dart' as http; +import 'package:http_parser/http_parser.dart'; import 'authorization_exception.dart'; import 'credentials.dart'; @@ -69,6 +70,9 @@ class Client extends http.BaseClient { Credentials get credentials => _credentials; Credentials _credentials; + /// Whether to use HTTP Basic authentication for authorizing the client. + final bool _basicAuth; + /// The underlying HTTP client. http.Client _httpClient; @@ -79,12 +83,16 @@ class Client extends http.BaseClient { /// /// [httpClient] is the underlying client that this forwards requests to after /// adding authorization credentials to them. - Client( - this.identifier, - this.secret, - this._credentials, - {http.Client httpClient}) - : _httpClient = httpClient == null ? new http.Client() : httpClient; + /// + /// Throws an [ArgumentError] if [secret] is passed without [identifier]. + Client(this._credentials, {this.identifier, this.secret, + bool basicAuth: true, http.Client httpClient}) + : _basicAuth = basicAuth, + _httpClient = httpClient == null ? new http.Client() : httpClient { + if (identifier == null && secret != null) { + throw new ArgumentError("secret may not be passed without identifier."); + } + } /// Sends an HTTP request with OAuth2 authorization credentials attached. /// @@ -102,17 +110,19 @@ class Client extends http.BaseClient { if (response.statusCode != 401) return response; if (!response.headers.containsKey('www-authenticate')) return response; - var authenticate; + var challenges; try { - authenticate = new AuthenticateHeader.parse( + challenges = AuthenticationChallenge.parseHeader( response.headers['www-authenticate']); } on FormatException catch (_) { return response; } - if (authenticate.scheme != 'bearer') return response; + var challenge = challenges.firstWhere( + (challenge) => challenge.scheme == 'bearer', orElse: () => null); + if (challenge == null) return response; - var params = authenticate.parameters; + var params = challenge.parameters; if (!params.containsKey('error')) return response; throw new AuthorizationException( @@ -137,8 +147,11 @@ class Client extends http.BaseClient { } _credentials = await credentials.refresh( - identifier, secret, - newScopes: newScopes, httpClient: _httpClient); + identifier: identifier, + secret: secret, + newScopes: newScopes, + basicAuth: _basicAuth, + httpClient: _httpClient); return this; } diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index 37e711487..0a12f3f88 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -5,11 +5,13 @@ library oauth2.credentials; import 'dart:async'; +import 'dart:collection'; import 'dart:convert'; import 'package:http/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. @@ -72,11 +74,15 @@ class Credentials { /// [AuthorizationCodeGrant]. Alternately, it may be loaded from a serialized /// form via [Credentials.fromJson]. Credentials( - this.accessToken, - [this.refreshToken, - this.tokenEndpoint, - this.scopes, - this.expiration]); + this.accessToken, + {this.refreshToken, + this.tokenEndpoint, + Iterable scopes, + this.expiration}) + : scopes = new UnmodifiableListView( + // Explicitly type-annotate the list literal to work around + // sdk#24202. + scopes == null ? [] : scopes.toList()); /// Loads a set of credentials from a JSON-serialized form. /// @@ -126,10 +132,10 @@ class Credentials { return new Credentials( parsed['accessToken'], - parsed['refreshToken'], - tokenEndpoint, - scopes, - expiration); + refreshToken: parsed['refreshToken'], + tokenEndpoint: tokenEndpoint, + scopes: scopes, + expiration: expiration); } /// Serializes a set of credentials to JSON. @@ -152,19 +158,25 @@ class Credentials { /// 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 + /// This throws an [ArgumentError] if [secret] is passed without [identifier], + /// 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 refresh( - String identifier, + {String identifier, String secret, - {List newScopes, - http.Client httpClient}) async { + Iterable newScopes, + bool basicAuth: true, + http.Client httpClient}) async { var scopes = this.scopes; - if (newScopes != null) scopes = newScopes; - if (scopes == null) scopes = []; + if (newScopes != null) scopes = newScopes.toList(); + if (scopes == null) scopes = []; if (httpClient == null) httpClient = new http.Client(); + if (identifier == null && secret != null) { + throw new ArgumentError("secret may not be passed without identifier."); + } + var startTime = new DateTime.now(); if (refreshToken == null) { throw new StateError("Can't refresh credentials without a refresh " @@ -174,29 +186,34 @@ class Credentials { "endpoint."); } - var fields = { + var headers = {}; + + var body = { "grant_type": "refresh_token", - "refresh_token": refreshToken, - // TODO(nweiz): the spec recommends that HTTP basic auth be used in - // preference to form parameters, but Google doesn't support that. - // Should it be configurable? - "client_id": identifier, - "client_secret": secret + "refresh_token": refreshToken }; - if (!scopes.isEmpty) fields["scope"] = scopes.join(' '); + if (!scopes.isEmpty) body["scope"] = scopes.join(' '); + + if (basicAuth && secret != null) { + headers["Authorization"] = basicAuthHeader(identifier, secret); + } else { + if (identifier != null) body["client_id"] = identifier; + if (secret != null) body["client_secret"] = secret; + } - var response = await httpClient.post(tokenEndpoint, body: fields); + var response = await httpClient.post(tokenEndpoint, + headers: headers, body: body); var credentials = await handleAccessTokenResponse( - response, tokenEndpoint, startTime, scopes); + response, tokenEndpoint, startTime, scopes); // The authorization server may issue a new refresh token. If it doesn't, // we should re-use the one we already have. if (credentials.refreshToken != null) return credentials; return new Credentials( credentials.accessToken, - this.refreshToken, - credentials.tokenEndpoint, - credentials.scopes, - credentials.expiration); + refreshToken: this.refreshToken, + tokenEndpoint: credentials.tokenEndpoint, + scopes: credentials.scopes, + expiration: credentials.expiration); } } diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart index 0065f0c67..ef9b198f4 100644 --- a/pkgs/oauth2/lib/src/handle_access_token_response.dart +++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart @@ -81,10 +81,10 @@ Credentials handleAccessTokenResponse( return new Credentials( parameters['access_token'], - parameters['refresh_token'], - tokenEndpoint, - scopes, - expiration); + refreshToken: parameters['refresh_token'], + tokenEndpoint: tokenEndpoint, + scopes: scopes, + expiration: expiration); } /// Throws the appropriate exception for an error response from the diff --git a/pkgs/oauth2/lib/src/utils.dart b/pkgs/oauth2/lib/src/utils.dart index 734c58e26..bf260eb73 100644 --- a/pkgs/oauth2/lib/src/utils.dart +++ b/pkgs/oauth2/lib/src/utils.dart @@ -4,65 +4,16 @@ library oauth2.utils; +import 'dart:convert'; + +import 'package:crypto/crypto.dart'; + /// Adds additional query parameters to [url], overwriting the original /// parameters if a name conflict occurs. Uri addQueryParameters(Uri url, Map parameters) => url.replace( queryParameters: new Map.from(url.queryParameters)..addAll(parameters)); -/// Like [String.split], but only splits on the first occurrence of the pattern. -/// -/// This will always return a list of two elements or fewer. -List split1(String toSplit, String pattern) { - if (toSplit.isEmpty) return []; - - var index = toSplit.indexOf(pattern); - if (index == -1) return [toSplit]; - return [toSplit.substring(0, index), - toSplit.substring(index + pattern.length)]; -} - -/// A WWW-Authenticate header value, parsed as per [RFC 2617][]. -/// -/// [RFC 2617]: http://tools.ietf.org/html/rfc2617 -class AuthenticateHeader { - final String scheme; - final Map parameters; - - AuthenticateHeader(this.scheme, this.parameters); - - /// Parses a header string. Throws a [FormatException] if the header is - /// invalid. - factory AuthenticateHeader.parse(String header) { - var split = split1(header, ' '); - if (split.length == 0) { - throw new FormatException('Invalid WWW-Authenticate header: "$header"'); - } else if (split.length == 1 || split[1].trim().isEmpty) { - return new AuthenticateHeader(split[0].toLowerCase(), {}); - } - var scheme = split[0].toLowerCase(); - var paramString = split[1]; - - // From http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html. - var tokenChar = r'[^\0-\x1F()<>@,;:\\"/\[\]?={} \t\x7F]'; - var quotedStringChar = r'(?:[^\0-\x1F\x7F"]|\\.)'; - var regexp = new RegExp('^ *($tokenChar+)="($quotedStringChar*)" *(, *)?'); - - var parameters = {}; - var match; - do { - match = regexp.firstMatch(paramString); - if (match == null) { - throw new FormatException('Invalid WWW-Authenticate header: "$header"'); - } - - paramString = paramString.substring(match.end); - parameters[match.group(1).toLowerCase()] = match.group(2); - } while (match.group(3) != null); - - if (!paramString.trim().isEmpty) { - throw new FormatException('Invalid WWW-Authenticate header: "$header"'); - } - - return new AuthenticateHeader(scheme, parameters); - } +String basicAuthHeader(String identifier, String secret) { + var userPass = Uri.encodeFull(identifier) + ":" + Uri.encodeFull(secret); + return "Basic " + CryptoUtils.bytesToBase64(ASCII.encode(userPass)); } diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index e40265aa5..f52b8befa 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,5 @@ name: oauth2 -version: 0.9.4-dev +version: 1.0.0-dev author: Dart Team homepage: http://github.com/dart-lang/oauth2 description: > @@ -10,6 +10,6 @@ environment: sdk: '>=1.9.0 <2.0.0' dependencies: http: '>=0.11.0 <0.12.0' - http_parser: '>=0.0.0 <0.1.0' + http_parser: '^1.0.0' dev_dependencies: test: '>=0.12.0 <0.13.0' diff --git a/pkgs/oauth2/test/authorization_code_grant_test.dart b/pkgs/oauth2/test/authorization_code_grant_test.dart index deaad5c14..e03a60759 100644 --- a/pkgs/oauth2/test/authorization_code_grant_test.dart +++ b/pkgs/oauth2/test/authorization_code_grant_test.dart @@ -20,9 +20,9 @@ void main() { client = new ExpectClient(); grant = new oauth2.AuthorizationCodeGrant( 'identifier', - 'secret', Uri.parse('https://example.com/authorization'), Uri.parse('https://example.com/token'), + secret: 'secret', httpClient: client); }); @@ -60,9 +60,9 @@ void main() { test('merges with existing query parameters', () { grant = new oauth2.AuthorizationCodeGrant( 'identifier', - 'secret', Uri.parse('https://example.com/authorization?query=value'), Uri.parse('https://example.com/token'), + secret: 'secret', httpClient: client); var authorizationUrl = grant.getAuthorizationUrl(redirectUrl); @@ -127,10 +127,11 @@ void main() { expect(request.bodyFields, equals({ 'grant_type': 'authorization_code', 'code': 'auth code', - 'redirect_uri': redirectUrl.toString(), - 'client_id': 'identifier', - 'client_secret': 'secret' + 'redirect_uri': redirectUrl.toString() })); + expect(request.headers, containsPair( + "Authorization", + "Basic aWRlbnRpZmllcjpzZWNyZXQ=")); return new Future.value(new http.Response(JSON.encode({ 'access_token': 'access token', @@ -156,6 +157,71 @@ void main() { }); test('sends an authorization code request', () { + grant.getAuthorizationUrl(redirectUrl); + client.expectRequest((request) { + expect(request.method, equals('POST')); + expect(request.url.toString(), equals(grant.tokenEndpoint.toString())); + expect(request.bodyFields, equals({ + 'grant_type': 'authorization_code', + 'code': 'auth code', + 'redirect_uri': redirectUrl.toString() + })); + expect(request.headers, containsPair( + "Authorization", + "Basic aWRlbnRpZmllcjpzZWNyZXQ=")); + + return new Future.value(new http.Response(JSON.encode({ + 'access_token': 'access token', + 'token_type': 'bearer', + }), 200, headers: {'content-type': 'application/json'})); + }); + + expect(grant.handleAuthorizationCode('auth code'), + completion(predicate((client) { + expect(client.credentials.accessToken, equals('access token')); + return true; + }))); + }); + }); + + group("with basicAuth: false", () { + setUp(() { + client = new ExpectClient(); + grant = new oauth2.AuthorizationCodeGrant( + 'identifier', + Uri.parse('https://example.com/authorization'), + Uri.parse('https://example.com/token'), + secret: 'secret', + basicAuth: false, + httpClient: client); + }); + + test('.handleAuthorizationResponse sends an authorization code request', + () { + grant.getAuthorizationUrl(redirectUrl); + client.expectRequest((request) { + expect(request.method, equals('POST')); + expect(request.url.toString(), equals(grant.tokenEndpoint.toString())); + expect(request.bodyFields, equals({ + 'grant_type': 'authorization_code', + 'code': 'auth code', + 'redirect_uri': redirectUrl.toString(), + 'client_id': 'identifier', + 'client_secret': 'secret' + })); + + return new Future.value(new http.Response(JSON.encode({ + 'access_token': 'access token', + 'token_type': 'bearer', + }), 200, headers: {'content-type': 'application/json'})); + }); + + expect(grant.handleAuthorizationResponse({'code': 'auth code'}) + .then((client) => client.credentials.accessToken), + completion(equals('access token'))); + }); + + test('.handleAuthorizationCode sends an authorization code request', () { grant.getAuthorizationUrl(redirectUrl); client.expectRequest((request) { expect(request.method, equals('POST')); diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart index 969787a94..7dc5afd2e 100644 --- a/pkgs/oauth2/test/client_test.dart +++ b/pkgs/oauth2/test/client_test.dart @@ -23,8 +23,10 @@ void main() { test("that can't be refreshed throws an ExpirationException on send", () { var expiration = new DateTime.now().subtract(new Duration(hours: 1)); var credentials = new oauth2.Credentials( - 'access token', null, null, [], expiration); - var client = new oauth2.Client('identifier', 'secret', credentials, + 'access token', expiration: expiration); + var client = new oauth2.Client(credentials, + identifier: 'identifier', + secret: 'secret', httpClient: httpClient); expect(client.get(requestUri), @@ -35,8 +37,13 @@ void main() { "request", () async { var expiration = new DateTime.now().subtract(new Duration(hours: 1)); var credentials = new oauth2.Credentials( - 'access token', 'refresh token', tokenEndpoint, [], expiration); - var client = new oauth2.Client('identifier', 'secret', credentials, + 'access token', + refreshToken: 'refresh token', + tokenEndpoint: tokenEndpoint, + expiration: expiration); + var client = new oauth2.Client(credentials, + identifier: 'identifier', + secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { @@ -65,7 +72,9 @@ void main() { group('with valid credentials', () { test("sends a request with bearer authorization", () { var credentials = new oauth2.Credentials('access token'); - var client = new oauth2.Client('identifier', 'secret', credentials, + var client = new oauth2.Client(credentials, + identifier: 'identifier', + secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { @@ -81,8 +90,12 @@ void main() { test("can manually refresh the credentials", () async { var credentials = new oauth2.Credentials( - 'access token', 'refresh token', tokenEndpoint); - var client = new oauth2.Client('identifier', 'secret', credentials, + 'access token', + refreshToken: 'refresh token', + tokenEndpoint: tokenEndpoint); + var client = new oauth2.Client(credentials, + identifier: 'identifier', + secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { @@ -100,7 +113,9 @@ void main() { test("without a refresh token can't manually refresh the credentials", () { var credentials = new oauth2.Credentials('access token'); - var client = new oauth2.Client('identifier', 'secret', credentials, + var client = new oauth2.Client(credentials, + identifier: 'identifier', + secret: 'secret', httpClient: httpClient); expect(client.refreshCredentials(), throwsA(isStateError)); @@ -110,7 +125,9 @@ void main() { group('with invalid credentials', () { test('throws an AuthorizationException for a 401 response', () { var credentials = new oauth2.Credentials('access token'); - var client = new oauth2.Client('identifier', 'secret', credentials, + var client = new oauth2.Client(credentials, + identifier: 'identifier', + secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { @@ -130,7 +147,9 @@ void main() { test('passes through a 401 response without www-authenticate', () async { var credentials = new oauth2.Credentials('access token'); - var client = new oauth2.Client('identifier', 'secret', credentials, + var client = new oauth2.Client(credentials, + identifier: 'identifier', + secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { @@ -148,7 +167,9 @@ void main() { test('passes through a 401 response with invalid www-authenticate', () async { var credentials = new oauth2.Credentials('access token'); - var client = new oauth2.Client('identifier', 'secret', credentials, + var client = new oauth2.Client(credentials, + identifier: 'identifier', + secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { @@ -157,8 +178,8 @@ void main() { expect(request.headers['authorization'], equals('Bearer access token')); - var authenticate = 'Bearer error="invalid_token", error_description=' - '"Something is terribly wrong.", '; + var authenticate = 'Bearer error="invalid_token" error_description=' + '"Something is terribly wrong."'; return new Future.value(new http.Response('bad job', 401, headers: {'www-authenticate': authenticate})); }); @@ -169,7 +190,9 @@ void main() { test('passes through a 401 response with non-bearer www-authenticate', () async { var credentials = new oauth2.Credentials('access token'); - var client = new oauth2.Client('identifier', 'secret', credentials, + var client = new oauth2.Client(credentials, + identifier: 'identifier', + secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { @@ -188,7 +211,9 @@ void main() { test('passes through a 401 response with non-OAuth2 www-authenticate', () async { var credentials = new oauth2.Credentials('access token'); - var client = new oauth2.Client('identifier', 'secret', credentials, + var client = new oauth2.Client(credentials, + identifier: 'identifier', + secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { diff --git a/pkgs/oauth2/test/credentials_test.dart b/pkgs/oauth2/test/credentials_test.dart index d72614f01..f4d8886e8 100644 --- a/pkgs/oauth2/test/credentials_test.dart +++ b/pkgs/oauth2/test/credentials_test.dart @@ -25,37 +25,82 @@ void main() { test('is not expired if the expiration is in the future', () { var expiration = new DateTime.now().add(new Duration(hours: 1)); var credentials = new oauth2.Credentials( - 'access token', null, null, null, expiration); + 'access token', expiration: expiration); expect(credentials.isExpired, isFalse); }); test('is expired if the expiration is in the past', () { var expiration = new DateTime.now().subtract(new Duration(hours: 1)); var credentials = new oauth2.Credentials( - 'access token', null, null, null, expiration); + 'access token', expiration: expiration); expect(credentials.isExpired, isTrue); }); test("can't refresh without a refresh token", () { var credentials = new oauth2.Credentials( - 'access token', null, tokenEndpoint); + 'access token', tokenEndpoint: tokenEndpoint); expect(credentials.canRefresh, false); - expect(credentials.refresh('identifier', 'secret', httpClient: httpClient), + expect(credentials.refresh( + identifier: 'identifier', + secret: 'secret', + httpClient: httpClient), throwsStateError); }); test("can't refresh without a token endpoint", () { - var credentials = new oauth2.Credentials('access token', 'refresh token'); + var credentials = new oauth2.Credentials( + 'access token', refreshToken: 'refresh token'); expect(credentials.canRefresh, false); - expect(credentials.refresh('identifier', 'secret', httpClient: httpClient), + expect(credentials.refresh( + identifier: 'identifier', + secret: 'secret', + httpClient: httpClient), throwsStateError); }); test("can refresh with a refresh token and a token endpoint", () async { var credentials = new oauth2.Credentials( - 'access token', 'refresh token', tokenEndpoint, ['scope1', 'scope2']); + 'access token', + refreshToken: 'refresh token', + tokenEndpoint: tokenEndpoint, + scopes: ['scope1', 'scope2']); + expect(credentials.canRefresh, true); + + httpClient.expectRequest((request) { + expect(request.method, equals('POST')); + expect(request.url.toString(), equals(tokenEndpoint.toString())); + expect(request.bodyFields, equals({ + "grant_type": "refresh_token", + "refresh_token": "refresh token", + "scope": "scope1 scope2" + })); + expect(request.headers, containsPair( + "Authorization", + "Basic aWQlQzMlQUJudCVDNCVBQmZpZXI6cyVDMyVBQmNyZXQ=")); + + return new Future.value(new http.Response(JSON.encode({ + 'access_token': 'new access token', + 'token_type': 'bearer', + 'refresh_token': 'new refresh token' + }), 200, headers: {'content-type': 'application/json'})); + }); + + credentials = await credentials.refresh( + identifier: 'idëntīfier', + secret: 'sëcret', + httpClient: httpClient); + expect(credentials.accessToken, equals('new access token')); + expect(credentials.refreshToken, equals('new refresh token')); + }); + + test("can refresh without a client secret", () async { + var credentials = new oauth2.Credentials( + 'access token', + refreshToken: 'refresh token', + tokenEndpoint: tokenEndpoint, + scopes: ['scope1', 'scope2']); expect(credentials.canRefresh, true); httpClient.expectRequest((request) { @@ -65,8 +110,7 @@ void main() { "grant_type": "refresh_token", "refresh_token": "refresh token", "scope": "scope1 scope2", - "client_id": "identifier", - "client_secret": "secret" + "client_id": "identifier" })); return new Future.value(new http.Response(JSON.encode({ @@ -77,15 +121,19 @@ void main() { }); - credentials = await credentials.refresh('identifier', 'secret', + credentials = await credentials.refresh( + identifier: 'identifier', httpClient: httpClient); expect(credentials.accessToken, equals('new access token')); expect(credentials.refreshToken, equals('new refresh token')); }); - test("uses the old refresh token if a new one isn't provided", () async { + test("can refresh without client authentication", () async { var credentials = new oauth2.Credentials( - 'access token', 'refresh token', tokenEndpoint); + 'access token', + refreshToken: 'refresh token', + tokenEndpoint: tokenEndpoint, + scopes: ['scope1', 'scope2']); expect(credentials.canRefresh, true); httpClient.expectRequest((request) { @@ -94,9 +142,39 @@ void main() { expect(request.bodyFields, equals({ "grant_type": "refresh_token", "refresh_token": "refresh token", - "client_id": "identifier", - "client_secret": "secret" + "scope": "scope1 scope2" + })); + + return new Future.value(new http.Response(JSON.encode({ + 'access_token': 'new access token', + 'token_type': 'bearer', + 'refresh_token': 'new refresh token' + }), 200, headers: {'content-type': 'application/json'})); + }); + + + credentials = await credentials.refresh(httpClient: httpClient); + expect(credentials.accessToken, equals('new access token')); + expect(credentials.refreshToken, equals('new refresh token')); + }); + + test("uses the old refresh token if a new one isn't provided", () async { + var credentials = new oauth2.Credentials( + 'access token', + refreshToken: 'refresh token', + tokenEndpoint: tokenEndpoint); + expect(credentials.canRefresh, true); + + httpClient.expectRequest((request) { + expect(request.method, equals('POST')); + expect(request.url.toString(), equals(tokenEndpoint.toString())); + expect(request.bodyFields, equals({ + "grant_type": "refresh_token", + "refresh_token": "refresh token" })); + expect(request.headers, containsPair( + "Authorization", + "Basic aWQlQzMlQUJudCVDNCVBQmZpZXI6cyVDMyVBQmNyZXQ=")); return new Future.value(new http.Response(JSON.encode({ 'access_token': 'new access token', @@ -105,12 +183,49 @@ void main() { }); - credentials = await credentials.refresh('identifier', 'secret', + credentials = await credentials.refresh( + identifier: 'idëntīfier', + secret: 'sëcret', httpClient: httpClient); expect(credentials.accessToken, equals('new access token')); expect(credentials.refreshToken, equals('refresh token')); }); + test("uses form-field authentication if basicAuth is false", () async { + var credentials = new oauth2.Credentials( + 'access token', + refreshToken: 'refresh token', + tokenEndpoint: tokenEndpoint, + scopes: ['scope1', 'scope2']); + expect(credentials.canRefresh, true); + + httpClient.expectRequest((request) { + expect(request.method, equals('POST')); + expect(request.url.toString(), equals(tokenEndpoint.toString())); + expect(request.bodyFields, equals({ + "grant_type": "refresh_token", + "refresh_token": "refresh token", + "scope": "scope1 scope2", + "client_id": "idëntīfier", + "client_secret": "sëcret" + })); + + return new Future.value(new http.Response(JSON.encode({ + 'access_token': 'new access token', + 'token_type': 'bearer', + 'refresh_token': 'new refresh token' + }), 200, headers: {'content-type': 'application/json'})); + }); + + credentials = await credentials.refresh( + identifier: 'idëntīfier', + secret: 'sëcret', + basicAuth: false, + httpClient: httpClient); + expect(credentials.accessToken, equals('new access token')); + expect(credentials.refreshToken, equals('new refresh token')); + }); + group("fromJson", () { oauth2.Credentials fromMap(Map map) => new oauth2.Credentials.fromJson(JSON.encode(map)); @@ -118,8 +233,11 @@ void main() { test("should load the same credentials from toJson", () { var expiration = new DateTime.now().subtract(new Duration(hours: 1)); var credentials = new oauth2.Credentials( - 'access token', 'refresh token', tokenEndpoint, ['scope1', 'scope2'], - expiration); + 'access token', + refreshToken: 'refresh token', + tokenEndpoint: tokenEndpoint, + scopes: ['scope1', 'scope2'], + expiration: expiration); var reloaded = new oauth2.Credentials.fromJson(credentials.toJson()); expect(reloaded.accessToken, equals(credentials.accessToken)); diff --git a/pkgs/oauth2/test/utils_test.dart b/pkgs/oauth2/test/utils_test.dart deleted file mode 100644 index 54c2da58e..000000000 --- a/pkgs/oauth2/test/utils_test.dart +++ /dev/null @@ -1,88 +0,0 @@ -// 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. - -import 'package:oauth2/src/utils.dart'; -import 'package:test/test.dart'; - -void main() { - group('AuthenticateHeader', () { - test("parses a scheme", () { - var header = new AuthenticateHeader.parse('bearer'); - expect(header.scheme, equals('bearer')); - expect(header.parameters, equals({})); - }); - - test("lower-cases the scheme", () { - var header = new AuthenticateHeader.parse('BeaRer'); - expect(header.scheme, equals('bearer')); - expect(header.parameters, equals({})); - }); - - test("parses a scheme with trailing whitespace", () { - var header = new AuthenticateHeader.parse('bearer '); - expect(header.scheme, equals('bearer')); - expect(header.parameters, equals({})); - }); - - test("parses a scheme with one param", () { - var header = new AuthenticateHeader.parse('bearer foo="bar"'); - expect(header.scheme, equals('bearer')); - expect(header.parameters, equals({'foo': 'bar'})); - }); - - test("parses a scheme with several params", () { - var header = new AuthenticateHeader.parse( - 'bearer foo="bar", bar="baz" ,baz="qux"'); - expect(header.scheme, equals('bearer')); - expect(header.parameters, equals({ - 'foo': 'bar', - 'bar': 'baz', - 'baz': 'qux' - })); - }); - - test("lower-cases parameter names but not values", () { - var header = new AuthenticateHeader.parse('bearer FoO="bAr"'); - expect(header.scheme, equals('bearer')); - expect(header.parameters, equals({'foo': 'bAr'})); - }); - - test("allows empty values", () { - var header = new AuthenticateHeader.parse('bearer foo=""'); - expect(header.scheme, equals('bearer')); - expect(header.parameters, equals({'foo': ''})); - }); - - test("won't parse an empty string", () { - expect(() => new AuthenticateHeader.parse(''), - throwsFormatException); - }); - - test("won't parse a token without a value", () { - expect(() => new AuthenticateHeader.parse('bearer foo'), - throwsFormatException); - - expect(() => new AuthenticateHeader.parse('bearer foo='), - throwsFormatException); - }); - - test("won't parse a token without a value", () { - expect(() => new AuthenticateHeader.parse('bearer foo'), - throwsFormatException); - - expect(() => new AuthenticateHeader.parse('bearer foo='), - throwsFormatException); - }); - - test("won't parse a trailing comma", () { - expect(() => new AuthenticateHeader.parse('bearer foo="bar",'), - throwsFormatException); - }); - - test("won't parse a multiple params without a comma", () { - expect(() => new AuthenticateHeader.parse('bearer foo="bar" bar="baz"'), - throwsFormatException); - }); - }); -} From ace4c79c61929ed688ac95605e3ecd981a18902d Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 26 Aug 2015 13:46:14 -0700 Subject: [PATCH 063/159] Revert "Code review changes" This reverts commit 4868e9093a651d7048bc05b5ce2cf66132adb202. --- pkgs/oauth2/CHANGELOG.md | 32 ---- pkgs/oauth2/README.md | 6 +- .../lib/src/authorization_code_grant.dart | 59 ++----- pkgs/oauth2/lib/src/client.dart | 37 ++--- pkgs/oauth2/lib/src/credentials.dart | 75 ++++----- .../lib/src/handle_access_token_response.dart | 8 +- pkgs/oauth2/lib/src/utils.dart | 63 +++++++- pkgs/oauth2/pubspec.yaml | 4 +- .../test/authorization_code_grant_test.dart | 76 +-------- pkgs/oauth2/test/client_test.dart | 55 ++----- pkgs/oauth2/test/credentials_test.dart | 152 ++---------------- pkgs/oauth2/test/utils_test.dart | 88 ++++++++++ 12 files changed, 244 insertions(+), 411 deletions(-) create mode 100644 pkgs/oauth2/test/utils_test.dart diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md index 686b6acac..fc869c940 100644 --- a/pkgs/oauth2/CHANGELOG.md +++ b/pkgs/oauth2/CHANGELOG.md @@ -1,35 +1,3 @@ -# 1.0.0 - -## Breaking changes - -* Requests that use client authentication, such as the - `AuthorizationCodeGrant`'s access token request and `Credentials`' refresh - request, now use HTTP Basic authentication by default. This form of - authentication is strongly recommended by the OAuth 2.0 spec. The new - `basicAuth` parameter may be set to `false` to force form-based authentication - for servers that require it. - -* `new AuthorizationCodeGrant()` now takes `secret` as an optional named - argument rather than a required argument. This matches the OAuth 2.0 spec, - which says that a client secret is only required for confidential clients. - -* `new Client()` and `Credentials.refresh()` now take both `identifier` and - `secret` as optional named arguments rather than required arguments. This - matches the OAuth 2.0 spec, which says that the server may choose not to - require client authentication for some flows. - -* `new Credentials()` now takes named arguments rather than optional positional - arguments. - -## Non-breaking changes - -* The `scopes` argument to `AuthorizationCodeGrant.getAuthorizationUrl()` and - `new Credentials()` and the `newScopes` argument to `Credentials.refresh` now - take an `Iterable` rather than just a `List`. - -* The `scopes` argument to `AuthorizationCodeGrant.getAuthorizationUrl()` now - defaults to `null` rather than `const []`. - # 0.9.3 * Update the `http` dependency. diff --git a/pkgs/oauth2/README.md b/pkgs/oauth2/README.md index 7e390cd4c..23be5ff20 100644 --- a/pkgs/oauth2/README.md +++ b/pkgs/oauth2/README.md @@ -64,15 +64,13 @@ Future getClient() async { if (exists) { var credentials = new oauth2.Credentials.fromJson( await credentialsFile.readAsString()); - return new oauth2.Client(credentials, - identifier: identifier, secret: secret); + return new oauth2.Client(identifier, secret, credentials); } // If we don't have OAuth2 credentials yet, we need to get the resource owner // to authorize us. We're assuming here that we're a command-line application. var grant = new oauth2.AuthorizationCodeGrant( - identifier, authorizationEndpoint, tokenEndpoint, - secret: secret); + identifier, secret, authorizationEndpoint, tokenEndpoint); // Redirect the resource owner to the authorization URL. This will be a URL on // the authorization server (authorizationEndpoint with some additional query diff --git a/pkgs/oauth2/lib/src/authorization_code_grant.dart b/pkgs/oauth2/lib/src/authorization_code_grant.dart index 7a73d44ab..e3bb64538 100644 --- a/pkgs/oauth2/lib/src/authorization_code_grant.dart +++ b/pkgs/oauth2/lib/src/authorization_code_grant.dart @@ -68,9 +68,6 @@ class AuthorizationCodeGrant { /// documentation. final Uri tokenEndpoint; - /// Whether to use HTTP Basic authentication for authorizing the client. - final bool _basicAuth; - /// The HTTP client used to make HTTP requests. http.Client _httpClient; @@ -90,23 +87,15 @@ class AuthorizationCodeGrant { /// Creates a new grant. /// - /// If [basicAuth] is `true` (the default), the client credentials are sent to - /// the server using using HTTP Basic authentication as defined in [RFC 2617]. - /// Otherwise, they're included in the request body. Note that the latter form - /// is not recommended by the OAuth 2.0 spec, and should only be used if the - /// server doesn't support Basic authentication. - /// - /// [RFC 2617]: https://tools.ietf.org/html/rfc2617 - /// /// [httpClient] is used for all HTTP requests made by this grant, as well as /// those of the [Client] is constructs. AuthorizationCodeGrant( this.identifier, + this.secret, this.authorizationEndpoint, this.tokenEndpoint, - {this.secret, bool basicAuth: true, http.Client httpClient}) - : _basicAuth = basicAuth, - _httpClient = httpClient == null ? new http.Client() : httpClient; + {http.Client httpClient}) + : _httpClient = httpClient == null ? new http.Client() : httpClient; /// Returns the URL to which the resource owner should be redirected to /// authorize this client. @@ -127,19 +116,13 @@ class AuthorizationCodeGrant { /// query parameters provided to the redirect URL. /// /// It is a [StateError] to call this more than once. - Uri getAuthorizationUrl(Uri redirect, {Iterable scopes, - String state}) { + Uri getAuthorizationUrl(Uri redirect, + {List scopes: const [], String state}) { if (_state != _State.initial) { throw new StateError('The authorization URL has already been generated.'); } _state = _State.awaitingResponse; - if (scopes == null) { - scopes = []; - } else { - scopes = scopes.toList(); - } - this._redirectEndpoint = redirect; this._scopes = scopes; this._stateString = state; @@ -241,35 +224,21 @@ class AuthorizationCodeGrant { /// the state beforehand. Future _handleAuthorizationCode(String authorizationCode) async { var startTime = new DateTime.now(); - - var headers = {}; - - var body = { + var response = await _httpClient.post(this.tokenEndpoint, body: { "grant_type": "authorization_code", "code": authorizationCode, - "redirect_uri": this._redirectEndpoint.toString() - }; - - if (_basicAuth && secret != null) { - headers["Authorization"] = basicAuthHeader(identifier, secret); - } else { - // The ID is required for this request any time basic auth isn't being - // used, even if there's no actual client authentication to be done. - body["client_id"] = identifier; - if (secret != null) body["client_secret"] = secret; - } - - var response = await _httpClient.post(this.tokenEndpoint, - headers: headers, body: body); + "redirect_uri": this._redirectEndpoint.toString(), + // TODO(nweiz): the spec recommends that HTTP basic auth be used in + // preference to form parameters, but Google doesn't support that. Should + // it be configurable? + "client_id": this.identifier, + "client_secret": this.secret + }); var credentials = handleAccessTokenResponse( response, tokenEndpoint, startTime, _scopes); return new Client( - credentials, - identifier: this.identifier, - secret: this.secret, - basicAuth: _basicAuth, - httpClient: _httpClient); + this.identifier, this.secret, credentials, httpClient: _httpClient); } /// Closes the grant and frees its resources. diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart index adf53d526..371eed309 100644 --- a/pkgs/oauth2/lib/src/client.dart +++ b/pkgs/oauth2/lib/src/client.dart @@ -7,7 +7,6 @@ library oauth2.client; import 'dart:async'; import 'package:http/http.dart' as http; -import 'package:http_parser/http_parser.dart'; import 'authorization_exception.dart'; import 'credentials.dart'; @@ -70,9 +69,6 @@ class Client extends http.BaseClient { Credentials get credentials => _credentials; Credentials _credentials; - /// Whether to use HTTP Basic authentication for authorizing the client. - final bool _basicAuth; - /// The underlying HTTP client. http.Client _httpClient; @@ -83,16 +79,12 @@ class Client extends http.BaseClient { /// /// [httpClient] is the underlying client that this forwards requests to after /// adding authorization credentials to them. - /// - /// Throws an [ArgumentError] if [secret] is passed without [identifier]. - Client(this._credentials, {this.identifier, this.secret, - bool basicAuth: true, http.Client httpClient}) - : _basicAuth = basicAuth, - _httpClient = httpClient == null ? new http.Client() : httpClient { - if (identifier == null && secret != null) { - throw new ArgumentError("secret may not be passed without identifier."); - } - } + Client( + this.identifier, + this.secret, + this._credentials, + {http.Client httpClient}) + : _httpClient = httpClient == null ? new http.Client() : httpClient; /// Sends an HTTP request with OAuth2 authorization credentials attached. /// @@ -110,19 +102,17 @@ class Client extends http.BaseClient { if (response.statusCode != 401) return response; if (!response.headers.containsKey('www-authenticate')) return response; - var challenges; + var authenticate; try { - challenges = AuthenticationChallenge.parseHeader( + authenticate = new AuthenticateHeader.parse( response.headers['www-authenticate']); } on FormatException catch (_) { return response; } - var challenge = challenges.firstWhere( - (challenge) => challenge.scheme == 'bearer', orElse: () => null); - if (challenge == null) return response; + if (authenticate.scheme != 'bearer') return response; - var params = challenge.parameters; + var params = authenticate.parameters; if (!params.containsKey('error')) return response; throw new AuthorizationException( @@ -147,11 +137,8 @@ class Client extends http.BaseClient { } _credentials = await credentials.refresh( - identifier: identifier, - secret: secret, - newScopes: newScopes, - basicAuth: _basicAuth, - httpClient: _httpClient); + identifier, secret, + newScopes: newScopes, httpClient: _httpClient); return this; } diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index 0a12f3f88..37e711487 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -5,13 +5,11 @@ library oauth2.credentials; import 'dart:async'; -import 'dart:collection'; import 'dart:convert'; import 'package:http/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. @@ -74,15 +72,11 @@ class Credentials { /// [AuthorizationCodeGrant]. Alternately, it may be loaded from a serialized /// form via [Credentials.fromJson]. Credentials( - this.accessToken, - {this.refreshToken, - this.tokenEndpoint, - Iterable scopes, - this.expiration}) - : scopes = new UnmodifiableListView( - // Explicitly type-annotate the list literal to work around - // sdk#24202. - scopes == null ? [] : scopes.toList()); + this.accessToken, + [this.refreshToken, + this.tokenEndpoint, + this.scopes, + this.expiration]); /// Loads a set of credentials from a JSON-serialized form. /// @@ -132,10 +126,10 @@ class Credentials { return new Credentials( parsed['accessToken'], - refreshToken: parsed['refreshToken'], - tokenEndpoint: tokenEndpoint, - scopes: scopes, - expiration: expiration); + parsed['refreshToken'], + tokenEndpoint, + scopes, + expiration); } /// Serializes a set of credentials to JSON. @@ -158,25 +152,19 @@ class Credentials { /// You may request different scopes than the default by passing in /// [newScopes]. These must be a subset of [scopes]. /// - /// This throws an [ArgumentError] if [secret] is passed without [identifier], - /// a [StateError] if these credentials can't be refreshed, an + /// 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 refresh( - {String identifier, + String identifier, String secret, - Iterable newScopes, - bool basicAuth: true, - http.Client httpClient}) async { + {List newScopes, + http.Client httpClient}) async { var scopes = this.scopes; - if (newScopes != null) scopes = newScopes.toList(); - if (scopes == null) scopes = []; + if (newScopes != null) scopes = newScopes; + if (scopes == null) scopes = []; if (httpClient == null) httpClient = new http.Client(); - if (identifier == null && secret != null) { - throw new ArgumentError("secret may not be passed without identifier."); - } - var startTime = new DateTime.now(); if (refreshToken == null) { throw new StateError("Can't refresh credentials without a refresh " @@ -186,34 +174,29 @@ class Credentials { "endpoint."); } - var headers = {}; - - var body = { + var fields = { "grant_type": "refresh_token", - "refresh_token": refreshToken + "refresh_token": refreshToken, + // TODO(nweiz): the spec recommends that HTTP basic auth be used in + // preference to form parameters, but Google doesn't support that. + // Should it be configurable? + "client_id": identifier, + "client_secret": secret }; - if (!scopes.isEmpty) body["scope"] = scopes.join(' '); - - if (basicAuth && secret != null) { - headers["Authorization"] = basicAuthHeader(identifier, secret); - } else { - if (identifier != null) body["client_id"] = identifier; - if (secret != null) body["client_secret"] = secret; - } + if (!scopes.isEmpty) fields["scope"] = scopes.join(' '); - var response = await httpClient.post(tokenEndpoint, - headers: headers, body: body); + var response = await httpClient.post(tokenEndpoint, body: fields); var credentials = await handleAccessTokenResponse( - response, tokenEndpoint, startTime, scopes); + response, tokenEndpoint, startTime, scopes); // The authorization server may issue a new refresh token. If it doesn't, // we should re-use the one we already have. if (credentials.refreshToken != null) return credentials; return new Credentials( credentials.accessToken, - refreshToken: this.refreshToken, - tokenEndpoint: credentials.tokenEndpoint, - scopes: credentials.scopes, - expiration: credentials.expiration); + this.refreshToken, + credentials.tokenEndpoint, + credentials.scopes, + credentials.expiration); } } diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart index ef9b198f4..0065f0c67 100644 --- a/pkgs/oauth2/lib/src/handle_access_token_response.dart +++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart @@ -81,10 +81,10 @@ Credentials handleAccessTokenResponse( return new Credentials( parameters['access_token'], - refreshToken: parameters['refresh_token'], - tokenEndpoint: tokenEndpoint, - scopes: scopes, - expiration: expiration); + parameters['refresh_token'], + tokenEndpoint, + scopes, + expiration); } /// Throws the appropriate exception for an error response from the diff --git a/pkgs/oauth2/lib/src/utils.dart b/pkgs/oauth2/lib/src/utils.dart index bf260eb73..734c58e26 100644 --- a/pkgs/oauth2/lib/src/utils.dart +++ b/pkgs/oauth2/lib/src/utils.dart @@ -4,16 +4,65 @@ library oauth2.utils; -import 'dart:convert'; - -import 'package:crypto/crypto.dart'; - /// Adds additional query parameters to [url], overwriting the original /// parameters if a name conflict occurs. Uri addQueryParameters(Uri url, Map parameters) => url.replace( queryParameters: new Map.from(url.queryParameters)..addAll(parameters)); -String basicAuthHeader(String identifier, String secret) { - var userPass = Uri.encodeFull(identifier) + ":" + Uri.encodeFull(secret); - return "Basic " + CryptoUtils.bytesToBase64(ASCII.encode(userPass)); +/// Like [String.split], but only splits on the first occurrence of the pattern. +/// +/// This will always return a list of two elements or fewer. +List split1(String toSplit, String pattern) { + if (toSplit.isEmpty) return []; + + var index = toSplit.indexOf(pattern); + if (index == -1) return [toSplit]; + return [toSplit.substring(0, index), + toSplit.substring(index + pattern.length)]; +} + +/// A WWW-Authenticate header value, parsed as per [RFC 2617][]. +/// +/// [RFC 2617]: http://tools.ietf.org/html/rfc2617 +class AuthenticateHeader { + final String scheme; + final Map parameters; + + AuthenticateHeader(this.scheme, this.parameters); + + /// Parses a header string. Throws a [FormatException] if the header is + /// invalid. + factory AuthenticateHeader.parse(String header) { + var split = split1(header, ' '); + if (split.length == 0) { + throw new FormatException('Invalid WWW-Authenticate header: "$header"'); + } else if (split.length == 1 || split[1].trim().isEmpty) { + return new AuthenticateHeader(split[0].toLowerCase(), {}); + } + var scheme = split[0].toLowerCase(); + var paramString = split[1]; + + // From http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html. + var tokenChar = r'[^\0-\x1F()<>@,;:\\"/\[\]?={} \t\x7F]'; + var quotedStringChar = r'(?:[^\0-\x1F\x7F"]|\\.)'; + var regexp = new RegExp('^ *($tokenChar+)="($quotedStringChar*)" *(, *)?'); + + var parameters = {}; + var match; + do { + match = regexp.firstMatch(paramString); + if (match == null) { + throw new FormatException('Invalid WWW-Authenticate header: "$header"'); + } + + paramString = paramString.substring(match.end); + parameters[match.group(1).toLowerCase()] = match.group(2); + } while (match.group(3) != null); + + if (!paramString.trim().isEmpty) { + throw new FormatException('Invalid WWW-Authenticate header: "$header"'); + } + + return new AuthenticateHeader(scheme, parameters); + } } diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index f52b8befa..e40265aa5 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,5 @@ name: oauth2 -version: 1.0.0-dev +version: 0.9.4-dev author: Dart Team homepage: http://github.com/dart-lang/oauth2 description: > @@ -10,6 +10,6 @@ environment: sdk: '>=1.9.0 <2.0.0' dependencies: http: '>=0.11.0 <0.12.0' - http_parser: '^1.0.0' + http_parser: '>=0.0.0 <0.1.0' dev_dependencies: test: '>=0.12.0 <0.13.0' diff --git a/pkgs/oauth2/test/authorization_code_grant_test.dart b/pkgs/oauth2/test/authorization_code_grant_test.dart index e03a60759..deaad5c14 100644 --- a/pkgs/oauth2/test/authorization_code_grant_test.dart +++ b/pkgs/oauth2/test/authorization_code_grant_test.dart @@ -20,9 +20,9 @@ void main() { client = new ExpectClient(); grant = new oauth2.AuthorizationCodeGrant( 'identifier', + 'secret', Uri.parse('https://example.com/authorization'), Uri.parse('https://example.com/token'), - secret: 'secret', httpClient: client); }); @@ -60,9 +60,9 @@ void main() { test('merges with existing query parameters', () { grant = new oauth2.AuthorizationCodeGrant( 'identifier', + 'secret', Uri.parse('https://example.com/authorization?query=value'), Uri.parse('https://example.com/token'), - secret: 'secret', httpClient: client); var authorizationUrl = grant.getAuthorizationUrl(redirectUrl); @@ -127,11 +127,10 @@ void main() { expect(request.bodyFields, equals({ 'grant_type': 'authorization_code', 'code': 'auth code', - 'redirect_uri': redirectUrl.toString() + 'redirect_uri': redirectUrl.toString(), + 'client_id': 'identifier', + 'client_secret': 'secret' })); - expect(request.headers, containsPair( - "Authorization", - "Basic aWRlbnRpZmllcjpzZWNyZXQ=")); return new Future.value(new http.Response(JSON.encode({ 'access_token': 'access token', @@ -157,71 +156,6 @@ void main() { }); test('sends an authorization code request', () { - grant.getAuthorizationUrl(redirectUrl); - client.expectRequest((request) { - expect(request.method, equals('POST')); - expect(request.url.toString(), equals(grant.tokenEndpoint.toString())); - expect(request.bodyFields, equals({ - 'grant_type': 'authorization_code', - 'code': 'auth code', - 'redirect_uri': redirectUrl.toString() - })); - expect(request.headers, containsPair( - "Authorization", - "Basic aWRlbnRpZmllcjpzZWNyZXQ=")); - - return new Future.value(new http.Response(JSON.encode({ - 'access_token': 'access token', - 'token_type': 'bearer', - }), 200, headers: {'content-type': 'application/json'})); - }); - - expect(grant.handleAuthorizationCode('auth code'), - completion(predicate((client) { - expect(client.credentials.accessToken, equals('access token')); - return true; - }))); - }); - }); - - group("with basicAuth: false", () { - setUp(() { - client = new ExpectClient(); - grant = new oauth2.AuthorizationCodeGrant( - 'identifier', - Uri.parse('https://example.com/authorization'), - Uri.parse('https://example.com/token'), - secret: 'secret', - basicAuth: false, - httpClient: client); - }); - - test('.handleAuthorizationResponse sends an authorization code request', - () { - grant.getAuthorizationUrl(redirectUrl); - client.expectRequest((request) { - expect(request.method, equals('POST')); - expect(request.url.toString(), equals(grant.tokenEndpoint.toString())); - expect(request.bodyFields, equals({ - 'grant_type': 'authorization_code', - 'code': 'auth code', - 'redirect_uri': redirectUrl.toString(), - 'client_id': 'identifier', - 'client_secret': 'secret' - })); - - return new Future.value(new http.Response(JSON.encode({ - 'access_token': 'access token', - 'token_type': 'bearer', - }), 200, headers: {'content-type': 'application/json'})); - }); - - expect(grant.handleAuthorizationResponse({'code': 'auth code'}) - .then((client) => client.credentials.accessToken), - completion(equals('access token'))); - }); - - test('.handleAuthorizationCode sends an authorization code request', () { grant.getAuthorizationUrl(redirectUrl); client.expectRequest((request) { expect(request.method, equals('POST')); diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart index 7dc5afd2e..969787a94 100644 --- a/pkgs/oauth2/test/client_test.dart +++ b/pkgs/oauth2/test/client_test.dart @@ -23,10 +23,8 @@ void main() { test("that can't be refreshed throws an ExpirationException on send", () { var expiration = new DateTime.now().subtract(new Duration(hours: 1)); var credentials = new oauth2.Credentials( - 'access token', expiration: expiration); - var client = new oauth2.Client(credentials, - identifier: 'identifier', - secret: 'secret', + 'access token', null, null, [], expiration); + var client = new oauth2.Client('identifier', 'secret', credentials, httpClient: httpClient); expect(client.get(requestUri), @@ -37,13 +35,8 @@ void main() { "request", () async { var expiration = new DateTime.now().subtract(new Duration(hours: 1)); var credentials = new oauth2.Credentials( - 'access token', - refreshToken: 'refresh token', - tokenEndpoint: tokenEndpoint, - expiration: expiration); - var client = new oauth2.Client(credentials, - identifier: 'identifier', - secret: 'secret', + 'access token', 'refresh token', tokenEndpoint, [], expiration); + var client = new oauth2.Client('identifier', 'secret', credentials, httpClient: httpClient); httpClient.expectRequest((request) { @@ -72,9 +65,7 @@ void main() { group('with valid credentials', () { test("sends a request with bearer authorization", () { var credentials = new oauth2.Credentials('access token'); - var client = new oauth2.Client(credentials, - identifier: 'identifier', - secret: 'secret', + var client = new oauth2.Client('identifier', 'secret', credentials, httpClient: httpClient); httpClient.expectRequest((request) { @@ -90,12 +81,8 @@ void main() { test("can manually refresh the credentials", () async { var credentials = new oauth2.Credentials( - 'access token', - refreshToken: 'refresh token', - tokenEndpoint: tokenEndpoint); - var client = new oauth2.Client(credentials, - identifier: 'identifier', - secret: 'secret', + 'access token', 'refresh token', tokenEndpoint); + var client = new oauth2.Client('identifier', 'secret', credentials, httpClient: httpClient); httpClient.expectRequest((request) { @@ -113,9 +100,7 @@ void main() { test("without a refresh token can't manually refresh the credentials", () { var credentials = new oauth2.Credentials('access token'); - var client = new oauth2.Client(credentials, - identifier: 'identifier', - secret: 'secret', + var client = new oauth2.Client('identifier', 'secret', credentials, httpClient: httpClient); expect(client.refreshCredentials(), throwsA(isStateError)); @@ -125,9 +110,7 @@ void main() { group('with invalid credentials', () { test('throws an AuthorizationException for a 401 response', () { var credentials = new oauth2.Credentials('access token'); - var client = new oauth2.Client(credentials, - identifier: 'identifier', - secret: 'secret', + var client = new oauth2.Client('identifier', 'secret', credentials, httpClient: httpClient); httpClient.expectRequest((request) { @@ -147,9 +130,7 @@ void main() { test('passes through a 401 response without www-authenticate', () async { var credentials = new oauth2.Credentials('access token'); - var client = new oauth2.Client(credentials, - identifier: 'identifier', - secret: 'secret', + var client = new oauth2.Client('identifier', 'secret', credentials, httpClient: httpClient); httpClient.expectRequest((request) { @@ -167,9 +148,7 @@ void main() { test('passes through a 401 response with invalid www-authenticate', () async { var credentials = new oauth2.Credentials('access token'); - var client = new oauth2.Client(credentials, - identifier: 'identifier', - secret: 'secret', + var client = new oauth2.Client('identifier', 'secret', credentials, httpClient: httpClient); httpClient.expectRequest((request) { @@ -178,8 +157,8 @@ void main() { expect(request.headers['authorization'], equals('Bearer access token')); - var authenticate = 'Bearer error="invalid_token" error_description=' - '"Something is terribly wrong."'; + var authenticate = 'Bearer error="invalid_token", error_description=' + '"Something is terribly wrong.", '; return new Future.value(new http.Response('bad job', 401, headers: {'www-authenticate': authenticate})); }); @@ -190,9 +169,7 @@ void main() { test('passes through a 401 response with non-bearer www-authenticate', () async { var credentials = new oauth2.Credentials('access token'); - var client = new oauth2.Client(credentials, - identifier: 'identifier', - secret: 'secret', + var client = new oauth2.Client('identifier', 'secret', credentials, httpClient: httpClient); httpClient.expectRequest((request) { @@ -211,9 +188,7 @@ void main() { test('passes through a 401 response with non-OAuth2 www-authenticate', () async { var credentials = new oauth2.Credentials('access token'); - var client = new oauth2.Client(credentials, - identifier: 'identifier', - secret: 'secret', + var client = new oauth2.Client('identifier', 'secret', credentials, httpClient: httpClient); httpClient.expectRequest((request) { diff --git a/pkgs/oauth2/test/credentials_test.dart b/pkgs/oauth2/test/credentials_test.dart index f4d8886e8..d72614f01 100644 --- a/pkgs/oauth2/test/credentials_test.dart +++ b/pkgs/oauth2/test/credentials_test.dart @@ -25,82 +25,37 @@ void main() { test('is not expired if the expiration is in the future', () { var expiration = new DateTime.now().add(new Duration(hours: 1)); var credentials = new oauth2.Credentials( - 'access token', expiration: expiration); + 'access token', null, null, null, expiration); expect(credentials.isExpired, isFalse); }); test('is expired if the expiration is in the past', () { var expiration = new DateTime.now().subtract(new Duration(hours: 1)); var credentials = new oauth2.Credentials( - 'access token', expiration: expiration); + 'access token', null, null, null, expiration); expect(credentials.isExpired, isTrue); }); test("can't refresh without a refresh token", () { var credentials = new oauth2.Credentials( - 'access token', tokenEndpoint: tokenEndpoint); + 'access token', null, tokenEndpoint); expect(credentials.canRefresh, false); - expect(credentials.refresh( - identifier: 'identifier', - secret: 'secret', - httpClient: httpClient), + expect(credentials.refresh('identifier', 'secret', httpClient: httpClient), throwsStateError); }); test("can't refresh without a token endpoint", () { - var credentials = new oauth2.Credentials( - 'access token', refreshToken: 'refresh token'); + var credentials = new oauth2.Credentials('access token', 'refresh token'); expect(credentials.canRefresh, false); - expect(credentials.refresh( - identifier: 'identifier', - secret: 'secret', - httpClient: httpClient), + expect(credentials.refresh('identifier', 'secret', httpClient: httpClient), throwsStateError); }); test("can refresh with a refresh token and a token endpoint", () async { var credentials = new oauth2.Credentials( - 'access token', - refreshToken: 'refresh token', - tokenEndpoint: tokenEndpoint, - scopes: ['scope1', 'scope2']); - expect(credentials.canRefresh, true); - - httpClient.expectRequest((request) { - expect(request.method, equals('POST')); - expect(request.url.toString(), equals(tokenEndpoint.toString())); - expect(request.bodyFields, equals({ - "grant_type": "refresh_token", - "refresh_token": "refresh token", - "scope": "scope1 scope2" - })); - expect(request.headers, containsPair( - "Authorization", - "Basic aWQlQzMlQUJudCVDNCVBQmZpZXI6cyVDMyVBQmNyZXQ=")); - - return new Future.value(new http.Response(JSON.encode({ - 'access_token': 'new access token', - 'token_type': 'bearer', - 'refresh_token': 'new refresh token' - }), 200, headers: {'content-type': 'application/json'})); - }); - - credentials = await credentials.refresh( - identifier: 'idëntīfier', - secret: 'sëcret', - httpClient: httpClient); - expect(credentials.accessToken, equals('new access token')); - expect(credentials.refreshToken, equals('new refresh token')); - }); - - test("can refresh without a client secret", () async { - var credentials = new oauth2.Credentials( - 'access token', - refreshToken: 'refresh token', - tokenEndpoint: tokenEndpoint, - scopes: ['scope1', 'scope2']); + 'access token', 'refresh token', tokenEndpoint, ['scope1', 'scope2']); expect(credentials.canRefresh, true); httpClient.expectRequest((request) { @@ -110,7 +65,8 @@ void main() { "grant_type": "refresh_token", "refresh_token": "refresh token", "scope": "scope1 scope2", - "client_id": "identifier" + "client_id": "identifier", + "client_secret": "secret" })); return new Future.value(new http.Response(JSON.encode({ @@ -121,48 +77,15 @@ void main() { }); - credentials = await credentials.refresh( - identifier: 'identifier', + credentials = await credentials.refresh('identifier', 'secret', httpClient: httpClient); expect(credentials.accessToken, equals('new access token')); expect(credentials.refreshToken, equals('new refresh token')); }); - test("can refresh without client authentication", () async { - var credentials = new oauth2.Credentials( - 'access token', - refreshToken: 'refresh token', - tokenEndpoint: tokenEndpoint, - scopes: ['scope1', 'scope2']); - expect(credentials.canRefresh, true); - - httpClient.expectRequest((request) { - expect(request.method, equals('POST')); - expect(request.url.toString(), equals(tokenEndpoint.toString())); - expect(request.bodyFields, equals({ - "grant_type": "refresh_token", - "refresh_token": "refresh token", - "scope": "scope1 scope2" - })); - - return new Future.value(new http.Response(JSON.encode({ - 'access_token': 'new access token', - 'token_type': 'bearer', - 'refresh_token': 'new refresh token' - }), 200, headers: {'content-type': 'application/json'})); - }); - - - credentials = await credentials.refresh(httpClient: httpClient); - expect(credentials.accessToken, equals('new access token')); - expect(credentials.refreshToken, equals('new refresh token')); - }); - test("uses the old refresh token if a new one isn't provided", () async { var credentials = new oauth2.Credentials( - 'access token', - refreshToken: 'refresh token', - tokenEndpoint: tokenEndpoint); + 'access token', 'refresh token', tokenEndpoint); expect(credentials.canRefresh, true); httpClient.expectRequest((request) { @@ -170,11 +93,10 @@ void main() { expect(request.url.toString(), equals(tokenEndpoint.toString())); expect(request.bodyFields, equals({ "grant_type": "refresh_token", - "refresh_token": "refresh token" + "refresh_token": "refresh token", + "client_id": "identifier", + "client_secret": "secret" })); - expect(request.headers, containsPair( - "Authorization", - "Basic aWQlQzMlQUJudCVDNCVBQmZpZXI6cyVDMyVBQmNyZXQ=")); return new Future.value(new http.Response(JSON.encode({ 'access_token': 'new access token', @@ -183,49 +105,12 @@ void main() { }); - credentials = await credentials.refresh( - identifier: 'idëntīfier', - secret: 'sëcret', + credentials = await credentials.refresh('identifier', 'secret', httpClient: httpClient); expect(credentials.accessToken, equals('new access token')); expect(credentials.refreshToken, equals('refresh token')); }); - test("uses form-field authentication if basicAuth is false", () async { - var credentials = new oauth2.Credentials( - 'access token', - refreshToken: 'refresh token', - tokenEndpoint: tokenEndpoint, - scopes: ['scope1', 'scope2']); - expect(credentials.canRefresh, true); - - httpClient.expectRequest((request) { - expect(request.method, equals('POST')); - expect(request.url.toString(), equals(tokenEndpoint.toString())); - expect(request.bodyFields, equals({ - "grant_type": "refresh_token", - "refresh_token": "refresh token", - "scope": "scope1 scope2", - "client_id": "idëntīfier", - "client_secret": "sëcret" - })); - - return new Future.value(new http.Response(JSON.encode({ - 'access_token': 'new access token', - 'token_type': 'bearer', - 'refresh_token': 'new refresh token' - }), 200, headers: {'content-type': 'application/json'})); - }); - - credentials = await credentials.refresh( - identifier: 'idëntīfier', - secret: 'sëcret', - basicAuth: false, - httpClient: httpClient); - expect(credentials.accessToken, equals('new access token')); - expect(credentials.refreshToken, equals('new refresh token')); - }); - group("fromJson", () { oauth2.Credentials fromMap(Map map) => new oauth2.Credentials.fromJson(JSON.encode(map)); @@ -233,11 +118,8 @@ void main() { test("should load the same credentials from toJson", () { var expiration = new DateTime.now().subtract(new Duration(hours: 1)); var credentials = new oauth2.Credentials( - 'access token', - refreshToken: 'refresh token', - tokenEndpoint: tokenEndpoint, - scopes: ['scope1', 'scope2'], - expiration: expiration); + 'access token', 'refresh token', tokenEndpoint, ['scope1', 'scope2'], + expiration); var reloaded = new oauth2.Credentials.fromJson(credentials.toJson()); expect(reloaded.accessToken, equals(credentials.accessToken)); diff --git a/pkgs/oauth2/test/utils_test.dart b/pkgs/oauth2/test/utils_test.dart new file mode 100644 index 000000000..54c2da58e --- /dev/null +++ b/pkgs/oauth2/test/utils_test.dart @@ -0,0 +1,88 @@ +// 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. + +import 'package:oauth2/src/utils.dart'; +import 'package:test/test.dart'; + +void main() { + group('AuthenticateHeader', () { + test("parses a scheme", () { + var header = new AuthenticateHeader.parse('bearer'); + expect(header.scheme, equals('bearer')); + expect(header.parameters, equals({})); + }); + + test("lower-cases the scheme", () { + var header = new AuthenticateHeader.parse('BeaRer'); + expect(header.scheme, equals('bearer')); + expect(header.parameters, equals({})); + }); + + test("parses a scheme with trailing whitespace", () { + var header = new AuthenticateHeader.parse('bearer '); + expect(header.scheme, equals('bearer')); + expect(header.parameters, equals({})); + }); + + test("parses a scheme with one param", () { + var header = new AuthenticateHeader.parse('bearer foo="bar"'); + expect(header.scheme, equals('bearer')); + expect(header.parameters, equals({'foo': 'bar'})); + }); + + test("parses a scheme with several params", () { + var header = new AuthenticateHeader.parse( + 'bearer foo="bar", bar="baz" ,baz="qux"'); + expect(header.scheme, equals('bearer')); + expect(header.parameters, equals({ + 'foo': 'bar', + 'bar': 'baz', + 'baz': 'qux' + })); + }); + + test("lower-cases parameter names but not values", () { + var header = new AuthenticateHeader.parse('bearer FoO="bAr"'); + expect(header.scheme, equals('bearer')); + expect(header.parameters, equals({'foo': 'bAr'})); + }); + + test("allows empty values", () { + var header = new AuthenticateHeader.parse('bearer foo=""'); + expect(header.scheme, equals('bearer')); + expect(header.parameters, equals({'foo': ''})); + }); + + test("won't parse an empty string", () { + expect(() => new AuthenticateHeader.parse(''), + throwsFormatException); + }); + + test("won't parse a token without a value", () { + expect(() => new AuthenticateHeader.parse('bearer foo'), + throwsFormatException); + + expect(() => new AuthenticateHeader.parse('bearer foo='), + throwsFormatException); + }); + + test("won't parse a token without a value", () { + expect(() => new AuthenticateHeader.parse('bearer foo'), + throwsFormatException); + + expect(() => new AuthenticateHeader.parse('bearer foo='), + throwsFormatException); + }); + + test("won't parse a trailing comma", () { + expect(() => new AuthenticateHeader.parse('bearer foo="bar",'), + throwsFormatException); + }); + + test("won't parse a multiple params without a comma", () { + expect(() => new AuthenticateHeader.parse('bearer foo="bar" bar="baz"'), + throwsFormatException); + }); + }); +} From 9fa27f1c6597d04224501f5a360ab0bea165e572 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 26 Aug 2015 13:47:18 -0700 Subject: [PATCH 064/159] Make a bunch of API changes. These are generally very small; some of them are breaking, but it'll be easy for users to change over to the new APIs. The new APIs generally bring the package more in line with the OAuth 2.0 spec. The old API was narrowly focused on specifically connecting to Google's servers using an authorization code grant; the new APIs should be more flexible and consistent with more grant types. R=rnystrom@google.com Review URL: https://codereview.chromium.org//1320523003 . --- pkgs/oauth2/CHANGELOG.md | 32 ++++ pkgs/oauth2/README.md | 6 +- .../lib/src/authorization_code_grant.dart | 59 +++++-- pkgs/oauth2/lib/src/client.dart | 26 ++- pkgs/oauth2/lib/src/credentials.dart | 75 +++++---- .../lib/src/handle_access_token_response.dart | 8 +- pkgs/oauth2/lib/src/utils.dart | 9 ++ pkgs/oauth2/pubspec.yaml | 4 +- .../test/authorization_code_grant_test.dart | 76 ++++++++- pkgs/oauth2/test/client_test.dart | 51 ++++-- pkgs/oauth2/test/credentials_test.dart | 152 ++++++++++++++++-- 11 files changed, 404 insertions(+), 94 deletions(-) diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md index fc869c940..686b6acac 100644 --- a/pkgs/oauth2/CHANGELOG.md +++ b/pkgs/oauth2/CHANGELOG.md @@ -1,3 +1,35 @@ +# 1.0.0 + +## Breaking changes + +* Requests that use client authentication, such as the + `AuthorizationCodeGrant`'s access token request and `Credentials`' refresh + request, now use HTTP Basic authentication by default. This form of + authentication is strongly recommended by the OAuth 2.0 spec. The new + `basicAuth` parameter may be set to `false` to force form-based authentication + for servers that require it. + +* `new AuthorizationCodeGrant()` now takes `secret` as an optional named + argument rather than a required argument. This matches the OAuth 2.0 spec, + which says that a client secret is only required for confidential clients. + +* `new Client()` and `Credentials.refresh()` now take both `identifier` and + `secret` as optional named arguments rather than required arguments. This + matches the OAuth 2.0 spec, which says that the server may choose not to + require client authentication for some flows. + +* `new Credentials()` now takes named arguments rather than optional positional + arguments. + +## Non-breaking changes + +* The `scopes` argument to `AuthorizationCodeGrant.getAuthorizationUrl()` and + `new Credentials()` and the `newScopes` argument to `Credentials.refresh` now + take an `Iterable` rather than just a `List`. + +* The `scopes` argument to `AuthorizationCodeGrant.getAuthorizationUrl()` now + defaults to `null` rather than `const []`. + # 0.9.3 * Update the `http` dependency. diff --git a/pkgs/oauth2/README.md b/pkgs/oauth2/README.md index 23be5ff20..7e390cd4c 100644 --- a/pkgs/oauth2/README.md +++ b/pkgs/oauth2/README.md @@ -64,13 +64,15 @@ Future getClient() async { if (exists) { var credentials = new oauth2.Credentials.fromJson( await credentialsFile.readAsString()); - return new oauth2.Client(identifier, secret, credentials); + return new oauth2.Client(credentials, + identifier: identifier, secret: secret); } // If we don't have OAuth2 credentials yet, we need to get the resource owner // to authorize us. We're assuming here that we're a command-line application. var grant = new oauth2.AuthorizationCodeGrant( - identifier, secret, authorizationEndpoint, tokenEndpoint); + identifier, authorizationEndpoint, tokenEndpoint, + secret: secret); // Redirect the resource owner to the authorization URL. This will be a URL on // the authorization server (authorizationEndpoint with some additional query diff --git a/pkgs/oauth2/lib/src/authorization_code_grant.dart b/pkgs/oauth2/lib/src/authorization_code_grant.dart index e3bb64538..7a73d44ab 100644 --- a/pkgs/oauth2/lib/src/authorization_code_grant.dart +++ b/pkgs/oauth2/lib/src/authorization_code_grant.dart @@ -68,6 +68,9 @@ class AuthorizationCodeGrant { /// documentation. final Uri tokenEndpoint; + /// Whether to use HTTP Basic authentication for authorizing the client. + final bool _basicAuth; + /// The HTTP client used to make HTTP requests. http.Client _httpClient; @@ -87,15 +90,23 @@ class AuthorizationCodeGrant { /// Creates a new grant. /// + /// If [basicAuth] is `true` (the default), the client credentials are sent to + /// the server using using HTTP Basic authentication as defined in [RFC 2617]. + /// Otherwise, they're included in the request body. Note that the latter form + /// is not recommended by the OAuth 2.0 spec, and should only be used if the + /// server doesn't support Basic authentication. + /// + /// [RFC 2617]: https://tools.ietf.org/html/rfc2617 + /// /// [httpClient] is used for all HTTP requests made by this grant, as well as /// those of the [Client] is constructs. AuthorizationCodeGrant( this.identifier, - this.secret, this.authorizationEndpoint, this.tokenEndpoint, - {http.Client httpClient}) - : _httpClient = httpClient == null ? new http.Client() : httpClient; + {this.secret, bool basicAuth: true, http.Client httpClient}) + : _basicAuth = basicAuth, + _httpClient = httpClient == null ? new http.Client() : httpClient; /// Returns the URL to which the resource owner should be redirected to /// authorize this client. @@ -116,13 +127,19 @@ class AuthorizationCodeGrant { /// query parameters provided to the redirect URL. /// /// It is a [StateError] to call this more than once. - Uri getAuthorizationUrl(Uri redirect, - {List scopes: const [], String state}) { + Uri getAuthorizationUrl(Uri redirect, {Iterable scopes, + String state}) { if (_state != _State.initial) { throw new StateError('The authorization URL has already been generated.'); } _state = _State.awaitingResponse; + if (scopes == null) { + scopes = []; + } else { + scopes = scopes.toList(); + } + this._redirectEndpoint = redirect; this._scopes = scopes; this._stateString = state; @@ -224,21 +241,35 @@ class AuthorizationCodeGrant { /// the state beforehand. Future _handleAuthorizationCode(String authorizationCode) async { var startTime = new DateTime.now(); - var response = await _httpClient.post(this.tokenEndpoint, body: { + + var headers = {}; + + var body = { "grant_type": "authorization_code", "code": authorizationCode, - "redirect_uri": this._redirectEndpoint.toString(), - // TODO(nweiz): the spec recommends that HTTP basic auth be used in - // preference to form parameters, but Google doesn't support that. Should - // it be configurable? - "client_id": this.identifier, - "client_secret": this.secret - }); + "redirect_uri": this._redirectEndpoint.toString() + }; + + if (_basicAuth && secret != null) { + headers["Authorization"] = basicAuthHeader(identifier, secret); + } else { + // The ID is required for this request any time basic auth isn't being + // used, even if there's no actual client authentication to be done. + body["client_id"] = identifier; + if (secret != null) body["client_secret"] = secret; + } + + var response = await _httpClient.post(this.tokenEndpoint, + headers: headers, body: body); var credentials = handleAccessTokenResponse( response, tokenEndpoint, startTime, _scopes); return new Client( - this.identifier, this.secret, credentials, httpClient: _httpClient); + credentials, + identifier: this.identifier, + secret: this.secret, + basicAuth: _basicAuth, + httpClient: _httpClient); } /// Closes the grant and frees its resources. diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart index 371eed309..d604696e0 100644 --- a/pkgs/oauth2/lib/src/client.dart +++ b/pkgs/oauth2/lib/src/client.dart @@ -69,6 +69,9 @@ class Client extends http.BaseClient { Credentials get credentials => _credentials; Credentials _credentials; + /// Whether to use HTTP Basic authentication for authorizing the client. + final bool _basicAuth; + /// The underlying HTTP client. http.Client _httpClient; @@ -79,12 +82,16 @@ class Client extends http.BaseClient { /// /// [httpClient] is the underlying client that this forwards requests to after /// adding authorization credentials to them. - Client( - this.identifier, - this.secret, - this._credentials, - {http.Client httpClient}) - : _httpClient = httpClient == null ? new http.Client() : httpClient; + /// + /// Thrwos an [ArgumentError] if [secret] is passed without [identifier]. + Client(this._credentials, {this.identifier, this.secret, + bool basicAuth: true, http.Client httpClient}) + : _basicAuth = basicAuth, + _httpClient = httpClient == null ? new http.Client() : httpClient { + if (identifier == null && secret != null) { + throw new ArgumentError("secret may not be passed without identifier."); + } + } /// Sends an HTTP request with OAuth2 authorization credentials attached. /// @@ -137,8 +144,11 @@ class Client extends http.BaseClient { } _credentials = await credentials.refresh( - identifier, secret, - newScopes: newScopes, httpClient: _httpClient); + identifier: identifier, + secret: secret, + newScopes: newScopes, + basicAuth: _basicAuth, + httpClient: _httpClient); return this; } diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index 37e711487..0a12f3f88 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -5,11 +5,13 @@ library oauth2.credentials; import 'dart:async'; +import 'dart:collection'; import 'dart:convert'; import 'package:http/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. @@ -72,11 +74,15 @@ class Credentials { /// [AuthorizationCodeGrant]. Alternately, it may be loaded from a serialized /// form via [Credentials.fromJson]. Credentials( - this.accessToken, - [this.refreshToken, - this.tokenEndpoint, - this.scopes, - this.expiration]); + this.accessToken, + {this.refreshToken, + this.tokenEndpoint, + Iterable scopes, + this.expiration}) + : scopes = new UnmodifiableListView( + // Explicitly type-annotate the list literal to work around + // sdk#24202. + scopes == null ? [] : scopes.toList()); /// Loads a set of credentials from a JSON-serialized form. /// @@ -126,10 +132,10 @@ class Credentials { return new Credentials( parsed['accessToken'], - parsed['refreshToken'], - tokenEndpoint, - scopes, - expiration); + refreshToken: parsed['refreshToken'], + tokenEndpoint: tokenEndpoint, + scopes: scopes, + expiration: expiration); } /// Serializes a set of credentials to JSON. @@ -152,19 +158,25 @@ class Credentials { /// 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 + /// This throws an [ArgumentError] if [secret] is passed without [identifier], + /// 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 refresh( - String identifier, + {String identifier, String secret, - {List newScopes, - http.Client httpClient}) async { + Iterable newScopes, + bool basicAuth: true, + http.Client httpClient}) async { var scopes = this.scopes; - if (newScopes != null) scopes = newScopes; - if (scopes == null) scopes = []; + if (newScopes != null) scopes = newScopes.toList(); + if (scopes == null) scopes = []; if (httpClient == null) httpClient = new http.Client(); + if (identifier == null && secret != null) { + throw new ArgumentError("secret may not be passed without identifier."); + } + var startTime = new DateTime.now(); if (refreshToken == null) { throw new StateError("Can't refresh credentials without a refresh " @@ -174,29 +186,34 @@ class Credentials { "endpoint."); } - var fields = { + var headers = {}; + + var body = { "grant_type": "refresh_token", - "refresh_token": refreshToken, - // TODO(nweiz): the spec recommends that HTTP basic auth be used in - // preference to form parameters, but Google doesn't support that. - // Should it be configurable? - "client_id": identifier, - "client_secret": secret + "refresh_token": refreshToken }; - if (!scopes.isEmpty) fields["scope"] = scopes.join(' '); + if (!scopes.isEmpty) body["scope"] = scopes.join(' '); + + if (basicAuth && secret != null) { + headers["Authorization"] = basicAuthHeader(identifier, secret); + } else { + if (identifier != null) body["client_id"] = identifier; + if (secret != null) body["client_secret"] = secret; + } - var response = await httpClient.post(tokenEndpoint, body: fields); + var response = await httpClient.post(tokenEndpoint, + headers: headers, body: body); var credentials = await handleAccessTokenResponse( - response, tokenEndpoint, startTime, scopes); + response, tokenEndpoint, startTime, scopes); // The authorization server may issue a new refresh token. If it doesn't, // we should re-use the one we already have. if (credentials.refreshToken != null) return credentials; return new Credentials( credentials.accessToken, - this.refreshToken, - credentials.tokenEndpoint, - credentials.scopes, - credentials.expiration); + refreshToken: this.refreshToken, + tokenEndpoint: credentials.tokenEndpoint, + scopes: credentials.scopes, + expiration: credentials.expiration); } } diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart index 0065f0c67..ef9b198f4 100644 --- a/pkgs/oauth2/lib/src/handle_access_token_response.dart +++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart @@ -81,10 +81,10 @@ Credentials handleAccessTokenResponse( return new Credentials( parameters['access_token'], - parameters['refresh_token'], - tokenEndpoint, - scopes, - expiration); + refreshToken: parameters['refresh_token'], + tokenEndpoint: tokenEndpoint, + scopes: scopes, + expiration: expiration); } /// Throws the appropriate exception for an error response from the diff --git a/pkgs/oauth2/lib/src/utils.dart b/pkgs/oauth2/lib/src/utils.dart index 734c58e26..af19f9056 100644 --- a/pkgs/oauth2/lib/src/utils.dart +++ b/pkgs/oauth2/lib/src/utils.dart @@ -4,11 +4,20 @@ library oauth2.utils; +import 'dart:convert'; + +import 'package:crypto/crypto.dart'; + /// Adds additional query parameters to [url], overwriting the original /// parameters if a name conflict occurs. Uri addQueryParameters(Uri url, Map parameters) => url.replace( queryParameters: new Map.from(url.queryParameters)..addAll(parameters)); +String basicAuthHeader(String identifier, String secret) { + var userPass = Uri.encodeFull(identifier) + ":" + Uri.encodeFull(secret); + return "Basic " + CryptoUtils.bytesToBase64(ASCII.encode(userPass)); +} + /// Like [String.split], but only splits on the first occurrence of the pattern. /// /// This will always return a list of two elements or fewer. diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index e40265aa5..ce4bd4a90 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,5 @@ name: oauth2 -version: 0.9.4-dev +version: 1.0.0-dev author: Dart Team homepage: http://github.com/dart-lang/oauth2 description: > @@ -10,6 +10,6 @@ environment: sdk: '>=1.9.0 <2.0.0' dependencies: http: '>=0.11.0 <0.12.0' - http_parser: '>=0.0.0 <0.1.0' + http_parser: '>=0.0.0 <2.0.0' dev_dependencies: test: '>=0.12.0 <0.13.0' diff --git a/pkgs/oauth2/test/authorization_code_grant_test.dart b/pkgs/oauth2/test/authorization_code_grant_test.dart index deaad5c14..e03a60759 100644 --- a/pkgs/oauth2/test/authorization_code_grant_test.dart +++ b/pkgs/oauth2/test/authorization_code_grant_test.dart @@ -20,9 +20,9 @@ void main() { client = new ExpectClient(); grant = new oauth2.AuthorizationCodeGrant( 'identifier', - 'secret', Uri.parse('https://example.com/authorization'), Uri.parse('https://example.com/token'), + secret: 'secret', httpClient: client); }); @@ -60,9 +60,9 @@ void main() { test('merges with existing query parameters', () { grant = new oauth2.AuthorizationCodeGrant( 'identifier', - 'secret', Uri.parse('https://example.com/authorization?query=value'), Uri.parse('https://example.com/token'), + secret: 'secret', httpClient: client); var authorizationUrl = grant.getAuthorizationUrl(redirectUrl); @@ -127,10 +127,11 @@ void main() { expect(request.bodyFields, equals({ 'grant_type': 'authorization_code', 'code': 'auth code', - 'redirect_uri': redirectUrl.toString(), - 'client_id': 'identifier', - 'client_secret': 'secret' + 'redirect_uri': redirectUrl.toString() })); + expect(request.headers, containsPair( + "Authorization", + "Basic aWRlbnRpZmllcjpzZWNyZXQ=")); return new Future.value(new http.Response(JSON.encode({ 'access_token': 'access token', @@ -156,6 +157,71 @@ void main() { }); test('sends an authorization code request', () { + grant.getAuthorizationUrl(redirectUrl); + client.expectRequest((request) { + expect(request.method, equals('POST')); + expect(request.url.toString(), equals(grant.tokenEndpoint.toString())); + expect(request.bodyFields, equals({ + 'grant_type': 'authorization_code', + 'code': 'auth code', + 'redirect_uri': redirectUrl.toString() + })); + expect(request.headers, containsPair( + "Authorization", + "Basic aWRlbnRpZmllcjpzZWNyZXQ=")); + + return new Future.value(new http.Response(JSON.encode({ + 'access_token': 'access token', + 'token_type': 'bearer', + }), 200, headers: {'content-type': 'application/json'})); + }); + + expect(grant.handleAuthorizationCode('auth code'), + completion(predicate((client) { + expect(client.credentials.accessToken, equals('access token')); + return true; + }))); + }); + }); + + group("with basicAuth: false", () { + setUp(() { + client = new ExpectClient(); + grant = new oauth2.AuthorizationCodeGrant( + 'identifier', + Uri.parse('https://example.com/authorization'), + Uri.parse('https://example.com/token'), + secret: 'secret', + basicAuth: false, + httpClient: client); + }); + + test('.handleAuthorizationResponse sends an authorization code request', + () { + grant.getAuthorizationUrl(redirectUrl); + client.expectRequest((request) { + expect(request.method, equals('POST')); + expect(request.url.toString(), equals(grant.tokenEndpoint.toString())); + expect(request.bodyFields, equals({ + 'grant_type': 'authorization_code', + 'code': 'auth code', + 'redirect_uri': redirectUrl.toString(), + 'client_id': 'identifier', + 'client_secret': 'secret' + })); + + return new Future.value(new http.Response(JSON.encode({ + 'access_token': 'access token', + 'token_type': 'bearer', + }), 200, headers: {'content-type': 'application/json'})); + }); + + expect(grant.handleAuthorizationResponse({'code': 'auth code'}) + .then((client) => client.credentials.accessToken), + completion(equals('access token'))); + }); + + test('.handleAuthorizationCode sends an authorization code request', () { grant.getAuthorizationUrl(redirectUrl); client.expectRequest((request) { expect(request.method, equals('POST')); diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart index 969787a94..885f83e4a 100644 --- a/pkgs/oauth2/test/client_test.dart +++ b/pkgs/oauth2/test/client_test.dart @@ -23,8 +23,10 @@ void main() { test("that can't be refreshed throws an ExpirationException on send", () { var expiration = new DateTime.now().subtract(new Duration(hours: 1)); var credentials = new oauth2.Credentials( - 'access token', null, null, [], expiration); - var client = new oauth2.Client('identifier', 'secret', credentials, + 'access token', expiration: expiration); + var client = new oauth2.Client(credentials, + identifier: 'identifier', + secret: 'secret', httpClient: httpClient); expect(client.get(requestUri), @@ -35,8 +37,13 @@ void main() { "request", () async { var expiration = new DateTime.now().subtract(new Duration(hours: 1)); var credentials = new oauth2.Credentials( - 'access token', 'refresh token', tokenEndpoint, [], expiration); - var client = new oauth2.Client('identifier', 'secret', credentials, + 'access token', + refreshToken: 'refresh token', + tokenEndpoint: tokenEndpoint, + expiration: expiration); + var client = new oauth2.Client(credentials, + identifier: 'identifier', + secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { @@ -65,7 +72,9 @@ void main() { group('with valid credentials', () { test("sends a request with bearer authorization", () { var credentials = new oauth2.Credentials('access token'); - var client = new oauth2.Client('identifier', 'secret', credentials, + var client = new oauth2.Client(credentials, + identifier: 'identifier', + secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { @@ -81,8 +90,12 @@ void main() { test("can manually refresh the credentials", () async { var credentials = new oauth2.Credentials( - 'access token', 'refresh token', tokenEndpoint); - var client = new oauth2.Client('identifier', 'secret', credentials, + 'access token', + refreshToken: 'refresh token', + tokenEndpoint: tokenEndpoint); + var client = new oauth2.Client(credentials, + identifier: 'identifier', + secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { @@ -100,7 +113,9 @@ void main() { test("without a refresh token can't manually refresh the credentials", () { var credentials = new oauth2.Credentials('access token'); - var client = new oauth2.Client('identifier', 'secret', credentials, + var client = new oauth2.Client(credentials, + identifier: 'identifier', + secret: 'secret', httpClient: httpClient); expect(client.refreshCredentials(), throwsA(isStateError)); @@ -110,7 +125,9 @@ void main() { group('with invalid credentials', () { test('throws an AuthorizationException for a 401 response', () { var credentials = new oauth2.Credentials('access token'); - var client = new oauth2.Client('identifier', 'secret', credentials, + var client = new oauth2.Client(credentials, + identifier: 'identifier', + secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { @@ -130,7 +147,9 @@ void main() { test('passes through a 401 response without www-authenticate', () async { var credentials = new oauth2.Credentials('access token'); - var client = new oauth2.Client('identifier', 'secret', credentials, + var client = new oauth2.Client(credentials, + identifier: 'identifier', + secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { @@ -148,7 +167,9 @@ void main() { test('passes through a 401 response with invalid www-authenticate', () async { var credentials = new oauth2.Credentials('access token'); - var client = new oauth2.Client('identifier', 'secret', credentials, + var client = new oauth2.Client(credentials, + identifier: 'identifier', + secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { @@ -169,7 +190,9 @@ void main() { test('passes through a 401 response with non-bearer www-authenticate', () async { var credentials = new oauth2.Credentials('access token'); - var client = new oauth2.Client('identifier', 'secret', credentials, + var client = new oauth2.Client(credentials, + identifier: 'identifier', + secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { @@ -188,7 +211,9 @@ void main() { test('passes through a 401 response with non-OAuth2 www-authenticate', () async { var credentials = new oauth2.Credentials('access token'); - var client = new oauth2.Client('identifier', 'secret', credentials, + var client = new oauth2.Client(credentials, + identifier: 'identifier', + secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { diff --git a/pkgs/oauth2/test/credentials_test.dart b/pkgs/oauth2/test/credentials_test.dart index d72614f01..f4d8886e8 100644 --- a/pkgs/oauth2/test/credentials_test.dart +++ b/pkgs/oauth2/test/credentials_test.dart @@ -25,37 +25,82 @@ void main() { test('is not expired if the expiration is in the future', () { var expiration = new DateTime.now().add(new Duration(hours: 1)); var credentials = new oauth2.Credentials( - 'access token', null, null, null, expiration); + 'access token', expiration: expiration); expect(credentials.isExpired, isFalse); }); test('is expired if the expiration is in the past', () { var expiration = new DateTime.now().subtract(new Duration(hours: 1)); var credentials = new oauth2.Credentials( - 'access token', null, null, null, expiration); + 'access token', expiration: expiration); expect(credentials.isExpired, isTrue); }); test("can't refresh without a refresh token", () { var credentials = new oauth2.Credentials( - 'access token', null, tokenEndpoint); + 'access token', tokenEndpoint: tokenEndpoint); expect(credentials.canRefresh, false); - expect(credentials.refresh('identifier', 'secret', httpClient: httpClient), + expect(credentials.refresh( + identifier: 'identifier', + secret: 'secret', + httpClient: httpClient), throwsStateError); }); test("can't refresh without a token endpoint", () { - var credentials = new oauth2.Credentials('access token', 'refresh token'); + var credentials = new oauth2.Credentials( + 'access token', refreshToken: 'refresh token'); expect(credentials.canRefresh, false); - expect(credentials.refresh('identifier', 'secret', httpClient: httpClient), + expect(credentials.refresh( + identifier: 'identifier', + secret: 'secret', + httpClient: httpClient), throwsStateError); }); test("can refresh with a refresh token and a token endpoint", () async { var credentials = new oauth2.Credentials( - 'access token', 'refresh token', tokenEndpoint, ['scope1', 'scope2']); + 'access token', + refreshToken: 'refresh token', + tokenEndpoint: tokenEndpoint, + scopes: ['scope1', 'scope2']); + expect(credentials.canRefresh, true); + + httpClient.expectRequest((request) { + expect(request.method, equals('POST')); + expect(request.url.toString(), equals(tokenEndpoint.toString())); + expect(request.bodyFields, equals({ + "grant_type": "refresh_token", + "refresh_token": "refresh token", + "scope": "scope1 scope2" + })); + expect(request.headers, containsPair( + "Authorization", + "Basic aWQlQzMlQUJudCVDNCVBQmZpZXI6cyVDMyVBQmNyZXQ=")); + + return new Future.value(new http.Response(JSON.encode({ + 'access_token': 'new access token', + 'token_type': 'bearer', + 'refresh_token': 'new refresh token' + }), 200, headers: {'content-type': 'application/json'})); + }); + + credentials = await credentials.refresh( + identifier: 'idëntīfier', + secret: 'sëcret', + httpClient: httpClient); + expect(credentials.accessToken, equals('new access token')); + expect(credentials.refreshToken, equals('new refresh token')); + }); + + test("can refresh without a client secret", () async { + var credentials = new oauth2.Credentials( + 'access token', + refreshToken: 'refresh token', + tokenEndpoint: tokenEndpoint, + scopes: ['scope1', 'scope2']); expect(credentials.canRefresh, true); httpClient.expectRequest((request) { @@ -65,8 +110,7 @@ void main() { "grant_type": "refresh_token", "refresh_token": "refresh token", "scope": "scope1 scope2", - "client_id": "identifier", - "client_secret": "secret" + "client_id": "identifier" })); return new Future.value(new http.Response(JSON.encode({ @@ -77,15 +121,19 @@ void main() { }); - credentials = await credentials.refresh('identifier', 'secret', + credentials = await credentials.refresh( + identifier: 'identifier', httpClient: httpClient); expect(credentials.accessToken, equals('new access token')); expect(credentials.refreshToken, equals('new refresh token')); }); - test("uses the old refresh token if a new one isn't provided", () async { + test("can refresh without client authentication", () async { var credentials = new oauth2.Credentials( - 'access token', 'refresh token', tokenEndpoint); + 'access token', + refreshToken: 'refresh token', + tokenEndpoint: tokenEndpoint, + scopes: ['scope1', 'scope2']); expect(credentials.canRefresh, true); httpClient.expectRequest((request) { @@ -94,9 +142,39 @@ void main() { expect(request.bodyFields, equals({ "grant_type": "refresh_token", "refresh_token": "refresh token", - "client_id": "identifier", - "client_secret": "secret" + "scope": "scope1 scope2" + })); + + return new Future.value(new http.Response(JSON.encode({ + 'access_token': 'new access token', + 'token_type': 'bearer', + 'refresh_token': 'new refresh token' + }), 200, headers: {'content-type': 'application/json'})); + }); + + + credentials = await credentials.refresh(httpClient: httpClient); + expect(credentials.accessToken, equals('new access token')); + expect(credentials.refreshToken, equals('new refresh token')); + }); + + test("uses the old refresh token if a new one isn't provided", () async { + var credentials = new oauth2.Credentials( + 'access token', + refreshToken: 'refresh token', + tokenEndpoint: tokenEndpoint); + expect(credentials.canRefresh, true); + + httpClient.expectRequest((request) { + expect(request.method, equals('POST')); + expect(request.url.toString(), equals(tokenEndpoint.toString())); + expect(request.bodyFields, equals({ + "grant_type": "refresh_token", + "refresh_token": "refresh token" })); + expect(request.headers, containsPair( + "Authorization", + "Basic aWQlQzMlQUJudCVDNCVBQmZpZXI6cyVDMyVBQmNyZXQ=")); return new Future.value(new http.Response(JSON.encode({ 'access_token': 'new access token', @@ -105,12 +183,49 @@ void main() { }); - credentials = await credentials.refresh('identifier', 'secret', + credentials = await credentials.refresh( + identifier: 'idëntīfier', + secret: 'sëcret', httpClient: httpClient); expect(credentials.accessToken, equals('new access token')); expect(credentials.refreshToken, equals('refresh token')); }); + test("uses form-field authentication if basicAuth is false", () async { + var credentials = new oauth2.Credentials( + 'access token', + refreshToken: 'refresh token', + tokenEndpoint: tokenEndpoint, + scopes: ['scope1', 'scope2']); + expect(credentials.canRefresh, true); + + httpClient.expectRequest((request) { + expect(request.method, equals('POST')); + expect(request.url.toString(), equals(tokenEndpoint.toString())); + expect(request.bodyFields, equals({ + "grant_type": "refresh_token", + "refresh_token": "refresh token", + "scope": "scope1 scope2", + "client_id": "idëntīfier", + "client_secret": "sëcret" + })); + + return new Future.value(new http.Response(JSON.encode({ + 'access_token': 'new access token', + 'token_type': 'bearer', + 'refresh_token': 'new refresh token' + }), 200, headers: {'content-type': 'application/json'})); + }); + + credentials = await credentials.refresh( + identifier: 'idëntīfier', + secret: 'sëcret', + basicAuth: false, + httpClient: httpClient); + expect(credentials.accessToken, equals('new access token')); + expect(credentials.refreshToken, equals('new refresh token')); + }); + group("fromJson", () { oauth2.Credentials fromMap(Map map) => new oauth2.Credentials.fromJson(JSON.encode(map)); @@ -118,8 +233,11 @@ void main() { test("should load the same credentials from toJson", () { var expiration = new DateTime.now().subtract(new Duration(hours: 1)); var credentials = new oauth2.Credentials( - 'access token', 'refresh token', tokenEndpoint, ['scope1', 'scope2'], - expiration); + 'access token', + refreshToken: 'refresh token', + tokenEndpoint: tokenEndpoint, + scopes: ['scope1', 'scope2'], + expiration: expiration); var reloaded = new oauth2.Credentials.fromJson(credentials.toJson()); expect(reloaded.accessToken, equals(credentials.accessToken)); From f6c2b2efae87e2641e8b512ea686d8463a4cc256 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 26 Aug 2015 14:03:43 -0700 Subject: [PATCH 065/159] Use http_parser's AuthenticationChallenge class. R=rnystrom@google.com Review URL: https://codereview.chromium.org//1304363005 . --- pkgs/oauth2/lib/src/client.dart | 13 +++-- pkgs/oauth2/lib/src/utils.dart | 58 -------------------- pkgs/oauth2/pubspec.yaml | 2 +- pkgs/oauth2/test/client_test.dart | 4 +- pkgs/oauth2/test/utils_test.dart | 88 ------------------------------- 5 files changed, 11 insertions(+), 154 deletions(-) delete mode 100644 pkgs/oauth2/test/utils_test.dart diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart index d604696e0..adf53d526 100644 --- a/pkgs/oauth2/lib/src/client.dart +++ b/pkgs/oauth2/lib/src/client.dart @@ -7,6 +7,7 @@ library oauth2.client; import 'dart:async'; import 'package:http/http.dart' as http; +import 'package:http_parser/http_parser.dart'; import 'authorization_exception.dart'; import 'credentials.dart'; @@ -83,7 +84,7 @@ class Client extends http.BaseClient { /// [httpClient] is the underlying client that this forwards requests to after /// adding authorization credentials to them. /// - /// Thrwos an [ArgumentError] if [secret] is passed without [identifier]. + /// Throws an [ArgumentError] if [secret] is passed without [identifier]. Client(this._credentials, {this.identifier, this.secret, bool basicAuth: true, http.Client httpClient}) : _basicAuth = basicAuth, @@ -109,17 +110,19 @@ class Client extends http.BaseClient { if (response.statusCode != 401) return response; if (!response.headers.containsKey('www-authenticate')) return response; - var authenticate; + var challenges; try { - authenticate = new AuthenticateHeader.parse( + challenges = AuthenticationChallenge.parseHeader( response.headers['www-authenticate']); } on FormatException catch (_) { return response; } - if (authenticate.scheme != 'bearer') return response; + var challenge = challenges.firstWhere( + (challenge) => challenge.scheme == 'bearer', orElse: () => null); + if (challenge == null) return response; - var params = authenticate.parameters; + var params = challenge.parameters; if (!params.containsKey('error')) return response; throw new AuthorizationException( diff --git a/pkgs/oauth2/lib/src/utils.dart b/pkgs/oauth2/lib/src/utils.dart index af19f9056..bf260eb73 100644 --- a/pkgs/oauth2/lib/src/utils.dart +++ b/pkgs/oauth2/lib/src/utils.dart @@ -17,61 +17,3 @@ String basicAuthHeader(String identifier, String secret) { var userPass = Uri.encodeFull(identifier) + ":" + Uri.encodeFull(secret); return "Basic " + CryptoUtils.bytesToBase64(ASCII.encode(userPass)); } - -/// Like [String.split], but only splits on the first occurrence of the pattern. -/// -/// This will always return a list of two elements or fewer. -List split1(String toSplit, String pattern) { - if (toSplit.isEmpty) return []; - - var index = toSplit.indexOf(pattern); - if (index == -1) return [toSplit]; - return [toSplit.substring(0, index), - toSplit.substring(index + pattern.length)]; -} - -/// A WWW-Authenticate header value, parsed as per [RFC 2617][]. -/// -/// [RFC 2617]: http://tools.ietf.org/html/rfc2617 -class AuthenticateHeader { - final String scheme; - final Map parameters; - - AuthenticateHeader(this.scheme, this.parameters); - - /// Parses a header string. Throws a [FormatException] if the header is - /// invalid. - factory AuthenticateHeader.parse(String header) { - var split = split1(header, ' '); - if (split.length == 0) { - throw new FormatException('Invalid WWW-Authenticate header: "$header"'); - } else if (split.length == 1 || split[1].trim().isEmpty) { - return new AuthenticateHeader(split[0].toLowerCase(), {}); - } - var scheme = split[0].toLowerCase(); - var paramString = split[1]; - - // From http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html. - var tokenChar = r'[^\0-\x1F()<>@,;:\\"/\[\]?={} \t\x7F]'; - var quotedStringChar = r'(?:[^\0-\x1F\x7F"]|\\.)'; - var regexp = new RegExp('^ *($tokenChar+)="($quotedStringChar*)" *(, *)?'); - - var parameters = {}; - var match; - do { - match = regexp.firstMatch(paramString); - if (match == null) { - throw new FormatException('Invalid WWW-Authenticate header: "$header"'); - } - - paramString = paramString.substring(match.end); - parameters[match.group(1).toLowerCase()] = match.group(2); - } while (match.group(3) != null); - - if (!paramString.trim().isEmpty) { - throw new FormatException('Invalid WWW-Authenticate header: "$header"'); - } - - return new AuthenticateHeader(scheme, parameters); - } -} diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index ce4bd4a90..f52b8befa 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -10,6 +10,6 @@ environment: sdk: '>=1.9.0 <2.0.0' dependencies: http: '>=0.11.0 <0.12.0' - http_parser: '>=0.0.0 <2.0.0' + http_parser: '^1.0.0' dev_dependencies: test: '>=0.12.0 <0.13.0' diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart index 885f83e4a..7dc5afd2e 100644 --- a/pkgs/oauth2/test/client_test.dart +++ b/pkgs/oauth2/test/client_test.dart @@ -178,8 +178,8 @@ void main() { expect(request.headers['authorization'], equals('Bearer access token')); - var authenticate = 'Bearer error="invalid_token", error_description=' - '"Something is terribly wrong.", '; + var authenticate = 'Bearer error="invalid_token" error_description=' + '"Something is terribly wrong."'; return new Future.value(new http.Response('bad job', 401, headers: {'www-authenticate': authenticate})); }); diff --git a/pkgs/oauth2/test/utils_test.dart b/pkgs/oauth2/test/utils_test.dart deleted file mode 100644 index 54c2da58e..000000000 --- a/pkgs/oauth2/test/utils_test.dart +++ /dev/null @@ -1,88 +0,0 @@ -// 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. - -import 'package:oauth2/src/utils.dart'; -import 'package:test/test.dart'; - -void main() { - group('AuthenticateHeader', () { - test("parses a scheme", () { - var header = new AuthenticateHeader.parse('bearer'); - expect(header.scheme, equals('bearer')); - expect(header.parameters, equals({})); - }); - - test("lower-cases the scheme", () { - var header = new AuthenticateHeader.parse('BeaRer'); - expect(header.scheme, equals('bearer')); - expect(header.parameters, equals({})); - }); - - test("parses a scheme with trailing whitespace", () { - var header = new AuthenticateHeader.parse('bearer '); - expect(header.scheme, equals('bearer')); - expect(header.parameters, equals({})); - }); - - test("parses a scheme with one param", () { - var header = new AuthenticateHeader.parse('bearer foo="bar"'); - expect(header.scheme, equals('bearer')); - expect(header.parameters, equals({'foo': 'bar'})); - }); - - test("parses a scheme with several params", () { - var header = new AuthenticateHeader.parse( - 'bearer foo="bar", bar="baz" ,baz="qux"'); - expect(header.scheme, equals('bearer')); - expect(header.parameters, equals({ - 'foo': 'bar', - 'bar': 'baz', - 'baz': 'qux' - })); - }); - - test("lower-cases parameter names but not values", () { - var header = new AuthenticateHeader.parse('bearer FoO="bAr"'); - expect(header.scheme, equals('bearer')); - expect(header.parameters, equals({'foo': 'bAr'})); - }); - - test("allows empty values", () { - var header = new AuthenticateHeader.parse('bearer foo=""'); - expect(header.scheme, equals('bearer')); - expect(header.parameters, equals({'foo': ''})); - }); - - test("won't parse an empty string", () { - expect(() => new AuthenticateHeader.parse(''), - throwsFormatException); - }); - - test("won't parse a token without a value", () { - expect(() => new AuthenticateHeader.parse('bearer foo'), - throwsFormatException); - - expect(() => new AuthenticateHeader.parse('bearer foo='), - throwsFormatException); - }); - - test("won't parse a token without a value", () { - expect(() => new AuthenticateHeader.parse('bearer foo'), - throwsFormatException); - - expect(() => new AuthenticateHeader.parse('bearer foo='), - throwsFormatException); - }); - - test("won't parse a trailing comma", () { - expect(() => new AuthenticateHeader.parse('bearer foo="bar",'), - throwsFormatException); - }); - - test("won't parse a multiple params without a comma", () { - expect(() => new AuthenticateHeader.parse('bearer foo="bar" bar="baz"'), - throwsFormatException); - }); - }); -} From 0f10895c5b5ba869432351939e35345431276dad Mon Sep 17 00:00:00 2001 From: Erik Grimes Date: Mon, 14 Sep 2015 12:55:18 -0700 Subject: [PATCH 066/159] Add support for Resource Owner Password Credentials grants. --- pkgs/oauth2/README.md | 24 +++- pkgs/oauth2/lib/oauth2.dart | 1 + .../src/resource_owner_password_grant.dart | 62 ++++++++++ .../resource_owner_password_grant_test.dart | 111 ++++++++++++++++++ 4 files changed, 194 insertions(+), 4 deletions(-) create mode 100644 pkgs/oauth2/lib/src/resource_owner_password_grant.dart create mode 100644 pkgs/oauth2/test/resource_owner_password_grant_test.dart diff --git a/pkgs/oauth2/README.md b/pkgs/oauth2/README.md index 7e390cd4c..ac6f5015a 100644 --- a/pkgs/oauth2/README.md +++ b/pkgs/oauth2/README.md @@ -1,3 +1,4 @@ +# overview A client library for authenticating with a remote service via OAuth2 on behalf of a user, and making authorized HTTP requests with the user's OAuth2 credentials. @@ -12,12 +13,16 @@ client has permission to access resources on behalf of the resource owner. OAuth2 provides several different methods for the client to obtain authorization. At the time of writing, this library only supports the -[AuthorizationCodeGrant][] method, but further methods may be added in the -future. The following example uses this method to authenticate, and assumes -that the library is being used by a server-side application. +[AuthorizationCodeGrant][] and [resourceOwnerPasswordGrant][] methods, but +further methods may be added in the future. The following example uses this +method to authenticate, and assumes that the library is being used by a server-side +application. -[AuthorizationCodeGrant]: https://api.dartlang.org/apidocs/channels/stable/#oauth2/oauth2.AuthorizationCodeGrant +[AuthorizationCodeGrant]: http://www.dartdocs.org/documentation/oauth2/latest/index.html#oauth2/oauth2.AuthorizationCodeGrant +[resourceOwnerPasswordGrant]: http://www.dartdocs.org/documentation/oauth2/latest/index.html#oauth2/oauth2.resourceOwnerPasswordGrant +# examples +## authorization code grant ```dart import 'dart:io' import 'package:oauth2/oauth2.dart' as oauth2; @@ -107,3 +112,14 @@ main() async { print(result); } ``` +## resource owner password grant + +``` + // Get a fully authorized client + var client = await oauth2.resourceOwnerPasswordGrant( + authorizationEndpoint, 'username', 'userpass', + clientId: 'client', clientSecret: 'secret'); + // Interact with server using the authorized client + var result = client.read("http://example.com/protected-resources.txt"); + +``` diff --git a/pkgs/oauth2/lib/oauth2.dart b/pkgs/oauth2/lib/oauth2.dart index cb3c59232..01474ed11 100644 --- a/pkgs/oauth2/lib/oauth2.dart +++ b/pkgs/oauth2/lib/oauth2.dart @@ -5,6 +5,7 @@ library oauth2; export 'src/authorization_code_grant.dart'; +export 'src/resource_owner_password_grant.dart'; export 'src/client.dart'; export 'src/credentials.dart'; export 'src/authorization_exception.dart'; diff --git a/pkgs/oauth2/lib/src/resource_owner_password_grant.dart b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart new file mode 100644 index 000000000..3d06a6df0 --- /dev/null +++ b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart @@ -0,0 +1,62 @@ +// 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 resource_owner_password_grant; + +import 'dart:async'; +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:crypto/crypto.dart'; +import 'client.dart'; +import 'handle_access_token_response.dart'; +import 'utils.dart'; + +/// Implementation of the [resource owner password grant] (http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-4.3) for oauth 2. +/// + +/// Returns a fully-authorized [Client] if authorization is successful. +/// +/// The client can provide a [clientId] and [clientSecret] for authenticating itself as required by the server. The +/// default authentication is basic authentication as recommended by the spec. This can be overridden to be passed as +/// query parameters by passing [useBasicAuth]: false. +/// +/// Specific scopes can be requested vis [scopes], but is not required. The server may choose to grant less scopes than +/// actually requested. The actual scopes granted are returned in [Credentials] property of the [Client]. +/// +Future resourceOwnerPasswordGrant( + Uri authorizationEndpoint, String username, String password, + {String clientId, + String clientSecret, + List scopes: const [], + bool useBasicAuth: true, + http.Client httpClient}) async { + + var startTime = new DateTime.now(); + + var body = {"grant_type": "password", "username": username, "password": password}; + + var headers = {}; + + if (clientId != null) { + if (useBasicAuth) { + headers['authorization'] = 'Basic ' + + CryptoUtils.bytesToBase64(UTF8.encode('$clientId:$clientSecret')); + } else { + body['client_id'] = clientId; + if(clientSecret != null) body['client_secret'] = clientSecret; + } + } + + if (!scopes.isEmpty) body['scope'] = scopes.join(' '); + + if (httpClient == null) { + httpClient = new http.Client(); + } + + var response = await httpClient.post(authorizationEndpoint, headers: headers, body: body); + + var credentials = await handleAccessTokenResponse( + response, authorizationEndpoint, startTime, scopes); + return new Client(credentials, identifier: clientId, secret: clientSecret); +} diff --git a/pkgs/oauth2/test/resource_owner_password_grant_test.dart b/pkgs/oauth2/test/resource_owner_password_grant_test.dart new file mode 100644 index 000000000..dc4631f38 --- /dev/null +++ b/pkgs/oauth2/test/resource_owner_password_grant_test.dart @@ -0,0 +1,111 @@ +// 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. +@TestOn("vm") +library resource_owner_password_grant_test; + +import 'dart:convert'; +import 'dart:async'; +import 'package:http/http.dart' as http; +import 'package:crypto/crypto.dart'; + +import 'package:test/test.dart'; +import 'package:oauth2/oauth2.dart' as oauth2; +import 'utils.dart'; + +final String SUCCESS = JSON.encode({ + "access_token": "2YotnFZFEjr1zCsicMWpAA", + "token_type": "bearer", + "expires_in": 3600, + "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA", +}); + +var auth = 'Basic ${CryptoUtils.bytesToBase64(UTF8.encode('client:secret'))}'; +var authEndpoint = Uri.parse('https://example.com'); +var expectClient = new ExpectClient(); + +void main() { + group('basic', () { + test('builds correct request with client when using basic auth for client', + () async { + expectClient.expectRequest((request) { + expect(auth, equals(request.headers['authorization'])); + expect(request.bodyFields['grant_type'], equals('password')); + expect(request.bodyFields['username'], equals('username')); + expect(request.bodyFields['password'], equals('userpass')); + return new Future.value(new http.Response(SUCCESS, 200, + headers: {'content-type': 'application/json'})); + }); + + var client = await oauth2.resourceOwnerPasswordGrant( + authEndpoint, 'username', 'userpass', + clientId: 'client', clientSecret: 'secret', httpClient: expectClient); + + expect(client.credentials, isNotNull); + expect(client.credentials.accessToken, equals('2YotnFZFEjr1zCsicMWpAA')); + }); + + test('builds correct request when using query parameters for client', + () async { + expectClient.expectRequest((request) { + expect(request.bodyFields['grant_type'], equals('password')); + expect(request.bodyFields['client_id'], equals('client')); + expect(request.bodyFields['client_secret'], equals('secret')); + expect(request.bodyFields['username'], equals('username')); + expect(request.bodyFields['password'], equals('userpass')); + return new Future.value(new http.Response(SUCCESS, 200, + headers: {'content-type': 'application/json'})); + }); + + var client = await oauth2.resourceOwnerPasswordGrant( + authEndpoint, 'username', 'userpass', + clientId: 'client', + clientSecret: 'secret', + useBasicAuth: false, + httpClient: expectClient); + expect(client.credentials, isNotNull); + expect(client.credentials.accessToken, equals('2YotnFZFEjr1zCsicMWpAA')); + }); + + test('builds correct request using scope', () async { + expectClient.expectRequest((request) { + expect(request.bodyFields['grant_type'], equals('password')); + expect(request.bodyFields['username'], equals('username')); + expect(request.bodyFields['password'], equals('userpass')); + expect(request.bodyFields['scope'], equals('one two')); + return new Future.value(new http.Response(SUCCESS, 200, + headers: {'content-type': 'application/json'})); + }); + + var client = await oauth2.resourceOwnerPasswordGrant( + authEndpoint, 'username', 'userpass', + scopes: ['one', 'two'], httpClient: expectClient); + expect(client.credentials, isNotNull); + expect(client.credentials.accessToken, equals('2YotnFZFEjr1zCsicMWpAA')); + }); + + test('merges with existing query parameters', () async { + var authEndpoint = Uri.parse('https://example.com?query=value'); + + expectClient.expectRequest((request) { + expect(request.bodyFields['grant_type'], equals('password')); + expect(request.bodyFields['client_id'], equals('client')); + expect(request.bodyFields['client_secret'], equals('secret')); + expect(request.bodyFields['username'], equals('username')); + expect(request.bodyFields['password'], equals('userpass')); + expect(request.url.queryParameters['query'], equals('value')); + return new Future.value(new http.Response(SUCCESS, 200, + headers: {'content-type': 'application/json'})); + }); + + var client = await oauth2.resourceOwnerPasswordGrant( + authEndpoint, 'username', 'userpass', + clientId: 'client', + clientSecret: 'secret', + useBasicAuth: false, + httpClient: expectClient); + expect(client.credentials, isNotNull); + expect(client.credentials.accessToken, equals('2YotnFZFEjr1zCsicMWpAA')); + }); + }); +} From 053402bd89b7e521d9288371a9d84db867ccda18 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Mon, 14 Sep 2015 12:55:56 -0700 Subject: [PATCH 067/159] Style changes. --- pkgs/oauth2/CHANGELOG.md | 2 + pkgs/oauth2/README.md | 52 ++++++++++---- pkgs/oauth2/lib/src/client.dart | 4 +- .../src/resource_owner_password_grant.dart | 72 ++++++++++--------- pkgs/oauth2/pubspec.yaml | 2 +- .../resource_owner_password_grant_test.dart | 57 ++++++++------- 6 files changed, 114 insertions(+), 75 deletions(-) diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md index 686b6acac..f5865d228 100644 --- a/pkgs/oauth2/CHANGELOG.md +++ b/pkgs/oauth2/CHANGELOG.md @@ -23,6 +23,8 @@ ## Non-breaking changes +* Added a `resourceOwnerPasswordGrant` method. + * The `scopes` argument to `AuthorizationCodeGrant.getAuthorizationUrl()` and `new Credentials()` and the `newScopes` argument to `Credentials.refresh` now take an `Iterable` rather than just a `List`. diff --git a/pkgs/oauth2/README.md b/pkgs/oauth2/README.md index ac6f5015a..ad3b0c01c 100644 --- a/pkgs/oauth2/README.md +++ b/pkgs/oauth2/README.md @@ -1,4 +1,3 @@ -# overview A client library for authenticating with a remote service via OAuth2 on behalf of a user, and making authorized HTTP requests with the user's OAuth2 credentials. @@ -15,14 +14,14 @@ OAuth2 provides several different methods for the client to obtain authorization. At the time of writing, this library only supports the [AuthorizationCodeGrant][] and [resourceOwnerPasswordGrant][] methods, but further methods may be added in the future. The following example uses this -method to authenticate, and assumes that the library is being used by a server-side -application. +method to authenticate, and assumes that the library is being used by a +server-side application. [AuthorizationCodeGrant]: http://www.dartdocs.org/documentation/oauth2/latest/index.html#oauth2/oauth2.AuthorizationCodeGrant [resourceOwnerPasswordGrant]: http://www.dartdocs.org/documentation/oauth2/latest/index.html#oauth2/oauth2.resourceOwnerPasswordGrant -# examples -## authorization code grant +## Authorization Code Grant + ```dart import 'dart:io' import 'package:oauth2/oauth2.dart' as oauth2; @@ -112,14 +111,41 @@ main() async { print(result); } ``` -## resource owner password grant -``` - // Get a fully authorized client - var client = await oauth2.resourceOwnerPasswordGrant( - authorizationEndpoint, 'username', 'userpass', - clientId: 'client', clientSecret: 'secret'); - // Interact with server using the authorized client - var result = client.read("http://example.com/protected-resources.txt"); +## Resource Owner Password Grant + +```dart +// This URL is an endpoint that's provided by the authorization server. It's +// usually included in the server's documentation of its OAuth2 API. +final authorizationEndpoint = + Uri.parse("http://example.com/oauth2/authorization"); + +// The user should supply their own username and password. +final username = "example user"; +final password = "example password"; + +// The authorization server may issue each client a separate client +// identifier and secret, which allows the server to tell which client +// is accessing it. Some servers may also have an anonymous +// identifier/secret pair that any client may use. +// +// Some servers don't require the client to authenticate itself, in which case +// these should be omitted. +final identifier = "my client identifier"; +final secret = "my client secret"; + +// Make a request to the authorization endpoint that will produce the fully +// authenticated Client. +var client = await oauth2.resourceOwnerPasswordGrant( + authorizationEndpoint, username, password, + identifier: identifier, secret: secret); + +// Once you have the client, you can use it just like any other HTTP client. +var result = await client.read("http://example.com/protected-resources.txt"); +// Once we're done with the client, save the credentials file. This will allow +// us to re-use the credentials and avoid storing the username and password +// directly. +new File("~/.myapp/credentials.json") + .writeAsString(client.credentials.toJson()); ``` diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart index adf53d526..c27ab4e5d 100644 --- a/pkgs/oauth2/lib/src/client.dart +++ b/pkgs/oauth2/lib/src/client.dart @@ -12,7 +12,6 @@ import 'package:http_parser/http_parser.dart'; import 'authorization_exception.dart'; import 'credentials.dart'; import 'expiration_exception.dart'; -import 'utils.dart'; // TODO(nweiz): Add an onCredentialsRefreshed event once we have some event // infrastructure. @@ -79,7 +78,8 @@ class Client extends http.BaseClient { /// Creates a new client from a pre-existing set of credentials. /// /// When authorizing a client for the first time, you should use - /// [AuthorizationCodeGrant] instead of constructing a [Client] directly. + /// [AuthorizationCodeGrant] or [resourceOwnerPasswordGrant] instead of + /// constructing a [Client] directly. /// /// [httpClient] is the underlying client that this forwards requests to after /// adding authorization credentials to them. diff --git a/pkgs/oauth2/lib/src/resource_owner_password_grant.dart b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart index 3d06a6df0..28498ca72 100644 --- a/pkgs/oauth2/lib/src/resource_owner_password_grant.dart +++ b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart @@ -2,61 +2,69 @@ // 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 resource_owner_password_grant; +library oauth2.resource_owner_password_grant; import 'dart:async'; -import 'dart:convert'; + import 'package:http/http.dart' as http; -import 'package:crypto/crypto.dart'; + import 'client.dart'; import 'handle_access_token_response.dart'; import 'utils.dart'; -/// Implementation of the [resource owner password grant] (http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-4.3) for oauth 2. -/// - -/// Returns a fully-authorized [Client] if authorization is successful. +/// Obtains credentials using a [resource owner password grant][]. /// -/// The client can provide a [clientId] and [clientSecret] for authenticating itself as required by the server. The -/// default authentication is basic authentication as recommended by the spec. This can be overridden to be passed as -/// query parameters by passing [useBasicAuth]: false. +/// This mode of authorization uses the user's username and password to obtain +/// an authentication token, which can then be stored. This is safer than +/// storing the username and password directly, but it should be avoided if any +/// other authorization method is available, since it requires the user to +/// provide their username and password to a third party (you). /// -/// Specific scopes can be requested vis [scopes], but is not required. The server may choose to grant less scopes than -/// actually requested. The actual scopes granted are returned in [Credentials] property of the [Client]. +/// The client [identifier] and [secret] may be issued by the server, and are +/// used to identify and authenticate your specific OAuth2 client. These are +/// usually global to the program using this library. /// +/// The specific permissions being requested from the authorization server may +/// be specified via [scopes]. The scope strings are specific to the +/// authorization server and may be found in its documentation. Note that you +/// may not be granted access to every scope you request; you may check the +/// [Credentials.scopes] field of [Client.credentials] to see which scopes you +/// were granted. Future resourceOwnerPasswordGrant( - Uri authorizationEndpoint, String username, String password, - {String clientId, - String clientSecret, - List scopes: const [], - bool useBasicAuth: true, + Uri authorizationEndpoint, + String username, + String password, + {String identifier, + String secret, + Iterable scopes, + bool basicAuth: true, http.Client httpClient}) async { - var startTime = new DateTime.now(); - var body = {"grant_type": "password", "username": username, "password": password}; + var body = { + "grant_type": "password", + "username": username, + "password": password + }; var headers = {}; - if (clientId != null) { - if (useBasicAuth) { - headers['authorization'] = 'Basic ' + - CryptoUtils.bytesToBase64(UTF8.encode('$clientId:$clientSecret')); + if (identifier != null) { + if (basicAuth) { + headers['Authorization'] = basicAuthHeader(identifier, secret); } else { - body['client_id'] = clientId; - if(clientSecret != null) body['client_secret'] = clientSecret; + body['client_id'] = identifier; + if (secret != null) body['client_secret'] = secret; } } - if (!scopes.isEmpty) body['scope'] = scopes.join(' '); - - if (httpClient == null) { - httpClient = new http.Client(); - } + if (scopes != null && !scopes.isEmpty) body['scope'] = scopes.join(' '); - var response = await httpClient.post(authorizationEndpoint, headers: headers, body: body); + if (httpClient == null) httpClient = new http.Client(); + var response = await httpClient.post(authorizationEndpoint, + headers: headers, body: body); var credentials = await handleAccessTokenResponse( response, authorizationEndpoint, startTime, scopes); - return new Client(credentials, identifier: clientId, secret: clientSecret); + return new Client(credentials, identifier: identifier, secret: secret); } diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index f52b8befa..bd6bd4c39 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,5 @@ name: oauth2 -version: 1.0.0-dev +version: 1.0.0 author: Dart Team homepage: http://github.com/dart-lang/oauth2 description: > diff --git a/pkgs/oauth2/test/resource_owner_password_grant_test.dart b/pkgs/oauth2/test/resource_owner_password_grant_test.dart index dc4631f38..1d73a0fdc 100644 --- a/pkgs/oauth2/test/resource_owner_password_grant_test.dart +++ b/pkgs/oauth2/test/resource_owner_password_grant_test.dart @@ -1,45 +1,48 @@ // 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. + @TestOn("vm") -library resource_owner_password_grant_test; -import 'dart:convert'; import 'dart:async'; -import 'package:http/http.dart' as http; -import 'package:crypto/crypto.dart'; +import 'dart:convert'; -import 'package:test/test.dart'; +import 'package:crypto/crypto.dart'; +import 'package:http/http.dart' as http; import 'package:oauth2/oauth2.dart' as oauth2; +import 'package:test/test.dart'; + import 'utils.dart'; -final String SUCCESS = JSON.encode({ +final success = JSON.encode({ "access_token": "2YotnFZFEjr1zCsicMWpAA", "token_type": "bearer", "expires_in": 3600, "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA", }); -var auth = 'Basic ${CryptoUtils.bytesToBase64(UTF8.encode('client:secret'))}'; +var auth = 'Basic Y2xpZW50OnNlY3JldA=='; var authEndpoint = Uri.parse('https://example.com'); -var expectClient = new ExpectClient(); void main() { + var expectClient; + setUp(() => expectClient = new ExpectClient()); + group('basic', () { test('builds correct request with client when using basic auth for client', () async { - expectClient.expectRequest((request) { + expectClient.expectRequest((request) async { expect(auth, equals(request.headers['authorization'])); expect(request.bodyFields['grant_type'], equals('password')); expect(request.bodyFields['username'], equals('username')); expect(request.bodyFields['password'], equals('userpass')); - return new Future.value(new http.Response(SUCCESS, 200, - headers: {'content-type': 'application/json'})); + return new http.Response(success, 200, + headers: {'content-type': 'application/json'}); }); var client = await oauth2.resourceOwnerPasswordGrant( authEndpoint, 'username', 'userpass', - clientId: 'client', clientSecret: 'secret', httpClient: expectClient); + identifier: 'client', secret: 'secret', httpClient: expectClient); expect(client.credentials, isNotNull); expect(client.credentials.accessToken, equals('2YotnFZFEjr1zCsicMWpAA')); @@ -47,34 +50,34 @@ void main() { test('builds correct request when using query parameters for client', () async { - expectClient.expectRequest((request) { + expectClient.expectRequest((request) async { expect(request.bodyFields['grant_type'], equals('password')); expect(request.bodyFields['client_id'], equals('client')); expect(request.bodyFields['client_secret'], equals('secret')); expect(request.bodyFields['username'], equals('username')); expect(request.bodyFields['password'], equals('userpass')); - return new Future.value(new http.Response(SUCCESS, 200, - headers: {'content-type': 'application/json'})); + return new http.Response(success, 200, + headers: {'content-type': 'application/json'}); }); var client = await oauth2.resourceOwnerPasswordGrant( authEndpoint, 'username', 'userpass', - clientId: 'client', - clientSecret: 'secret', - useBasicAuth: false, + identifier: 'client', + secret: 'secret', + basicAuth: false, httpClient: expectClient); expect(client.credentials, isNotNull); expect(client.credentials.accessToken, equals('2YotnFZFEjr1zCsicMWpAA')); }); test('builds correct request using scope', () async { - expectClient.expectRequest((request) { + expectClient.expectRequest((request) async { expect(request.bodyFields['grant_type'], equals('password')); expect(request.bodyFields['username'], equals('username')); expect(request.bodyFields['password'], equals('userpass')); expect(request.bodyFields['scope'], equals('one two')); - return new Future.value(new http.Response(SUCCESS, 200, - headers: {'content-type': 'application/json'})); + return new http.Response(success, 200, + headers: {'content-type': 'application/json'}); }); var client = await oauth2.resourceOwnerPasswordGrant( @@ -87,22 +90,22 @@ void main() { test('merges with existing query parameters', () async { var authEndpoint = Uri.parse('https://example.com?query=value'); - expectClient.expectRequest((request) { + expectClient.expectRequest((request) async { expect(request.bodyFields['grant_type'], equals('password')); expect(request.bodyFields['client_id'], equals('client')); expect(request.bodyFields['client_secret'], equals('secret')); expect(request.bodyFields['username'], equals('username')); expect(request.bodyFields['password'], equals('userpass')); expect(request.url.queryParameters['query'], equals('value')); - return new Future.value(new http.Response(SUCCESS, 200, - headers: {'content-type': 'application/json'})); + return new http.Response(success, 200, + headers: {'content-type': 'application/json'}); }); var client = await oauth2.resourceOwnerPasswordGrant( authEndpoint, 'username', 'userpass', - clientId: 'client', - clientSecret: 'secret', - useBasicAuth: false, + identifier: 'client', + secret: 'secret', + basicAuth: false, httpClient: expectClient); expect(client.credentials, isNotNull); expect(client.credentials.accessToken, equals('2YotnFZFEjr1zCsicMWpAA')); From 4c1f80b99640479d28e6e1ad15cb187980b4e25e Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Mon, 14 Dec 2015 14:21:16 -0800 Subject: [PATCH 068/159] Support http_parser 2.0.0. R=kevmoo@google.com Review URL: https://codereview.chromium.org//1522923003 . --- pkgs/oauth2/CHANGELOG.md | 4 ++++ pkgs/oauth2/pubspec.yaml | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md index f5865d228..e81b5c604 100644 --- a/pkgs/oauth2/CHANGELOG.md +++ b/pkgs/oauth2/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.0.1 + +* Support `http_parser` 2.0.0. + # 1.0.0 ## Breaking changes diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index bd6bd4c39..559fc7b91 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,5 @@ name: oauth2 -version: 1.0.0 +version: 1.0.1 author: Dart Team homepage: http://github.com/dart-lang/oauth2 description: > @@ -10,6 +10,6 @@ environment: sdk: '>=1.9.0 <2.0.0' dependencies: http: '>=0.11.0 <0.12.0' - http_parser: '^1.0.0' + http_parser: '>=1.0.0 <3.0.0' dev_dependencies: test: '>=0.12.0 <0.13.0' From 3c24a2c9a28d66e9d2c76a44ca355039c4d85fdd Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 16 Dec 2015 14:37:05 -0800 Subject: [PATCH 069/159] Fix the toJson/fromJson test. DateTime has begun storing sub-millisecond times, which means that storing and retrieving the milliseconds since the epoch no longer produces an identical object. This rounds the test value to avoid this failure. R=kevmoo@google.com Review URL: https://codereview.chromium.org//1531083002 . --- pkgs/oauth2/pubspec.yaml | 2 +- pkgs/oauth2/test/credentials_test.dart | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index 559fc7b91..bdf86d6f2 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,5 @@ name: oauth2 -version: 1.0.1 +version: 1.0.2-dev author: Dart Team homepage: http://github.com/dart-lang/oauth2 description: > diff --git a/pkgs/oauth2/test/credentials_test.dart b/pkgs/oauth2/test/credentials_test.dart index f4d8886e8..cedea6b2d 100644 --- a/pkgs/oauth2/test/credentials_test.dart +++ b/pkgs/oauth2/test/credentials_test.dart @@ -231,7 +231,13 @@ void main() { new oauth2.Credentials.fromJson(JSON.encode(map)); test("should load the same credentials from toJson", () { + // Round the expiration down to milliseconds since epoch, since that's + // what the credentials file stores. Otherwise sub-millisecond time gets + // in the way. var expiration = new DateTime.now().subtract(new Duration(hours: 1)); + expiration = new DateTime.fromMillisecondsSinceEpoch( + expiration.millisecondsSinceEpoch); + var credentials = new oauth2.Credentials( 'access token', refreshToken: 'refresh token', From 0d193a6b92cd688b02b282ede7de4d43f4d8c1f9 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Tue, 12 Jan 2016 17:21:40 -0800 Subject: [PATCH 070/159] Get rid of all the library tags. R=rnystrom@google.com Review URL: https://codereview.chromium.org//1582703002 . --- pkgs/oauth2/lib/oauth2.dart | 2 -- pkgs/oauth2/lib/src/authorization_code_grant.dart | 2 -- pkgs/oauth2/lib/src/authorization_exception.dart | 2 -- pkgs/oauth2/lib/src/client.dart | 2 -- pkgs/oauth2/lib/src/credentials.dart | 2 -- pkgs/oauth2/lib/src/expiration_exception.dart | 2 -- pkgs/oauth2/lib/src/handle_access_token_response.dart | 2 -- pkgs/oauth2/lib/src/resource_owner_password_grant.dart | 2 -- pkgs/oauth2/lib/src/utils.dart | 2 -- pkgs/oauth2/test/handle_access_token_response_test.dart | 2 -- 10 files changed, 20 deletions(-) diff --git a/pkgs/oauth2/lib/oauth2.dart b/pkgs/oauth2/lib/oauth2.dart index 01474ed11..3b81cf1d4 100644 --- a/pkgs/oauth2/lib/oauth2.dart +++ b/pkgs/oauth2/lib/oauth2.dart @@ -2,8 +2,6 @@ // 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 oauth2; - export 'src/authorization_code_grant.dart'; export 'src/resource_owner_password_grant.dart'; export 'src/client.dart'; diff --git a/pkgs/oauth2/lib/src/authorization_code_grant.dart b/pkgs/oauth2/lib/src/authorization_code_grant.dart index 7a73d44ab..1cb45dae3 100644 --- a/pkgs/oauth2/lib/src/authorization_code_grant.dart +++ b/pkgs/oauth2/lib/src/authorization_code_grant.dart @@ -2,8 +2,6 @@ // 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 oauth2.authorization_code_grant; - import 'dart:async'; import 'package:http/http.dart' as http; diff --git a/pkgs/oauth2/lib/src/authorization_exception.dart b/pkgs/oauth2/lib/src/authorization_exception.dart index 838d0011f..9ce4b8c95 100644 --- a/pkgs/oauth2/lib/src/authorization_exception.dart +++ b/pkgs/oauth2/lib/src/authorization_exception.dart @@ -2,8 +2,6 @@ // 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 oauth2.authorization_exception; - /// An exception raised when OAuth2 authorization fails. class AuthorizationException implements Exception { /// The name of the error. diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart index c27ab4e5d..fc741474f 100644 --- a/pkgs/oauth2/lib/src/client.dart +++ b/pkgs/oauth2/lib/src/client.dart @@ -2,8 +2,6 @@ // 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 oauth2.client; - import 'dart:async'; import 'package:http/http.dart' as http; diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index 0a12f3f88..49d2a6415 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -2,8 +2,6 @@ // 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 oauth2.credentials; - import 'dart:async'; import 'dart:collection'; import 'dart:convert'; diff --git a/pkgs/oauth2/lib/src/expiration_exception.dart b/pkgs/oauth2/lib/src/expiration_exception.dart index f684e6ae5..2df2f8d59 100644 --- a/pkgs/oauth2/lib/src/expiration_exception.dart +++ b/pkgs/oauth2/lib/src/expiration_exception.dart @@ -2,8 +2,6 @@ // 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 oauth2.expiration_exception; - import 'credentials.dart'; /// An exception raised when attempting to use expired OAuth2 credentials. diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart index ef9b198f4..94d9487ab 100644 --- a/pkgs/oauth2/lib/src/handle_access_token_response.dart +++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart @@ -2,8 +2,6 @@ // 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 oauth2.handle_access_token_response; - import 'dart:convert'; import 'package:http/http.dart' as http; diff --git a/pkgs/oauth2/lib/src/resource_owner_password_grant.dart b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart index 28498ca72..f67382be4 100644 --- a/pkgs/oauth2/lib/src/resource_owner_password_grant.dart +++ b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart @@ -2,8 +2,6 @@ // 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 oauth2.resource_owner_password_grant; - import 'dart:async'; import 'package:http/http.dart' as http; diff --git a/pkgs/oauth2/lib/src/utils.dart b/pkgs/oauth2/lib/src/utils.dart index bf260eb73..e7cc55080 100644 --- a/pkgs/oauth2/lib/src/utils.dart +++ b/pkgs/oauth2/lib/src/utils.dart @@ -2,8 +2,6 @@ // 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 oauth2.utils; - import 'dart:convert'; import 'package:crypto/crypto.dart'; diff --git a/pkgs/oauth2/test/handle_access_token_response_test.dart b/pkgs/oauth2/test/handle_access_token_response_test.dart index 4d822626c..fe2a3387d 100644 --- a/pkgs/oauth2/test/handle_access_token_response_test.dart +++ b/pkgs/oauth2/test/handle_access_token_response_test.dart @@ -2,8 +2,6 @@ // 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 handle_access_token_response_test; - import 'dart:convert'; import 'package:http/http.dart' as http; From 48eadf75041bf5b8c9d1f1662df93860277b9890 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 5 Apr 2016 17:45:56 -0700 Subject: [PATCH 071/159] Fixing analyzer warning: UNUSED_CATCH_CLAUSE --- pkgs/oauth2/lib/src/client.dart | 2 +- pkgs/oauth2/lib/src/credentials.dart | 2 +- pkgs/oauth2/lib/src/handle_access_token_response.dart | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart index fc741474f..20a4d70c7 100644 --- a/pkgs/oauth2/lib/src/client.dart +++ b/pkgs/oauth2/lib/src/client.dart @@ -112,7 +112,7 @@ class Client extends http.BaseClient { try { challenges = AuthenticationChallenge.parseHeader( response.headers['www-authenticate']); - } on FormatException catch (_) { + } on FormatException { return response; } diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index 49d2a6415..2a7087f3f 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -95,7 +95,7 @@ class Credentials { var parsed; try { parsed = JSON.decode(json); - } on FormatException catch (_) { + } on FormatException { validate(false, 'invalid JSON'); } diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart index 94d9487ab..c9e80fdcf 100644 --- a/pkgs/oauth2/lib/src/handle_access_token_response.dart +++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart @@ -44,7 +44,7 @@ Credentials handleAccessTokenResponse( var parameters; try { parameters = JSON.decode(response.body); - } on FormatException catch (_) { + } on FormatException { validate(false, 'invalid JSON'); } @@ -111,7 +111,7 @@ void _handleErrorResponse(http.Response response, Uri tokenEndpoint) { var parameters; try { parameters = JSON.decode(response.body); - } on FormatException catch (_) { + } on FormatException { validate(false, 'invalid JSON'); } From 72ee55fb95802fff0d190dbeed9fb568bca50286 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 5 May 2016 10:27:04 -0700 Subject: [PATCH 072/159] Fix all strong-mode warnings. R=rnystrom@google.com, jmesserly@google.com Review URL: https://codereview.chromium.org//1953643002 . --- pkgs/oauth2/CHANGELOG.md | 8 +++++++ .../lib/src/authorization_code_grant.dart | 2 +- pkgs/oauth2/lib/src/credentials.dart | 4 ++-- .../lib/src/handle_access_token_response.dart | 23 +++++++++++++------ .../src/resource_owner_password_grant.dart | 2 +- pkgs/oauth2/lib/src/utils.dart | 4 +--- pkgs/oauth2/pubspec.yaml | 7 +++--- 7 files changed, 33 insertions(+), 17 deletions(-) diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md index e81b5c604..32df0355b 100644 --- a/pkgs/oauth2/CHANGELOG.md +++ b/pkgs/oauth2/CHANGELOG.md @@ -1,3 +1,11 @@ +# 1.0.2 + +* Fix all strong-mode warnings. + +* Support `crypto` 1.0.0. + +* Support `http_parser` 3.0.0. + # 1.0.1 * Support `http_parser` 2.0.0. diff --git a/pkgs/oauth2/lib/src/authorization_code_grant.dart b/pkgs/oauth2/lib/src/authorization_code_grant.dart index 1cb45dae3..bd44c6a07 100644 --- a/pkgs/oauth2/lib/src/authorization_code_grant.dart +++ b/pkgs/oauth2/lib/src/authorization_code_grant.dart @@ -240,7 +240,7 @@ class AuthorizationCodeGrant { Future _handleAuthorizationCode(String authorizationCode) async { var startTime = new DateTime.now(); - var headers = {}; + var headers = {}; var body = { "grant_type": "authorization_code", diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index 2a7087f3f..453181830 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -132,7 +132,7 @@ class Credentials { parsed['accessToken'], refreshToken: parsed['refreshToken'], tokenEndpoint: tokenEndpoint, - scopes: scopes, + scopes: (scopes as List).map((scope) => scope as String), expiration: expiration); } @@ -184,7 +184,7 @@ class Credentials { "endpoint."); } - var headers = {}; + var headers = {}; var body = { "grant_type": "refresh_token", diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart index c9e80fdcf..e0fb6f6a0 100644 --- a/pkgs/oauth2/lib/src/handle_access_token_response.dart +++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart @@ -4,6 +4,7 @@ import 'dart:convert'; +import 'package:collection/collection.dart'; import 'package:http/http.dart' as http; import 'package:http_parser/http_parser.dart'; @@ -31,8 +32,10 @@ Credentials handleAccessTokenResponse( validate(condition, message) => _validate(response, tokenEndpoint, condition, message); - var contentType = response.headers['content-type']; - if (contentType != null) contentType = new MediaType.parse(contentType); + var contentTypeString = response.headers['content-type']; + var contentType = contentTypeString == null + ? null + : new MediaType.parse(contentTypeString); // The spec requires a content-type of application/json, but some endpoints // (e.g. Dropbox) serve it as text/javascript instead. @@ -41,9 +44,12 @@ Credentials handleAccessTokenResponse( contentType.mimeType == "text/javascript"), 'content-type was "$contentType", expected "application/json"'); - var parameters; + Map parameters; try { - parameters = JSON.decode(response.body); + var untypedParameters = JSON.decode(response.body); + validate(untypedParameters is Map, + 'parameters must be a map, was "$parameters"'); + parameters = DelegatingMap.typed(untypedParameters); } on FormatException { validate(false, 'invalid JSON'); } @@ -71,7 +77,7 @@ Credentials handleAccessTokenResponse( 'parameter "$name" was not a string, was "$value"'); } - var scope = parameters['scope']; + var scope = parameters['scope'] as String; if (scope != null) scopes = scope.split(" "); var expiration = expiresIn == null ? null : @@ -103,8 +109,11 @@ void _handleErrorResponse(http.Response response, Uri tokenEndpoint) { 'with status ${response.statusCode}$reason.\n\n${response.body}'); } - var contentType = response.headers['content-type']; - if (contentType != null) contentType = new MediaType.parse(contentType); + var contentTypeString = response.headers['content-type']; + var contentType = contentTypeString == null + ? null + : new MediaType.parse(contentTypeString); + validate(contentType != null && contentType.mimeType == "application/json", 'content-type was "$contentType", expected "application/json"'); diff --git a/pkgs/oauth2/lib/src/resource_owner_password_grant.dart b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart index f67382be4..5427ecc2d 100644 --- a/pkgs/oauth2/lib/src/resource_owner_password_grant.dart +++ b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart @@ -45,7 +45,7 @@ Future resourceOwnerPasswordGrant( "password": password }; - var headers = {}; + var headers = {}; if (identifier != null) { if (basicAuth) { diff --git a/pkgs/oauth2/lib/src/utils.dart b/pkgs/oauth2/lib/src/utils.dart index e7cc55080..150fa174a 100644 --- a/pkgs/oauth2/lib/src/utils.dart +++ b/pkgs/oauth2/lib/src/utils.dart @@ -4,8 +4,6 @@ import 'dart:convert'; -import 'package:crypto/crypto.dart'; - /// Adds additional query parameters to [url], overwriting the original /// parameters if a name conflict occurs. Uri addQueryParameters(Uri url, Map parameters) => url.replace( @@ -13,5 +11,5 @@ Uri addQueryParameters(Uri url, Map parameters) => url.replace( String basicAuthHeader(String identifier, String secret) { var userPass = Uri.encodeFull(identifier) + ":" + Uri.encodeFull(secret); - return "Basic " + CryptoUtils.bytesToBase64(ASCII.encode(userPass)); + return "Basic " + BASE64.encode(ASCII.encode(userPass)); } diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index bdf86d6f2..a8a05c06f 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,5 @@ name: oauth2 -version: 1.0.2-dev +version: 1.0.2 author: Dart Team homepage: http://github.com/dart-lang/oauth2 description: > @@ -7,9 +7,10 @@ description: > behalf of a user, and making authorized HTTP requests with the user's OAuth2 credentials. environment: - sdk: '>=1.9.0 <2.0.0' + sdk: '>=1.13.0 <2.0.0' dependencies: + collection: '^1.5.0' http: '>=0.11.0 <0.12.0' - http_parser: '>=1.0.0 <3.0.0' + http_parser: '>=1.0.0 <4.0.0' dev_dependencies: test: '>=0.12.0 <0.13.0' From 009c201178494a0cd00e9fe545fc52c7b53d2e17 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Fri, 2 Jun 2017 17:22:14 -0400 Subject: [PATCH 073/159] Add scope delimiter option (dart-lang/oauth2#17) --- pkgs/oauth2/CHANGELOG.md | 7 +++++++ .../lib/src/authorization_code_grant.dart | 19 +++++++++++++---- pkgs/oauth2/lib/src/credentials.dart | 17 +++++++++++---- .../lib/src/handle_access_token_response.dart | 7 +++++-- .../src/resource_owner_password_grant.dart | 12 ++++++++--- pkgs/oauth2/pubspec.yaml | 2 +- .../test/authorization_code_grant_test.dart | 18 ++++++++++++++++ pkgs/oauth2/test/credentials_test.dart | 21 +++++++++++++++++++ .../handle_access_token_response_test.dart | 15 ++++++++++++- .../resource_owner_password_grant_test.dart | 15 +++++++++++++ 10 files changed, 118 insertions(+), 15 deletions(-) diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md index 32df0355b..d629306e6 100644 --- a/pkgs/oauth2/CHANGELOG.md +++ b/pkgs/oauth2/CHANGELOG.md @@ -1,3 +1,10 @@ +# 1.1.0 + +* Add a `delimiter` parameter to `new AuthorizationCodeGrant()`, `new + Credentials()`, and `resourceOwnerPasswordGrant()`. This controls the + delimiter between scopes, which some authorization servers require to be + different values than the specified `' '`. + # 1.0.2 * Fix all strong-mode warnings. diff --git a/pkgs/oauth2/lib/src/authorization_code_grant.dart b/pkgs/oauth2/lib/src/authorization_code_grant.dart index bd44c6a07..af0b0c6f1 100644 --- a/pkgs/oauth2/lib/src/authorization_code_grant.dart +++ b/pkgs/oauth2/lib/src/authorization_code_grant.dart @@ -69,6 +69,9 @@ class AuthorizationCodeGrant { /// Whether to use HTTP Basic authentication for authorizing the client. final bool _basicAuth; + /// A [String] used to separate scopes; defaults to `" "`. + String _delimiter; + /// The HTTP client used to make HTTP requests. http.Client _httpClient; @@ -98,13 +101,21 @@ class AuthorizationCodeGrant { /// /// [httpClient] is used for all HTTP requests made by this grant, as well as /// those of the [Client] is constructs. + /// + /// The scope strings will be separated by the provided [delimiter]. This + /// defaults to `" "`, the OAuth2 standard, but some APIs (such as Facebook's) + /// use non-standard delimiters. AuthorizationCodeGrant( this.identifier, this.authorizationEndpoint, this.tokenEndpoint, - {this.secret, bool basicAuth: true, http.Client httpClient}) + {this.secret, + String delimiter, + bool basicAuth: true, + http.Client httpClient}) : _basicAuth = basicAuth, - _httpClient = httpClient == null ? new http.Client() : httpClient; + _httpClient = httpClient == null ? new http.Client() : httpClient, + _delimiter = delimiter ?? ' '; /// Returns the URL to which the resource owner should be redirected to /// authorize this client. @@ -148,7 +159,7 @@ class AuthorizationCodeGrant { }; if (state != null) parameters['state'] = state; - if (!scopes.isEmpty) parameters['scope'] = scopes.join(' '); + if (!scopes.isEmpty) parameters['scope'] = scopes.join(_delimiter); return addQueryParameters(this.authorizationEndpoint, parameters); } @@ -261,7 +272,7 @@ class AuthorizationCodeGrant { headers: headers, body: body); var credentials = handleAccessTokenResponse( - response, tokenEndpoint, startTime, _scopes); + response, tokenEndpoint, startTime, _scopes, _delimiter); return new Client( credentials, identifier: this.identifier, diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index 453181830..a0cbbc3c4 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -26,6 +26,9 @@ import 'utils.dart'; /// Note that a given set of credentials can only be refreshed once, so be sure /// to save the refreshed credentials for future use. class Credentials { + /// A [String] used to separate scopes; defaults to `" "`. + String _delimiter; + /// The token that is sent to the resource server to prove the authorization /// of a client. final String accessToken; @@ -71,16 +74,22 @@ class Credentials { /// [Client.credentials] after a [Client] is created by /// [AuthorizationCodeGrant]. Alternately, it may be loaded from a serialized /// form via [Credentials.fromJson]. + /// + /// The scope strings will be separated by the provided [delimiter]. This + /// defaults to `" "`, the OAuth2 standard, but some APIs (such as Facebook's) + /// use non-standard delimiters. Credentials( this.accessToken, {this.refreshToken, this.tokenEndpoint, Iterable scopes, - this.expiration}) + this.expiration, + String delimiter}) : scopes = new UnmodifiableListView( // Explicitly type-annotate the list literal to work around // sdk#24202. - scopes == null ? [] : scopes.toList()); + scopes == null ? [] : scopes.toList()), + _delimiter = delimiter ?? ' '; /// Loads a set of credentials from a JSON-serialized form. /// @@ -190,7 +199,7 @@ class Credentials { "grant_type": "refresh_token", "refresh_token": refreshToken }; - if (!scopes.isEmpty) body["scope"] = scopes.join(' '); + if (!scopes.isEmpty) body["scope"] = scopes.join(_delimiter); if (basicAuth && secret != null) { headers["Authorization"] = basicAuthHeader(identifier, secret); @@ -202,7 +211,7 @@ class Credentials { var response = await httpClient.post(tokenEndpoint, headers: headers, body: body); var credentials = await handleAccessTokenResponse( - response, tokenEndpoint, startTime, scopes); + response, tokenEndpoint, startTime, scopes, _delimiter); // The authorization server may issue a new refresh token. If it doesn't, // we should re-use the one we already have. diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart index e0fb6f6a0..70fe56001 100644 --- a/pkgs/oauth2/lib/src/handle_access_token_response.dart +++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart @@ -22,11 +22,14 @@ const _expirationGrace = const Duration(seconds: 10); /// /// This response format is common across several different components of the /// OAuth2 flow. +/// +/// The scope strings will be separated by the provided [delimiter]. Credentials handleAccessTokenResponse( http.Response response, Uri tokenEndpoint, DateTime startTime, - List scopes) { + List scopes, + String delimiter) { if (response.statusCode != 200) _handleErrorResponse(response, tokenEndpoint); validate(condition, message) => @@ -78,7 +81,7 @@ Credentials handleAccessTokenResponse( } var scope = parameters['scope'] as String; - if (scope != null) scopes = scope.split(" "); + if (scope != null) scopes = scope.split(delimiter); var expiration = expiresIn == null ? null : startTime.add(new Duration(seconds: expiresIn) - _expirationGrace); diff --git a/pkgs/oauth2/lib/src/resource_owner_password_grant.dart b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart index 5427ecc2d..9986ba742 100644 --- a/pkgs/oauth2/lib/src/resource_owner_password_grant.dart +++ b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart @@ -28,6 +28,10 @@ import 'utils.dart'; /// may not be granted access to every scope you request; you may check the /// [Credentials.scopes] field of [Client.credentials] to see which scopes you /// were granted. +/// +/// The scope strings will be separated by the provided [delimiter]. This +/// defaults to `" "`, the OAuth2 standard, but some APIs (such as Facebook's) +/// use non-standard delimiters. Future resourceOwnerPasswordGrant( Uri authorizationEndpoint, String username, @@ -36,7 +40,9 @@ Future resourceOwnerPasswordGrant( String secret, Iterable scopes, bool basicAuth: true, - http.Client httpClient}) async { + http.Client httpClient, + String delimiter}) async { + delimiter ??= ' '; var startTime = new DateTime.now(); var body = { @@ -56,13 +62,13 @@ Future resourceOwnerPasswordGrant( } } - if (scopes != null && !scopes.isEmpty) body['scope'] = scopes.join(' '); + if (scopes != null && !scopes.isEmpty) body['scope'] = scopes.join(delimiter); if (httpClient == null) httpClient = new http.Client(); var response = await httpClient.post(authorizationEndpoint, headers: headers, body: body); var credentials = await handleAccessTokenResponse( - response, authorizationEndpoint, startTime, scopes); + response, authorizationEndpoint, startTime, scopes, delimiter); return new Client(credentials, identifier: identifier, secret: secret); } diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index a8a05c06f..87d5a2352 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,5 @@ name: oauth2 -version: 1.0.2 +version: 1.1.0 author: Dart Team homepage: http://github.com/dart-lang/oauth2 description: > diff --git a/pkgs/oauth2/test/authorization_code_grant_test.dart b/pkgs/oauth2/test/authorization_code_grant_test.dart index e03a60759..db04725d2 100644 --- a/pkgs/oauth2/test/authorization_code_grant_test.dart +++ b/pkgs/oauth2/test/authorization_code_grant_test.dart @@ -46,6 +46,24 @@ void main() { '&scope=scope+other%2Fscope')); }); + test('separates scopes with the correct delimiter', () { + var grant = new oauth2.AuthorizationCodeGrant( + 'identifier', + Uri.parse('https://example.com/authorization'), + Uri.parse('https://example.com/token'), + secret: 'secret', + httpClient: client, + delimiter: '_'); + var authorizationUrl = grant.getAuthorizationUrl( + redirectUrl, scopes: ['scope', 'other/scope']); + expect(authorizationUrl.toString(), + equals('https://example.com/authorization' + '?response_type=code' + '&client_id=identifier' + '&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect' + '&scope=scope_other%2Fscope')); + }); + test('builds the correct URL with state', () { var authorizationUrl = grant.getAuthorizationUrl( redirectUrl, state: 'state'); diff --git a/pkgs/oauth2/test/credentials_test.dart b/pkgs/oauth2/test/credentials_test.dart index cedea6b2d..0d4eff0ee 100644 --- a/pkgs/oauth2/test/credentials_test.dart +++ b/pkgs/oauth2/test/credentials_test.dart @@ -95,6 +95,27 @@ void main() { expect(credentials.refreshToken, equals('new refresh token')); }); + test('sets proper scope string when using custom delimiter', () async { + var credentials = new oauth2.Credentials( + 'access token', + refreshToken: 'refresh token', + tokenEndpoint: tokenEndpoint, + scopes: ['scope1', 'scope2'], + delimiter: ','); + httpClient.expectRequest((http.Request request) { + expect(request.bodyFields['scope'], equals('scope1,scope2')); + return new Future.value(new http.Response(JSON.encode({ + 'access_token': 'new access token', + 'token_type': 'bearer', + 'refresh_token': 'new refresh token' + }), 200, headers: {'content-type': 'application/json'})); + }); + await credentials.refresh( + identifier: 'idëntīfier', + secret: 'sëcret', + httpClient: httpClient); + }); + test("can refresh without a client secret", () async { var credentials = new oauth2.Credentials( 'access token', diff --git a/pkgs/oauth2/test/handle_access_token_response_test.dart b/pkgs/oauth2/test/handle_access_token_response_test.dart index fe2a3387d..50343ed4d 100644 --- a/pkgs/oauth2/test/handle_access_token_response_test.dart +++ b/pkgs/oauth2/test/handle_access_token_response_test.dart @@ -16,7 +16,7 @@ final Uri tokenEndpoint = Uri.parse("https://example.com/token"); final DateTime startTime = new DateTime.now(); oauth2.Credentials handle(http.Response response) => - handleAccessTokenResponse(response, tokenEndpoint, startTime, ["scope"]); + handleAccessTokenResponse(response, tokenEndpoint, startTime, ["scope"], ' '); void main() { group('an error response', () { @@ -186,5 +186,18 @@ void main() { var credentials = handleSuccess(scope: "scope1 scope2"); expect(credentials.scopes, equals(["scope1", "scope2"])); }); + + test('with a custom scope delimiter sets the scopes', () { + var response = new http.Response(JSON.encode({ + 'access_token': 'access token', + 'token_type': 'bearer', + 'expires_in': null, + 'refresh_token': null, + 'scope': 'scope1,scope2' + }), 200, headers: {'content-type': 'application/json'}); + var credentials = handleAccessTokenResponse( + response, tokenEndpoint, startTime, ['scope'], ','); + expect(credentials.scopes, equals(['scope1', 'scope2'])); + }); }); } diff --git a/pkgs/oauth2/test/resource_owner_password_grant_test.dart b/pkgs/oauth2/test/resource_owner_password_grant_test.dart index 1d73a0fdc..e6d3f35dd 100644 --- a/pkgs/oauth2/test/resource_owner_password_grant_test.dart +++ b/pkgs/oauth2/test/resource_owner_password_grant_test.dart @@ -87,6 +87,21 @@ void main() { expect(client.credentials.accessToken, equals('2YotnFZFEjr1zCsicMWpAA')); }); + test('builds correct request using scope with custom delimiter', () async { + expectClient.expectRequest((request) async { + expect(request.bodyFields['grant_type'], equals('password')); + expect(request.bodyFields['username'], equals('username')); + expect(request.bodyFields['password'], equals('userpass')); + expect(request.bodyFields['scope'], equals('one,two')); + return new http.Response(success, 200, + headers: {'content-type': 'application/json'}); + }); + + await oauth2.resourceOwnerPasswordGrant( + authEndpoint, 'username', 'userpass', + scopes: ['one', 'two'], httpClient: expectClient, delimiter: ','); + }); + test('merges with existing query parameters', () async { var authEndpoint = Uri.parse('https://example.com?query=value'); From b4508932e8741245dcb040257c70f6b74b8f5596 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 2 Jun 2017 14:49:07 -0700 Subject: [PATCH 074/159] Remove unused imports. (dart-lang/oauth2#19) --- pkgs/oauth2/pubspec.yaml | 2 +- pkgs/oauth2/test/resource_owner_password_grant_test.dart | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index 87d5a2352..4f81c5e4a 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,5 @@ name: oauth2 -version: 1.1.0 +version: 1.1.1-dev author: Dart Team homepage: http://github.com/dart-lang/oauth2 description: > diff --git a/pkgs/oauth2/test/resource_owner_password_grant_test.dart b/pkgs/oauth2/test/resource_owner_password_grant_test.dart index e6d3f35dd..c8274f0e4 100644 --- a/pkgs/oauth2/test/resource_owner_password_grant_test.dart +++ b/pkgs/oauth2/test/resource_owner_password_grant_test.dart @@ -4,10 +4,8 @@ @TestOn("vm") -import 'dart:async'; import 'dart:convert'; -import 'package:crypto/crypto.dart'; import 'package:http/http.dart' as http; import 'package:oauth2/oauth2.dart' as oauth2; import 'package:test/test.dart'; From 17677e0e197a0952166f13abebae596cd697da21 Mon Sep 17 00:00:00 2001 From: Peter Carroll Date: Mon, 10 Jul 2017 16:36:21 -0500 Subject: [PATCH 075/159] resourceOwnerPasswordGrant should consistently use HTTP client (dart-lang/oauth2#21) --- pkgs/oauth2/CHANGELOG.md | 5 +++++ pkgs/oauth2/lib/src/resource_owner_password_grant.dart | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md index d629306e6..2a72fc41f 100644 --- a/pkgs/oauth2/CHANGELOG.md +++ b/pkgs/oauth2/CHANGELOG.md @@ -1,3 +1,8 @@ +# 1.1.1 + +* `resourceOwnerPasswordGrant()` now properly uses its HTTP client for requests + made by the OAuth2 client it returns. + # 1.1.0 * Add a `delimiter` parameter to `new AuthorizationCodeGrant()`, `new diff --git a/pkgs/oauth2/lib/src/resource_owner_password_grant.dart b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart index 9986ba742..9b483786c 100644 --- a/pkgs/oauth2/lib/src/resource_owner_password_grant.dart +++ b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart @@ -70,5 +70,6 @@ Future resourceOwnerPasswordGrant( var credentials = await handleAccessTokenResponse( response, authorizationEndpoint, startTime, scopes, delimiter); - return new Client(credentials, identifier: identifier, secret: secret); + return new Client(credentials, + identifier: identifier, secret: secret, httpClient: httpClient); } From 0ea43a81080d9cc5cfb0cfc01d2c0215e18984eb Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Wed, 2 Aug 2017 12:07:22 -0700 Subject: [PATCH 076/159] Homepage: use https (dart-lang/oauth2#22) --- pkgs/oauth2/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index 4f81c5e4a..44e41faa2 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,7 +1,7 @@ name: oauth2 version: 1.1.1-dev author: Dart Team -homepage: http://github.com/dart-lang/oauth2 +homepage: https://github.com/dart-lang/oauth2 description: > A client library for authenticating with a remote service via OAuth2 on behalf of a user, and making authorized HTTP requests with the user's From 28fbd9ad6a45d188844f3c3b72945f7995513c72 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Mon, 16 Oct 2017 14:17:42 -0700 Subject: [PATCH 077/159] Format the package --- .../lib/src/authorization_code_grant.dart | 35 +-- pkgs/oauth2/lib/src/client.dart | 17 +- pkgs/oauth2/lib/src/credentials.dart | 50 ++-- .../lib/src/handle_access_token_response.dart | 46 ++-- .../src/resource_owner_password_grant.dart | 4 +- .../test/authorization_code_grant_test.dart | 178 +++++++------ pkgs/oauth2/test/client_test.dart | 96 +++---- pkgs/oauth2/test/credentials_test.dart | 240 +++++++++--------- .../handle_access_token_response_test.dart | 123 +++++---- pkgs/oauth2/test/utils.dart | 16 +- 10 files changed, 406 insertions(+), 399 deletions(-) diff --git a/pkgs/oauth2/lib/src/authorization_code_grant.dart b/pkgs/oauth2/lib/src/authorization_code_grant.dart index af0b0c6f1..faec8c961 100644 --- a/pkgs/oauth2/lib/src/authorization_code_grant.dart +++ b/pkgs/oauth2/lib/src/authorization_code_grant.dart @@ -106,13 +106,11 @@ class AuthorizationCodeGrant { /// defaults to `" "`, the OAuth2 standard, but some APIs (such as Facebook's) /// use non-standard delimiters. AuthorizationCodeGrant( - this.identifier, - this.authorizationEndpoint, - this.tokenEndpoint, - {this.secret, - String delimiter, - bool basicAuth: true, - http.Client httpClient}) + this.identifier, this.authorizationEndpoint, this.tokenEndpoint, + {this.secret, + String delimiter, + bool basicAuth: true, + http.Client httpClient}) : _basicAuth = basicAuth, _httpClient = httpClient == null ? new http.Client() : httpClient, _delimiter = delimiter ?? ' '; @@ -136,8 +134,8 @@ class AuthorizationCodeGrant { /// query parameters provided to the redirect URL. /// /// It is a [StateError] to call this more than once. - Uri getAuthorizationUrl(Uri redirect, {Iterable scopes, - String state}) { + Uri getAuthorizationUrl(Uri redirect, + {Iterable scopes, String state}) { if (_state != _State.initial) { throw new StateError('The authorization URL has already been generated.'); } @@ -181,14 +179,12 @@ class AuthorizationCodeGrant { /// [FormatError] if the `state` parameter doesn't match the original value. /// /// Throws [AuthorizationException] if the authorization fails. - Future handleAuthorizationResponse(Map parameters) - async { + Future handleAuthorizationResponse( + Map parameters) async { if (_state == _State.initial) { - throw new StateError( - 'The authorization URL has not yet been generated.'); + throw new StateError('The authorization URL has not yet been generated.'); } else if (_state == _State.finished) { - throw new StateError( - 'The authorization code has already been received.'); + throw new StateError('The authorization code has already been received.'); } _state = _State.finished; @@ -235,11 +231,9 @@ class AuthorizationCodeGrant { /// Throws [AuthorizationException] if the authorization fails. Future handleAuthorizationCode(String authorizationCode) async { if (_state == _State.initial) { - throw new StateError( - 'The authorization URL has not yet been generated.'); + throw new StateError('The authorization URL has not yet been generated.'); } else if (_state == _State.finished) { - throw new StateError( - 'The authorization code has already been received.'); + throw new StateError('The authorization code has already been received.'); } _state = _State.finished; @@ -273,8 +267,7 @@ class AuthorizationCodeGrant { var credentials = handleAccessTokenResponse( response, tokenEndpoint, startTime, _scopes, _delimiter); - return new Client( - credentials, + return new Client(credentials, identifier: this.identifier, secret: this.secret, basicAuth: _basicAuth, diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart index 20a4d70c7..7d634e540 100644 --- a/pkgs/oauth2/lib/src/client.dart +++ b/pkgs/oauth2/lib/src/client.dart @@ -83,8 +83,11 @@ class Client extends http.BaseClient { /// adding authorization credentials to them. /// /// Throws an [ArgumentError] if [secret] is passed without [identifier]. - Client(this._credentials, {this.identifier, this.secret, - bool basicAuth: true, http.Client httpClient}) + Client(this._credentials, + {this.identifier, + this.secret, + bool basicAuth: true, + http.Client httpClient}) : _basicAuth = basicAuth, _httpClient = httpClient == null ? new http.Client() : httpClient { if (identifier == null && secret != null) { @@ -110,21 +113,23 @@ class Client extends http.BaseClient { var challenges; try { - challenges = AuthenticationChallenge.parseHeader( - response.headers['www-authenticate']); + challenges = AuthenticationChallenge + .parseHeader(response.headers['www-authenticate']); } on FormatException { return response; } var challenge = challenges.firstWhere( - (challenge) => challenge.scheme == 'bearer', orElse: () => null); + (challenge) => challenge.scheme == 'bearer', + orElse: () => null); if (challenge == null) return response; var params = challenge.parameters; if (!params.containsKey('error')) return response; throw new AuthorizationException( - params['error'], params['error_description'], + params['error'], + params['error_description'], params['error_uri'] == null ? null : Uri.parse(params['error_uri'])); } diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index a0cbbc3c4..06a89d0b4 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -62,8 +62,8 @@ class Credentials { /// 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 DateTime.now().isAfter(expiration); + bool get isExpired => + expiration != null && new DateTime.now().isAfter(expiration); /// Whether it's possible to refresh these credentials. bool get canRefresh => refreshToken != null && tokenEndpoint != null; @@ -78,13 +78,12 @@ class Credentials { /// The scope strings will be separated by the provided [delimiter]. This /// defaults to `" "`, the OAuth2 standard, but some APIs (such as Facebook's) /// use non-standard delimiters. - Credentials( - this.accessToken, - {this.refreshToken, - this.tokenEndpoint, - Iterable scopes, - this.expiration, - String delimiter}) + Credentials(this.accessToken, + {this.refreshToken, + this.tokenEndpoint, + Iterable scopes, + this.expiration, + String delimiter}) : scopes = new UnmodifiableListView( // Explicitly type-annotate the list literal to work around // sdk#24202. @@ -111,11 +110,11 @@ class Credentials { validate(parsed is Map, 'was not a JSON map'); validate(parsed.containsKey('accessToken'), 'did not contain required field "accessToken"'); - validate(parsed['accessToken'] is String, + validate( + parsed['accessToken'] is String, 'required field "accessToken" was not a string, was ' '${parsed["accessToken"]}'); - for (var stringField in ['refreshToken', 'tokenEndpoint']) { var value = parsed[stringField]; validate(value == null || value is String, @@ -137,8 +136,7 @@ class Credentials { expiration = new DateTime.fromMillisecondsSinceEpoch(expiration); } - return new Credentials( - parsed['accessToken'], + return new Credentials(parsed['accessToken'], refreshToken: parsed['refreshToken'], tokenEndpoint: tokenEndpoint, scopes: (scopes as List).map((scope) => scope as String), @@ -150,12 +148,14 @@ class Credentials { /// Nothing is guaranteed about the output except that it's valid JSON and /// compatible with [Credentials.toJson]. String toJson() => JSON.encode({ - 'accessToken': accessToken, - 'refreshToken': refreshToken, - 'tokenEndpoint': tokenEndpoint == null ? null : tokenEndpoint.toString(), - 'scopes': scopes, - 'expiration': expiration == null ? null : expiration.millisecondsSinceEpoch - }); + '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. /// @@ -195,10 +195,7 @@ class Credentials { var headers = {}; - var body = { - "grant_type": "refresh_token", - "refresh_token": refreshToken - }; + var body = {"grant_type": "refresh_token", "refresh_token": refreshToken}; if (!scopes.isEmpty) body["scope"] = scopes.join(_delimiter); if (basicAuth && secret != null) { @@ -208,16 +205,15 @@ class Credentials { if (secret != null) body["client_secret"] = secret; } - var response = await httpClient.post(tokenEndpoint, - headers: headers, body: body); + var response = + await httpClient.post(tokenEndpoint, headers: headers, body: body); var credentials = await handleAccessTokenResponse( response, tokenEndpoint, startTime, scopes, _delimiter); // The authorization server may issue a new refresh token. If it doesn't, // we should re-use the one we already have. if (credentials.refreshToken != null) return credentials; - return new Credentials( - credentials.accessToken, + return new Credentials(credentials.accessToken, refreshToken: this.refreshToken, tokenEndpoint: credentials.tokenEndpoint, scopes: credentials.scopes, diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart index 70fe56001..7f03f1f3a 100644 --- a/pkgs/oauth2/lib/src/handle_access_token_response.dart +++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart @@ -22,29 +22,25 @@ const _expirationGrace = const Duration(seconds: 10); /// /// This response format is common across several different components of the /// OAuth2 flow. -/// +/// /// The scope strings will be separated by the provided [delimiter]. -Credentials handleAccessTokenResponse( - http.Response response, - Uri tokenEndpoint, - DateTime startTime, - List scopes, - String delimiter) { +Credentials handleAccessTokenResponse(http.Response response, Uri tokenEndpoint, + DateTime startTime, List scopes, String delimiter) { if (response.statusCode != 200) _handleErrorResponse(response, tokenEndpoint); validate(condition, message) => _validate(response, tokenEndpoint, condition, message); var contentTypeString = response.headers['content-type']; - var contentType = contentTypeString == null - ? null - : new MediaType.parse(contentTypeString); + var contentType = + contentTypeString == null ? null : new MediaType.parse(contentTypeString); // The spec requires a content-type of application/json, but some endpoints // (e.g. Dropbox) serve it as text/javascript instead. - validate(contentType != null && - (contentType.mimeType == "application/json" || - contentType.mimeType == "text/javascript"), + validate( + contentType != null && + (contentType.mimeType == "application/json" || + contentType.mimeType == "text/javascript"), 'content-type was "$contentType", expected "application/json"'); Map parameters; @@ -60,7 +56,8 @@ Credentials handleAccessTokenResponse( for (var requiredParameter in ['access_token', 'token_type']) { validate(parameters.containsKey(requiredParameter), 'did not contain required parameter "$requiredParameter"'); - validate(parameters[requiredParameter] is String, + validate( + parameters[requiredParameter] is String, 'required parameter "$requiredParameter" was not a string, was ' '"${parameters[requiredParameter]}"'); } @@ -83,11 +80,11 @@ Credentials handleAccessTokenResponse( var scope = parameters['scope'] as String; if (scope != null) scopes = scope.split(delimiter); - var expiration = expiresIn == null ? null : - startTime.add(new Duration(seconds: expiresIn) - _expirationGrace); + var expiration = expiresIn == null + ? null + : startTime.add(new Duration(seconds: expiresIn) - _expirationGrace); - return new Credentials( - parameters['access_token'], + return new Credentials(parameters['access_token'], refreshToken: parameters['refresh_token'], tokenEndpoint: tokenEndpoint, scopes: scopes, @@ -113,9 +110,8 @@ void _handleErrorResponse(http.Response response, Uri tokenEndpoint) { } var contentTypeString = response.headers['content-type']; - var contentType = contentTypeString == null - ? null - : new MediaType.parse(contentTypeString); + var contentType = + contentTypeString == null ? null : new MediaType.parse(contentTypeString); validate(contentType != null && contentType.mimeType == "application/json", 'content-type was "$contentType", expected "application/json"'); @@ -129,7 +125,8 @@ void _handleErrorResponse(http.Response response, Uri tokenEndpoint) { validate(parameters.containsKey('error'), 'did not contain required parameter "error"'); - validate(parameters["error"] is String, + validate( + parameters["error"] is String, 'required parameter "error" was not a string, was ' '"${parameters["error"]}"'); @@ -146,10 +143,7 @@ void _handleErrorResponse(http.Response response, Uri tokenEndpoint) { } void _validate( - http.Response response, - Uri tokenEndpoint, - bool condition, - String message) { + http.Response response, Uri tokenEndpoint, bool condition, String message) { if (condition) return; throw new FormatException('Invalid OAuth response for "$tokenEndpoint": ' '$message.\n\n${response.body}'); diff --git a/pkgs/oauth2/lib/src/resource_owner_password_grant.dart b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart index 9b483786c..90c7f6cf5 100644 --- a/pkgs/oauth2/lib/src/resource_owner_password_grant.dart +++ b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart @@ -33,9 +33,7 @@ import 'utils.dart'; /// defaults to `" "`, the OAuth2 standard, but some APIs (such as Facebook's) /// use non-standard delimiters. Future resourceOwnerPasswordGrant( - Uri authorizationEndpoint, - String username, - String password, + Uri authorizationEndpoint, String username, String password, {String identifier, String secret, Iterable scopes, diff --git a/pkgs/oauth2/test/authorization_code_grant_test.dart b/pkgs/oauth2/test/authorization_code_grant_test.dart index db04725d2..9cb1e733e 100644 --- a/pkgs/oauth2/test/authorization_code_grant_test.dart +++ b/pkgs/oauth2/test/authorization_code_grant_test.dart @@ -28,7 +28,8 @@ void main() { group('.getAuthorizationUrl', () { test('builds the correct URL', () { - expect(grant.getAuthorizationUrl(redirectUrl).toString(), + expect( + grant.getAuthorizationUrl(redirectUrl).toString(), equals('https://example.com/authorization' '?response_type=code' '&client_id=identifier' @@ -36,9 +37,10 @@ void main() { }); test('builds the correct URL with scopes', () { - var authorizationUrl = grant.getAuthorizationUrl( - redirectUrl, scopes: ['scope', 'other/scope']); - expect(authorizationUrl.toString(), + var authorizationUrl = grant + .getAuthorizationUrl(redirectUrl, scopes: ['scope', 'other/scope']); + expect( + authorizationUrl.toString(), equals('https://example.com/authorization' '?response_type=code' '&client_id=identifier' @@ -54,9 +56,10 @@ void main() { secret: 'secret', httpClient: client, delimiter: '_'); - var authorizationUrl = grant.getAuthorizationUrl( - redirectUrl, scopes: ['scope', 'other/scope']); - expect(authorizationUrl.toString(), + var authorizationUrl = grant + .getAuthorizationUrl(redirectUrl, scopes: ['scope', 'other/scope']); + expect( + authorizationUrl.toString(), equals('https://example.com/authorization' '?response_type=code' '&client_id=identifier' @@ -65,9 +68,10 @@ void main() { }); test('builds the correct URL with state', () { - var authorizationUrl = grant.getAuthorizationUrl( - redirectUrl, state: 'state'); - expect(authorizationUrl.toString(), + var authorizationUrl = + grant.getAuthorizationUrl(redirectUrl, state: 'state'); + expect( + authorizationUrl.toString(), equals('https://example.com/authorization' '?response_type=code' '&client_id=identifier' @@ -84,7 +88,8 @@ void main() { httpClient: client); var authorizationUrl = grant.getAuthorizationUrl(redirectUrl); - expect(authorizationUrl.toString(), + expect( + authorizationUrl.toString(), equals('https://example.com/authorization' '?query=value' '&response_type=code' @@ -119,10 +124,10 @@ void main() { test('must have the same state parameter the authorization URL did', () { grant.getAuthorizationUrl(redirectUrl, state: 'state'); - expect(grant.handleAuthorizationResponse({ - 'code': 'auth code', - 'state': 'other state' - }), throwsFormatException); + expect( + grant.handleAuthorizationResponse( + {'code': 'auth code', 'state': 'other state'}), + throwsFormatException); }); test('must have a code parameter', () { @@ -132,8 +137,7 @@ void main() { test('with an error parameter throws an AuthorizationException', () { grant.getAuthorizationUrl(redirectUrl); - expect( - grant.handleAuthorizationResponse({'error': 'invalid_request'}), + expect(grant.handleAuthorizationResponse({'error': 'invalid_request'}), throwsA((e) => e is oauth2.AuthorizationException)); }); @@ -142,23 +146,28 @@ void main() { client.expectRequest((request) { expect(request.method, equals('POST')); expect(request.url.toString(), equals(grant.tokenEndpoint.toString())); - expect(request.bodyFields, equals({ - 'grant_type': 'authorization_code', - 'code': 'auth code', - 'redirect_uri': redirectUrl.toString() - })); - expect(request.headers, containsPair( - "Authorization", - "Basic aWRlbnRpZmllcjpzZWNyZXQ=")); - - return new Future.value(new http.Response(JSON.encode({ - 'access_token': 'access token', - 'token_type': 'bearer', - }), 200, headers: {'content-type': 'application/json'})); + expect( + request.bodyFields, + equals({ + 'grant_type': 'authorization_code', + 'code': 'auth code', + 'redirect_uri': redirectUrl.toString() + })); + expect(request.headers, + containsPair("Authorization", "Basic aWRlbnRpZmllcjpzZWNyZXQ=")); + + return new Future.value(new http.Response( + JSON.encode({ + 'access_token': 'access token', + 'token_type': 'bearer', + }), + 200, + headers: {'content-type': 'application/json'})); }); - expect(grant.handleAuthorizationResponse({'code': 'auth code'}) - .then((client) => client.credentials.accessToken), + expect( + grant.handleAuthorizationResponse({'code': 'auth code'}).then( + (client) => client.credentials.accessToken), completion(equals('access token'))); }); }); @@ -179,26 +188,30 @@ void main() { client.expectRequest((request) { expect(request.method, equals('POST')); expect(request.url.toString(), equals(grant.tokenEndpoint.toString())); - expect(request.bodyFields, equals({ - 'grant_type': 'authorization_code', - 'code': 'auth code', - 'redirect_uri': redirectUrl.toString() - })); - expect(request.headers, containsPair( - "Authorization", - "Basic aWRlbnRpZmllcjpzZWNyZXQ=")); - - return new Future.value(new http.Response(JSON.encode({ - 'access_token': 'access token', - 'token_type': 'bearer', - }), 200, headers: {'content-type': 'application/json'})); + expect( + request.bodyFields, + equals({ + 'grant_type': 'authorization_code', + 'code': 'auth code', + 'redirect_uri': redirectUrl.toString() + })); + expect(request.headers, + containsPair("Authorization", "Basic aWRlbnRpZmllcjpzZWNyZXQ=")); + + return new Future.value(new http.Response( + JSON.encode({ + 'access_token': 'access token', + 'token_type': 'bearer', + }), + 200, + headers: {'content-type': 'application/json'})); }); expect(grant.handleAuthorizationCode('auth code'), completion(predicate((client) { - expect(client.credentials.accessToken, equals('access token')); - return true; - }))); + expect(client.credentials.accessToken, equals('access token')); + return true; + }))); }); }); @@ -220,22 +233,28 @@ void main() { client.expectRequest((request) { expect(request.method, equals('POST')); expect(request.url.toString(), equals(grant.tokenEndpoint.toString())); - expect(request.bodyFields, equals({ - 'grant_type': 'authorization_code', - 'code': 'auth code', - 'redirect_uri': redirectUrl.toString(), - 'client_id': 'identifier', - 'client_secret': 'secret' - })); - - return new Future.value(new http.Response(JSON.encode({ - 'access_token': 'access token', - 'token_type': 'bearer', - }), 200, headers: {'content-type': 'application/json'})); + expect( + request.bodyFields, + equals({ + 'grant_type': 'authorization_code', + 'code': 'auth code', + 'redirect_uri': redirectUrl.toString(), + 'client_id': 'identifier', + 'client_secret': 'secret' + })); + + return new Future.value(new http.Response( + JSON.encode({ + 'access_token': 'access token', + 'token_type': 'bearer', + }), + 200, + headers: {'content-type': 'application/json'})); }); - expect(grant.handleAuthorizationResponse({'code': 'auth code'}) - .then((client) => client.credentials.accessToken), + expect( + grant.handleAuthorizationResponse({'code': 'auth code'}).then( + (client) => client.credentials.accessToken), completion(equals('access token'))); }); @@ -244,25 +263,30 @@ void main() { client.expectRequest((request) { expect(request.method, equals('POST')); expect(request.url.toString(), equals(grant.tokenEndpoint.toString())); - expect(request.bodyFields, equals({ - 'grant_type': 'authorization_code', - 'code': 'auth code', - 'redirect_uri': redirectUrl.toString(), - 'client_id': 'identifier', - 'client_secret': 'secret' - })); - - return new Future.value(new http.Response(JSON.encode({ - 'access_token': 'access token', - 'token_type': 'bearer', - }), 200, headers: {'content-type': 'application/json'})); + expect( + request.bodyFields, + equals({ + 'grant_type': 'authorization_code', + 'code': 'auth code', + 'redirect_uri': redirectUrl.toString(), + 'client_id': 'identifier', + 'client_secret': 'secret' + })); + + return new Future.value(new http.Response( + JSON.encode({ + 'access_token': 'access token', + 'token_type': 'bearer', + }), + 200, + headers: {'content-type': 'application/json'})); }); expect(grant.handleAuthorizationCode('auth code'), completion(predicate((client) { - expect(client.credentials.accessToken, equals('access token')); - return true; - }))); + expect(client.credentials.accessToken, equals('access token')); + return true; + }))); }); }); } diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart index 7dc5afd2e..7f5980370 100644 --- a/pkgs/oauth2/test/client_test.dart +++ b/pkgs/oauth2/test/client_test.dart @@ -22,37 +22,34 @@ void main() { group('with expired credentials', () { test("that can't be refreshed throws an ExpirationException on send", () { var expiration = new DateTime.now().subtract(new Duration(hours: 1)); - var credentials = new oauth2.Credentials( - 'access token', expiration: expiration); + var credentials = + new oauth2.Credentials('access token', expiration: expiration); var client = new oauth2.Client(credentials, - identifier: 'identifier', - secret: 'secret', - httpClient: httpClient); + identifier: 'identifier', secret: 'secret', httpClient: httpClient); expect(client.get(requestUri), throwsA(new isInstanceOf())); }); - test("that can be refreshed refreshes the credentials and sends the " + test( + "that can be refreshed refreshes the credentials and sends the " "request", () async { var expiration = new DateTime.now().subtract(new Duration(hours: 1)); - var credentials = new oauth2.Credentials( - 'access token', + var credentials = new oauth2.Credentials('access token', refreshToken: 'refresh token', tokenEndpoint: tokenEndpoint, expiration: expiration); var client = new oauth2.Client(credentials, - identifier: 'identifier', - secret: 'secret', - httpClient: httpClient); + identifier: 'identifier', secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { expect(request.method, equals('POST')); expect(request.url.toString(), equals(tokenEndpoint.toString())); - return new Future.value(new http.Response(JSON.encode({ - 'access_token': 'new access token', - 'token_type': 'bearer' - }), 200, headers: {'content-type': 'application/json'})); + return new Future.value(new http.Response( + JSON.encode( + {'access_token': 'new access token', 'token_type': 'bearer'}), + 200, + headers: {'content-type': 'application/json'})); }); httpClient.expectRequest((request) { @@ -73,9 +70,7 @@ void main() { test("sends a request with bearer authorization", () { var credentials = new oauth2.Credentials('access token'); var client = new oauth2.Client(credentials, - identifier: 'identifier', - secret: 'secret', - httpClient: httpClient); + identifier: 'identifier', secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { expect(request.method, equals('GET')); @@ -89,22 +84,19 @@ void main() { }); test("can manually refresh the credentials", () async { - var credentials = new oauth2.Credentials( - 'access token', - refreshToken: 'refresh token', - tokenEndpoint: tokenEndpoint); + var credentials = new oauth2.Credentials('access token', + refreshToken: 'refresh token', tokenEndpoint: tokenEndpoint); var client = new oauth2.Client(credentials, - identifier: 'identifier', - secret: 'secret', - httpClient: httpClient); + identifier: 'identifier', secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { expect(request.method, equals('POST')); expect(request.url.toString(), equals(tokenEndpoint.toString())); - return new Future.value(new http.Response(JSON.encode({ - 'access_token': 'new access token', - 'token_type': 'bearer' - }), 200, headers: {'content-type': 'application/json'})); + return new Future.value(new http.Response( + JSON.encode( + {'access_token': 'new access token', 'token_type': 'bearer'}), + 200, + headers: {'content-type': 'application/json'})); }); await client.refreshCredentials(); @@ -114,9 +106,7 @@ void main() { test("without a refresh token can't manually refresh the credentials", () { var credentials = new oauth2.Credentials('access token'); var client = new oauth2.Client(credentials, - identifier: 'identifier', - secret: 'secret', - httpClient: httpClient); + identifier: 'identifier', secret: 'secret', httpClient: httpClient); expect(client.refreshCredentials(), throwsA(isStateError)); }); @@ -126,9 +116,7 @@ void main() { test('throws an AuthorizationException for a 401 response', () { var credentials = new oauth2.Credentials('access token'); var client = new oauth2.Client(credentials, - identifier: 'identifier', - secret: 'secret', - httpClient: httpClient); + identifier: 'identifier', secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { expect(request.method, equals('GET')); @@ -138,7 +126,7 @@ void main() { var authenticate = 'Bearer error="invalid_token", error_description=' '"Something is terribly wrong."'; return new Future.value(new http.Response('bad job', 401, - headers: {'www-authenticate': authenticate})); + headers: {'www-authenticate': authenticate})); }); expect(client.read(requestUri), @@ -148,15 +136,12 @@ void main() { test('passes through a 401 response without www-authenticate', () async { var credentials = new oauth2.Credentials('access token'); var client = new oauth2.Client(credentials, - identifier: 'identifier', - secret: 'secret', - httpClient: httpClient); + identifier: 'identifier', secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { expect(request.method, equals('GET')); expect(request.url.toString(), equals(requestUri.toString())); - expect(request.headers['authorization'], - equals('Bearer access token')); + expect(request.headers['authorization'], equals('Bearer access token')); return new Future.value(new http.Response('bad job', 401)); }); @@ -168,20 +153,17 @@ void main() { () async { var credentials = new oauth2.Credentials('access token'); var client = new oauth2.Client(credentials, - identifier: 'identifier', - secret: 'secret', - httpClient: httpClient); + identifier: 'identifier', secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { expect(request.method, equals('GET')); expect(request.url.toString(), equals(requestUri.toString())); - expect(request.headers['authorization'], - equals('Bearer access token')); + expect(request.headers['authorization'], equals('Bearer access token')); var authenticate = 'Bearer error="invalid_token" error_description=' - '"Something is terribly wrong."'; + '"Something is terribly wrong."'; return new Future.value(new http.Response('bad job', 401, - headers: {'www-authenticate': authenticate})); + headers: {'www-authenticate': authenticate})); }); expect((await client.get(requestUri)).statusCode, equals(401)); @@ -191,18 +173,15 @@ void main() { () async { var credentials = new oauth2.Credentials('access token'); var client = new oauth2.Client(credentials, - identifier: 'identifier', - secret: 'secret', - httpClient: httpClient); + identifier: 'identifier', secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { expect(request.method, equals('GET')); expect(request.url.toString(), equals(requestUri.toString())); - expect(request.headers['authorization'], - equals('Bearer access token')); + expect(request.headers['authorization'], equals('Bearer access token')); return new Future.value(new http.Response('bad job', 401, - headers: {'www-authenticate': 'Digest'})); + headers: {'www-authenticate': 'Digest'})); }); expect((await client.get(requestUri)).statusCode, equals(401)); @@ -212,18 +191,15 @@ void main() { () async { var credentials = new oauth2.Credentials('access token'); var client = new oauth2.Client(credentials, - identifier: 'identifier', - secret: 'secret', - httpClient: httpClient); + identifier: 'identifier', secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { expect(request.method, equals('GET')); expect(request.url.toString(), equals(requestUri.toString())); - expect(request.headers['authorization'], - equals('Bearer access token')); + expect(request.headers['authorization'], equals('Bearer access token')); return new Future.value(new http.Response('bad job', 401, - headers: {'www-authenticate': 'Bearer'})); + headers: {'www-authenticate': 'Bearer'})); }); expect((await client.get(requestUri)).statusCode, equals(401)); diff --git a/pkgs/oauth2/test/credentials_test.dart b/pkgs/oauth2/test/credentials_test.dart index 0d4eff0ee..fd77efac0 100644 --- a/pkgs/oauth2/test/credentials_test.dart +++ b/pkgs/oauth2/test/credentials_test.dart @@ -24,45 +24,42 @@ void main() { test('is not expired if the expiration is in the future', () { var expiration = new DateTime.now().add(new Duration(hours: 1)); - var credentials = new oauth2.Credentials( - 'access token', expiration: expiration); + var credentials = + new oauth2.Credentials('access token', expiration: expiration); expect(credentials.isExpired, isFalse); }); test('is expired if the expiration is in the past', () { var expiration = new DateTime.now().subtract(new Duration(hours: 1)); - var credentials = new oauth2.Credentials( - 'access token', expiration: expiration); + var credentials = + new oauth2.Credentials('access token', expiration: expiration); expect(credentials.isExpired, isTrue); }); test("can't refresh without a refresh token", () { - var credentials = new oauth2.Credentials( - 'access token', tokenEndpoint: tokenEndpoint); + var credentials = + new oauth2.Credentials('access token', tokenEndpoint: tokenEndpoint); expect(credentials.canRefresh, false); - expect(credentials.refresh( - identifier: 'identifier', - secret: 'secret', - httpClient: httpClient), + expect( + credentials.refresh( + identifier: 'identifier', secret: 'secret', httpClient: httpClient), throwsStateError); }); test("can't refresh without a token endpoint", () { - var credentials = new oauth2.Credentials( - 'access token', refreshToken: 'refresh token'); + var credentials = + new oauth2.Credentials('access token', refreshToken: 'refresh token'); expect(credentials.canRefresh, false); - expect(credentials.refresh( - identifier: 'identifier', - secret: 'secret', - httpClient: httpClient), + expect( + credentials.refresh( + identifier: 'identifier', secret: 'secret', httpClient: httpClient), throwsStateError); }); test("can refresh with a refresh token and a token endpoint", () async { - var credentials = new oauth2.Credentials( - 'access token', + var credentials = new oauth2.Credentials('access token', refreshToken: 'refresh token', tokenEndpoint: tokenEndpoint, scopes: ['scope1', 'scope2']); @@ -71,54 +68,57 @@ void main() { httpClient.expectRequest((request) { expect(request.method, equals('POST')); expect(request.url.toString(), equals(tokenEndpoint.toString())); - expect(request.bodyFields, equals({ - "grant_type": "refresh_token", - "refresh_token": "refresh token", - "scope": "scope1 scope2" - })); - expect(request.headers, containsPair( - "Authorization", - "Basic aWQlQzMlQUJudCVDNCVBQmZpZXI6cyVDMyVBQmNyZXQ=")); - - return new Future.value(new http.Response(JSON.encode({ - 'access_token': 'new access token', - 'token_type': 'bearer', - 'refresh_token': 'new refresh token' - }), 200, headers: {'content-type': 'application/json'})); + expect( + request.bodyFields, + equals({ + "grant_type": "refresh_token", + "refresh_token": "refresh token", + "scope": "scope1 scope2" + })); + expect( + request.headers, + containsPair("Authorization", + "Basic aWQlQzMlQUJudCVDNCVBQmZpZXI6cyVDMyVBQmNyZXQ=")); + + return new Future.value(new http.Response( + JSON.encode({ + 'access_token': 'new access token', + 'token_type': 'bearer', + 'refresh_token': 'new refresh token' + }), + 200, + headers: {'content-type': 'application/json'})); }); credentials = await credentials.refresh( - identifier: 'idëntīfier', - secret: 'sëcret', - httpClient: httpClient); + identifier: 'idëntīfier', secret: 'sëcret', httpClient: httpClient); expect(credentials.accessToken, equals('new access token')); expect(credentials.refreshToken, equals('new refresh token')); }); test('sets proper scope string when using custom delimiter', () async { - var credentials = new oauth2.Credentials( - 'access token', + var credentials = new oauth2.Credentials('access token', refreshToken: 'refresh token', tokenEndpoint: tokenEndpoint, scopes: ['scope1', 'scope2'], delimiter: ','); httpClient.expectRequest((http.Request request) { expect(request.bodyFields['scope'], equals('scope1,scope2')); - return new Future.value(new http.Response(JSON.encode({ - 'access_token': 'new access token', - 'token_type': 'bearer', - 'refresh_token': 'new refresh token' - }), 200, headers: {'content-type': 'application/json'})); + return new Future.value(new http.Response( + JSON.encode({ + 'access_token': 'new access token', + 'token_type': 'bearer', + 'refresh_token': 'new refresh token' + }), + 200, + headers: {'content-type': 'application/json'})); }); await credentials.refresh( - identifier: 'idëntīfier', - secret: 'sëcret', - httpClient: httpClient); + identifier: 'idëntīfier', secret: 'sëcret', httpClient: httpClient); }); test("can refresh without a client secret", () async { - var credentials = new oauth2.Credentials( - 'access token', + var credentials = new oauth2.Credentials('access token', refreshToken: 'refresh token', tokenEndpoint: tokenEndpoint, scopes: ['scope1', 'scope2']); @@ -127,31 +127,33 @@ void main() { httpClient.expectRequest((request) { expect(request.method, equals('POST')); expect(request.url.toString(), equals(tokenEndpoint.toString())); - expect(request.bodyFields, equals({ - "grant_type": "refresh_token", - "refresh_token": "refresh token", - "scope": "scope1 scope2", - "client_id": "identifier" - })); - - return new Future.value(new http.Response(JSON.encode({ - 'access_token': 'new access token', - 'token_type': 'bearer', - 'refresh_token': 'new refresh token' - }), 200, headers: {'content-type': 'application/json'})); + expect( + request.bodyFields, + equals({ + "grant_type": "refresh_token", + "refresh_token": "refresh token", + "scope": "scope1 scope2", + "client_id": "identifier" + })); + + return new Future.value(new http.Response( + JSON.encode({ + 'access_token': 'new access token', + 'token_type': 'bearer', + 'refresh_token': 'new refresh token' + }), + 200, + headers: {'content-type': 'application/json'})); }); - credentials = await credentials.refresh( - identifier: 'identifier', - httpClient: httpClient); + identifier: 'identifier', httpClient: httpClient); expect(credentials.accessToken, equals('new access token')); expect(credentials.refreshToken, equals('new refresh token')); }); test("can refresh without client authentication", () async { - var credentials = new oauth2.Credentials( - 'access token', + var credentials = new oauth2.Credentials('access token', refreshToken: 'refresh token', tokenEndpoint: tokenEndpoint, scopes: ['scope1', 'scope2']); @@ -160,61 +162,63 @@ void main() { httpClient.expectRequest((request) { expect(request.method, equals('POST')); expect(request.url.toString(), equals(tokenEndpoint.toString())); - expect(request.bodyFields, equals({ - "grant_type": "refresh_token", - "refresh_token": "refresh token", - "scope": "scope1 scope2" - })); - - return new Future.value(new http.Response(JSON.encode({ - 'access_token': 'new access token', - 'token_type': 'bearer', - 'refresh_token': 'new refresh token' - }), 200, headers: {'content-type': 'application/json'})); + expect( + request.bodyFields, + equals({ + "grant_type": "refresh_token", + "refresh_token": "refresh token", + "scope": "scope1 scope2" + })); + + return new Future.value(new http.Response( + JSON.encode({ + 'access_token': 'new access token', + 'token_type': 'bearer', + 'refresh_token': 'new refresh token' + }), + 200, + headers: {'content-type': 'application/json'})); }); - credentials = await credentials.refresh(httpClient: httpClient); expect(credentials.accessToken, equals('new access token')); expect(credentials.refreshToken, equals('new refresh token')); }); test("uses the old refresh token if a new one isn't provided", () async { - var credentials = new oauth2.Credentials( - 'access token', - refreshToken: 'refresh token', - tokenEndpoint: tokenEndpoint); + var credentials = new oauth2.Credentials('access token', + refreshToken: 'refresh token', tokenEndpoint: tokenEndpoint); expect(credentials.canRefresh, true); httpClient.expectRequest((request) { expect(request.method, equals('POST')); expect(request.url.toString(), equals(tokenEndpoint.toString())); - expect(request.bodyFields, equals({ - "grant_type": "refresh_token", - "refresh_token": "refresh token" - })); - expect(request.headers, containsPair( - "Authorization", - "Basic aWQlQzMlQUJudCVDNCVBQmZpZXI6cyVDMyVBQmNyZXQ=")); - - return new Future.value(new http.Response(JSON.encode({ - 'access_token': 'new access token', - 'token_type': 'bearer' - }), 200, headers: {'content-type': 'application/json'})); + expect( + request.bodyFields, + equals({ + "grant_type": "refresh_token", + "refresh_token": "refresh token" + })); + expect( + request.headers, + containsPair("Authorization", + "Basic aWQlQzMlQUJudCVDNCVBQmZpZXI6cyVDMyVBQmNyZXQ=")); + + return new Future.value(new http.Response( + JSON.encode( + {'access_token': 'new access token', 'token_type': 'bearer'}), + 200, + headers: {'content-type': 'application/json'})); }); - credentials = await credentials.refresh( - identifier: 'idëntīfier', - secret: 'sëcret', - httpClient: httpClient); + identifier: 'idëntīfier', secret: 'sëcret', httpClient: httpClient); expect(credentials.accessToken, equals('new access token')); expect(credentials.refreshToken, equals('refresh token')); }); test("uses form-field authentication if basicAuth is false", () async { - var credentials = new oauth2.Credentials( - 'access token', + var credentials = new oauth2.Credentials('access token', refreshToken: 'refresh token', tokenEndpoint: tokenEndpoint, scopes: ['scope1', 'scope2']); @@ -223,19 +227,24 @@ void main() { httpClient.expectRequest((request) { expect(request.method, equals('POST')); expect(request.url.toString(), equals(tokenEndpoint.toString())); - expect(request.bodyFields, equals({ - "grant_type": "refresh_token", - "refresh_token": "refresh token", - "scope": "scope1 scope2", - "client_id": "idëntīfier", - "client_secret": "sëcret" - })); - - return new Future.value(new http.Response(JSON.encode({ - 'access_token': 'new access token', - 'token_type': 'bearer', - 'refresh_token': 'new refresh token' - }), 200, headers: {'content-type': 'application/json'})); + expect( + request.bodyFields, + equals({ + "grant_type": "refresh_token", + "refresh_token": "refresh token", + "scope": "scope1 scope2", + "client_id": "idëntīfier", + "client_secret": "sëcret" + })); + + return new Future.value(new http.Response( + JSON.encode({ + 'access_token': 'new access token', + 'token_type': 'bearer', + 'refresh_token': 'new refresh token' + }), + 200, + headers: {'content-type': 'application/json'})); }); credentials = await credentials.refresh( @@ -249,7 +258,7 @@ void main() { group("fromJson", () { oauth2.Credentials fromMap(Map map) => - new oauth2.Credentials.fromJson(JSON.encode(map)); + new oauth2.Credentials.fromJson(JSON.encode(map)); test("should load the same credentials from toJson", () { // Round the expiration down to milliseconds since epoch, since that's @@ -259,8 +268,7 @@ void main() { expiration = new DateTime.fromMillisecondsSinceEpoch( expiration.millisecondsSinceEpoch); - var credentials = new oauth2.Credentials( - 'access token', + var credentials = new oauth2.Credentials('access token', refreshToken: 'refresh token', tokenEndpoint: tokenEndpoint, scopes: ['scope1', 'scope2'], @@ -281,8 +289,8 @@ void main() { }); test("should throw a FormatException for JSON that's not a map", () { - expect(() => new oauth2.Credentials.fromJson("null"), - throwsFormatException); + expect( + () => new oauth2.Credentials.fromJson("null"), throwsFormatException); }); test("should throw a FormatException if there is no accessToken", () { diff --git a/pkgs/oauth2/test/handle_access_token_response_test.dart b/pkgs/oauth2/test/handle_access_token_response_test.dart index 50343ed4d..6edfbcb31 100644 --- a/pkgs/oauth2/test/handle_access_token_response_test.dart +++ b/pkgs/oauth2/test/handle_access_token_response_test.dart @@ -15,17 +15,18 @@ final Uri tokenEndpoint = Uri.parse("https://example.com/token"); final DateTime startTime = new DateTime.now(); -oauth2.Credentials handle(http.Response response) => - handleAccessTokenResponse(response, tokenEndpoint, startTime, ["scope"], ' '); +oauth2.Credentials handle(http.Response response) => handleAccessTokenResponse( + response, tokenEndpoint, startTime, ["scope"], ' '); void main() { group('an error response', () { oauth2.Credentials handleError( - {String body: '{"error": "invalid_request"}', - int statusCode: 400, - Map headers: - const {"content-type": "application/json"}}) => - handle(new http.Response(body, statusCode, headers: headers)); + {String body: '{"error": "invalid_request"}', + int statusCode: 400, + Map headers: const { + "content-type": "application/json" + }}) => + handle(new http.Response(body, statusCode, headers: headers)); test('causes an AuthorizationException', () { expect(() => handleError(), throwsAuthorizationException); @@ -44,16 +45,17 @@ void main() { }); test('with a non-JSON content-type causes a FormatException', () { - expect(() => handleError(headers: { - 'content-type': 'text/plain' - }), throwsFormatException); + expect(() => handleError(headers: {'content-type': 'text/plain'}), + throwsFormatException); }); - test('with a JSON content-type and charset causes an ' + test( + 'with a JSON content-type and charset causes an ' 'AuthorizationException', () { - expect(() => handleError(headers: { - 'content-type': 'application/json; charset=UTF-8' - }), throwsAuthorizationException); + expect( + () => handleError( + headers: {'content-type': 'application/json; charset=UTF-8'}), + throwsAuthorizationException); }); test('with invalid JSON causes a FormatException', () { @@ -65,49 +67,59 @@ void main() { }); test('with a non-string error_description causes a FormatException', () { - expect(() => handleError(body: JSON.encode({ - "error": "invalid_request", - "error_description": 12 - })), throwsFormatException); + expect( + () => handleError( + body: JSON.encode( + {"error": "invalid_request", "error_description": 12})), + throwsFormatException); }); test('with a non-string error_uri causes a FormatException', () { - expect(() => handleError(body: JSON.encode({ - "error": "invalid_request", - "error_uri": 12 - })), throwsFormatException); + expect( + () => handleError( + body: JSON.encode({"error": "invalid_request", "error_uri": 12})), + throwsFormatException); }); test('with a string error_description causes a AuthorizationException', () { - expect(() => handleError(body: JSON.encode({ - "error": "invalid_request", - "error_description": "description" - })), throwsAuthorizationException); + expect( + () => handleError( + body: JSON.encode({ + "error": "invalid_request", + "error_description": "description" + })), + throwsAuthorizationException); }); test('with a string error_uri causes a AuthorizationException', () { - expect(() => handleError(body: JSON.encode({ - "error": "invalid_request", - "error_uri": "http://example.com/error" - })), throwsAuthorizationException); + expect( + () => handleError( + body: JSON.encode({ + "error": "invalid_request", + "error_uri": "http://example.com/error" + })), + throwsAuthorizationException); }); }); group('a success response', () { oauth2.Credentials handleSuccess( {String contentType: "application/json", - accessToken: 'access token', - tokenType: 'bearer', - expiresIn, - refreshToken, - scope}) { - return handle(new http.Response(JSON.encode({ - 'access_token': accessToken, - 'token_type': tokenType, - 'expires_in': expiresIn, - 'refresh_token': refreshToken, - 'scope': scope - }), 200, headers: {'content-type': contentType})); + accessToken: 'access token', + tokenType: 'bearer', + expiresIn, + refreshToken, + scope}) { + return handle(new http.Response( + JSON.encode({ + 'access_token': accessToken, + 'token_type': tokenType, + 'expires_in': expiresIn, + 'refresh_token': refreshToken, + 'scope': scope + }), + 200, + headers: {'content-type': contentType})); } test('returns the correct credentials', () { @@ -126,10 +138,11 @@ void main() { throwsFormatException); }); - test('with a JSON content-type and charset returns the correct ' + test( + 'with a JSON content-type and charset returns the correct ' 'credentials', () { - var credentials = handleSuccess( - contentType: 'application/json; charset=UTF-8'); + var credentials = + handleSuccess(contentType: 'application/json; charset=UTF-8'); expect(credentials.accessToken, equals('access token')); }); @@ -162,7 +175,8 @@ void main() { expect(() => handleSuccess(expiresIn: "whenever"), throwsFormatException); }); - test('with expires-in sets the expiration to ten seconds earlier than the ' + test( + 'with expires-in sets the expiration to ten seconds earlier than the ' 'server says', () { var credentials = handleSuccess(expiresIn: 100); expect(credentials.expiration.millisecondsSinceEpoch, @@ -188,13 +202,16 @@ void main() { }); test('with a custom scope delimiter sets the scopes', () { - var response = new http.Response(JSON.encode({ - 'access_token': 'access token', - 'token_type': 'bearer', - 'expires_in': null, - 'refresh_token': null, - 'scope': 'scope1,scope2' - }), 200, headers: {'content-type': 'application/json'}); + var response = new http.Response( + JSON.encode({ + 'access_token': 'access token', + 'token_type': 'bearer', + 'expires_in': null, + 'refresh_token': null, + 'scope': 'scope1,scope2' + }), + 200, + headers: {'content-type': 'application/json'}); var credentials = handleAccessTokenResponse( response, tokenEndpoint, startTime, ['scope'], ','); expect(credentials.scopes, equals(['scope1', 'scope2'])); diff --git a/pkgs/oauth2/test/utils.dart b/pkgs/oauth2/test/utils.dart index 5bdf78a07..d6b1d6273 100644 --- a/pkgs/oauth2/test/utils.dart +++ b/pkgs/oauth2/test/utils.dart @@ -14,13 +14,12 @@ class ExpectClient extends MockClient { final Queue _handlers; ExpectClient._(MockClientHandler fn) - : _handlers = new Queue(), - super(fn); + : _handlers = new Queue(), + super(fn); factory ExpectClient() { var client; - client = new ExpectClient._((request) => - client._handleRequest(request)); + client = new ExpectClient._((request) => client._handleRequest(request)); return client; } @@ -52,19 +51,16 @@ const Matcher throwsAuthorizationException = class _AuthorizationException extends TypeMatcher { const _AuthorizationException() : super("AuthorizationException"); - bool matches(item, Map matchState) => - item is oauth2.AuthorizationException; + bool matches(item, Map matchState) => item is oauth2.AuthorizationException; } /// A matcher for ExpirationExceptions. const isExpirationException = const _ExpirationException(); /// A matcher for functions that throw ExpirationException. -const Matcher throwsExpirationException = - const Throws(isExpirationException); +const Matcher throwsExpirationException = const Throws(isExpirationException); class _ExpirationException extends TypeMatcher { const _ExpirationException() : super("ExpirationException"); - bool matches(item, Map matchState) => - item is oauth2.ExpirationException; + bool matches(item, Map matchState) => item is oauth2.ExpirationException; } From 1f94cd9b1959cbddbcccabad5d7f8d7b470a6115 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Mon, 16 Oct 2017 14:20:49 -0700 Subject: [PATCH 078/159] Enable strong mode --- pkgs/oauth2/analysis_options.yaml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 pkgs/oauth2/analysis_options.yaml diff --git a/pkgs/oauth2/analysis_options.yaml b/pkgs/oauth2/analysis_options.yaml new file mode 100644 index 000000000..a10d4c5a0 --- /dev/null +++ b/pkgs/oauth2/analysis_options.yaml @@ -0,0 +1,2 @@ +analyzer: + strong-mode: true From a7455d2ce6b9fa6d333d308cef3046f6809d0d27 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Mon, 16 Oct 2017 16:41:13 -0700 Subject: [PATCH 079/159] Enable Travis CI --- pkgs/oauth2/.travis.yml | 25 +++++++++++++++++++++++++ pkgs/oauth2/codereview.settings | 3 --- 2 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 pkgs/oauth2/.travis.yml delete mode 100644 pkgs/oauth2/codereview.settings diff --git a/pkgs/oauth2/.travis.yml b/pkgs/oauth2/.travis.yml new file mode 100644 index 000000000..91b73d0d9 --- /dev/null +++ b/pkgs/oauth2/.travis.yml @@ -0,0 +1,25 @@ +language: dart + +dart: +- dev +- stable + +dart_task: +- test: --platform vm,firefox + +# Only run one instance of the formatter and the analyzer, rather than running +# them against each Dart version. +matrix: + include: + - dart: dev + dart_task: dartfmt + - dart: dev + dart_task: dartanalyzer + +# Only building master means that we don't run two builds for each pull request. +branches: + only: [master] + +cache: + directories: + - $HOME/.pub-cache diff --git a/pkgs/oauth2/codereview.settings b/pkgs/oauth2/codereview.settings deleted file mode 100644 index 0b7a5a8f9..000000000 --- a/pkgs/oauth2/codereview.settings +++ /dev/null @@ -1,3 +0,0 @@ -CODE_REVIEW_SERVER: http://codereview.chromium.org/ -VIEW_VC: https://github.com/dart-lang/oauth2/commit/ -CC_LIST: reviews@dartlang.org \ No newline at end of file From 9b63a175f619e24d72fc8410eabec4f6ae26f191 Mon Sep 17 00:00:00 2001 From: ovo-6 Date: Mon, 1 Jan 2018 23:06:30 +0100 Subject: [PATCH 080/159] Missing semicolon in code example (dart-lang/oauth2#24) --- pkgs/oauth2/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/oauth2/README.md b/pkgs/oauth2/README.md index ad3b0c01c..d3045a325 100644 --- a/pkgs/oauth2/README.md +++ b/pkgs/oauth2/README.md @@ -23,7 +23,7 @@ server-side application. ## Authorization Code Grant ```dart -import 'dart:io' +import 'dart:io'; import 'package:oauth2/oauth2.dart' as oauth2; // These URLs are endpoints that are provided by the authorization From c6e2e4f4a43d1e7d987398c2196af4f5f446df87 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Mon, 26 Mar 2018 19:03:51 -0400 Subject: [PATCH 081/159] Allow response parsing to be customized (dart-lang/oauth2#20) --- pkgs/oauth2/CHANGELOG.md | 7 + .../lib/src/authorization_code_grant.dart | 24 ++- pkgs/oauth2/lib/src/credentials.dart | 24 ++- .../lib/src/handle_access_token_response.dart | 166 +++++++++--------- pkgs/oauth2/lib/src/parameters.dart | 34 ++++ .../src/resource_owner_password_grant.dart | 18 +- pkgs/oauth2/pubspec.yaml | 2 +- .../handle_access_token_response_test.dart | 49 +++++- pkgs/oauth2/test/utils.dart | 5 +- 9 files changed, 227 insertions(+), 102 deletions(-) create mode 100644 pkgs/oauth2/lib/src/parameters.dart diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md index 2a72fc41f..4a26b867d 100644 --- a/pkgs/oauth2/CHANGELOG.md +++ b/pkgs/oauth2/CHANGELOG.md @@ -1,3 +1,10 @@ +# 1.2.0 + +* Add a `getParameter()` parameter to `new AuthorizationCodeGrant()`, `new + Credentials()`, and `resourceOwnerPasswordGrant()`. This controls how the + authorization server's response is parsed for servers that don't provide the + standard JSON response. + # 1.1.1 * `resourceOwnerPasswordGrant()` now properly uses its HTTP client for requests diff --git a/pkgs/oauth2/lib/src/authorization_code_grant.dart b/pkgs/oauth2/lib/src/authorization_code_grant.dart index faec8c961..bacb680be 100644 --- a/pkgs/oauth2/lib/src/authorization_code_grant.dart +++ b/pkgs/oauth2/lib/src/authorization_code_grant.dart @@ -5,10 +5,12 @@ import 'dart:async'; import 'package:http/http.dart' as http; +import 'package:http_parser/http_parser.dart'; import 'client.dart'; import 'authorization_exception.dart'; import 'handle_access_token_response.dart'; +import 'parameters.dart'; import 'utils.dart'; /// A class for obtaining credentials via an [authorization code grant][]. @@ -26,6 +28,9 @@ import 'utils.dart'; /// /// [authorization code grant]: http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-4.1 class AuthorizationCodeGrant { + /// The function used to parse parameters from a host's response. + final GetParameters _getParameters; + /// The client identifier for this client. /// /// The authorization server will issue each client a separate client @@ -105,15 +110,27 @@ class AuthorizationCodeGrant { /// The scope strings will be separated by the provided [delimiter]. This /// defaults to `" "`, the OAuth2 standard, but some APIs (such as Facebook's) /// use non-standard delimiters. + /// + /// By default, this follows the OAuth2 spec and requires the server's + /// responses to be in JSON format. However, some servers return non-standard + /// response formats, which can be parsed using the [getParameters] function. + /// + /// This function is passed the `Content-Type` header of the response as well + /// as its body as a UTF-8-decoded string. It should return a map in the same + /// format as the [standard JSON response][]. + /// + /// [standard JSON response]: https://tools.ietf.org/html/rfc6749#section-5.1 AuthorizationCodeGrant( this.identifier, this.authorizationEndpoint, this.tokenEndpoint, {this.secret, String delimiter, bool basicAuth: true, - http.Client httpClient}) + http.Client httpClient, + Map getParameters(MediaType contentType, String body)}) : _basicAuth = basicAuth, _httpClient = httpClient == null ? new http.Client() : httpClient, - _delimiter = delimiter ?? ' '; + _delimiter = delimiter ?? ' ', + _getParameters = getParameters ?? parseJsonParameters; /// Returns the URL to which the resource owner should be redirected to /// authorize this client. @@ -266,7 +283,8 @@ class AuthorizationCodeGrant { headers: headers, body: body); var credentials = handleAccessTokenResponse( - response, tokenEndpoint, startTime, _scopes, _delimiter); + response, tokenEndpoint, startTime, _scopes, _delimiter, + getParameters: _getParameters); return new Client(credentials, identifier: this.identifier, secret: this.secret, diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index 06a89d0b4..4bd22a152 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -7,8 +7,10 @@ import 'dart:collection'; import 'dart:convert'; import 'package:http/http.dart' as http; +import 'package:http_parser/http_parser.dart'; import 'handle_access_token_response.dart'; +import 'parameters.dart'; import 'utils.dart'; /// Credentials that prove that a client is allowed to access a resource on the @@ -57,6 +59,9 @@ class Credentials { /// expiration date. final DateTime expiration; + /// The function used to parse parameters from a host's response. + final GetParameters _getParameters; + /// Whether or not these credentials have expired. /// /// Note that it's possible the credentials will expire shortly after this is @@ -78,17 +83,29 @@ class Credentials { /// The scope strings will be separated by the provided [delimiter]. This /// defaults to `" "`, the OAuth2 standard, but some APIs (such as Facebook's) /// use non-standard delimiters. + /// + /// By default, this follows the OAuth2 spec and requires the server's + /// responses to be in JSON format. However, some servers return non-standard + /// response formats, which can be parsed using the [getParameters] function. + /// + /// This function is passed the `Content-Type` header of the response as well + /// as its body as a UTF-8-decoded string. It should return a map in the same + /// format as the [standard JSON response][]. + /// + /// [standard JSON response]: https://tools.ietf.org/html/rfc6749#section-5.1 Credentials(this.accessToken, {this.refreshToken, this.tokenEndpoint, Iterable scopes, this.expiration, - String delimiter}) + String delimiter, + Map getParameters(MediaType mediaType, String body)}) : scopes = new UnmodifiableListView( // Explicitly type-annotate the list literal to work around // sdk#24202. scopes == null ? [] : scopes.toList()), - _delimiter = delimiter ?? ' '; + _delimiter = delimiter ?? ' ', + _getParameters = getParameters ?? parseJsonParameters; /// Loads a set of credentials from a JSON-serialized form. /// @@ -208,7 +225,8 @@ class Credentials { var response = await httpClient.post(tokenEndpoint, headers: headers, body: body); var credentials = await handleAccessTokenResponse( - response, tokenEndpoint, startTime, scopes, _delimiter); + response, tokenEndpoint, startTime, scopes, _delimiter, + getParameters: _getParameters); // The authorization server may issue a new refresh token. If it doesn't, // we should re-use the one we already have. diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart index 7f03f1f3a..84fac6747 100644 --- a/pkgs/oauth2/lib/src/handle_access_token_response.dart +++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart @@ -2,14 +2,12 @@ // 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. -import 'dart:convert'; - -import 'package:collection/collection.dart'; import 'package:http/http.dart' as http; import 'package:http_parser/http_parser.dart'; import 'credentials.dart'; import 'authorization_exception.dart'; +import 'parameters.dart'; /// The amount of time to add as a "grace period" for credential expiration. /// @@ -23,80 +21,86 @@ const _expirationGrace = const Duration(seconds: 10); /// This response format is common across several different components of the /// OAuth2 flow. /// -/// The scope strings will be separated by the provided [delimiter]. +/// By default, this follows the OAuth2 spec and requires the server's responses +/// to be in JSON format. However, some servers return non-standard response +/// formats, which can be parsed using the [getParameters] function. +/// +/// This function is passed the `Content-Type` header of the response as well as +/// its body as a UTF-8-decoded string. It should return a map in the same +/// format as the [standard JSON response][]. +/// +/// [standard JSON response]: https://tools.ietf.org/html/rfc6749#section-5.1 Credentials handleAccessTokenResponse(http.Response response, Uri tokenEndpoint, - DateTime startTime, List scopes, String delimiter) { - if (response.statusCode != 200) _handleErrorResponse(response, tokenEndpoint); - - validate(condition, message) => - _validate(response, tokenEndpoint, condition, message); - - var contentTypeString = response.headers['content-type']; - var contentType = - contentTypeString == null ? null : new MediaType.parse(contentTypeString); - - // The spec requires a content-type of application/json, but some endpoints - // (e.g. Dropbox) serve it as text/javascript instead. - validate( - contentType != null && - (contentType.mimeType == "application/json" || - contentType.mimeType == "text/javascript"), - 'content-type was "$contentType", expected "application/json"'); + DateTime startTime, List scopes, String delimiter, + {Map getParameters(MediaType contentType, String body)}) { + getParameters ??= parseJsonParameters; - Map parameters; try { - var untypedParameters = JSON.decode(response.body); - validate(untypedParameters is Map, - 'parameters must be a map, was "$parameters"'); - parameters = DelegatingMap.typed(untypedParameters); - } on FormatException { - validate(false, 'invalid JSON'); - } - - for (var requiredParameter in ['access_token', 'token_type']) { - validate(parameters.containsKey(requiredParameter), - 'did not contain required parameter "$requiredParameter"'); - validate( - parameters[requiredParameter] is String, - 'required parameter "$requiredParameter" was not a string, was ' - '"${parameters[requiredParameter]}"'); - } + if (response.statusCode != 200) { + _handleErrorResponse(response, tokenEndpoint, getParameters); + } - // TODO(nweiz): support the "mac" token type - // (http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01) - validate(parameters['token_type'].toLowerCase() == 'bearer', - '"$tokenEndpoint": unknown token type "${parameters['token_type']}"'); + var contentTypeString = response.headers['content-type']; + if (contentTypeString == null) { + throw new FormatException('Missing Content-Type string.'); + } - var expiresIn = parameters['expires_in']; - validate(expiresIn == null || expiresIn is int, - 'parameter "expires_in" was not an int, was "$expiresIn"'); + var parameters = + getParameters(new MediaType.parse(contentTypeString), response.body); + + for (var requiredParameter in ['access_token', 'token_type']) { + if (!parameters.containsKey(requiredParameter)) { + throw new FormatException( + 'did not contain required parameter "$requiredParameter"'); + } else if (parameters[requiredParameter] is! String) { + throw new FormatException( + 'required parameter "$requiredParameter" was not a string, was ' + '"${parameters[requiredParameter]}"'); + } + } - for (var name in ['refresh_token', 'scope']) { - var value = parameters[name]; - validate(value == null || value is String, - 'parameter "$name" was not a string, was "$value"'); - } + // TODO(nweiz): support the "mac" token type + // (http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01) + if (parameters['token_type'].toLowerCase() != 'bearer') { + throw new FormatException( + '"$tokenEndpoint": unknown token type "${parameters['token_type']}"'); + } - var scope = parameters['scope'] as String; - if (scope != null) scopes = scope.split(delimiter); + var expiresIn = parameters['expires_in']; + if (expiresIn != null && expiresIn is! int) { + throw new FormatException( + 'parameter "expires_in" was not an int, was "$expiresIn"'); + } - var expiration = expiresIn == null - ? null - : startTime.add(new Duration(seconds: expiresIn) - _expirationGrace); + for (var name in ['refresh_token', 'scope']) { + var value = parameters[name]; + if (value != null && value is! String) + throw new FormatException( + 'parameter "$name" was not a string, was "$value"'); + } - return new Credentials(parameters['access_token'], - refreshToken: parameters['refresh_token'], - tokenEndpoint: tokenEndpoint, - scopes: scopes, - expiration: expiration); + var scope = parameters['scope'] as String; + if (scope != null) scopes = scope.split(delimiter); + + var expiration = expiresIn == null + ? null + : startTime.add(new Duration(seconds: expiresIn) - _expirationGrace); + + return new Credentials(parameters['access_token'], + refreshToken: parameters['refresh_token'], + tokenEndpoint: tokenEndpoint, + scopes: scopes, + expiration: expiration); + } on FormatException catch (e) { + throw new FormatException('Invalid OAuth response for "$tokenEndpoint": ' + '${e.message}.\n\n${response.body}'); + } } /// Throws the appropriate exception for an error response from the /// authorization server. -void _handleErrorResponse(http.Response response, Uri tokenEndpoint) { - validate(condition, message) => - _validate(response, tokenEndpoint, condition, message); - +void _handleErrorResponse( + http.Response response, Uri tokenEndpoint, GetParameters getParameters) { // OAuth2 mandates a 400 or 401 response code for access token error // responses. If it's not a 400 reponse, the server is either broken or // off-spec. @@ -113,27 +117,22 @@ void _handleErrorResponse(http.Response response, Uri tokenEndpoint) { var contentType = contentTypeString == null ? null : new MediaType.parse(contentTypeString); - validate(contentType != null && contentType.mimeType == "application/json", - 'content-type was "$contentType", expected "application/json"'); + var parameters = getParameters(contentType, response.body); - var parameters; - try { - parameters = JSON.decode(response.body); - } on FormatException { - validate(false, 'invalid JSON'); + if (!parameters.containsKey('error')) { + throw new FormatException('did not contain required parameter "error"'); + } else if (parameters['error'] is! String) { + throw new FormatException( + 'required parameter "error" was not a string, was ' + '"${parameters["error"]}"'); } - validate(parameters.containsKey('error'), - 'did not contain required parameter "error"'); - validate( - parameters["error"] is String, - 'required parameter "error" was not a string, was ' - '"${parameters["error"]}"'); - for (var name in ['error_description', 'error_uri']) { var value = parameters[name]; - validate(value == null || value is String, - 'parameter "$name" was not a string, was "$value"'); + + if (value != null && value is! String) + throw new FormatException( + 'parameter "$name" was not a string, was "$value"'); } var description = parameters['error_description']; @@ -141,10 +140,3 @@ void _handleErrorResponse(http.Response response, Uri tokenEndpoint) { var uri = uriString == null ? null : Uri.parse(uriString); throw new AuthorizationException(parameters['error'], description, uri); } - -void _validate( - http.Response response, Uri tokenEndpoint, bool condition, String message) { - if (condition) return; - throw new FormatException('Invalid OAuth response for "$tokenEndpoint": ' - '$message.\n\n${response.body}'); -} diff --git a/pkgs/oauth2/lib/src/parameters.dart b/pkgs/oauth2/lib/src/parameters.dart new file mode 100644 index 000000000..ccbf23eaa --- /dev/null +++ b/pkgs/oauth2/lib/src/parameters.dart @@ -0,0 +1,34 @@ +// Copyright (c) 2018, 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. + +import 'dart:convert'; + +import 'package:collection/collection.dart'; +import 'package:http_parser/http_parser.dart'; + +/// The type of a callback that parses parameters from an HTTP response. +typedef Map GetParameters(MediaType contentType, String body); + +/// Parses parameters from a response with a JSON body, as per the [OAuth2 +/// spec][]. +/// +/// [OAuth2 spec]: https://tools.ietf.org/html/rfc6749#section-5.1 +Map parseJsonParameters(MediaType contentType, String body) { + // The spec requires a content-type of application/json, but some endpoints + // (e.g. Dropbox) serve it as text/javascript instead. + if (contentType == null || + (contentType.mimeType != "application/json" && + contentType.mimeType != "text/javascript")) { + throw new FormatException( + 'Content-Type was "$contentType", expected "application/json"'); + } + + var untypedParameters = JSON.decode(body); + if (untypedParameters is! Map) { + throw new FormatException( + 'Parameters must be a map, was "$untypedParameters"'); + } + + return DelegatingMap.typed(untypedParameters); +} diff --git a/pkgs/oauth2/lib/src/resource_owner_password_grant.dart b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart index 90c7f6cf5..ebd1d124b 100644 --- a/pkgs/oauth2/lib/src/resource_owner_password_grant.dart +++ b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'package:http/http.dart' as http; +import 'package:http_parser/http_parser.dart'; import 'client.dart'; import 'handle_access_token_response.dart'; @@ -32,6 +33,16 @@ import 'utils.dart'; /// The scope strings will be separated by the provided [delimiter]. This /// defaults to `" "`, the OAuth2 standard, but some APIs (such as Facebook's) /// use non-standard delimiters. +/// +/// By default, this follows the OAuth2 spec and requires the server's responses +/// to be in JSON format. However, some servers return non-standard response +/// formats, which can be parsed using the [getParameters] function. +/// +/// This function is passed the `Content-Type` header of the response as well as +/// its body as a UTF-8-decoded string. It should return a map in the same +/// format as the [standard JSON response][]. +/// +/// [standard JSON response]: https://tools.ietf.org/html/rfc6749#section-5.1 Future resourceOwnerPasswordGrant( Uri authorizationEndpoint, String username, String password, {String identifier, @@ -39,7 +50,9 @@ Future resourceOwnerPasswordGrant( Iterable scopes, bool basicAuth: true, http.Client httpClient, - String delimiter}) async { + String delimiter, + Map getParameters( + MediaType contentType, String body)}) async { delimiter ??= ' '; var startTime = new DateTime.now(); @@ -67,7 +80,8 @@ Future resourceOwnerPasswordGrant( headers: headers, body: body); var credentials = await handleAccessTokenResponse( - response, authorizationEndpoint, startTime, scopes, delimiter); + response, authorizationEndpoint, startTime, scopes, delimiter, + getParameters: getParameters); return new Client(credentials, identifier: identifier, secret: secret, httpClient: httpClient); } diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index 44e41faa2..8c4960344 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,5 @@ name: oauth2 -version: 1.1.1-dev +version: 1.2.0 author: Dart Team homepage: https://github.com/dart-lang/oauth2 description: > diff --git a/pkgs/oauth2/test/handle_access_token_response_test.dart b/pkgs/oauth2/test/handle_access_token_response_test.dart index 6edfbcb31..e32e13d44 100644 --- a/pkgs/oauth2/test/handle_access_token_response_test.dart +++ b/pkgs/oauth2/test/handle_access_token_response_test.dart @@ -5,9 +5,12 @@ import 'dart:convert'; import 'package:http/http.dart' as http; +import 'package:http_parser/http_parser.dart'; +import 'package:test/test.dart'; + import 'package:oauth2/oauth2.dart' as oauth2; import 'package:oauth2/src/handle_access_token_response.dart'; -import 'package:test/test.dart'; +import 'package:oauth2/src/parameters.dart'; import 'utils.dart'; @@ -15,8 +18,11 @@ final Uri tokenEndpoint = Uri.parse("https://example.com/token"); final DateTime startTime = new DateTime.now(); -oauth2.Credentials handle(http.Response response) => handleAccessTokenResponse( - response, tokenEndpoint, startTime, ["scope"], ' '); +oauth2.Credentials handle(http.Response response, + {GetParameters getParameters}) => + handleAccessTokenResponse( + response, tokenEndpoint, startTime, ["scope"], ' ', + getParameters: getParameters); void main() { group('an error response', () { @@ -49,6 +55,12 @@ void main() { throwsFormatException); }); + test('with a non-JSON, non-plain content-type causes a FormatException', + () { + expect(() => handleError(headers: {'content-type': 'image/png'}), + throwsFormatException); + }); + test( 'with a JSON content-type and charset causes an ' 'AuthorizationException', () { @@ -151,6 +163,37 @@ void main() { expect(credentials.accessToken, equals('access token')); }); + test('with custom getParameters() returns the correct credentials', () { + var body = '_' + + JSON.encode({'token_type': 'bearer', 'access_token': 'access token'}); + var credentials = handle( + new http.Response(body, 200, headers: {'content-type': 'text/plain'}), + getParameters: (contentType, body) => JSON.decode(body.substring(1))); + expect(credentials.accessToken, equals('access token')); + expect(credentials.tokenEndpoint.toString(), + equals(tokenEndpoint.toString())); + }); + + test('throws a FormatException if custom getParameters rejects response', + () { + var response = new http.Response( + JSON.encode({ + 'access_token': 'access token', + 'token_type': 'bearer', + 'expires_in': 24, + 'refresh_token': 'refresh token', + 'scope': 'scope', + }), + 200, + headers: {'content-type': 'foo/bar'}); + + expect( + () => handle(response, + getParameters: (contentType, body) => throw new FormatException( + 'unsupported content-type: $contentType')), + throwsFormatException); + }); + test('with a null access token throws a FormatException', () { expect(() => handleSuccess(accessToken: null), throwsFormatException); }); diff --git a/pkgs/oauth2/test/utils.dart b/pkgs/oauth2/test/utils.dart index d6b1d6273..92835eae8 100644 --- a/pkgs/oauth2/test/utils.dart +++ b/pkgs/oauth2/test/utils.dart @@ -46,8 +46,7 @@ class ExpectClient extends MockClient { const isAuthorizationException = const _AuthorizationException(); /// A matcher for functions that throw AuthorizationException. -const Matcher throwsAuthorizationException = - const Throws(isAuthorizationException); +final Matcher throwsAuthorizationException = throwsA(isAuthorizationException); class _AuthorizationException extends TypeMatcher { const _AuthorizationException() : super("AuthorizationException"); @@ -58,7 +57,7 @@ class _AuthorizationException extends TypeMatcher { const isExpirationException = const _ExpirationException(); /// A matcher for functions that throw ExpirationException. -const Matcher throwsExpirationException = const Throws(isExpirationException); +final Matcher throwsExpirationException = throwsA(isExpirationException); class _ExpirationException extends TypeMatcher { const _ExpirationException() : super("ExpirationException"); From 9960120d3ba964cc86ee867bef74eae0c2ef346f Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 2 May 2018 14:04:08 -0700 Subject: [PATCH 082/159] Fix Dart 2 runtime failures in tests (dart-lang/oauth2#28) --- pkgs/oauth2/.gitignore | 1 + pkgs/oauth2/test/authorization_code_grant_test.dart | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/.gitignore b/pkgs/oauth2/.gitignore index 7dbf0350d..bbe1007d7 100644 --- a/pkgs/oauth2/.gitignore +++ b/pkgs/oauth2/.gitignore @@ -1,6 +1,7 @@ # Don’t commit the following directories created by pub. .buildlog .pub/ +.dart_tool/ build/ packages .packages diff --git a/pkgs/oauth2/test/authorization_code_grant_test.dart b/pkgs/oauth2/test/authorization_code_grant_test.dart index 9cb1e733e..137755c6a 100644 --- a/pkgs/oauth2/test/authorization_code_grant_test.dart +++ b/pkgs/oauth2/test/authorization_code_grant_test.dart @@ -14,8 +14,8 @@ import 'utils.dart'; final redirectUrl = Uri.parse('http://example.com/redirect'); void main() { - var client; - var grant; + ExpectClient client; + oauth2.AuthorizationCodeGrant grant; setUp(() { client = new ExpectClient(); grant = new oauth2.AuthorizationCodeGrant( From 95094a63a52f5bebcabbe9ddb3c5598b5751ad67 Mon Sep 17 00:00:00 2001 From: "Lasse R.H. Nielsen" Date: Tue, 22 May 2018 15:07:06 +0200 Subject: [PATCH 083/159] Remove upper case constants (dart-lang/oauth2#26) * Remove usage of upper-case constants. * update SDK version * remove stable from Travis config --- pkgs/oauth2/.travis.yml | 2 -- pkgs/oauth2/CHANGELOG.md | 4 ++++ pkgs/oauth2/lib/src/credentials.dart | 4 ++-- pkgs/oauth2/lib/src/parameters.dart | 2 +- pkgs/oauth2/lib/src/utils.dart | 2 +- pkgs/oauth2/pubspec.yaml | 4 ++-- .../test/authorization_code_grant_test.dart | 8 ++++---- pkgs/oauth2/test/client_test.dart | 4 ++-- pkgs/oauth2/test/credentials_test.dart | 14 +++++++------- .../handle_access_token_response_test.dart | 18 +++++++++--------- .../resource_owner_password_grant_test.dart | 2 +- 11 files changed, 33 insertions(+), 31 deletions(-) diff --git a/pkgs/oauth2/.travis.yml b/pkgs/oauth2/.travis.yml index 91b73d0d9..c15fd3318 100644 --- a/pkgs/oauth2/.travis.yml +++ b/pkgs/oauth2/.travis.yml @@ -2,8 +2,6 @@ language: dart dart: - dev -- stable - dart_task: - test: --platform vm,firefox diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md index 4a26b867d..ad6d77713 100644 --- a/pkgs/oauth2/CHANGELOG.md +++ b/pkgs/oauth2/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.2.1 + +* Updated SDK version to 2.0.0-dev.17.0 + # 1.2.0 * Add a `getParameter()` parameter to `new AuthorizationCodeGrant()`, `new diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index 4bd22a152..f40b26db2 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -119,7 +119,7 @@ class Credentials { var parsed; try { - parsed = JSON.decode(json); + parsed = jsonDecode(json); } on FormatException { validate(false, 'invalid JSON'); } @@ -164,7 +164,7 @@ class Credentials { /// /// Nothing is guaranteed about the output except that it's valid JSON and /// compatible with [Credentials.toJson]. - String toJson() => JSON.encode({ + String toJson() => jsonEncode({ 'accessToken': accessToken, 'refreshToken': refreshToken, 'tokenEndpoint': diff --git a/pkgs/oauth2/lib/src/parameters.dart b/pkgs/oauth2/lib/src/parameters.dart index ccbf23eaa..61a9659ac 100644 --- a/pkgs/oauth2/lib/src/parameters.dart +++ b/pkgs/oauth2/lib/src/parameters.dart @@ -24,7 +24,7 @@ Map parseJsonParameters(MediaType contentType, String body) { 'Content-Type was "$contentType", expected "application/json"'); } - var untypedParameters = JSON.decode(body); + var untypedParameters = jsonDecode(body); if (untypedParameters is! Map) { throw new FormatException( 'Parameters must be a map, was "$untypedParameters"'); diff --git a/pkgs/oauth2/lib/src/utils.dart b/pkgs/oauth2/lib/src/utils.dart index 150fa174a..6c3453feb 100644 --- a/pkgs/oauth2/lib/src/utils.dart +++ b/pkgs/oauth2/lib/src/utils.dart @@ -11,5 +11,5 @@ Uri addQueryParameters(Uri url, Map parameters) => url.replace( String basicAuthHeader(String identifier, String secret) { var userPass = Uri.encodeFull(identifier) + ":" + Uri.encodeFull(secret); - return "Basic " + BASE64.encode(ASCII.encode(userPass)); + return "Basic " + base64Encode(ascii.encode(userPass)); } diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index 8c4960344..1b8473275 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,5 @@ name: oauth2 -version: 1.2.0 +version: 1.2.1 author: Dart Team homepage: https://github.com/dart-lang/oauth2 description: > @@ -7,7 +7,7 @@ description: > behalf of a user, and making authorized HTTP requests with the user's OAuth2 credentials. environment: - sdk: '>=1.13.0 <2.0.0' + sdk: '>=2.0.0-dev.17.0 <2.0.0' dependencies: collection: '^1.5.0' http: '>=0.11.0 <0.12.0' diff --git a/pkgs/oauth2/test/authorization_code_grant_test.dart b/pkgs/oauth2/test/authorization_code_grant_test.dart index 137755c6a..ec6e71bff 100644 --- a/pkgs/oauth2/test/authorization_code_grant_test.dart +++ b/pkgs/oauth2/test/authorization_code_grant_test.dart @@ -157,7 +157,7 @@ void main() { containsPair("Authorization", "Basic aWRlbnRpZmllcjpzZWNyZXQ=")); return new Future.value(new http.Response( - JSON.encode({ + jsonEncode({ 'access_token': 'access token', 'token_type': 'bearer', }), @@ -199,7 +199,7 @@ void main() { containsPair("Authorization", "Basic aWRlbnRpZmllcjpzZWNyZXQ=")); return new Future.value(new http.Response( - JSON.encode({ + jsonEncode({ 'access_token': 'access token', 'token_type': 'bearer', }), @@ -244,7 +244,7 @@ void main() { })); return new Future.value(new http.Response( - JSON.encode({ + jsonEncode({ 'access_token': 'access token', 'token_type': 'bearer', }), @@ -274,7 +274,7 @@ void main() { })); return new Future.value(new http.Response( - JSON.encode({ + jsonEncode({ 'access_token': 'access token', 'token_type': 'bearer', }), diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart index 7f5980370..c7b4bbe5d 100644 --- a/pkgs/oauth2/test/client_test.dart +++ b/pkgs/oauth2/test/client_test.dart @@ -46,7 +46,7 @@ void main() { expect(request.method, equals('POST')); expect(request.url.toString(), equals(tokenEndpoint.toString())); return new Future.value(new http.Response( - JSON.encode( + jsonEncode( {'access_token': 'new access token', 'token_type': 'bearer'}), 200, headers: {'content-type': 'application/json'})); @@ -93,7 +93,7 @@ void main() { expect(request.method, equals('POST')); expect(request.url.toString(), equals(tokenEndpoint.toString())); return new Future.value(new http.Response( - JSON.encode( + jsonEncode( {'access_token': 'new access token', 'token_type': 'bearer'}), 200, headers: {'content-type': 'application/json'})); diff --git a/pkgs/oauth2/test/credentials_test.dart b/pkgs/oauth2/test/credentials_test.dart index fd77efac0..039a06094 100644 --- a/pkgs/oauth2/test/credentials_test.dart +++ b/pkgs/oauth2/test/credentials_test.dart @@ -81,7 +81,7 @@ void main() { "Basic aWQlQzMlQUJudCVDNCVBQmZpZXI6cyVDMyVBQmNyZXQ=")); return new Future.value(new http.Response( - JSON.encode({ + jsonEncode({ 'access_token': 'new access token', 'token_type': 'bearer', 'refresh_token': 'new refresh token' @@ -105,7 +105,7 @@ void main() { httpClient.expectRequest((http.Request request) { expect(request.bodyFields['scope'], equals('scope1,scope2')); return new Future.value(new http.Response( - JSON.encode({ + jsonEncode({ 'access_token': 'new access token', 'token_type': 'bearer', 'refresh_token': 'new refresh token' @@ -137,7 +137,7 @@ void main() { })); return new Future.value(new http.Response( - JSON.encode({ + jsonEncode({ 'access_token': 'new access token', 'token_type': 'bearer', 'refresh_token': 'new refresh token' @@ -171,7 +171,7 @@ void main() { })); return new Future.value(new http.Response( - JSON.encode({ + jsonEncode({ 'access_token': 'new access token', 'token_type': 'bearer', 'refresh_token': 'new refresh token' @@ -205,7 +205,7 @@ void main() { "Basic aWQlQzMlQUJudCVDNCVBQmZpZXI6cyVDMyVBQmNyZXQ=")); return new Future.value(new http.Response( - JSON.encode( + jsonEncode( {'access_token': 'new access token', 'token_type': 'bearer'}), 200, headers: {'content-type': 'application/json'})); @@ -238,7 +238,7 @@ void main() { })); return new Future.value(new http.Response( - JSON.encode({ + jsonEncode({ 'access_token': 'new access token', 'token_type': 'bearer', 'refresh_token': 'new refresh token' @@ -258,7 +258,7 @@ void main() { group("fromJson", () { oauth2.Credentials fromMap(Map map) => - new oauth2.Credentials.fromJson(JSON.encode(map)); + new oauth2.Credentials.fromJson(jsonEncode(map)); test("should load the same credentials from toJson", () { // Round the expiration down to milliseconds since epoch, since that's diff --git a/pkgs/oauth2/test/handle_access_token_response_test.dart b/pkgs/oauth2/test/handle_access_token_response_test.dart index e32e13d44..c54e54d57 100644 --- a/pkgs/oauth2/test/handle_access_token_response_test.dart +++ b/pkgs/oauth2/test/handle_access_token_response_test.dart @@ -81,7 +81,7 @@ void main() { test('with a non-string error_description causes a FormatException', () { expect( () => handleError( - body: JSON.encode( + body: jsonEncode( {"error": "invalid_request", "error_description": 12})), throwsFormatException); }); @@ -89,14 +89,14 @@ void main() { test('with a non-string error_uri causes a FormatException', () { expect( () => handleError( - body: JSON.encode({"error": "invalid_request", "error_uri": 12})), + body: jsonEncode({"error": "invalid_request", "error_uri": 12})), throwsFormatException); }); test('with a string error_description causes a AuthorizationException', () { expect( () => handleError( - body: JSON.encode({ + body: jsonEncode({ "error": "invalid_request", "error_description": "description" })), @@ -106,7 +106,7 @@ void main() { test('with a string error_uri causes a AuthorizationException', () { expect( () => handleError( - body: JSON.encode({ + body: jsonEncode({ "error": "invalid_request", "error_uri": "http://example.com/error" })), @@ -123,7 +123,7 @@ void main() { refreshToken, scope}) { return handle(new http.Response( - JSON.encode({ + jsonEncode({ 'access_token': accessToken, 'token_type': tokenType, 'expires_in': expiresIn, @@ -165,10 +165,10 @@ void main() { test('with custom getParameters() returns the correct credentials', () { var body = '_' + - JSON.encode({'token_type': 'bearer', 'access_token': 'access token'}); + jsonEncode({'token_type': 'bearer', 'access_token': 'access token'}); var credentials = handle( new http.Response(body, 200, headers: {'content-type': 'text/plain'}), - getParameters: (contentType, body) => JSON.decode(body.substring(1))); + getParameters: (contentType, body) => jsonDecode(body.substring(1))); expect(credentials.accessToken, equals('access token')); expect(credentials.tokenEndpoint.toString(), equals(tokenEndpoint.toString())); @@ -177,7 +177,7 @@ void main() { test('throws a FormatException if custom getParameters rejects response', () { var response = new http.Response( - JSON.encode({ + jsonEncode({ 'access_token': 'access token', 'token_type': 'bearer', 'expires_in': 24, @@ -246,7 +246,7 @@ void main() { test('with a custom scope delimiter sets the scopes', () { var response = new http.Response( - JSON.encode({ + jsonEncode({ 'access_token': 'access token', 'token_type': 'bearer', 'expires_in': null, diff --git a/pkgs/oauth2/test/resource_owner_password_grant_test.dart b/pkgs/oauth2/test/resource_owner_password_grant_test.dart index c8274f0e4..04d1537d9 100644 --- a/pkgs/oauth2/test/resource_owner_password_grant_test.dart +++ b/pkgs/oauth2/test/resource_owner_password_grant_test.dart @@ -12,7 +12,7 @@ import 'package:test/test.dart'; import 'utils.dart'; -final success = JSON.encode({ +final success = jsonEncode({ "access_token": "2YotnFZFEjr1zCsicMWpAA", "token_type": "bearer", "expires_in": 3600, From 3abf1737c330a22c6d0e882aafc5f682d022f8dc Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Tue, 10 Jul 2018 09:17:03 -0700 Subject: [PATCH 084/159] dartfmt --- pkgs/oauth2/lib/src/client.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart index 7d634e540..865b946ad 100644 --- a/pkgs/oauth2/lib/src/client.dart +++ b/pkgs/oauth2/lib/src/client.dart @@ -113,8 +113,8 @@ class Client extends http.BaseClient { var challenges; try { - challenges = AuthenticationChallenge - .parseHeader(response.headers['www-authenticate']); + challenges = AuthenticationChallenge.parseHeader( + response.headers['www-authenticate']); } on FormatException { return response; } From 1960ebf818302df77b2fab81d63fc2fc6763cfd6 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Tue, 7 Aug 2018 14:13:45 -0700 Subject: [PATCH 085/159] Allow the stable 2.0 SDK (dart-lang/oauth2#35) Update SDK and package:test constraints. --- pkgs/oauth2/CHANGELOG.md | 4 ++++ pkgs/oauth2/pubspec.yaml | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md index ad6d77713..68d89dae2 100644 --- a/pkgs/oauth2/CHANGELOG.md +++ b/pkgs/oauth2/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.2.2 + +* Allow the stable 2.0 SDK. + # 1.2.1 * Updated SDK version to 2.0.0-dev.17.0 diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index 1b8473275..dd721a69a 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,5 @@ name: oauth2 -version: 1.2.1 +version: 1.2.2 author: Dart Team homepage: https://github.com/dart-lang/oauth2 description: > @@ -7,10 +7,10 @@ description: > behalf of a user, and making authorized HTTP requests with the user's OAuth2 credentials. environment: - sdk: '>=2.0.0-dev.17.0 <2.0.0' + sdk: '>=2.0.0-dev.17.0 <3.0.0' dependencies: collection: '^1.5.0' http: '>=0.11.0 <0.12.0' http_parser: '>=1.0.0 <4.0.0' dev_dependencies: - test: '>=0.12.0 <0.13.0' + test: ^1.0.0 From 6e365bc6227a85c6a7431a5e88a82a501315b73f Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Fri, 26 Oct 2018 10:38:29 -0700 Subject: [PATCH 086/159] Eliminate deprecated usages, removed collection dependency --- pkgs/oauth2/lib/src/parameters.dart | 9 ++++---- pkgs/oauth2/pubspec.yaml | 1 - pkgs/oauth2/test/client_test.dart | 4 ++-- .../handle_access_token_response_test.dart | 1 - pkgs/oauth2/test/utils.dart | 22 ++++--------------- 5 files changed, 10 insertions(+), 27 deletions(-) diff --git a/pkgs/oauth2/lib/src/parameters.dart b/pkgs/oauth2/lib/src/parameters.dart index 61a9659ac..09ee5b77d 100644 --- a/pkgs/oauth2/lib/src/parameters.dart +++ b/pkgs/oauth2/lib/src/parameters.dart @@ -4,7 +4,6 @@ import 'dart:convert'; -import 'package:collection/collection.dart'; import 'package:http_parser/http_parser.dart'; /// The type of a callback that parses parameters from an HTTP response. @@ -25,10 +24,10 @@ Map parseJsonParameters(MediaType contentType, String body) { } var untypedParameters = jsonDecode(body); - if (untypedParameters is! Map) { - throw new FormatException( - 'Parameters must be a map, was "$untypedParameters"'); + if (untypedParameters is Map) { + return untypedParameters; } - return DelegatingMap.typed(untypedParameters); + throw new FormatException( + 'Parameters must be a map, was "$untypedParameters"'); } diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index dd721a69a..f39e17576 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -9,7 +9,6 @@ description: > environment: sdk: '>=2.0.0-dev.17.0 <3.0.0' dependencies: - collection: '^1.5.0' http: '>=0.11.0 <0.12.0' http_parser: '>=1.0.0 <4.0.0' dev_dependencies: diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart index c7b4bbe5d..ec52589dc 100644 --- a/pkgs/oauth2/test/client_test.dart +++ b/pkgs/oauth2/test/client_test.dart @@ -28,7 +28,7 @@ void main() { identifier: 'identifier', secret: 'secret', httpClient: httpClient); expect(client.get(requestUri), - throwsA(new isInstanceOf())); + throwsA(const TypeMatcher())); }); test( @@ -130,7 +130,7 @@ void main() { }); expect(client.read(requestUri), - throwsA(new isInstanceOf())); + throwsA(const TypeMatcher())); }); test('passes through a 401 response without www-authenticate', () async { diff --git a/pkgs/oauth2/test/handle_access_token_response_test.dart b/pkgs/oauth2/test/handle_access_token_response_test.dart index c54e54d57..e4008fbaf 100644 --- a/pkgs/oauth2/test/handle_access_token_response_test.dart +++ b/pkgs/oauth2/test/handle_access_token_response_test.dart @@ -5,7 +5,6 @@ import 'dart:convert'; import 'package:http/http.dart' as http; -import 'package:http_parser/http_parser.dart'; import 'package:test/test.dart'; import 'package:oauth2/oauth2.dart' as oauth2; diff --git a/pkgs/oauth2/test/utils.dart b/pkgs/oauth2/test/utils.dart index 92835eae8..64d1eb8fd 100644 --- a/pkgs/oauth2/test/utils.dart +++ b/pkgs/oauth2/test/utils.dart @@ -42,24 +42,10 @@ class ExpectClient extends MockClient { } } -/// A matcher for AuthorizationExceptions. -const isAuthorizationException = const _AuthorizationException(); - /// A matcher for functions that throw AuthorizationException. -final Matcher throwsAuthorizationException = throwsA(isAuthorizationException); - -class _AuthorizationException extends TypeMatcher { - const _AuthorizationException() : super("AuthorizationException"); - bool matches(item, Map matchState) => item is oauth2.AuthorizationException; -} - -/// A matcher for ExpirationExceptions. -const isExpirationException = const _ExpirationException(); +final Matcher throwsAuthorizationException = + throwsA(const TypeMatcher()); /// A matcher for functions that throw ExpirationException. -final Matcher throwsExpirationException = throwsA(isExpirationException); - -class _ExpirationException extends TypeMatcher { - const _ExpirationException() : super("ExpirationException"); - bool matches(item, Map matchState) => item is oauth2.ExpirationException; -} +final Matcher throwsExpirationException = + throwsA(const TypeMatcher()); From bb9ae0e98ec4c8ad7f520f140891da7b7bd2096a Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Fri, 26 Oct 2018 10:40:06 -0700 Subject: [PATCH 087/159] enable pedantic lints --- pkgs/oauth2/analysis_options.yaml | 3 +-- pkgs/oauth2/lib/src/authorization_code_grant.dart | 4 ++-- pkgs/oauth2/lib/src/client.dart | 2 +- pkgs/oauth2/lib/src/credentials.dart | 4 ++-- .../oauth2/lib/src/handle_access_token_response.dart | 2 +- .../lib/src/resource_owner_password_grant.dart | 5 +++-- pkgs/oauth2/pubspec.yaml | 1 + .../test/handle_access_token_response_test.dart | 12 ++++++------ 8 files changed, 17 insertions(+), 16 deletions(-) diff --git a/pkgs/oauth2/analysis_options.yaml b/pkgs/oauth2/analysis_options.yaml index a10d4c5a0..108d1058a 100644 --- a/pkgs/oauth2/analysis_options.yaml +++ b/pkgs/oauth2/analysis_options.yaml @@ -1,2 +1 @@ -analyzer: - strong-mode: true +include: package:pedantic/analysis_options.yaml diff --git a/pkgs/oauth2/lib/src/authorization_code_grant.dart b/pkgs/oauth2/lib/src/authorization_code_grant.dart index bacb680be..6b2829006 100644 --- a/pkgs/oauth2/lib/src/authorization_code_grant.dart +++ b/pkgs/oauth2/lib/src/authorization_code_grant.dart @@ -124,7 +124,7 @@ class AuthorizationCodeGrant { this.identifier, this.authorizationEndpoint, this.tokenEndpoint, {this.secret, String delimiter, - bool basicAuth: true, + bool basicAuth = true, http.Client httpClient, Map getParameters(MediaType contentType, String body)}) : _basicAuth = basicAuth, @@ -174,7 +174,7 @@ class AuthorizationCodeGrant { }; if (state != null) parameters['state'] = state; - if (!scopes.isEmpty) parameters['scope'] = scopes.join(_delimiter); + if (scopes.isNotEmpty) parameters['scope'] = scopes.join(_delimiter); return addQueryParameters(this.authorizationEndpoint, parameters); } diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart index 865b946ad..367d5838b 100644 --- a/pkgs/oauth2/lib/src/client.dart +++ b/pkgs/oauth2/lib/src/client.dart @@ -86,7 +86,7 @@ class Client extends http.BaseClient { Client(this._credentials, {this.identifier, this.secret, - bool basicAuth: true, + bool basicAuth = true, http.Client httpClient}) : _basicAuth = basicAuth, _httpClient = httpClient == null ? new http.Client() : httpClient { diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index f40b26db2..343f12dc4 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -190,7 +190,7 @@ class Credentials { {String identifier, String secret, Iterable newScopes, - bool basicAuth: true, + bool basicAuth = true, http.Client httpClient}) async { var scopes = this.scopes; if (newScopes != null) scopes = newScopes.toList(); @@ -213,7 +213,7 @@ class Credentials { var headers = {}; var body = {"grant_type": "refresh_token", "refresh_token": refreshToken}; - if (!scopes.isEmpty) body["scope"] = scopes.join(_delimiter); + if (scopes.isNotEmpty) body["scope"] = scopes.join(_delimiter); if (basicAuth && secret != null) { headers["Authorization"] = basicAuthHeader(identifier, secret); diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart index 84fac6747..ca4896e70 100644 --- a/pkgs/oauth2/lib/src/handle_access_token_response.dart +++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart @@ -106,7 +106,7 @@ void _handleErrorResponse( // off-spec. if (response.statusCode != 400 && response.statusCode != 401) { var reason = ''; - if (response.reasonPhrase != null && !response.reasonPhrase.isEmpty) { + if (response.reasonPhrase != null && response.reasonPhrase.isNotEmpty) { ' ${response.reasonPhrase}'; } throw new FormatException('OAuth request for "$tokenEndpoint" failed ' diff --git a/pkgs/oauth2/lib/src/resource_owner_password_grant.dart b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart index ebd1d124b..13d8f2862 100644 --- a/pkgs/oauth2/lib/src/resource_owner_password_grant.dart +++ b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart @@ -48,7 +48,7 @@ Future resourceOwnerPasswordGrant( {String identifier, String secret, Iterable scopes, - bool basicAuth: true, + bool basicAuth = true, http.Client httpClient, String delimiter, Map getParameters( @@ -73,7 +73,8 @@ Future resourceOwnerPasswordGrant( } } - if (scopes != null && !scopes.isEmpty) body['scope'] = scopes.join(delimiter); + if (scopes != null && scopes.isNotEmpty) + body['scope'] = scopes.join(delimiter); if (httpClient == null) httpClient = new http.Client(); var response = await httpClient.post(authorizationEndpoint, diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index f39e17576..915622b01 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -12,4 +12,5 @@ dependencies: http: '>=0.11.0 <0.12.0' http_parser: '>=1.0.0 <4.0.0' dev_dependencies: + pedantic: ^1.2.0 test: ^1.0.0 diff --git a/pkgs/oauth2/test/handle_access_token_response_test.dart b/pkgs/oauth2/test/handle_access_token_response_test.dart index e4008fbaf..fd4b1a7b8 100644 --- a/pkgs/oauth2/test/handle_access_token_response_test.dart +++ b/pkgs/oauth2/test/handle_access_token_response_test.dart @@ -26,9 +26,9 @@ oauth2.Credentials handle(http.Response response, void main() { group('an error response', () { oauth2.Credentials handleError( - {String body: '{"error": "invalid_request"}', - int statusCode: 400, - Map headers: const { + {String body = '{"error": "invalid_request"}', + int statusCode = 400, + Map headers = const { "content-type": "application/json" }}) => handle(new http.Response(body, statusCode, headers: headers)); @@ -115,9 +115,9 @@ void main() { group('a success response', () { oauth2.Credentials handleSuccess( - {String contentType: "application/json", - accessToken: 'access token', - tokenType: 'bearer', + {String contentType = "application/json", + accessToken = 'access token', + tokenType = 'bearer', expiresIn, refreshToken, scope}) { From 0f170f49048bb7ec320c5e8abb50e51a377a3a2f Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Fri, 26 Oct 2018 10:41:14 -0700 Subject: [PATCH 088/159] Support latest pkg:http, prepare for release --- pkgs/oauth2/CHANGELOG.md | 4 ++++ pkgs/oauth2/pubspec.yaml | 11 +++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md index 68d89dae2..b0767a20d 100644 --- a/pkgs/oauth2/CHANGELOG.md +++ b/pkgs/oauth2/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.2.3 + +* Support the latest `package:http` release. + # 1.2.2 * Allow the stable 2.0 SDK. diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index 915622b01..6e9d47ff0 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,16 +1,19 @@ name: oauth2 -version: 1.2.2 +version: 1.2.3 author: Dart Team homepage: https://github.com/dart-lang/oauth2 -description: > +description: >- A client library for authenticating with a remote service via OAuth2 on behalf of a user, and making authorized HTTP requests with the user's OAuth2 credentials. + environment: - sdk: '>=2.0.0-dev.17.0 <3.0.0' + sdk: '>=2.0.0 <3.0.0' + dependencies: - http: '>=0.11.0 <0.12.0' + http: '>=0.11.0 <0.13.0' http_parser: '>=1.0.0 <4.0.0' + dev_dependencies: pedantic: ^1.2.0 test: ^1.0.0 From 239135a6359f5c714994e2c7ff6432119d70f532 Mon Sep 17 00:00:00 2001 From: Wesley Fantinel Date: Fri, 14 Jun 2019 10:57:06 -0300 Subject: [PATCH 089/159] OpenID's id_token implementation --- pkgs/oauth2/CHANGELOG.md | 4 ++++ pkgs/oauth2/lib/src/credentials.dart | 14 +++++++++++++- .../lib/src/handle_access_token_response.dart | 3 ++- pkgs/oauth2/pubspec.yaml | 2 +- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md index b0767a20d..3827b2b9f 100644 --- a/pkgs/oauth2/CHANGELOG.md +++ b/pkgs/oauth2/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.2.4 + +* OpenID's id_token treated. + # 1.2.3 * Support the latest `package:http` release. diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index 343f12dc4..9e575f715 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -41,6 +41,14 @@ class Credentials { /// This may be `null`, indicating that the credentials can't be refreshed. final String refreshToken; + /// The token that is received from the authorization server to enable + /// End-Users to be Authenticated, contains Claims, represented as a + /// JSON Web Token (JWT). + /// + /// [spec]: https://openid.net/specs/openid-connect-core-1_0.html#IDToken + /// This may be `null`, indicating that the openid scope is not implemented. + final String idToken; + /// The URL of the authorization server endpoint that's used to refresh the /// credentials. /// @@ -95,6 +103,7 @@ class Credentials { /// [standard JSON response]: https://tools.ietf.org/html/rfc6749#section-5.1 Credentials(this.accessToken, {this.refreshToken, + this.idToken, this.tokenEndpoint, Iterable scopes, this.expiration, @@ -132,7 +141,7 @@ class Credentials { 'required field "accessToken" was not a string, was ' '${parsed["accessToken"]}'); - for (var stringField in ['refreshToken', 'tokenEndpoint']) { + for (var stringField in ['refreshToken', 'idToken', 'tokenEndpoint']) { var value = parsed[stringField]; validate(value == null || value is String, 'field "$stringField" was not a string, was "$value"'); @@ -155,6 +164,7 @@ class Credentials { return new Credentials(parsed['accessToken'], refreshToken: parsed['refreshToken'], + idToken: parsed['idToken'], tokenEndpoint: tokenEndpoint, scopes: (scopes as List).map((scope) => scope as String), expiration: expiration); @@ -167,6 +177,7 @@ class Credentials { String toJson() => jsonEncode({ 'accessToken': accessToken, 'refreshToken': refreshToken, + 'idToken': idToken, 'tokenEndpoint': tokenEndpoint == null ? null : tokenEndpoint.toString(), 'scopes': scopes, @@ -233,6 +244,7 @@ class Credentials { if (credentials.refreshToken != null) return credentials; return new Credentials(credentials.accessToken, refreshToken: this.refreshToken, + idToken: credentials.idToken, tokenEndpoint: credentials.tokenEndpoint, scopes: credentials.scopes, expiration: credentials.expiration); diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart index ca4896e70..851475aed 100644 --- a/pkgs/oauth2/lib/src/handle_access_token_response.dart +++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart @@ -72,7 +72,7 @@ Credentials handleAccessTokenResponse(http.Response response, Uri tokenEndpoint, 'parameter "expires_in" was not an int, was "$expiresIn"'); } - for (var name in ['refresh_token', 'scope']) { + for (var name in ['refresh_token', 'id_token', 'scope']) { var value = parameters[name]; if (value != null && value is! String) throw new FormatException( @@ -88,6 +88,7 @@ Credentials handleAccessTokenResponse(http.Response response, Uri tokenEndpoint, return new Credentials(parameters['access_token'], refreshToken: parameters['refresh_token'], + idToken: parameters['id_token'], tokenEndpoint: tokenEndpoint, scopes: scopes, expiration: expiration); diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index 6e9d47ff0..9da666f5a 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,5 @@ name: oauth2 -version: 1.2.3 +version: 1.2.4 author: Dart Team homepage: https://github.com/dart-lang/oauth2 description: >- From 379037cc79292f56764ccf0b7aa1f9d6cb5b3062 Mon Sep 17 00:00:00 2001 From: Michael Thomsen Date: Wed, 19 Jun 2019 15:05:27 +0200 Subject: [PATCH 090/159] Update README.md --- pkgs/oauth2/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/oauth2/README.md b/pkgs/oauth2/README.md index d3045a325..053f88bda 100644 --- a/pkgs/oauth2/README.md +++ b/pkgs/oauth2/README.md @@ -97,7 +97,7 @@ Future getClient() async { } main() async { - var client = await loadClient(); + var client = await getClient(); // Once you have a Client, you can use it just like any other HTTP client. var result = client.read("http://example.com/protected-resources.txt"); From 6d4537870d08fc8aab9d6bc355bf18f0d7503de1 Mon Sep 17 00:00:00 2001 From: Wesley Fantinel Date: Wed, 19 Jun 2019 10:24:01 -0300 Subject: [PATCH 091/159] Documentation improved --- pkgs/oauth2/lib/src/credentials.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index 9e575f715..635eed1b6 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -45,8 +45,9 @@ class Credentials { /// End-Users to be Authenticated, contains Claims, represented as a /// JSON Web Token (JWT). /// - /// [spec]: https://openid.net/specs/openid-connect-core-1_0.html#IDToken /// This may be `null`, indicating that the openid scope is not implemented. + /// + /// [spec]: https://openid.net/specs/openid-connect-core-1_0.html#IDToken final String idToken; /// The URL of the authorization server endpoint that's used to refresh the From d6072667e34b6c7a77a16dbcae9fc1aea07d1325 Mon Sep 17 00:00:00 2001 From: Wesley Fantinel Date: Wed, 19 Jun 2019 10:51:42 -0300 Subject: [PATCH 092/159] Add tests for the OpenID's id_token --- pkgs/oauth2/test/credentials_test.dart | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkgs/oauth2/test/credentials_test.dart b/pkgs/oauth2/test/credentials_test.dart index 039a06094..386f94892 100644 --- a/pkgs/oauth2/test/credentials_test.dart +++ b/pkgs/oauth2/test/credentials_test.dart @@ -270,6 +270,7 @@ void main() { var credentials = new oauth2.Credentials('access token', refreshToken: 'refresh token', + idToken: 'id token', tokenEndpoint: tokenEndpoint, scopes: ['scope1', 'scope2'], expiration: expiration); @@ -277,6 +278,7 @@ void main() { expect(reloaded.accessToken, equals(credentials.accessToken)); expect(reloaded.refreshToken, equals(credentials.refreshToken)); + expect(reloaded.idToken, equals(credentials.idToken)); expect(reloaded.tokenEndpoint.toString(), equals(credentials.tokenEndpoint.toString())); expect(reloaded.scopes, equals(credentials.scopes)); @@ -306,6 +308,11 @@ void main() { throwsFormatException); }); + test("should throw a FormatException if idToken is not a string", () { + expect(() => fromMap({"accessToken": "foo", "idToken": 12}), + throwsFormatException); + }); + test("should throw a FormatException if tokenEndpoint is not a string", () { expect(() => fromMap({"accessToken": "foo", "tokenEndpoint": 12}), throwsFormatException); From f2ec2a2aeace8f5f4307a9c4666036e2a73751cc Mon Sep 17 00:00:00 2001 From: Wesley Fantinel Date: Wed, 19 Jun 2019 11:02:16 -0300 Subject: [PATCH 093/159] Add tests for the OpenID's id_token --- .../handle_access_token_response_test.dart | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/pkgs/oauth2/test/handle_access_token_response_test.dart b/pkgs/oauth2/test/handle_access_token_response_test.dart index fd4b1a7b8..1ab32392f 100644 --- a/pkgs/oauth2/test/handle_access_token_response_test.dart +++ b/pkgs/oauth2/test/handle_access_token_response_test.dart @@ -259,4 +259,36 @@ void main() { expect(credentials.scopes, equals(['scope1', 'scope2'])); }); }); + + group('a success response with id_token', () { + oauth2.Credentials handleSuccess( + {String contentType = "application/json", + accessToken = 'access token', + tokenType = 'bearer', + expiresIn, + refreshToken, + idToken = 'decode me', + scope}) { + return handle(new http.Response( + jsonEncode({ + 'access_token': accessToken, + 'token_type': tokenType, + 'expires_in': expiresIn, + 'refresh_token': refreshToken, + 'id_token': idToken, + 'scope': scope + }), + 200, + headers: {'content-type': contentType})); + } + + test('with a non-string id token throws a FormatException', () { + expect(() => handleSuccess(idToken: 12), throwsFormatException); + }); + + test('with a id token sets the id token', () { + var credentials = handleSuccess(idToken: "decode me"); + expect(credentials.idToken, equals("decode me")); + }); + }); } From fde2aa896a3bc83da34f322016d04e2f97c73d15 Mon Sep 17 00:00:00 2001 From: Wesley Fantinel Date: Wed, 19 Jun 2019 11:27:13 -0300 Subject: [PATCH 094/159] Add tests for the OpenID's id_token, dartfmt --- pkgs/oauth2/test/handle_access_token_response_test.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkgs/oauth2/test/handle_access_token_response_test.dart b/pkgs/oauth2/test/handle_access_token_response_test.dart index 1ab32392f..8752cbda4 100644 --- a/pkgs/oauth2/test/handle_access_token_response_test.dart +++ b/pkgs/oauth2/test/handle_access_token_response_test.dart @@ -260,13 +260,12 @@ void main() { }); }); - group('a success response with id_token', () { + group('a success response with a id_token', () { oauth2.Credentials handleSuccess( {String contentType = "application/json", accessToken = 'access token', tokenType = 'bearer', expiresIn, - refreshToken, idToken = 'decode me', scope}) { return handle(new http.Response( @@ -274,7 +273,6 @@ void main() { 'access_token': accessToken, 'token_type': tokenType, 'expires_in': expiresIn, - 'refresh_token': refreshToken, 'id_token': idToken, 'scope': scope }), From 6cab259575e703fa1297e602400984db9f4ed0e2 Mon Sep 17 00:00:00 2001 From: Wesley Fantinel Date: Wed, 19 Jun 2019 11:40:32 -0300 Subject: [PATCH 095/159] Add tests for the OpenID's id_token, dartfmt --- .../oauth2/test/handle_access_token_response_test.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkgs/oauth2/test/handle_access_token_response_test.dart b/pkgs/oauth2/test/handle_access_token_response_test.dart index 8752cbda4..1556164e8 100644 --- a/pkgs/oauth2/test/handle_access_token_response_test.dart +++ b/pkgs/oauth2/test/handle_access_token_response_test.dart @@ -263,11 +263,11 @@ void main() { group('a success response with a id_token', () { oauth2.Credentials handleSuccess( {String contentType = "application/json", - accessToken = 'access token', - tokenType = 'bearer', - expiresIn, - idToken = 'decode me', - scope}) { + accessToken = 'access token', + tokenType = 'bearer', + expiresIn, + idToken = 'decode me', + scope}) { return handle(new http.Response( jsonEncode({ 'access_token': accessToken, From bf7c80e2670f344fe2355e17297b6bbed8bc15d1 Mon Sep 17 00:00:00 2001 From: tamcy Date: Fri, 7 Jun 2019 12:38:32 +0800 Subject: [PATCH 096/159] adds onCredentialsRefreshed callback support in Client class, adds onCredentialsRefreshed callback parameter in resourceOwnerPasswordGrant --- pkgs/oauth2/lib/src/client.dart | 9 ++++++ pkgs/oauth2/lib/src/credentials.dart | 3 ++ .../src/resource_owner_password_grant.dart | 7 ++++- pkgs/oauth2/test/client_test.dart | 31 +++++++++++++++++++ .../resource_owner_password_grant_test.dart | 25 ++++++++++++++- 5 files changed, 73 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart index 367d5838b..7986b5eb2 100644 --- a/pkgs/oauth2/lib/src/client.dart +++ b/pkgs/oauth2/lib/src/client.dart @@ -67,6 +67,9 @@ class Client extends http.BaseClient { Credentials get credentials => _credentials; Credentials _credentials; + /// Callback to be invoked whenever the credentials refreshed. + CredentialsRefreshedCallback _onCredentialsRefreshed; + /// Whether to use HTTP Basic authentication for authorizing the client. final bool _basicAuth; @@ -86,9 +89,11 @@ class Client extends http.BaseClient { Client(this._credentials, {this.identifier, this.secret, + CredentialsRefreshedCallback onCredentialsRefreshed, bool basicAuth = true, http.Client httpClient}) : _basicAuth = basicAuth, + _onCredentialsRefreshed = onCredentialsRefreshed, _httpClient = httpClient == null ? new http.Client() : httpClient { if (identifier == null && secret != null) { throw new ArgumentError("secret may not be passed without identifier."); @@ -156,6 +161,10 @@ class Client extends http.BaseClient { basicAuth: _basicAuth, httpClient: _httpClient); + if (_onCredentialsRefreshed != null) { + _onCredentialsRefreshed(_credentials); + } + return this; } diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index 343f12dc4..e8a161a45 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -13,6 +13,9 @@ import 'handle_access_token_response.dart'; import 'parameters.dart'; import 'utils.dart'; +/// Type of the callback when credentials are refreshed. +typedef CredentialsRefreshedCallback = void Function(Credentials); + /// Credentials that prove that a client is allowed to access a resource on the /// resource owner's behalf. /// diff --git a/pkgs/oauth2/lib/src/resource_owner_password_grant.dart b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart index 13d8f2862..a3c16f523 100644 --- a/pkgs/oauth2/lib/src/resource_owner_password_grant.dart +++ b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart @@ -10,6 +10,7 @@ import 'package:http_parser/http_parser.dart'; import 'client.dart'; import 'handle_access_token_response.dart'; import 'utils.dart'; +import 'credentials.dart'; /// Obtains credentials using a [resource owner password grant][]. /// @@ -49,6 +50,7 @@ Future resourceOwnerPasswordGrant( String secret, Iterable scopes, bool basicAuth = true, + CredentialsRefreshedCallback onCredentialsRefreshed, http.Client httpClient, String delimiter, Map getParameters( @@ -84,5 +86,8 @@ Future resourceOwnerPasswordGrant( response, authorizationEndpoint, startTime, scopes, delimiter, getParameters: getParameters); return new Client(credentials, - identifier: identifier, secret: secret, httpClient: httpClient); + identifier: identifier, + secret: secret, + httpClient: httpClient, + onCredentialsRefreshed: onCredentialsRefreshed); } diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart index ec52589dc..9284d28d7 100644 --- a/pkgs/oauth2/test/client_test.dart +++ b/pkgs/oauth2/test/client_test.dart @@ -64,6 +64,37 @@ void main() { await client.read(requestUri); expect(client.credentials.accessToken, equals('new access token')); }); + + test("that onCredentialsRefreshed is called", () async { + var callbackCalled = false; + + var expiration = new DateTime.now().subtract(new Duration(hours: 1)); + var credentials = new oauth2.Credentials('access token', + refreshToken: 'refresh token', + tokenEndpoint: tokenEndpoint, + expiration: expiration); + var client = new oauth2.Client(credentials, + identifier: 'identifier', + secret: 'secret', + httpClient: httpClient, onCredentialsRefreshed: (credentials) { + callbackCalled = true; + }); + + httpClient.expectRequest((request) { + return new Future.value(new http.Response( + jsonEncode( + {'access_token': 'new access token', 'token_type': 'bearer'}), + 200, + headers: {'content-type': 'application/json'})); + }); + + httpClient.expectRequest((request) { + return new Future.value(new http.Response('good job', 200)); + }); + + await client.read(requestUri); + expect(callbackCalled, equals(true)); + }); }); group('with valid credentials', () { diff --git a/pkgs/oauth2/test/resource_owner_password_grant_test.dart b/pkgs/oauth2/test/resource_owner_password_grant_test.dart index 04d1537d9..65e67d6a3 100644 --- a/pkgs/oauth2/test/resource_owner_password_grant_test.dart +++ b/pkgs/oauth2/test/resource_owner_password_grant_test.dart @@ -3,8 +3,8 @@ // BSD-style license that can be found in the LICENSE file. @TestOn("vm") - import 'dart:convert'; +import 'dart:mirrors'; import 'package:http/http.dart' as http; import 'package:oauth2/oauth2.dart' as oauth2; @@ -46,6 +46,29 @@ void main() { expect(client.credentials.accessToken, equals('2YotnFZFEjr1zCsicMWpAA')); }); + test('passes the onCredentialsRefreshed callback to the client', () async { + expectClient.expectRequest((request) async { + return new http.Response(success, 200, + headers: {'content-type': 'application/json'}); + }); + + var onCredentialsRefreshedCallback = (oauth2.Credentials credentials) {}; + + var client = await oauth2.resourceOwnerPasswordGrant( + authEndpoint, 'username', 'userpass', + identifier: 'client', + secret: 'secret', + httpClient: expectClient, + onCredentialsRefreshed: onCredentialsRefreshedCallback); + + var reflection = reflect(client); + var sym = MirrorSystem.getSymbol( + '_onCredentialsRefreshed', reflection.type.owner); + + expect(reflection.getField(sym).reflectee, + equals(onCredentialsRefreshedCallback)); + }); + test('builds correct request when using query parameters for client', () async { expectClient.expectRequest((request) async { From eb35346d698ac3912c350b8ce4cb7ca710dd4e97 Mon Sep 17 00:00:00 2001 From: tamcy Date: Fri, 21 Jun 2019 09:25:41 +0800 Subject: [PATCH 097/159] fix style --- pkgs/oauth2/lib/src/client.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart index 7986b5eb2..9ce35f611 100644 --- a/pkgs/oauth2/lib/src/client.dart +++ b/pkgs/oauth2/lib/src/client.dart @@ -68,7 +68,7 @@ class Client extends http.BaseClient { Credentials _credentials; /// Callback to be invoked whenever the credentials refreshed. - CredentialsRefreshedCallback _onCredentialsRefreshed; + final CredentialsRefreshedCallback _onCredentialsRefreshed; /// Whether to use HTTP Basic authentication for authorizing the client. final bool _basicAuth; @@ -161,9 +161,8 @@ class Client extends http.BaseClient { basicAuth: _basicAuth, httpClient: _httpClient); - if (_onCredentialsRefreshed != null) { + if (_onCredentialsRefreshed != null) _onCredentialsRefreshed(_credentials); - } return this; } From 1179d64d802edb83998c532b442cf6696a1b86e2 Mon Sep 17 00:00:00 2001 From: tamcy Date: Fri, 21 Jun 2019 09:26:39 +0800 Subject: [PATCH 098/159] update test cases --- pkgs/oauth2/test/client_test.dart | 1 + .../resource_owner_password_grant_test.dart | 39 +++++++++++++------ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart index 9284d28d7..1f13d58f9 100644 --- a/pkgs/oauth2/test/client_test.dart +++ b/pkgs/oauth2/test/client_test.dart @@ -78,6 +78,7 @@ void main() { secret: 'secret', httpClient: httpClient, onCredentialsRefreshed: (credentials) { callbackCalled = true; + expect(credentials.accessToken, equals('new access token')); }); httpClient.expectRequest((request) { diff --git a/pkgs/oauth2/test/resource_owner_password_grant_test.dart b/pkgs/oauth2/test/resource_owner_password_grant_test.dart index 65e67d6a3..42a2909f5 100644 --- a/pkgs/oauth2/test/resource_owner_password_grant_test.dart +++ b/pkgs/oauth2/test/resource_owner_password_grant_test.dart @@ -4,7 +4,7 @@ @TestOn("vm") import 'dart:convert'; -import 'dart:mirrors'; +import 'dart:async'; import 'package:http/http.dart' as http; import 'package:oauth2/oauth2.dart' as oauth2; @@ -48,25 +48,40 @@ void main() { test('passes the onCredentialsRefreshed callback to the client', () async { expectClient.expectRequest((request) async { - return new http.Response(success, 200, + return new http.Response( + jsonEncode({ + "access_token": "2YotnFZFEjr1zCsicMWpAA", + "token_type": "bearer", + "expires_in": -3600, + "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA", + }), + 200, headers: {'content-type': 'application/json'}); }); - var onCredentialsRefreshedCallback = (oauth2.Credentials credentials) {}; + var callbackCalled = false; var client = await oauth2.resourceOwnerPasswordGrant( authEndpoint, 'username', 'userpass', - identifier: 'client', - secret: 'secret', - httpClient: expectClient, - onCredentialsRefreshed: onCredentialsRefreshedCallback); + identifier: 'client', secret: 'secret', httpClient: expectClient, + onCredentialsRefreshed: (oauth2.Credentials credentials) { + callbackCalled = true; + }); - var reflection = reflect(client); - var sym = MirrorSystem.getSymbol( - '_onCredentialsRefreshed', reflection.type.owner); + expectClient.expectRequest((request) { + return new Future.value(new http.Response( + jsonEncode( + {'access_token': 'new access token', 'token_type': 'bearer'}), + 200, + headers: {'content-type': 'application/json'})); + }); + + expectClient.expectRequest((request) { + return new Future.value(new http.Response('good job', 200)); + }); - expect(reflection.getField(sym).reflectee, - equals(onCredentialsRefreshedCallback)); + await client.read(Uri.parse("http://example.com/resource")); + expect(callbackCalled, equals(true)); }); test('builds correct request when using query parameters for client', From c3ea94990bea730092c30c388193c36a6103b203 Mon Sep 17 00:00:00 2001 From: tamcy Date: Fri, 21 Jun 2019 11:12:18 +0800 Subject: [PATCH 099/159] add `onCredentialsRefreshed` parameter to `AuthorizationCodeGrant` --- .../lib/src/authorization_code_grant.dart | 16 ++++++- .../test/authorization_code_grant_test.dart | 46 +++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/lib/src/authorization_code_grant.dart b/pkgs/oauth2/lib/src/authorization_code_grant.dart index 6b2829006..526ab9fab 100644 --- a/pkgs/oauth2/lib/src/authorization_code_grant.dart +++ b/pkgs/oauth2/lib/src/authorization_code_grant.dart @@ -9,6 +9,7 @@ import 'package:http_parser/http_parser.dart'; import 'client.dart'; import 'authorization_exception.dart'; +import 'credentials.dart'; import 'handle_access_token_response.dart'; import 'parameters.dart'; import 'utils.dart'; @@ -71,6 +72,11 @@ class AuthorizationCodeGrant { /// documentation. final Uri tokenEndpoint; + /// Callback to be invoked whenever the credentials are refreshed. + /// + /// This will be passed as-is to the constructed [Client]. + CredentialsRefreshedCallback _onCredentialsRefreshed; + /// Whether to use HTTP Basic authentication for authorizing the client. final bool _basicAuth; @@ -107,6 +113,9 @@ class AuthorizationCodeGrant { /// [httpClient] is used for all HTTP requests made by this grant, as well as /// those of the [Client] is constructs. /// + /// [onCredentialsRefreshed] will be called by the constructed [Client] + /// whenever the credentials are refreshed. + /// /// The scope strings will be separated by the provided [delimiter]. This /// defaults to `" "`, the OAuth2 standard, but some APIs (such as Facebook's) /// use non-standard delimiters. @@ -126,11 +135,13 @@ class AuthorizationCodeGrant { String delimiter, bool basicAuth = true, http.Client httpClient, + CredentialsRefreshedCallback onCredentialsRefreshed, Map getParameters(MediaType contentType, String body)}) : _basicAuth = basicAuth, _httpClient = httpClient == null ? new http.Client() : httpClient, _delimiter = delimiter ?? ' ', - _getParameters = getParameters ?? parseJsonParameters; + _getParameters = getParameters ?? parseJsonParameters, + _onCredentialsRefreshed = onCredentialsRefreshed; /// Returns the URL to which the resource owner should be redirected to /// authorize this client. @@ -289,7 +300,8 @@ class AuthorizationCodeGrant { identifier: this.identifier, secret: this.secret, basicAuth: _basicAuth, - httpClient: _httpClient); + httpClient: _httpClient, + onCredentialsRefreshed: _onCredentialsRefreshed); } /// Closes the grant and frees its resources. diff --git a/pkgs/oauth2/test/authorization_code_grant_test.dart b/pkgs/oauth2/test/authorization_code_grant_test.dart index ec6e71bff..babdb78ac 100644 --- a/pkgs/oauth2/test/authorization_code_grant_test.dart +++ b/pkgs/oauth2/test/authorization_code_grant_test.dart @@ -289,4 +289,50 @@ void main() { }))); }); }); + + group('onCredentialsRefreshed', () { + test('is correctly propagated', () async { + var isCallbackInvoked = false; + var grant = new oauth2.AuthorizationCodeGrant( + 'identifier', + Uri.parse('https://example.com/authorization'), + Uri.parse('https://example.com/token'), + secret: 'secret', + basicAuth: false, + httpClient: client, onCredentialsRefreshed: (credentials) { + isCallbackInvoked = true; + }); + + grant.getAuthorizationUrl(redirectUrl); + client.expectRequest((request) { + return new Future.value(new http.Response( + jsonEncode({ + 'access_token': 'access token', + 'token_type': 'bearer', + "expires_in": -3600, + "refresh_token": "refresh token", + }), + 200, + headers: {'content-type': 'application/json'})); + }); + + var oauth2Client = await grant.handleAuthorizationCode('auth code'); + + client.expectRequest((request) { + return new Future.value(new http.Response( + jsonEncode( + {'access_token': 'new access token', 'token_type': 'bearer'}), + 200, + headers: {'content-type': 'application/json'})); + }); + + client.expectRequest((request) { + return new Future.value(new http.Response('good job', 200)); + }); + + await oauth2Client.read(Uri.parse("http://example.com/resource")); + + expect(isCallbackInvoked, equals(true)); + }); + }); } From b53ae3c1bc9a0990b6cfb6099ef9d29323a0c87e Mon Sep 17 00:00:00 2001 From: tamcy Date: Fri, 21 Jun 2019 11:15:56 +0800 Subject: [PATCH 100/159] fix variable naming in test case in `resource_owner_password_grant_test.dart` --- pkgs/oauth2/test/resource_owner_password_grant_test.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/oauth2/test/resource_owner_password_grant_test.dart b/pkgs/oauth2/test/resource_owner_password_grant_test.dart index 42a2909f5..acd76d12b 100644 --- a/pkgs/oauth2/test/resource_owner_password_grant_test.dart +++ b/pkgs/oauth2/test/resource_owner_password_grant_test.dart @@ -59,13 +59,13 @@ void main() { headers: {'content-type': 'application/json'}); }); - var callbackCalled = false; + var isCallbackInvoked = false; var client = await oauth2.resourceOwnerPasswordGrant( authEndpoint, 'username', 'userpass', identifier: 'client', secret: 'secret', httpClient: expectClient, onCredentialsRefreshed: (oauth2.Credentials credentials) { - callbackCalled = true; + isCallbackInvoked = true; }); expectClient.expectRequest((request) { @@ -81,7 +81,7 @@ void main() { }); await client.read(Uri.parse("http://example.com/resource")); - expect(callbackCalled, equals(true)); + expect(isCallbackInvoked, equals(true)); }); test('builds correct request when using query parameters for client', From 0a71e5120913ae4057ebaba8668a31808c4b90d4 Mon Sep 17 00:00:00 2001 From: tamcy Date: Fri, 21 Jun 2019 11:18:57 +0800 Subject: [PATCH 101/159] remove the TODO message concerning `onCredentialsRefreshed` --- pkgs/oauth2/lib/src/client.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart index 9ce35f611..6408415cc 100644 --- a/pkgs/oauth2/lib/src/client.dart +++ b/pkgs/oauth2/lib/src/client.dart @@ -11,8 +11,6 @@ import 'authorization_exception.dart'; import 'credentials.dart'; import 'expiration_exception.dart'; -// TODO(nweiz): Add an onCredentialsRefreshed event once we have some event -// infrastructure. /// An OAuth2 client. /// /// This acts as a drop-in replacement for an [http.Client], while sending From 0c1dc7a1e0831c8e98f1c2d27f8ee96d6d0ecfc7 Mon Sep 17 00:00:00 2001 From: tamcy Date: Fri, 21 Jun 2019 21:59:56 +0800 Subject: [PATCH 102/159] reformat code --- pkgs/oauth2/lib/src/client.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart index 6408415cc..4fa64a1db 100644 --- a/pkgs/oauth2/lib/src/client.dart +++ b/pkgs/oauth2/lib/src/client.dart @@ -159,8 +159,7 @@ class Client extends http.BaseClient { basicAuth: _basicAuth, httpClient: _httpClient); - if (_onCredentialsRefreshed != null) - _onCredentialsRefreshed(_credentials); + if (_onCredentialsRefreshed != null) _onCredentialsRefreshed(_credentials); return this; } From 9b7a2f39e49bedc7d3d859164a92e29e600a32a6 Mon Sep 17 00:00:00 2001 From: Jonas Finnemann Jensen Date: Mon, 24 Jun 2019 12:39:46 +0200 Subject: [PATCH 103/159] Update CHANGELOG.md See: https://github.com/dart-lang/oauth2/pull/46 --- pkgs/oauth2/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md index b0767a20d..2ee5442cd 100644 --- a/pkgs/oauth2/CHANGELOG.md +++ b/pkgs/oauth2/CHANGELOG.md @@ -1,3 +1,6 @@ + +* Added `onCredentialsRefreshed` option when creating `Client` objects. + # 1.2.3 * Support the latest `package:http` release. From cbdb2ad49c1e986412d5bac526215af529c2d6b7 Mon Sep 17 00:00:00 2001 From: Jonas Finnemann Jensen Date: Mon, 24 Jun 2019 12:41:03 +0200 Subject: [PATCH 104/159] Update CHANGELOG.md --- pkgs/oauth2/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md index 2ee5442cd..0181a09e4 100644 --- a/pkgs/oauth2/CHANGELOG.md +++ b/pkgs/oauth2/CHANGELOG.md @@ -1,3 +1,4 @@ +# 1.3.0 * Added `onCredentialsRefreshed` option when creating `Client` objects. From 2de5710194029fb02c79be49d7921988b2eaf772 Mon Sep 17 00:00:00 2001 From: Jonas Finnemann Jensen Date: Mon, 24 Jun 2019 12:41:34 +0200 Subject: [PATCH 105/159] Prepare 1.3.0 release --- pkgs/oauth2/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index 6e9d47ff0..9ccb43e41 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,5 @@ name: oauth2 -version: 1.2.3 +version: 1.3.0 author: Dart Team homepage: https://github.com/dart-lang/oauth2 description: >- From 06c351316ab3a64679a09d39ea7780a51ab77e2b Mon Sep 17 00:00:00 2001 From: Wesley Fantinel Date: Fri, 4 Oct 2019 09:20:26 -0300 Subject: [PATCH 106/159] Support for OpenID id_token --- pkgs/oauth2/lib/src/credentials.dart | 3 ++- pkgs/oauth2/pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index 635eed1b6..ecbe0d417 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -45,7 +45,8 @@ class Credentials { /// End-Users to be Authenticated, contains Claims, represented as a /// JSON Web Token (JWT). /// - /// This may be `null`, indicating that the openid scope is not implemented. + /// This may be `null`, indicating that the 'openid' scope was not + /// requested (or not supported). /// /// [spec]: https://openid.net/specs/openid-connect-core-1_0.html#IDToken final String idToken; diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index 9da666f5a..9e8c56f7f 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,5 @@ name: oauth2 -version: 1.2.4 +version: 1.4.0 author: Dart Team homepage: https://github.com/dart-lang/oauth2 description: >- From c05fbae3a10d970d3a5d6f9ee265a6c4f4e329f5 Mon Sep 17 00:00:00 2001 From: Tobe Osakwe Date: Tue, 19 Nov 2019 14:43:13 -0800 Subject: [PATCH 107/159] Add support for client credentials grant (dart-lang/oauth2#45) * Add client credentials grant * Add test * Export client credentials grant * Add client credentials grant to readme --- pkgs/oauth2/README.md | 38 +++++- pkgs/oauth2/lib/oauth2.dart | 1 + .../lib/src/client_credentials_grant.dart | 78 ++++++++++++ .../src/resource_owner_password_grant.dart | 2 +- .../test/client_credentials_grant_test.dart | 111 ++++++++++++++++++ 5 files changed, 228 insertions(+), 2 deletions(-) create mode 100644 pkgs/oauth2/lib/src/client_credentials_grant.dart create mode 100644 pkgs/oauth2/test/client_credentials_grant_test.dart diff --git a/pkgs/oauth2/README.md b/pkgs/oauth2/README.md index 053f88bda..567d73fcd 100644 --- a/pkgs/oauth2/README.md +++ b/pkgs/oauth2/README.md @@ -12,12 +12,13 @@ client has permission to access resources on behalf of the resource owner. OAuth2 provides several different methods for the client to obtain authorization. At the time of writing, this library only supports the -[AuthorizationCodeGrant][] and [resourceOwnerPasswordGrant][] methods, but +[AuthorizationCodeGrant][], [clientCredentialsGrant][] and [resourceOwnerPasswordGrant][] methods, but further methods may be added in the future. The following example uses this method to authenticate, and assumes that the library is being used by a server-side application. [AuthorizationCodeGrant]: http://www.dartdocs.org/documentation/oauth2/latest/index.html#oauth2/oauth2.AuthorizationCodeGrant +[clientCredentialsGrant]: http://www.dartdocs.org/documentation/oauth2/latest/index.html#oauth2/oauth2.clientCredentialsGrant [resourceOwnerPasswordGrant]: http://www.dartdocs.org/documentation/oauth2/latest/index.html#oauth2/oauth2.resourceOwnerPasswordGrant ## Authorization Code Grant @@ -112,6 +113,41 @@ main() async { } ``` +## Client Credentials Grant +```dart +// This URL is an endpoint that's provided by the authorization server. It's +// usually included in the server's documentation of its OAuth2 API. +final authorizationEndpoint = + Uri.parse("http://example.com/oauth2/authorization"); + +// The OAuth2 specification expects a client's identifier and secret +// to be sent when using the client credentials grant. +// +// Because the client credentials grant is not inherently associated with a user, +// it is up to the server in question whether the returned token allows limited +// API access. +// +// Either way, you must provide both a client identifier and a client secret: +final identifier = "my client identifier"; +final secret = "my client secret"; + +// Calling the top-level `clientCredentialsGrant` function will return a +// [Client] instead. +var client = await oauth2.clientCredentialsGrant( + authorizationEndpoint, identifier, secret); + +// With an authenticated client, you can make requests, and the `Bearer` token +// returned by the server during the client credentials grant will be attached +// to any request you make. +var response = await client.read("https://example.com/api/some_resource.json"); + +// You can save the client's credentials, which consists of an access token, and +// potentially a refresh token and expiry date, to a file. This way, subsequent runs +// do not need to reauthenticate, and you can avoid saving the client identifier and +// secret. +await credentialsFile.writeAsString(client.credentials.toJson()); +``` + ## Resource Owner Password Grant ```dart diff --git a/pkgs/oauth2/lib/oauth2.dart b/pkgs/oauth2/lib/oauth2.dart index 3b81cf1d4..dfd2d57cf 100644 --- a/pkgs/oauth2/lib/oauth2.dart +++ b/pkgs/oauth2/lib/oauth2.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. export 'src/authorization_code_grant.dart'; +export 'src/client_credentials_grant.dart'; export 'src/resource_owner_password_grant.dart'; export 'src/client.dart'; export 'src/credentials.dart'; diff --git a/pkgs/oauth2/lib/src/client_credentials_grant.dart b/pkgs/oauth2/lib/src/client_credentials_grant.dart new file mode 100644 index 000000000..3c4693597 --- /dev/null +++ b/pkgs/oauth2/lib/src/client_credentials_grant.dart @@ -0,0 +1,78 @@ +// 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. + +import 'dart:async'; + +import 'package:http/http.dart' as http; +import 'package:http_parser/http_parser.dart'; + +import 'client.dart'; +import 'handle_access_token_response.dart'; +import 'utils.dart'; + +/// Obtains credentials using a [client credentials grant](https://tools.ietf.org/html/rfc6749#section-1.3.4). +/// +/// This mode of authorization uses the client's [identifier] and [secret] +/// to obtain an authorization token from the authorization server, instead +/// of sending a user through a dedicated flow. +/// +/// The client [identifier] and [secret] are required, and are +/// used to identify and authenticate your specific OAuth2 client. These are +/// usually global to the program using this library. +/// +/// The specific permissions being requested from the authorization server may +/// be specified via [scopes]. The scope strings are specific to the +/// authorization server and may be found in its documentation. Note that you +/// may not be granted access to every scope you request; you may check the +/// [Credentials.scopes] field of [Client.credentials] to see which scopes you +/// were granted. +/// +/// The scope strings will be separated by the provided [delimiter]. This +/// defaults to `" "`, the OAuth2 standard, but some APIs (such as Facebook's) +/// use non-standard delimiters. +/// +/// By default, this follows the OAuth2 spec and requires the server's responses +/// to be in JSON format. However, some servers return non-standard response +/// formats, which can be parsed using the [getParameters] function. +/// +/// This function is passed the `Content-Type` header of the response as well as +/// its body as a UTF-8-decoded string. It should return a map in the same +/// format as the [standard JSON response](https://tools.ietf.org/html/rfc6749#section-5.1) +Future clientCredentialsGrant( + Uri authorizationEndpoint, String identifier, String secret, + {Iterable scopes, + bool basicAuth = true, + http.Client httpClient, + String delimiter, + Map getParameters( + MediaType contentType, String body)}) async { + delimiter ??= ' '; + var startTime = new DateTime.now(); + + var body = {"grant_type": "client_credentials"}; + + var headers = {}; + + if (identifier != null) { + if (basicAuth) { + headers['Authorization'] = basicAuthHeader(identifier, secret); + } else { + body['client_id'] = identifier; + if (secret != null) body['client_secret'] = secret; + } + } + + if (scopes != null && scopes.isNotEmpty) + body['scope'] = scopes.join(delimiter); + + if (httpClient == null) httpClient = new http.Client(); + var response = await httpClient.post(authorizationEndpoint, + headers: headers, body: body); + + var credentials = await handleAccessTokenResponse( + response, authorizationEndpoint, startTime, scopes, delimiter, + getParameters: getParameters); + return new Client(credentials, + identifier: identifier, secret: secret, httpClient: httpClient); +} diff --git a/pkgs/oauth2/lib/src/resource_owner_password_grant.dart b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart index a3c16f523..174a3c087 100644 --- a/pkgs/oauth2/lib/src/resource_owner_password_grant.dart +++ b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart @@ -12,7 +12,7 @@ import 'handle_access_token_response.dart'; import 'utils.dart'; import 'credentials.dart'; -/// Obtains credentials using a [resource owner password grant][]. +/// Obtains credentials using a [resource owner password grant](https://tools.ietf.org/html/rfc6749#section-1.3.3). /// /// This mode of authorization uses the user's username and password to obtain /// an authentication token, which can then be stored. This is safer than diff --git a/pkgs/oauth2/test/client_credentials_grant_test.dart b/pkgs/oauth2/test/client_credentials_grant_test.dart new file mode 100644 index 000000000..062414d6f --- /dev/null +++ b/pkgs/oauth2/test/client_credentials_grant_test.dart @@ -0,0 +1,111 @@ +// 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. + +@TestOn("vm") + +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:oauth2/oauth2.dart' as oauth2; +import 'package:test/test.dart'; + +import 'utils.dart'; + +final success = jsonEncode({ + "access_token": "2YotnFZFEjr1zCsicMWpAA", + "token_type": "bearer", + "expires_in": 3600, + "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA", +}); + +var auth = 'Basic Y2xpZW50OnNlY3JldA=='; +var authEndpoint = Uri.parse('https://example.com'); + +void main() { + var expectClient; + setUp(() => expectClient = new ExpectClient()); + + group('basic', () { + test('builds correct request with client when using basic auth for client', + () async { + expectClient.expectRequest((request) async { + expect(auth, equals(request.headers['authorization'])); + expect(request.bodyFields['grant_type'], equals('client_credentials')); + return new http.Response(success, 200, + headers: {'content-type': 'application/json'}); + }); + + var client = await oauth2.clientCredentialsGrant( + authEndpoint, 'client', 'secret', + httpClient: expectClient); + + expect(client.credentials, isNotNull); + expect(client.credentials.accessToken, equals('2YotnFZFEjr1zCsicMWpAA')); + }); + + test('builds correct request when using query parameters for client', + () async { + expectClient.expectRequest((request) async { + expect(request.bodyFields['grant_type'], equals('client_credentials')); + expect(request.bodyFields['client_id'], equals('client')); + expect(request.bodyFields['client_secret'], equals('secret')); + return new http.Response(success, 200, + headers: {'content-type': 'application/json'}); + }); + + var client = await oauth2.clientCredentialsGrant( + authEndpoint, 'client', 'secret', + basicAuth: false, httpClient: expectClient); + expect(client.credentials, isNotNull); + expect(client.credentials.accessToken, equals('2YotnFZFEjr1zCsicMWpAA')); + }); + + test('builds correct request using scope', () async { + expectClient.expectRequest((request) async { + expect(auth, equals(request.headers['authorization'])); + expect(request.bodyFields['grant_type'], equals('client_credentials')); + expect(request.bodyFields['scope'], equals('one two')); + return new http.Response(success, 200, + headers: {'content-type': 'application/json'}); + }); + + var client = await oauth2.clientCredentialsGrant( + authEndpoint, 'client', 'secret', + scopes: ['one', 'two'], httpClient: expectClient); + expect(client.credentials, isNotNull); + expect(client.credentials.accessToken, equals('2YotnFZFEjr1zCsicMWpAA')); + }); + + test('builds correct request using scope with custom delimiter', () async { + expectClient.expectRequest((request) async { + expect(request.bodyFields['grant_type'], equals('client_credentials')); + expect(request.bodyFields['scope'], equals('one,two')); + return new http.Response(success, 200, + headers: {'content-type': 'application/json'}); + }); + + await oauth2.clientCredentialsGrant(authEndpoint, 'client', 'secret', + scopes: ['one', 'two'], httpClient: expectClient, delimiter: ','); + }); + + test('merges with existing query parameters', () async { + var authEndpoint = Uri.parse('https://example.com?query=value'); + + expectClient.expectRequest((request) async { + expect(request.bodyFields['grant_type'], equals('client_credentials')); + expect(request.bodyFields['client_id'], equals('client')); + expect(request.bodyFields['client_secret'], equals('secret')); + expect(request.url.queryParameters['query'], equals('value')); + return new http.Response(success, 200, + headers: {'content-type': 'application/json'}); + }); + + var client = await oauth2.clientCredentialsGrant( + authEndpoint, 'client', 'secret', + basicAuth: false, httpClient: expectClient); + expect(client.credentials, isNotNull); + expect(client.credentials.accessToken, equals('2YotnFZFEjr1zCsicMWpAA')); + }); + }); +} From d40144d56c22a4ab2cc06ded9b3de242475b50fd Mon Sep 17 00:00:00 2001 From: Jonas Finnemann Jensen Date: Thu, 21 Nov 2019 18:09:08 +0100 Subject: [PATCH 108/159] Prepare release of 1.5.0 (dart-lang/oauth2#57) --- pkgs/oauth2/CHANGELOG.md | 4 ++++ pkgs/oauth2/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md index 87dfbad1f..6de1d4b29 100644 --- a/pkgs/oauth2/CHANGELOG.md +++ b/pkgs/oauth2/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.5.0 + +* Added support for `clientCredentialsGrant`. + # 1.4.0 * OpenID's id_token treated. diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index 9e8c56f7f..30cd3f879 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,5 @@ name: oauth2 -version: 1.4.0 +version: 1.5.0 author: Dart Team homepage: https://github.com/dart-lang/oauth2 description: >- From 149264e82882b976f409df0fd1f0ffce4f07cc80 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Fri, 24 Apr 2020 08:19:00 -0500 Subject: [PATCH 109/159] Add PKCE support to AuthorizationCodeGrant (dart-lang/oauth2#69) * Add PKCE support * Run dartfmt --- .../lib/src/authorization_code_grant.dart | 28 ++++- pkgs/oauth2/pubspec.yaml | 1 + .../test/authorization_code_grant_test.dart | 119 +++++++++++------- 3 files changed, 98 insertions(+), 50 deletions(-) diff --git a/pkgs/oauth2/lib/src/authorization_code_grant.dart b/pkgs/oauth2/lib/src/authorization_code_grant.dart index 526ab9fab..1e930e114 100644 --- a/pkgs/oauth2/lib/src/authorization_code_grant.dart +++ b/pkgs/oauth2/lib/src/authorization_code_grant.dart @@ -3,7 +3,10 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; +import 'dart:convert'; +import 'dart:math'; +import 'package:crypto/crypto.dart'; import 'package:http/http.dart' as http; import 'package:http_parser/http_parser.dart'; @@ -100,6 +103,13 @@ class AuthorizationCodeGrant { /// The current state of the grant object. _State _state = _State.initial; + /// Allowed characters for generating the _codeVerifier + static const String _charset = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"; + + /// The generated PKCE code verifier + String _codeVerifier; + /// Creates a new grant. /// /// If [basicAuth] is `true` (the default), the client credentials are sent to @@ -175,13 +185,20 @@ class AuthorizationCodeGrant { scopes = scopes.toList(); } + _codeVerifier = _createCodeVerifier(); + var codeChallenge = base64Url + .encode(sha256.convert(ascii.encode(_codeVerifier)).bytes) + .replaceAll("=", ""); + this._redirectEndpoint = redirect; this._scopes = scopes; this._stateString = state; var parameters = { "response_type": "code", "client_id": this.identifier, - "redirect_uri": redirect.toString() + "redirect_uri": redirect.toString(), + "code_challenge": codeChallenge, + "code_challenge_method": "S256" }; if (state != null) parameters['state'] = state; @@ -278,7 +295,8 @@ class AuthorizationCodeGrant { var body = { "grant_type": "authorization_code", "code": authorizationCode, - "redirect_uri": this._redirectEndpoint.toString() + "redirect_uri": this._redirectEndpoint.toString(), + "code_verifier": _codeVerifier }; if (_basicAuth && secret != null) { @@ -304,6 +322,12 @@ class AuthorizationCodeGrant { onCredentialsRefreshed: _onCredentialsRefreshed); } + /// Randomly generate a 128 character string to be used as the PKCE code verifier + static String _createCodeVerifier() { + return List.generate( + 128, (i) => _charset[Random.secure().nextInt(_charset.length)]).join(); + } + /// Closes the grant and frees its resources. /// /// This will close the underlying HTTP client, which is shared by the diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index 30cd3f879..0b3d3f2de 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -13,6 +13,7 @@ environment: dependencies: http: '>=0.11.0 <0.13.0' http_parser: '>=1.0.0 <4.0.0' + crypto: '^2.1.3' dev_dependencies: pedantic: ^1.2.0 diff --git a/pkgs/oauth2/test/authorization_code_grant_test.dart b/pkgs/oauth2/test/authorization_code_grant_test.dart index babdb78ac..2fcf36ba4 100644 --- a/pkgs/oauth2/test/authorization_code_grant_test.dart +++ b/pkgs/oauth2/test/authorization_code_grant_test.dart @@ -30,10 +30,13 @@ void main() { test('builds the correct URL', () { expect( grant.getAuthorizationUrl(redirectUrl).toString(), - equals('https://example.com/authorization' - '?response_type=code' - '&client_id=identifier' - '&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect')); + allOf([ + startsWith('https://example.com/authorization?response_type=code'), + contains('&client_id=identifier'), + contains('&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect'), + matches(r'&code_challenge=[A-Za-z0-9\+\/\-\_]{43}'), + contains('&code_challenge_method=S256') + ])); }); test('builds the correct URL with scopes', () { @@ -41,11 +44,14 @@ void main() { .getAuthorizationUrl(redirectUrl, scopes: ['scope', 'other/scope']); expect( authorizationUrl.toString(), - equals('https://example.com/authorization' - '?response_type=code' - '&client_id=identifier' - '&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect' - '&scope=scope+other%2Fscope')); + allOf([ + startsWith('https://example.com/authorization?response_type=code'), + contains('&client_id=identifier'), + contains('&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect'), + matches(r'&code_challenge=[A-Za-z0-9\+\/\-\_]{43}'), + contains('&code_challenge_method=S256'), + contains('&scope=scope+other%2Fscope') + ])); }); test('separates scopes with the correct delimiter', () { @@ -60,11 +66,14 @@ void main() { .getAuthorizationUrl(redirectUrl, scopes: ['scope', 'other/scope']); expect( authorizationUrl.toString(), - equals('https://example.com/authorization' - '?response_type=code' - '&client_id=identifier' - '&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect' - '&scope=scope_other%2Fscope')); + allOf([ + startsWith('https://example.com/authorization?response_type=code'), + contains('&client_id=identifier'), + contains('&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect'), + matches(r'&code_challenge=[A-Za-z0-9\+\/\-\_]{43}'), + contains('&code_challenge_method=S256'), + contains('&scope=scope_other%2Fscope') + ])); }); test('builds the correct URL with state', () { @@ -72,11 +81,14 @@ void main() { grant.getAuthorizationUrl(redirectUrl, state: 'state'); expect( authorizationUrl.toString(), - equals('https://example.com/authorization' - '?response_type=code' - '&client_id=identifier' - '&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect' - '&state=state')); + allOf([ + startsWith('https://example.com/authorization?response_type=code'), + contains('&client_id=identifier'), + contains('&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect'), + matches(r'&code_challenge=[A-Za-z0-9\+\/\-\_]{43}'), + contains('&code_challenge_method=S256'), + contains('&state=state') + ])); }); test('merges with existing query parameters', () { @@ -90,11 +102,14 @@ void main() { var authorizationUrl = grant.getAuthorizationUrl(redirectUrl); expect( authorizationUrl.toString(), - equals('https://example.com/authorization' - '?query=value' - '&response_type=code' - '&client_id=identifier' - '&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect')); + allOf([ + startsWith('https://example.com/authorization?query=value'), + contains('&response_type=code'), + contains('&client_id=identifier'), + contains('&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect'), + matches(r'&code_challenge=[A-Za-z0-9\+\/\-\_]{43}'), + contains('&code_challenge_method=S256'), + ])); }); test("can't be called twice", () { @@ -148,11 +163,13 @@ void main() { expect(request.url.toString(), equals(grant.tokenEndpoint.toString())); expect( request.bodyFields, - equals({ - 'grant_type': 'authorization_code', - 'code': 'auth code', - 'redirect_uri': redirectUrl.toString() - })); + allOf([ + containsPair('grant_type', 'authorization_code'), + containsPair('code', 'auth code'), + containsPair('redirect_uri', redirectUrl.toString()), + containsPair( + 'code_verifier', matches(r'[A-Za-z0-9\-\.\_\~]{128}')) + ])); expect(request.headers, containsPair("Authorization", "Basic aWRlbnRpZmllcjpzZWNyZXQ=")); @@ -190,11 +207,13 @@ void main() { expect(request.url.toString(), equals(grant.tokenEndpoint.toString())); expect( request.bodyFields, - equals({ - 'grant_type': 'authorization_code', - 'code': 'auth code', - 'redirect_uri': redirectUrl.toString() - })); + allOf([ + containsPair('grant_type', 'authorization_code'), + containsPair('code', 'auth code'), + containsPair('redirect_uri', redirectUrl.toString()), + containsPair( + 'code_verifier', matches(r'[A-Za-z0-9\-\.\_\~]{128}')) + ])); expect(request.headers, containsPair("Authorization", "Basic aWRlbnRpZmllcjpzZWNyZXQ=")); @@ -235,13 +254,15 @@ void main() { expect(request.url.toString(), equals(grant.tokenEndpoint.toString())); expect( request.bodyFields, - equals({ - 'grant_type': 'authorization_code', - 'code': 'auth code', - 'redirect_uri': redirectUrl.toString(), - 'client_id': 'identifier', - 'client_secret': 'secret' - })); + allOf([ + containsPair('grant_type', 'authorization_code'), + containsPair('code', 'auth code'), + containsPair('redirect_uri', redirectUrl.toString()), + containsPair( + 'code_verifier', matches(r'[A-Za-z0-9\-\.\_\~]{128}')), + containsPair('client_id', 'identifier'), + containsPair('client_secret', 'secret') + ])); return new Future.value(new http.Response( jsonEncode({ @@ -265,13 +286,15 @@ void main() { expect(request.url.toString(), equals(grant.tokenEndpoint.toString())); expect( request.bodyFields, - equals({ - 'grant_type': 'authorization_code', - 'code': 'auth code', - 'redirect_uri': redirectUrl.toString(), - 'client_id': 'identifier', - 'client_secret': 'secret' - })); + allOf([ + containsPair('grant_type', 'authorization_code'), + containsPair('code', 'auth code'), + containsPair('redirect_uri', redirectUrl.toString()), + containsPair( + 'code_verifier', matches(r'[A-Za-z0-9\-\.\_\~]{128}')), + containsPair('client_id', 'identifier'), + containsPair('client_secret', 'secret') + ])); return new Future.value(new http.Response( jsonEncode({ From c5612745f68db98da3301d793c1d4029825243c9 Mon Sep 17 00:00:00 2001 From: Roi Snir Date: Mon, 27 Apr 2020 12:04:23 +0300 Subject: [PATCH 110/159] dart analysis issues fixes (dart-lang/oauth2#71) * dart analysis issues fixes * fixed formatting --- .../lib/src/authorization_code_grant.dart | 82 ++++++----- .../lib/src/authorization_exception.dart | 1 + pkgs/oauth2/lib/src/client.dart | 20 +-- .../lib/src/client_credentials_grant.dart | 15 +- pkgs/oauth2/lib/src/credentials.dart | 46 +++--- pkgs/oauth2/lib/src/expiration_exception.dart | 1 + .../lib/src/handle_access_token_response.dart | 45 +++--- pkgs/oauth2/lib/src/parameters.dart | 12 +- .../src/resource_owner_password_grant.dart | 19 +-- pkgs/oauth2/lib/src/utils.dart | 6 +- .../test/authorization_code_grant_test.dart | 40 ++--- .../test/client_credentials_grant_test.dart | 22 +-- pkgs/oauth2/test/client_test.dart | 86 +++++------ pkgs/oauth2/test/credentials_test.dart | 139 +++++++++--------- .../handle_access_token_response_test.dart | 54 +++---- .../resource_owner_password_grant_test.dart | 38 ++--- pkgs/oauth2/test/utils.dart | 8 +- 17 files changed, 320 insertions(+), 314 deletions(-) diff --git a/pkgs/oauth2/lib/src/authorization_code_grant.dart b/pkgs/oauth2/lib/src/authorization_code_grant.dart index 1e930e114..c04bc0b61 100644 --- a/pkgs/oauth2/lib/src/authorization_code_grant.dart +++ b/pkgs/oauth2/lib/src/authorization_code_grant.dart @@ -78,13 +78,13 @@ class AuthorizationCodeGrant { /// Callback to be invoked whenever the credentials are refreshed. /// /// This will be passed as-is to the constructed [Client]. - CredentialsRefreshedCallback _onCredentialsRefreshed; + final CredentialsRefreshedCallback _onCredentialsRefreshed; /// Whether to use HTTP Basic authentication for authorizing the client. final bool _basicAuth; /// A [String] used to separate scopes; defaults to `" "`. - String _delimiter; + final String _delimiter; /// The HTTP client used to make HTTP requests. http.Client _httpClient; @@ -105,7 +105,7 @@ class AuthorizationCodeGrant { /// Allowed characters for generating the _codeVerifier static const String _charset = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"; + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~'; /// The generated PKCE code verifier String _codeVerifier; @@ -146,9 +146,10 @@ class AuthorizationCodeGrant { bool basicAuth = true, http.Client httpClient, CredentialsRefreshedCallback onCredentialsRefreshed, - Map getParameters(MediaType contentType, String body)}) + Map Function(MediaType contentType, String body) + getParameters}) : _basicAuth = basicAuth, - _httpClient = httpClient == null ? new http.Client() : httpClient, + _httpClient = httpClient ?? http.Client(), _delimiter = delimiter ?? ' ', _getParameters = getParameters ?? parseJsonParameters, _onCredentialsRefreshed = onCredentialsRefreshed; @@ -175,7 +176,7 @@ class AuthorizationCodeGrant { Uri getAuthorizationUrl(Uri redirect, {Iterable scopes, String state}) { if (_state != _State.initial) { - throw new StateError('The authorization URL has already been generated.'); + throw StateError('The authorization URL has already been generated.'); } _state = _State.awaitingResponse; @@ -188,23 +189,23 @@ class AuthorizationCodeGrant { _codeVerifier = _createCodeVerifier(); var codeChallenge = base64Url .encode(sha256.convert(ascii.encode(_codeVerifier)).bytes) - .replaceAll("=", ""); + .replaceAll('=', ''); - this._redirectEndpoint = redirect; - this._scopes = scopes; - this._stateString = state; + _redirectEndpoint = redirect; + _scopes = scopes; + _stateString = state; var parameters = { - "response_type": "code", - "client_id": this.identifier, - "redirect_uri": redirect.toString(), - "code_challenge": codeChallenge, - "code_challenge_method": "S256" + 'response_type': 'code', + 'client_id': identifier, + 'redirect_uri': redirect.toString(), + 'code_challenge': codeChallenge, + 'code_challenge_method': 'S256' }; if (state != null) parameters['state'] = state; if (scopes.isNotEmpty) parameters['scope'] = scopes.join(_delimiter); - return addQueryParameters(this.authorizationEndpoint, parameters); + return addQueryParameters(authorizationEndpoint, parameters); } /// Processes the query parameters added to a redirect from the authorization @@ -227,19 +228,19 @@ class AuthorizationCodeGrant { Future handleAuthorizationResponse( Map parameters) async { if (_state == _State.initial) { - throw new StateError('The authorization URL has not yet been generated.'); + throw StateError('The authorization URL has not yet been generated.'); } else if (_state == _State.finished) { - throw new StateError('The authorization code has already been received.'); + throw StateError('The authorization code has already been received.'); } _state = _State.finished; if (_stateString != null) { if (!parameters.containsKey('state')) { - throw new FormatException('Invalid OAuth response for ' + throw FormatException('Invalid OAuth response for ' '"$authorizationEndpoint": parameter "state" expected to be ' '"$_stateString", was missing.'); } else if (parameters['state'] != _stateString) { - throw new FormatException('Invalid OAuth response for ' + throw FormatException('Invalid OAuth response for ' '"$authorizationEndpoint": parameter "state" expected to be ' '"$_stateString", was "${parameters['state']}".'); } @@ -249,9 +250,9 @@ class AuthorizationCodeGrant { var description = parameters['error_description']; var uriString = parameters['error_uri']; var uri = uriString == null ? null : Uri.parse(uriString); - throw new AuthorizationException(parameters['error'], description, uri); + throw AuthorizationException(parameters['error'], description, uri); } else if (!parameters.containsKey('code')) { - throw new FormatException('Invalid OAuth response for ' + throw FormatException('Invalid OAuth response for ' '"$authorizationEndpoint": did not contain required parameter ' '"code".'); } @@ -276,9 +277,9 @@ class AuthorizationCodeGrant { /// Throws [AuthorizationException] if the authorization fails. Future handleAuthorizationCode(String authorizationCode) async { if (_state == _State.initial) { - throw new StateError('The authorization URL has not yet been generated.'); + throw StateError('The authorization URL has not yet been generated.'); } else if (_state == _State.finished) { - throw new StateError('The authorization code has already been received.'); + throw StateError('The authorization code has already been received.'); } _state = _State.finished; @@ -288,35 +289,35 @@ class AuthorizationCodeGrant { /// This works just like [handleAuthorizationCode], except it doesn't validate /// the state beforehand. Future _handleAuthorizationCode(String authorizationCode) async { - var startTime = new DateTime.now(); + var startTime = DateTime.now(); var headers = {}; var body = { - "grant_type": "authorization_code", - "code": authorizationCode, - "redirect_uri": this._redirectEndpoint.toString(), - "code_verifier": _codeVerifier + 'grant_type': 'authorization_code', + 'code': authorizationCode, + 'redirect_uri': _redirectEndpoint.toString(), + 'code_verifier': _codeVerifier }; if (_basicAuth && secret != null) { - headers["Authorization"] = basicAuthHeader(identifier, secret); + headers['Authorization'] = basicAuthHeader(identifier, secret); } else { // The ID is required for this request any time basic auth isn't being // used, even if there's no actual client authentication to be done. - body["client_id"] = identifier; - if (secret != null) body["client_secret"] = secret; + body['client_id'] = identifier; + if (secret != null) body['client_secret'] = secret; } - var response = await _httpClient.post(this.tokenEndpoint, - headers: headers, body: body); + var response = + await _httpClient.post(tokenEndpoint, headers: headers, body: body); var credentials = handleAccessTokenResponse( response, tokenEndpoint, startTime, _scopes, _delimiter, getParameters: _getParameters); - return new Client(credentials, - identifier: this.identifier, - secret: this.secret, + return Client(credentials, + identifier: identifier, + secret: secret, basicAuth: _basicAuth, httpClient: _httpClient, onCredentialsRefreshed: _onCredentialsRefreshed); @@ -343,21 +344,22 @@ class AuthorizationCodeGrant { class _State { /// [AuthorizationCodeGrant.getAuthorizationUrl] has not yet been called for /// this grant. - static const initial = const _State("initial"); + static const initial = _State('initial'); // [AuthorizationCodeGrant.getAuthorizationUrl] has been called but neither // [AuthorizationCodeGrant.handleAuthorizationResponse] nor // [AuthorizationCodeGrant.handleAuthorizationCode] has been called. - static const awaitingResponse = const _State("awaiting response"); + static const awaitingResponse = _State('awaiting response'); // [AuthorizationCodeGrant.getAuthorizationUrl] and either // [AuthorizationCodeGrant.handleAuthorizationResponse] or // [AuthorizationCodeGrant.handleAuthorizationCode] have been called. - static const finished = const _State("finished"); + static const finished = _State('finished'); final String _name; const _State(this._name); + @override String toString() => _name; } diff --git a/pkgs/oauth2/lib/src/authorization_exception.dart b/pkgs/oauth2/lib/src/authorization_exception.dart index 9ce4b8c95..a1b6dd9d2 100644 --- a/pkgs/oauth2/lib/src/authorization_exception.dart +++ b/pkgs/oauth2/lib/src/authorization_exception.dart @@ -26,6 +26,7 @@ class AuthorizationException implements Exception { AuthorizationException(this.error, this.description, this.uri); /// Provides a string description of the AuthorizationException. + @override String toString() { var header = 'OAuth authorization error ($error)'; if (description != null) { diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart index 4fa64a1db..d33aaa0d4 100644 --- a/pkgs/oauth2/lib/src/client.dart +++ b/pkgs/oauth2/lib/src/client.dart @@ -92,9 +92,9 @@ class Client extends http.BaseClient { http.Client httpClient}) : _basicAuth = basicAuth, _onCredentialsRefreshed = onCredentialsRefreshed, - _httpClient = httpClient == null ? new http.Client() : httpClient { + _httpClient = httpClient ?? http.Client() { if (identifier == null && secret != null) { - throw new ArgumentError("secret may not be passed without identifier."); + throw ArgumentError('secret may not be passed without identifier.'); } } @@ -102,13 +102,14 @@ class Client extends http.BaseClient { /// /// This will also automatically refresh this client's [Credentials] before /// sending the request if necessary. + @override Future send(http.BaseRequest request) async { if (credentials.isExpired) { - if (!credentials.canRefresh) throw new ExpirationException(credentials); + if (!credentials.canRefresh) throw ExpirationException(credentials); await refreshCredentials(); } - request.headers['authorization'] = "Bearer ${credentials.accessToken}"; + request.headers['authorization'] = 'Bearer ${credentials.accessToken}'; var response = await _httpClient.send(request); if (response.statusCode != 401) return response; @@ -130,9 +131,7 @@ class Client extends http.BaseClient { var params = challenge.parameters; if (!params.containsKey('error')) return response; - throw new AuthorizationException( - params['error'], - params['error_description'], + throw AuthorizationException(params['error'], params['error_description'], params['error_uri'] == null ? null : Uri.parse(params['error_uri'])); } @@ -147,9 +146,9 @@ class Client extends http.BaseClient { /// [Credentials.scopes] field of [Client.credentials]. Future refreshCredentials([List newScopes]) async { if (!credentials.canRefresh) { - var prefix = "OAuth credentials"; - if (credentials.isExpired) prefix = "$prefix have expired and"; - throw new StateError("$prefix can't be refreshed."); + var prefix = 'OAuth credentials'; + if (credentials.isExpired) prefix = '$prefix have expired and'; + throw StateError("$prefix can't be refreshed."); } _credentials = await credentials.refresh( @@ -165,6 +164,7 @@ class Client extends http.BaseClient { } /// Closes this client and its underlying HTTP client. + @override void close() { if (_httpClient != null) _httpClient.close(); _httpClient = null; diff --git a/pkgs/oauth2/lib/src/client_credentials_grant.dart b/pkgs/oauth2/lib/src/client_credentials_grant.dart index 3c4693597..c6d34b94c 100644 --- a/pkgs/oauth2/lib/src/client_credentials_grant.dart +++ b/pkgs/oauth2/lib/src/client_credentials_grant.dart @@ -45,12 +45,12 @@ Future clientCredentialsGrant( bool basicAuth = true, http.Client httpClient, String delimiter, - Map getParameters( - MediaType contentType, String body)}) async { + Map Function(MediaType contentType, String body) + getParameters}) async { delimiter ??= ' '; - var startTime = new DateTime.now(); + var startTime = DateTime.now(); - var body = {"grant_type": "client_credentials"}; + var body = {'grant_type': 'client_credentials'}; var headers = {}; @@ -63,16 +63,17 @@ Future clientCredentialsGrant( } } - if (scopes != null && scopes.isNotEmpty) + if (scopes != null && scopes.isNotEmpty) { body['scope'] = scopes.join(delimiter); + } - if (httpClient == null) httpClient = new http.Client(); + httpClient ??= http.Client(); var response = await httpClient.post(authorizationEndpoint, headers: headers, body: body); var credentials = await handleAccessTokenResponse( response, authorizationEndpoint, startTime, scopes, delimiter, getParameters: getParameters); - return new Client(credentials, + return Client(credentials, identifier: identifier, secret: secret, httpClient: httpClient); } diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index 6ea254dce..2b40b056d 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -81,7 +81,7 @@ class Credentials { /// 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 DateTime.now().isAfter(expiration); + expiration != null && DateTime.now().isAfter(expiration); /// Whether it's possible to refresh these credentials. bool get canRefresh => refreshToken != null && tokenEndpoint != null; @@ -113,8 +113,9 @@ class Credentials { Iterable scopes, this.expiration, String delimiter, - Map getParameters(MediaType mediaType, String body)}) - : scopes = new UnmodifiableListView( + Map Function(MediaType mediaType, String body) + getParameters}) + : scopes = UnmodifiableListView( // Explicitly type-annotate the list literal to work around // sdk#24202. scopes == null ? [] : scopes.toList()), @@ -125,10 +126,9 @@ class Credentials { /// /// Throws a [FormatException] if the JSON is incorrectly formatted. factory Credentials.fromJson(String json) { - validate(condition, message) { + void validate(condition, message) { if (condition) return; - throw new FormatException( - "Failed to load credentials: $message.\n\n$json"); + throw FormatException('Failed to load credentials: $message.\n\n$json'); } var parsed; @@ -164,10 +164,10 @@ class Credentials { if (expiration != null) { validate(expiration is int, 'field "expiration" was not an int, was "$expiration"'); - expiration = new DateTime.fromMillisecondsSinceEpoch(expiration); + expiration = DateTime.fromMillisecondsSinceEpoch(expiration); } - return new Credentials(parsed['accessToken'], + return Credentials(parsed['accessToken'], refreshToken: parsed['refreshToken'], idToken: parsed['idToken'], tokenEndpoint: tokenEndpoint, @@ -210,32 +210,32 @@ class Credentials { http.Client httpClient}) async { var scopes = this.scopes; if (newScopes != null) scopes = newScopes.toList(); - if (scopes == null) scopes = []; - if (httpClient == null) httpClient = new http.Client(); + scopes ??= []; + httpClient ??= http.Client(); if (identifier == null && secret != null) { - throw new ArgumentError("secret may not be passed without identifier."); + throw ArgumentError('secret may not be passed without identifier.'); } - var startTime = new DateTime.now(); + var startTime = DateTime.now(); if (refreshToken == null) { - throw new StateError("Can't refresh credentials without a refresh " - "token."); + throw StateError("Can't refresh credentials without a refresh " + 'token.'); } else if (tokenEndpoint == null) { - throw new StateError("Can't refresh credentials without a token " - "endpoint."); + throw StateError("Can't refresh credentials without a token " + 'endpoint.'); } var headers = {}; - var body = {"grant_type": "refresh_token", "refresh_token": refreshToken}; - if (scopes.isNotEmpty) body["scope"] = scopes.join(_delimiter); + var body = {'grant_type': 'refresh_token', 'refresh_token': refreshToken}; + if (scopes.isNotEmpty) body['scope'] = scopes.join(_delimiter); if (basicAuth && secret != null) { - headers["Authorization"] = basicAuthHeader(identifier, secret); + headers['Authorization'] = basicAuthHeader(identifier, secret); } else { - if (identifier != null) body["client_id"] = identifier; - if (secret != null) body["client_secret"] = secret; + if (identifier != null) body['client_id'] = identifier; + if (secret != null) body['client_secret'] = secret; } var response = @@ -247,8 +247,8 @@ class Credentials { // The authorization server may issue a new refresh token. If it doesn't, // we should re-use the one we already have. if (credentials.refreshToken != null) return credentials; - return new Credentials(credentials.accessToken, - refreshToken: this.refreshToken, + return Credentials(credentials.accessToken, + refreshToken: refreshToken, idToken: credentials.idToken, tokenEndpoint: credentials.tokenEndpoint, scopes: credentials.scopes, diff --git a/pkgs/oauth2/lib/src/expiration_exception.dart b/pkgs/oauth2/lib/src/expiration_exception.dart index 2df2f8d59..d72fcf64c 100644 --- a/pkgs/oauth2/lib/src/expiration_exception.dart +++ b/pkgs/oauth2/lib/src/expiration_exception.dart @@ -13,6 +13,7 @@ class ExpirationException implements Exception { ExpirationException(this.credentials); /// Provides a string description of the ExpirationException. + @override String toString() => "OAuth2 credentials have expired and can't be refreshed."; } diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart index 851475aed..7517af15c 100644 --- a/pkgs/oauth2/lib/src/handle_access_token_response.dart +++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart @@ -13,7 +13,7 @@ import 'parameters.dart'; /// /// This allows credential expiration checks to remain valid for a reasonable /// amount of time. -const _expirationGrace = const Duration(seconds: 10); +const _expirationGrace = Duration(seconds: 10); /// Handles a response from the authorization server that contains an access /// token. @@ -32,7 +32,8 @@ const _expirationGrace = const Duration(seconds: 10); /// [standard JSON response]: https://tools.ietf.org/html/rfc6749#section-5.1 Credentials handleAccessTokenResponse(http.Response response, Uri tokenEndpoint, DateTime startTime, List scopes, String delimiter, - {Map getParameters(MediaType contentType, String body)}) { + {Map Function(MediaType contentType, String body) + getParameters}) { getParameters ??= parseJsonParameters; try { @@ -42,18 +43,18 @@ Credentials handleAccessTokenResponse(http.Response response, Uri tokenEndpoint, var contentTypeString = response.headers['content-type']; if (contentTypeString == null) { - throw new FormatException('Missing Content-Type string.'); + throw FormatException('Missing Content-Type string.'); } var parameters = - getParameters(new MediaType.parse(contentTypeString), response.body); + getParameters(MediaType.parse(contentTypeString), response.body); for (var requiredParameter in ['access_token', 'token_type']) { if (!parameters.containsKey(requiredParameter)) { - throw new FormatException( + throw FormatException( 'did not contain required parameter "$requiredParameter"'); } else if (parameters[requiredParameter] is! String) { - throw new FormatException( + throw FormatException( 'required parameter "$requiredParameter" was not a string, was ' '"${parameters[requiredParameter]}"'); } @@ -62,21 +63,22 @@ Credentials handleAccessTokenResponse(http.Response response, Uri tokenEndpoint, // TODO(nweiz): support the "mac" token type // (http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01) if (parameters['token_type'].toLowerCase() != 'bearer') { - throw new FormatException( + throw FormatException( '"$tokenEndpoint": unknown token type "${parameters['token_type']}"'); } var expiresIn = parameters['expires_in']; if (expiresIn != null && expiresIn is! int) { - throw new FormatException( + throw FormatException( 'parameter "expires_in" was not an int, was "$expiresIn"'); } for (var name in ['refresh_token', 'id_token', 'scope']) { var value = parameters[name]; - if (value != null && value is! String) - throw new FormatException( + if (value != null && value is! String) { + throw FormatException( 'parameter "$name" was not a string, was "$value"'); + } } var scope = parameters['scope'] as String; @@ -84,16 +86,16 @@ Credentials handleAccessTokenResponse(http.Response response, Uri tokenEndpoint, var expiration = expiresIn == null ? null - : startTime.add(new Duration(seconds: expiresIn) - _expirationGrace); + : startTime.add(Duration(seconds: expiresIn) - _expirationGrace); - return new Credentials(parameters['access_token'], + return Credentials(parameters['access_token'], refreshToken: parameters['refresh_token'], idToken: parameters['id_token'], tokenEndpoint: tokenEndpoint, scopes: scopes, expiration: expiration); } on FormatException catch (e) { - throw new FormatException('Invalid OAuth response for "$tokenEndpoint": ' + throw FormatException('Invalid OAuth response for "$tokenEndpoint": ' '${e.message}.\n\n${response.body}'); } } @@ -110,34 +112,33 @@ void _handleErrorResponse( if (response.reasonPhrase != null && response.reasonPhrase.isNotEmpty) { ' ${response.reasonPhrase}'; } - throw new FormatException('OAuth request for "$tokenEndpoint" failed ' + throw FormatException('OAuth request for "$tokenEndpoint" failed ' 'with status ${response.statusCode}$reason.\n\n${response.body}'); } var contentTypeString = response.headers['content-type']; var contentType = - contentTypeString == null ? null : new MediaType.parse(contentTypeString); + contentTypeString == null ? null : MediaType.parse(contentTypeString); var parameters = getParameters(contentType, response.body); if (!parameters.containsKey('error')) { - throw new FormatException('did not contain required parameter "error"'); + throw FormatException('did not contain required parameter "error"'); } else if (parameters['error'] is! String) { - throw new FormatException( - 'required parameter "error" was not a string, was ' + throw FormatException('required parameter "error" was not a string, was ' '"${parameters["error"]}"'); } for (var name in ['error_description', 'error_uri']) { var value = parameters[name]; - if (value != null && value is! String) - throw new FormatException( - 'parameter "$name" was not a string, was "$value"'); + if (value != null && value is! String) { + throw FormatException('parameter "$name" was not a string, was "$value"'); + } } var description = parameters['error_description']; var uriString = parameters['error_uri']; var uri = uriString == null ? null : Uri.parse(uriString); - throw new AuthorizationException(parameters['error'], description, uri); + throw AuthorizationException(parameters['error'], description, uri); } diff --git a/pkgs/oauth2/lib/src/parameters.dart b/pkgs/oauth2/lib/src/parameters.dart index 09ee5b77d..e8290fc3d 100644 --- a/pkgs/oauth2/lib/src/parameters.dart +++ b/pkgs/oauth2/lib/src/parameters.dart @@ -7,7 +7,8 @@ import 'dart:convert'; import 'package:http_parser/http_parser.dart'; /// The type of a callback that parses parameters from an HTTP response. -typedef Map GetParameters(MediaType contentType, String body); +typedef GetParameters = Map Function( + MediaType contentType, String body); /// Parses parameters from a response with a JSON body, as per the [OAuth2 /// spec][]. @@ -17,9 +18,9 @@ Map parseJsonParameters(MediaType contentType, String body) { // The spec requires a content-type of application/json, but some endpoints // (e.g. Dropbox) serve it as text/javascript instead. if (contentType == null || - (contentType.mimeType != "application/json" && - contentType.mimeType != "text/javascript")) { - throw new FormatException( + (contentType.mimeType != 'application/json' && + contentType.mimeType != 'text/javascript')) { + throw FormatException( 'Content-Type was "$contentType", expected "application/json"'); } @@ -28,6 +29,5 @@ Map parseJsonParameters(MediaType contentType, String body) { return untypedParameters; } - throw new FormatException( - 'Parameters must be a map, was "$untypedParameters"'); + throw FormatException('Parameters must be a map, was "$untypedParameters"'); } diff --git a/pkgs/oauth2/lib/src/resource_owner_password_grant.dart b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart index 174a3c087..118f7c96c 100644 --- a/pkgs/oauth2/lib/src/resource_owner_password_grant.dart +++ b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart @@ -53,15 +53,15 @@ Future resourceOwnerPasswordGrant( CredentialsRefreshedCallback onCredentialsRefreshed, http.Client httpClient, String delimiter, - Map getParameters( - MediaType contentType, String body)}) async { + Map Function(MediaType contentType, String body) + getParameters}) async { delimiter ??= ' '; - var startTime = new DateTime.now(); + var startTime = DateTime.now(); var body = { - "grant_type": "password", - "username": username, - "password": password + 'grant_type': 'password', + 'username': username, + 'password': password }; var headers = {}; @@ -75,17 +75,18 @@ Future resourceOwnerPasswordGrant( } } - if (scopes != null && scopes.isNotEmpty) + if (scopes != null && scopes.isNotEmpty) { body['scope'] = scopes.join(delimiter); + } - if (httpClient == null) httpClient = new http.Client(); + httpClient ??= http.Client(); var response = await httpClient.post(authorizationEndpoint, headers: headers, body: body); var credentials = await handleAccessTokenResponse( response, authorizationEndpoint, startTime, scopes, delimiter, getParameters: getParameters); - return new Client(credentials, + return Client(credentials, identifier: identifier, secret: secret, httpClient: httpClient, diff --git a/pkgs/oauth2/lib/src/utils.dart b/pkgs/oauth2/lib/src/utils.dart index 6c3453feb..50a925a6e 100644 --- a/pkgs/oauth2/lib/src/utils.dart +++ b/pkgs/oauth2/lib/src/utils.dart @@ -7,9 +7,9 @@ import 'dart:convert'; /// Adds additional query parameters to [url], overwriting the original /// parameters if a name conflict occurs. Uri addQueryParameters(Uri url, Map parameters) => url.replace( - queryParameters: new Map.from(url.queryParameters)..addAll(parameters)); + queryParameters: Map.from(url.queryParameters)..addAll(parameters)); String basicAuthHeader(String identifier, String secret) { - var userPass = Uri.encodeFull(identifier) + ":" + Uri.encodeFull(secret); - return "Basic " + base64Encode(ascii.encode(userPass)); + var userPass = Uri.encodeFull(identifier) + ':' + Uri.encodeFull(secret); + return 'Basic ' + base64Encode(ascii.encode(userPass)); } diff --git a/pkgs/oauth2/test/authorization_code_grant_test.dart b/pkgs/oauth2/test/authorization_code_grant_test.dart index 2fcf36ba4..4909bf5b9 100644 --- a/pkgs/oauth2/test/authorization_code_grant_test.dart +++ b/pkgs/oauth2/test/authorization_code_grant_test.dart @@ -17,8 +17,8 @@ void main() { ExpectClient client; oauth2.AuthorizationCodeGrant grant; setUp(() { - client = new ExpectClient(); - grant = new oauth2.AuthorizationCodeGrant( + client = ExpectClient(); + grant = oauth2.AuthorizationCodeGrant( 'identifier', Uri.parse('https://example.com/authorization'), Uri.parse('https://example.com/token'), @@ -55,7 +55,7 @@ void main() { }); test('separates scopes with the correct delimiter', () { - var grant = new oauth2.AuthorizationCodeGrant( + var grant = oauth2.AuthorizationCodeGrant( 'identifier', Uri.parse('https://example.com/authorization'), Uri.parse('https://example.com/token'), @@ -92,7 +92,7 @@ void main() { }); test('merges with existing query parameters', () { - grant = new oauth2.AuthorizationCodeGrant( + grant = oauth2.AuthorizationCodeGrant( 'identifier', Uri.parse('https://example.com/authorization?query=value'), Uri.parse('https://example.com/token'), @@ -171,9 +171,9 @@ void main() { 'code_verifier', matches(r'[A-Za-z0-9\-\.\_\~]{128}')) ])); expect(request.headers, - containsPair("Authorization", "Basic aWRlbnRpZmllcjpzZWNyZXQ=")); + containsPair('Authorization', 'Basic aWRlbnRpZmllcjpzZWNyZXQ=')); - return new Future.value(new http.Response( + return Future.value(http.Response( jsonEncode({ 'access_token': 'access token', 'token_type': 'bearer', @@ -215,9 +215,9 @@ void main() { 'code_verifier', matches(r'[A-Za-z0-9\-\.\_\~]{128}')) ])); expect(request.headers, - containsPair("Authorization", "Basic aWRlbnRpZmllcjpzZWNyZXQ=")); + containsPair('Authorization', 'Basic aWRlbnRpZmllcjpzZWNyZXQ=')); - return new Future.value(new http.Response( + return Future.value(http.Response( jsonEncode({ 'access_token': 'access token', 'token_type': 'bearer', @@ -234,10 +234,10 @@ void main() { }); }); - group("with basicAuth: false", () { + group('with basicAuth: false', () { setUp(() { - client = new ExpectClient(); - grant = new oauth2.AuthorizationCodeGrant( + client = ExpectClient(); + grant = oauth2.AuthorizationCodeGrant( 'identifier', Uri.parse('https://example.com/authorization'), Uri.parse('https://example.com/token'), @@ -264,7 +264,7 @@ void main() { containsPair('client_secret', 'secret') ])); - return new Future.value(new http.Response( + return Future.value(http.Response( jsonEncode({ 'access_token': 'access token', 'token_type': 'bearer', @@ -296,7 +296,7 @@ void main() { containsPair('client_secret', 'secret') ])); - return new Future.value(new http.Response( + return Future.value(http.Response( jsonEncode({ 'access_token': 'access token', 'token_type': 'bearer', @@ -316,7 +316,7 @@ void main() { group('onCredentialsRefreshed', () { test('is correctly propagated', () async { var isCallbackInvoked = false; - var grant = new oauth2.AuthorizationCodeGrant( + var grant = oauth2.AuthorizationCodeGrant( 'identifier', Uri.parse('https://example.com/authorization'), Uri.parse('https://example.com/token'), @@ -328,12 +328,12 @@ void main() { grant.getAuthorizationUrl(redirectUrl); client.expectRequest((request) { - return new Future.value(new http.Response( + return Future.value(http.Response( jsonEncode({ 'access_token': 'access token', 'token_type': 'bearer', - "expires_in": -3600, - "refresh_token": "refresh token", + 'expires_in': -3600, + 'refresh_token': 'refresh token', }), 200, headers: {'content-type': 'application/json'})); @@ -342,7 +342,7 @@ void main() { var oauth2Client = await grant.handleAuthorizationCode('auth code'); client.expectRequest((request) { - return new Future.value(new http.Response( + return Future.value(http.Response( jsonEncode( {'access_token': 'new access token', 'token_type': 'bearer'}), 200, @@ -350,10 +350,10 @@ void main() { }); client.expectRequest((request) { - return new Future.value(new http.Response('good job', 200)); + return Future.value(http.Response('good job', 200)); }); - await oauth2Client.read(Uri.parse("http://example.com/resource")); + await oauth2Client.read(Uri.parse('http://example.com/resource')); expect(isCallbackInvoked, equals(true)); }); diff --git a/pkgs/oauth2/test/client_credentials_grant_test.dart b/pkgs/oauth2/test/client_credentials_grant_test.dart index 062414d6f..9e27c9ed5 100644 --- a/pkgs/oauth2/test/client_credentials_grant_test.dart +++ b/pkgs/oauth2/test/client_credentials_grant_test.dart @@ -2,7 +2,7 @@ // 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. -@TestOn("vm") +@TestOn('vm') import 'dart:convert'; @@ -13,10 +13,10 @@ import 'package:test/test.dart'; import 'utils.dart'; final success = jsonEncode({ - "access_token": "2YotnFZFEjr1zCsicMWpAA", - "token_type": "bearer", - "expires_in": 3600, - "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA", + 'access_token': '2YotnFZFEjr1zCsicMWpAA', + 'token_type': 'bearer', + 'expires_in': 3600, + 'refresh_token': 'tGzv3JOkF0XG5Qx2TlKWIA', }); var auth = 'Basic Y2xpZW50OnNlY3JldA=='; @@ -24,7 +24,7 @@ var authEndpoint = Uri.parse('https://example.com'); void main() { var expectClient; - setUp(() => expectClient = new ExpectClient()); + setUp(() => expectClient = ExpectClient()); group('basic', () { test('builds correct request with client when using basic auth for client', @@ -32,7 +32,7 @@ void main() { expectClient.expectRequest((request) async { expect(auth, equals(request.headers['authorization'])); expect(request.bodyFields['grant_type'], equals('client_credentials')); - return new http.Response(success, 200, + return http.Response(success, 200, headers: {'content-type': 'application/json'}); }); @@ -50,7 +50,7 @@ void main() { expect(request.bodyFields['grant_type'], equals('client_credentials')); expect(request.bodyFields['client_id'], equals('client')); expect(request.bodyFields['client_secret'], equals('secret')); - return new http.Response(success, 200, + return http.Response(success, 200, headers: {'content-type': 'application/json'}); }); @@ -66,7 +66,7 @@ void main() { expect(auth, equals(request.headers['authorization'])); expect(request.bodyFields['grant_type'], equals('client_credentials')); expect(request.bodyFields['scope'], equals('one two')); - return new http.Response(success, 200, + return http.Response(success, 200, headers: {'content-type': 'application/json'}); }); @@ -81,7 +81,7 @@ void main() { expectClient.expectRequest((request) async { expect(request.bodyFields['grant_type'], equals('client_credentials')); expect(request.bodyFields['scope'], equals('one,two')); - return new http.Response(success, 200, + return http.Response(success, 200, headers: {'content-type': 'application/json'}); }); @@ -97,7 +97,7 @@ void main() { expect(request.bodyFields['client_id'], equals('client')); expect(request.bodyFields['client_secret'], equals('secret')); expect(request.url.queryParameters['query'], equals('value')); - return new http.Response(success, 200, + return http.Response(success, 200, headers: {'content-type': 'application/json'}); }); diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart index 1f13d58f9..11b725ca0 100644 --- a/pkgs/oauth2/test/client_test.dart +++ b/pkgs/oauth2/test/client_test.dart @@ -11,20 +11,20 @@ import 'package:test/test.dart'; import 'utils.dart'; -final Uri requestUri = Uri.parse("http://example.com/resource"); +final Uri requestUri = Uri.parse('http://example.com/resource'); final Uri tokenEndpoint = Uri.parse('http://example.com/token'); void main() { var httpClient; - setUp(() => httpClient = new ExpectClient()); + setUp(() => httpClient = ExpectClient()); group('with expired credentials', () { test("that can't be refreshed throws an ExpirationException on send", () { - var expiration = new DateTime.now().subtract(new Duration(hours: 1)); + var expiration = DateTime.now().subtract(Duration(hours: 1)); var credentials = - new oauth2.Credentials('access token', expiration: expiration); - var client = new oauth2.Client(credentials, + oauth2.Credentials('access token', expiration: expiration); + var client = oauth2.Client(credentials, identifier: 'identifier', secret: 'secret', httpClient: httpClient); expect(client.get(requestUri), @@ -32,20 +32,20 @@ void main() { }); test( - "that can be refreshed refreshes the credentials and sends the " - "request", () async { - var expiration = new DateTime.now().subtract(new Duration(hours: 1)); - var credentials = new oauth2.Credentials('access token', + 'that can be refreshed refreshes the credentials and sends the ' + 'request', () async { + var expiration = DateTime.now().subtract(Duration(hours: 1)); + var credentials = oauth2.Credentials('access token', refreshToken: 'refresh token', tokenEndpoint: tokenEndpoint, expiration: expiration); - var client = new oauth2.Client(credentials, + var client = oauth2.Client(credentials, identifier: 'identifier', secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { expect(request.method, equals('POST')); expect(request.url.toString(), equals(tokenEndpoint.toString())); - return new Future.value(new http.Response( + return Future.value(http.Response( jsonEncode( {'access_token': 'new access token', 'token_type': 'bearer'}), 200, @@ -58,22 +58,22 @@ void main() { expect(request.headers['authorization'], equals('Bearer new access token')); - return new Future.value(new http.Response('good job', 200)); + return Future.value(http.Response('good job', 200)); }); await client.read(requestUri); expect(client.credentials.accessToken, equals('new access token')); }); - test("that onCredentialsRefreshed is called", () async { + test('that onCredentialsRefreshed is called', () async { var callbackCalled = false; - var expiration = new DateTime.now().subtract(new Duration(hours: 1)); - var credentials = new oauth2.Credentials('access token', + var expiration = DateTime.now().subtract(Duration(hours: 1)); + var credentials = oauth2.Credentials('access token', refreshToken: 'refresh token', tokenEndpoint: tokenEndpoint, expiration: expiration); - var client = new oauth2.Client(credentials, + var client = oauth2.Client(credentials, identifier: 'identifier', secret: 'secret', httpClient: httpClient, onCredentialsRefreshed: (credentials) { @@ -82,7 +82,7 @@ void main() { }); httpClient.expectRequest((request) { - return new Future.value(new http.Response( + return Future.value(http.Response( jsonEncode( {'access_token': 'new access token', 'token_type': 'bearer'}), 200, @@ -90,7 +90,7 @@ void main() { }); httpClient.expectRequest((request) { - return new Future.value(new http.Response('good job', 200)); + return Future.value(http.Response('good job', 200)); }); await client.read(requestUri); @@ -99,9 +99,9 @@ void main() { }); group('with valid credentials', () { - test("sends a request with bearer authorization", () { - var credentials = new oauth2.Credentials('access token'); - var client = new oauth2.Client(credentials, + test('sends a request with bearer authorization', () { + var credentials = oauth2.Credentials('access token'); + var client = oauth2.Client(credentials, identifier: 'identifier', secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { @@ -109,22 +109,22 @@ void main() { expect(request.url.toString(), equals(requestUri.toString())); expect(request.headers['authorization'], equals('Bearer access token')); - return new Future.value(new http.Response('good job', 200)); + return Future.value(http.Response('good job', 200)); }); expect(client.read(requestUri), completion(equals('good job'))); }); - test("can manually refresh the credentials", () async { - var credentials = new oauth2.Credentials('access token', + test('can manually refresh the credentials', () async { + var credentials = oauth2.Credentials('access token', refreshToken: 'refresh token', tokenEndpoint: tokenEndpoint); - var client = new oauth2.Client(credentials, + var client = oauth2.Client(credentials, identifier: 'identifier', secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { expect(request.method, equals('POST')); expect(request.url.toString(), equals(tokenEndpoint.toString())); - return new Future.value(new http.Response( + return Future.value(http.Response( jsonEncode( {'access_token': 'new access token', 'token_type': 'bearer'}), 200, @@ -136,8 +136,8 @@ void main() { }); test("without a refresh token can't manually refresh the credentials", () { - var credentials = new oauth2.Credentials('access token'); - var client = new oauth2.Client(credentials, + var credentials = oauth2.Credentials('access token'); + var client = oauth2.Client(credentials, identifier: 'identifier', secret: 'secret', httpClient: httpClient); expect(client.refreshCredentials(), throwsA(isStateError)); @@ -146,8 +146,8 @@ void main() { group('with invalid credentials', () { test('throws an AuthorizationException for a 401 response', () { - var credentials = new oauth2.Credentials('access token'); - var client = new oauth2.Client(credentials, + var credentials = oauth2.Credentials('access token'); + var client = oauth2.Client(credentials, identifier: 'identifier', secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { @@ -157,7 +157,7 @@ void main() { var authenticate = 'Bearer error="invalid_token", error_description=' '"Something is terribly wrong."'; - return new Future.value(new http.Response('bad job', 401, + return Future.value(http.Response('bad job', 401, headers: {'www-authenticate': authenticate})); }); @@ -166,8 +166,8 @@ void main() { }); test('passes through a 401 response without www-authenticate', () async { - var credentials = new oauth2.Credentials('access token'); - var client = new oauth2.Client(credentials, + var credentials = oauth2.Credentials('access token'); + var client = oauth2.Client(credentials, identifier: 'identifier', secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { @@ -175,7 +175,7 @@ void main() { expect(request.url.toString(), equals(requestUri.toString())); expect(request.headers['authorization'], equals('Bearer access token')); - return new Future.value(new http.Response('bad job', 401)); + return Future.value(http.Response('bad job', 401)); }); expect((await client.get(requestUri)).statusCode, equals(401)); @@ -183,8 +183,8 @@ void main() { test('passes through a 401 response with invalid www-authenticate', () async { - var credentials = new oauth2.Credentials('access token'); - var client = new oauth2.Client(credentials, + var credentials = oauth2.Credentials('access token'); + var client = oauth2.Client(credentials, identifier: 'identifier', secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { @@ -194,7 +194,7 @@ void main() { var authenticate = 'Bearer error="invalid_token" error_description=' '"Something is terribly wrong."'; - return new Future.value(new http.Response('bad job', 401, + return Future.value(http.Response('bad job', 401, headers: {'www-authenticate': authenticate})); }); @@ -203,8 +203,8 @@ void main() { test('passes through a 401 response with non-bearer www-authenticate', () async { - var credentials = new oauth2.Credentials('access token'); - var client = new oauth2.Client(credentials, + var credentials = oauth2.Credentials('access token'); + var client = oauth2.Client(credentials, identifier: 'identifier', secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { @@ -212,7 +212,7 @@ void main() { expect(request.url.toString(), equals(requestUri.toString())); expect(request.headers['authorization'], equals('Bearer access token')); - return new Future.value(new http.Response('bad job', 401, + return Future.value(http.Response('bad job', 401, headers: {'www-authenticate': 'Digest'})); }); @@ -221,8 +221,8 @@ void main() { test('passes through a 401 response with non-OAuth2 www-authenticate', () async { - var credentials = new oauth2.Credentials('access token'); - var client = new oauth2.Client(credentials, + var credentials = oauth2.Credentials('access token'); + var client = oauth2.Client(credentials, identifier: 'identifier', secret: 'secret', httpClient: httpClient); httpClient.expectRequest((request) { @@ -230,7 +230,7 @@ void main() { expect(request.url.toString(), equals(requestUri.toString())); expect(request.headers['authorization'], equals('Bearer access token')); - return new Future.value(new http.Response('bad job', 401, + return Future.value(http.Response('bad job', 401, headers: {'www-authenticate': 'Bearer'})); }); diff --git a/pkgs/oauth2/test/credentials_test.dart b/pkgs/oauth2/test/credentials_test.dart index 386f94892..1b3caa5e0 100644 --- a/pkgs/oauth2/test/credentials_test.dart +++ b/pkgs/oauth2/test/credentials_test.dart @@ -15,30 +15,30 @@ final Uri tokenEndpoint = Uri.parse('http://example.com/token'); void main() { var httpClient; - setUp(() => httpClient = new ExpectClient()); + setUp(() => httpClient = ExpectClient()); test('is not expired if no expiration exists', () { - var credentials = new oauth2.Credentials('access token'); + var credentials = oauth2.Credentials('access token'); expect(credentials.isExpired, isFalse); }); test('is not expired if the expiration is in the future', () { - var expiration = new DateTime.now().add(new Duration(hours: 1)); + var expiration = DateTime.now().add(Duration(hours: 1)); var credentials = - new oauth2.Credentials('access token', expiration: expiration); + oauth2.Credentials('access token', expiration: expiration); expect(credentials.isExpired, isFalse); }); test('is expired if the expiration is in the past', () { - var expiration = new DateTime.now().subtract(new Duration(hours: 1)); + var expiration = DateTime.now().subtract(Duration(hours: 1)); var credentials = - new oauth2.Credentials('access token', expiration: expiration); + oauth2.Credentials('access token', expiration: expiration); expect(credentials.isExpired, isTrue); }); test("can't refresh without a refresh token", () { var credentials = - new oauth2.Credentials('access token', tokenEndpoint: tokenEndpoint); + oauth2.Credentials('access token', tokenEndpoint: tokenEndpoint); expect(credentials.canRefresh, false); expect( @@ -49,7 +49,7 @@ void main() { test("can't refresh without a token endpoint", () { var credentials = - new oauth2.Credentials('access token', refreshToken: 'refresh token'); + oauth2.Credentials('access token', refreshToken: 'refresh token'); expect(credentials.canRefresh, false); expect( @@ -58,8 +58,8 @@ void main() { throwsStateError); }); - test("can refresh with a refresh token and a token endpoint", () async { - var credentials = new oauth2.Credentials('access token', + test('can refresh with a refresh token and a token endpoint', () async { + var credentials = oauth2.Credentials('access token', refreshToken: 'refresh token', tokenEndpoint: tokenEndpoint, scopes: ['scope1', 'scope2']); @@ -71,16 +71,16 @@ void main() { expect( request.bodyFields, equals({ - "grant_type": "refresh_token", - "refresh_token": "refresh token", - "scope": "scope1 scope2" + 'grant_type': 'refresh_token', + 'refresh_token': 'refresh token', + 'scope': 'scope1 scope2' })); expect( request.headers, - containsPair("Authorization", - "Basic aWQlQzMlQUJudCVDNCVBQmZpZXI6cyVDMyVBQmNyZXQ=")); + containsPair('Authorization', + 'Basic aWQlQzMlQUJudCVDNCVBQmZpZXI6cyVDMyVBQmNyZXQ=')); - return new Future.value(new http.Response( + return Future.value(http.Response( jsonEncode({ 'access_token': 'new access token', 'token_type': 'bearer', @@ -97,14 +97,14 @@ void main() { }); test('sets proper scope string when using custom delimiter', () async { - var credentials = new oauth2.Credentials('access token', + var credentials = oauth2.Credentials('access token', refreshToken: 'refresh token', tokenEndpoint: tokenEndpoint, scopes: ['scope1', 'scope2'], delimiter: ','); httpClient.expectRequest((http.Request request) { expect(request.bodyFields['scope'], equals('scope1,scope2')); - return new Future.value(new http.Response( + return Future.value(http.Response( jsonEncode({ 'access_token': 'new access token', 'token_type': 'bearer', @@ -117,8 +117,8 @@ void main() { identifier: 'idëntīfier', secret: 'sëcret', httpClient: httpClient); }); - test("can refresh without a client secret", () async { - var credentials = new oauth2.Credentials('access token', + test('can refresh without a client secret', () async { + var credentials = oauth2.Credentials('access token', refreshToken: 'refresh token', tokenEndpoint: tokenEndpoint, scopes: ['scope1', 'scope2']); @@ -130,13 +130,13 @@ void main() { expect( request.bodyFields, equals({ - "grant_type": "refresh_token", - "refresh_token": "refresh token", - "scope": "scope1 scope2", - "client_id": "identifier" + 'grant_type': 'refresh_token', + 'refresh_token': 'refresh token', + 'scope': 'scope1 scope2', + 'client_id': 'identifier' })); - return new Future.value(new http.Response( + return Future.value(http.Response( jsonEncode({ 'access_token': 'new access token', 'token_type': 'bearer', @@ -152,8 +152,8 @@ void main() { expect(credentials.refreshToken, equals('new refresh token')); }); - test("can refresh without client authentication", () async { - var credentials = new oauth2.Credentials('access token', + test('can refresh without client authentication', () async { + var credentials = oauth2.Credentials('access token', refreshToken: 'refresh token', tokenEndpoint: tokenEndpoint, scopes: ['scope1', 'scope2']); @@ -165,12 +165,12 @@ void main() { expect( request.bodyFields, equals({ - "grant_type": "refresh_token", - "refresh_token": "refresh token", - "scope": "scope1 scope2" + 'grant_type': 'refresh_token', + 'refresh_token': 'refresh token', + 'scope': 'scope1 scope2' })); - return new Future.value(new http.Response( + return Future.value(http.Response( jsonEncode({ 'access_token': 'new access token', 'token_type': 'bearer', @@ -186,7 +186,7 @@ void main() { }); test("uses the old refresh token if a new one isn't provided", () async { - var credentials = new oauth2.Credentials('access token', + var credentials = oauth2.Credentials('access token', refreshToken: 'refresh token', tokenEndpoint: tokenEndpoint); expect(credentials.canRefresh, true); @@ -196,15 +196,15 @@ void main() { expect( request.bodyFields, equals({ - "grant_type": "refresh_token", - "refresh_token": "refresh token" + 'grant_type': 'refresh_token', + 'refresh_token': 'refresh token' })); expect( request.headers, - containsPair("Authorization", - "Basic aWQlQzMlQUJudCVDNCVBQmZpZXI6cyVDMyVBQmNyZXQ=")); + containsPair('Authorization', + 'Basic aWQlQzMlQUJudCVDNCVBQmZpZXI6cyVDMyVBQmNyZXQ=')); - return new Future.value(new http.Response( + return Future.value(http.Response( jsonEncode( {'access_token': 'new access token', 'token_type': 'bearer'}), 200, @@ -217,8 +217,8 @@ void main() { expect(credentials.refreshToken, equals('refresh token')); }); - test("uses form-field authentication if basicAuth is false", () async { - var credentials = new oauth2.Credentials('access token', + test('uses form-field authentication if basicAuth is false', () async { + var credentials = oauth2.Credentials('access token', refreshToken: 'refresh token', tokenEndpoint: tokenEndpoint, scopes: ['scope1', 'scope2']); @@ -230,14 +230,14 @@ void main() { expect( request.bodyFields, equals({ - "grant_type": "refresh_token", - "refresh_token": "refresh token", - "scope": "scope1 scope2", - "client_id": "idëntīfier", - "client_secret": "sëcret" + 'grant_type': 'refresh_token', + 'refresh_token': 'refresh token', + 'scope': 'scope1 scope2', + 'client_id': 'idëntīfier', + 'client_secret': 'sëcret' })); - return new Future.value(new http.Response( + return Future.value(http.Response( jsonEncode({ 'access_token': 'new access token', 'token_type': 'bearer', @@ -256,25 +256,25 @@ void main() { expect(credentials.refreshToken, equals('new refresh token')); }); - group("fromJson", () { + group('fromJson', () { oauth2.Credentials fromMap(Map map) => - new oauth2.Credentials.fromJson(jsonEncode(map)); + oauth2.Credentials.fromJson(jsonEncode(map)); - test("should load the same credentials from toJson", () { + test('should load the same credentials from toJson', () { // Round the expiration down to milliseconds since epoch, since that's // what the credentials file stores. Otherwise sub-millisecond time gets // in the way. - var expiration = new DateTime.now().subtract(new Duration(hours: 1)); - expiration = new DateTime.fromMillisecondsSinceEpoch( + var expiration = DateTime.now().subtract(Duration(hours: 1)); + expiration = DateTime.fromMillisecondsSinceEpoch( expiration.millisecondsSinceEpoch); - var credentials = new oauth2.Credentials('access token', + var credentials = oauth2.Credentials('access token', refreshToken: 'refresh token', idToken: 'id token', tokenEndpoint: tokenEndpoint, scopes: ['scope1', 'scope2'], expiration: expiration); - var reloaded = new oauth2.Credentials.fromJson(credentials.toJson()); + var reloaded = oauth2.Credentials.fromJson(credentials.toJson()); expect(reloaded.accessToken, equals(credentials.accessToken)); expect(reloaded.refreshToken, equals(credentials.refreshToken)); @@ -285,46 +285,45 @@ void main() { expect(reloaded.expiration, equals(credentials.expiration)); }); - test("should throw a FormatException for invalid JSON", () { - expect(() => new oauth2.Credentials.fromJson("foo bar"), - throwsFormatException); + test('should throw a FormatException for invalid JSON', () { + expect( + () => oauth2.Credentials.fromJson('foo bar'), throwsFormatException); }); test("should throw a FormatException for JSON that's not a map", () { - expect( - () => new oauth2.Credentials.fromJson("null"), throwsFormatException); + expect(() => oauth2.Credentials.fromJson('null'), throwsFormatException); }); - test("should throw a FormatException if there is no accessToken", () { + test('should throw a FormatException if there is no accessToken', () { expect(() => fromMap({}), throwsFormatException); }); - test("should throw a FormatException if accessToken is not a string", () { - expect(() => fromMap({"accessToken": 12}), throwsFormatException); + test('should throw a FormatException if accessToken is not a string', () { + expect(() => fromMap({'accessToken': 12}), throwsFormatException); }); - test("should throw a FormatException if refreshToken is not a string", () { - expect(() => fromMap({"accessToken": "foo", "refreshToken": 12}), + test('should throw a FormatException if refreshToken is not a string', () { + expect(() => fromMap({'accessToken': 'foo', 'refreshToken': 12}), throwsFormatException); }); - test("should throw a FormatException if idToken is not a string", () { - expect(() => fromMap({"accessToken": "foo", "idToken": 12}), + test('should throw a FormatException if idToken is not a string', () { + expect(() => fromMap({'accessToken': 'foo', 'idToken': 12}), throwsFormatException); }); - test("should throw a FormatException if tokenEndpoint is not a string", () { - expect(() => fromMap({"accessToken": "foo", "tokenEndpoint": 12}), + test('should throw a FormatException if tokenEndpoint is not a string', () { + expect(() => fromMap({'accessToken': 'foo', 'tokenEndpoint': 12}), throwsFormatException); }); - test("should throw a FormatException if scopes is not a list", () { - expect(() => fromMap({"accessToken": "foo", "scopes": 12}), + test('should throw a FormatException if scopes is not a list', () { + expect(() => fromMap({'accessToken': 'foo', 'scopes': 12}), throwsFormatException); }); - test("should throw a FormatException if expiration is not an int", () { - expect(() => fromMap({"accessToken": "foo", "expiration": "12"}), + test('should throw a FormatException if expiration is not an int', () { + expect(() => fromMap({'accessToken': 'foo', 'expiration': '12'}), throwsFormatException); }); }); diff --git a/pkgs/oauth2/test/handle_access_token_response_test.dart b/pkgs/oauth2/test/handle_access_token_response_test.dart index 1556164e8..10aef48f6 100644 --- a/pkgs/oauth2/test/handle_access_token_response_test.dart +++ b/pkgs/oauth2/test/handle_access_token_response_test.dart @@ -13,14 +13,14 @@ import 'package:oauth2/src/parameters.dart'; import 'utils.dart'; -final Uri tokenEndpoint = Uri.parse("https://example.com/token"); +final Uri tokenEndpoint = Uri.parse('https://example.com/token'); -final DateTime startTime = new DateTime.now(); +final DateTime startTime = DateTime.now(); oauth2.Credentials handle(http.Response response, {GetParameters getParameters}) => handleAccessTokenResponse( - response, tokenEndpoint, startTime, ["scope"], ' ', + response, tokenEndpoint, startTime, ['scope'], ' ', getParameters: getParameters); void main() { @@ -29,9 +29,9 @@ void main() { {String body = '{"error": "invalid_request"}', int statusCode = 400, Map headers = const { - "content-type": "application/json" + 'content-type': 'application/json' }}) => - handle(new http.Response(body, statusCode, headers: headers)); + handle(http.Response(body, statusCode, headers: headers)); test('causes an AuthorizationException', () { expect(() => handleError(), throwsAuthorizationException); @@ -81,14 +81,14 @@ void main() { expect( () => handleError( body: jsonEncode( - {"error": "invalid_request", "error_description": 12})), + {'error': 'invalid_request', 'error_description': 12})), throwsFormatException); }); test('with a non-string error_uri causes a FormatException', () { expect( () => handleError( - body: jsonEncode({"error": "invalid_request", "error_uri": 12})), + body: jsonEncode({'error': 'invalid_request', 'error_uri': 12})), throwsFormatException); }); @@ -96,8 +96,8 @@ void main() { expect( () => handleError( body: jsonEncode({ - "error": "invalid_request", - "error_description": "description" + 'error': 'invalid_request', + 'error_description': 'description' })), throwsAuthorizationException); }); @@ -106,8 +106,8 @@ void main() { expect( () => handleError( body: jsonEncode({ - "error": "invalid_request", - "error_uri": "http://example.com/error" + 'error': 'invalid_request', + 'error_uri': 'http://example.com/error' })), throwsAuthorizationException); }); @@ -115,13 +115,13 @@ void main() { group('a success response', () { oauth2.Credentials handleSuccess( - {String contentType = "application/json", + {String contentType = 'application/json', accessToken = 'access token', tokenType = 'bearer', expiresIn, refreshToken, scope}) { - return handle(new http.Response( + return handle(http.Response( jsonEncode({ 'access_token': accessToken, 'token_type': tokenType, @@ -166,7 +166,7 @@ void main() { var body = '_' + jsonEncode({'token_type': 'bearer', 'access_token': 'access token'}); var credentials = handle( - new http.Response(body, 200, headers: {'content-type': 'text/plain'}), + http.Response(body, 200, headers: {'content-type': 'text/plain'}), getParameters: (contentType, body) => jsonDecode(body.substring(1))); expect(credentials.accessToken, equals('access token')); expect(credentials.tokenEndpoint.toString(), @@ -175,7 +175,7 @@ void main() { test('throws a FormatException if custom getParameters rejects response', () { - var response = new http.Response( + var response = http.Response( jsonEncode({ 'access_token': 'access token', 'token_type': 'bearer', @@ -188,7 +188,7 @@ void main() { expect( () => handle(response, - getParameters: (contentType, body) => throw new FormatException( + getParameters: (contentType, body) => throw FormatException( 'unsupported content-type: $contentType')), throwsFormatException); }); @@ -210,11 +210,11 @@ void main() { }); test('with a non-"bearer" token type throws a FormatException', () { - expect(() => handleSuccess(tokenType: "mac"), throwsFormatException); + expect(() => handleSuccess(tokenType: 'mac'), throwsFormatException); }); test('with a non-int expires-in throws a FormatException', () { - expect(() => handleSuccess(expiresIn: "whenever"), throwsFormatException); + expect(() => handleSuccess(expiresIn: 'whenever'), throwsFormatException); }); test( @@ -230,8 +230,8 @@ void main() { }); test('with a refresh token sets the refresh token', () { - var credentials = handleSuccess(refreshToken: "refresh me"); - expect(credentials.refreshToken, equals("refresh me")); + var credentials = handleSuccess(refreshToken: 'refresh me'); + expect(credentials.refreshToken, equals('refresh me')); }); test('with a non-string scope throws a FormatException', () { @@ -239,12 +239,12 @@ void main() { }); test('with a scope sets the scopes', () { - var credentials = handleSuccess(scope: "scope1 scope2"); - expect(credentials.scopes, equals(["scope1", "scope2"])); + var credentials = handleSuccess(scope: 'scope1 scope2'); + expect(credentials.scopes, equals(['scope1', 'scope2'])); }); test('with a custom scope delimiter sets the scopes', () { - var response = new http.Response( + var response = http.Response( jsonEncode({ 'access_token': 'access token', 'token_type': 'bearer', @@ -262,13 +262,13 @@ void main() { group('a success response with a id_token', () { oauth2.Credentials handleSuccess( - {String contentType = "application/json", + {String contentType = 'application/json', accessToken = 'access token', tokenType = 'bearer', expiresIn, idToken = 'decode me', scope}) { - return handle(new http.Response( + return handle(http.Response( jsonEncode({ 'access_token': accessToken, 'token_type': tokenType, @@ -285,8 +285,8 @@ void main() { }); test('with a id token sets the id token', () { - var credentials = handleSuccess(idToken: "decode me"); - expect(credentials.idToken, equals("decode me")); + var credentials = handleSuccess(idToken: 'decode me'); + expect(credentials.idToken, equals('decode me')); }); }); } diff --git a/pkgs/oauth2/test/resource_owner_password_grant_test.dart b/pkgs/oauth2/test/resource_owner_password_grant_test.dart index acd76d12b..c9afb6511 100644 --- a/pkgs/oauth2/test/resource_owner_password_grant_test.dart +++ b/pkgs/oauth2/test/resource_owner_password_grant_test.dart @@ -2,7 +2,7 @@ // 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. -@TestOn("vm") +@TestOn('vm') import 'dart:convert'; import 'dart:async'; @@ -13,10 +13,10 @@ import 'package:test/test.dart'; import 'utils.dart'; final success = jsonEncode({ - "access_token": "2YotnFZFEjr1zCsicMWpAA", - "token_type": "bearer", - "expires_in": 3600, - "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA", + 'access_token': '2YotnFZFEjr1zCsicMWpAA', + 'token_type': 'bearer', + 'expires_in': 3600, + 'refresh_token': 'tGzv3JOkF0XG5Qx2TlKWIA', }); var auth = 'Basic Y2xpZW50OnNlY3JldA=='; @@ -24,7 +24,7 @@ var authEndpoint = Uri.parse('https://example.com'); void main() { var expectClient; - setUp(() => expectClient = new ExpectClient()); + setUp(() => expectClient = ExpectClient()); group('basic', () { test('builds correct request with client when using basic auth for client', @@ -34,7 +34,7 @@ void main() { expect(request.bodyFields['grant_type'], equals('password')); expect(request.bodyFields['username'], equals('username')); expect(request.bodyFields['password'], equals('userpass')); - return new http.Response(success, 200, + return http.Response(success, 200, headers: {'content-type': 'application/json'}); }); @@ -48,12 +48,12 @@ void main() { test('passes the onCredentialsRefreshed callback to the client', () async { expectClient.expectRequest((request) async { - return new http.Response( + return http.Response( jsonEncode({ - "access_token": "2YotnFZFEjr1zCsicMWpAA", - "token_type": "bearer", - "expires_in": -3600, - "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA", + 'access_token': '2YotnFZFEjr1zCsicMWpAA', + 'token_type': 'bearer', + 'expires_in': -3600, + 'refresh_token': 'tGzv3JOkF0XG5Qx2TlKWIA', }), 200, headers: {'content-type': 'application/json'}); @@ -69,7 +69,7 @@ void main() { }); expectClient.expectRequest((request) { - return new Future.value(new http.Response( + return Future.value(http.Response( jsonEncode( {'access_token': 'new access token', 'token_type': 'bearer'}), 200, @@ -77,10 +77,10 @@ void main() { }); expectClient.expectRequest((request) { - return new Future.value(new http.Response('good job', 200)); + return Future.value(http.Response('good job', 200)); }); - await client.read(Uri.parse("http://example.com/resource")); + await client.read(Uri.parse('http://example.com/resource')); expect(isCallbackInvoked, equals(true)); }); @@ -92,7 +92,7 @@ void main() { expect(request.bodyFields['client_secret'], equals('secret')); expect(request.bodyFields['username'], equals('username')); expect(request.bodyFields['password'], equals('userpass')); - return new http.Response(success, 200, + return http.Response(success, 200, headers: {'content-type': 'application/json'}); }); @@ -112,7 +112,7 @@ void main() { expect(request.bodyFields['username'], equals('username')); expect(request.bodyFields['password'], equals('userpass')); expect(request.bodyFields['scope'], equals('one two')); - return new http.Response(success, 200, + return http.Response(success, 200, headers: {'content-type': 'application/json'}); }); @@ -129,7 +129,7 @@ void main() { expect(request.bodyFields['username'], equals('username')); expect(request.bodyFields['password'], equals('userpass')); expect(request.bodyFields['scope'], equals('one,two')); - return new http.Response(success, 200, + return http.Response(success, 200, headers: {'content-type': 'application/json'}); }); @@ -148,7 +148,7 @@ void main() { expect(request.bodyFields['username'], equals('username')); expect(request.bodyFields['password'], equals('userpass')); expect(request.url.queryParameters['query'], equals('value')); - return new http.Response(success, 200, + return http.Response(success, 200, headers: {'content-type': 'application/json'}); }); diff --git a/pkgs/oauth2/test/utils.dart b/pkgs/oauth2/test/utils.dart index 64d1eb8fd..528673c0f 100644 --- a/pkgs/oauth2/test/utils.dart +++ b/pkgs/oauth2/test/utils.dart @@ -14,17 +14,17 @@ class ExpectClient extends MockClient { final Queue _handlers; ExpectClient._(MockClientHandler fn) - : _handlers = new Queue(), + : _handlers = Queue(), super(fn); factory ExpectClient() { var client; - client = new ExpectClient._((request) => client._handleRequest(request)); + client = ExpectClient._((request) => client._handleRequest(request)); return client; } void expectRequest(MockClientHandler fn) { - var completer = new Completer(); + var completer = Completer(); expect(completer.future, completes); _handlers.add((request) { @@ -35,7 +35,7 @@ class ExpectClient extends MockClient { Future _handleRequest(http.Request request) { if (_handlers.isEmpty) { - return new Future.value(new http.Response('not found', 404)); + return Future.value(http.Response('not found', 404)); } else { return _handlers.removeFirst()(request); } From 1b94f300e56a805b1e606c59dd1844651e231822 Mon Sep 17 00:00:00 2001 From: Levi Hassel Date: Mon, 27 Apr 2020 04:12:26 -0500 Subject: [PATCH 111/159] Add imaginary function examples and OAuth documentation links to README (dart-lang/oauth2#72) --- pkgs/oauth2/README.md | 86 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 71 insertions(+), 15 deletions(-) diff --git a/pkgs/oauth2/README.md b/pkgs/oauth2/README.md index 567d73fcd..98df16af2 100644 --- a/pkgs/oauth2/README.md +++ b/pkgs/oauth2/README.md @@ -12,16 +12,10 @@ client has permission to access resources on behalf of the resource owner. OAuth2 provides several different methods for the client to obtain authorization. At the time of writing, this library only supports the -[AuthorizationCodeGrant][], [clientCredentialsGrant][] and [resourceOwnerPasswordGrant][] methods, but -further methods may be added in the future. The following example uses this -method to authenticate, and assumes that the library is being used by a -server-side application. - -[AuthorizationCodeGrant]: http://www.dartdocs.org/documentation/oauth2/latest/index.html#oauth2/oauth2.AuthorizationCodeGrant -[clientCredentialsGrant]: http://www.dartdocs.org/documentation/oauth2/latest/index.html#oauth2/oauth2.clientCredentialsGrant -[resourceOwnerPasswordGrant]: http://www.dartdocs.org/documentation/oauth2/latest/index.html#oauth2/oauth2.resourceOwnerPasswordGrant +[Authorization Code Grant][authorizationCodeGrantSection], [Client Credentials Grant][clientCredentialsGrantSection] and [Resource Owner Password Grant][resourceOwnerPasswordGrantSection] flows, but more may be added in the future. ## Authorization Code Grant +**Resources:** [Class summary][authorizationCodeGrantMethod], [OAuth documentation][authorizationCodeGrantDocs] ```dart import 'dart:io'; @@ -79,22 +73,25 @@ Future getClient() async { identifier, authorizationEndpoint, tokenEndpoint, secret: secret); - // Redirect the resource owner to the authorization URL. This will be a URL on - // the authorization server (authorizationEndpoint with some additional query - // parameters). Once the resource owner has authorized, they'll be redirected - // to `redirectUrl` with an authorization code. + // A URL on the authorization server (authorizationEndpoint with some additional + // query parameters). Scopes and state can optionally be passed into this method. + var authorizationUrl = grant.getAuthorizationUrl(redirectUrl); + + // Redirect the resource owner to the authorization URL. Once the resource + // owner has authorized, they'll be redirected to `redirectUrl` with an + // authorization code. // // `redirect` is an imaginary function that redirects the resource // owner's browser. - await redirect(grant.getAuthorizationUrl(redirectUrl)); + await redirect(authorizationUrl); // Another imaginary function that listens for a request to `redirectUrl`. - var request = await listen(redirectUrl); + var responseUrl = await listen(redirectUrl); // Once the user is redirected to `redirectUrl`, pass the query parameters to // the AuthorizationCodeGrant. It will validate them and extract the // authorization code to create a new Client. - return await grant.handleAuthorizationResponse(request.uri.queryParameters); + return await grant.handleAuthorizationResponse(responseUrl.queryParameters); } main() async { @@ -113,7 +110,52 @@ main() async { } ``` +
+ Click here to learn how to implement the imaginary functions mentioned above. + + ----- + + Unfortunately, there's not a universal example for implementing the imaginary functions, `redirect` and `listen`, because different options exist for each platform. + + For Flutter apps, there's two popular approaches: + 1. Launch a browser using [url_launcher][] and listen for a redirect using [uni_links][]. + ```dart + if (await canLaunch(authorizationUrl.toString())) { + await launch(authorizationUrl.toString()); + } + + ... + + final linksStream = getLinksStream().listen((Uri uri) async { + if (uri.toString().startsWith(redirectUrl)) { + responseUrl = uri; + } + }); + ``` + + 2. Launch a WebView inside the app and listen for a redirect using [webview_flutter][]. + ```dart + WebView( + javascriptMode: JavascriptMode.unrestricted, + initialUrl: authorizationUrl.toString(), + navigationDelegate: (navReq) { + if (navReq.url.startsWith(redirectUrl)) { + responseUrl = Uri.parse(navReq.url); + return NavigationDecision.prevent; + } + + return NavigationDecision.navigate; + }, + ... + ); + ``` + + For Dart apps, the best approach depends on the available options for accessing a browser. In general, you'll need to launch the authorization URL through the client's browser and listen for the redirect URL. +
+ ## Client Credentials Grant +**Resources:** [Method summary][clientCredentialsGrantMethod], [OAuth documentation][clientCredentialsGrantDocs] + ```dart // This URL is an endpoint that's provided by the authorization server. It's // usually included in the server's documentation of its OAuth2 API. @@ -149,6 +191,7 @@ await credentialsFile.writeAsString(client.credentials.toJson()); ``` ## Resource Owner Password Grant +**Resources:** [Method summary][resourceOwnerPasswordGrantMethod], [OAuth documentation][resourceOwnerPasswordGrantDocs] ```dart // This URL is an endpoint that's provided by the authorization server. It's @@ -185,3 +228,16 @@ var result = await client.read("http://example.com/protected-resources.txt"); new File("~/.myapp/credentials.json") .writeAsString(client.credentials.toJson()); ``` + +[authorizationCodeGrantDocs]: https://oauth.net/2/grant-types/authorization-code/ +[authorizationCodeGrantMethod]: https://pub.dev/documentation/oauth2/latest/oauth2/AuthorizationCodeGrant-class.html +[authorizationCodeGrantSection]: #authorization-code-grant +[clientCredentialsGrantDocs]: https://oauth.net/2/grant-types/client-credentials/ +[clientCredentialsGrantMethod]: https://pub.dev/documentation/oauth2/latest/oauth2/clientCredentialsGrant.html +[clientCredentialsGrantSection]: #client-credentials-grant +[resourceOwnerPasswordGrantDocs]: https://oauth.net/2/grant-types/password/ +[resourceOwnerPasswordGrantMethod]: https://pub.dev/documentation/oauth2/latest/oauth2/resourceOwnerPasswordGrant.html +[resourceOwnerPasswordGrantSection]: #resource-owner-password-grant +[uni_links]: https://pub.dev/packages/uni_links +[url_launcher]: https://pub.dev/packages/url_launcher +[webview_flutter]: https://pub.dev/packages/webview_flutter From 09f0fdbc36abbcc0c00cc3ce493094bd2c179c53 Mon Sep 17 00:00:00 2001 From: Jonas Finnemann Jensen Date: Mon, 27 Apr 2020 11:13:32 +0200 Subject: [PATCH 112/159] Release 1.6.0 (dart-lang/oauth2#70) --- pkgs/oauth2/CHANGELOG.md | 4 ++++ pkgs/oauth2/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md index 6de1d4b29..4f2809dd4 100644 --- a/pkgs/oauth2/CHANGELOG.md +++ b/pkgs/oauth2/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.6.0 + +* Added PKCE support to `AuthorizationCodeGrant`. + # 1.5.0 * Added support for `clientCredentialsGrant`. diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index 0b3d3f2de..db168e3bd 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,5 @@ name: oauth2 -version: 1.5.0 +version: 1.6.0 author: Dart Team homepage: https://github.com/dart-lang/oauth2 description: >- From 1d22d6b91bd3695714061284d29da7605581f0c9 Mon Sep 17 00:00:00 2001 From: Pieter van Loon Date: Mon, 8 Jun 2020 18:15:29 +0200 Subject: [PATCH 113/159] Refresh credentials once (dart-lang/oauth2#73) * Made sure refreshCredentials is only running once when multiple calls are made * Added unit test * Added line to changelog * Updated version in pubspec --- pkgs/oauth2/CHANGELOG.md | 4 ++++ pkgs/oauth2/lib/src/client.dart | 31 +++++++++++++++++------- pkgs/oauth2/pubspec.yaml | 2 +- pkgs/oauth2/test/client_test.dart | 40 +++++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 9 deletions(-) diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md index 4f2809dd4..21b211c43 100644 --- a/pkgs/oauth2/CHANGELOG.md +++ b/pkgs/oauth2/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.6.1 + +* Added fix to make sure that credentials are only refreshed once when multiple calls are made. + # 1.6.0 * Added PKCE support to `AuthorizationCodeGrant`. diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart index d33aaa0d4..7a78aa37f 100644 --- a/pkgs/oauth2/lib/src/client.dart +++ b/pkgs/oauth2/lib/src/client.dart @@ -135,6 +135,9 @@ class Client extends http.BaseClient { params['error_uri'] == null ? null : Uri.parse(params['error_uri'])); } + /// A [Future] used to track whether [refreshCredentials] is running. + Future _refreshingFuture; + /// Explicitly refreshes this client's credentials. Returns this client. /// /// This will throw a [StateError] if the [Credentials] can't be refreshed, an @@ -151,14 +154,26 @@ class Client extends http.BaseClient { throw StateError("$prefix can't be refreshed."); } - _credentials = await credentials.refresh( - identifier: identifier, - secret: secret, - newScopes: newScopes, - basicAuth: _basicAuth, - httpClient: _httpClient); - - if (_onCredentialsRefreshed != null) _onCredentialsRefreshed(_credentials); + // To make sure that only one refresh happens when credentials are expired + // we track it using the [_refreshingFuture]. And also make sure that the + // _onCredentialsRefreshed callback is only called once. + if (_refreshingFuture == null) { + try { + _refreshingFuture = credentials.refresh( + identifier: identifier, + secret: secret, + newScopes: newScopes, + basicAuth: _basicAuth, + httpClient: _httpClient, + ); + _credentials = await _refreshingFuture; + _onCredentialsRefreshed?.call(_credentials); + } finally { + _refreshingFuture = null; + } + } else { + await _refreshingFuture; + } return this; } diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index db168e3bd..4648628ab 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,5 @@ name: oauth2 -version: 1.6.0 +version: 1.6.1 author: Dart Team homepage: https://github.com/dart-lang/oauth2 description: >- diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart index 11b725ca0..5960cbd75 100644 --- a/pkgs/oauth2/test/client_test.dart +++ b/pkgs/oauth2/test/client_test.dart @@ -65,6 +65,46 @@ void main() { expect(client.credentials.accessToken, equals('new access token')); }); + test( + 'that can be refreshed refreshes only once if multiple requests are made', + () async { + var expiration = DateTime.now().subtract(Duration(hours: 1)); + var credentials = oauth2.Credentials('access token', + refreshToken: 'refresh token', + tokenEndpoint: tokenEndpoint, + expiration: expiration); + var client = oauth2.Client(credentials, + identifier: 'identifier', secret: 'secret', httpClient: httpClient); + + httpClient.expectRequest((request) { + expect(request.method, equals('POST')); + expect(request.url.toString(), equals(tokenEndpoint.toString())); + return Future.value(http.Response( + jsonEncode( + {'access_token': 'new access token', 'token_type': 'bearer'}), + 200, + headers: {'content-type': 'application/json'})); + }); + + final numCalls = 2; + + for (var i = 0; i < numCalls; i++) { + httpClient.expectRequest((request) { + expect(request.method, equals('GET')); + expect(request.url.toString(), equals(requestUri.toString())); + expect(request.headers['authorization'], + equals('Bearer new access token')); + + return Future.value(http.Response('good job', 200)); + }); + } + + await Future.wait( + List.generate(numCalls, (_) => client.read(requestUri))); + + expect(client.credentials.accessToken, equals('new access token')); + }); + test('that onCredentialsRefreshed is called', () async { var callbackCalled = false; From e25ff2d491060acbce15cfe20401b9e389f6a283 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Mon, 20 Jul 2020 14:16:16 -0700 Subject: [PATCH 114/159] Improve README style (dart-lang/oauth2#80) - Reflow to a more consistent 80 columns. - Update Dart code for style - use single quotes, format, drop `new`. - Rename a function to avoid leading "get". - Update phrasing about "imaginary" functions - make it clear that these are omitted implementation details. - Copy the code from the main block into `example/main.dart` to make it easier to work with and improve the pub score. - Remove unused `author` field from pubspec. --- pkgs/oauth2/CHANGELOG.md | 2 + pkgs/oauth2/README.md | 185 ++++++++++++++++++---------------- pkgs/oauth2/example/main.dart | 95 +++++++++++++++++ pkgs/oauth2/pubspec.yaml | 3 +- 4 files changed, 196 insertions(+), 89 deletions(-) create mode 100644 pkgs/oauth2/example/main.dart diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md index 21b211c43..28cc6e4d7 100644 --- a/pkgs/oauth2/CHANGELOG.md +++ b/pkgs/oauth2/CHANGELOG.md @@ -1,3 +1,5 @@ +# 1.6.2-dev + # 1.6.1 * Added fix to make sure that credentials are only refreshed once when multiple calls are made. diff --git a/pkgs/oauth2/README.md b/pkgs/oauth2/README.md index 98df16af2..a536740a3 100644 --- a/pkgs/oauth2/README.md +++ b/pkgs/oauth2/README.md @@ -1,33 +1,38 @@ -A client library for authenticating with a remote service via OAuth2 on -behalf of a user, and making authorized HTTP requests with the user's OAuth2 +A client library for authenticating with a remote service via OAuth2 on behalf +of a user, and making authorized HTTP requests with the user's OAuth2 credentials. -OAuth2 allows a client (the program using this library) to access and -manipulate a resource that's owned by a resource owner (the end user) and -lives on a remote server. The client directs the resource owner to an -authorization server (usually but not always the same as the server that -hosts the resource), where the resource owner tells the authorization server -to give the client an access token. This token serves as proof that the -client has permission to access resources on behalf of the resource owner. +OAuth2 allows a client (the program using this library) to access and manipulate +a resource that's owned by a resource owner (the end user) and lives on a remote +server. The client directs the resource owner to an authorization server +(usually but not always the same as the server that hosts the resource), where +the resource owner tells the authorization server to give the client an access +token. This token serves as proof that the client has permission to access +resources on behalf of the resource owner. OAuth2 provides several different methods for the client to obtain authorization. At the time of writing, this library only supports the -[Authorization Code Grant][authorizationCodeGrantSection], [Client Credentials Grant][clientCredentialsGrantSection] and [Resource Owner Password Grant][resourceOwnerPasswordGrantSection] flows, but more may be added in the future. +[Authorization Code Grant][authorizationCodeGrantSection], +[Client Credentials Grant][clientCredentialsGrantSection] and +[Resource Owner Password Grant][resourceOwnerPasswordGrantSection] flows, but +more may be added in the future. ## Authorization Code Grant -**Resources:** [Class summary][authorizationCodeGrantMethod], [OAuth documentation][authorizationCodeGrantDocs] + +**Resources:** [Class summary][authorizationCodeGrantMethod], +[OAuth documentation][authorizationCodeGrantDocs] ```dart import 'dart:io'; + import 'package:oauth2/oauth2.dart' as oauth2; // These URLs are endpoints that are provided by the authorization // server. They're usually included in the server's documentation of its // OAuth2 API. final authorizationEndpoint = - Uri.parse("http://example.com/oauth2/authorization"); -final tokenEndpoint = - Uri.parse("http://example.com/oauth2/token"); + Uri.parse('http://example.com/oauth2/authorization'); +final tokenEndpoint = Uri.parse('http://example.com/oauth2/token'); // The authorization server will issue each client a separate client // identifier and secret, which allows the server to tell which client @@ -38,38 +43,37 @@ final tokenEndpoint = // available may not be able to make sure the client secret is kept a // secret. This is fine; OAuth2 servers generally won't rely on knowing // with certainty that a client is who it claims to be. -final identifier = "my client identifier"; -final secret = "my client secret"; +final identifier = 'my client identifier'; +final secret = 'my client secret'; // This is a URL on your application's server. The authorization server // will redirect the resource owner here once they've authorized the // client. The redirection will include the authorization code in the // query parameters. -final redirectUrl = Uri.parse("http://my-site.com/oauth2-redirect"); +final redirectUrl = Uri.parse('http://my-site.com/oauth2-redirect'); /// A file in which the users credentials are stored persistently. If the server /// issues a refresh token allowing the client to refresh outdated credentials, /// these may be valid indefinitely, meaning the user never has to /// re-authenticate. -final credentialsFile = new File("~/.myapp/credentials.json"); +final credentialsFile = File('~/.myapp/credentials.json'); /// Either load an OAuth2 client from saved credentials or authenticate a new /// one. -Future getClient() async { +Future createClient() async { var exists = await credentialsFile.exists(); // If the OAuth2 credentials have already been saved from a previous run, we // just want to reload them. if (exists) { - var credentials = new oauth2.Credentials.fromJson( - await credentialsFile.readAsString()); - return new oauth2.Client(credentials, - identifier: identifier, secret: secret); + var credentials = + oauth2.Credentials.fromJson(await credentialsFile.readAsString()); + return oauth2.Client(credentials, identifier: identifier, secret: secret); } // If we don't have OAuth2 credentials yet, we need to get the resource owner // to authorize us. We're assuming here that we're a command-line application. - var grant = new oauth2.AuthorizationCodeGrant( + var grant = oauth2.AuthorizationCodeGrant( identifier, authorizationEndpoint, tokenEndpoint, secret: secret); @@ -79,13 +83,12 @@ Future getClient() async { // Redirect the resource owner to the authorization URL. Once the resource // owner has authorized, they'll be redirected to `redirectUrl` with an - // authorization code. + // authorization code. The `redirect` should cause the browser to redirect to + // another URL which should also have a listener. // - // `redirect` is an imaginary function that redirects the resource - // owner's browser. + // `redirect` and `listen` are not shown implemented here. See below for the + // details. await redirect(authorizationUrl); - - // Another imaginary function that listens for a request to `redirectUrl`. var responseUrl = await listen(redirectUrl); // Once the user is redirected to `redirectUrl`, pass the query parameters to @@ -94,73 +97,79 @@ Future getClient() async { return await grant.handleAuthorizationResponse(responseUrl.queryParameters); } -main() async { - var client = await getClient(); +void main() async { + var client = await createClient(); // Once you have a Client, you can use it just like any other HTTP client. - var result = client.read("http://example.com/protected-resources.txt"); + print(await client.read('http://example.com/protected-resources.txt')); // Once we're done with the client, save the credentials file. This ensures // that if the credentials were automatically refreshed while using the // client, the new credentials are available for the next run of the // program. await credentialsFile.writeAsString(client.credentials.toJson()); - - print(result); } ```
- Click here to learn how to implement the imaginary functions mentioned above. - - ----- - - Unfortunately, there's not a universal example for implementing the imaginary functions, `redirect` and `listen`, because different options exist for each platform. - - For Flutter apps, there's two popular approaches: - 1. Launch a browser using [url_launcher][] and listen for a redirect using [uni_links][]. - ```dart - if (await canLaunch(authorizationUrl.toString())) { - await launch(authorizationUrl.toString()); - } - - ... - - final linksStream = getLinksStream().listen((Uri uri) async { - if (uri.toString().startsWith(redirectUrl)) { - responseUrl = uri; + Click here to learn how to implement `redirect` and `listen`. + +-------------------------------------------------------------------------------- + +There is not a universal example for implementing `redirect` and `listen`, +because different options exist for each platform. + +For Flutter apps, there's two popular approaches: + +1. Launch a browser using [url_launcher][] and listen for a redirect using + [uni_links][]. + + ```dart + if (await canLaunch(authorizationUrl.toString())) { + await launch(authorizationUrl.toString()); } + + // ------- 8< ------- + + final linksStream = getLinksStream().listen((Uri uri) async { + if (uri.toString().startsWith(redirectUrl)) { + responseUrl = uri; + } + }); + ``` + +1. Launch a WebView inside the app and listen for a redirect using + [webview_flutter][]. + + ```dart + WebView( + javascriptMode: JavascriptMode.unrestricted, + initialUrl: authorizationUrl.toString(), + navigationDelegate: (navReq) { + if (navReq.url.startsWith(redirectUrl)) { + responseUrl = Uri.parse(navReq.url); + return NavigationDecision.prevent; } - }); - ``` - - 2. Launch a WebView inside the app and listen for a redirect using [webview_flutter][]. - ```dart - WebView( - javascriptMode: JavascriptMode.unrestricted, - initialUrl: authorizationUrl.toString(), - navigationDelegate: (navReq) { - if (navReq.url.startsWith(redirectUrl)) { - responseUrl = Uri.parse(navReq.url); - return NavigationDecision.prevent; - } - - return NavigationDecision.navigate; - }, - ... - ); - ``` - - For Dart apps, the best approach depends on the available options for accessing a browser. In general, you'll need to launch the authorization URL through the client's browser and listen for the redirect URL. + return NavigationDecision.navigate; + }, + // ------- 8< ------- + ); + ``` + +For Dart apps, the best approach depends on the available options for accessing +a browser. In general, you'll need to launch the authorization URL through the +client's browser and listen for the redirect URL.
## Client Credentials Grant -**Resources:** [Method summary][clientCredentialsGrantMethod], [OAuth documentation][clientCredentialsGrantDocs] + +**Resources:** [Method summary][clientCredentialsGrantMethod], +[OAuth documentation][clientCredentialsGrantDocs] ```dart // This URL is an endpoint that's provided by the authorization server. It's // usually included in the server's documentation of its OAuth2 API. final authorizationEndpoint = - Uri.parse("http://example.com/oauth2/authorization"); + Uri.parse('http://example.com/oauth2/authorization'); // The OAuth2 specification expects a client's identifier and secret // to be sent when using the client credentials grant. @@ -170,8 +179,8 @@ final authorizationEndpoint = // API access. // // Either way, you must provide both a client identifier and a client secret: -final identifier = "my client identifier"; -final secret = "my client secret"; +final identifier = 'my client identifier'; +final secret = 'my client secret'; // Calling the top-level `clientCredentialsGrant` function will return a // [Client] instead. @@ -181,7 +190,8 @@ var client = await oauth2.clientCredentialsGrant( // With an authenticated client, you can make requests, and the `Bearer` token // returned by the server during the client credentials grant will be attached // to any request you make. -var response = await client.read("https://example.com/api/some_resource.json"); +var response = + await client.read('https://example.com/api/some_resource.json'); // You can save the client's credentials, which consists of an access token, and // potentially a refresh token and expiry date, to a file. This way, subsequent runs @@ -191,17 +201,19 @@ await credentialsFile.writeAsString(client.credentials.toJson()); ``` ## Resource Owner Password Grant -**Resources:** [Method summary][resourceOwnerPasswordGrantMethod], [OAuth documentation][resourceOwnerPasswordGrantDocs] + +**Resources:** [Method summary][resourceOwnerPasswordGrantMethod], +[OAuth documentation][resourceOwnerPasswordGrantDocs] ```dart // This URL is an endpoint that's provided by the authorization server. It's // usually included in the server's documentation of its OAuth2 API. final authorizationEndpoint = - Uri.parse("http://example.com/oauth2/authorization"); + Uri.parse('http://example.com/oauth2/authorization'); // The user should supply their own username and password. -final username = "example user"; -final password = "example password"; +final username = 'example user'; +final password = 'example password'; // The authorization server may issue each client a separate client // identifier and secret, which allows the server to tell which client @@ -210,8 +222,8 @@ final password = "example password"; // // Some servers don't require the client to authenticate itself, in which case // these should be omitted. -final identifier = "my client identifier"; -final secret = "my client secret"; +final identifier = 'my client identifier'; +final secret = 'my client secret'; // Make a request to the authorization endpoint that will produce the fully // authenticated Client. @@ -220,13 +232,12 @@ var client = await oauth2.resourceOwnerPasswordGrant( identifier: identifier, secret: secret); // Once you have the client, you can use it just like any other HTTP client. -var result = await client.read("http://example.com/protected-resources.txt"); +var result = await client.read('http://example.com/protected-resources.txt'); // Once we're done with the client, save the credentials file. This will allow // us to re-use the credentials and avoid storing the username and password // directly. -new File("~/.myapp/credentials.json") - .writeAsString(client.credentials.toJson()); +File('~/.myapp/credentials.json').writeAsString(client.credentials.toJson()); ``` [authorizationCodeGrantDocs]: https://oauth.net/2/grant-types/authorization-code/ diff --git a/pkgs/oauth2/example/main.dart b/pkgs/oauth2/example/main.dart new file mode 100644 index 000000000..1560cdd3b --- /dev/null +++ b/pkgs/oauth2/example/main.dart @@ -0,0 +1,95 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:oauth2/oauth2.dart' as oauth2; + +// These URLs are endpoints that are provided by the authorization +// server. They're usually included in the server's documentation of its +// OAuth2 API. +final authorizationEndpoint = + Uri.parse('http://example.com/oauth2/authorization'); +final tokenEndpoint = Uri.parse('http://example.com/oauth2/token'); + +// The authorization server will issue each client a separate client +// identifier and secret, which allows the server to tell which client +// is accessing it. Some servers may also have an anonymous +// identifier/secret pair that any client may use. +// +// Note that clients whose source code or binary executable is readily +// available may not be able to make sure the client secret is kept a +// secret. This is fine; OAuth2 servers generally won't rely on knowing +// with certainty that a client is who it claims to be. +final identifier = 'my client identifier'; +final secret = 'my client secret'; + +// This is a URL on your application's server. The authorization server +// will redirect the resource owner here once they've authorized the +// client. The redirection will include the authorization code in the +// query parameters. +final redirectUrl = Uri.parse('http://my-site.com/oauth2-redirect'); + +/// A file in which the users credentials are stored persistently. If the server +/// issues a refresh token allowing the client to refresh outdated credentials, +/// these may be valid indefinitely, meaning the user never has to +/// re-authenticate. +final credentialsFile = File('~/.myapp/credentials.json'); + +/// Either load an OAuth2 client from saved credentials or authenticate a new +/// one. +Future createClient() async { + var exists = await credentialsFile.exists(); + + // If the OAuth2 credentials have already been saved from a previous run, we + // just want to reload them. + if (exists) { + var credentials = + oauth2.Credentials.fromJson(await credentialsFile.readAsString()); + return oauth2.Client(credentials, identifier: identifier, secret: secret); + } + + // If we don't have OAuth2 credentials yet, we need to get the resource owner + // to authorize us. We're assuming here that we're a command-line application. + var grant = oauth2.AuthorizationCodeGrant( + identifier, authorizationEndpoint, tokenEndpoint, + secret: secret); + + // A URL on the authorization server (authorizationEndpoint with some additional + // query parameters). Scopes and state can optionally be passed into this method. + var authorizationUrl = grant.getAuthorizationUrl(redirectUrl); + + // Redirect the resource owner to the authorization URL. Once the resource + // owner has authorized, they'll be redirected to `redirectUrl` with an + // authorization code. The `redirect` should cause the browser to redirect to + // another URL which should also have a listener. + // + // `redirect` and `listen` are not shown implemented here. + await redirect(authorizationUrl); + var responseUrl = await listen(redirectUrl); + + // Once the user is redirected to `redirectUrl`, pass the query parameters to + // the AuthorizationCodeGrant. It will validate them and extract the + // authorization code to create a new Client. + return await grant.handleAuthorizationResponse(responseUrl.queryParameters); +} + +void main() async { + var client = await createClient(); + + // Once you have a Client, you can use it just like any other HTTP client. + print(await client.read('http://example.com/protected-resources.txt')); + + // Once we're done with the client, save the credentials file. This ensures + // that if the credentials were automatically refreshed while using the + // client, the new credentials are available for the next run of the + // program. + await credentialsFile.writeAsString(client.credentials.toJson()); +} + +Future redirect(Uri url) async { + // Client implementation detail +} + +Future listen(Uri url) async { + // Client implementation detail + return null; +} diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index 4648628ab..856aac8bc 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,6 +1,5 @@ name: oauth2 -version: 1.6.1 -author: Dart Team +version: 1.6.2-dev homepage: https://github.com/dart-lang/oauth2 description: >- A client library for authenticating with a remote service via OAuth2 on From 6b384356572dc87988812ee52d51a2d8cc160789 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Tue, 28 Jul 2020 20:32:24 -0700 Subject: [PATCH 115/159] Delete .test_config No longer used --- pkgs/oauth2/.test_config | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 pkgs/oauth2/.test_config diff --git a/pkgs/oauth2/.test_config b/pkgs/oauth2/.test_config deleted file mode 100644 index 412fc5c5c..000000000 --- a/pkgs/oauth2/.test_config +++ /dev/null @@ -1,3 +0,0 @@ -{ - "test_package": true -} \ No newline at end of file From 003493f9bdf5984cf12eeb0c14c31309a3af7086 Mon Sep 17 00:00:00 2001 From: Joseph Lejtman Date: Thu, 15 Oct 2020 09:22:43 -0400 Subject: [PATCH 116/159] allow code verifier to be passed in constructor of AuthorizationCodeGrant (dart-lang/oauth2#92) * allow code verifier to be passed in constructor * update changelog and bump pubspec.yaml version * Update lib/src/authorization_code_grant.dart update docs for codeVerifier Co-authored-by: Jonas Finnemann Jensen Co-authored-by: Joseph Lejtman Co-authored-by: Jonas Finnemann Jensen --- pkgs/oauth2/CHANGELOG.md | 4 ++++ .../lib/src/authorization_code_grant.dart | 17 ++++++++++---- pkgs/oauth2/pubspec.yaml | 2 +- .../test/authorization_code_grant_test.dart | 23 +++++++++++++++++++ 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md index 28cc6e4d7..3bb8c5aaf 100644 --- a/pkgs/oauth2/CHANGELOG.md +++ b/pkgs/oauth2/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.6.3 + +* Added optional `codeVerifier` parameter to `AuthorizationCodeGrant` constructor. + # 1.6.2-dev # 1.6.1 diff --git a/pkgs/oauth2/lib/src/authorization_code_grant.dart b/pkgs/oauth2/lib/src/authorization_code_grant.dart index c04bc0b61..963e5b24b 100644 --- a/pkgs/oauth2/lib/src/authorization_code_grant.dart +++ b/pkgs/oauth2/lib/src/authorization_code_grant.dart @@ -107,8 +107,8 @@ class AuthorizationCodeGrant { static const String _charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~'; - /// The generated PKCE code verifier - String _codeVerifier; + /// The PKCE code verifier. Will be generated if one is not provided in the constructor. + final String _codeVerifier; /// Creates a new grant. /// @@ -126,6 +126,12 @@ class AuthorizationCodeGrant { /// [onCredentialsRefreshed] will be called by the constructed [Client] /// whenever the credentials are refreshed. /// + /// [codeVerifier] String to be used as PKCE code verifier. If none is provided a + /// random codeVerifier will be generated. + /// The codeVerifier must meet requirements specified in [RFC 7636]. + /// + /// [RFC 7636]: https://tools.ietf.org/html/rfc7636#section-4.1 + /// /// The scope strings will be separated by the provided [delimiter]. This /// defaults to `" "`, the OAuth2 standard, but some APIs (such as Facebook's) /// use non-standard delimiters. @@ -147,12 +153,14 @@ class AuthorizationCodeGrant { http.Client httpClient, CredentialsRefreshedCallback onCredentialsRefreshed, Map Function(MediaType contentType, String body) - getParameters}) + getParameters, + String codeVerifier}) : _basicAuth = basicAuth, _httpClient = httpClient ?? http.Client(), _delimiter = delimiter ?? ' ', _getParameters = getParameters ?? parseJsonParameters, - _onCredentialsRefreshed = onCredentialsRefreshed; + _onCredentialsRefreshed = onCredentialsRefreshed, + _codeVerifier = codeVerifier ?? _createCodeVerifier(); /// Returns the URL to which the resource owner should be redirected to /// authorize this client. @@ -186,7 +194,6 @@ class AuthorizationCodeGrant { scopes = scopes.toList(); } - _codeVerifier = _createCodeVerifier(); var codeChallenge = base64Url .encode(sha256.convert(ascii.encode(_codeVerifier)).bytes) .replaceAll('=', ''); diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index 856aac8bc..3fad97252 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,5 @@ name: oauth2 -version: 1.6.2-dev +version: 1.6.3 homepage: https://github.com/dart-lang/oauth2 description: >- A client library for authenticating with a remote service via OAuth2 on diff --git a/pkgs/oauth2/test/authorization_code_grant_test.dart b/pkgs/oauth2/test/authorization_code_grant_test.dart index 4909bf5b9..f74c061b8 100644 --- a/pkgs/oauth2/test/authorization_code_grant_test.dart +++ b/pkgs/oauth2/test/authorization_code_grant_test.dart @@ -54,6 +54,29 @@ void main() { ])); }); + test('builds the correct URL with passed in code verifier', () { + final codeVerifier = + 'it1shei7LooGoh3looxaa4sieveijeib2zecauz2oo8aebae5aehee0ahPirewoh5Bo6Maexooqui3uL2si6ahweiv7shauc1shahxooveoB3aeyahsaiye0Egh3raix'; + final expectedCodeChallenge = + 'EjfFMv8TFPd3GuNxAn5COhlWBGpfZLimHett7ypJfJ0'; + var grant = oauth2.AuthorizationCodeGrant( + 'identifier', + Uri.parse('https://example.com/authorization'), + Uri.parse('https://example.com/token'), + secret: 'secret', + httpClient: client, + codeVerifier: codeVerifier); + expect( + grant.getAuthorizationUrl(redirectUrl).toString(), + allOf([ + startsWith('https://example.com/authorization?response_type=code'), + contains('&client_id=identifier'), + contains('&redirect_uri=http%3A%2F%2Fexample.com%2Fredirect'), + contains('&code_challenge=$expectedCodeChallenge'), + contains('&code_challenge_method=S256') + ])); + }); + test('separates scopes with the correct delimiter', () { var grant = oauth2.AuthorizationCodeGrant( 'identifier', From e884400008cd8a46d2f2e090261031ed9a47d811 Mon Sep 17 00:00:00 2001 From: Alexander Thomas Date: Mon, 1 Feb 2021 17:44:51 +0100 Subject: [PATCH 117/159] Migrate to GitHub Actions (dart-lang/oauth2#100) * Migrate to GitHub Actions * Delete .travis.yml * Fix info --- .../oauth2/.github/workflows/test-package.yml | 64 +++++++++++++++++++ pkgs/oauth2/.travis.yml | 23 ------- pkgs/oauth2/test/utils.dart | 2 +- 3 files changed, 65 insertions(+), 24 deletions(-) create mode 100644 pkgs/oauth2/.github/workflows/test-package.yml delete mode 100644 pkgs/oauth2/.travis.yml diff --git a/pkgs/oauth2/.github/workflows/test-package.yml b/pkgs/oauth2/.github/workflows/test-package.yml new file mode 100644 index 000000000..0a2a87433 --- /dev/null +++ b/pkgs/oauth2/.github/workflows/test-package.yml @@ -0,0 +1,64 @@ +name: Dart CI + +on: + # Run on PRs and pushes to the default branch. + push: + branches: [ master ] + pull_request: + branches: [ master ] + schedule: + - cron: "0 0 * * 0" + +env: + PUB_ENVIRONMENT: bot.github + +jobs: + # Check code formatting and static analysis on a single OS (linux) + # against Dart dev. + analyze: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + sdk: [dev] + steps: + - uses: actions/checkout@v2 + - uses: dart-lang/setup-dart@v0.3 + with: + sdk: ${{ matrix.sdk }} + - id: install + name: Install dependencies + run: dart pub get + - name: Check formatting + run: dart format --output=none --set-exit-if-changed . + if: always() && steps.install.outcome == 'success' + - name: Analyze code + run: dart analyze --fatal-infos + if: always() && steps.install.outcome == 'success' + + # Run tests on a matrix consisting of two dimensions: + # 1. OS: ubuntu-latest, (macos-latest, windows-latest) + # 2. release channel: dev + test: + needs: analyze + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + # Add macos-latest and/or windows-latest if relevant for this package. + os: [ubuntu-latest] + sdk: [dev] + steps: + - uses: actions/checkout@v2 + - uses: dart-lang/setup-dart@v0.3 + with: + sdk: ${{ matrix.sdk }} + - id: install + name: Install dependencies + run: dart pub get + - name: Run VM tests + run: dart test --platform vm + if: always() && steps.install.outcome == 'success' + - name: Run Chrome tests + run: dart test --platform chrome + if: always() && steps.install.outcome == 'success' diff --git a/pkgs/oauth2/.travis.yml b/pkgs/oauth2/.travis.yml deleted file mode 100644 index c15fd3318..000000000 --- a/pkgs/oauth2/.travis.yml +++ /dev/null @@ -1,23 +0,0 @@ -language: dart - -dart: -- dev -dart_task: -- test: --platform vm,firefox - -# Only run one instance of the formatter and the analyzer, rather than running -# them against each Dart version. -matrix: - include: - - dart: dev - dart_task: dartfmt - - dart: dev - dart_task: dartanalyzer - -# Only building master means that we don't run two builds for each pull request. -branches: - only: [master] - -cache: - directories: - - $HOME/.pub-cache diff --git a/pkgs/oauth2/test/utils.dart b/pkgs/oauth2/test/utils.dart index 528673c0f..df43af5b4 100644 --- a/pkgs/oauth2/test/utils.dart +++ b/pkgs/oauth2/test/utils.dart @@ -18,7 +18,7 @@ class ExpectClient extends MockClient { super(fn); factory ExpectClient() { - var client; + ExpectClient client; client = ExpectClient._((request) => client._handleRequest(request)); return client; } From 101db29d0e14ef2519a8d9a1b288d6d49073fcfa Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Tue, 23 Feb 2021 10:42:50 -0800 Subject: [PATCH 118/159] Migrate to null safety (dart-lang/oauth2#102) --- pkgs/oauth2/CHANGELOG.md | 6 ++- pkgs/oauth2/example/main.dart | 4 +- .../lib/src/authorization_code_grant.dart | 44 +++++++++---------- .../lib/src/authorization_exception.dart | 4 +- pkgs/oauth2/lib/src/client.dart | 36 +++++++-------- .../lib/src/client_credentials_grant.dart | 16 +++---- pkgs/oauth2/lib/src/credentials.dart | 39 ++++++++-------- .../lib/src/handle_access_token_response.dart | 11 ++--- pkgs/oauth2/lib/src/parameters.dart | 4 +- .../src/resource_owner_password_grant.dart | 18 ++++---- pkgs/oauth2/pubspec.yaml | 15 ++++--- .../test/authorization_code_grant_test.dart | 27 ++++++------ .../handle_access_token_response_test.dart | 6 +-- pkgs/oauth2/test/utils.dart | 2 +- 14 files changed, 116 insertions(+), 116 deletions(-) diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md index 3bb8c5aaf..dd402ba62 100644 --- a/pkgs/oauth2/CHANGELOG.md +++ b/pkgs/oauth2/CHANGELOG.md @@ -1,9 +1,11 @@ +# 2.0.0-dev + +* Migrate to null safety. + # 1.6.3 * Added optional `codeVerifier` parameter to `AuthorizationCodeGrant` constructor. -# 1.6.2-dev - # 1.6.1 * Added fix to make sure that credentials are only refreshed once when multiple calls are made. diff --git a/pkgs/oauth2/example/main.dart b/pkgs/oauth2/example/main.dart index 1560cdd3b..4bd3cbc45 100644 --- a/pkgs/oauth2/example/main.dart +++ b/pkgs/oauth2/example/main.dart @@ -76,7 +76,7 @@ void main() async { var client = await createClient(); // Once you have a Client, you can use it just like any other HTTP client. - print(await client.read('http://example.com/protected-resources.txt')); + print(await client.read(Uri.http('example.com', 'protected-resources.txt'))); // Once we're done with the client, save the credentials file. This ensures // that if the credentials were automatically refreshed while using the @@ -91,5 +91,5 @@ Future redirect(Uri url) async { Future listen(Uri url) async { // Client implementation detail - return null; + return Uri(); } diff --git a/pkgs/oauth2/lib/src/authorization_code_grant.dart b/pkgs/oauth2/lib/src/authorization_code_grant.dart index 963e5b24b..4a7ef8a3b 100644 --- a/pkgs/oauth2/lib/src/authorization_code_grant.dart +++ b/pkgs/oauth2/lib/src/authorization_code_grant.dart @@ -58,7 +58,7 @@ class AuthorizationCodeGrant { /// available may not be able to make sure the client secret is kept a secret. /// This is fine; OAuth2 servers generally won't rely on knowing with /// certainty that a client is who it claims to be. - final String secret; + final String? secret; /// A URL provided by the authorization server that serves as the base for the /// URL that the resource owner will be redirected to to authorize this @@ -78,7 +78,7 @@ class AuthorizationCodeGrant { /// Callback to be invoked whenever the credentials are refreshed. /// /// This will be passed as-is to the constructed [Client]. - final CredentialsRefreshedCallback _onCredentialsRefreshed; + final CredentialsRefreshedCallback? _onCredentialsRefreshed; /// Whether to use HTTP Basic authentication for authorizing the client. final bool _basicAuth; @@ -87,18 +87,18 @@ class AuthorizationCodeGrant { final String _delimiter; /// The HTTP client used to make HTTP requests. - http.Client _httpClient; + http.Client? _httpClient; /// The URL to which the resource owner will be redirected after they /// authorize this client with the authorization server. - Uri _redirectEndpoint; + Uri? _redirectEndpoint; /// The scopes that the client is requesting access to. - List _scopes; + List? _scopes; /// An opaque string that users of this library may specify that will be /// included in the response query parameters. - String _stateString; + String? _stateString; /// The current state of the grant object. _State _state = _State.initial; @@ -148,13 +148,13 @@ class AuthorizationCodeGrant { AuthorizationCodeGrant( this.identifier, this.authorizationEndpoint, this.tokenEndpoint, {this.secret, - String delimiter, + String? delimiter, bool basicAuth = true, - http.Client httpClient, - CredentialsRefreshedCallback onCredentialsRefreshed, - Map Function(MediaType contentType, String body) + http.Client? httpClient, + CredentialsRefreshedCallback? onCredentialsRefreshed, + Map Function(MediaType? contentType, String body)? getParameters, - String codeVerifier}) + String? codeVerifier}) : _basicAuth = basicAuth, _httpClient = httpClient ?? http.Client(), _delimiter = delimiter ?? ' ', @@ -182,24 +182,19 @@ class AuthorizationCodeGrant { /// /// It is a [StateError] to call this more than once. Uri getAuthorizationUrl(Uri redirect, - {Iterable scopes, String state}) { + {Iterable? scopes, String? state}) { if (_state != _State.initial) { throw StateError('The authorization URL has already been generated.'); } _state = _State.awaitingResponse; - if (scopes == null) { - scopes = []; - } else { - scopes = scopes.toList(); - } - + var scopeList = scopes?.toList() ?? []; var codeChallenge = base64Url .encode(sha256.convert(ascii.encode(_codeVerifier)).bytes) .replaceAll('=', ''); _redirectEndpoint = redirect; - _scopes = scopes; + _scopes = scopeList; _stateString = state; var parameters = { 'response_type': 'code', @@ -210,7 +205,7 @@ class AuthorizationCodeGrant { }; if (state != null) parameters['state'] = state; - if (scopes.isNotEmpty) parameters['scope'] = scopes.join(_delimiter); + if (scopeList.isNotEmpty) parameters['scope'] = scopeList.join(_delimiter); return addQueryParameters(authorizationEndpoint, parameters); } @@ -257,7 +252,7 @@ class AuthorizationCodeGrant { var description = parameters['error_description']; var uriString = parameters['error_uri']; var uri = uriString == null ? null : Uri.parse(uriString); - throw AuthorizationException(parameters['error'], description, uri); + throw AuthorizationException(parameters['error']!, description, uri); } else if (!parameters.containsKey('code')) { throw FormatException('Invalid OAuth response for ' '"$authorizationEndpoint": did not contain required parameter ' @@ -295,7 +290,7 @@ class AuthorizationCodeGrant { /// This works just like [handleAuthorizationCode], except it doesn't validate /// the state beforehand. - Future _handleAuthorizationCode(String authorizationCode) async { + Future _handleAuthorizationCode(String? authorizationCode) async { var startTime = DateTime.now(); var headers = {}; @@ -307,6 +302,7 @@ class AuthorizationCodeGrant { 'code_verifier': _codeVerifier }; + var secret = this.secret; if (_basicAuth && secret != null) { headers['Authorization'] = basicAuthHeader(identifier, secret); } else { @@ -317,7 +313,7 @@ class AuthorizationCodeGrant { } var response = - await _httpClient.post(tokenEndpoint, headers: headers, body: body); + await _httpClient!.post(tokenEndpoint, headers: headers, body: body); var credentials = handleAccessTokenResponse( response, tokenEndpoint, startTime, _scopes, _delimiter, @@ -342,7 +338,7 @@ class AuthorizationCodeGrant { /// [Client] created by this grant, so it's not safe to close the grant and /// continue using the client. void close() { - if (_httpClient != null) _httpClient.close(); + _httpClient?.close(); _httpClient = null; } } diff --git a/pkgs/oauth2/lib/src/authorization_exception.dart b/pkgs/oauth2/lib/src/authorization_exception.dart index a1b6dd9d2..14a5a3c00 100644 --- a/pkgs/oauth2/lib/src/authorization_exception.dart +++ b/pkgs/oauth2/lib/src/authorization_exception.dart @@ -14,13 +14,13 @@ class AuthorizationException implements Exception { /// The description of the error, provided by the server. /// /// May be `null` if the server provided no description. - final String description; + final String? description; /// A URL for a page that describes the error in more detail, provided by the /// server. /// /// May be `null` if the server provided no URL. - final Uri uri; + final Uri? uri; /// Creates an AuthorizationException. AuthorizationException(this.error, this.description, this.uri); diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart index 7a78aa37f..dc50e43ca 100644 --- a/pkgs/oauth2/lib/src/client.dart +++ b/pkgs/oauth2/lib/src/client.dart @@ -4,6 +4,7 @@ import 'dart:async'; +import 'package:collection/collection.dart'; import 'package:http/http.dart' as http; import 'package:http_parser/http_parser.dart'; @@ -40,7 +41,7 @@ class Client extends http.BaseClient { /// pair that any client may use. /// /// This is usually global to the program using this library. - final String identifier; + final String? identifier; /// The client secret for this client. /// @@ -55,7 +56,7 @@ class Client extends http.BaseClient { /// available may not be able to make sure the client secret is kept a secret. /// This is fine; OAuth2 servers generally won't rely on knowing with /// certainty that a client is who it claims to be. - final String secret; + final String? secret; /// The credentials this client uses to prove to the resource server that it's /// authorized. @@ -66,13 +67,13 @@ class Client extends http.BaseClient { Credentials _credentials; /// Callback to be invoked whenever the credentials refreshed. - final CredentialsRefreshedCallback _onCredentialsRefreshed; + final CredentialsRefreshedCallback? _onCredentialsRefreshed; /// Whether to use HTTP Basic authentication for authorizing the client. final bool _basicAuth; /// The underlying HTTP client. - http.Client _httpClient; + http.Client? _httpClient; /// Creates a new client from a pre-existing set of credentials. /// @@ -87,9 +88,9 @@ class Client extends http.BaseClient { Client(this._credentials, {this.identifier, this.secret, - CredentialsRefreshedCallback onCredentialsRefreshed, + CredentialsRefreshedCallback? onCredentialsRefreshed, bool basicAuth = true, - http.Client httpClient}) + http.Client? httpClient}) : _basicAuth = basicAuth, _onCredentialsRefreshed = onCredentialsRefreshed, _httpClient = httpClient ?? http.Client() { @@ -110,33 +111,32 @@ class Client extends http.BaseClient { } request.headers['authorization'] = 'Bearer ${credentials.accessToken}'; - var response = await _httpClient.send(request); + var response = await _httpClient!.send(request); if (response.statusCode != 401) return response; if (!response.headers.containsKey('www-authenticate')) return response; - var challenges; + List challenges; try { challenges = AuthenticationChallenge.parseHeader( - response.headers['www-authenticate']); + response.headers['www-authenticate']!); } on FormatException { return response; } - var challenge = challenges.firstWhere( - (challenge) => challenge.scheme == 'bearer', - orElse: () => null); + var challenge = challenges + .firstWhereOrNull((challenge) => challenge.scheme == 'bearer'); if (challenge == null) return response; var params = challenge.parameters; if (!params.containsKey('error')) return response; - throw AuthorizationException(params['error'], params['error_description'], - params['error_uri'] == null ? null : Uri.parse(params['error_uri'])); + throw AuthorizationException(params['error']!, params['error_description'], + params['error_uri'] == null ? null : Uri.parse(params['error_uri']!)); } /// A [Future] used to track whether [refreshCredentials] is running. - Future _refreshingFuture; + Future? _refreshingFuture; /// Explicitly refreshes this client's credentials. Returns this client. /// @@ -147,7 +147,7 @@ class Client extends http.BaseClient { /// You may request different scopes than the default by passing in /// [newScopes]. These must be a subset of the scopes in the /// [Credentials.scopes] field of [Client.credentials]. - Future refreshCredentials([List newScopes]) async { + Future refreshCredentials([List? newScopes]) async { if (!credentials.canRefresh) { var prefix = 'OAuth credentials'; if (credentials.isExpired) prefix = '$prefix have expired and'; @@ -166,7 +166,7 @@ class Client extends http.BaseClient { basicAuth: _basicAuth, httpClient: _httpClient, ); - _credentials = await _refreshingFuture; + _credentials = await _refreshingFuture!; _onCredentialsRefreshed?.call(_credentials); } finally { _refreshingFuture = null; @@ -181,7 +181,7 @@ class Client extends http.BaseClient { /// Closes this client and its underlying HTTP client. @override void close() { - if (_httpClient != null) _httpClient.close(); + _httpClient?.close(); _httpClient = null; } } diff --git a/pkgs/oauth2/lib/src/client_credentials_grant.dart b/pkgs/oauth2/lib/src/client_credentials_grant.dart index c6d34b94c..ab13a3f03 100644 --- a/pkgs/oauth2/lib/src/client_credentials_grant.dart +++ b/pkgs/oauth2/lib/src/client_credentials_grant.dart @@ -40,12 +40,12 @@ import 'utils.dart'; /// its body as a UTF-8-decoded string. It should return a map in the same /// format as the [standard JSON response](https://tools.ietf.org/html/rfc6749#section-5.1) Future clientCredentialsGrant( - Uri authorizationEndpoint, String identifier, String secret, - {Iterable scopes, + Uri authorizationEndpoint, String? identifier, String? secret, + {Iterable? scopes, bool basicAuth = true, - http.Client httpClient, - String delimiter, - Map Function(MediaType contentType, String body) + http.Client? httpClient, + String? delimiter, + Map Function(MediaType? contentType, String body)? getParameters}) async { delimiter ??= ' '; var startTime = DateTime.now(); @@ -56,7 +56,7 @@ Future clientCredentialsGrant( if (identifier != null) { if (basicAuth) { - headers['Authorization'] = basicAuthHeader(identifier, secret); + headers['Authorization'] = basicAuthHeader(identifier, secret!); } else { body['client_id'] = identifier; if (secret != null) body['client_secret'] = secret; @@ -71,8 +71,8 @@ Future clientCredentialsGrant( var response = await httpClient.post(authorizationEndpoint, headers: headers, body: body); - var credentials = await handleAccessTokenResponse( - response, authorizationEndpoint, startTime, scopes, delimiter, + var credentials = await handleAccessTokenResponse(response, + authorizationEndpoint, startTime, scopes?.toList() ?? [], delimiter, getParameters: getParameters); return Client(credentials, identifier: identifier, secret: secret, httpClient: httpClient); diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index 2b40b056d..88a8f45e5 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -42,7 +42,7 @@ class Credentials { /// credentials. /// /// This may be `null`, indicating that the credentials can't be refreshed. - final String refreshToken; + final String? refreshToken; /// The token that is received from the authorization server to enable /// End-Users to be Authenticated, contains Claims, represented as a @@ -52,25 +52,25 @@ class Credentials { /// requested (or not supported). /// /// [spec]: https://openid.net/specs/openid-connect-core-1_0.html#IDToken - final String idToken; + final String? idToken; /// The URL of the authorization server endpoint that's used to refresh the /// credentials. /// /// This may be `null`, indicating that the credentials can't be refreshed. - final Uri tokenEndpoint; + final Uri? tokenEndpoint; /// The specific permissions being requested from the authorization server. /// /// The scope strings are specific to the authorization server and may be /// found in its documentation. - final List scopes; + final List? 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 DateTime expiration; + final DateTime? expiration; /// The function used to parse parameters from a host's response. final GetParameters _getParameters; @@ -80,8 +80,10 @@ class Credentials { /// 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 && DateTime.now().isAfter(expiration); + bool get isExpired { + var expiration = this.expiration; + return expiration != null && DateTime.now().isAfter(expiration); + } /// Whether it's possible to refresh these credentials. bool get canRefresh => refreshToken != null && tokenEndpoint != null; @@ -110,10 +112,10 @@ class Credentials { {this.refreshToken, this.idToken, this.tokenEndpoint, - Iterable scopes, + Iterable? scopes, this.expiration, - String delimiter, - Map Function(MediaType mediaType, String body) + String? delimiter, + Map Function(MediaType? mediaType, String body)? getParameters}) : scopes = UnmodifiableListView( // Explicitly type-annotate the list literal to work around @@ -183,11 +185,9 @@ class Credentials { 'accessToken': accessToken, 'refreshToken': refreshToken, 'idToken': idToken, - 'tokenEndpoint': - tokenEndpoint == null ? null : tokenEndpoint.toString(), + 'tokenEndpoint': tokenEndpoint?.toString(), 'scopes': scopes, - 'expiration': - expiration == null ? null : expiration.millisecondsSinceEpoch + 'expiration': expiration?.millisecondsSinceEpoch }); /// Returns a new set of refreshed credentials. @@ -203,11 +203,11 @@ class Credentials { /// [AuthorizationException] if refreshing the credentials fails, or a /// [FormatError] if the authorization server returns invalid responses. Future refresh( - {String identifier, - String secret, - Iterable newScopes, + {String? identifier, + String? secret, + Iterable? newScopes, bool basicAuth = true, - http.Client httpClient}) async { + http.Client? httpClient}) async { var scopes = this.scopes; if (newScopes != null) scopes = newScopes.toList(); scopes ??= []; @@ -218,6 +218,7 @@ class Credentials { } var startTime = DateTime.now(); + var tokenEndpoint = this.tokenEndpoint; if (refreshToken == null) { throw StateError("Can't refresh credentials without a refresh " 'token.'); @@ -232,7 +233,7 @@ class Credentials { if (scopes.isNotEmpty) body['scope'] = scopes.join(_delimiter); if (basicAuth && secret != null) { - headers['Authorization'] = basicAuthHeader(identifier, secret); + headers['Authorization'] = basicAuthHeader(identifier!, secret); } else { if (identifier != null) body['client_id'] = identifier; if (secret != null) body['client_secret'] = secret; diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart index 7517af15c..370ffcba2 100644 --- a/pkgs/oauth2/lib/src/handle_access_token_response.dart +++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart @@ -31,8 +31,8 @@ const _expirationGrace = Duration(seconds: 10); /// /// [standard JSON response]: https://tools.ietf.org/html/rfc6749#section-5.1 Credentials handleAccessTokenResponse(http.Response response, Uri tokenEndpoint, - DateTime startTime, List scopes, String delimiter, - {Map Function(MediaType contentType, String body) + DateTime startTime, List? scopes, String delimiter, + {Map Function(MediaType? contentType, String body)? getParameters}) { getParameters ??= parseJsonParameters; @@ -81,7 +81,7 @@ Credentials handleAccessTokenResponse(http.Response response, Uri tokenEndpoint, } } - var scope = parameters['scope'] as String; + var scope = parameters['scope'] as String?; if (scope != null) scopes = scope.split(delimiter); var expiration = expiresIn == null @@ -109,8 +109,9 @@ void _handleErrorResponse( // off-spec. if (response.statusCode != 400 && response.statusCode != 401) { var reason = ''; - if (response.reasonPhrase != null && response.reasonPhrase.isNotEmpty) { - ' ${response.reasonPhrase}'; + var reasonPhrase = response.reasonPhrase; + if (reasonPhrase != null && reasonPhrase.isNotEmpty) { + reason = ' $reasonPhrase'; } throw FormatException('OAuth request for "$tokenEndpoint" failed ' 'with status ${response.statusCode}$reason.\n\n${response.body}'); diff --git a/pkgs/oauth2/lib/src/parameters.dart b/pkgs/oauth2/lib/src/parameters.dart index e8290fc3d..789f05618 100644 --- a/pkgs/oauth2/lib/src/parameters.dart +++ b/pkgs/oauth2/lib/src/parameters.dart @@ -8,13 +8,13 @@ import 'package:http_parser/http_parser.dart'; /// The type of a callback that parses parameters from an HTTP response. typedef GetParameters = Map Function( - MediaType contentType, String body); + MediaType? contentType, String body); /// Parses parameters from a response with a JSON body, as per the [OAuth2 /// spec][]. /// /// [OAuth2 spec]: https://tools.ietf.org/html/rfc6749#section-5.1 -Map parseJsonParameters(MediaType contentType, String body) { +Map parseJsonParameters(MediaType? contentType, String body) { // The spec requires a content-type of application/json, but some endpoints // (e.g. Dropbox) serve it as text/javascript instead. if (contentType == null || diff --git a/pkgs/oauth2/lib/src/resource_owner_password_grant.dart b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart index 118f7c96c..f54ac85af 100644 --- a/pkgs/oauth2/lib/src/resource_owner_password_grant.dart +++ b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart @@ -46,14 +46,14 @@ import 'credentials.dart'; /// [standard JSON response]: https://tools.ietf.org/html/rfc6749#section-5.1 Future resourceOwnerPasswordGrant( Uri authorizationEndpoint, String username, String password, - {String identifier, - String secret, - Iterable scopes, + {String? identifier, + String? secret, + Iterable? scopes, bool basicAuth = true, - CredentialsRefreshedCallback onCredentialsRefreshed, - http.Client httpClient, - String delimiter, - Map Function(MediaType contentType, String body) + CredentialsRefreshedCallback? onCredentialsRefreshed, + http.Client? httpClient, + String? delimiter, + Map Function(MediaType? contentType, String body)? getParameters}) async { delimiter ??= ' '; var startTime = DateTime.now(); @@ -68,7 +68,7 @@ Future resourceOwnerPasswordGrant( if (identifier != null) { if (basicAuth) { - headers['Authorization'] = basicAuthHeader(identifier, secret); + headers['Authorization'] = basicAuthHeader(identifier, secret!); } else { body['client_id'] = identifier; if (secret != null) body['client_secret'] = secret; @@ -84,7 +84,7 @@ Future resourceOwnerPasswordGrant( headers: headers, body: body); var credentials = await handleAccessTokenResponse( - response, authorizationEndpoint, startTime, scopes, delimiter, + response, authorizationEndpoint, startTime, scopes?.toList(), delimiter, getParameters: getParameters); return Client(credentials, identifier: identifier, diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index 3fad97252..ab42be21f 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,5 @@ name: oauth2 -version: 1.6.3 +version: 2.0.0-dev homepage: https://github.com/dart-lang/oauth2 description: >- A client library for authenticating with a remote service via OAuth2 on @@ -7,13 +7,14 @@ description: >- OAuth2 credentials. environment: - sdk: '>=2.0.0 <3.0.0' + sdk: '>=2.12.0-0 <3.0.0' dependencies: - http: '>=0.11.0 <0.13.0' - http_parser: '>=1.0.0 <4.0.0' - crypto: '^2.1.3' + collection: ^1.15.0 + crypto: ^3.0.0 + http: ^0.13.0 + http_parser: ^4.0.0 dev_dependencies: - pedantic: ^1.2.0 - test: ^1.0.0 + pedantic: ^1.10.0 + test: ^1.16.0 diff --git a/pkgs/oauth2/test/authorization_code_grant_test.dart b/pkgs/oauth2/test/authorization_code_grant_test.dart index f74c061b8..8010bcf76 100644 --- a/pkgs/oauth2/test/authorization_code_grant_test.dart +++ b/pkgs/oauth2/test/authorization_code_grant_test.dart @@ -14,8 +14,8 @@ import 'utils.dart'; final redirectUrl = Uri.parse('http://example.com/redirect'); void main() { - ExpectClient client; - oauth2.AuthorizationCodeGrant grant; + late ExpectClient client; + late oauth2.AuthorizationCodeGrant grant; setUp(() { client = ExpectClient(); grant = oauth2.AuthorizationCodeGrant( @@ -223,7 +223,7 @@ void main() { expect(grant.handleAuthorizationCode('auth code'), throwsStateError); }); - test('sends an authorization code request', () { + test('sends an authorization code request', () async { grant.getAuthorizationUrl(redirectUrl); client.expectRequest((request) { expect(request.method, equals('POST')); @@ -249,11 +249,10 @@ void main() { headers: {'content-type': 'application/json'})); }); - expect(grant.handleAuthorizationCode('auth code'), - completion(predicate((client) { - expect(client.credentials.accessToken, equals('access token')); - return true; - }))); + expect( + await grant.handleAuthorizationCode('auth code'), + isA().having((c) => c.credentials.accessToken, + 'credentials.accessToken', 'access token')); }); }); @@ -302,7 +301,8 @@ void main() { completion(equals('access token'))); }); - test('.handleAuthorizationCode sends an authorization code request', () { + test('.handleAuthorizationCode sends an authorization code request', + () async { grant.getAuthorizationUrl(redirectUrl); client.expectRequest((request) { expect(request.method, equals('POST')); @@ -328,11 +328,10 @@ void main() { headers: {'content-type': 'application/json'})); }); - expect(grant.handleAuthorizationCode('auth code'), - completion(predicate((client) { - expect(client.credentials.accessToken, equals('access token')); - return true; - }))); + expect( + await grant.handleAuthorizationCode('auth code'), + isA().having((c) => c.credentials.accessToken, + 'credentials.accessToken', 'access token')); }); }); diff --git a/pkgs/oauth2/test/handle_access_token_response_test.dart b/pkgs/oauth2/test/handle_access_token_response_test.dart index 10aef48f6..456174981 100644 --- a/pkgs/oauth2/test/handle_access_token_response_test.dart +++ b/pkgs/oauth2/test/handle_access_token_response_test.dart @@ -18,7 +18,7 @@ final Uri tokenEndpoint = Uri.parse('https://example.com/token'); final DateTime startTime = DateTime.now(); oauth2.Credentials handle(http.Response response, - {GetParameters getParameters}) => + {GetParameters? getParameters}) => handleAccessTokenResponse( response, tokenEndpoint, startTime, ['scope'], ' ', getParameters: getParameters); @@ -141,7 +141,7 @@ void main() { }); test('with no content-type causes a FormatException', () { - expect(() => handleSuccess(contentType: null), throwsFormatException); + expect(() => handleSuccess(contentType: ''), throwsFormatException); }); test('with a non-JSON content-type causes a FormatException', () { @@ -221,7 +221,7 @@ void main() { 'with expires-in sets the expiration to ten seconds earlier than the ' 'server says', () { var credentials = handleSuccess(expiresIn: 100); - expect(credentials.expiration.millisecondsSinceEpoch, + expect(credentials.expiration?.millisecondsSinceEpoch, startTime.millisecondsSinceEpoch + 90 * 1000); }); diff --git a/pkgs/oauth2/test/utils.dart b/pkgs/oauth2/test/utils.dart index df43af5b4..f66e7df87 100644 --- a/pkgs/oauth2/test/utils.dart +++ b/pkgs/oauth2/test/utils.dart @@ -18,7 +18,7 @@ class ExpectClient extends MockClient { super(fn); factory ExpectClient() { - ExpectClient client; + late ExpectClient client; client = ExpectClient._((request) => client._handleRequest(request)); return client; } From e9436ca6bd20714f547fa53e01655aaec0872fec Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Thu, 4 Mar 2021 11:57:40 -0800 Subject: [PATCH 119/159] Fix lint await_only_futures (dart-lang/oauth2#104) --- pkgs/oauth2/lib/src/client_credentials_grant.dart | 2 +- pkgs/oauth2/lib/src/credentials.dart | 2 +- pkgs/oauth2/lib/src/resource_owner_password_grant.dart | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/oauth2/lib/src/client_credentials_grant.dart b/pkgs/oauth2/lib/src/client_credentials_grant.dart index ab13a3f03..1d79c8063 100644 --- a/pkgs/oauth2/lib/src/client_credentials_grant.dart +++ b/pkgs/oauth2/lib/src/client_credentials_grant.dart @@ -71,7 +71,7 @@ Future clientCredentialsGrant( var response = await httpClient.post(authorizationEndpoint, headers: headers, body: body); - var credentials = await handleAccessTokenResponse(response, + var credentials = handleAccessTokenResponse(response, authorizationEndpoint, startTime, scopes?.toList() ?? [], delimiter, getParameters: getParameters); return Client(credentials, diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index 88a8f45e5..2acc8f2db 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -241,7 +241,7 @@ class Credentials { var response = await httpClient.post(tokenEndpoint, headers: headers, body: body); - var credentials = await handleAccessTokenResponse( + var credentials = handleAccessTokenResponse( response, tokenEndpoint, startTime, scopes, _delimiter, getParameters: _getParameters); diff --git a/pkgs/oauth2/lib/src/resource_owner_password_grant.dart b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart index f54ac85af..b1cc7729a 100644 --- a/pkgs/oauth2/lib/src/resource_owner_password_grant.dart +++ b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart @@ -83,7 +83,7 @@ Future resourceOwnerPasswordGrant( var response = await httpClient.post(authorizationEndpoint, headers: headers, body: body); - var credentials = await handleAccessTokenResponse( + var credentials = handleAccessTokenResponse( response, authorizationEndpoint, startTime, scopes?.toList(), delimiter, getParameters: getParameters); return Client(credentials, From 6311ae31762034285a5bb4b91073f4d9544a6ad4 Mon Sep 17 00:00:00 2001 From: Nate Bosch Date: Thu, 4 Mar 2021 12:21:12 -0800 Subject: [PATCH 120/159] Prepare to publish for stable null safety (dart-lang/oauth2#103) Add CI testing on the oldest supported stable SDK. --- pkgs/oauth2/.github/workflows/test-package.yml | 15 +++++++++------ pkgs/oauth2/CHANGELOG.md | 2 +- pkgs/oauth2/pubspec.yaml | 4 ++-- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/pkgs/oauth2/.github/workflows/test-package.yml b/pkgs/oauth2/.github/workflows/test-package.yml index 0a2a87433..530c86d42 100644 --- a/pkgs/oauth2/.github/workflows/test-package.yml +++ b/pkgs/oauth2/.github/workflows/test-package.yml @@ -13,14 +13,14 @@ env: PUB_ENVIRONMENT: bot.github jobs: - # Check code formatting and static analysis on a single OS (linux) - # against Dart dev. + # Check code formatting and lints against Dart dev, check analyzer warnings + # against the oldest supported SDK. analyze: runs-on: ubuntu-latest strategy: fail-fast: false matrix: - sdk: [dev] + sdk: [2.12.0, dev] steps: - uses: actions/checkout@v2 - uses: dart-lang/setup-dart@v0.3 @@ -31,10 +31,13 @@ jobs: run: dart pub get - name: Check formatting run: dart format --output=none --set-exit-if-changed . - if: always() && steps.install.outcome == 'success' + if: matrix.sdk == 'dev' && steps.install.outcome == 'success' - name: Analyze code run: dart analyze --fatal-infos - if: always() && steps.install.outcome == 'success' + if: matrix.sdk == 'dev' && steps.install.outcome == 'success' + - name: Analyze code + run: dart analyze + if: matrix.sdk != 'dev' && steps.install.outcome == 'success' # Run tests on a matrix consisting of two dimensions: # 1. OS: ubuntu-latest, (macos-latest, windows-latest) @@ -47,7 +50,7 @@ jobs: matrix: # Add macos-latest and/or windows-latest if relevant for this package. os: [ubuntu-latest] - sdk: [dev] + sdk: [2.12.0, dev] steps: - uses: actions/checkout@v2 - uses: dart-lang/setup-dart@v0.3 diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md index dd402ba62..803d654bb 100644 --- a/pkgs/oauth2/CHANGELOG.md +++ b/pkgs/oauth2/CHANGELOG.md @@ -1,4 +1,4 @@ -# 2.0.0-dev +# 2.0.0 * Migrate to null safety. diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index ab42be21f..1ff762d73 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,5 @@ name: oauth2 -version: 2.0.0-dev +version: 2.0.0 homepage: https://github.com/dart-lang/oauth2 description: >- A client library for authenticating with a remote service via OAuth2 on @@ -7,7 +7,7 @@ description: >- OAuth2 credentials. environment: - sdk: '>=2.12.0-0 <3.0.0' + sdk: '>=2.12.0 <3.0.0' dependencies: collection: ^1.15.0 From f52ed91aeddecf7e355d7bc704a649365f5c33eb Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Sat, 20 Mar 2021 19:35:50 -0700 Subject: [PATCH 121/159] Fix formatting (dart-lang/oauth2#106) --- pkgs/oauth2/lib/src/client_credentials_grant.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/lib/src/client_credentials_grant.dart b/pkgs/oauth2/lib/src/client_credentials_grant.dart index 1d79c8063..045d1a086 100644 --- a/pkgs/oauth2/lib/src/client_credentials_grant.dart +++ b/pkgs/oauth2/lib/src/client_credentials_grant.dart @@ -71,8 +71,8 @@ Future clientCredentialsGrant( var response = await httpClient.post(authorizationEndpoint, headers: headers, body: body); - var credentials = handleAccessTokenResponse(response, - authorizationEndpoint, startTime, scopes?.toList() ?? [], delimiter, + var credentials = handleAccessTokenResponse(response, authorizationEndpoint, + startTime, scopes?.toList() ?? [], delimiter, getParameters: getParameters); return Client(credentials, identifier: identifier, secret: secret, httpClient: httpClient); From 374357a5cdee36108cbc388c7807c4e97b848de9 Mon Sep 17 00:00:00 2001 From: Franklin Yow <58489007+franklinyow@users.noreply.github.com> Date: Fri, 2 Apr 2021 16:42:06 -0700 Subject: [PATCH 122/159] Update LICENSE --- pkgs/oauth2/LICENSE | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/LICENSE b/pkgs/oauth2/LICENSE index 5c60afea3..000cd7bec 100644 --- a/pkgs/oauth2/LICENSE +++ b/pkgs/oauth2/LICENSE @@ -1,4 +1,5 @@ -Copyright 2014, the Dart project authors. All rights reserved. +Copyright 2014, the Dart project authors. + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -9,7 +10,7 @@ met: copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Google Inc. nor the names of its + * Neither the name of Google LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. From b71cee3110768a46af7e52e9cb36baefb2ec4d85 Mon Sep 17 00:00:00 2001 From: RaphaelStAlme Date: Wed, 7 Apr 2021 11:35:59 +0200 Subject: [PATCH 123/159] Fix expiresIn (convert String to Int) --- .../lib/src/handle_access_token_response.dart | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart index 370ffcba2..ba78d9428 100644 --- a/pkgs/oauth2/lib/src/handle_access_token_response.dart +++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart @@ -68,9 +68,18 @@ Credentials handleAccessTokenResponse(http.Response response, Uri tokenEndpoint, } var expiresIn = parameters['expires_in']; - if (expiresIn != null && expiresIn is! int) { - throw FormatException( - 'parameter "expires_in" was not an int, was "$expiresIn"'); + if (expiresIn != null) { + if (expiresIn is String) { + try { + expiresIn = double.tryParse(expiresIn).toInt(); + } catch (e) { + throw FormatException( + 'parameter "expires_in" was not an int, and given string cant not be parsed to int : "$expiresIn"'); + } + } else if (expiresIn is! int) { + throw FormatException( + 'parameter "expires_in" was not an int, was of type ${expiresIn.runtimeType.toString()} value : "$expiresIn"'); + } } for (var name in ['refresh_token', 'id_token', 'scope']) { From 36e7b993d3817a3a0a6f9de8ba945d6a299ca502 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Saint-Alme?= <51606138+RaphaelStAlme@users.noreply.github.com> Date: Wed, 7 Apr 2021 12:14:37 +0200 Subject: [PATCH 124/159] Update handle_access_token_response.dart --- pkgs/oauth2/lib/src/handle_access_token_response.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart index ba78d9428..83d57fa15 100644 --- a/pkgs/oauth2/lib/src/handle_access_token_response.dart +++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart @@ -71,7 +71,7 @@ Credentials handleAccessTokenResponse(http.Response response, Uri tokenEndpoint, if (expiresIn != null) { if (expiresIn is String) { try { - expiresIn = double.tryParse(expiresIn).toInt(); + expiresIn = double.tryParse(expiresIn)!.toInt(); } catch (e) { throw FormatException( 'parameter "expires_in" was not an int, and given string cant not be parsed to int : "$expiresIn"'); From 620e263457ef284001995dc8d04e474a80ada0fe Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Sat, 5 Jun 2021 13:28:17 -0700 Subject: [PATCH 125/159] Add dependabot --- pkgs/oauth2/.github/dependabot.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 pkgs/oauth2/.github/dependabot.yml diff --git a/pkgs/oauth2/.github/dependabot.yml b/pkgs/oauth2/.github/dependabot.yml new file mode 100644 index 000000000..430a85e7d --- /dev/null +++ b/pkgs/oauth2/.github/dependabot.yml @@ -0,0 +1,11 @@ +# Set update schedule for GitHub Actions +# See https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/keeping-your-actions-up-to-date-with-dependabot + +version: 2 +updates: + +- package-ecosystem: "github-actions" + directory: "/" + schedule: + # Check for updates to GitHub Actions every weekday + interval: "daily" From fc3bc020d67ce5e9c97b52f7a3b6f4cbb0b6fba6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 5 Jun 2021 13:36:31 -0700 Subject: [PATCH 126/159] Bump dart-lang/setup-dart from 0.3 to 1 (dart-lang/oauth2#115) Bumps [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart) from 0.3 to 1. - [Release notes](https://github.com/dart-lang/setup-dart/releases) - [Changelog](https://github.com/dart-lang/setup-dart/blob/main/CHANGELOG.md) - [Commits](https://github.com/dart-lang/setup-dart/compare/v0.3...v1) --- updated-dependencies: - dependency-name: dart-lang/setup-dart dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pkgs/oauth2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/.github/workflows/test-package.yml b/pkgs/oauth2/.github/workflows/test-package.yml index 530c86d42..c7b634c14 100644 --- a/pkgs/oauth2/.github/workflows/test-package.yml +++ b/pkgs/oauth2/.github/workflows/test-package.yml @@ -23,7 +23,7 @@ jobs: sdk: [2.12.0, dev] steps: - uses: actions/checkout@v2 - - uses: dart-lang/setup-dart@v0.3 + - uses: dart-lang/setup-dart@v1 with: sdk: ${{ matrix.sdk }} - id: install @@ -53,7 +53,7 @@ jobs: sdk: [2.12.0, dev] steps: - uses: actions/checkout@v2 - - uses: dart-lang/setup-dart@v0.3 + - uses: dart-lang/setup-dart@v1 with: sdk: ${{ matrix.sdk }} - id: install From 5b080b6c917ac0a2f5321e7cd186258ee593e1bf Mon Sep 17 00:00:00 2001 From: Eduardo Vital Alencar Cunha Date: Mon, 19 Jul 2021 10:09:06 -0300 Subject: [PATCH 127/159] Correct in the code comments the name of the FormatException (dart-lang/oauth2#117) - FormatException was mistakenly referenced as FormatError --- pkgs/oauth2/lib/src/authorization_code_grant.dart | 6 +++--- pkgs/oauth2/lib/src/client.dart | 2 +- pkgs/oauth2/lib/src/credentials.dart | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkgs/oauth2/lib/src/authorization_code_grant.dart b/pkgs/oauth2/lib/src/authorization_code_grant.dart index 4a7ef8a3b..fafa5f7da 100644 --- a/pkgs/oauth2/lib/src/authorization_code_grant.dart +++ b/pkgs/oauth2/lib/src/authorization_code_grant.dart @@ -221,10 +221,10 @@ class AuthorizationCodeGrant { /// [getAuthorizationUrl] is called, or to call it after /// [handleAuthorizationCode] is called. /// - /// Throws [FormatError] if [parameters] is invalid according to the OAuth2 + /// Throws [FormatException] if [parameters] is invalid according to the OAuth2 /// spec or if the authorization server otherwise provides invalid responses. /// If `state` was passed to [getAuthorizationUrl], this will throw a - /// [FormatError] if the `state` parameter doesn't match the original value. + /// [FormatException] if the `state` parameter doesn't match the original value. /// /// Throws [AuthorizationException] if the authorization fails. Future handleAuthorizationResponse( @@ -273,7 +273,7 @@ class AuthorizationCodeGrant { /// [getAuthorizationUrl] is called, or to call it after /// [handleAuthorizationCode] is called. /// - /// Throws [FormatError] if the authorization server provides invalid + /// Throws [FormatException] if the authorization server provides invalid /// responses while retrieving credentials. /// /// Throws [AuthorizationException] if the authorization fails. diff --git a/pkgs/oauth2/lib/src/client.dart b/pkgs/oauth2/lib/src/client.dart index dc50e43ca..1dd2282fb 100644 --- a/pkgs/oauth2/lib/src/client.dart +++ b/pkgs/oauth2/lib/src/client.dart @@ -142,7 +142,7 @@ class Client extends http.BaseClient { /// /// This will throw a [StateError] if the [Credentials] can't be refreshed, an /// [AuthorizationException] if refreshing the credentials fails, or a - /// [FormatError] if the authorization server returns invalid responses. + /// [FormatException] if the authorization server returns invalid responses. /// /// You may request different scopes than the default by passing in /// [newScopes]. These must be a subset of the scopes in the diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index 2acc8f2db..2c2ac515a 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -201,7 +201,7 @@ class Credentials { /// This throws an [ArgumentError] if [secret] is passed without [identifier], /// 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. + /// [FormatException] if the authorization server returns invalid responses. Future refresh( {String? identifier, String? secret, From cbdf3f91f2f148d9161568be7cdeae11cf77ac24 Mon Sep 17 00:00:00 2001 From: Jonas Finnemann Jensen Date: Mon, 19 Jul 2021 15:13:12 +0200 Subject: [PATCH 128/159] handle expires_in when encoded as string --- pkgs/oauth2/lib/src/handle_access_token_response.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart index 83d57fa15..4c9281081 100644 --- a/pkgs/oauth2/lib/src/handle_access_token_response.dart +++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart @@ -71,14 +71,14 @@ Credentials handleAccessTokenResponse(http.Response response, Uri tokenEndpoint, if (expiresIn != null) { if (expiresIn is String) { try { - expiresIn = double.tryParse(expiresIn)!.toInt(); - } catch (e) { + expiresIn = double.parse(expiresIn).toInt(); + } on FormatException { throw FormatException( - 'parameter "expires_in" was not an int, and given string cant not be parsed to int : "$expiresIn"'); + 'parameter "expires_in" could not be parsed as in, was: "$expiresIn"'); } } else if (expiresIn is! int) { throw FormatException( - 'parameter "expires_in" was not an int, was of type ${expiresIn.runtimeType.toString()} value : "$expiresIn"'); + 'parameter "expires_in" was not an int, was: "$expiresIn"'); } } From 2c63431cfeaf9658391f3e5c4b7bd512593206ab Mon Sep 17 00:00:00 2001 From: Jonas Finnemann Jensen Date: Mon, 19 Jul 2021 15:20:21 +0200 Subject: [PATCH 129/159] Added a test covering "expires_in" as string --- pkgs/oauth2/test/handle_access_token_response_test.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkgs/oauth2/test/handle_access_token_response_test.dart b/pkgs/oauth2/test/handle_access_token_response_test.dart index 456174981..1663d3fbb 100644 --- a/pkgs/oauth2/test/handle_access_token_response_test.dart +++ b/pkgs/oauth2/test/handle_access_token_response_test.dart @@ -225,6 +225,12 @@ void main() { startTime.millisecondsSinceEpoch + 90 * 1000); }); + test('with expires-in encoded as string', () { + var credentials = handleSuccess(expiresIn: '110'); + expect(credentials.expiration?.millisecondsSinceEpoch, + startTime.millisecondsSinceEpoch + 100 * 1000); + }); + test('with a non-string refresh token throws a FormatException', () { expect(() => handleSuccess(refreshToken: 12), throwsFormatException); }); From 402b291dbb1b002c15c414ffb8102d8553607367 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Mar 2022 11:16:06 -0800 Subject: [PATCH 130/159] Bump actions/checkout from 2 to 3 (dart-lang/oauth2#125) Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pkgs/oauth2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/.github/workflows/test-package.yml b/pkgs/oauth2/.github/workflows/test-package.yml index c7b634c14..7d3ad4827 100644 --- a/pkgs/oauth2/.github/workflows/test-package.yml +++ b/pkgs/oauth2/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [2.12.0, dev] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dart-lang/setup-dart@v1 with: sdk: ${{ matrix.sdk }} @@ -52,7 +52,7 @@ jobs: os: [ubuntu-latest] sdk: [2.12.0, dev] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dart-lang/setup-dart@v1 with: sdk: ${{ matrix.sdk }} From 6006643b4226057262c9cb05220d1e549af6accf Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Fri, 29 Apr 2022 16:55:38 -0700 Subject: [PATCH 131/159] Update pubspec.yaml --- pkgs/oauth2/pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index 1ff762d73..492ef00ff 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,11 +1,11 @@ name: oauth2 version: 2.0.0 -homepage: https://github.com/dart-lang/oauth2 description: >- A client library for authenticating with a remote service via OAuth2 on behalf of a user, and making authorized HTTP requests with the user's OAuth2 credentials. - +repository: https://github.com/dart-lang/oauth2 + environment: sdk: '>=2.12.0 <3.0.0' From 1d50b1e6c39060ecd78239728c30038880daa8a7 Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Fri, 29 Apr 2022 23:58:40 +0000 Subject: [PATCH 132/159] rev to a dev version --- pkgs/oauth2/CHANGELOG.md | 5 +++++ pkgs/oauth2/pubspec.yaml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md index 803d654bb..642f05db0 100644 --- a/pkgs/oauth2/CHANGELOG.md +++ b/pkgs/oauth2/CHANGELOG.md @@ -1,3 +1,8 @@ +# 2.0.1-dev + +* Handle `expires_in` when encoded as string. +* Populate the pubspec `repository` field. + # 2.0.0 * Migrate to null safety. diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index 492ef00ff..a07f64808 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,5 @@ name: oauth2 -version: 2.0.0 +version: 2.0.1-dev description: >- A client library for authenticating with a remote service via OAuth2 on behalf of a user, and making authorized HTTP requests with the user's From b8a0a575ef792d422d47b1ca68f99c74cad6fce5 Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Tue, 25 Oct 2022 00:54:31 +0000 Subject: [PATCH 133/159] use package:lints; rev pubspec version (dart-lang/oauth2#134) * use package:lints; rev pubspec version * use package:lints 2.0.0 * rev CI to 2.17 * use super initializers --- pkgs/oauth2/.github/dependabot.yaml | 8 ++++ pkgs/oauth2/.github/dependabot.yml | 11 ----- .../oauth2/.github/workflows/test-package.yml | 12 +++--- pkgs/oauth2/CHANGELOG.md | 3 +- pkgs/oauth2/LICENSE | 2 +- pkgs/oauth2/README.md | 6 +++ pkgs/oauth2/analysis_options.yaml | 41 +++++++++++++++++- pkgs/oauth2/example/main.dart | 11 ++--- pkgs/oauth2/lib/oauth2.dart | 6 +-- .../lib/src/authorization_code_grant.dart | 33 ++++++++------- pkgs/oauth2/lib/src/credentials.dart | 35 ++++++++++------ .../lib/src/handle_access_token_response.dart | 32 +++++++------- pkgs/oauth2/lib/src/parameters.dart | 4 +- .../src/resource_owner_password_grant.dart | 2 +- pkgs/oauth2/lib/src/utils.dart | 4 +- pkgs/oauth2/pubspec.yaml | 6 +-- .../test/authorization_code_grant_test.dart | 42 +++++++++++-------- .../test/client_credentials_grant_test.dart | 7 ++-- pkgs/oauth2/test/client_test.dart | 35 +++++++++------- pkgs/oauth2/test/credentials_test.dart | 11 ++--- .../handle_access_token_response_test.dart | 17 ++++---- .../resource_owner_password_grant_test.dart | 9 ++-- pkgs/oauth2/test/utils.dart | 4 +- 23 files changed, 208 insertions(+), 133 deletions(-) create mode 100644 pkgs/oauth2/.github/dependabot.yaml delete mode 100644 pkgs/oauth2/.github/dependabot.yml diff --git a/pkgs/oauth2/.github/dependabot.yaml b/pkgs/oauth2/.github/dependabot.yaml new file mode 100644 index 000000000..214481934 --- /dev/null +++ b/pkgs/oauth2/.github/dependabot.yaml @@ -0,0 +1,8 @@ +# Dependabot configuration file. +version: 2 + +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" diff --git a/pkgs/oauth2/.github/dependabot.yml b/pkgs/oauth2/.github/dependabot.yml deleted file mode 100644 index 430a85e7d..000000000 --- a/pkgs/oauth2/.github/dependabot.yml +++ /dev/null @@ -1,11 +0,0 @@ -# Set update schedule for GitHub Actions -# See https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/keeping-your-actions-up-to-date-with-dependabot - -version: 2 -updates: - -- package-ecosystem: "github-actions" - directory: "/" - schedule: - # Check for updates to GitHub Actions every weekday - interval: "daily" diff --git a/pkgs/oauth2/.github/workflows/test-package.yml b/pkgs/oauth2/.github/workflows/test-package.yml index 7d3ad4827..eeb824ff5 100644 --- a/pkgs/oauth2/.github/workflows/test-package.yml +++ b/pkgs/oauth2/.github/workflows/test-package.yml @@ -20,10 +20,10 @@ jobs: strategy: fail-fast: false matrix: - sdk: [2.12.0, dev] + sdk: [2.17.0, dev] steps: - - uses: actions/checkout@v3 - - uses: dart-lang/setup-dart@v1 + - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b + - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d with: sdk: ${{ matrix.sdk }} - id: install @@ -50,10 +50,10 @@ jobs: matrix: # Add macos-latest and/or windows-latest if relevant for this package. os: [ubuntu-latest] - sdk: [2.12.0, dev] + sdk: [2.17.0, dev] steps: - - uses: actions/checkout@v3 - - uses: dart-lang/setup-dart@v1 + - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b + - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d with: sdk: ${{ matrix.sdk }} - id: install diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md index 642f05db0..0c0deb205 100644 --- a/pkgs/oauth2/CHANGELOG.md +++ b/pkgs/oauth2/CHANGELOG.md @@ -1,7 +1,8 @@ -# 2.0.1-dev +# 2.0.1 * Handle `expires_in` when encoded as string. * Populate the pubspec `repository` field. +* Increase the minimum Dart SDK to `2.17.0`. # 2.0.0 diff --git a/pkgs/oauth2/LICENSE b/pkgs/oauth2/LICENSE index 000cd7bec..162572a44 100644 --- a/pkgs/oauth2/LICENSE +++ b/pkgs/oauth2/LICENSE @@ -1,4 +1,4 @@ -Copyright 2014, the Dart project authors. +Copyright 2014, the Dart project authors. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are diff --git a/pkgs/oauth2/README.md b/pkgs/oauth2/README.md index a536740a3..196b9f789 100644 --- a/pkgs/oauth2/README.md +++ b/pkgs/oauth2/README.md @@ -1,7 +1,13 @@ +[![Dart CI](https://github.com/dart-lang/oauth2/actions/workflows/test-package.yml/badge.svg)](https://github.com/dart-lang/oauth2/actions/workflows/test-package.yml) +[![pub package](https://img.shields.io/pub/v/oauth2.svg)](https://pub.dev/packages/oauth2) +[![package publisher](https://img.shields.io/pub/publisher/oauth2.svg)](https://pub.dev/packages/oauth2/publisher) + A client library for authenticating with a remote service via OAuth2 on behalf of a user, and making authorized HTTP requests with the user's OAuth2 credentials. +## About OAuth2 + OAuth2 allows a client (the program using this library) to access and manipulate a resource that's owned by a resource owner (the end user) and lives on a remote server. The client directs the resource owner to an authorization server diff --git a/pkgs/oauth2/analysis_options.yaml b/pkgs/oauth2/analysis_options.yaml index 108d1058a..c8bc59c2c 100644 --- a/pkgs/oauth2/analysis_options.yaml +++ b/pkgs/oauth2/analysis_options.yaml @@ -1 +1,40 @@ -include: package:pedantic/analysis_options.yaml +include: package:lints/recommended.yaml + +analyzer: + language: + strict-casts: true + strict-raw-types: true + +linter: + rules: + - always_declare_return_types + - avoid_catching_errors + - avoid_dynamic_calls + - avoid_private_typedef_functions + - avoid_unused_constructor_parameters + - avoid_void_async + - cancel_subscriptions + - directives_ordering + - literal_only_boolean_expressions + - no_adjacent_strings_in_list + - no_runtimeType_toString + - omit_local_variable_types + - only_throw_errors + - package_api_docs + - prefer_asserts_in_initializer_lists + - prefer_const_constructors + - prefer_const_declarations + - prefer_relative_imports + - prefer_single_quotes + - sort_pub_dependencies + - test_types_in_equals + - throw_in_finally + - type_annotate_public_apis + - unawaited_futures + - unnecessary_await_in_return + - unnecessary_lambdas + - unnecessary_parenthesis + - unnecessary_statements + - use_is_even_rather_than_modulo + - use_string_buffers + - use_super_parameters diff --git a/pkgs/oauth2/example/main.dart b/pkgs/oauth2/example/main.dart index 4bd3cbc45..9db62fc2f 100644 --- a/pkgs/oauth2/example/main.dart +++ b/pkgs/oauth2/example/main.dart @@ -19,8 +19,8 @@ final tokenEndpoint = Uri.parse('http://example.com/oauth2/token'); // available may not be able to make sure the client secret is kept a // secret. This is fine; OAuth2 servers generally won't rely on knowing // with certainty that a client is who it claims to be. -final identifier = 'my client identifier'; -final secret = 'my client secret'; +const identifier = 'my client identifier'; +const secret = 'my client secret'; // This is a URL on your application's server. The authorization server // will redirect the resource owner here once they've authorized the @@ -53,8 +53,9 @@ Future createClient() async { identifier, authorizationEndpoint, tokenEndpoint, secret: secret); - // A URL on the authorization server (authorizationEndpoint with some additional - // query parameters). Scopes and state can optionally be passed into this method. + // A URL on the authorization server (authorizationEndpoint with some + // additional query parameters). Scopes and state can optionally be passed + // into this method. var authorizationUrl = grant.getAuthorizationUrl(redirectUrl); // Redirect the resource owner to the authorization URL. Once the resource @@ -69,7 +70,7 @@ Future createClient() async { // Once the user is redirected to `redirectUrl`, pass the query parameters to // the AuthorizationCodeGrant. It will validate them and extract the // authorization code to create a new Client. - return await grant.handleAuthorizationResponse(responseUrl.queryParameters); + return grant.handleAuthorizationResponse(responseUrl.queryParameters); } void main() async { diff --git a/pkgs/oauth2/lib/oauth2.dart b/pkgs/oauth2/lib/oauth2.dart index dfd2d57cf..45efc5c1b 100644 --- a/pkgs/oauth2/lib/oauth2.dart +++ b/pkgs/oauth2/lib/oauth2.dart @@ -3,9 +3,9 @@ // BSD-style license that can be found in the LICENSE file. export 'src/authorization_code_grant.dart'; -export 'src/client_credentials_grant.dart'; -export 'src/resource_owner_password_grant.dart'; +export 'src/authorization_exception.dart'; export 'src/client.dart'; +export 'src/client_credentials_grant.dart'; export 'src/credentials.dart'; -export 'src/authorization_exception.dart'; export 'src/expiration_exception.dart'; +export 'src/resource_owner_password_grant.dart'; diff --git a/pkgs/oauth2/lib/src/authorization_code_grant.dart b/pkgs/oauth2/lib/src/authorization_code_grant.dart index fafa5f7da..fac56ba0d 100644 --- a/pkgs/oauth2/lib/src/authorization_code_grant.dart +++ b/pkgs/oauth2/lib/src/authorization_code_grant.dart @@ -10,8 +10,8 @@ import 'package:crypto/crypto.dart'; import 'package:http/http.dart' as http; import 'package:http_parser/http_parser.dart'; -import 'client.dart'; import 'authorization_exception.dart'; +import 'client.dart'; import 'credentials.dart'; import 'handle_access_token_response.dart'; import 'parameters.dart'; @@ -107,7 +107,8 @@ class AuthorizationCodeGrant { static const String _charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~'; - /// The PKCE code verifier. Will be generated if one is not provided in the constructor. + /// The PKCE code verifier. Will be generated if one is not provided in the + /// constructor. final String _codeVerifier; /// Creates a new grant. @@ -126,8 +127,8 @@ class AuthorizationCodeGrant { /// [onCredentialsRefreshed] will be called by the constructed [Client] /// whenever the credentials are refreshed. /// - /// [codeVerifier] String to be used as PKCE code verifier. If none is provided a - /// random codeVerifier will be generated. + /// [codeVerifier] String to be used as PKCE code verifier. If none is + /// provided a random codeVerifier will be generated. /// The codeVerifier must meet requirements specified in [RFC 7636]. /// /// [RFC 7636]: https://tools.ietf.org/html/rfc7636#section-4.1 @@ -221,10 +222,11 @@ class AuthorizationCodeGrant { /// [getAuthorizationUrl] is called, or to call it after /// [handleAuthorizationCode] is called. /// - /// Throws [FormatException] if [parameters] is invalid according to the OAuth2 - /// spec or if the authorization server otherwise provides invalid responses. - /// If `state` was passed to [getAuthorizationUrl], this will throw a - /// [FormatException] if the `state` parameter doesn't match the original value. + /// Throws [FormatException] if [parameters] is invalid according to the + /// OAuth2 spec or if the authorization server otherwise provides invalid + /// responses. If `state` was passed to [getAuthorizationUrl], this will throw + /// a [FormatException] if the `state` parameter doesn't match the original + /// value. /// /// Throws [AuthorizationException] if the authorization fails. Future handleAuthorizationResponse( @@ -259,7 +261,7 @@ class AuthorizationCodeGrant { '"code".'); } - return await _handleAuthorizationCode(parameters['code']); + return _handleAuthorizationCode(parameters['code']); } /// Processes an authorization code directly. @@ -285,7 +287,7 @@ class AuthorizationCodeGrant { } _state = _State.finished; - return await _handleAuthorizationCode(authorizationCode); + return _handleAuthorizationCode(authorizationCode); } /// This works just like [handleAuthorizationCode], except it doesn't validate @@ -326,11 +328,12 @@ class AuthorizationCodeGrant { onCredentialsRefreshed: _onCredentialsRefreshed); } - /// Randomly generate a 128 character string to be used as the PKCE code verifier - static String _createCodeVerifier() { - return List.generate( - 128, (i) => _charset[Random.secure().nextInt(_charset.length)]).join(); - } + // Randomly generate a 128 character string to be used as the PKCE code + // verifier. + static String _createCodeVerifier() => List.generate( + 128, + (i) => _charset[Random.secure().nextInt(_charset.length)], + ).join(); /// Closes the grant and frees its resources. /// diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index 2c2ac515a..459e63e5c 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -128,12 +128,12 @@ class Credentials { /// /// Throws a [FormatException] if the JSON is incorrectly formatted. factory Credentials.fromJson(String json) { - void validate(condition, message) { + void validate(bool condition, message) { if (condition) return; throw FormatException('Failed to load credentials: $message.\n\n$json'); } - var parsed; + dynamic parsed; try { parsed = jsonDecode(json); } on FormatException { @@ -141,12 +141,15 @@ class Credentials { } validate(parsed is Map, 'was not a JSON map'); + + parsed = parsed as Map; validate(parsed.containsKey('accessToken'), 'did not contain required field "accessToken"'); validate( - parsed['accessToken'] is String, - 'required field "accessToken" was not a string, was ' - '${parsed["accessToken"]}'); + parsed['accessToken'] is String, + 'required field "accessToken" was not a string, was ' + '${parsed["accessToken"]}', + ); for (var stringField in ['refreshToken', 'idToken', 'tokenEndpoint']) { var value = parsed[stringField]; @@ -159,22 +162,28 @@ class Credentials { 'field "scopes" was not a list, was "$scopes"'); var tokenEndpoint = parsed['tokenEndpoint']; + Uri? tokenEndpointUri; if (tokenEndpoint != null) { - tokenEndpoint = Uri.parse(tokenEndpoint); + tokenEndpointUri = Uri.parse(tokenEndpoint as String); } + var expiration = parsed['expiration']; + DateTime? expirationDateTime; if (expiration != null) { validate(expiration is int, 'field "expiration" was not an int, was "$expiration"'); - expiration = DateTime.fromMillisecondsSinceEpoch(expiration); + expiration = expiration as int; + expirationDateTime = DateTime.fromMillisecondsSinceEpoch(expiration); } - return Credentials(parsed['accessToken'], - refreshToken: parsed['refreshToken'], - idToken: parsed['idToken'], - tokenEndpoint: tokenEndpoint, - scopes: (scopes as List).map((scope) => scope as String), - expiration: expiration); + return Credentials( + parsed['accessToken'] as String, + refreshToken: parsed['refreshToken'] as String?, + idToken: parsed['idToken'] as String?, + tokenEndpoint: tokenEndpointUri, + scopes: (scopes as List).map((scope) => scope as String), + expiration: expirationDateTime, + ); } /// Serializes a set of credentials to JSON. diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart index 4c9281081..931ae9dd7 100644 --- a/pkgs/oauth2/lib/src/handle_access_token_response.dart +++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart @@ -5,8 +5,8 @@ import 'package:http/http.dart' as http; import 'package:http_parser/http_parser.dart'; -import 'credentials.dart'; import 'authorization_exception.dart'; +import 'credentials.dart'; import 'parameters.dart'; /// The amount of time to add as a "grace period" for credential expiration. @@ -43,7 +43,7 @@ Credentials handleAccessTokenResponse(http.Response response, Uri tokenEndpoint, var contentTypeString = response.headers['content-type']; if (contentTypeString == null) { - throw FormatException('Missing Content-Type string.'); + throw const FormatException('Missing Content-Type string.'); } var parameters = @@ -62,7 +62,7 @@ Credentials handleAccessTokenResponse(http.Response response, Uri tokenEndpoint, // TODO(nweiz): support the "mac" token type // (http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01) - if (parameters['token_type'].toLowerCase() != 'bearer') { + if ((parameters['token_type'] as String).toLowerCase() != 'bearer') { throw FormatException( '"$tokenEndpoint": unknown token type "${parameters['token_type']}"'); } @@ -95,14 +95,16 @@ Credentials handleAccessTokenResponse(http.Response response, Uri tokenEndpoint, var expiration = expiresIn == null ? null - : startTime.add(Duration(seconds: expiresIn) - _expirationGrace); - - return Credentials(parameters['access_token'], - refreshToken: parameters['refresh_token'], - idToken: parameters['id_token'], - tokenEndpoint: tokenEndpoint, - scopes: scopes, - expiration: expiration); + : startTime.add(Duration(seconds: expiresIn as int) - _expirationGrace); + + return Credentials( + parameters['access_token'] as String, + refreshToken: parameters['refresh_token'] as String?, + idToken: parameters['id_token'] as String?, + tokenEndpoint: tokenEndpoint, + scopes: scopes, + expiration: expiration, + ); } on FormatException catch (e) { throw FormatException('Invalid OAuth response for "$tokenEndpoint": ' '${e.message}.\n\n${response.body}'); @@ -133,7 +135,7 @@ void _handleErrorResponse( var parameters = getParameters(contentType, response.body); if (!parameters.containsKey('error')) { - throw FormatException('did not contain required parameter "error"'); + throw const FormatException('did not contain required parameter "error"'); } else if (parameters['error'] is! String) { throw FormatException('required parameter "error" was not a string, was ' '"${parameters["error"]}"'); @@ -147,8 +149,8 @@ void _handleErrorResponse( } } - var description = parameters['error_description']; - var uriString = parameters['error_uri']; + var uriString = parameters['error_uri'] as String?; var uri = uriString == null ? null : Uri.parse(uriString); - throw AuthorizationException(parameters['error'], description, uri); + var description = parameters['error_description'] as String?; + throw AuthorizationException(parameters['error'] as String, description, uri); } diff --git a/pkgs/oauth2/lib/src/parameters.dart b/pkgs/oauth2/lib/src/parameters.dart index 789f05618..ecc655978 100644 --- a/pkgs/oauth2/lib/src/parameters.dart +++ b/pkgs/oauth2/lib/src/parameters.dart @@ -10,8 +10,8 @@ import 'package:http_parser/http_parser.dart'; typedef GetParameters = Map Function( MediaType? contentType, String body); -/// Parses parameters from a response with a JSON body, as per the [OAuth2 -/// spec][]. +/// Parses parameters from a response with a JSON body, as per the +/// [OAuth2 spec][]. /// /// [OAuth2 spec]: https://tools.ietf.org/html/rfc6749#section-5.1 Map parseJsonParameters(MediaType? contentType, String body) { diff --git a/pkgs/oauth2/lib/src/resource_owner_password_grant.dart b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart index b1cc7729a..96fb5037b 100644 --- a/pkgs/oauth2/lib/src/resource_owner_password_grant.dart +++ b/pkgs/oauth2/lib/src/resource_owner_password_grant.dart @@ -8,9 +8,9 @@ import 'package:http/http.dart' as http; import 'package:http_parser/http_parser.dart'; import 'client.dart'; +import 'credentials.dart'; import 'handle_access_token_response.dart'; import 'utils.dart'; -import 'credentials.dart'; /// Obtains credentials using a [resource owner password grant](https://tools.ietf.org/html/rfc6749#section-1.3.3). /// diff --git a/pkgs/oauth2/lib/src/utils.dart b/pkgs/oauth2/lib/src/utils.dart index 50a925a6e..2a22b9fa5 100644 --- a/pkgs/oauth2/lib/src/utils.dart +++ b/pkgs/oauth2/lib/src/utils.dart @@ -10,6 +10,6 @@ Uri addQueryParameters(Uri url, Map parameters) => url.replace( queryParameters: Map.from(url.queryParameters)..addAll(parameters)); String basicAuthHeader(String identifier, String secret) { - var userPass = Uri.encodeFull(identifier) + ':' + Uri.encodeFull(secret); - return 'Basic ' + base64Encode(ascii.encode(userPass)); + var userPass = '${Uri.encodeFull(identifier)}:${Uri.encodeFull(secret)}'; + return 'Basic ${base64Encode(ascii.encode(userPass))}'; } diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index a07f64808..bb43bb7d3 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,5 @@ name: oauth2 -version: 2.0.1-dev +version: 2.0.1 description: >- A client library for authenticating with a remote service via OAuth2 on behalf of a user, and making authorized HTTP requests with the user's @@ -7,7 +7,7 @@ description: >- repository: https://github.com/dart-lang/oauth2 environment: - sdk: '>=2.12.0 <3.0.0' + sdk: '>=2.17.0 <3.0.0' dependencies: collection: ^1.15.0 @@ -16,5 +16,5 @@ dependencies: http_parser: ^4.0.0 dev_dependencies: - pedantic: ^1.10.0 + lints: ^2.0.0 test: ^1.16.0 diff --git a/pkgs/oauth2/test/authorization_code_grant_test.dart b/pkgs/oauth2/test/authorization_code_grant_test.dart index 8010bcf76..b4807bc2b 100644 --- a/pkgs/oauth2/test/authorization_code_grant_test.dart +++ b/pkgs/oauth2/test/authorization_code_grant_test.dart @@ -5,9 +5,9 @@ import 'dart:async'; import 'dart:convert'; -import 'package:test/test.dart'; import 'package:http/http.dart' as http; import 'package:oauth2/oauth2.dart' as oauth2; +import 'package:test/test.dart'; import 'utils.dart'; @@ -55,9 +55,10 @@ void main() { }); test('builds the correct URL with passed in code verifier', () { - final codeVerifier = - 'it1shei7LooGoh3looxaa4sieveijeib2zecauz2oo8aebae5aehee0ahPirewoh5Bo6Maexooqui3uL2si6ahweiv7shauc1shahxooveoB3aeyahsaiye0Egh3raix'; - final expectedCodeChallenge = + const codeVerifier = + 'it1shei7LooGoh3looxaa4sieveijeib2zecauz2oo8aebae5aehee0ahPirewoh' + '5Bo6Maexooqui3uL2si6ahweiv7shauc1shahxooveoB3aeyahsaiye0Egh3raix'; + const expectedCodeChallenge = 'EjfFMv8TFPd3GuNxAn5COhlWBGpfZLimHett7ypJfJ0'; var grant = oauth2.AuthorizationCodeGrant( 'identifier', @@ -349,8 +350,9 @@ void main() { }); grant.getAuthorizationUrl(redirectUrl); - client.expectRequest((request) { - return Future.value(http.Response( + client.expectRequest( + (request) => Future.value( + http.Response( jsonEncode({ 'access_token': 'access token', 'token_type': 'bearer', @@ -358,22 +360,28 @@ void main() { 'refresh_token': 'refresh token', }), 200, - headers: {'content-type': 'application/json'})); - }); + headers: {'content-type': 'application/json'}, + ), + ), + ); var oauth2Client = await grant.handleAuthorizationCode('auth code'); - client.expectRequest((request) { - return Future.value(http.Response( - jsonEncode( - {'access_token': 'new access token', 'token_type': 'bearer'}), + client.expectRequest( + (request) => Future.value( + http.Response( + jsonEncode({ + 'access_token': 'new access token', + 'token_type': 'bearer', + }), 200, - headers: {'content-type': 'application/json'})); - }); + headers: {'content-type': 'application/json'}, + ), + ), + ); - client.expectRequest((request) { - return Future.value(http.Response('good job', 200)); - }); + client.expectRequest( + (request) => Future.value(http.Response('good job', 200))); await oauth2Client.read(Uri.parse('http://example.com/resource')); diff --git a/pkgs/oauth2/test/client_credentials_grant_test.dart b/pkgs/oauth2/test/client_credentials_grant_test.dart index 9e27c9ed5..67c71a51a 100644 --- a/pkgs/oauth2/test/client_credentials_grant_test.dart +++ b/pkgs/oauth2/test/client_credentials_grant_test.dart @@ -19,11 +19,12 @@ final success = jsonEncode({ 'refresh_token': 'tGzv3JOkF0XG5Qx2TlKWIA', }); -var auth = 'Basic Y2xpZW50OnNlY3JldA=='; -var authEndpoint = Uri.parse('https://example.com'); +String auth = 'Basic Y2xpZW50OnNlY3JldA=='; +Uri authEndpoint = Uri.parse('https://example.com'); void main() { - var expectClient; + late ExpectClient expectClient; + setUp(() => expectClient = ExpectClient()); group('basic', () { diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart index 5960cbd75..b583f8c7c 100644 --- a/pkgs/oauth2/test/client_test.dart +++ b/pkgs/oauth2/test/client_test.dart @@ -16,12 +16,13 @@ final Uri requestUri = Uri.parse('http://example.com/resource'); final Uri tokenEndpoint = Uri.parse('http://example.com/token'); void main() { - var httpClient; + late ExpectClient httpClient; + setUp(() => httpClient = ExpectClient()); group('with expired credentials', () { test("that can't be refreshed throws an ExpirationException on send", () { - var expiration = DateTime.now().subtract(Duration(hours: 1)); + var expiration = DateTime.now().subtract(const Duration(hours: 1)); var credentials = oauth2.Credentials('access token', expiration: expiration); var client = oauth2.Client(credentials, @@ -34,7 +35,7 @@ void main() { test( 'that can be refreshed refreshes the credentials and sends the ' 'request', () async { - var expiration = DateTime.now().subtract(Duration(hours: 1)); + var expiration = DateTime.now().subtract(const Duration(hours: 1)); var credentials = oauth2.Credentials('access token', refreshToken: 'refresh token', tokenEndpoint: tokenEndpoint, @@ -68,7 +69,7 @@ void main() { test( 'that can be refreshed refreshes only once if multiple requests are made', () async { - var expiration = DateTime.now().subtract(Duration(hours: 1)); + var expiration = DateTime.now().subtract(const Duration(hours: 1)); var credentials = oauth2.Credentials('access token', refreshToken: 'refresh token', tokenEndpoint: tokenEndpoint, @@ -86,7 +87,7 @@ void main() { headers: {'content-type': 'application/json'})); }); - final numCalls = 2; + const numCalls = 2; for (var i = 0; i < numCalls; i++) { httpClient.expectRequest((request) { @@ -100,7 +101,8 @@ void main() { } await Future.wait( - List.generate(numCalls, (_) => client.read(requestUri))); + List>.generate(numCalls, (_) => client.read(requestUri)), + ); expect(client.credentials.accessToken, equals('new access token')); }); @@ -108,7 +110,7 @@ void main() { test('that onCredentialsRefreshed is called', () async { var callbackCalled = false; - var expiration = DateTime.now().subtract(Duration(hours: 1)); + var expiration = DateTime.now().subtract(const Duration(hours: 1)); var credentials = oauth2.Credentials('access token', refreshToken: 'refresh token', tokenEndpoint: tokenEndpoint, @@ -121,17 +123,20 @@ void main() { expect(credentials.accessToken, equals('new access token')); }); - httpClient.expectRequest((request) { - return Future.value(http.Response( + httpClient.expectRequest( + (request) => Future.value( + http.Response( jsonEncode( - {'access_token': 'new access token', 'token_type': 'bearer'}), + {'access_token': 'new access token', 'token_type': 'bearer'}, + ), 200, - headers: {'content-type': 'application/json'})); - }); + headers: {'content-type': 'application/json'}, + ), + ), + ); - httpClient.expectRequest((request) { - return Future.value(http.Response('good job', 200)); - }); + httpClient.expectRequest( + (request) => Future.value(http.Response('good job', 200))); await client.read(requestUri); expect(callbackCalled, equals(true)); diff --git a/pkgs/oauth2/test/credentials_test.dart b/pkgs/oauth2/test/credentials_test.dart index 1b3caa5e0..d83bc7e6d 100644 --- a/pkgs/oauth2/test/credentials_test.dart +++ b/pkgs/oauth2/test/credentials_test.dart @@ -14,7 +14,8 @@ import 'utils.dart'; final Uri tokenEndpoint = Uri.parse('http://example.com/token'); void main() { - var httpClient; + late ExpectClient httpClient; + setUp(() => httpClient = ExpectClient()); test('is not expired if no expiration exists', () { @@ -23,14 +24,14 @@ void main() { }); test('is not expired if the expiration is in the future', () { - var expiration = DateTime.now().add(Duration(hours: 1)); + var expiration = DateTime.now().add(const Duration(hours: 1)); var credentials = oauth2.Credentials('access token', expiration: expiration); expect(credentials.isExpired, isFalse); }); test('is expired if the expiration is in the past', () { - var expiration = DateTime.now().subtract(Duration(hours: 1)); + var expiration = DateTime.now().subtract(const Duration(hours: 1)); var credentials = oauth2.Credentials('access token', expiration: expiration); expect(credentials.isExpired, isTrue); @@ -257,14 +258,14 @@ void main() { }); group('fromJson', () { - oauth2.Credentials fromMap(Map map) => + oauth2.Credentials fromMap(Map map) => oauth2.Credentials.fromJson(jsonEncode(map)); test('should load the same credentials from toJson', () { // Round the expiration down to milliseconds since epoch, since that's // what the credentials file stores. Otherwise sub-millisecond time gets // in the way. - var expiration = DateTime.now().subtract(Duration(hours: 1)); + var expiration = DateTime.now().subtract(const Duration(hours: 1)); expiration = DateTime.fromMillisecondsSinceEpoch( expiration.millisecondsSinceEpoch); diff --git a/pkgs/oauth2/test/handle_access_token_response_test.dart b/pkgs/oauth2/test/handle_access_token_response_test.dart index 1663d3fbb..481c1af84 100644 --- a/pkgs/oauth2/test/handle_access_token_response_test.dart +++ b/pkgs/oauth2/test/handle_access_token_response_test.dart @@ -5,11 +5,10 @@ import 'dart:convert'; import 'package:http/http.dart' as http; -import 'package:test/test.dart'; - import 'package:oauth2/oauth2.dart' as oauth2; import 'package:oauth2/src/handle_access_token_response.dart'; import 'package:oauth2/src/parameters.dart'; +import 'package:test/test.dart'; import 'utils.dart'; @@ -34,7 +33,7 @@ void main() { handle(http.Response(body, statusCode, headers: headers)); test('causes an AuthorizationException', () { - expect(() => handleError(), throwsAuthorizationException); + expect(handleError, throwsAuthorizationException); }); test('with a 401 code causes an AuthorizationException', () { @@ -163,11 +162,15 @@ void main() { }); test('with custom getParameters() returns the correct credentials', () { - var body = '_' + - jsonEncode({'token_type': 'bearer', 'access_token': 'access token'}); + var body = '_${jsonEncode({ + 'token_type': 'bearer', + 'access_token': 'access token' + })}'; var credentials = handle( - http.Response(body, 200, headers: {'content-type': 'text/plain'}), - getParameters: (contentType, body) => jsonDecode(body.substring(1))); + http.Response(body, 200, headers: {'content-type': 'text/plain'}), + getParameters: (contentType, body) => + jsonDecode(body.substring(1)) as Map, + ); expect(credentials.accessToken, equals('access token')); expect(credentials.tokenEndpoint.toString(), equals(tokenEndpoint.toString())); diff --git a/pkgs/oauth2/test/resource_owner_password_grant_test.dart b/pkgs/oauth2/test/resource_owner_password_grant_test.dart index c9afb6511..2614b14c8 100644 --- a/pkgs/oauth2/test/resource_owner_password_grant_test.dart +++ b/pkgs/oauth2/test/resource_owner_password_grant_test.dart @@ -3,8 +3,8 @@ // BSD-style license that can be found in the LICENSE file. @TestOn('vm') -import 'dart:convert'; import 'dart:async'; +import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:oauth2/oauth2.dart' as oauth2; @@ -19,11 +19,12 @@ final success = jsonEncode({ 'refresh_token': 'tGzv3JOkF0XG5Qx2TlKWIA', }); -var auth = 'Basic Y2xpZW50OnNlY3JldA=='; -var authEndpoint = Uri.parse('https://example.com'); +String auth = 'Basic Y2xpZW50OnNlY3JldA=='; +Uri authEndpoint = Uri.parse('https://example.com'); void main() { - var expectClient; + late ExpectClient expectClient; + setUp(() => expectClient = ExpectClient()); group('basic', () { diff --git a/pkgs/oauth2/test/utils.dart b/pkgs/oauth2/test/utils.dart index f66e7df87..90980c801 100644 --- a/pkgs/oauth2/test/utils.dart +++ b/pkgs/oauth2/test/utils.dart @@ -13,9 +13,7 @@ import 'package:test/test.dart'; class ExpectClient extends MockClient { final Queue _handlers; - ExpectClient._(MockClientHandler fn) - : _handlers = Queue(), - super(fn); + ExpectClient._(super.fn) : _handlers = Queue(); factory ExpectClient() { late ExpectClient client; From 69b8e082b119ef71ce3e2560381dcbe27342ae0e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Oct 2022 18:03:38 -0700 Subject: [PATCH 134/159] Bump actions/checkout from 3.0.2 to 3.1.0 (dart-lang/oauth2#135) Bumps [actions/checkout](https://github.com/actions/checkout) from 3.0.2 to 3.1.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/2541b1294d2704b0964813337f33b291d3f8596b...93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pkgs/oauth2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/.github/workflows/test-package.yml b/pkgs/oauth2/.github/workflows/test-package.yml index eeb824ff5..7386837de 100644 --- a/pkgs/oauth2/.github/workflows/test-package.yml +++ b/pkgs/oauth2/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [2.17.0, dev] steps: - - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b + - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d with: sdk: ${{ matrix.sdk }} @@ -52,7 +52,7 @@ jobs: os: [ubuntu-latest] sdk: [2.17.0, dev] steps: - - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b + - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d with: sdk: ${{ matrix.sdk }} From 5c6bd5f2b0938147bf483330c370698ef24f215b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Jan 2023 13:06:47 -0800 Subject: [PATCH 135/159] Bump actions/checkout from 3.1.0 to 3.2.0 (dart-lang/oauth2#138) Bumps [actions/checkout](https://github.com/actions/checkout) from 3.1.0 to 3.2.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8...755da8c3cf115ac066823e79a1e1788f8940201b) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pkgs/oauth2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/.github/workflows/test-package.yml b/pkgs/oauth2/.github/workflows/test-package.yml index 7386837de..4e5bb2d1b 100644 --- a/pkgs/oauth2/.github/workflows/test-package.yml +++ b/pkgs/oauth2/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [2.17.0, dev] steps: - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d with: sdk: ${{ matrix.sdk }} @@ -52,7 +52,7 @@ jobs: os: [ubuntu-latest] sdk: [2.17.0, dev] steps: - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d with: sdk: ${{ matrix.sdk }} From e7aafdfcbc248ef4d171ea691f03e1643e082cde Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Jan 2023 16:16:59 -0800 Subject: [PATCH 136/159] Bump actions/checkout from 3.2.0 to 3.3.0 (dart-lang/oauth2#139) Bumps [actions/checkout](https://github.com/actions/checkout) from 3.2.0 to 3.3.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/755da8c3cf115ac066823e79a1e1788f8940201b...ac593985615ec2ede58e132d2e21d2b1cbd6127c) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pkgs/oauth2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/.github/workflows/test-package.yml b/pkgs/oauth2/.github/workflows/test-package.yml index 4e5bb2d1b..509f077a8 100644 --- a/pkgs/oauth2/.github/workflows/test-package.yml +++ b/pkgs/oauth2/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [2.17.0, dev] steps: - - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d with: sdk: ${{ matrix.sdk }} @@ -52,7 +52,7 @@ jobs: os: [ubuntu-latest] sdk: [2.17.0, dev] steps: - - uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b + - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d with: sdk: ${{ matrix.sdk }} From 715b623af21eb2fb629157f9378e8cff9f5a4451 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Jan 2023 16:34:23 -0800 Subject: [PATCH 137/159] Bump dart-lang/setup-dart from 1.3 to 1.4 (dart-lang/oauth2#140) Bumps [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart) from 1.3 to 1.4. - [Release notes](https://github.com/dart-lang/setup-dart/releases) - [Changelog](https://github.com/dart-lang/setup-dart/blob/main/CHANGELOG.md) - [Commits](https://github.com/dart-lang/setup-dart/compare/6a218f2413a3e78e9087f638a238f6b40893203d...a57a6c04cf7d4840e88432aad6281d1e125f0d46) --- updated-dependencies: - dependency-name: dart-lang/setup-dart dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pkgs/oauth2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/.github/workflows/test-package.yml b/pkgs/oauth2/.github/workflows/test-package.yml index 509f077a8..2c6ba1230 100644 --- a/pkgs/oauth2/.github/workflows/test-package.yml +++ b/pkgs/oauth2/.github/workflows/test-package.yml @@ -23,7 +23,7 @@ jobs: sdk: [2.17.0, dev] steps: - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d + - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 with: sdk: ${{ matrix.sdk }} - id: install @@ -53,7 +53,7 @@ jobs: sdk: [2.17.0, dev] steps: - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d + - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 with: sdk: ${{ matrix.sdk }} - id: install From 336d232f50da3a0e67d936d8b7eb009ab10c0087 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 2 Apr 2023 19:50:10 -0700 Subject: [PATCH 138/159] Bump dart-lang/setup-dart from 1.4.0 to 1.5.0 (dart-lang/oauth2#142) Bumps [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart) from 1.4.0 to 1.5.0. - [Release notes](https://github.com/dart-lang/setup-dart/releases) - [Changelog](https://github.com/dart-lang/setup-dart/blob/main/CHANGELOG.md) - [Commits](https://github.com/dart-lang/setup-dart/compare/a57a6c04cf7d4840e88432aad6281d1e125f0d46...d6a63dab3335f427404425de0fbfed4686d93c4f) --- updated-dependencies: - dependency-name: dart-lang/setup-dart dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pkgs/oauth2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/.github/workflows/test-package.yml b/pkgs/oauth2/.github/workflows/test-package.yml index 2c6ba1230..50f5aedb7 100644 --- a/pkgs/oauth2/.github/workflows/test-package.yml +++ b/pkgs/oauth2/.github/workflows/test-package.yml @@ -23,7 +23,7 @@ jobs: sdk: [2.17.0, dev] steps: - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 + - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} - id: install @@ -53,7 +53,7 @@ jobs: sdk: [2.17.0, dev] steps: - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c - - uses: dart-lang/setup-dart@a57a6c04cf7d4840e88432aad6281d1e125f0d46 + - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} - id: install From b76f90d536add332a84ab26a9a5ebceb077809dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 2 Apr 2023 19:55:32 -0700 Subject: [PATCH 139/159] Bump actions/checkout from 3.3.0 to 3.5.0 (dart-lang/oauth2#143) Bumps [actions/checkout](https://github.com/actions/checkout) from 3.3.0 to 3.5.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/ac593985615ec2ede58e132d2e21d2b1cbd6127c...8f4b7f84864484a7bf31766abe9204da3cbe65b3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pkgs/oauth2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/.github/workflows/test-package.yml b/pkgs/oauth2/.github/workflows/test-package.yml index 50f5aedb7..4bb94ee82 100644 --- a/pkgs/oauth2/.github/workflows/test-package.yml +++ b/pkgs/oauth2/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [2.17.0, dev] steps: - - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} @@ -52,7 +52,7 @@ jobs: os: [ubuntu-latest] sdk: [2.17.0, dev] steps: - - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} From a00292d03194297daffc9d8a9ab5d815346c9b49 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 May 2023 12:34:59 -0700 Subject: [PATCH 140/159] Bump actions/checkout from 3.5.0 to 3.5.2 (dart-lang/oauth2#145) Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.0 to 3.5.2. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/8f4b7f84864484a7bf31766abe9204da3cbe65b3...8e5e7e5ab8b370d6c329ec480221332ada57f0ab) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pkgs/oauth2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/.github/workflows/test-package.yml b/pkgs/oauth2/.github/workflows/test-package.yml index 4bb94ee82..f4e528aba 100644 --- a/pkgs/oauth2/.github/workflows/test-package.yml +++ b/pkgs/oauth2/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [2.17.0, dev] steps: - - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} @@ -52,7 +52,7 @@ jobs: os: [ubuntu-latest] sdk: [2.17.0, dev] steps: - - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} From 938d3b8d91fa517f1a4b2516d1c44cdaaa133032 Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Mon, 22 May 2023 09:20:48 -0700 Subject: [PATCH 141/159] blast_repo fixes (dart-lang/oauth2#147) dependabot --- pkgs/oauth2/.github/dependabot.yaml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pkgs/oauth2/.github/dependabot.yaml b/pkgs/oauth2/.github/dependabot.yaml index 214481934..439e796b4 100644 --- a/pkgs/oauth2/.github/dependabot.yaml +++ b/pkgs/oauth2/.github/dependabot.yaml @@ -2,7 +2,9 @@ version: 2 updates: - - package-ecosystem: "github-actions" - directory: "/" + - package-ecosystem: github-actions + directory: / schedule: - interval: "monthly" + interval: monthly + labels: + - autosubmit From 47ac4fc911c6dc981fa59d23b32b5905d6cbd2d6 Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Tue, 30 May 2023 14:44:01 -0700 Subject: [PATCH 142/159] support 'package:http' 1.0.0 (dart-lang/oauth2#149) * support 'package:http' 2.0.0 * update changelog * update the CI SDK versions --- .../oauth2/.github/workflows/test-package.yml | 4 +- pkgs/oauth2/CHANGELOG.md | 47 ++++++++++--------- pkgs/oauth2/lib/src/credentials.dart | 2 +- pkgs/oauth2/pubspec.yaml | 6 +-- 4 files changed, 31 insertions(+), 28 deletions(-) diff --git a/pkgs/oauth2/.github/workflows/test-package.yml b/pkgs/oauth2/.github/workflows/test-package.yml index f4e528aba..e3f7d264b 100644 --- a/pkgs/oauth2/.github/workflows/test-package.yml +++ b/pkgs/oauth2/.github/workflows/test-package.yml @@ -20,7 +20,7 @@ jobs: strategy: fail-fast: false matrix: - sdk: [2.17.0, dev] + sdk: [3.0.0, dev] steps: - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f @@ -50,7 +50,7 @@ jobs: matrix: # Add macos-latest and/or windows-latest if relevant for this package. os: [ubuntu-latest] - sdk: [2.17.0, dev] + sdk: [stable, beta] steps: - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md index 0c0deb205..906517d38 100644 --- a/pkgs/oauth2/CHANGELOG.md +++ b/pkgs/oauth2/CHANGELOG.md @@ -1,83 +1,86 @@ -# 2.0.1 +## 2.0.2 + +* Require Dart 3.0. +* Support `package:http` 1.0.0. + +## 2.0.1 * Handle `expires_in` when encoded as string. * Populate the pubspec `repository` field. * Increase the minimum Dart SDK to `2.17.0`. -# 2.0.0 +## 2.0.0 * Migrate to null safety. -# 1.6.3 +## 1.6.3 * Added optional `codeVerifier` parameter to `AuthorizationCodeGrant` constructor. -# 1.6.1 +## 1.6.1 * Added fix to make sure that credentials are only refreshed once when multiple calls are made. -# 1.6.0 +## 1.6.0 * Added PKCE support to `AuthorizationCodeGrant`. -# 1.5.0 +## 1.5.0 * Added support for `clientCredentialsGrant`. -# 1.4.0 +## 1.4.0 * OpenID's id_token treated. -# 1.3.0 +## 1.3.0 * Added `onCredentialsRefreshed` option when creating `Client` objects. -# 1.2.3 +## 1.2.3 * Support the latest `package:http` release. -# 1.2.2 +## 1.2.2 * Allow the stable 2.0 SDK. -# 1.2.1 +## 1.2.1 * Updated SDK version to 2.0.0-dev.17.0 -# 1.2.0 +## 1.2.0 * Add a `getParameter()` parameter to `new AuthorizationCodeGrant()`, `new Credentials()`, and `resourceOwnerPasswordGrant()`. This controls how the authorization server's response is parsed for servers that don't provide the standard JSON response. -# 1.1.1 +## 1.1.1 * `resourceOwnerPasswordGrant()` now properly uses its HTTP client for requests made by the OAuth2 client it returns. -# 1.1.0 +## 1.1.0 * Add a `delimiter` parameter to `new AuthorizationCodeGrant()`, `new Credentials()`, and `resourceOwnerPasswordGrant()`. This controls the delimiter between scopes, which some authorization servers require to be different values than the specified `' '`. -# 1.0.2 +## 1.0.2 * Fix all strong-mode warnings. - * Support `crypto` 1.0.0. - * Support `http_parser` 3.0.0. -# 1.0.1 +## 1.0.1 * Support `http_parser` 2.0.0. -# 1.0.0 +## 1.0.0 -## Breaking changes +### Breaking changes * Requests that use client authentication, such as the `AuthorizationCodeGrant`'s access token request and `Credentials`' refresh @@ -98,7 +101,7 @@ * `new Credentials()` now takes named arguments rather than optional positional arguments. -## Non-breaking changes +### Non-breaking changes * Added a `resourceOwnerPasswordGrant` method. @@ -116,7 +119,7 @@ * Since `http` 0.11.0 now works in non-`dart:io` contexts, `oauth2` does as well. -# 0.9.2 +## 0.9.2 * Expand the dependency on the HTTP package to include 0.10.x. diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index 459e63e5c..a11b2ba33 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -128,7 +128,7 @@ class Credentials { /// /// Throws a [FormatException] if the JSON is incorrectly formatted. factory Credentials.fromJson(String json) { - void validate(bool condition, message) { + void validate(bool condition, String message) { if (condition) return; throw FormatException('Failed to load credentials: $message.\n\n$json'); } diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index bb43bb7d3..544a12653 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,5 @@ name: oauth2 -version: 2.0.1 +version: 2.0.2 description: >- A client library for authenticating with a remote service via OAuth2 on behalf of a user, and making authorized HTTP requests with the user's @@ -7,12 +7,12 @@ description: >- repository: https://github.com/dart-lang/oauth2 environment: - sdk: '>=2.17.0 <3.0.0' + sdk: ^3.0.0 dependencies: collection: ^1.15.0 crypto: ^3.0.0 - http: ^0.13.0 + http: '>=0.13.0 <2.0.0' http_parser: ^4.0.0 dev_dependencies: From 8078fddd6fb497c811aa498527e277486ce6786a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Jul 2023 00:08:04 +0000 Subject: [PATCH 143/159] Bump actions/checkout from 3.5.2 to 3.5.3 (dart-lang/oauth2#151) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.2 to 3.5.3.
Release notes

Sourced from actions/checkout's releases.

v3.5.3

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v3...v3.5.3

Changelog

Sourced from actions/checkout's changelog.

Changelog

v3.5.3

v3.5.2

v3.5.1

v3.5.0

v3.4.0

v3.3.0

v3.2.0

v3.1.0

v3.0.2

v3.0.1

v3.0.0

v2.3.1

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=3.5.2&new-version=3.5.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/oauth2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/.github/workflows/test-package.yml b/pkgs/oauth2/.github/workflows/test-package.yml index e3f7d264b..1211830ac 100644 --- a/pkgs/oauth2/.github/workflows/test-package.yml +++ b/pkgs/oauth2/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [3.0.0, dev] steps: - - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} @@ -52,7 +52,7 @@ jobs: os: [ubuntu-latest] sdk: [stable, beta] steps: - - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} From 220f4d0b3adafc1ef52169d409313d1be1ed925c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Sep 2023 00:39:56 +0000 Subject: [PATCH 144/159] Bump actions/checkout from 3.5.3 to 3.6.0 (dart-lang/oauth2#154) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.3 to 3.6.0.
Release notes

Sourced from actions/checkout's releases.

v3.6.0

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v3.5.3...v3.6.0

Changelog

Sourced from actions/checkout's changelog.

Changelog

v3.6.0

v3.5.3

v3.5.2

v3.5.1

v3.5.0

v3.4.0

v3.3.0

v3.2.0

v3.1.0

v3.0.2

v3.0.1

v3.0.0

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=3.5.3&new-version=3.6.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/oauth2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/.github/workflows/test-package.yml b/pkgs/oauth2/.github/workflows/test-package.yml index 1211830ac..21336a4cd 100644 --- a/pkgs/oauth2/.github/workflows/test-package.yml +++ b/pkgs/oauth2/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [3.0.0, dev] steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} @@ -52,7 +52,7 @@ jobs: os: [ubuntu-latest] sdk: [stable, beta] steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: ${{ matrix.sdk }} From 299ba978d096cb1cbe32f6a9f6f7b408d4d93a6f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Oct 2023 00:24:04 +0000 Subject: [PATCH 145/159] Bump dart-lang/setup-dart from 1.5.0 to 1.5.1 (dart-lang/oauth2#155) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart) from 1.5.0 to 1.5.1.
Release notes

Sourced from dart-lang/setup-dart's releases.

v1.5.1

  • No longer test the setup-dart action on pre-2.12 SDKs.
  • Upgrade JS interop code to use extension types (the new name for inline classes).
  • The upcoming rename of the be channel to main is now supported with forward compatibility that switches when the rename happens.
Changelog

Sourced from dart-lang/setup-dart's changelog.

v1.6.0

  • Enable provisioning of the latest Dart SDK patch release by specifying just the major and minor version (e.g. 3.2).

v1.5.1

  • No longer test the setup-dart action on pre-2.12 SDKs.
  • Upgrade JS interop code to use extension types (the new name for inline classes).
  • The upcoming rename of the be channel to main is now supported with forward compatibility that switches when the rename happens.

v1.5.0

  • Re-wrote the implementation of the action into Dart.
  • Auto-detect the platform architecture (x64, ia32, arm, arm64).
  • Improved the caching and download resilience of the sdk.
  • Added a new action output: dart-version - the installed version of the sdk.

v1.4.0

  • Automatically create OIDC token for pub.dev.
  • Add a reusable workflow for publishing.

v1.3.0

  • The install location of the Dart SDK is now available in an environment variable, DART_HOME (dart-lang/oauth2#43).
  • Fixed an issue where cached downloads could lead to unzip issues on self-hosted runners (dart-lang/oauth2#35).

v1.2.0

  • Fixed a path issue impacting git dependencies on Windows.

v1.1.0

  • Added a flavor option setup.sh to allow downloading unpublished builds.

v1.0.0

  • Promoted to 1.0 stable.

v0.5

  • Fixed a Windows pub global activate path issue.

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=dart-lang/setup-dart&package-manager=github_actions&previous-version=1.5.0&new-version=1.5.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/oauth2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/.github/workflows/test-package.yml b/pkgs/oauth2/.github/workflows/test-package.yml index 21336a4cd..2d57bde63 100644 --- a/pkgs/oauth2/.github/workflows/test-package.yml +++ b/pkgs/oauth2/.github/workflows/test-package.yml @@ -23,7 +23,7 @@ jobs: sdk: [3.0.0, dev] steps: - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 - - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f + - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 with: sdk: ${{ matrix.sdk }} - id: install @@ -53,7 +53,7 @@ jobs: sdk: [stable, beta] steps: - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 - - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f + - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 with: sdk: ${{ matrix.sdk }} - id: install From 9791af7e63d0b6467ff6d69f68641f2c11009e58 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Oct 2023 07:15:34 -0700 Subject: [PATCH 146/159] Bump actions/checkout from 3.6.0 to 4.1.0 (dart-lang/oauth2#156) Bumps [actions/checkout](https://github.com/actions/checkout) from 3.6.0 to 4.1.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/f43a0e5ff2bd294095638e18286ca9a3d1956744...8ade135a41bc03ea155e62e844d188df1ea18608) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pkgs/oauth2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/.github/workflows/test-package.yml b/pkgs/oauth2/.github/workflows/test-package.yml index 2d57bde63..6b8492000 100644 --- a/pkgs/oauth2/.github/workflows/test-package.yml +++ b/pkgs/oauth2/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [3.0.0, dev] steps: - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 with: sdk: ${{ matrix.sdk }} @@ -52,7 +52,7 @@ jobs: os: [ubuntu-latest] sdk: [stable, beta] steps: - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 with: sdk: ${{ matrix.sdk }} From e0d5b7500f5c54daf11b6ccf2acf024485db21f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Nov 2023 00:31:53 +0000 Subject: [PATCH 147/159] Bump actions/checkout from 4.1.0 to 4.1.1 (dart-lang/oauth2#160) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.0 to 4.1.1.
Release notes

Sourced from actions/checkout's releases.

v4.1.1

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v4.1.0...v4.1.1

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=4.1.0&new-version=4.1.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/oauth2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/.github/workflows/test-package.yml b/pkgs/oauth2/.github/workflows/test-package.yml index 6b8492000..bffed29b3 100644 --- a/pkgs/oauth2/.github/workflows/test-package.yml +++ b/pkgs/oauth2/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [3.0.0, dev] steps: - - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 with: sdk: ${{ matrix.sdk }} @@ -52,7 +52,7 @@ jobs: os: [ubuntu-latest] sdk: [stable, beta] steps: - - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 with: sdk: ${{ matrix.sdk }} From ed8237ac1eac4ad99572ed82a7842078e672fe45 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Nov 2023 00:32:33 +0000 Subject: [PATCH 148/159] Bump dart-lang/setup-dart from 1.5.1 to 1.6.0 (dart-lang/oauth2#161) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ⚠️ **Dependabot is rebasing this PR** ⚠️ Rebasing might not happen immediately, so don't worry if this takes some time. Note: if you make any changes to this PR yourself, they will take precedence over the rebase. --- Bumps [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart) from 1.5.1 to 1.6.0.
Release notes

Sourced from dart-lang/setup-dart's releases.

v1.6.0

  • Enable provisioning of the latest Dart SDK patch release by specifying just the major and minor version (e.g. 3.2).
Changelog

Sourced from dart-lang/setup-dart's changelog.

v1.6.0

  • Enable provisioning of the latest Dart SDK patch release by specifying just the major and minor version (e.g. 3.2).

v1.5.1

  • No longer test the setup-dart action on pre-2.12 SDKs.
  • Upgrade JS interop code to use extension types (the new name for inline classes).
  • The upcoming rename of the be channel to main is now supported with forward compatibility that switches when the rename happens.

v1.5.0

  • Re-wrote the implementation of the action into Dart.
  • Auto-detect the platform architecture (x64, ia32, arm, arm64).
  • Improved the caching and download resilience of the sdk.
  • Added a new action output: dart-version - the installed version of the sdk.

v1.4.0

  • Automatically create OIDC token for pub.dev.
  • Add a reusable workflow for publishing.

v1.3.0

  • The install location of the Dart SDK is now available in an environment variable, DART_HOME (dart-lang/oauth2#43).
  • Fixed an issue where cached downloads could lead to unzip issues on self-hosted runners (dart-lang/oauth2#35).

v1.2.0

  • Fixed a path issue impacting git dependencies on Windows.

v1.1.0

  • Added a flavor option setup.sh to allow downloading unpublished builds.

v1.0.0

  • Promoted to 1.0 stable.

v0.5

  • Fixed a Windows pub global activate path issue.

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=dart-lang/setup-dart&package-manager=github_actions&previous-version=1.5.1&new-version=1.6.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/oauth2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/.github/workflows/test-package.yml b/pkgs/oauth2/.github/workflows/test-package.yml index bffed29b3..6353bf940 100644 --- a/pkgs/oauth2/.github/workflows/test-package.yml +++ b/pkgs/oauth2/.github/workflows/test-package.yml @@ -23,7 +23,7 @@ jobs: sdk: [3.0.0, dev] steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 + - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d with: sdk: ${{ matrix.sdk }} - id: install @@ -53,7 +53,7 @@ jobs: sdk: [stable, beta] steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 + - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d with: sdk: ${{ matrix.sdk }} - id: install From b37dd1e7f5197658351b5ef209cad9bf237f4b37 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Mon, 4 Dec 2023 16:28:09 -0800 Subject: [PATCH 149/159] Move to latest lints, fixes, bump pkg:http dep (dart-lang/oauth2#162) --- pkgs/oauth2/CHANGELOG.md | 4 ++ pkgs/oauth2/analysis_options.yaml | 5 +- pkgs/oauth2/lib/src/credentials.dart | 4 +- .../lib/src/handle_access_token_response.dart | 4 +- pkgs/oauth2/pubspec.yaml | 6 +- .../test/authorization_code_grant_test.dart | 2 +- .../test/client_credentials_grant_test.dart | 1 + pkgs/oauth2/test/client_test.dart | 68 ++++++++++--------- .../handle_access_token_response_test.dart | 20 +++--- .../resource_owner_password_grant_test.dart | 2 + pkgs/oauth2/test/utils.dart | 2 +- 11 files changed, 66 insertions(+), 52 deletions(-) diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md index 906517d38..2c5da180d 100644 --- a/pkgs/oauth2/CHANGELOG.md +++ b/pkgs/oauth2/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.0.3-wip + +* Require `package:http` v1.0.0 + ## 2.0.2 * Require Dart 3.0. diff --git a/pkgs/oauth2/analysis_options.yaml b/pkgs/oauth2/analysis_options.yaml index c8bc59c2c..68d2a49af 100644 --- a/pkgs/oauth2/analysis_options.yaml +++ b/pkgs/oauth2/analysis_options.yaml @@ -1,6 +1,9 @@ -include: package:lints/recommended.yaml +include: package:dart_flutter_team_lints/analysis_options.yaml analyzer: + errors: + # Too many exceptions + comment_references: ignore language: strict-casts: true strict-raw-types: true diff --git a/pkgs/oauth2/lib/src/credentials.dart b/pkgs/oauth2/lib/src/credentials.dart index a11b2ba33..088b482fb 100644 --- a/pkgs/oauth2/lib/src/credentials.dart +++ b/pkgs/oauth2/lib/src/credentials.dart @@ -24,9 +24,9 @@ typedef CredentialsRefreshedCallback = void Function(Credentials); /// /// 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 +/// once they've expired. The [http.Client] will automatically refresh its /// credentials when necessary. It's also possible to explicitly refresh them -/// via [Client.refreshCredentials] or [Credentials.refresh]. +/// via [http.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. diff --git a/pkgs/oauth2/lib/src/handle_access_token_response.dart b/pkgs/oauth2/lib/src/handle_access_token_response.dart index 931ae9dd7..f318e3b0c 100644 --- a/pkgs/oauth2/lib/src/handle_access_token_response.dart +++ b/pkgs/oauth2/lib/src/handle_access_token_response.dart @@ -74,7 +74,9 @@ Credentials handleAccessTokenResponse(http.Response response, Uri tokenEndpoint, expiresIn = double.parse(expiresIn).toInt(); } on FormatException { throw FormatException( - 'parameter "expires_in" could not be parsed as in, was: "$expiresIn"'); + 'parameter "expires_in" could not be parsed as in, was: ' + '"$expiresIn"', + ); } } else if (expiresIn is! int) { throw FormatException( diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index 544a12653..d019335a0 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,5 +1,5 @@ name: oauth2 -version: 2.0.2 +version: 2.0.3-wip description: >- A client library for authenticating with a remote service via OAuth2 on behalf of a user, and making authorized HTTP requests with the user's @@ -12,9 +12,9 @@ environment: dependencies: collection: ^1.15.0 crypto: ^3.0.0 - http: '>=0.13.0 <2.0.0' + http: ^1.0.0 http_parser: ^4.0.0 dev_dependencies: - lints: ^2.0.0 + dart_flutter_team_lints: ^2.0.0 test: ^1.16.0 diff --git a/pkgs/oauth2/test/authorization_code_grant_test.dart b/pkgs/oauth2/test/authorization_code_grant_test.dart index b4807bc2b..06e88afdd 100644 --- a/pkgs/oauth2/test/authorization_code_grant_test.dart +++ b/pkgs/oauth2/test/authorization_code_grant_test.dart @@ -177,7 +177,7 @@ void main() { test('with an error parameter throws an AuthorizationException', () { grant.getAuthorizationUrl(redirectUrl); expect(grant.handleAuthorizationResponse({'error': 'invalid_request'}), - throwsA((e) => e is oauth2.AuthorizationException)); + throwsA(isA())); }); test('sends an authorization code request', () { diff --git a/pkgs/oauth2/test/client_credentials_grant_test.dart b/pkgs/oauth2/test/client_credentials_grant_test.dart index 67c71a51a..28de4253e 100644 --- a/pkgs/oauth2/test/client_credentials_grant_test.dart +++ b/pkgs/oauth2/test/client_credentials_grant_test.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. @TestOn('vm') +library; import 'dart:convert'; diff --git a/pkgs/oauth2/test/client_test.dart b/pkgs/oauth2/test/client_test.dart index b583f8c7c..3c30d36c8 100644 --- a/pkgs/oauth2/test/client_test.dart +++ b/pkgs/oauth2/test/client_test.dart @@ -67,45 +67,47 @@ void main() { }); test( - 'that can be refreshed refreshes only once if multiple requests are made', - () async { - var expiration = DateTime.now().subtract(const Duration(hours: 1)); - var credentials = oauth2.Credentials('access token', - refreshToken: 'refresh token', - tokenEndpoint: tokenEndpoint, - expiration: expiration); - var client = oauth2.Client(credentials, - identifier: 'identifier', secret: 'secret', httpClient: httpClient); + 'that can be refreshed refreshes only once if multiple requests are made', + () async { + var expiration = DateTime.now().subtract(const Duration(hours: 1)); + var credentials = oauth2.Credentials('access token', + refreshToken: 'refresh token', + tokenEndpoint: tokenEndpoint, + expiration: expiration); + var client = oauth2.Client(credentials, + identifier: 'identifier', secret: 'secret', httpClient: httpClient); - httpClient.expectRequest((request) { - expect(request.method, equals('POST')); - expect(request.url.toString(), equals(tokenEndpoint.toString())); - return Future.value(http.Response( - jsonEncode( - {'access_token': 'new access token', 'token_type': 'bearer'}), - 200, - headers: {'content-type': 'application/json'})); - }); + httpClient.expectRequest((request) { + expect(request.method, equals('POST')); + expect(request.url.toString(), equals(tokenEndpoint.toString())); + return Future.value(http.Response( + jsonEncode( + {'access_token': 'new access token', 'token_type': 'bearer'}), + 200, + headers: {'content-type': 'application/json'})); + }); - const numCalls = 2; + const numCalls = 2; - for (var i = 0; i < numCalls; i++) { - httpClient.expectRequest((request) { - expect(request.method, equals('GET')); - expect(request.url.toString(), equals(requestUri.toString())); - expect(request.headers['authorization'], - equals('Bearer new access token')); + for (var i = 0; i < numCalls; i++) { + httpClient.expectRequest((request) { + expect(request.method, equals('GET')); + expect(request.url.toString(), equals(requestUri.toString())); + expect(request.headers['authorization'], + equals('Bearer new access token')); - return Future.value(http.Response('good job', 200)); - }); - } + return Future.value(http.Response('good job', 200)); + }); + } - await Future.wait( - List>.generate(numCalls, (_) => client.read(requestUri)), - ); + await Future.wait( + List>.generate( + numCalls, (_) => client.read(requestUri)), + ); - expect(client.credentials.accessToken, equals('new access token')); - }); + expect(client.credentials.accessToken, equals('new access token')); + }, + ); test('that onCredentialsRefreshed is called', () async { var callbackCalled = false; diff --git a/pkgs/oauth2/test/handle_access_token_response_test.dart b/pkgs/oauth2/test/handle_access_token_response_test.dart index 481c1af84..4d7b5199e 100644 --- a/pkgs/oauth2/test/handle_access_token_response_test.dart +++ b/pkgs/oauth2/test/handle_access_token_response_test.dart @@ -115,11 +115,11 @@ void main() { group('a success response', () { oauth2.Credentials handleSuccess( {String contentType = 'application/json', - accessToken = 'access token', - tokenType = 'bearer', - expiresIn, - refreshToken, - scope}) { + Object? accessToken = 'access token', + Object? tokenType = 'bearer', + Object? expiresIn, + Object? refreshToken, + Object? scope}) { return handle(http.Response( jsonEncode({ 'access_token': accessToken, @@ -272,11 +272,11 @@ void main() { group('a success response with a id_token', () { oauth2.Credentials handleSuccess( {String contentType = 'application/json', - accessToken = 'access token', - tokenType = 'bearer', - expiresIn, - idToken = 'decode me', - scope}) { + Object? accessToken = 'access token', + Object? tokenType = 'bearer', + Object? expiresIn, + Object? idToken = 'decode me', + Object? scope}) { return handle(http.Response( jsonEncode({ 'access_token': accessToken, diff --git a/pkgs/oauth2/test/resource_owner_password_grant_test.dart b/pkgs/oauth2/test/resource_owner_password_grant_test.dart index 2614b14c8..7a5d9b5a0 100644 --- a/pkgs/oauth2/test/resource_owner_password_grant_test.dart +++ b/pkgs/oauth2/test/resource_owner_password_grant_test.dart @@ -3,6 +3,8 @@ // BSD-style license that can be found in the LICENSE file. @TestOn('vm') +library; + import 'dart:async'; import 'dart:convert'; diff --git a/pkgs/oauth2/test/utils.dart b/pkgs/oauth2/test/utils.dart index 90980c801..4f3b74726 100644 --- a/pkgs/oauth2/test/utils.dart +++ b/pkgs/oauth2/test/utils.dart @@ -22,7 +22,7 @@ class ExpectClient extends MockClient { } void expectRequest(MockClientHandler fn) { - var completer = Completer(); + var completer = Completer(); expect(completer.future, completes); _handlers.add((request) { From bb8c14af402252e04312602690133cdeefa08b70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 00:05:19 +0000 Subject: [PATCH 150/159] Bump dart-lang/setup-dart from 1.6.0 to 1.6.2 (dart-lang/oauth2#167) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart) from 1.6.0 to 1.6.2.
Release notes

Sourced from dart-lang/setup-dart's releases.

v1.6.2

v1.6.1

  • Updated the google storage url for main channel releases.
Changelog

Sourced from dart-lang/setup-dart's changelog.

v1.6.2

v1.6.1

  • Updated the google storage url for main channel releases.

v1.6.0

  • Enable provisioning of the latest Dart SDK patch release by specifying just the major and minor version (e.g. 3.2).

v1.5.1

  • No longer test the setup-dart action on pre-2.12 SDKs.
  • Upgrade JS interop code to use extension types (the new name for inline classes).
  • The upcoming rename of the be channel to main is now supported with forward compatibility that switches when the rename happens.

v1.5.0

  • Re-wrote the implementation of the action into Dart.
  • Auto-detect the platform architecture (x64, ia32, arm, arm64).
  • Improved the caching and download resilience of the sdk.
  • Added a new action output: dart-version - the installed version of the sdk.

v1.4.0

  • Automatically create OIDC token for pub.dev.
  • Add a reusable workflow for publishing.

v1.3.0

  • The install location of the Dart SDK is now available in an environment variable, DART_HOME (dart-lang/oauth2#43).
  • Fixed an issue where cached downloads could lead to unzip issues on self-hosted runners (dart-lang/oauth2#35).

v1.2.0

  • Fixed a path issue impacting git dependencies on Windows.

v1.1.0

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=dart-lang/setup-dart&package-manager=github_actions&previous-version=1.6.0&new-version=1.6.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/oauth2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/.github/workflows/test-package.yml b/pkgs/oauth2/.github/workflows/test-package.yml index 6353bf940..6848fc342 100644 --- a/pkgs/oauth2/.github/workflows/test-package.yml +++ b/pkgs/oauth2/.github/workflows/test-package.yml @@ -23,7 +23,7 @@ jobs: sdk: [3.0.0, dev] steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d + - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 with: sdk: ${{ matrix.sdk }} - id: install @@ -53,7 +53,7 @@ jobs: sdk: [stable, beta] steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d + - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 with: sdk: ${{ matrix.sdk }} - id: install From 69307be186912400c85fc80af7c1946c70988ede Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 00:50:29 +0000 Subject: [PATCH 151/159] Bump actions/checkout from 4.1.1 to 4.1.2 (dart-lang/oauth2#169) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.1 to 4.1.2.
Release notes

Sourced from actions/checkout's releases.

v4.1.2

We are investigating the following issue with this release and have rolled-back the v4 tag to point to v4.1.1

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v4.1.1...v4.1.2

Changelog

Sourced from actions/checkout's changelog.

Changelog

v4.1.2

v4.1.1

v4.1.0

v4.0.0

v3.6.0

v3.5.3

v3.5.2

v3.5.1

v3.5.0

v3.4.0

v3.3.0

v3.2.0

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=4.1.1&new-version=4.1.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/oauth2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/.github/workflows/test-package.yml b/pkgs/oauth2/.github/workflows/test-package.yml index 6848fc342..5cac1b2be 100644 --- a/pkgs/oauth2/.github/workflows/test-package.yml +++ b/pkgs/oauth2/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [3.0.0, dev] steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 with: sdk: ${{ matrix.sdk }} @@ -52,7 +52,7 @@ jobs: os: [ubuntu-latest] sdk: [stable, beta] steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 with: sdk: ${{ matrix.sdk }} From 944ebaf0f844b9ec8a4c91b13537bc79332f0ded Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 May 2024 00:28:49 +0000 Subject: [PATCH 152/159] Bump dart-lang/setup-dart from 1.6.2 to 1.6.4 (dart-lang/oauth2#172) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart) from 1.6.2 to 1.6.4.
Release notes

Sourced from dart-lang/setup-dart's releases.

v1.6.4

  • Rebuild JS code to include changes from v1.6.3

v1.6.3

Changelog

Sourced from dart-lang/setup-dart's changelog.

v1.6.4

  • Rebuild JS code.

v1.6.3

v1.6.2

v1.6.1

  • Updated the google storage url for main channel releases.

v1.6.0

  • Enable provisioning of the latest Dart SDK patch release by specifying just the major and minor version (e.g. 3.2).

v1.5.1

  • No longer test the setup-dart action on pre-2.12 SDKs.
  • Upgrade JS interop code to use extension types (the new name for inline classes).
  • The upcoming rename of the be channel to main is now supported with forward compatibility that switches when the rename happens.

v1.5.0

  • Re-wrote the implementation of the action into Dart.
  • Auto-detect the platform architecture (x64, ia32, arm, arm64).
  • Improved the caching and download resilience of the sdk.
  • Added a new action output: dart-version - the installed version of the sdk.

v1.4.0

  • Automatically create OIDC token for pub.dev.
  • Add a reusable workflow for publishing.

v1.3.0

  • The install location of the Dart SDK is now available

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=dart-lang/setup-dart&package-manager=github_actions&previous-version=1.6.2&new-version=1.6.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/oauth2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/.github/workflows/test-package.yml b/pkgs/oauth2/.github/workflows/test-package.yml index 5cac1b2be..997064dc9 100644 --- a/pkgs/oauth2/.github/workflows/test-package.yml +++ b/pkgs/oauth2/.github/workflows/test-package.yml @@ -23,7 +23,7 @@ jobs: sdk: [3.0.0, dev] steps: - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 - - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 + - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30 with: sdk: ${{ matrix.sdk }} - id: install @@ -53,7 +53,7 @@ jobs: sdk: [stable, beta] steps: - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 - - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 + - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30 with: sdk: ${{ matrix.sdk }} - id: install From 6480cfaa41129cb576ec7089f0acad1453bea019 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 May 2024 15:21:35 +0000 Subject: [PATCH 153/159] Bump actions/checkout from 4.1.2 to 4.1.4 (dart-lang/oauth2#171) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.2 to 4.1.4.
Release notes

Sourced from actions/checkout's releases.

v4.1.4

What's Changed

Full Changelog: https://github.com/actions/checkout/compare/v4.1.3...v4.1.4

v4.1.3

What's Changed

Full Changelog: https://github.com/actions/checkout/compare/v4.1.2...v4.1.3

Changelog

Sourced from actions/checkout's changelog.

Changelog

v4.1.4

v4.1.3

v4.1.2

v4.1.1

v4.1.0

v4.0.0

v3.6.0

v3.5.3

v3.5.2

v3.5.1

v3.5.0

v3.4.0

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=4.1.2&new-version=4.1.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- pkgs/oauth2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/.github/workflows/test-package.yml b/pkgs/oauth2/.github/workflows/test-package.yml index 997064dc9..57a6eb379 100644 --- a/pkgs/oauth2/.github/workflows/test-package.yml +++ b/pkgs/oauth2/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [3.0.0, dev] steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30 with: sdk: ${{ matrix.sdk }} @@ -52,7 +52,7 @@ jobs: os: [ubuntu-latest] sdk: [stable, beta] steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30 with: sdk: ${{ matrix.sdk }} From 95a5abb47a03ce7d809f652a9145a88db7fb664c Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Tue, 7 May 2024 12:19:11 -0700 Subject: [PATCH 154/159] blast_repo fixes (dart-lang/oauth2#173) dependabot --- pkgs/oauth2/.github/dependabot.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkgs/oauth2/.github/dependabot.yaml b/pkgs/oauth2/.github/dependabot.yaml index 439e796b4..bf6b38a4d 100644 --- a/pkgs/oauth2/.github/dependabot.yaml +++ b/pkgs/oauth2/.github/dependabot.yaml @@ -8,3 +8,7 @@ updates: interval: monthly labels: - autosubmit + groups: + github-actions: + patterns: + - "*" From 4bb146c324be24c04b4c0eac85f5dedff6a1e9af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 May 2024 19:23:19 +0000 Subject: [PATCH 155/159] Bump actions/checkout from 4.1.4 to 4.1.5 in the github-actions group (dart-lang/oauth2#174) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 4.1.4 to 4.1.5
Release notes

Sourced from actions/checkout's releases.

v4.1.5

What's Changed

Full Changelog: https://github.com/actions/checkout/compare/v4.1.4...v4.1.5

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=4.1.4&new-version=4.1.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
--- pkgs/oauth2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/.github/workflows/test-package.yml b/pkgs/oauth2/.github/workflows/test-package.yml index 57a6eb379..fd0f03105 100644 --- a/pkgs/oauth2/.github/workflows/test-package.yml +++ b/pkgs/oauth2/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [3.0.0, dev] steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30 with: sdk: ${{ matrix.sdk }} @@ -52,7 +52,7 @@ jobs: os: [ubuntu-latest] sdk: [stable, beta] steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30 with: sdk: ${{ matrix.sdk }} From 9b8e45711d808c9c62c57f42d5eae703c302457e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Jun 2024 00:39:31 +0000 Subject: [PATCH 156/159] Bump actions/checkout from 4.1.5 to 4.1.6 in the github-actions group (dart-lang/oauth2#176) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 4.1.5 to 4.1.6
Release notes

Sourced from actions/checkout's releases.

v4.1.6

What's Changed

Full Changelog: https://github.com/actions/checkout/compare/v4.1.5...v4.1.6

Changelog

Sourced from actions/checkout's changelog.

Changelog

v4.1.6

v4.1.5

v4.1.4

v4.1.3

v4.1.2

v4.1.1

v4.1.0

v4.0.0

v3.6.0

v3.5.3

v3.5.2

v3.5.1

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=4.1.5&new-version=4.1.6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
--- pkgs/oauth2/.github/workflows/test-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/oauth2/.github/workflows/test-package.yml b/pkgs/oauth2/.github/workflows/test-package.yml index fd0f03105..a5d84e41a 100644 --- a/pkgs/oauth2/.github/workflows/test-package.yml +++ b/pkgs/oauth2/.github/workflows/test-package.yml @@ -22,7 +22,7 @@ jobs: matrix: sdk: [3.0.0, dev] steps: - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30 with: sdk: ${{ matrix.sdk }} @@ -52,7 +52,7 @@ jobs: os: [ubuntu-latest] sdk: [stable, beta] steps: - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30 with: sdk: ${{ matrix.sdk }} From 653ae268e08fccd62fb5bd26379546de7f3ad07c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 00:27:18 +0000 Subject: [PATCH 157/159] Bump the github-actions group with 2 updates (dart-lang/oauth2#178) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the github-actions group with 2 updates: [actions/checkout](https://github.com/actions/checkout) and [dart-lang/setup-dart](https://github.com/dart-lang/setup-dart). Updates `actions/checkout` from 4.1.6 to 4.1.7
Release notes

Sourced from actions/checkout's releases.

v4.1.7

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v4.1.6...v4.1.7

Changelog

Sourced from actions/checkout's changelog.

Changelog

v4.1.7

v4.1.6

v4.1.5

v4.1.4

v4.1.3

v4.1.2

v4.1.1

v4.1.0

v4.0.0

v3.6.0

v3.5.3

... (truncated)

Commits

Updates `dart-lang/setup-dart` from 1.6.4 to 1.6.5
Release notes

Sourced from dart-lang/setup-dart's releases.

v1.6.5

dart-lang/oauth2#118: dart-lang/setup-dartdart-lang/oauth2#118

Changelog

Sourced from dart-lang/setup-dart's changelog.

v1.6.5

dart-lang/oauth2#118: dart-lang/setup-dartdart-lang/oauth2#118

v1.6.4

  • Rebuild JS code.

v1.6.3

v1.6.2

v1.6.1

  • Updated the google storage url for main channel releases.

v1.6.0

  • Enable provisioning of the latest Dart SDK patch release by specifying just the major and minor version (e.g. 3.2).

v1.5.1

  • No longer test the setup-dart action on pre-2.12 SDKs.
  • Upgrade JS interop code to use extension types (the new name for inline classes).
  • The upcoming rename of the be channel to main is now supported with forward compatibility that switches when the rename happens.

v1.5.0

  • Re-wrote the implementation of the action into Dart.
  • Auto-detect the platform architecture (x64, ia32, arm, arm64).
  • Improved the caching and download resilience of the sdk.
  • Added a new action output: dart-version - the installed version of the sdk.

v1.4.0

... (truncated)

Commits

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
--- pkgs/oauth2/.github/workflows/test-package.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkgs/oauth2/.github/workflows/test-package.yml b/pkgs/oauth2/.github/workflows/test-package.yml index a5d84e41a..fa5bfafe4 100644 --- a/pkgs/oauth2/.github/workflows/test-package.yml +++ b/pkgs/oauth2/.github/workflows/test-package.yml @@ -22,8 +22,8 @@ jobs: matrix: sdk: [3.0.0, dev] steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 - - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 with: sdk: ${{ matrix.sdk }} - id: install @@ -52,8 +52,8 @@ jobs: os: [ubuntu-latest] sdk: [stable, beta] steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 - - uses: dart-lang/setup-dart@f0ead981b4d9a35b37f30d36160575d60931ec30 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 with: sdk: ${{ matrix.sdk }} - id: install From 76c4e02ef8f133be5e4caad00823ec65b301da3f Mon Sep 17 00:00:00 2001 From: Moritz Date: Wed, 21 Aug 2024 14:25:32 +0200 Subject: [PATCH 158/159] Merge fixes --- .../workflows/oauth2.yml | 16 ++++++++++++++-- README.md | 1 + pkgs/oauth2/.github/dependabot.yaml | 14 -------------- pkgs/oauth2/CHANGELOG.md | 3 ++- pkgs/oauth2/README.md | 2 +- pkgs/oauth2/pubspec.yaml | 4 ++-- 6 files changed, 20 insertions(+), 20 deletions(-) rename pkgs/oauth2/.github/workflows/test-package.yml => .github/workflows/oauth2.yml (86%) delete mode 100644 pkgs/oauth2/.github/dependabot.yaml diff --git a/pkgs/oauth2/.github/workflows/test-package.yml b/.github/workflows/oauth2.yml similarity index 86% rename from pkgs/oauth2/.github/workflows/test-package.yml rename to .github/workflows/oauth2.yml index fa5bfafe4..820dd1a4c 100644 --- a/pkgs/oauth2/.github/workflows/test-package.yml +++ b/.github/workflows/oauth2.yml @@ -3,9 +3,15 @@ name: Dart CI on: # Run on PRs and pushes to the default branch. push: - branches: [ master ] + branches: [ main ] + paths: + - '.github/workflows/oauth2.yml' + - 'pkgs/oauth2/**' pull_request: - branches: [ master ] + branches: [ main ] + paths: + - '.github/workflows/oauth2.yml' + - 'pkgs/oauth2/**' schedule: - cron: "0 0 * * 0" @@ -17,6 +23,9 @@ jobs: # against the oldest supported SDK. analyze: runs-on: ubuntu-latest + defaults: + run: + working-directory: pkgs/oauth2/ strategy: fail-fast: false matrix: @@ -45,6 +54,9 @@ jobs: test: needs: analyze runs-on: ${{ matrix.os }} + defaults: + run: + working-directory: pkgs/oauth2/ strategy: fail-fast: false matrix: diff --git a/README.md b/README.md index daf3d7195..5847cf74b 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ don't naturally belong to other topic monorepos (like | [graphs](pkgs/graphs/) | Graph algorithms that operate on graphs in any representation | [![pub package](https://img.shields.io/pub/v/graphs.svg)](https://pub.dev/packages/graphs) | | [unified_analytics](pkgs/unified_analytics/) | A package for logging analytics for all Dart and Flutter related tooling to Google Analytics. | [![pub package](https://img.shields.io/pub/v/unified_analytics.svg)](https://pub.dev/packages/unified_analytics) | | [source_map_stack_trace](pkgs/source_map_stack_trace/) | A package for applying source maps to stack traces. | [![pub package](https://img.shields.io/pub/v/source_map_stack_trace.svg)](https://pub.dev/packages/source_map_stack_trace) | +| [oauth2](pkgs/oauth2/) | A client library for authenticatingand making requests via OAuth2. | [![pub package](https://img.shields.io/pub/v/oauth2.svg)](https://pub.dev/packages/oauth2) | ## Publishing automation diff --git a/pkgs/oauth2/.github/dependabot.yaml b/pkgs/oauth2/.github/dependabot.yaml deleted file mode 100644 index bf6b38a4d..000000000 --- a/pkgs/oauth2/.github/dependabot.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# Dependabot configuration file. -version: 2 - -updates: - - package-ecosystem: github-actions - directory: / - schedule: - interval: monthly - labels: - - autosubmit - groups: - github-actions: - patterns: - - "*" diff --git a/pkgs/oauth2/CHANGELOG.md b/pkgs/oauth2/CHANGELOG.md index 2c5da180d..33d927e25 100644 --- a/pkgs/oauth2/CHANGELOG.md +++ b/pkgs/oauth2/CHANGELOG.md @@ -1,6 +1,7 @@ -## 2.0.3-wip +## 2.0.3 * Require `package:http` v1.0.0 +* Move to `dart-lang/tools`. ## 2.0.2 diff --git a/pkgs/oauth2/README.md b/pkgs/oauth2/README.md index 196b9f789..07a5976b6 100644 --- a/pkgs/oauth2/README.md +++ b/pkgs/oauth2/README.md @@ -1,4 +1,4 @@ -[![Dart CI](https://github.com/dart-lang/oauth2/actions/workflows/test-package.yml/badge.svg)](https://github.com/dart-lang/oauth2/actions/workflows/test-package.yml) +[![Dart CI](https://github.com/dart-lang/tools/actions/workflows/oauth2.yml/badge.svg)](https://github.com/dart-lang/tools/actions/workflows/oauth2.yml) [![pub package](https://img.shields.io/pub/v/oauth2.svg)](https://pub.dev/packages/oauth2) [![package publisher](https://img.shields.io/pub/publisher/oauth2.svg)](https://pub.dev/packages/oauth2/publisher) diff --git a/pkgs/oauth2/pubspec.yaml b/pkgs/oauth2/pubspec.yaml index d019335a0..9c89b7bd4 100644 --- a/pkgs/oauth2/pubspec.yaml +++ b/pkgs/oauth2/pubspec.yaml @@ -1,10 +1,10 @@ name: oauth2 -version: 2.0.3-wip +version: 2.0.3 description: >- A client library for authenticating with a remote service via OAuth2 on behalf of a user, and making authorized HTTP requests with the user's OAuth2 credentials. -repository: https://github.com/dart-lang/oauth2 +repository: https://github.com/dart-lang/tools/tree/main/pkgs/oauth2 environment: sdk: ^3.0.0 From 032078caa32026293f8961ca7b1f09749544aa0d Mon Sep 17 00:00:00 2001 From: Moritz Date: Wed, 21 Aug 2024 14:28:19 +0200 Subject: [PATCH 159/159] Add license to example --- pkgs/oauth2/example/main.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkgs/oauth2/example/main.dart b/pkgs/oauth2/example/main.dart index 9db62fc2f..68e5aa062 100644 --- a/pkgs/oauth2/example/main.dart +++ b/pkgs/oauth2/example/main.dart @@ -1,3 +1,7 @@ +// Copyright (c) 2024, 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. + import 'dart:async'; import 'dart:io';