fix(auth): fall back to GOOGLE_CLOUD_QUOTA_PROJECT when ADC has no quota_project_id (#751)
diff --git a/googleapis_auth/CHANGELOG.md b/googleapis_auth/CHANGELOG.md
index 0b7b716..78e21da 100644
--- a/googleapis_auth/CHANGELOG.md
+++ b/googleapis_auth/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 2.3.3
+
+- `GOOGLE_CLOUD_QUOTA_PROJECT` now takes precedence over a credential's
+ `quota_project_id` when set.
+
## 2.3.2
- Fix web compatibility by removing transitive `dart:io` and
diff --git a/googleapis_auth/lib/auth_io.dart b/googleapis_auth/lib/auth_io.dart
index 371f645..73230e3 100644
--- a/googleapis_auth/lib/auth_io.dart
+++ b/googleapis_auth/lib/auth_io.dart
@@ -39,6 +39,12 @@
/// 3. On Google Compute Engine and App Engine Flex we fetch credentials from
/// [GCE metadata service][meta-data].
///
+/// **Note on Quota Project:**
+/// When using credentials from a file, the quota project is determined by
+/// checking the `GOOGLE_CLOUD_QUOTA_PROJECT` environment variable first. If
+/// it is not set, it falls back to the `quota_project_id` defined within the
+/// credential file itself.
+///
/// [meta-data]: https://cloud.google.com/compute/docs/storing-retrieving-metadata
/// [svc-keys]: https://cloud.google.com/docs/authentication/getting-started
/// [gcloud-login]: https://cloud.google.com/sdk/gcloud/reference/auth/application-default/login
diff --git a/googleapis_auth/lib/src/adc_utils.dart b/googleapis_auth/lib/src/adc_utils.dart
index c99ec28..b979df9 100644
--- a/googleapis_auth/lib/src/adc_utils.dart
+++ b/googleapis_auth/lib/src/adc_utils.dart
@@ -59,7 +59,9 @@
Client baseClient, {
String? fileSource,
}) async {
- final quotaProject = credentials['quota_project_id'] as String?;
+ final quotaProject =
+ Platform.environment['GOOGLE_CLOUD_QUOTA_PROJECT'] ??
+ credentials['quota_project_id'] as String?;
if (credentials case {
'type': 'authorized_user',
'client_id': final String clientIdString,
diff --git a/googleapis_auth/pubspec.yaml b/googleapis_auth/pubspec.yaml
index 9d942a1..86380c3 100644
--- a/googleapis_auth/pubspec.yaml
+++ b/googleapis_auth/pubspec.yaml
@@ -1,5 +1,5 @@
name: googleapis_auth
-version: 2.3.2
+version: 2.3.3
description: Obtain Access credentials for Google services using OAuth 2.0
repository: https://github.com/google/googleapis.dart/tree/master/googleapis_auth
@@ -23,6 +23,7 @@
build_web_compilers: ^4.1.1
test: ^1.25.9
test_descriptor: ^2.0.2
+ test_process: ^2.0.3
false_secrets:
- test/test_utils.dart
diff --git a/googleapis_auth/test/adc_test.dart b/googleapis_auth/test/adc_test.dart
index fbaf635..1ff202c 100644
--- a/googleapis_auth/test/adc_test.dart
+++ b/googleapis_auth/test/adc_test.dart
@@ -9,6 +9,7 @@
import 'dart:convert';
import 'dart:io';
+import 'dart:isolate';
import 'package:googleapis_auth/src/adc_utils.dart'
show fromApplicationsCredentialsFile;
@@ -16,6 +17,7 @@
import 'package:http/http.dart';
import 'package:test/test.dart';
import 'package:test_descriptor/test_descriptor.dart' as d;
+import 'package:test_process/test_process.dart';
import 'test_utils.dart';
@@ -78,6 +80,60 @@
c.close();
});
+ group('GOOGLE_CLOUD_QUOTA_PROJECT environment variable', () {
+ late String quotaProjectPrint;
+
+ setUpAll(() async {
+ final packageUri = await Isolate.resolvePackageUri(
+ Uri.parse('package:googleapis_auth/'),
+ );
+ quotaProjectPrint = packageUri!
+ .resolve('../test/src/quota_project_print.dart')
+ .toFilePath();
+ });
+
+ Future<void> writeCreds({String? quotaProjectId}) => d
+ .file(
+ 'quota_project_env_creds.json',
+ json.encode({
+ 'client_id': 'id',
+ 'client_secret': 'secret',
+ 'refresh_token': 'refresh',
+ 'type': 'authorized_user',
+ 'quota_project_id': ?quotaProjectId,
+ }),
+ )
+ .create();
+
+ test('is used when credentials have no quota_project_id', () async {
+ await writeCreds();
+
+ final proc = await TestProcess.start(
+ Platform.resolvedExecutable,
+ [quotaProjectPrint, d.path('quota_project_env_creds.json')],
+ environment: {'GOOGLE_CLOUD_QUOTA_PROJECT': 'env-project'},
+ includeParentEnvironment: false,
+ );
+
+ await expectLater(proc.stdout, emits('env-project'));
+ await proc.shouldExit(0);
+ });
+
+ test('takes precedence over quota_project_id in credentials', () async {
+ await writeCreds(quotaProjectId: 'file-project');
+
+ final proc = await TestProcess.start(
+ Platform.resolvedExecutable,
+ [quotaProjectPrint, d.path('quota_project_env_creds.json')],
+ environment: {'GOOGLE_CLOUD_QUOTA_PROJECT': 'env-project'},
+ includeParentEnvironment: false,
+ );
+
+ await expectLater(proc.stdout, emits('env-project'));
+ await proc.shouldExit(0);
+ });
+ });
+
test('authorized_user credentials with quota_project_id', () async {
await d
.file(
diff --git a/googleapis_auth/test/src/quota_project_print.dart b/googleapis_auth/test/src/quota_project_print.dart
new file mode 100644
index 0000000..b1b99a7
--- /dev/null
+++ b/googleapis_auth/test/src/quota_project_print.dart
@@ -0,0 +1,41 @@
+// Copyright 2021 Google LLC
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:googleapis_auth/src/adc_utils.dart'
+ show fromApplicationsCredentialsFile;
+import 'package:googleapis_auth/src/known_uris.dart';
+import 'package:http/http.dart';
+import 'package:http/testing.dart';
+
+Future<void> main(List<String> args) async {
+ final client = await fromApplicationsCredentialsFile(
+ File(args[0]),
+ 'test-credentials-file',
+ [],
+ MockClient((request) async {
+ if (request.url == googleOauth2TokenEndpoint) {
+ return Response(
+ jsonEncode({
+ 'token_type': 'Bearer',
+ 'access_token': 'atoken',
+ 'expires_in': 3600,
+ }),
+ 200,
+ headers: {'content-type': 'application/json'},
+ );
+ }
+
+ print(request.headers['X-Goog-User-Project'] ?? 'NONE');
+ return Response('ok', 200);
+ }),
+ );
+
+ await client.get(Uri.https('storage.googleapis.com', '/b/bucket/o/obj'));
+ client.close();
+}