diff --git a/googleapis_auth/CHANGELOG.md b/googleapis_auth/CHANGELOG.md index 738765cb1..6b2882f9b 100644 --- a/googleapis_auth/CHANGELOG.md +++ b/googleapis_auth/CHANGELOG.md @@ -31,6 +31,11 @@ - Added an optional `listenPort` parameter to `clientViaUserConsent` and `obtainAccessCredentialsViaUserConsent`. +#### `auth_io.dart` + +- Handle the `source_credentials` from the credentials file to support + usage of impersonating service account with gcloud cli. + ## 1.3.1 - Include `plugin_name` during browser authorization. diff --git a/googleapis_auth/lib/src/adc_utils.dart b/googleapis_auth/lib/src/adc_utils.dart index e44b4f5a5..d02b7dd3f 100644 --- a/googleapis_auth/lib/src/adc_utils.dart +++ b/googleapis_auth/lib/src/adc_utils.dart @@ -32,10 +32,16 @@ Future fromApplicationsCredentialsFile( ); } - if (credentials is Map && credentials['type'] == 'authorized_user') { + if (credentials is Map && + (credentials['type'] == 'authorized_user' || + credentials['type'] == 'impersonated_service_account')) { + final credentialsValue = credentials['type'] == 'authorized_user' + ? credentials + : credentials['source_credentials'] as Map; + final clientId = ClientId( - credentials['client_id'] as String, - credentials['client_secret'] as String?, + credentialsValue['client_id'] as String, + credentialsValue['client_secret'] as String?, ); return AutoRefreshingClient( baseClient, @@ -45,7 +51,7 @@ Future fromApplicationsCredentialsFile( AccessCredentials( // Hack: Create empty credentials that have expired. AccessToken('Bearer', '', DateTime(0).toUtc()), - credentials['refresh_token'] as String?, + credentialsValue['refresh_token'] as String?, scopes, ), baseClient, @@ -53,6 +59,7 @@ Future fromApplicationsCredentialsFile( quotaProject: credentials['quota_project_id'] as String?, ); } + return await clientViaServiceAccount( ServiceAccountCredentials.fromJson(credentials), scopes, diff --git a/googleapis_auth/test/adc_test.dart b/googleapis_auth/test/adc_test.dart index 3d9fd8b93..4f11bc068 100644 --- a/googleapis_auth/test/adc_test.dart +++ b/googleapis_auth/test/adc_test.dart @@ -67,6 +67,64 @@ void main() { } }); + test('fromApplicationsCredentialsFile w. source_credentials', () async { + final tmp = await Directory.systemTemp.createTemp('googleapis_auth-test'); + try { + final credsFile = File.fromUri(tmp.uri.resolve('creds.json')); + await credsFile.writeAsString(json.encode({ + 'service_account_impersonation_url': 'url', + 'source_credentials': { + 'client_id': 'id', + 'client_secret': 'secret', + 'refresh_token': 'refresh', + 'type': 'authorized_user' + }, + 'type': 'impersonated_service_account', + })); + final c = await fromApplicationsCredentialsFile( + credsFile, + 'test-credentials-file', + [], + mockClient((Request request) async { + final url = request.url; + if (url == googleOauth2TokenEndpoint) { + expect(request.method, equals('POST')); + 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); + } + if (url.toString() == + 'https://storage.googleapis.com/b/bucket/o/obj') { + expect(request.method, equals('GET')); + expect(request.headers['Authorization'], equals('Bearer atoken')); + expect(request.headers['X-Goog-User-Project'], isNull); + return Response('hello world', 200); + } + return Response('bad', 404); + }), + ); + expect(c.credentials.accessToken.data, equals('atoken')); + + final r = + await c.get(Uri.https('storage.googleapis.com', '/b/bucket/o/obj')); + expect(r.statusCode, equals(200)); + expect(r.body, equals('hello world')); + + c.close(); + } finally { + await tmp.delete(recursive: true); + } + }); + test('fromApplicationsCredentialsFile w. quota_project_id', () async { final tmp = await Directory.systemTemp.createTemp('googleapis_auth-test'); try {