Merge branch 'master' into master
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3827b2b..87dfbad 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,11 @@
-# 1.2.4
+# 1.4.0
* OpenID's id_token treated.
+# 1.3.0
+
+* Added `onCredentialsRefreshed` option when creating `Client` objects.
+
# 1.2.3
* Support the latest `package:http` release.
diff --git a/README.md b/README.md
index d3045a3..053f88b 100644
--- a/README.md
+++ b/README.md
@@ -97,7 +97,7 @@
}
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");
diff --git a/lib/src/authorization_code_grant.dart b/lib/src/authorization_code_grant.dart
index 6b28290..526ab9f 100644
--- a/lib/src/authorization_code_grant.dart
+++ b/lib/src/authorization_code_grant.dart
@@ -9,6 +9,7 @@
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 @@
/// 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 @@
/// [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 @@
String delimiter,
bool basicAuth = true,
http.Client httpClient,
+ CredentialsRefreshedCallback onCredentialsRefreshed,
Map<String, dynamic> 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 @@
identifier: this.identifier,
secret: this.secret,
basicAuth: _basicAuth,
- httpClient: _httpClient);
+ httpClient: _httpClient,
+ onCredentialsRefreshed: _onCredentialsRefreshed);
}
/// Closes the grant and frees its resources.
diff --git a/lib/src/client.dart b/lib/src/client.dart
index 367d583..4fa64a1 100644
--- a/lib/src/client.dart
+++ b/lib/src/client.dart
@@ -11,8 +11,6 @@
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
@@ -67,6 +65,9 @@
Credentials get credentials => _credentials;
Credentials _credentials;
+ /// Callback to be invoked whenever the credentials refreshed.
+ final CredentialsRefreshedCallback _onCredentialsRefreshed;
+
/// Whether to use HTTP Basic authentication for authorizing the client.
final bool _basicAuth;
@@ -86,9 +87,11 @@
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 +159,8 @@
basicAuth: _basicAuth,
httpClient: _httpClient);
+ if (_onCredentialsRefreshed != null) _onCredentialsRefreshed(_credentials);
+
return this;
}
diff --git a/lib/src/credentials.dart b/lib/src/credentials.dart
index ecbe0d4..6ea254d 100644
--- a/lib/src/credentials.dart
+++ b/lib/src/credentials.dart
@@ -13,6 +13,9 @@
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/lib/src/resource_owner_password_grant.dart b/lib/src/resource_owner_password_grant.dart
index 13d8f28..a3c16f5 100644
--- a/lib/src/resource_owner_password_grant.dart
+++ b/lib/src/resource_owner_password_grant.dart
@@ -10,6 +10,7 @@
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 @@
String secret,
Iterable<String> scopes,
bool basicAuth = true,
+ CredentialsRefreshedCallback onCredentialsRefreshed,
http.Client httpClient,
String delimiter,
Map<String, dynamic> getParameters(
@@ -84,5 +86,8 @@
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/test/authorization_code_grant_test.dart b/test/authorization_code_grant_test.dart
index ec6e71b..babdb78 100644
--- a/test/authorization_code_grant_test.dart
+++ b/test/authorization_code_grant_test.dart
@@ -289,4 +289,50 @@
})));
});
});
+
+ 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));
+ });
+ });
}
diff --git a/test/client_test.dart b/test/client_test.dart
index ec52589..1f13d58 100644
--- a/test/client_test.dart
+++ b/test/client_test.dart
@@ -64,6 +64,38 @@
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;
+ expect(credentials.accessToken, equals('new access token'));
+ });
+
+ 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/test/resource_owner_password_grant_test.dart b/test/resource_owner_password_grant_test.dart
index 04d1537..acd76d1 100644
--- a/test/resource_owner_password_grant_test.dart
+++ b/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 'package:http/http.dart' as http;
import 'package:oauth2/oauth2.dart' as oauth2;
@@ -46,6 +46,44 @@
expect(client.credentials.accessToken, equals('2YotnFZFEjr1zCsicMWpAA'));
});
+ test('passes the onCredentialsRefreshed callback to the client', () async {
+ expectClient.expectRequest((request) async {
+ return new http.Response(
+ jsonEncode({
+ "access_token": "2YotnFZFEjr1zCsicMWpAA",
+ "token_type": "bearer",
+ "expires_in": -3600,
+ "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
+ }),
+ 200,
+ headers: {'content-type': 'application/json'});
+ });
+
+ var isCallbackInvoked = false;
+
+ var client = await oauth2.resourceOwnerPasswordGrant(
+ authEndpoint, 'username', 'userpass',
+ identifier: 'client', secret: 'secret', httpClient: expectClient,
+ onCredentialsRefreshed: (oauth2.Credentials credentials) {
+ isCallbackInvoked = true;
+ });
+
+ 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));
+ });
+
+ await client.read(Uri.parse("http://example.com/resource"));
+ expect(isCallbackInvoked, equals(true));
+ });
+
test('builds correct request when using query parameters for client',
() async {
expectClient.expectRequest((request) async {