blob: 51d003128ee343696045f8a77315c38dee6ba630 [file] [log] [blame]
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library googleapis_auth.auth_io;
import 'dart:io';
import 'dart:async';
import 'package:http/http.dart';
import 'auth.dart';
import 'src/auth_http_utils.dart';
import 'src/adc_utils.dart';
import 'src/http_client_base.dart';
import 'src/oauth2_flows/auth_code.dart';
import 'src/oauth2_flows/jwt.dart';
import 'src/oauth2_flows/metadata_server.dart';
import 'src/typedefs.dart';
export 'auth.dart';
export 'src/typedefs.dart';
/// Create a client 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<AutoRefreshingAuthClient> clientViaApplicationDefaultCredentials({
List<String> scopes,
Client baseClient,
}) async {
if (baseClient == null) {
baseClient = new Client();
} else {
baseClient = nonClosingClient(baseClient);
}
// 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 specific and not empty, we always try to load, even if
// the file doesn't exist.
return await fromApplicationsCredentialsFile(
File(credsEnv),
'GOOGLE_APPLICATION_CREDENTIALS',
scopes,
baseClient,
);
}
// Attempt to use file created by `gcloud auth application-default login`
File credFile;
if (Platform.isWindows) {
credFile = File.fromUri(Uri.directory(Platform.environment['APPDATA'])
.resolve('gcloud/application_default_credentials.json'));
} else {
credFile = File.fromUri(Uri.directory(Platform.environment['HOME'])
.resolve('.config/gcloud/application_default_credentials.json'));
}
// Only try to load from credFile if it exists.
if (await credFile.exists()) {
return await fromApplicationsCredentialsFile(
credFile,
'`gcloud auth application-default login`',
scopes,
baseClient,
);
}
return await clientViaMetadataServer(baseClient: baseClient);
}
/// Obtains oauth2 credentials and returns an authenticated HTTP client.
///
/// See [obtainAccessCredentialsViaUserConsent] for specifics about the
/// arguments used for obtaining access credentials.
///
/// Once access credentials have been obtained, this function will complete
/// with an auto-refreshing HTTP client. Once the `AccessCredentials` expire
/// it will use it's refresh token (if available) to obtain new credentials.
/// See [autoRefreshingClient] for more information.
///
/// If [baseClient] is not given, one will be automatically created. It will be
/// used for making authenticated HTTP requests.
///
/// The user is responsible for closing the returned HTTP [Client].
/// Closing the returned [Client] will not close [baseClient].
Future<AutoRefreshingAuthClient> clientViaUserConsent(
ClientId clientId, List<String> scopes, PromptUserForConsent userPrompt,
{Client baseClient}) async {
bool closeUnderlyingClient = false;
if (baseClient == null) {
baseClient = new Client();
closeUnderlyingClient = true;
}
var flow = new AuthorizationCodeGrantServerFlow(
clientId, scopes, baseClient, userPrompt);
AccessCredentials credentials;
try {
credentials = await flow.run();
} catch (e) {
if (closeUnderlyingClient) {
baseClient.close();
}
rethrow;
}
return new AutoRefreshingClient(baseClient, clientId, credentials,
closeUnderlyingClient: closeUnderlyingClient);
}
/// Obtains oauth2 credentials and returns an authenticated HTTP client.
///
/// See [obtainAccessCredentialsViaUserConsentManual] for specifics about the
/// arguments used for obtaining access credentials.
///
/// Once access credentials have been obtained, this function will complete
/// with an auto-refreshing HTTP client. Once the `AccessCredentials` expire
/// it will use it's refresh token (if available) to obtain new credentials.
/// See [autoRefreshingClient] for more information.
///
/// If [baseClient] is not given, one will be automatically created. It will be
/// used for making authenticated HTTP requests.
///
/// The user is responsible for closing the returned HTTP [Client].
/// Closing the returned [Client] will not close [baseClient].
Future<AutoRefreshingAuthClient> clientViaUserConsentManual(ClientId clientId,
List<String> scopes, PromptUserForConsentManual userPrompt,
{Client baseClient}) async {
bool closeUnderlyingClient = false;
if (baseClient == null) {
baseClient = new Client();
closeUnderlyingClient = true;
}
var flow = new AuthorizationCodeGrantManualFlow(
clientId, scopes, baseClient, userPrompt);
AccessCredentials credentials;
try {
credentials = await flow.run();
} catch (e) {
if (closeUnderlyingClient) {
baseClient.close();
}
rethrow;
}
return new AutoRefreshingClient(baseClient, clientId, credentials,
closeUnderlyingClient: closeUnderlyingClient);
}
/// Obtains oauth2 credentials and returns an authenticated HTTP client.
///
/// See [obtainAccessCredentialsViaServiceAccount] for specifics about the
/// arguments used for obtaining access credentials.
///
/// Once access credentials have been obtained, this function will complete
/// with an auto-refreshing HTTP client. Once the `AccessCredentials` expire
/// it will obtain new access credentials.
///
/// If [baseClient] is not given, one will be automatically created. It will be
/// used for making authenticated HTTP requests and for obtaining access
/// credentials.
///
/// The user is responsible for closing the returned HTTP [Client].
/// Closing the returned [Client] will not close [baseClient].
Future<AutoRefreshingAuthClient> clientViaServiceAccount(
ServiceAccountCredentials clientCredentials, List<String> scopes,
{Client baseClient}) async {
if (baseClient == null) {
baseClient = new Client();
} else {
baseClient = nonClosingClient(baseClient);
}
var flow = new JwtFlow(
clientCredentials.email,
clientCredentials.privateRSAKey,
clientCredentials.impersonatedUser,
scopes,
baseClient);
AccessCredentials credentials;
try {
credentials = await flow.run();
} catch (e) {
baseClient.close();
rethrow;
}
return new _ServiceAccountClient(baseClient, credentials, flow);
}
/// Obtains oauth2 credentials and returns an authenticated HTTP client.
///
/// See [obtainAccessCredentialsViaMetadataServer] for specifics about the
/// arguments used for obtaining access credentials.
///
/// Once access credentials have been obtained, this function will complete
/// with an auto-refreshing HTTP client. Once the `AccessCredentials` expire
/// it will obtain new access credentials.
///
/// If [baseClient] is not given, one will be automatically created. It will be
/// used for making authenticated HTTP requests and for obtaining access
/// credentials.
///
/// The user is responsible for closing the returned HTTP [Client].
/// Closing the returned [Client] will not close [baseClient].
Future<AutoRefreshingAuthClient> clientViaMetadataServer(
{Client baseClient}) async {
if (baseClient == null) {
baseClient = new Client();
} else {
baseClient = nonClosingClient(baseClient);
}
var flow = new MetadataServerAuthorizationFlow(baseClient);
AccessCredentials credentials;
try {
credentials = await flow.run();
} catch (e) {
baseClient.close();
rethrow;
}
return new _MetadataServerClient(baseClient, credentials, flow);
}
/// Obtains a HTTP client which uses the given [apiKey] for making HTTP
/// requests.
///
/// Note that the returned client should *only* be used for making HTTP requests
/// to Google Services. The [apiKey] should not be disclosed to third parties.
///
/// The user is responsible for closing the returned HTTP [Client].
/// Closing the returned [Client] will not close [baseClient].
Client clientViaApiKey(String apiKey, {Client baseClient}) {
if (baseClient == null) {
baseClient = new Client();
} else {
baseClient = nonClosingClient(baseClient);
}
return new ApiKeyClient(baseClient, apiKey);
}
/// Obtain oauth2 [AccessCredentials] using the oauth2 authentication code flow.
///
/// The returned future will complete with `AccessCredentials` if the user
/// has given the application access to it's data. Otherwise the future will
/// complete with a `UserConsentException`.
///
/// In case another error occurs the returned future will complete with an
/// `Exception`.
///
/// [userPrompt] will be used for directing the user/user-agent to a URI. See
/// [PromptUserForConsent] for more information.
///
/// [client] will be used for obtaining `AccessCredentials` from an
/// authorization code.
///
/// The [ClientId] can be obtained in the Google Cloud Console.
Future<AccessCredentials> obtainAccessCredentialsViaUserConsent(
ClientId clientId,
List<String> scopes,
Client client,
PromptUserForConsent userPrompt) {
return new AuthorizationCodeGrantServerFlow(
clientId, scopes, client, userPrompt)
.run();
}
/// Obtain oauth2 [AccessCredentials] using the oauth2 authentication code flow.
///
/// The returned future will complete with `AccessCredentials` if the user
/// has given the application access to it's data. Otherwise the future will
/// complete with a `UserConsentException`.
///
/// In case another error occurs the returned future will complete with an
/// `Exception`.
///
/// [userPrompt] will be used for directing the user/user-agent to a URI. See
/// [PromptUserForConsentManual] for more information.
///
/// [client] will be used for obtaining `AccessCredentials` from an
/// authorization code.
///
/// The [ClientId] can be obtained in the Google Cloud Console.
Future<AccessCredentials> obtainAccessCredentialsViaUserConsentManual(
ClientId clientId,
List<String> scopes,
Client client,
PromptUserForConsentManual userPrompt) {
return new AuthorizationCodeGrantManualFlow(
clientId, scopes, client, userPrompt)
.run();
}
/// Obtain oauth2 [AccessCredentials] using service account credentials.
///
/// In case the service account has no access to the requested scopes or another
/// error occurs the returned future will complete with an `Exception`.
///
/// [baseClient] will be used for obtaining `AccessCredentials`.
///
/// The [ServiceAccountCredentials] can be obtained in the Google Cloud Console.
Future<AccessCredentials> obtainAccessCredentialsViaServiceAccount(
ServiceAccountCredentials clientCredentials,
List<String> scopes,
Client baseClient) {
return new JwtFlow(clientCredentials.email, clientCredentials.privateRSAKey,
clientCredentials.impersonatedUser, scopes, baseClient)
.run();
}
/// Obtain oauth2 [AccessCredentials] using the metadata API on ComputeEngine.
///
/// In case the VM was not configured with access to the requested scopes or an
/// error occurs the returned future will complete with an `Exception`.
///
/// [baseClient] will be used for obtaining `AccessCredentials`.
///
/// No credentials are needed. But this function is only intended to work on a
/// Google Compute Engine VM with configured access to Google APIs.
Future<AccessCredentials> obtainAccessCredentialsViaMetadataServer(
Client baseClient) {
return new MetadataServerAuthorizationFlow(baseClient).run();
}
/// Obtain oauth2 [AccessCredentials] by exchanging an authorization code.
///
/// Running a hybrid oauth2 flow as described in the
/// `googleapis_auth.auth_browser` library results in a `HybridFlowResult` which
/// contains short-lived [AccessCredentials] for the client and an authorization
/// code. This authorization code needs to be transferred to the server, which
/// can exchange it against long-lived [AccessCredentials].
///
/// If the authorization code was obtained using the mentioned hybrid flow, the
/// [redirectUrl] must be `"postmessage"` (default).
///
/// If you obtained the authorization code using a different mechanism, the
/// [redirectUrl] must be the same that was used to obtain the code.
///
/// NOTE: Only the server application will know the `client secret` - which is
/// necessary to exchange an authorization code against access tokens.
///
/// NOTE: It is important to transmit the authorization code in a secure manner
/// to the server. You should use "anti-request forgery state tokens" to guard
/// against "cross site request forgery" attacks.
/// An example on how to do this can be found here:
/// https://developers.google.com/+/web/signin/server-side-flow
Future<AccessCredentials> obtainAccessCredentialsViaCodeExchange(
Client baseClient, ClientId clientId, String code,
{String redirectUrl: 'postmessage'}) {
return obtainAccessCredentialsUsingCode(
clientId, code, redirectUrl, baseClient);
}
/// Will close the underlying `http.Client`.
class _ServiceAccountClient extends AutoRefreshDelegatingClient {
final JwtFlow flow;
AccessCredentials credentials;
Client authClient;
_ServiceAccountClient(Client client, this.credentials, this.flow)
: super(client) {
authClient = authenticatedClient(baseClient, credentials);
}
Future<StreamedResponse> send(BaseRequest request) async {
if (!credentials.accessToken.hasExpired) {
return authClient.send(request);
} else {
var newCredentials = await flow.run();
notifyAboutNewCredentials(newCredentials);
credentials = newCredentials;
authClient = authenticatedClient(baseClient, credentials);
return authClient.send(request);
}
}
}
/// Will close the underlying `http.Client`.
class _MetadataServerClient extends AutoRefreshDelegatingClient {
final MetadataServerAuthorizationFlow flow;
AccessCredentials credentials;
Client authClient;
_MetadataServerClient(Client client, this.credentials, this.flow)
: super(client) {
authClient = authenticatedClient(baseClient, credentials);
}
Future<StreamedResponse> send(BaseRequest request) async {
if (!credentials.accessToken.hasExpired) {
return authClient.send(request);
}
var newCredentials = await flow.run();
notifyAboutNewCredentials(newCredentials);
credentials = newCredentials;
authClient = authenticatedClient(baseClient, credentials);
return authClient.send(request);
}
}