com.google.auth.oauth2.ImpersonatedCredentials Maven / Gradle / Ivy
/*
* Copyright 2018, Google Inc. All rights reserved.
*
* 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 Inc. 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 static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpContent;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.json.JsonHttpContent;
import com.google.api.client.json.JsonObjectParser;
import com.google.api.client.util.GenericData;
import com.google.auth.ServiceAccountSigner;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.auth.http.HttpTransportFactory;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableMap;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* ImpersonatedCredentials allowing credentials issued to a user or service account to impersonate
* another. The source project using ImpersonatedCredentials must enable the "IAMCredentials" API.
* Also, the target service account must grant the originating principal the "Service Account Token
* Creator" IAM role.
*
* Usage:
*
*
* String credPath = "/path/to/svc_account.json";
* ServiceAccountCredentials sourceCredentials = ServiceAccountCredentials
* .fromStream(new FileInputStream(credPath));
* sourceCredentials = (ServiceAccountCredentials) sourceCredentials
* .createScoped(Arrays.asList("https://www.googleapis.com/auth/iam"));
*
* ImpersonatedCredentials targetCredentials = ImpersonatedCredentials.create(sourceCredentials,
* "[email protected]", null,
* Arrays.asList("https://www.googleapis.com/auth/devstorage.read_only"), 300);
*
* Storage storage_service = StorageOptions.newBuilder().setProjectId("project-id")
* .setCredentials(targetCredentials).build().getService();
*
* for (Bucket b : storage_service.list().iterateAll())
* System.out.println(b);
*
*/
public class ImpersonatedCredentials extends GoogleCredentials
implements ServiceAccountSigner, IdTokenProvider {
static final String IMPERSONATED_CREDENTIALS_FILE_TYPE = "impersonated_service_account";
private static final long serialVersionUID = -2133257318957488431L;
private static final String RFC3339 = "yyyy-MM-dd'T'HH:mm:ssX";
private static final int TWELVE_HOURS_IN_SECONDS = 43200;
private static final int DEFAULT_LIFETIME_IN_SECONDS = 3600;
private static final String CLOUD_PLATFORM_SCOPE =
"https://www.googleapis.com/auth/cloud-platform";
private static final String IAM_ACCESS_TOKEN_ENDPOINT =
"https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/%s:generateAccessToken";
private GoogleCredentials sourceCredentials;
private String targetPrincipal;
private List delegates;
private List scopes;
private int lifetime;
private String iamEndpointOverride;
private final String transportFactoryClassName;
private transient HttpTransportFactory transportFactory;
private transient Calendar calendar;
/**
* @param sourceCredentials the source credential used to acquire the impersonated credentials. It
* should be either a user account credential or a service account credential.
* @param targetPrincipal the service account to impersonate
* @param delegates the chained list of delegates required to grant the final access_token. If
* set, the sequence of identities must have "Service Account Token Creator" capability
* granted to the preceding identity. For example, if set to [serviceAccountB,
* serviceAccountC], the sourceCredential must have the Token Creator role on serviceAccountB.
* serviceAccountB must have the Token Creator on serviceAccountC. Finally, C must have Token
* Creator on target_principal. If unset, sourceCredential must have that role on
* targetPrincipal.
* @param scopes scopes to request during the authorization grant
* @param lifetime number of seconds the delegated credential should be valid. By default this
* value should be at most 3600. However, you can follow these
* instructions to set up the service account and extend the maximum lifetime to 43200 (12
* hours). If the given lifetime is 0, default value 3600 will be used instead when creating
* the credentials.
* @param transportFactory HTTP transport factory that creates the transport used to get access
* tokens
* @return new credentials
*/
public static ImpersonatedCredentials create(
GoogleCredentials sourceCredentials,
String targetPrincipal,
List delegates,
List scopes,
int lifetime,
HttpTransportFactory transportFactory) {
return ImpersonatedCredentials.newBuilder()
.setSourceCredentials(sourceCredentials)
.setTargetPrincipal(targetPrincipal)
.setDelegates(delegates)
.setScopes(scopes)
.setLifetime(lifetime)
.setHttpTransportFactory(transportFactory)
.build();
}
/**
* @param sourceCredentials the source credential used to acquire the impersonated credentials. It
* should be either a user account credential or a service account credential.
* @param targetPrincipal the service account to impersonate
* @param delegates the chained list of delegates required to grant the final access_token. If
* set, the sequence of identities must have "Service Account Token Creator" capability
* granted to the preceding identity. For example, if set to [serviceAccountB,
* serviceAccountC], the sourceCredential must have the Token Creator role on serviceAccountB.
* serviceAccountB must have the Token Creator on serviceAccountC. Finally, C must have Token
* Creator on target_principal. If unset, sourceCredential must have that role on
* targetPrincipal.
* @param scopes scopes to request during the authorization grant
* @param lifetime number of seconds the delegated credential should be valid. By default this
* value should be at most 3600. However, you can follow these
* instructions to set up the service account and extend the maximum lifetime to 43200 (12
* hours). If the given lifetime is 0, default value 3600 will be used instead when creating
* the credentials.
* @param transportFactory HTTP transport factory that creates the transport used to get access
* tokens.
* @param quotaProjectId the project used for quota and billing purposes. Should be null unless
* the caller wants to use a project different from the one that owns the impersonated
* credential for billing/quota purposes.
* @return new credentials
*/
public static ImpersonatedCredentials create(
GoogleCredentials sourceCredentials,
String targetPrincipal,
List delegates,
List scopes,
int lifetime,
HttpTransportFactory transportFactory,
String quotaProjectId) {
return ImpersonatedCredentials.newBuilder()
.setSourceCredentials(sourceCredentials)
.setTargetPrincipal(targetPrincipal)
.setDelegates(delegates)
.setScopes(scopes)
.setLifetime(lifetime)
.setHttpTransportFactory(transportFactory)
.setQuotaProjectId(quotaProjectId)
.build();
}
/**
* @param sourceCredentials the source credential used to acquire the impersonated credentials. It
* should be either a user account credential or a service account credential.
* @param targetPrincipal the service account to impersonate
* @param delegates the chained list of delegates required to grant the final access_token. If
* set, the sequence of identities must have "Service Account Token Creator" capability
* granted to the preceding identity. For example, if set to [serviceAccountB,
* serviceAccountC], the sourceCredential must have the Token Creator role on serviceAccountB.
* serviceAccountB must have the Token Creator on serviceAccountC. Finally, C must have Token
* Creator on target_principal. If unset, sourceCredential must have that role on
* targetPrincipal.
* @param scopes scopes to request during the authorization grant
* @param lifetime number of seconds the delegated credential should be valid. By default this
* value should be at most 3600. However, you can follow these
* instructions to set up the service account and extend the maximum lifetime to 43200 (12
* hours). If the given lifetime is 0, default value 3600 will be used instead when creating
* the credentials.
* @param transportFactory HTTP transport factory that creates the transport used to get access
* tokens.
* @param quotaProjectId the project used for quota and billing purposes. Should be null unless
* the caller wants to use a project different from the one that owns the impersonated
* credential for billing/quota purposes.
* @param iamEndpointOverride The full IAM endpoint override with the target_principal embedded.
* This is useful when supporting impersonation with regional endpoints.
* @return new credentials
*/
public static ImpersonatedCredentials create(
GoogleCredentials sourceCredentials,
String targetPrincipal,
List delegates,
List scopes,
int lifetime,
HttpTransportFactory transportFactory,
String quotaProjectId,
String iamEndpointOverride) {
return ImpersonatedCredentials.newBuilder()
.setSourceCredentials(sourceCredentials)
.setTargetPrincipal(targetPrincipal)
.setDelegates(delegates)
.setScopes(scopes)
.setLifetime(lifetime)
.setHttpTransportFactory(transportFactory)
.setQuotaProjectId(quotaProjectId)
.setIamEndpointOverride(iamEndpointOverride)
.build();
}
/**
* @param sourceCredentials the source credential used to acquire the impersonated credentials. It
* should be either a user account credential or a service account credential.
* @param targetPrincipal the service account to impersonate
* @param delegates the chained list of delegates required to grant the final access_token. If
* set, the sequence of identities must have "Service Account Token Creator" capability
* granted to the preceding identity. For example, if set to [serviceAccountB,
* serviceAccountC], the sourceCredential must have the Token Creator role on serviceAccountB.
* serviceAccountB must have the Token Creator on serviceAccountC. Finally, C must have Token
* Creator on target_principal. If left unset, sourceCredential must have that role on
* targetPrincipal.
* @param scopes scopes to request during the authorization grant
* @param lifetime number of seconds the delegated credential should be valid. By default this
* value should be at most 3600. However, you can follow these
* instructions to set up the service account and extend the maximum lifetime to 43200 (12
* hours).
* https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials#sa-credentials-oauth
* If the given lifetime is 0, default value 3600 will be used instead when creating the
* credentials.
* @return new credentials
*/
public static ImpersonatedCredentials create(
GoogleCredentials sourceCredentials,
String targetPrincipal,
List delegates,
List scopes,
int lifetime) {
return ImpersonatedCredentials.newBuilder()
.setSourceCredentials(sourceCredentials)
.setTargetPrincipal(targetPrincipal)
.setDelegates(delegates)
.setScopes(scopes)
.setLifetime(lifetime)
.build();
}
static String extractTargetPrincipal(String serviceAccountImpersonationUrl) {
// Extract the target principal.
int startIndex = serviceAccountImpersonationUrl.lastIndexOf('/');
int endIndex = serviceAccountImpersonationUrl.indexOf(":generateAccessToken");
if (startIndex != -1 && endIndex != -1 && startIndex < endIndex) {
return serviceAccountImpersonationUrl.substring(startIndex + 1, endIndex);
} else {
throw new IllegalArgumentException(
"Unable to determine target principal from service account impersonation URL.");
}
}
/**
* Returns the email field of the serviceAccount that is being impersonated.
*
* @return email address of the impersonated service account
*/
@Override
public String getAccount() {
return this.targetPrincipal;
}
@VisibleForTesting
String getIamEndpointOverride() {
return this.iamEndpointOverride;
}
@VisibleForTesting
List getDelegates() {
return delegates;
}
@VisibleForTesting
List getScopes() {
return scopes;
}
public GoogleCredentials getSourceCredentials() {
return sourceCredentials;
}
int getLifetime() {
return this.lifetime;
}
public void setTransportFactory(HttpTransportFactory httpTransportFactory) {
this.transportFactory = httpTransportFactory;
}
/**
* Signs the provided bytes using the private key associated with the impersonated service account
*
* @param toSign bytes to sign
* @return signed bytes
* @throws SigningException if the attempt to sign the provided bytes failed
* @see Blob
* Signing
*/
@Override
public byte[] sign(byte[] toSign) {
return IamUtils.sign(
getAccount(),
sourceCredentials,
transportFactory.create(),
toSign,
ImmutableMap.of("delegates", this.delegates));
}
/**
* Returns impersonation account credentials defined by JSON using the format generated by gCloud.
* The source credentials in the JSON should be either user account credentials or service account
* credentials.
*
* @param json a map from the JSON representing the credentials
* @param transportFactory HTTP transport factory, creates the transport used to get access tokens
* @return the credentials defined by the JSON
* @throws IOException if the credential cannot be created from the JSON.
*/
@SuppressWarnings("unchecked")
static ImpersonatedCredentials fromJson(
Map json, HttpTransportFactory transportFactory) throws IOException {
checkNotNull(json);
checkNotNull(transportFactory);
List delegates = null;
Map sourceCredentialsJson;
String sourceCredentialsType;
String quotaProjectId;
String targetPrincipal;
String serviceAccountImpersonationUrl;
try {
serviceAccountImpersonationUrl = (String) json.get("service_account_impersonation_url");
if (json.containsKey("delegates")) {
delegates = (List) json.get("delegates");
}
sourceCredentialsJson = (Map) json.get("source_credentials");
sourceCredentialsType = (String) sourceCredentialsJson.get("type");
quotaProjectId = (String) json.get("quota_project_id");
targetPrincipal = extractTargetPrincipal(serviceAccountImpersonationUrl);
} catch (ClassCastException | NullPointerException | IllegalArgumentException e) {
throw new CredentialFormatException("An invalid input stream was provided.", e);
}
GoogleCredentials sourceCredentials;
if (GoogleCredentials.USER_FILE_TYPE.equals(sourceCredentialsType)) {
sourceCredentials = UserCredentials.fromJson(sourceCredentialsJson, transportFactory);
} else if (GoogleCredentials.SERVICE_ACCOUNT_FILE_TYPE.equals(sourceCredentialsType)) {
sourceCredentials =
ServiceAccountCredentials.fromJson(sourceCredentialsJson, transportFactory);
} else {
throw new IOException(
String.format(
"A credential of type %s is not supported as source credential for impersonation.",
sourceCredentialsType));
}
return ImpersonatedCredentials.newBuilder()
.setSourceCredentials(sourceCredentials)
.setTargetPrincipal(targetPrincipal)
.setDelegates(delegates)
.setScopes(new ArrayList())
.setLifetime(DEFAULT_LIFETIME_IN_SECONDS)
.setHttpTransportFactory(transportFactory)
.setQuotaProjectId(quotaProjectId)
.setIamEndpointOverride(serviceAccountImpersonationUrl)
.build();
}
@Override
public boolean createScopedRequired() {
return this.scopes == null || this.scopes.isEmpty();
}
@Override
public GoogleCredentials createScoped(Collection scopes) {
return toBuilder()
.setScopes(new ArrayList<>(scopes))
.setLifetime(this.lifetime)
.setDelegates(this.delegates)
.setHttpTransportFactory(this.transportFactory)
.setQuotaProjectId(this.quotaProjectId)
.setIamEndpointOverride(this.iamEndpointOverride)
.build();
}
/**
* Clones the impersonated credentials with a new calendar.
*
* @param calendar the calendar that will be used by the new ImpersonatedCredentials instance when
* parsing the received expiration time of the refreshed access token
* @return the cloned impersonated credentials with the given custom calendar
*/
public ImpersonatedCredentials createWithCustomCalendar(Calendar calendar) {
return toBuilder()
.setScopes(this.scopes)
.setLifetime(this.lifetime)
.setDelegates(this.delegates)
.setHttpTransportFactory(this.transportFactory)
.setQuotaProjectId(this.quotaProjectId)
.setIamEndpointOverride(this.iamEndpointOverride)
.setCalendar(calendar)
.build();
}
private ImpersonatedCredentials(Builder builder) {
super(builder);
this.sourceCredentials = builder.getSourceCredentials();
this.targetPrincipal = builder.getTargetPrincipal();
this.delegates = builder.getDelegates();
this.scopes = builder.getScopes();
this.lifetime = builder.getLifetime();
this.transportFactory =
firstNonNull(
builder.getHttpTransportFactory(),
getFromServiceLoader(HttpTransportFactory.class, OAuth2Utils.HTTP_TRANSPORT_FACTORY));
this.iamEndpointOverride = builder.iamEndpointOverride;
this.transportFactoryClassName = this.transportFactory.getClass().getName();
this.calendar = builder.getCalendar();
if (this.delegates == null) {
this.delegates = new ArrayList();
}
if (this.scopes == null) {
throw new IllegalStateException("Scopes cannot be null");
}
if (this.lifetime > TWELVE_HOURS_IN_SECONDS) {
throw new IllegalStateException("lifetime must be less than or equal to 43200");
}
}
@Override
public AccessToken refreshAccessToken() throws IOException {
if (this.sourceCredentials.getAccessToken() == null) {
this.sourceCredentials =
this.sourceCredentials.createScoped(Arrays.asList(CLOUD_PLATFORM_SCOPE));
}
try {
this.sourceCredentials.refreshIfExpired();
} catch (IOException e) {
throw new IOException("Unable to refresh sourceCredentials", e);
}
HttpTransport httpTransport = this.transportFactory.create();
JsonObjectParser parser = new JsonObjectParser(OAuth2Utils.JSON_FACTORY);
HttpCredentialsAdapter adapter = new HttpCredentialsAdapter(sourceCredentials);
HttpRequestFactory requestFactory = httpTransport.createRequestFactory();
String endpointUrl =
this.iamEndpointOverride != null
? this.iamEndpointOverride
: String.format(IAM_ACCESS_TOKEN_ENDPOINT, this.targetPrincipal);
GenericUrl url = new GenericUrl(endpointUrl);
Map body =
ImmutableMap.of(
"delegates", this.delegates, "scope", this.scopes, "lifetime", this.lifetime + "s");
HttpContent requestContent = new JsonHttpContent(parser.getJsonFactory(), body);
HttpRequest request = requestFactory.buildPostRequest(url, requestContent);
adapter.initialize(request);
request.setParser(parser);
HttpResponse response = null;
try {
response = request.execute();
} catch (IOException e) {
throw new IOException("Error requesting access token", e);
}
GenericData responseData = response.parseAs(GenericData.class);
response.disconnect();
String accessToken =
OAuth2Utils.validateString(responseData, "accessToken", "Expected to find an accessToken");
String expireTime =
OAuth2Utils.validateString(responseData, "expireTime", "Expected to find an expireTime");
DateFormat format = new SimpleDateFormat(RFC3339);
format.setCalendar(calendar);
try {
Date date = format.parse(expireTime);
return new AccessToken(accessToken, date);
} catch (ParseException pe) {
throw new IOException("Error parsing expireTime: " + pe.getMessage());
}
}
/**
* Returns an IdToken for the current Credential.
*
* @param targetAudience the audience field for the issued ID token
* @param options credential specific options for for the token. For example, an ID token for an
* ImpersonatedCredentials can return the email address within the token claims if
* "ImpersonatedCredentials.INCLUDE_EMAIL" is provided as a list option.
* Only one option value is supported: "ImpersonatedCredentials.INCLUDE_EMAIL" If no options
* are set, the default excludes the "includeEmail" attribute in the API request.
* @return IdToken object which includes the raw id_token, expiration, and audience
* @throws IOException if the attempt to get an ID token failed
*/
@Override
public IdToken idTokenWithAudience(String targetAudience, List options)
throws IOException {
boolean includeEmail =
options != null && options.contains(IdTokenProvider.Option.INCLUDE_EMAIL);
return IamUtils.getIdToken(
getAccount(),
sourceCredentials,
transportFactory.create(),
targetAudience,
includeEmail,
ImmutableMap.of("delegates", this.delegates));
}
@Override
public int hashCode() {
return Objects.hash(
sourceCredentials,
targetPrincipal,
delegates,
scopes,
lifetime,
quotaProjectId,
iamEndpointOverride);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("sourceCredentials", sourceCredentials)
.add("targetPrincipal", targetPrincipal)
.add("delegates", delegates)
.add("scopes", scopes)
.add("lifetime", lifetime)
.add("transportFactoryClassName", transportFactoryClassName)
.add("quotaProjectId", quotaProjectId)
.add("iamEndpointOverride", iamEndpointOverride)
.toString();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ImpersonatedCredentials)) {
return false;
}
ImpersonatedCredentials other = (ImpersonatedCredentials) obj;
return Objects.equals(this.sourceCredentials, other.sourceCredentials)
&& Objects.equals(this.targetPrincipal, other.targetPrincipal)
&& Objects.equals(this.delegates, other.delegates)
&& Objects.equals(this.scopes, other.scopes)
&& Objects.equals(this.lifetime, other.lifetime)
&& Objects.equals(this.transportFactoryClassName, other.transportFactoryClassName)
&& Objects.equals(this.quotaProjectId, other.quotaProjectId)
&& Objects.equals(this.iamEndpointOverride, other.iamEndpointOverride);
}
@Override
public Builder toBuilder() {
return new Builder(this.sourceCredentials, this.targetPrincipal);
}
public static Builder newBuilder() {
return new Builder();
}
public static class Builder extends GoogleCredentials.Builder {
private GoogleCredentials sourceCredentials;
private String targetPrincipal;
private List delegates;
private List scopes;
private int lifetime = DEFAULT_LIFETIME_IN_SECONDS;
private HttpTransportFactory transportFactory;
private String iamEndpointOverride;
private Calendar calendar = Calendar.getInstance();
protected Builder() {}
protected Builder(GoogleCredentials sourceCredentials, String targetPrincipal) {
this.sourceCredentials = sourceCredentials;
this.targetPrincipal = targetPrincipal;
}
@CanIgnoreReturnValue
public Builder setSourceCredentials(GoogleCredentials sourceCredentials) {
this.sourceCredentials = sourceCredentials;
return this;
}
public GoogleCredentials getSourceCredentials() {
return this.sourceCredentials;
}
@CanIgnoreReturnValue
public Builder setTargetPrincipal(String targetPrincipal) {
this.targetPrincipal = targetPrincipal;
return this;
}
public String getTargetPrincipal() {
return this.targetPrincipal;
}
@CanIgnoreReturnValue
public Builder setDelegates(List delegates) {
this.delegates = delegates;
return this;
}
public List getDelegates() {
return this.delegates;
}
@CanIgnoreReturnValue
public Builder setScopes(List scopes) {
this.scopes = scopes;
return this;
}
public List getScopes() {
return this.scopes;
}
@CanIgnoreReturnValue
public Builder setLifetime(int lifetime) {
this.lifetime = lifetime == 0 ? DEFAULT_LIFETIME_IN_SECONDS : lifetime;
return this;
}
public int getLifetime() {
return this.lifetime;
}
@CanIgnoreReturnValue
public Builder setHttpTransportFactory(HttpTransportFactory transportFactory) {
this.transportFactory = transportFactory;
return this;
}
public HttpTransportFactory getHttpTransportFactory() {
return transportFactory;
}
@Override
@CanIgnoreReturnValue
public Builder setQuotaProjectId(String quotaProjectId) {
super.setQuotaProjectId(quotaProjectId);
return this;
}
@CanIgnoreReturnValue
public Builder setIamEndpointOverride(String iamEndpointOverride) {
this.iamEndpointOverride = iamEndpointOverride;
return this;
}
@CanIgnoreReturnValue
public Builder setCalendar(Calendar calendar) {
this.calendar = calendar;
return this;
}
public Calendar getCalendar() {
return this.calendar;
}
@Override
public ImpersonatedCredentials build() {
return new ImpersonatedCredentials(this);
}
}
private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException {
input.defaultReadObject();
transportFactory = newInstance(transportFactoryClassName);
}
}