diff --git a/gcloud-java-core/src/main/java/com/google/gcloud/AuthCredentials.java b/gcloud-java-core/src/main/java/com/google/gcloud/AuthCredentials.java index 3303e4f8a652..dab6b928374a 100644 --- a/gcloud-java-core/src/main/java/com/google/gcloud/AuthCredentials.java +++ b/gcloud-java-core/src/main/java/com/google/gcloud/AuthCredentials.java @@ -18,21 +18,17 @@ import static com.google.common.base.Preconditions.checkNotNull; -import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; -import com.google.api.client.googleapis.extensions.appengine.auth.oauth2.AppIdentityCredential; -import com.google.api.client.http.HttpRequestInitializer; -import com.google.api.client.http.HttpTransport; -import com.google.api.client.json.jackson.JacksonFactory; -import com.google.auth.http.HttpCredentialsAdapter; +import com.google.auth.oauth2.AccessToken; import com.google.auth.oauth2.GoogleCredentials; import com.google.auth.oauth2.ServiceAccountCredentials; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; +import java.lang.reflect.Method; import java.security.PrivateKey; +import java.util.Collection; import java.util.Objects; -import java.util.Set; /** * Credentials for accessing Google Cloud services. @@ -42,8 +38,67 @@ public abstract class AuthCredentials implements Restorable { private static class AppEngineAuthCredentials extends AuthCredentials { private static final AuthCredentials INSTANCE = new AppEngineAuthCredentials(); - private static final AppEngineAuthCredentialsState STATE = - new AppEngineAuthCredentialsState(); + private static final AppEngineAuthCredentialsState STATE = new AppEngineAuthCredentialsState(); + + private static class AppEngineCredentials extends GoogleCredentials { + + private final Object appIdentityService; + private final Method getAccessToken; + private final Method getAccessTokenResult; + private final Collection scopes; + + AppEngineCredentials() { + try { + Class factoryClass = + Class.forName("com.google.appengine.api.appidentity.AppIdentityServiceFactory"); + Method method = factoryClass.getMethod("getAppIdentityService"); + this.appIdentityService = method.invoke(null); + Class serviceClass = + Class.forName("com.google.appengine.api.appidentity.AppIdentityService"); + Class tokenResultClass = Class.forName( + "com.google.appengine.api.appidentity.AppIdentityService$GetAccessTokenResult"); + this.getAccessTokenResult = serviceClass.getMethod("getAccessToken", Iterable.class); + this.getAccessToken = tokenResultClass.getMethod("getAccessToken"); + this.scopes = null; + } catch (Exception e) { + throw new RuntimeException("Could not create AppEngineCredentials.", e); + } + } + + AppEngineCredentials(Collection scopes, AppEngineCredentials unscoped) { + this.appIdentityService = unscoped.appIdentityService; + this.getAccessToken = unscoped.getAccessToken; + this.getAccessTokenResult = unscoped.getAccessTokenResult; + this.scopes = scopes; + } + + /** + * Refresh the access token by getting it from the App Identity service + */ + @Override + public AccessToken refreshAccessToken() throws IOException { + if (createScopedRequired()) { + throw new IOException("AppEngineCredentials requires createScoped call before use."); + } + try { + Object accessTokenResult = getAccessTokenResult.invoke(appIdentityService, scopes); + String accessToken = (String) getAccessToken.invoke(accessTokenResult); + return new AccessToken(accessToken, null); + } catch (Exception e) { + throw new IOException("Could not get the access token.", e); + } + } + + @Override + public boolean createScopedRequired() { + return scopes == null || scopes.isEmpty(); + } + + @Override + public GoogleCredentials createScoped(Collection scopes) { + return new AppEngineCredentials(scopes, this); + } + } private static class AppEngineAuthCredentialsState implements RestorableState, Serializable { @@ -67,9 +122,8 @@ public boolean equals(Object obj) { } @Override - protected HttpRequestInitializer httpRequestInitializer(HttpTransport transport, - Set scopes) { - return new AppIdentityCredential(scopes); + public GoogleCredentials credentials() { + return new AppEngineCredentials(); } @Override @@ -83,8 +137,6 @@ public static class ServiceAccountAuthCredentials extends AuthCredentials { private final String account; private final PrivateKey privateKey; - private static final AuthCredentials NO_CREDENTIALS = new ServiceAccountAuthCredentials(); - private static class ServiceAccountAuthCredentialsState implements RestorableState, Serializable { @@ -100,9 +152,6 @@ private ServiceAccountAuthCredentialsState(String account, PrivateKey privateKey @Override public AuthCredentials restore() { - if (account == null && privateKey == null) { - return NO_CREDENTIALS; - } return new ServiceAccountAuthCredentials(account, privateKey); } @@ -127,23 +176,9 @@ public boolean equals(Object obj) { this.privateKey = checkNotNull(privateKey); } - ServiceAccountAuthCredentials() { - account = null; - privateKey = null; - } - @Override - protected HttpRequestInitializer httpRequestInitializer( - HttpTransport transport, Set scopes) { - GoogleCredential.Builder builder = new GoogleCredential.Builder() - .setTransport(transport) - .setJsonFactory(new JacksonFactory()); - if (privateKey != null) { - builder.setServiceAccountPrivateKey(privateKey); - builder.setServiceAccountId(account); - builder.setServiceAccountScopes(scopes); - } - return builder.build(); + public ServiceAccountCredentials credentials() { + return new ServiceAccountCredentials(null, account, privateKey, null, null); } public String account() { @@ -198,18 +233,8 @@ public boolean equals(Object obj) { } @Override - protected HttpRequestInitializer httpRequestInitializer(HttpTransport transport, - Set scopes) { - return new HttpCredentialsAdapter(googleCredentials.createScoped(scopes)); - } - - public ServiceAccountAuthCredentials toServiceAccountCredentials() { - if (googleCredentials instanceof ServiceAccountCredentials) { - ServiceAccountCredentials credentials = (ServiceAccountCredentials) googleCredentials; - return new ServiceAccountAuthCredentials(credentials.getClientEmail(), - credentials.getPrivateKey()); - } - return null; + public GoogleCredentials credentials() { + return googleCredentials; } @Override @@ -218,8 +243,7 @@ public RestorableState capture() { } } - protected abstract HttpRequestInitializer httpRequestInitializer(HttpTransport transport, - Set scopes); + public abstract GoogleCredentials credentials(); public static AuthCredentials createForAppEngine() { return AppEngineAuthCredentials.INSTANCE; @@ -271,12 +295,15 @@ public static ServiceAccountAuthCredentials createFor(String account, PrivateKey */ public static ServiceAccountAuthCredentials createForJson(InputStream jsonCredentialStream) throws IOException { - GoogleCredential tempCredentials = GoogleCredential.fromStream(jsonCredentialStream); - return new ServiceAccountAuthCredentials(tempCredentials.getServiceAccountId(), - tempCredentials.getServiceAccountPrivateKey()); - } - - public static AuthCredentials noCredentials() { - return ServiceAccountAuthCredentials.NO_CREDENTIALS; + GoogleCredentials tempCredentials = GoogleCredentials.fromStream(jsonCredentialStream); + if (tempCredentials instanceof ServiceAccountCredentials) { + ServiceAccountCredentials tempServiceAccountCredentials = + (ServiceAccountCredentials) tempCredentials; + return new ServiceAccountAuthCredentials( + tempServiceAccountCredentials.getClientEmail(), + tempServiceAccountCredentials.getPrivateKey()); + } + throw new IOException( + "The given JSON Credentials Stream is not for a service account credential."); } } diff --git a/gcloud-java-core/src/main/java/com/google/gcloud/ServiceOptions.java b/gcloud-java-core/src/main/java/com/google/gcloud/ServiceOptions.java index 25fda29c363d..715b0b92ca98 100644 --- a/gcloud-java-core/src/main/java/com/google/gcloud/ServiceOptions.java +++ b/gcloud-java-core/src/main/java/com/google/gcloud/ServiceOptions.java @@ -25,6 +25,7 @@ import com.google.api.client.http.HttpRequestInitializer; import com.google.api.client.http.HttpTransport; import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.auth.http.HttpCredentialsAdapter; import com.google.common.collect.Iterables; import com.google.gcloud.spi.ServiceRpcFactory; @@ -311,8 +312,9 @@ protected ServiceOptions(Class> ser httpTransportFactory = firstNonNull(builder.httpTransportFactory, getFromServiceLoader(HttpTransportFactory.class, DefaultHttpTransportFactory.INSTANCE)); httpTransportFactoryClassName = httpTransportFactory.getClass().getName(); - authCredentials = firstNonNull(builder.authCredentials, defaultAuthCredentials()); - authCredentialsState = authCredentials.capture(); + authCredentials = + builder.authCredentials != null ? builder.authCredentials : defaultAuthCredentials(); + authCredentialsState = authCredentials != null ? authCredentials.capture() : null; retryParams = builder.retryParams; serviceFactory = firstNonNull(builder.serviceFactory, getFromServiceLoader(serviceFactoryClass, defaultServiceFactory())); @@ -348,7 +350,7 @@ private static AuthCredentials defaultAuthCredentials() { try { return AuthCredentials.createApplicationDefaults(); } catch (Exception ex) { - return AuthCredentials.noCredentials(); + return null; } } @@ -508,13 +510,15 @@ public RetryParams retryParams() { * options. */ public HttpRequestInitializer httpRequestInitializer() { - HttpTransport httpTransport = httpTransportFactory.create(); - final HttpRequestInitializer baseRequestInitializer = - authCredentials().httpRequestInitializer(httpTransport, scopes()); + final HttpRequestInitializer delegate = authCredentials() != null + ? new HttpCredentialsAdapter(authCredentials().credentials().createScoped(scopes())) + : null; return new HttpRequestInitializer() { @Override public void initialize(HttpRequest httpRequest) throws IOException { - baseRequestInitializer.initialize(httpRequest); + if (delegate != null) { + delegate.initialize(httpRequest); + } if (connectTimeout >= 0) { httpRequest.setConnectTimeout(connectTimeout); } @@ -580,7 +584,7 @@ private void readObject(ObjectInputStream input) throws IOException, ClassNotFou httpTransportFactory = newInstance(httpTransportFactoryClassName); serviceFactory = newInstance(serviceFactoryClassName); serviceRpcFactory = newInstance(serviceRpcFactoryClassName); - authCredentials = authCredentialsState.restore(); + authCredentials = authCredentialsState != null ? authCredentialsState.restore() : null; } private static T newInstance(String className) throws IOException, ClassNotFoundException { diff --git a/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/SerializationTest.java b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/SerializationTest.java index 32e14fb47ea0..3976be2cc383 100644 --- a/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/SerializationTest.java +++ b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/SerializationTest.java @@ -144,7 +144,7 @@ public void testServiceOptions() throws Exception { options = options.toBuilder() .namespace("ns1") .retryParams(RetryParams.defaultInstance()) - .authCredentials(AuthCredentials.noCredentials()) + .authCredentials(null) .force(true) .build(); serializedCopy = serializeAndDeserialize(options); diff --git a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/StorageImpl.java b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/StorageImpl.java index 91a408657847..e31c4073548e 100644 --- a/gcloud-java-storage/src/main/java/com/google/gcloud/storage/StorageImpl.java +++ b/gcloud-java-storage/src/main/java/com/google/gcloud/storage/StorageImpl.java @@ -31,6 +31,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import com.google.api.services.storage.model.StorageObject; +import com.google.auth.oauth2.ServiceAccountCredentials; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.collect.ImmutableList; @@ -38,19 +39,16 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import com.google.common.collect.Sets; import com.google.common.hash.Hashing; import com.google.common.io.BaseEncoding; import com.google.common.primitives.Ints; -import com.google.gcloud.AuthCredentials; -import com.google.gcloud.AuthCredentials.ApplicationDefaultAuthCredentials; import com.google.gcloud.AuthCredentials.ServiceAccountAuthCredentials; -import com.google.gcloud.PageImpl; import com.google.gcloud.BaseService; import com.google.gcloud.ExceptionHandler; import com.google.gcloud.ExceptionHandler.Interceptor; -import com.google.gcloud.RetryHelper.RetryHelperException; import com.google.gcloud.Page; +import com.google.gcloud.PageImpl; +import com.google.gcloud.RetryHelper.RetryHelperException; import com.google.gcloud.spi.StorageRpc; import com.google.gcloud.spi.StorageRpc.RewriteResponse; import com.google.gcloud.spi.StorageRpc.Tuple; @@ -71,7 +69,6 @@ import java.util.EnumMap; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; @@ -563,18 +560,15 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio for (SignUrlOption option : options) { optionMap.put(option.option(), option.value()); } - ServiceAccountAuthCredentials cred = + ServiceAccountAuthCredentials authCred = (ServiceAccountAuthCredentials) optionMap.get(SignUrlOption.Option.SERVICE_ACCOUNT_CRED); - if (cred == null) { - AuthCredentials serviceCred = this.options().authCredentials(); - if (serviceCred instanceof ServiceAccountAuthCredentials) { - cred = (ServiceAccountAuthCredentials) serviceCred; - } else { - if (serviceCred instanceof ApplicationDefaultAuthCredentials) { - cred = ((ApplicationDefaultAuthCredentials) serviceCred).toServiceAccountCredentials(); - } - } - checkArgument(cred != null, "Signing key was not provided and could not be derived"); + ServiceAccountCredentials cred = authCred != null ? authCred.credentials() : null; + if (authCred == null) { + checkArgument( + this.options().authCredentials() != null + && this.options().authCredentials().credentials() instanceof ServiceAccountCredentials, + "Signing key was not provided and could not be derived"); + cred = (ServiceAccountCredentials) this.options().authCredentials().credentials(); } // construct signature - see https://cloud.google.com/storage/docs/access-control#Signed-URLs StringBuilder stBuilder = new StringBuilder(); @@ -610,12 +604,12 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio stBuilder.append(path); try { Signature signer = Signature.getInstance("SHA256withRSA"); - signer.initSign(cred.privateKey()); + signer.initSign(cred.getPrivateKey()); signer.update(stBuilder.toString().getBytes(UTF_8)); String signature = URLEncoder.encode(BaseEncoding.base64().encode(signer.sign()), UTF_8.name()); stBuilder = new StringBuilder("https://storage.googleapis.com").append(path); - stBuilder.append("?GoogleAccessId=").append(cred.account()); + stBuilder.append("?GoogleAccessId=").append(cred.getClientEmail()); stBuilder.append("&Expires=").append(expiration); stBuilder.append("&Signature=").append(signature); return new URL(stBuilder.toString()); diff --git a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/SerializationTest.java b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/SerializationTest.java index 2d80191aeb2d..256f5fc23f9a 100644 --- a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/SerializationTest.java +++ b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/SerializationTest.java @@ -83,7 +83,7 @@ public void testServiceOptions() throws Exception { options = options.toBuilder() .projectId("p2") .retryParams(RetryParams.defaultInstance()) - .authCredentials(AuthCredentials.noCredentials()) + .authCredentials(null) .pathDelimiter(":") .build(); serializedCopy = serializeAndDeserialize(options); @@ -111,7 +111,6 @@ public void testReadChannelState() throws IOException, ClassNotFoundException { StorageOptions options = StorageOptions.builder() .projectId("p2") .retryParams(RetryParams.defaultInstance()) - .authCredentials(AuthCredentials.noCredentials()) .build(); BlobReadChannel reader = new BlobReadChannelImpl(options, BlobId.of("b", "n"), EMPTY_RPC_OPTIONS); @@ -127,7 +126,6 @@ public void testWriteChannelState() throws IOException, ClassNotFoundException { StorageOptions options = StorageOptions.builder() .projectId("p2") .retryParams(RetryParams.defaultInstance()) - .authCredentials(AuthCredentials.noCredentials()) .build(); BlobWriteChannelImpl writer = new BlobWriteChannelImpl( options, BlobInfo.builder(BlobId.of("b", "n")).build(), "upload-id"); diff --git a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/StorageImplTest.java b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/StorageImplTest.java index 32a466a9d551..42671d37c60c 100644 --- a/gcloud-java-storage/src/test/java/com/google/gcloud/storage/StorageImplTest.java +++ b/gcloud-java-storage/src/test/java/com/google/gcloud/storage/StorageImplTest.java @@ -31,11 +31,10 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Maps; import com.google.common.io.BaseEncoding; -import com.google.gcloud.AuthCredentials; import com.google.gcloud.AuthCredentials.ServiceAccountAuthCredentials; +import com.google.gcloud.Page; import com.google.gcloud.RetryParams; import com.google.gcloud.ServiceOptions; -import com.google.gcloud.Page; import com.google.gcloud.spi.StorageRpc; import com.google.gcloud.spi.StorageRpc.Tuple; import com.google.gcloud.spi.StorageRpcFactory; @@ -260,7 +259,6 @@ public void setUp() throws IOException, InterruptedException { EasyMock.replay(rpcFactoryMock); options = StorageOptions.builder() .projectId("projectId") - .authCredentials(AuthCredentials.noCredentials()) .clock(TIME_SOURCE) .serviceRpcFactory(rpcFactoryMock) .build();