Added Application Default Credentials Support (#315)

* Application Default Credentials support

* Prepare a 2.2.0 release of grpc

* Update CHANGELOG.md

Co-authored-by: Sigurd Meldgaard <sigurdm@google.com>

Co-authored-by: Sigurd Meldgaard <sigurdm@google.com>
This commit is contained in:
Jonas Finnemann Jensen 2020-06-11 14:18:30 +02:00 committed by GitHub
parent 9ed03b6b96
commit 449faa80ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 138 additions and 2 deletions

View File

@ -1,3 +1,9 @@
## 2.2.0
* Added `applicationDefaultCredentialsAuthenticator` function for creating an
authenticator using [Application Default Credentials](https://cloud.google.com/docs/authentication/production).
* Less latency by using the `tcpNoDelay` option for sockets.
* Support grpc-web in a non-web setting.
## 2.1.3
* Fix bug in grpc-web when receiving an empty trailer.

View File

@ -20,7 +20,10 @@ export 'src/auth/auth.dart'
JwtServiceAccountAuthenticator;
export 'src/auth/auth_io.dart'
show ComputeEngineAuthenticator, ServiceAccountAuthenticator;
show
applicationDefaultCredentialsAuthenticator,
ComputeEngineAuthenticator,
ServiceAccountAuthenticator;
export 'src/client/call.dart' show CallOptions, ClientCall, MetadataProvider;
export 'src/client/client.dart' show Client;

View File

@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:googleapis_auth/auth_io.dart' as auth;
import 'package:http/http.dart' as http;
@ -31,3 +32,129 @@ class ServiceAccountAuthenticator extends HttpBasedAuthenticator {
auth.obtainAccessCredentialsViaServiceAccount(
_serviceAccountCredentials, _scopes, client);
}
class _CredentialsRefreshingAuthenticator extends HttpBasedAuthenticator {
final auth.ClientId _clientId;
auth.AccessCredentials _accessCredentials;
final String _quotaProject;
_CredentialsRefreshingAuthenticator(
this._clientId,
this._accessCredentials,
this._quotaProject,
);
Future<void> authenticate(Map<String, String> metadata, String uri) async {
await super.authenticate(metadata, uri);
if (_quotaProject != null) {
// https://cloud.google.com/apis/docs/system-parameters#definitions
metadata['X-Goog-User-Project'] = _quotaProject;
}
}
@override
Future<auth.AccessCredentials> obtainCredentialsWithClient(
http.Client client,
String uri,
) async {
_accessCredentials = await auth.refreshCredentials(
_clientId,
_accessCredentials,
client,
);
return _accessCredentials;
}
}
/// Create an [HttpBasedAuthenticator] using [Application Default Credentials][ADC].
///
/// Looks for credentials in the following order of preference:
/// 1. A JSON file whose path is specified by `GOOGLE_APPLICATION_CREDENTIALS`,
/// this file typically contains [exported service account keys][svc-keys].
/// 2. A JSON file created by [`gcloud auth application-default login`][gcloud-login]
/// in a well-known location (`%APPDATA%/gcloud/application_default_credentials.json`
/// on Windows and `$HOME/.config/gcloud/application_default_credentials.json` on Linux/Mac).
/// 3. On Google Compute Engine and App Engine Flex we fetch credentials from
/// [GCE metadata service][metadata].
///
/// [metadata]: 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
/// [ADC]: https://cloud.google.com/docs/authentication/production
Future<HttpBasedAuthenticator> applicationDefaultCredentialsAuthenticator(
List<String> scopes,
) async {
File credFile;
String fileSource;
// If env var specifies a file to load credentials from we'll do that.
final credsEnv = Platform.environment['GOOGLE_APPLICATION_CREDENTIALS'];
if (credsEnv != null && credsEnv.isNotEmpty) {
// If env var is specified and not empty, we always try to load, even if
// the file doesn't exist.
credFile = File(credsEnv);
fileSource = 'GOOGLE_APPLICATION_CREDENTIALS';
}
// Attempt to use file created by `gcloud auth application-default login`
File gcloudAdcFile;
if (Platform.isWindows) {
gcloudAdcFile = File.fromUri(Uri.directory(Platform.environment['APPDATA'])
.resolve('gcloud/application_default_credentials.json'));
} else {
gcloudAdcFile = File.fromUri(Uri.directory(Platform.environment['HOME'])
.resolve('.config/gcloud/application_default_credentials.json'));
}
// Only try to load from gcloudAdcFile if it exists.
if (credFile == null && await gcloudAdcFile.exists()) {
credFile = gcloudAdcFile;
fileSource = '`gcloud auth application-default login`';
}
// Attempt to load form credFile, if detected
if (credFile != null) {
var credentials;
try {
credentials = json.decode(await credFile.readAsString());
} on IOException {
throw Exception(
'Failed to read credentials file from $fileSource',
);
} on FormatException {
throw Exception(
'Failed to parse JSON from credentials file from $fileSource',
);
}
if (credentials is Map && credentials['type'] == 'authorized_user') {
final clientId = auth.ClientId(
credentials['client_id'],
credentials['client_secret'],
);
final client = http.Client();
try {
final accessCreds = await auth.refreshCredentials(
clientId,
auth.AccessCredentials(
// Hack: Create empty credentials that have expired.
auth.AccessToken('Bearer', '', DateTime(0).toUtc()),
credentials['refresh_token'],
scopes,
),
client,
);
return _CredentialsRefreshingAuthenticator(
clientId,
accessCreds,
credentials['quota_project_id'],
);
} finally {
client.close();
}
}
return ServiceAccountAuthenticator(json.encode(credentials), scopes);
}
return ComputeEngineAuthenticator();
}

View File

@ -1,7 +1,7 @@
name: grpc
description: Dart implementation of gRPC, a high performance, open-source universal RPC framework.
version: 2.1.3
version: 2.2.0
author: Dart Team <misc@dartlang.org>
homepage: https://github.com/dart-lang/grpc-dart