| // 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 '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 Uri tokenEndpoint = Uri.parse('http://example.com/token'); |
| |
| void main() { |
| late ExpectClient httpClient; |
| |
| setUp(() => httpClient = ExpectClient()); |
| |
| test('is not expired if no expiration exists', () { |
| var credentials = oauth2.Credentials('access token'); |
| expect(credentials.isExpired, isFalse); |
| }); |
| |
| test('is not expired if the expiration is in the future', () { |
| 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(const Duration(hours: 1)); |
| var credentials = |
| oauth2.Credentials('access token', expiration: expiration); |
| expect(credentials.isExpired, isTrue); |
| }); |
| |
| test("can't refresh without a refresh token", () { |
| var credentials = |
| oauth2.Credentials('access token', tokenEndpoint: tokenEndpoint); |
| expect(credentials.canRefresh, false); |
| |
| expect( |
| credentials.refresh( |
| identifier: 'identifier', secret: 'secret', httpClient: httpClient), |
| throwsStateError); |
| }); |
| |
| test("can't refresh without a token endpoint", () { |
| var credentials = |
| oauth2.Credentials('access token', refreshToken: 'refresh token'); |
| expect(credentials.canRefresh, false); |
| |
| expect( |
| credentials.refresh( |
| identifier: 'identifier', secret: 'secret', httpClient: httpClient), |
| throwsStateError); |
| }); |
| |
| 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']); |
| 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 Future.value(http.Response( |
| jsonEncode({ |
| '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('sets proper scope string when using custom delimiter', () async { |
| 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 Future.value(http.Response( |
| jsonEncode({ |
| '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 = 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': 'identifier' |
| })); |
| |
| return Future.value(http.Response( |
| jsonEncode({ |
| '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); |
| expect(credentials.accessToken, equals('new access token')); |
| expect(credentials.refreshToken, equals('new refresh token')); |
| }); |
| |
| test('can refresh without client authentication', () async { |
| var credentials = 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 Future.value(http.Response( |
| jsonEncode({ |
| '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 = 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 Future.value(http.Response( |
| jsonEncode( |
| {'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); |
| 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 = 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 Future.value(http.Response( |
| jsonEncode({ |
| '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<String, dynamic> 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(const Duration(hours: 1)); |
| expiration = DateTime.fromMillisecondsSinceEpoch( |
| expiration.millisecondsSinceEpoch); |
| |
| var credentials = oauth2.Credentials('access token', |
| refreshToken: 'refresh token', |
| idToken: 'id token', |
| tokenEndpoint: tokenEndpoint, |
| scopes: ['scope1', 'scope2'], |
| expiration: expiration); |
| var reloaded = oauth2.Credentials.fromJson(credentials.toJson()); |
| |
| 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)); |
| expect(reloaded.expiration, equals(credentials.expiration)); |
| }); |
| |
| 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(() => 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 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); |
| }); |
| |
| 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); |
| }); |
| }); |
| } |