mirror of https://github.com/grpc/grpc-dart.git
182 lines
6.3 KiB
Dart
182 lines
6.3 KiB
Dart
// Copyright (c) 2023, the gRPC project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:googleapis_auth/auth_io.dart' as auth;
|
|
import 'package:http/http.dart' as http;
|
|
|
|
import 'auth.dart';
|
|
|
|
class ComputeEngineAuthenticator extends HttpBasedAuthenticator {
|
|
@override
|
|
Future<auth.AccessCredentials> obtainCredentialsWithClient(
|
|
http.Client client, String uri) =>
|
|
auth.obtainAccessCredentialsViaMetadataServer(client);
|
|
}
|
|
|
|
class ServiceAccountAuthenticator extends HttpBasedAuthenticator {
|
|
late auth.ServiceAccountCredentials _serviceAccountCredentials;
|
|
final List<String> _scopes;
|
|
String? _projectId;
|
|
|
|
ServiceAccountAuthenticator.fromJson(
|
|
Map<String, dynamic> serviceAccountJson, this._scopes)
|
|
: _serviceAccountCredentials =
|
|
auth.ServiceAccountCredentials.fromJson(serviceAccountJson),
|
|
_projectId = serviceAccountJson['project_id'];
|
|
|
|
factory ServiceAccountAuthenticator(
|
|
String serviceAccountJsonString, List<String> scopes) =>
|
|
ServiceAccountAuthenticator.fromJson(
|
|
jsonDecode(serviceAccountJsonString), scopes);
|
|
|
|
String? get projectId => _projectId;
|
|
|
|
@override
|
|
Future<auth.AccessCredentials> obtainCredentialsWithClient(
|
|
http.Client client, String uri) =>
|
|
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,
|
|
);
|
|
|
|
@override
|
|
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) {
|
|
Object? 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();
|
|
}
|