blob: 41065cde4114336d636d3c74ea79926648176e8d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
package org.apache.beam.sdk.util;
import static com.google.common.base.Preconditions.checkArgument;
import org.apache.beam.sdk.options.GcpOptions;
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.extensions.java6.auth.oauth2.AbstractPromptReceiver;
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.auth.oauth2.GoogleOAuthConstants;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.store.FileDataStoreFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
* Provides support for loading credentials.
*/
public class Credentials {
private static final Logger LOG = LoggerFactory.getLogger(Credentials.class);
/**
* OAuth 2.0 scopes used by a local worker (not on GCE).
* The scope cloud-platform provides access to all Cloud Platform resources.
* cloud-platform isn't sufficient yet for talking to datastore so we request
* those resources separately.
*
* <p>Note that trusted scope relationships don't apply to OAuth tokens, so for
* services we access directly (GCS) as opposed to through the backend
* (BigQuery, GCE), we need to explicitly request that scope.
*/
private static final List<String> SCOPES = Arrays.asList(
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/devstorage.full_control",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/datastore");
private static class PromptReceiver extends AbstractPromptReceiver {
@Override
public String getRedirectUri() {
return GoogleOAuthConstants.OOB_REDIRECT_URI;
}
}
/**
* Initializes OAuth2 credentials.
*
* <p>This can use 3 different mechanisms for obtaining a credential:
* <ol>
* <li>
* It can fetch the
* <a href="https://developers.google.com/accounts/docs/application-default-credentials">
* application default credentials</a>.
* </li>
* <li>
* The user can specify a client secrets file and go through the OAuth2
* webflow. The credential will then be cached in the user's home
* directory for reuse. Provide the property "secrets_file" to use this
* mechanism.
* </li>
* <li>
* The user can specify a file containing a service account.
* Provide the properties "service_account_keyfile" and
* "service_account_name" to use this mechanism.
* </li>
* </ol>
* The default mechanism is to use the
* <a href="https://developers.google.com/accounts/docs/application-default-credentials">
* application default credentials</a>. The other options can be used by providing the
* corresponding properties.
*/
public static Credential getCredential(GcpOptions options)
throws IOException, GeneralSecurityException {
String keyFile = options.getServiceAccountKeyfile();
String accountName = options.getServiceAccountName();
if (keyFile != null && accountName != null) {
try {
return getCredentialFromFile(keyFile, accountName, SCOPES);
} catch (GeneralSecurityException e) {
throw new IOException("Unable to obtain credentials from file", e);
}
}
if (options.getSecretsFile() != null) {
return getCredentialFromClientSecrets(options, SCOPES);
}
try {
return GoogleCredential.getApplicationDefault().createScoped(SCOPES);
} catch (IOException e) {
throw new RuntimeException("Unable to get application default credentials. Please see "
+ "https://developers.google.com/accounts/docs/application-default-credentials "
+ "for details on how to specify credentials. This version of the SDK is "
+ "dependent on the gcloud core component version 2015.02.05 or newer to "
+ "be able to get credentials from the currently authorized user via gcloud auth.", e);
}
}
/**
* Loads OAuth2 credential from a local file.
*/
private static Credential getCredentialFromFile(
String keyFile, String accountId, Collection<String> scopes)
throws IOException, GeneralSecurityException {
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(Transport.getTransport())
.setJsonFactory(Transport.getJsonFactory())
.setServiceAccountId(accountId)
.setServiceAccountScopes(scopes)
.setServiceAccountPrivateKeyFromP12File(new File(keyFile))
.build();
LOG.info("Created credential from file {}", keyFile);
return credential;
}
/**
* Loads OAuth2 credential from client secrets, which may require an
* interactive authorization prompt.
*/
private static Credential getCredentialFromClientSecrets(
GcpOptions options, Collection<String> scopes)
throws IOException, GeneralSecurityException {
String clientSecretsFile = options.getSecretsFile();
checkArgument(clientSecretsFile != null);
HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
GoogleClientSecrets clientSecrets;
try {
clientSecrets = GoogleClientSecrets.load(jsonFactory,
new FileReader(clientSecretsFile));
} catch (IOException e) {
throw new RuntimeException(
"Could not read the client secrets from file: " + clientSecretsFile,
e);
}
FileDataStoreFactory dataStoreFactory =
new FileDataStoreFactory(new java.io.File(options.getCredentialDir()));
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
httpTransport, jsonFactory, clientSecrets, scopes)
.setDataStoreFactory(dataStoreFactory)
.setTokenServerUrl(new GenericUrl(options.getTokenServerUrl()))
.setAuthorizationServerEncodedUrl(options.getAuthorizationServerEncodedUrl())
.build();
// The credentialId identifies the credential if we're using a persistent
// credential store.
Credential credential =
new AuthorizationCodeInstalledApp(flow, new PromptReceiver())
.authorize(options.getCredentialId());
LOG.info("Got credential from client secret");
return credential;
}
}