blob: 20a375fd684eba3a74607255779802c6f5599b84 [file] [log] [blame]
// Copyright 2019 The Flutter Authors. 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:io';
import 'package:cocoon_service/src/model/appengine/allowed_account.dart';
import 'package:cocoon_service/src/model/google/token_info.dart';
import 'package:cocoon_service/src/request_handling/authentication.dart';
import 'package:cocoon_service/src/request_handling/exceptions.dart';
import 'package:cocoon_service/src/service/logging.dart';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:logging/logging.dart';
import 'package:test/test.dart';
import '../src/datastore/fake_config.dart';
import '../src/request_handling/fake_authentication.dart';
import '../src/request_handling/fake_http.dart';
void main() {
group('AuthenticationProvider', () {
late FakeConfig config;
late FakeClientContext clientContext;
late FakeHttpRequest request;
late AuthenticationProvider auth;
late TokenInfo token;
setUp(() {
config = FakeConfig();
token = TokenInfo(email: 'abc123@gmail.com', issued: DateTime.now());
clientContext = FakeClientContext();
request = FakeHttpRequest();
auth = AuthenticationProvider(
config: config,
clientContextProvider: () => clientContext,
httpClientProvider: () => throw AssertionError(),
);
});
test('throws Unauthenticated with no auth headers', () async {
expect(auth.authenticate(request), throwsA(isA<Unauthenticated>()));
});
test('succeeds for App Engine cronjobs', () async {
request.headers.set('X-Appengine-Cron', 'true');
final AuthenticatedContext result = await auth.authenticate(request);
expect(result.clientContext, same(clientContext));
});
group('when id token is given', () {
late MockClient httpClient;
setUp(() {
auth = AuthenticationProvider(
config: config,
clientContextProvider: () => clientContext,
httpClientProvider: () => httpClient,
);
});
test('auth succeeds with authenticated header', () async {
httpClient = MockClient((_) async => http.Response('{"aud": "client-id", "hd": "google.com"}', HttpStatus.ok));
auth = AuthenticationProvider(
config: config,
clientContextProvider: () => clientContext,
httpClientProvider: () => httpClient,
);
config.oauthClientIdValue = 'client-id';
request.headers.add('X-Flutter-IdToken', 'authenticated');
final AuthenticatedContext result = await auth.authenticate(request);
expect(result.clientContext, same(clientContext));
expect(result, isNotNull);
});
test('fails if token verification fails', () async {
config.oauthClientIdValue = 'client-id';
request.headers.add('X-Flutter-IdToken', 'authenticated');
await expectLater(
auth.authenticateToken(
token,
clientContext: FakeClientContext(),
),
throwsA(isA<Unauthenticated>()),
);
});
test('fails if tokenInfo returns invalid JSON', () async {
httpClient = MockClient((_) async => http.Response('Not JSON!', HttpStatus.ok));
final List<LogRecord> records = <LogRecord>[];
log.onRecord.listen((LogRecord record) => records.add(record));
await expectLater(auth.tokenInfo(request), throwsA(isA<InternalServerError>()));
expect(records, isEmpty);
});
test('fails if token verification yields forged token', () async {
final TokenInfo token = TokenInfo(
audience: 'forgery',
email: 'abc@abc.com',
issued: DateTime.now(),
);
config.oauthClientIdValue = 'expected-client-id';
await expectLater(
auth.authenticateToken(
token,
clientContext: FakeClientContext(),
),
throwsA(isA<Unauthenticated>()),
);
});
test('allows different aud for gcloud tokens with google accounts', () async {
final TokenInfo token = TokenInfo(
audience: 'different',
email: 'abc@google.com',
issued: DateTime.now(),
);
config.oauthClientIdValue = 'expected-client-id';
await expectLater(
auth.authenticateToken(
token,
clientContext: FakeClientContext(),
),
throwsA(isA<Unauthenticated>()),
);
});
test('succeeds for google.com auth user', () async {
final TokenInfo token = TokenInfo(
audience: 'client-id',
hostedDomain: 'google.com',
email: 'abc@google.com',
issued: DateTime.now(),
);
config.oauthClientIdValue = 'client-id';
final AuthenticatedContext result = await auth.authenticateToken(
token,
clientContext: clientContext,
);
expect(result.clientContext, same(clientContext));
});
test('fails for non-allowed non-Google auth users', () async {
final TokenInfo token =
TokenInfo(audience: 'client-id', hostedDomain: 'gmail.com', email: 'abc@gmail.com', issued: DateTime.now());
config.oauthClientIdValue = 'client-id';
await expectLater(
auth.authenticateToken(
token,
clientContext: FakeClientContext(),
),
throwsA(isA<Unauthenticated>()),
);
});
test('succeeds for allowed non-Google auth users', () async {
final AllowedAccount account = AllowedAccount(
key: config.db.emptyKey.append<int>(AllowedAccount, id: 123),
email: 'test@gmail.com',
);
config.db.values[account.key] = account;
final TokenInfo token = TokenInfo(
audience: 'client-id',
email: 'test@gmail.com',
issued: DateTime.now(),
);
config.oauthClientIdValue = 'client-id';
final AuthenticatedContext result = await auth.authenticateToken(token, clientContext: clientContext);
expect(result.clientContext, same(clientContext));
});
});
});
}