Revert "Code review changes"
This reverts commit 3b3e3a6617f4f028ee6ee638ef690ba502d4645b.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 686b6ac..fc869c9 100644
--- a/CHANGELOG.md
+++ b/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/README.md b/README.md
index 7e390cd..23be5ff 100644
--- a/README.md
+++ b/README.md
@@ -64,15 +64,13 @@
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/lib/src/authorization_code_grant.dart b/lib/src/authorization_code_grant.dart
index 7a73d44..e3bb645 100644
--- a/lib/src/authorization_code_grant.dart
+++ b/lib/src/authorization_code_grant.dart
@@ -68,9 +68,6 @@
/// 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 @@
/// 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 @@
/// query parameters provided to the redirect URL.
///
/// It is a [StateError] to call this more than once.
- Uri getAuthorizationUrl(Uri redirect, {Iterable<String> scopes,
- String state}) {
+ Uri getAuthorizationUrl(Uri redirect,
+ {List<String> scopes: const <String>[], 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 @@
/// the state beforehand.
Future<Client> _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/lib/src/client.dart b/lib/src/client.dart
index adf53d5..371eed3 100644
--- a/lib/src/client.dart
+++ b/lib/src/client.dart
@@ -7,7 +7,6 @@
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 @@
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 @@
///
/// [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 @@
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 @@
}
_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/lib/src/credentials.dart b/lib/src/credentials.dart
index 0a12f3f..37e7114 100644
--- a/lib/src/credentials.dart
+++ b/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 @@
/// [AuthorizationCodeGrant]. Alternately, it may be loaded from a serialized
/// form via [Credentials.fromJson].
Credentials(
- this.accessToken,
- {this.refreshToken,
- this.tokenEndpoint,
- Iterable<String> scopes,
- this.expiration})
- : scopes = new UnmodifiableListView(
- // Explicitly type-annotate the list literal to work around
- // sdk#24202.
- scopes == null ? <String>[] : 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 @@
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 @@
/// 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<Credentials> refresh(
- {String identifier,
+ String identifier,
String secret,
- Iterable<String> newScopes,
- bool basicAuth: true,
- http.Client httpClient}) async {
+ {List<String> 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 = <String>[];
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 @@
"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 (!scopes.isEmpty) fields["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,
- 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/lib/src/handle_access_token_response.dart b/lib/src/handle_access_token_response.dart
index ef9b198..0065f0c 100644
--- a/lib/src/handle_access_token_response.dart
+++ b/lib/src/handle_access_token_response.dart
@@ -81,10 +81,10 @@
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/lib/src/utils.dart b/lib/src/utils.dart
index bf260eb..734c58e 100644
--- a/lib/src/utils.dart
+++ b/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<String, String> 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<String> 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<String, String> 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/pubspec.yaml b/pubspec.yaml
index f52b8be..e40265a 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
name: oauth2
-version: 1.0.0-dev
+version: 0.9.4-dev
author: Dart Team <misc@dartlang.org>
homepage: http://github.com/dart-lang/oauth2
description: >
@@ -10,6 +10,6 @@
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/test/authorization_code_grant_test.dart b/test/authorization_code_grant_test.dart
index e03a607..deaad5c 100644
--- a/test/authorization_code_grant_test.dart
+++ b/test/authorization_code_grant_test.dart
@@ -20,9 +20,9 @@
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 @@
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 @@
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',
@@ -164,71 +163,6 @@
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'));
- 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'
diff --git a/test/client_test.dart b/test/client_test.dart
index 7dc5afd..969787a 100644
--- a/test/client_test.dart
+++ b/test/client_test.dart
@@ -23,10 +23,8 @@
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 @@
"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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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/test/credentials_test.dart b/test/credentials_test.dart
index f4d8886..d72614f 100644
--- a/test/credentials_test.dart
+++ b/test/credentials_test.dart
@@ -25,82 +25,37 @@
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 @@
"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,19 +77,15 @@
});
- 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 {
+ 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,
- scopes: ['scope1', 'scope2']);
+ 'access token', 'refresh token', tokenEndpoint);
expect(credentials.canRefresh, true);
httpClient.expectRequest((request) {
@@ -142,90 +94,23 @@
expect(request.bodyFields, equals({
"grant_type": "refresh_token",
"refresh_token": "refresh token",
- "scope": "scope1 scope2"
+ "client_id": "identifier",
+ "client_secret": "secret"
}));
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',
'token_type': 'bearer'
}), 200, headers: {'content-type': 'application/json'}));
});
- 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 @@
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/test/utils_test.dart b/test/utils_test.dart
new file mode 100644
index 0000000..54c2da5
--- /dev/null
+++ b/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);
+ });
+ });
+}