blob: e6863a201d6f36d7bf9b8ee661518f50a1c358ff [file] [log] [blame]
// 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.
import 'dart:async';
import 'dart:convert';
import 'package:googleapis_auth/googleapis_auth.dart';
import 'package:googleapis_auth/src/http_client_base.dart';
import 'package:googleapis_auth/src/known_uris.dart';
import 'package:googleapis_auth/src/utils.dart';
import 'package:http/http.dart';
import 'package:test/test.dart';
import 'test_utils.dart';
final _defaultResponse = Response('', 500);
Future<Response> _defaultResponseHandler(Request _) async => _defaultResponse;
void main() {
test('access-token', () {
final expiry = DateTime.now().subtract(const Duration(seconds: 1));
final expiryUtc = expiry.toUtc();
expect(() => AccessToken('foo', 'bar', expiry), throwsArgumentError);
final token = AccessToken('foo', 'bar', expiryUtc);
expect(token.type, equals('foo'));
expect(token.data, equals('bar'));
expect(token.expiry, equals(expiryUtc));
expect(token.hasExpired, isTrue);
final nonExpiredToken =
AccessToken('foo', 'bar', expiryUtc.add(const Duration(days: 1)));
expect(nonExpiredToken.hasExpired, isFalse);
});
test('access-credentials', () {
final expiry = DateTime.now().add(const Duration(days: 1)).toUtc();
final aToken = AccessToken('foo', 'bar', expiry);
final credentials = AccessCredentials(aToken, 'refresh', ['scope']);
expect(credentials.accessToken, equals(aToken));
expect(credentials.refreshToken, equals('refresh'));
expect(credentials.scopes, equals(['scope']));
});
test('client-id', () {
final clientId = ClientId('id', 'secret');
expect(clientId.identifier, equals('id'));
expect(clientId.secret, equals('secret'));
});
group('service-account-credentials', () {
final clientId = ClientId.serviceAccount('id');
const credentials = {
'private_key_id': '301029',
'private_key': testPrivateKeyString,
'client_email': 'a@b.com',
'client_id': 'myid',
'type': 'service_account'
};
test('from-valid-individual-params', () {
final credentials =
ServiceAccountCredentials('email', clientId, testPrivateKeyString);
expect(credentials.email, equals('email'));
expect(credentials.clientId, equals(clientId));
expect(credentials.privateKey, equals(testPrivateKeyString));
expect(credentials.impersonatedUser, isNull);
});
test('from-valid-individual-params-with-user', () {
final credentials = ServiceAccountCredentials(
'email', clientId, testPrivateKeyString,
impersonatedUser: 'x@y.com');
expect(credentials.email, equals('email'));
expect(credentials.clientId, equals(clientId));
expect(credentials.privateKey, equals(testPrivateKeyString));
expect(credentials.impersonatedUser, equals('x@y.com'));
});
test('from-json-string', () {
final credentialsFromJson =
ServiceAccountCredentials.fromJson(jsonEncode(credentials));
expect(credentialsFromJson.email, equals('a@b.com'));
expect(credentialsFromJson.clientId.identifier, equals('myid'));
expect(credentialsFromJson.clientId.secret, isNull);
expect(credentialsFromJson.privateKey, equals(testPrivateKeyString));
expect(credentialsFromJson.impersonatedUser, isNull);
});
test('from-json-string-with-user', () {
final credentialsFromJson = ServiceAccountCredentials.fromJson(
jsonEncode(credentials),
impersonatedUser: 'x@y.com');
expect(credentialsFromJson.email, equals('a@b.com'));
expect(credentialsFromJson.clientId.identifier, equals('myid'));
expect(credentialsFromJson.clientId.secret, isNull);
expect(credentialsFromJson.privateKey, equals(testPrivateKeyString));
expect(credentialsFromJson.impersonatedUser, equals('x@y.com'));
});
test('from-json-map', () {
final credentialsFromJson =
ServiceAccountCredentials.fromJson(credentials);
expect(credentialsFromJson.email, equals('a@b.com'));
expect(credentialsFromJson.clientId.identifier, equals('myid'));
expect(credentialsFromJson.clientId.secret, isNull);
expect(credentialsFromJson.privateKey, equals(testPrivateKeyString));
expect(credentialsFromJson.impersonatedUser, isNull);
});
test('from-json-map-with-user', () {
final credentialsFromJson = ServiceAccountCredentials.fromJson(
credentials,
impersonatedUser: 'x@y.com');
expect(credentialsFromJson.email, equals('a@b.com'));
expect(credentialsFromJson.clientId.identifier, equals('myid'));
expect(credentialsFromJson.clientId.secret, isNull);
expect(credentialsFromJson.privateKey, equals(testPrivateKeyString));
expect(credentialsFromJson.impersonatedUser, equals('x@y.com'));
});
});
group('client-wrappers', () {
final clientId = ClientId('id', 'secret');
final tomorrow = DateTime.now().add(const Duration(days: 1)).toUtc();
final yesterday = DateTime.now().subtract(const Duration(days: 1)).toUtc();
final aToken = AccessToken('Bearer', 'bar', tomorrow);
final credentials = AccessCredentials(aToken, 'refresh', ['s1', 's2']);
Future<Response> successfulRefresh(Request request) async {
expect(request.method, equals('POST'));
expect(request.url, googleOauth2TokenEndpoint);
expect(
request.body,
equals(
'client_id=id&'
'client_secret=secret&'
'refresh_token=refresh&'
'grant_type=refresh_token',
),
);
final body = jsonEncode({
'token_type': 'Bearer',
'access_token': 'atoken',
'expires_in': 3600,
});
return Response(body, 200, headers: jsonContentType);
}
Future<Response> refreshErrorResponse(Request request) async {
final body = jsonEncode({'error': 'An error occurred'});
return Response(body, 400, headers: jsonContentType);
}
Future<Response> serverError(Request request) =>
Future<Response>.error(Exception('transport layer exception'));
test('refreshCredentials-successful', () async {
final newCredentials = await refreshCredentials(clientId, credentials,
mockClient(expectAsync1(successfulRefresh), expectClose: false));
final expectedResultUtc = DateTime.now()
.toUtc()
.add(const Duration(seconds: 3600 - maxExpectedTimeDiffInSeconds));
final accessToken = newCredentials.accessToken;
expect(accessToken.type, equals('Bearer'));
expect(accessToken.data, equals('atoken'));
expect(accessToken.expiry.difference(expectedResultUtc).inSeconds,
equals(0));
expect(newCredentials.refreshToken, equals('refresh'));
expect(newCredentials.scopes, equals(['s1', 's2']));
});
test('refreshCredentials-http-error', () async {
await expectLater(
refreshCredentials(
clientId,
credentials,
mockClient(serverError, expectClose: false),
),
throwsA(
isA<Exception>().having(
(p0) => p0.toString(),
'toString',
'Exception: transport layer exception',
),
),
);
});
test('refreshCredentials-error-response', () async {
await expectLater(
refreshCredentials(
clientId,
credentials,
mockClient(refreshErrorResponse, expectClose: false),
),
throwsA(isServerRequestFailedException),
);
});
group('authenticatedClient', () {
final url = Uri.parse('http://www.example.com');
test('successful', () async {
final client = authenticatedClient(
mockClient(expectAsync1((request) async {
expect(request.method, equals('POST'));
expect(request.url, equals(url));
expect(request.headers.length, equals(1));
expect(request.headers['Authorization'], equals('Bearer bar'));
return Response('', 204);
}), expectClose: false),
credentials,
);
expect(client.credentials, equals(credentials));
final response = await client.send(RequestImpl('POST', url));
expect(response.statusCode, equals(204));
});
test('access-denied', () {
final client = authenticatedClient(
mockClient(expectAsync1((request) async {
expect(request.method, equals('POST'));
expect(request.url, equals(url));
expect(request.headers.length, equals(1));
expect(request.headers['Authorization'], equals('Bearer bar'));
const headers = {'www-authenticate': 'foobar'};
return Response('', 401, headers: headers);
}), expectClose: false),
credentials,
);
expect(client.credentials, equals(credentials));
expect(client.send(RequestImpl('POST', url)),
throwsA(isAccessDeniedException));
});
test('non-bearer-token', () {
final aToken = credentials.accessToken;
final nonBearerCredentials = AccessCredentials(
AccessToken('foobar', aToken.data, aToken.expiry),
'refresh',
['s1', 's2']);
expect(
() => authenticatedClient(
mockClient(_defaultResponseHandler, expectClose: false),
nonBearerCredentials,
),
throwsA(isArgumentError),
);
});
});
group('autoRefreshingClient', () {
final url = Uri.parse('http://www.example.com');
test('up-to-date', () async {
final client = autoRefreshingClient(
clientId,
credentials,
mockClient(
expectAsync1((request) async => Response('', 200)),
expectClose: false,
),
);
expect(client.credentials, equals(credentials));
final response = await client.send(RequestImpl('POST', url));
expect(response.statusCode, equals(200));
});
test('no-refresh-token', () {
final credentials = AccessCredentials(
AccessToken('Bearer', 'bar', yesterday), null, ['s1', 's2']);
expect(
() => autoRefreshingClient(
clientId,
credentials,
mockClient(_defaultResponseHandler, expectClose: false),
),
throwsA(isArgumentError),
);
});
test('refresh-failed', () {
final credentials = AccessCredentials(
AccessToken('Bearer', 'bar', yesterday), 'refresh', ['s1', 's2']);
final client = autoRefreshingClient(
clientId,
credentials,
mockClient(expectAsync1((request) {
// This should be a refresh request.
expect(request.headers['foo'], isNull);
return refreshErrorResponse(request);
}), expectClose: false),
);
expect(client.credentials, equals(credentials));
final request = RequestImpl('POST', url);
request.headers.addAll({'foo': 'bar'});
expect(client.send(request), throwsA(isServerRequestFailedException));
});
test('invalid-content-type', () {
final credentials = AccessCredentials(
AccessToken('Bearer', 'bar', yesterday), 'refresh', ['s1', 's2']);
final client = autoRefreshingClient(
clientId,
credentials,
mockClient(expectAsync1((request) async {
// This should be a refresh request.
expect(request.headers['foo'], isNull);
final headers = {'content-type': 'image/png'};
return Response('', 200, headers: headers);
}), expectClose: false),
);
expect(client.credentials, equals(credentials));
final request = RequestImpl('POST', url);
request.headers.addAll({'foo': 'bar'});
expect(client.send(request), throwsA(isServerRequestFailedException));
});
test('successful-refresh', () async {
var serverInvocation = 0;
final credentials = AccessCredentials(
AccessToken('Bearer', 'bar', yesterday), 'refresh', ['s1']);
final client = autoRefreshingClient(
clientId,
credentials,
mockClient(
expectAsync1(
(request) async {
if (serverInvocation++ == 0) {
// This should be a refresh request.
expect(request.headers['foo'], isNull);
return successfulRefresh(request);
} else {
// This is the real request.
expect(request.headers['foo'], equals('bar'));
return Response('', 200);
}
},
count: 2,
),
));
expect(client.credentials, equals(credentials));
var executed = false;
client.credentialUpdates.listen(
expectAsync1((newCredentials) {
expect(newCredentials.accessToken.type, equals('Bearer'));
expect(newCredentials.accessToken.data, equals('atoken'));
executed = true;
}),
onDone: expectAsync0(() {}),
);
final request = RequestImpl('POST', url);
request.headers.addAll({'foo': 'bar'});
final response = await client.send(request);
expect(response.statusCode, equals(200));
// The `client.send()` will have triggered a credentials refresh.
expect(executed, isTrue);
client.close();
});
});
});
}