/* * Copyright © 2017 Google Inc. * * 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. */ package com.google.enterprise.cloudsearch.sdk; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static java.nio.file.Files.newInputStream; import static java.util.Locale.ENGLISH; import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; import com.google.api.client.http.HttpRequestInitializer; 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.common.annotations.VisibleForTesting; import com.google.enterprise.cloudsearch.sdk.config.ConfigValue; import com.google.enterprise.cloudsearch.sdk.config.Configuration; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.GeneralSecurityException; import java.util.Collection; /** * Factory to create a {@link GoogleCredential} object for accessing the Cloud Search API. * * <p>Required configuration parameters: * * <ul> * <li>{@code api.serviceAccountPrivateKeyFile} - Specifies the service account private key file * path. * </ul> * * <p>Optional configuration parameters: * * <ul> * <li>{@code api.serviceAccountId} - Specifies the service account ID. If {@code * api.serviceAccountPrivateKeyFile} is not a json key, then {@code api.serviceAccountId} is * required. * </ul> */ public class LocalFileCredentialFactory implements CredentialFactory { /** * Configuration key for service account ID. */ public static final String SERVICE_ACCOUNT_ID_CONFIG = "api.serviceAccountId"; /** * Configuration key for service account key file path. */ public static final String SERVICE_ACCOUNT_KEY_FILE_CONFIG = "api.serviceAccountPrivateKeyFile"; private static CredentialHelper credentialHelper = new CredentialHelper(); private final String serviceAccountId; private final Path serviceAccountKeyFilePath; private final boolean isJsonKey; private final GoogleProxy proxy; /** * Method to build an instance of {@link LocalFileCredentialFactory} from configuration. * * @return an instance of {@link LocalFileCredentialFactory} */ public static LocalFileCredentialFactory fromConfiguration() { checkState(Configuration.isInitialized(), "configuration not initialized"); ConfigValue<String> serviceAccountKeyFilePath = Configuration.getString(LocalFileCredentialFactory.SERVICE_ACCOUNT_KEY_FILE_CONFIG, null); ConfigValue<String> serviceAccountId = Configuration.getString(LocalFileCredentialFactory.SERVICE_ACCOUNT_ID_CONFIG, ""); return new Builder() .setServiceAccountKeyFilePath(serviceAccountKeyFilePath.get()) .setServiceAccountId(serviceAccountId.get()) .setProxy(GoogleProxy.fromConfiguration()) .build(); } /** * Gets {@link GoogleCredential} instance constructed for service account. */ @Override public GoogleCredential getCredential(Collection<String> scopes) throws GeneralSecurityException, IOException { JsonFactory jsonFactory = JacksonFactory.getDefaultInstance(); HttpTransport transport = proxy.getHttpTransport(); if (!isJsonKey) { return credentialHelper.getP12Credential( serviceAccountId, serviceAccountKeyFilePath, transport, jsonFactory, proxy.getHttpRequestInitializer(), scopes); } return credentialHelper.getJsonCredential( serviceAccountKeyFilePath, transport, jsonFactory, proxy.getHttpRequestInitializer(), scopes); } private LocalFileCredentialFactory(Builder builder) { this.serviceAccountId = builder.serviceAccountId; this.serviceAccountKeyFilePath = builder.serviceAccountKeyFilePath; this.isJsonKey = builder.isJsonKey; this.proxy = builder.proxy; } @VisibleForTesting static class CredentialHelper { GoogleCredential getJsonCredential( Path keyPath, HttpTransport transport, JsonFactory jsonFactory, HttpRequestInitializer httpRequestInitializer, Collection<String> scopes) throws IOException { try (InputStream is = newInputStream(keyPath)) { GoogleCredential credential = GoogleCredential.fromStream(is, transport, jsonFactory).createScoped(scopes); return new GoogleCredential.Builder() .setServiceAccountId(credential.getServiceAccountId()) .setServiceAccountScopes(scopes) .setServiceAccountPrivateKey(credential.getServiceAccountPrivateKey()) .setTransport(transport) .setJsonFactory(jsonFactory) .setRequestInitializer(httpRequestInitializer) .build(); } } GoogleCredential getP12Credential( String serviceAccountId, Path keyPath, HttpTransport transport, JsonFactory jsonFactory, HttpRequestInitializer requestInitializer, Collection<String> scopes) throws GeneralSecurityException, IOException { return new GoogleCredential.Builder() .setServiceAccountId(serviceAccountId) .setServiceAccountScopes(scopes) .setServiceAccountPrivateKeyFromP12File(keyPath.toFile()) .setTransport(transport) .setJsonFactory(jsonFactory) .setRequestInitializer(requestInitializer) .build(); } } @VisibleForTesting static void setCredentialHelper(CredentialHelper helper) { credentialHelper = helper; } /** * Builder for creating instance of {@link LocalFileCredentialFactory}. */ public static class Builder { private String serviceAccountId; private String serviceAccountKeyFile; private Path serviceAccountKeyFilePath; private boolean isJsonKey = true; private GoogleProxy proxy = new GoogleProxy.Builder().build(); /** * Sets service account ID for creating {@link GoogleCredential}. * * @param serviceAccountId to be used for creating {@link GoogleCredential}. */ public Builder setServiceAccountId(String serviceAccountId) { this.serviceAccountId = serviceAccountId; return this; } /** * Sets service account key file path for creating {@link GoogleCredential}. * * @param serviceAccountKeyFile path to be used for creating {@link GoogleCredential}. */ public Builder setServiceAccountKeyFilePath(String serviceAccountKeyFile) { this.serviceAccountKeyFile = serviceAccountKeyFile; this.serviceAccountKeyFilePath = Paths.get(this.serviceAccountKeyFile); return this; } /** * Sets {@link GoogleProxy} for creating {@link GoogleCredential}. * * @param proxy to be used for creating {@link GoogleCredential}. */ public Builder setProxy(GoogleProxy proxy) { this.proxy = proxy; return this; } /** * Builds an instance of {@link LocalFileCredentialFactory}. */ public LocalFileCredentialFactory build() { checkNotNull(serviceAccountKeyFile, SERVICE_ACCOUNT_KEY_FILE_CONFIG + " is not specified."); checkArgument(!serviceAccountKeyFile.isEmpty()); checkArgument( Files.exists(serviceAccountKeyFilePath), serviceAccountKeyFile + " does not exist."); checkArgument( !Files.isDirectory(serviceAccountKeyFilePath), serviceAccountKeyFile + "is a directory. A file is expected"); if (!serviceAccountKeyFilePath.toString().toLowerCase(ENGLISH).endsWith(".json")) { checkNotNull(serviceAccountId, SERVICE_ACCOUNT_ID_CONFIG + " is not specified"); checkArgument(!serviceAccountId.isEmpty(), "service account Id is empty."); isJsonKey = false; } return new LocalFileCredentialFactory(this); } } }