These credentials use the IAM API to sign data. See {@link #sign(byte[])} for more details.
*/
-public class ComputeEngineCredentials extends GoogleCredentials implements ServiceAccountSigner {
+public class ComputeEngineCredentials extends GoogleCredentials
+ implements ServiceAccountSigner, IdTokenProvider {
private static final Logger LOGGER = Logger.getLogger(ComputeEngineCredentials.class.getName());
@@ -157,6 +160,45 @@ public AccessToken refreshAccessToken() throws IOException {
return new AccessToken(accessToken, new Date(expiresAtMilliseconds));
}
+ /**
+ * Returns a Google ID Token from the metadata server on ComputeEngine
+ *
+ * @param targetAudience the aud: field the IdToken should include
+ * @param options list of Credential specific options for the token. For example, an IDToken for a
+ * ComputeEngineCredential could have the full formatted claims returned if
+ * IdTokenProvider.Option.FORMAT_FULL) is provided as a list option. Valid option values are:
+ *
+ * IdTokenProvider.Option.FORMAT_FULL
+ * IdTokenProvider.Option.LICENSES_TRUE
+ * If no options are set, the defaults are "&format=standard&licenses=false"
+ * @throws IOException if the attempt to get an IdToken failed
+ * @return IdToken object which includes the raw id_token, JsonWebSignature
+ */
+ @Beta
+ @Override
+ public IdToken idTokenWithAudience(String targetAudience, List options)
+ throws IOException {
+ GenericUrl documentUrl = new GenericUrl(getIdentityDocumentUrl());
+ if (options != null) {
+ if (options.contains(IdTokenProvider.Option.FORMAT_FULL)) {
+ documentUrl.set("format", "full");
+ }
+ if (options.contains(IdTokenProvider.Option.LICENSES_TRUE)) {
+ // license will only get returned if format is also full
+ documentUrl.set("format", "full");
+ documentUrl.set("license", "TRUE");
+ }
+ }
+ documentUrl.set("audience", targetAudience);
+ HttpResponse response = getMetadataResponse(documentUrl.toString());
+ InputStream content = response.getContent();
+ if (content == null) {
+ throw new IOException("Empty content from metadata token server request.");
+ }
+ String rawToken = response.parseAsString();
+ return IdToken.create(rawToken);
+ }
+
private HttpResponse getMetadataResponse(String url) throws IOException {
GenericUrl genericUrl = new GenericUrl(url);
HttpRequest request =
@@ -243,6 +285,11 @@ public static String getServiceAccountsUrl() {
+ "/computeMetadata/v1/instance/service-accounts/?recursive=true";
}
+ public static String getIdentityDocumentUrl() {
+ return getMetadataServerUrl(DefaultCredentialsProvider.DEFAULT)
+ + "/computeMetadata/v1/instance/service-accounts/default/identity";
+ }
+
@Override
public int hashCode() {
return Objects.hash(transportFactoryClassName);
diff --git a/oauth2_http/java/com/google/auth/oauth2/IamUtils.java b/oauth2_http/java/com/google/auth/oauth2/IamUtils.java
index d4675c963..58ba91f97 100644
--- a/oauth2_http/java/com/google/auth/oauth2/IamUtils.java
+++ b/oauth2_http/java/com/google/auth/oauth2/IamUtils.java
@@ -37,6 +37,7 @@
import com.google.api.client.http.HttpStatusCodes;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.json.JsonHttpContent;
+import com.google.api.client.json.GenericJson;
import com.google.api.client.json.JsonObjectParser;
import com.google.api.client.util.GenericData;
import com.google.auth.Credentials;
@@ -54,6 +55,8 @@
class IamUtils {
private static final String SIGN_BLOB_URL_FORMAT =
"https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/%s:signBlob";
+ private static final String ID_TOKEN_URL_FORMAT =
+ "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/%s:generateIdToken";
private static final String PARSE_ERROR_MESSAGE = "Error parsing error message response. ";
private static final String PARSE_ERROR_SIGNATURE = "Error parsing signature response. ";
@@ -138,4 +141,74 @@ private static String getSignature(
GenericData responseData = response.parseAs(GenericData.class);
return OAuth2Utils.validateString(responseData, "signedBlob", PARSE_ERROR_SIGNATURE);
}
+
+ /**
+ * Returns an IdToken issued to the serviceAccount with a specified targetAudience
+ *
+ * @param serviceAccountEmail the email address for the service account to get an ID Token for
+ * @param credentials credentials required for making the IAM call
+ * @param transport transport used for building the HTTP request
+ * @param targetAudience the audience the issued ID token should include
+ * @param additionalFields additional fields to send in the IAM call
+ * @return IdToken issed to the serviceAccount
+ * @throws IOException if the IdToken cannot be issued.
+ * @see
+ * https://cloud.google.com/iam/credentials/reference/rest/v1/projects.serviceAccounts/generateIdToken
+ */
+ static IdToken getIdToken(
+ String serviceAccountEmail,
+ Credentials credentials,
+ HttpTransport transport,
+ String targetAudience,
+ boolean includeEmail,
+ Map additionalFields)
+ throws IOException {
+
+ String idTokenUrl = String.format(ID_TOKEN_URL_FORMAT, serviceAccountEmail);
+ GenericUrl genericUrl = new GenericUrl(idTokenUrl);
+
+ GenericData idTokenRequest = new GenericData();
+ idTokenRequest.set("audience", targetAudience);
+ idTokenRequest.set("includeEmail", includeEmail);
+ for (Map.Entry entry : additionalFields.entrySet()) {
+ idTokenRequest.set(entry.getKey(), entry.getValue());
+ }
+ JsonHttpContent idTokenContent = new JsonHttpContent(OAuth2Utils.JSON_FACTORY, idTokenRequest);
+
+ HttpCredentialsAdapter adapter = new HttpCredentialsAdapter(credentials);
+ HttpRequest request =
+ transport.createRequestFactory(adapter).buildPostRequest(genericUrl, idTokenContent);
+
+ JsonObjectParser parser = new JsonObjectParser(OAuth2Utils.JSON_FACTORY);
+ request.setParser(parser);
+ request.setThrowExceptionOnExecuteError(false);
+
+ HttpResponse response = request.execute();
+ int statusCode = response.getStatusCode();
+ if (statusCode >= 400 && statusCode < HttpStatusCodes.STATUS_CODE_SERVER_ERROR) {
+ GenericData responseError = response.parseAs(GenericData.class);
+ Map error =
+ OAuth2Utils.validateMap(responseError, "error", PARSE_ERROR_MESSAGE);
+ String errorMessage = OAuth2Utils.validateString(error, "message", PARSE_ERROR_MESSAGE);
+ throw new IOException(
+ String.format("Error code %s trying to getIDToken: %s", statusCode, errorMessage));
+ }
+ if (statusCode != HttpStatusCodes.STATUS_CODE_OK) {
+ throw new IOException(
+ String.format(
+ "Unexpected Error code %s trying to getIDToken: %s",
+ statusCode, response.parseAsString()));
+ }
+ InputStream content = response.getContent();
+ if (content == null) {
+ // Throw explicitly here on empty content to avoid NullPointerException from
+ // parseAs call.
+ // Mock transports will have success code with empty content by default.
+ throw new IOException("Empty content from generateIDToken server request.");
+ }
+
+ GenericJson responseData = response.parseAs(GenericJson.class);
+ String rawToken = OAuth2Utils.validateString(responseData, "token", PARSE_ERROR_MESSAGE);
+ return IdToken.create(rawToken);
+ }
}
diff --git a/oauth2_http/java/com/google/auth/oauth2/IdToken.java b/oauth2_http/java/com/google/auth/oauth2/IdToken.java
new file mode 100644
index 000000000..4d7e88eea
--- /dev/null
+++ b/oauth2_http/java/com/google/auth/oauth2/IdToken.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2019, Google LLC
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * * Neither the name of Google LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.google.auth.oauth2;
+
+import com.google.api.client.json.JsonFactory;
+import com.google.api.client.json.webtoken.JsonWebSignature;
+import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.Objects;
+
+/** Represents a temporary IdToken and its JsonWebSignature object */
+@Beta
+public class IdToken extends AccessToken implements Serializable {
+
+ private static final long serialVersionUID = -8514239465808977353L;
+
+ private transient JsonWebSignature jsonWebSignature;
+
+ /**
+ * @param tokenValue String representation of the ID token.
+ * @param jsonWebSignature JsonWebSignature as object
+ */
+ private IdToken(String tokenValue, JsonWebSignature jsonWebSignature) {
+ super(tokenValue, new Date(jsonWebSignature.getPayload().getExpirationTimeSeconds() * 1000));
+ this.jsonWebSignature = jsonWebSignature;
+ }
+
+ /**
+ * Creates an IdToken given the encoded Json Web Signature.
+ *
+ * @param tokenValue String representation of the ID token.
+ * @return returns com.google.auth.oauth2.IdToken
+ */
+ public static IdToken create(String tokenValue) throws IOException {
+ return create(tokenValue, OAuth2Utils.JSON_FACTORY);
+ }
+
+ /**
+ * Creates an IdToken given the encoded Json Web Signature and JSON Factory
+ *
+ * @param jsonFactory JsonFactory to use for parsing the provided token.
+ * @param tokenValue String representation of the ID token.
+ * @return returns com.google.auth.oauth2.IdToken
+ */
+ public static IdToken create(String tokenValue, JsonFactory jsonFactory) throws IOException {
+ return new IdToken(tokenValue, JsonWebSignature.parse(jsonFactory, tokenValue));
+ }
+
+ /**
+ * The JsonWebSignature as object
+ *
+ * @return returns com.google.api.client.json.webtoken.JsonWebSignature
+ */
+ public JsonWebSignature getJsonWebSignature() {
+ return jsonWebSignature;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ super.getTokenValue(), jsonWebSignature.getHeader(), jsonWebSignature.getPayload());
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("tokenValue", super.getTokenValue())
+ .add("JsonWebSignature", jsonWebSignature)
+ .toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof IdToken)) {
+ return false;
+ }
+ IdToken other = (IdToken) obj;
+ return Objects.equals(super.getTokenValue(), other.getTokenValue())
+ && Objects.equals(this.jsonWebSignature.getHeader(), other.jsonWebSignature.getHeader())
+ && Objects.equals(this.jsonWebSignature.getPayload(), other.jsonWebSignature.getPayload());
+ }
+
+ private void writeObject(ObjectOutputStream oos) throws IOException {
+ oos.writeObject(this.getTokenValue());
+ }
+
+ private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
+ String signature = (String) ois.readObject();
+ this.jsonWebSignature = JsonWebSignature.parse(OAuth2Utils.JSON_FACTORY, signature);
+ }
+}
diff --git a/oauth2_http/java/com/google/auth/oauth2/IdTokenCredentials.java b/oauth2_http/java/com/google/auth/oauth2/IdTokenCredentials.java
new file mode 100644
index 000000000..6b827c5f9
--- /dev/null
+++ b/oauth2_http/java/com/google/auth/oauth2/IdTokenCredentials.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2019, Google LLC
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * * Neither the name of Google LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.google.auth.oauth2;
+
+import com.google.api.client.util.Preconditions;
+import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+import java.io.IOException;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * IdTokenCredentials provides a Google Issued OpenIdConnect token.
+ * Use an ID token to access services that require presenting an ID token for authentication such as
+ * Cloud Functions or Cloud Run.
+ * The following Credential subclasses support IDTokens: ServiceAccountCredentials,
+ * ComputeEngineCredentials, ImpersonatedCredentials.
+ *
+ *
+ */
+@Beta
+public class IdTokenCredentials extends OAuth2Credentials {
+
+ private static final long serialVersionUID = -2133257318957588431L;
+
+ private IdTokenProvider idTokenProvider;
+ private String targetAudience;
+ private List options;
+
+ private IdTokenCredentials(Builder builder) {
+ this.idTokenProvider = Preconditions.checkNotNull(builder.getIdTokenProvider());
+ this.targetAudience = Preconditions.checkNotNull(builder.getTargetAudience());
+ this.options = builder.getOptions();
+ }
+
+ @Override
+ public AccessToken refreshAccessToken() throws IOException {
+ return this.idTokenProvider.idTokenWithAudience(targetAudience, options);
+ }
+
+ public IdToken getIdToken() {
+ return (IdToken) getAccessToken();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(options, targetAudience);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this).toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof IdTokenCredentials)) {
+ return false;
+ }
+ IdTokenCredentials other = (IdTokenCredentials) obj;
+ return Objects.equals(this.idTokenProvider, other.idTokenProvider)
+ && Objects.equals(this.targetAudience, other.targetAudience);
+ }
+
+ public Builder toBuilder() {
+ return new Builder()
+ .setIdTokenProvider(this.idTokenProvider)
+ .setTargetAudience(this.targetAudience)
+ .setOptions(this.options);
+ }
+
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ public static class Builder extends OAuth2Credentials.Builder {
+
+ private IdTokenProvider idTokenProvider;
+ private String targetAudience;
+ private List options;
+
+ protected Builder() {}
+
+ public Builder setIdTokenProvider(IdTokenProvider idTokenProvider) {
+ this.idTokenProvider = idTokenProvider;
+ return this;
+ }
+
+ public IdTokenProvider getIdTokenProvider() {
+ return this.idTokenProvider;
+ }
+
+ public Builder setTargetAudience(String targetAudience) {
+ this.targetAudience = targetAudience;
+ return this;
+ }
+
+ public String getTargetAudience() {
+ return this.targetAudience;
+ }
+
+ public Builder setOptions(List options) {
+ this.options = options;
+ return this;
+ }
+
+ public List getOptions() {
+ return this.options;
+ }
+
+ public IdTokenCredentials build() {
+ return new IdTokenCredentials(this);
+ }
+ }
+}
diff --git a/oauth2_http/java/com/google/auth/oauth2/IdTokenProvider.java b/oauth2_http/java/com/google/auth/oauth2/IdTokenProvider.java
new file mode 100644
index 000000000..fe3b0f667
--- /dev/null
+++ b/oauth2_http/java/com/google/auth/oauth2/IdTokenProvider.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2019, Google LLC
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * * Neither the name of Google LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.google.auth.oauth2;
+
+import com.google.common.annotations.Beta;
+import java.io.IOException;
+import java.util.List;
+
+/** Interface for an Google OIDC token provider. This type represents a google issued OIDC token. */
+@Beta
+public interface IdTokenProvider {
+
+ /**
+ * Enum of various credential-specific options to apply to the token.
+ *
+ *
ComputeEngineCredentials
+ *
+ *
+ *
FORMAT_FULL
+ *
LICENSES_TRUE
+ *
+ *
+ *
+ * ImpersonatedCredential
+ *
+ *
+ *
INCLUDE_EMAIL
+ *
+ */
+ public enum Option {
+ FORMAT_FULL("formatFull"),
+ LICENSES_TRUE("licensesTrue"),
+ INCLUDE_EMAIL("includeEmail");
+
+ private String option;
+
+ private Option(String option) {
+ this.option = option;
+ }
+
+ public String getOption() {
+ return option;
+ }
+ }
+
+ /**
+ * Returns a Google OpenID Token with the provided audience field.
+ *
+ * @param targetAudience List of audiences the issued ID Token should be valid for. targetAudience
+ * accepts a single string value (multiple audiences are not supported)
+ * @param options List of Credential specific options for for the token. For example, an IDToken
+ * for a ComputeEngineCredential can return platform specific claims if
+ * "ComputeEngineCredentials.ID_TOKEN_FORMAT_FULL" is provided as a list option.
+ * @return IdToken object which includes the raw id_token, expiration and audience.
+ */
+ IdToken idTokenWithAudience(String targetAudience, List