• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2015, Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *    * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *    * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *
15  *    * Neither the name of Google Inc. nor the names of its
16  * contributors may be used to endorse or promote products derived from
17  * this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 package com.google.auth.oauth2;
33 
34 import static com.google.common.base.MoreObjects.firstNonNull;
35 
36 import com.google.api.client.http.GenericUrl;
37 import com.google.api.client.http.HttpBackOffIOExceptionHandler;
38 import com.google.api.client.http.HttpBackOffUnsuccessfulResponseHandler;
39 import com.google.api.client.http.HttpRequest;
40 import com.google.api.client.http.HttpRequestFactory;
41 import com.google.api.client.http.HttpResponse;
42 import com.google.api.client.http.HttpResponseException;
43 import com.google.api.client.http.UrlEncodedContent;
44 import com.google.api.client.json.JsonFactory;
45 import com.google.api.client.json.JsonObjectParser;
46 import com.google.api.client.json.webtoken.JsonWebSignature;
47 import com.google.api.client.json.webtoken.JsonWebToken;
48 import com.google.api.client.util.ExponentialBackOff;
49 import com.google.api.client.util.GenericData;
50 import com.google.api.client.util.Joiner;
51 import com.google.api.client.util.Preconditions;
52 import com.google.auth.Credentials;
53 import com.google.auth.RequestMetadataCallback;
54 import com.google.auth.ServiceAccountSigner;
55 import com.google.auth.http.HttpTransportFactory;
56 import com.google.common.annotations.VisibleForTesting;
57 import com.google.common.base.MoreObjects.ToStringHelper;
58 import com.google.common.collect.ImmutableSet;
59 import com.google.errorprone.annotations.CanIgnoreReturnValue;
60 import java.io.IOException;
61 import java.io.InputStream;
62 import java.io.ObjectInputStream;
63 import java.net.URI;
64 import java.net.URISyntaxException;
65 import java.security.GeneralSecurityException;
66 import java.security.InvalidKeyException;
67 import java.security.NoSuchAlgorithmException;
68 import java.security.PrivateKey;
69 import java.security.Signature;
70 import java.security.SignatureException;
71 import java.util.Collection;
72 import java.util.Collections;
73 import java.util.Date;
74 import java.util.List;
75 import java.util.Map;
76 import java.util.Objects;
77 import java.util.concurrent.Executor;
78 
79 /**
80  * OAuth2 credentials representing a Service Account for calling Google APIs.
81  *
82  * <p>By default uses a JSON Web Token (JWT) to fetch access tokens.
83  */
84 public class ServiceAccountCredentials extends GoogleCredentials
85     implements ServiceAccountSigner, IdTokenProvider, JwtProvider {
86 
87   private static final long serialVersionUID = 7807543542681217978L;
88   private static final String GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer";
89   private static final String PARSE_ERROR_PREFIX = "Error parsing token refresh response. ";
90   private static final int TWELVE_HOURS_IN_SECONDS = 43200;
91   private static final int DEFAULT_LIFETIME_IN_SECONDS = 3600;
92   private static final int INITIAL_RETRY_INTERVAL_MILLIS = 1000;
93   private static final double RETRY_RANDOMIZATION_FACTOR = 0.1;
94   private static final double RETRY_MULTIPLIER = 2;
95   static final int DEFAULT_NUMBER_OF_RETRIES = 3;
96 
97   private final String clientId;
98   private final String clientEmail;
99   private final PrivateKey privateKey;
100   private final String privateKeyId;
101   private final String serviceAccountUser;
102   private final String projectId;
103   private final String transportFactoryClassName;
104   private final URI tokenServerUri;
105   private final Collection<String> scopes;
106   private final Collection<String> defaultScopes;
107   private final int lifetime;
108   private final boolean useJwtAccessWithScope;
109   private final boolean defaultRetriesEnabled;
110 
111   private transient HttpTransportFactory transportFactory;
112 
113   private transient JwtCredentials selfSignedJwtCredentialsWithScope = null;
114 
115   /**
116    * Internal constructor
117    *
118    * @param builder A builder for {@link ServiceAccountCredentials} See {@link
119    *     ServiceAccountCredentials.Builder}
120    */
ServiceAccountCredentials(ServiceAccountCredentials.Builder builder)121   ServiceAccountCredentials(ServiceAccountCredentials.Builder builder) {
122     super(builder);
123     this.clientId = builder.clientId;
124     this.clientEmail = Preconditions.checkNotNull(builder.clientEmail);
125     this.privateKey = Preconditions.checkNotNull(builder.privateKey);
126     this.privateKeyId = builder.privateKeyId;
127     this.scopes =
128         (builder.scopes == null) ? ImmutableSet.<String>of() : ImmutableSet.copyOf(builder.scopes);
129     this.defaultScopes =
130         (builder.defaultScopes == null)
131             ? ImmutableSet.<String>of()
132             : ImmutableSet.copyOf(builder.defaultScopes);
133     this.transportFactory =
134         firstNonNull(
135             builder.transportFactory,
136             getFromServiceLoader(HttpTransportFactory.class, OAuth2Utils.HTTP_TRANSPORT_FACTORY));
137     this.transportFactoryClassName = this.transportFactory.getClass().getName();
138     this.tokenServerUri =
139         (builder.tokenServerUri == null) ? OAuth2Utils.TOKEN_SERVER_URI : builder.tokenServerUri;
140     this.serviceAccountUser = builder.serviceAccountUser;
141     this.projectId = builder.projectId;
142     if (builder.lifetime > TWELVE_HOURS_IN_SECONDS) {
143       throw new IllegalStateException("lifetime must be less than or equal to 43200");
144     }
145     this.lifetime = builder.lifetime;
146     this.useJwtAccessWithScope = builder.useJwtAccessWithScope;
147     this.defaultRetriesEnabled = builder.defaultRetriesEnabled;
148   }
149 
150   /**
151    * Returns service account credentials defined by JSON using the format supported by the Google
152    * Developers Console.
153    *
154    * @param json a map from the JSON representing the credentials.
155    * @param transportFactory HTTP transport factory, creates the transport used to get access
156    *     tokens.
157    * @return the credentials defined by the JSON.
158    * @throws IOException if the credential cannot be created from the JSON.
159    */
fromJson( Map<String, Object> json, HttpTransportFactory transportFactory)160   static ServiceAccountCredentials fromJson(
161       Map<String, Object> json, HttpTransportFactory transportFactory) throws IOException {
162     String clientId = (String) json.get("client_id");
163     String clientEmail = (String) json.get("client_email");
164     String privateKeyPkcs8 = (String) json.get("private_key");
165     String privateKeyId = (String) json.get("private_key_id");
166     String projectId = (String) json.get("project_id");
167     String tokenServerUriStringFromCreds = (String) json.get("token_uri");
168     String quotaProjectId = (String) json.get("quota_project_id");
169     String universeDomain = (String) json.get("universe_domain");
170     URI tokenServerUriFromCreds = null;
171     try {
172       if (tokenServerUriStringFromCreds != null) {
173         tokenServerUriFromCreds = new URI(tokenServerUriStringFromCreds);
174       }
175     } catch (URISyntaxException e) {
176       throw new IOException("Token server URI specified in 'token_uri' could not be parsed.");
177     }
178 
179     if (clientId == null
180         || clientEmail == null
181         || privateKeyPkcs8 == null
182         || privateKeyId == null) {
183       throw new IOException(
184           "Error reading service account credential from JSON, "
185               + "expecting  'client_id', 'client_email', 'private_key' and 'private_key_id'.");
186     }
187 
188     ServiceAccountCredentials.Builder builder =
189         ServiceAccountCredentials.newBuilder()
190             .setClientId(clientId)
191             .setClientEmail(clientEmail)
192             .setPrivateKeyId(privateKeyId)
193             .setHttpTransportFactory(transportFactory)
194             .setTokenServerUri(tokenServerUriFromCreds)
195             .setProjectId(projectId)
196             .setQuotaProjectId(quotaProjectId)
197             .setUniverseDomain(universeDomain);
198 
199     return fromPkcs8(privateKeyPkcs8, builder);
200   }
201 
202   /**
203    * Factory with minimum identifying information using PKCS#8 for the private key.
204    *
205    * @param clientId Client ID of the service account from the console. May be null.
206    * @param clientEmail Client email address of the service account from the console.
207    * @param privateKeyPkcs8 RSA private key object for the service account in PKCS#8 format.
208    * @param privateKeyId Private key identifier for the service account. May be null.
209    * @param scopes Scope strings for the APIs to be called. May be null or an empty collection,
210    *     which results in a credential that must have createScoped called before use.
211    * @return New ServiceAccountCredentials created from a private key.
212    * @throws IOException if the credential cannot be created from the private key.
213    */
fromPkcs8( String clientId, String clientEmail, String privateKeyPkcs8, String privateKeyId, Collection<String> scopes)214   public static ServiceAccountCredentials fromPkcs8(
215       String clientId,
216       String clientEmail,
217       String privateKeyPkcs8,
218       String privateKeyId,
219       Collection<String> scopes)
220       throws IOException {
221     ServiceAccountCredentials.Builder builder =
222         ServiceAccountCredentials.newBuilder()
223             .setClientId(clientId)
224             .setClientEmail(clientEmail)
225             .setPrivateKeyId(privateKeyId)
226             .setScopes(scopes);
227 
228     return fromPkcs8(privateKeyPkcs8, builder);
229   }
230 
231   /**
232    * Factory with minimum identifying information using PKCS#8 for the private key.
233    *
234    * @param clientId client ID of the service account from the console. May be null.
235    * @param clientEmail client email address of the service account from the console
236    * @param privateKeyPkcs8 RSA private key object for the service account in PKCS#8 format.
237    * @param privateKeyId private key identifier for the service account. May be null.
238    * @param scopes scope strings for the APIs to be called. May be null or an empty collection.
239    * @param defaultScopes default scope strings for the APIs to be called. May be null or an empty.
240    * @return new ServiceAccountCredentials created from a private key
241    * @throws IOException if the credential cannot be created from the private key
242    */
fromPkcs8( String clientId, String clientEmail, String privateKeyPkcs8, String privateKeyId, Collection<String> scopes, Collection<String> defaultScopes)243   public static ServiceAccountCredentials fromPkcs8(
244       String clientId,
245       String clientEmail,
246       String privateKeyPkcs8,
247       String privateKeyId,
248       Collection<String> scopes,
249       Collection<String> defaultScopes)
250       throws IOException {
251     ServiceAccountCredentials.Builder builder =
252         ServiceAccountCredentials.newBuilder()
253             .setClientId(clientId)
254             .setClientEmail(clientEmail)
255             .setPrivateKeyId(privateKeyId)
256             .setScopes(scopes, defaultScopes);
257 
258     return fromPkcs8(privateKeyPkcs8, builder);
259   }
260 
261   /**
262    * Factory with minimum identifying information and custom transport using PKCS#8 for the private
263    * key.
264    *
265    * @param clientId Client ID of the service account from the console. May be null.
266    * @param clientEmail Client email address of the service account from the console.
267    * @param privateKeyPkcs8 RSA private key object for the service account in PKCS#8 format.
268    * @param privateKeyId Private key identifier for the service account. May be null.
269    * @param scopes Scope strings for the APIs to be called. May be null or an empty collection,
270    *     which results in a credential that must have createScoped called before use.
271    * @param transportFactory HTTP transport factory, creates the transport used to get access
272    *     tokens.
273    * @param tokenServerUri URI of the end point that provides tokens.
274    * @return New ServiceAccountCredentials created from a private key.
275    * @throws IOException if the credential cannot be created from the private key.
276    */
fromPkcs8( String clientId, String clientEmail, String privateKeyPkcs8, String privateKeyId, Collection<String> scopes, HttpTransportFactory transportFactory, URI tokenServerUri)277   public static ServiceAccountCredentials fromPkcs8(
278       String clientId,
279       String clientEmail,
280       String privateKeyPkcs8,
281       String privateKeyId,
282       Collection<String> scopes,
283       HttpTransportFactory transportFactory,
284       URI tokenServerUri)
285       throws IOException {
286 
287     ServiceAccountCredentials.Builder builder =
288         ServiceAccountCredentials.newBuilder()
289             .setClientId(clientId)
290             .setClientEmail(clientEmail)
291             .setPrivateKeyId(privateKeyId)
292             .setScopes(scopes)
293             .setHttpTransportFactory(transportFactory)
294             .setTokenServerUri(tokenServerUri);
295 
296     return fromPkcs8(privateKeyPkcs8, builder);
297   }
298 
299   /**
300    * Factory with minimum identifying information and custom transport using PKCS#8 for the private
301    * key.
302    *
303    * @param clientId client ID of the service account from the console. May be null.
304    * @param clientEmail client email address of the service account from the console
305    * @param privateKeyPkcs8 RSA private key object for the service account in PKCS#8 format.
306    * @param privateKeyId private key identifier for the service account. May be null.
307    * @param scopes scope strings for the APIs to be called. May be null or an empty collection,
308    *     which results in a credential that must have createScoped called before use.
309    * @param defaultScopes default scope strings for the APIs to be called. May be null or an empty
310    *     collection, which results in a credential that must have createScoped called before use.
311    * @param transportFactory HTTP transport factory, creates the transport used to get access
312    *     tokens.
313    * @param tokenServerUri URI of the end point that provides tokens
314    * @return new ServiceAccountCredentials created from a private key
315    * @throws IOException if the credential cannot be created from the private key
316    */
fromPkcs8( String clientId, String clientEmail, String privateKeyPkcs8, String privateKeyId, Collection<String> scopes, Collection<String> defaultScopes, HttpTransportFactory transportFactory, URI tokenServerUri)317   public static ServiceAccountCredentials fromPkcs8(
318       String clientId,
319       String clientEmail,
320       String privateKeyPkcs8,
321       String privateKeyId,
322       Collection<String> scopes,
323       Collection<String> defaultScopes,
324       HttpTransportFactory transportFactory,
325       URI tokenServerUri)
326       throws IOException {
327 
328     ServiceAccountCredentials.Builder builder =
329         ServiceAccountCredentials.newBuilder()
330             .setClientId(clientId)
331             .setClientEmail(clientEmail)
332             .setPrivateKeyId(privateKeyId)
333             .setScopes(scopes, defaultScopes)
334             .setHttpTransportFactory(transportFactory)
335             .setTokenServerUri(tokenServerUri);
336 
337     return fromPkcs8(privateKeyPkcs8, builder);
338   }
339 
340   /**
341    * Factory with minimum identifying information and custom transport using PKCS#8 for the private
342    * key.
343    *
344    * @param clientId Client ID of the service account from the console. May be null.
345    * @param clientEmail Client email address of the service account from the console.
346    * @param privateKeyPkcs8 RSA private key object for the service account in PKCS#8 format.
347    * @param privateKeyId Private key identifier for the service account. May be null.
348    * @param scopes Scope strings for the APIs to be called. May be null or an empty collection,
349    *     which results in a credential that must have createScoped called before use.
350    * @param transportFactory HTTP transport factory, creates the transport used to get access
351    *     tokens.
352    * @param tokenServerUri URI of the end point that provides tokens.
353    * @param serviceAccountUser The email of the user account to impersonate, if delegating
354    *     domain-wide authority to the service account.
355    * @return New ServiceAccountCredentials created from a private key.
356    * @throws IOException if the credential cannot be created from the private key.
357    */
fromPkcs8( String clientId, String clientEmail, String privateKeyPkcs8, String privateKeyId, Collection<String> scopes, HttpTransportFactory transportFactory, URI tokenServerUri, String serviceAccountUser)358   public static ServiceAccountCredentials fromPkcs8(
359       String clientId,
360       String clientEmail,
361       String privateKeyPkcs8,
362       String privateKeyId,
363       Collection<String> scopes,
364       HttpTransportFactory transportFactory,
365       URI tokenServerUri,
366       String serviceAccountUser)
367       throws IOException {
368 
369     ServiceAccountCredentials.Builder builder =
370         ServiceAccountCredentials.newBuilder()
371             .setClientId(clientId)
372             .setClientEmail(clientEmail)
373             .setPrivateKeyId(privateKeyId)
374             .setScopes(scopes)
375             .setHttpTransportFactory(transportFactory)
376             .setTokenServerUri(tokenServerUri)
377             .setServiceAccountUser(serviceAccountUser);
378 
379     return fromPkcs8(privateKeyPkcs8, builder);
380   }
381 
382   /**
383    * Factory with minimum identifying information and custom transport using PKCS#8 for the private
384    * key.
385    *
386    * @param clientId client ID of the service account from the console. May be null.
387    * @param clientEmail client email address of the service account from the console
388    * @param privateKeyPkcs8 RSA private key object for the service account in PKCS#8 format.
389    * @param privateKeyId private key identifier for the service account. May be null.
390    * @param scopes scope strings for the APIs to be called. May be null or an empty collection,
391    *     which results in a credential that must have createScoped called before use.
392    * @param defaultScopes default scope strings for the APIs to be called. May be null or an empty
393    *     collection, which results in a credential that must have createScoped called before use.
394    * @param transportFactory HTTP transport factory, creates the transport used to get access
395    *     tokens.
396    * @param tokenServerUri URI of the end point that provides tokens
397    * @param serviceAccountUser the email of the user account to impersonate, if delegating
398    *     domain-wide authority to the service account.
399    * @return new ServiceAccountCredentials created from a private key
400    * @throws IOException if the credential cannot be created from the private key
401    */
fromPkcs8( String clientId, String clientEmail, String privateKeyPkcs8, String privateKeyId, Collection<String> scopes, Collection<String> defaultScopes, HttpTransportFactory transportFactory, URI tokenServerUri, String serviceAccountUser)402   public static ServiceAccountCredentials fromPkcs8(
403       String clientId,
404       String clientEmail,
405       String privateKeyPkcs8,
406       String privateKeyId,
407       Collection<String> scopes,
408       Collection<String> defaultScopes,
409       HttpTransportFactory transportFactory,
410       URI tokenServerUri,
411       String serviceAccountUser)
412       throws IOException {
413     ServiceAccountCredentials.Builder builder =
414         ServiceAccountCredentials.newBuilder()
415             .setClientId(clientId)
416             .setClientEmail(clientEmail)
417             .setPrivateKeyId(privateKeyId)
418             .setScopes(scopes, defaultScopes)
419             .setHttpTransportFactory(transportFactory)
420             .setTokenServerUri(tokenServerUri)
421             .setServiceAccountUser(serviceAccountUser);
422 
423     return fromPkcs8(privateKeyPkcs8, builder);
424   }
425 
426   /**
427    * Internal constructor
428    *
429    * @param privateKeyPkcs8 RSA private key object for the service account in PKCS#8 format.
430    * @param builder A builder for {@link ServiceAccountCredentials} See {@link
431    *     ServiceAccountCredentials.Builder}
432    * @return an instance of {@link ServiceAccountCredentials}
433    */
fromPkcs8( String privateKeyPkcs8, ServiceAccountCredentials.Builder builder)434   static ServiceAccountCredentials fromPkcs8(
435       String privateKeyPkcs8, ServiceAccountCredentials.Builder builder) throws IOException {
436     PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(privateKeyPkcs8);
437     builder.setPrivateKey(privateKey);
438 
439     return new ServiceAccountCredentials(builder);
440   }
441 
442   /**
443    * Returns credentials defined by a Service Account key file in JSON format from the Google
444    * Developers Console.
445    *
446    * @param credentialsStream the stream with the credential definition.
447    * @return the credential defined by the credentialsStream.
448    * @throws IOException if the credential cannot be created from the stream.
449    */
fromStream(InputStream credentialsStream)450   public static ServiceAccountCredentials fromStream(InputStream credentialsStream)
451       throws IOException {
452     return fromStream(credentialsStream, OAuth2Utils.HTTP_TRANSPORT_FACTORY);
453   }
454 
455   /**
456    * Returns credentials defined by a Service Account key file in JSON format from the Google
457    * Developers Console.
458    *
459    * @param credentialsStream the stream with the credential definition.
460    * @param transportFactory HTTP transport factory, creates the transport used to get access
461    *     tokens.
462    * @return the credential defined by the credentialsStream.
463    * @throws IOException if the credential cannot be created from the stream.
464    */
fromStream( InputStream credentialsStream, HttpTransportFactory transportFactory)465   public static ServiceAccountCredentials fromStream(
466       InputStream credentialsStream, HttpTransportFactory transportFactory) throws IOException {
467     ServiceAccountCredentials credential =
468         (ServiceAccountCredentials)
469             GoogleCredentials.fromStream(credentialsStream, transportFactory);
470     if (credential == null) {
471       throw new IOException(
472           String.format(
473               "Error reading credentials from stream, ServiceAccountCredentials type is not recognized."));
474     }
475     return credential;
476   }
477 
478   /** Returns whether the scopes are empty, meaning createScoped must be called before use. */
479   @Override
createScopedRequired()480   public boolean createScopedRequired() {
481     return scopes.isEmpty() && defaultScopes.isEmpty();
482   }
483 
484   /** Returns true if credential is configured domain wide delegation */
485   @VisibleForTesting
isConfiguredForDomainWideDelegation()486   boolean isConfiguredForDomainWideDelegation() {
487     return serviceAccountUser != null && serviceAccountUser.length() > 0;
488   }
489 
490   /**
491    * Refreshes the OAuth2 access token by getting a new access token using a JSON Web Token (JWT).
492    */
493   @Override
refreshAccessToken()494   public AccessToken refreshAccessToken() throws IOException {
495     JsonFactory jsonFactory = OAuth2Utils.JSON_FACTORY;
496     long currentTime = clock.currentTimeMillis();
497     String assertion = createAssertion(jsonFactory, currentTime);
498 
499     GenericData tokenRequest = new GenericData();
500     tokenRequest.set("grant_type", GRANT_TYPE);
501     tokenRequest.set("assertion", assertion);
502     UrlEncodedContent content = new UrlEncodedContent(tokenRequest);
503 
504     HttpRequestFactory requestFactory = transportFactory.create().createRequestFactory();
505     HttpRequest request = requestFactory.buildPostRequest(new GenericUrl(tokenServerUri), content);
506 
507     if (this.defaultRetriesEnabled) {
508       request.setNumberOfRetries(DEFAULT_NUMBER_OF_RETRIES);
509     } else {
510       request.setNumberOfRetries(0);
511     }
512     request.setParser(new JsonObjectParser(jsonFactory));
513 
514     ExponentialBackOff backoff =
515         new ExponentialBackOff.Builder()
516             .setInitialIntervalMillis(INITIAL_RETRY_INTERVAL_MILLIS)
517             .setRandomizationFactor(RETRY_RANDOMIZATION_FACTOR)
518             .setMultiplier(RETRY_MULTIPLIER)
519             .build();
520 
521     request.setUnsuccessfulResponseHandler(
522         new HttpBackOffUnsuccessfulResponseHandler(backoff)
523             .setBackOffRequired(
524                 response -> {
525                   int code = response.getStatusCode();
526                   return OAuth2Utils.TOKEN_ENDPOINT_RETRYABLE_STATUS_CODES.contains(code);
527                 }));
528 
529     request.setIOExceptionHandler(new HttpBackOffIOExceptionHandler(backoff));
530     HttpResponse response;
531 
532     String errorTemplate = "Error getting access token for service account: %s, iss: %s";
533 
534     try {
535       response = request.execute();
536     } catch (HttpResponseException re) {
537       String message = String.format(errorTemplate, re.getMessage(), getIssuer());
538       throw GoogleAuthException.createWithTokenEndpointResponseException(re, message);
539     } catch (IOException e) {
540       throw GoogleAuthException.createWithTokenEndpointIOException(
541           e, String.format(errorTemplate, e.getMessage(), getIssuer()));
542     }
543 
544     GenericData responseData = response.parseAs(GenericData.class);
545     String accessToken =
546         OAuth2Utils.validateString(responseData, "access_token", PARSE_ERROR_PREFIX);
547     int expiresInSeconds =
548         OAuth2Utils.validateInt32(responseData, "expires_in", PARSE_ERROR_PREFIX);
549     long expiresAtMilliseconds = clock.currentTimeMillis() + expiresInSeconds * 1000L;
550     return new AccessToken(accessToken, new Date(expiresAtMilliseconds));
551   }
552 
553   /**
554    * Returns a Google ID Token from the metadata server on ComputeEngine.
555    *
556    * @param targetAudience the aud: field the IdToken should include.
557    * @param options list of Credential specific options for the token. Currently, unused for
558    *     ServiceAccountCredentials.
559    * @throws IOException if the attempt to get an IdToken failed
560    * @return IdToken object which includes the raw id_token, expiration and audience
561    */
562   @Override
idTokenWithAudience(String targetAudience, List<Option> options)563   public IdToken idTokenWithAudience(String targetAudience, List<Option> options)
564       throws IOException {
565 
566     JsonFactory jsonFactory = OAuth2Utils.JSON_FACTORY;
567     long currentTime = clock.currentTimeMillis();
568     String assertion =
569         createAssertionForIdToken(
570             jsonFactory, currentTime, tokenServerUri.toString(), targetAudience);
571 
572     GenericData tokenRequest = new GenericData();
573     tokenRequest.set("grant_type", GRANT_TYPE);
574     tokenRequest.set("assertion", assertion);
575     UrlEncodedContent content = new UrlEncodedContent(tokenRequest);
576 
577     HttpRequestFactory requestFactory = transportFactory.create().createRequestFactory();
578     HttpRequest request = requestFactory.buildPostRequest(new GenericUrl(tokenServerUri), content);
579     request.setParser(new JsonObjectParser(jsonFactory));
580     HttpResponse response;
581     try {
582       response = request.execute();
583     } catch (IOException e) {
584       throw new IOException(
585           String.format(
586               "Error getting id token for service account: %s, iss: %s",
587               e.getMessage(), getIssuer()),
588           e);
589     }
590 
591     GenericData responseData = response.parseAs(GenericData.class);
592     String rawToken = OAuth2Utils.validateString(responseData, "id_token", PARSE_ERROR_PREFIX);
593 
594     return IdToken.create(rawToken);
595   }
596 
597   /**
598    * Clones the service account with the specified default retries.
599    *
600    * @param defaultRetriesEnabled a flag enabling or disabling default retries
601    * @return GoogleCredentials with the specified retry configuration.
602    */
603   @Override
createWithCustomRetryStrategy(boolean defaultRetriesEnabled)604   public ServiceAccountCredentials createWithCustomRetryStrategy(boolean defaultRetriesEnabled) {
605     return this.toBuilder().setDefaultRetriesEnabled(defaultRetriesEnabled).build();
606   }
607 
608   /**
609    * Clones the service account with the specified scopes.
610    *
611    * <p>Should be called before use for instances with empty scopes.
612    */
613   @Override
createScoped(Collection<String> newScopes)614   public GoogleCredentials createScoped(Collection<String> newScopes) {
615     return createScoped(newScopes, null);
616   }
617 
618   /**
619    * Clones the service account with the specified scopes.
620    *
621    * <p>Should be called before use for instances with empty scopes.
622    */
623   @Override
createScoped( Collection<String> newScopes, Collection<String> newDefaultScopes)624   public GoogleCredentials createScoped(
625       Collection<String> newScopes, Collection<String> newDefaultScopes) {
626     return this.toBuilder().setScopes(newScopes, newDefaultScopes).build();
627   }
628 
629   /**
630    * Clones the service account with a new lifetime value.
631    *
632    * @param lifetime life time value in seconds. The value should be at most 43200 (12 hours). If
633    *     the token is used for calling a Google API, then the value should be at most 3600 (1 hour).
634    *     If the given value is 0, then the default value 3600 will be used when creating the
635    *     credentials.
636    * @return the cloned service account credentials with the given custom life time
637    */
createWithCustomLifetime(int lifetime)638   public ServiceAccountCredentials createWithCustomLifetime(int lifetime) {
639     return this.toBuilder().setLifetime(lifetime).build();
640   }
641 
642   /**
643    * Clones the service account with a new useJwtAccessWithScope value. This flag will be ignored if
644    * universeDomain field is different from {@link Credentials.GOOGLE_DEFAULT_UNIVERSE}.
645    *
646    * @param useJwtAccessWithScope whether self-signed JWT with scopes should be used
647    * @return the cloned service account credentials with the given useJwtAccessWithScope
648    */
createWithUseJwtAccessWithScope(boolean useJwtAccessWithScope)649   public ServiceAccountCredentials createWithUseJwtAccessWithScope(boolean useJwtAccessWithScope) {
650     return this.toBuilder().setUseJwtAccessWithScope(useJwtAccessWithScope).build();
651   }
652 
653   @Override
createDelegated(String user)654   public GoogleCredentials createDelegated(String user) {
655     return this.toBuilder().setServiceAccountUser(user).build();
656   }
657 
getClientId()658   public final String getClientId() {
659     return clientId;
660   }
661 
getClientEmail()662   public final String getClientEmail() {
663     return clientEmail;
664   }
665 
getPrivateKey()666   public final PrivateKey getPrivateKey() {
667     return privateKey;
668   }
669 
getPrivateKeyId()670   public final String getPrivateKeyId() {
671     return privateKeyId;
672   }
673 
getScopes()674   public final Collection<String> getScopes() {
675     return scopes;
676   }
677 
getDefaultScopes()678   public final Collection<String> getDefaultScopes() {
679     return defaultScopes;
680   }
681 
getServiceAccountUser()682   public final String getServiceAccountUser() {
683     return serviceAccountUser;
684   }
685 
getProjectId()686   public final String getProjectId() {
687     return projectId;
688   }
689 
getTokenServerUri()690   public final URI getTokenServerUri() {
691     return tokenServerUri;
692   }
693 
getIssuer()694   private String getIssuer() {
695     return this.clientEmail;
696   }
697 
698   @VisibleForTesting
getLifetime()699   int getLifetime() {
700     return lifetime;
701   }
702 
getUseJwtAccessWithScope()703   public boolean getUseJwtAccessWithScope() {
704     return useJwtAccessWithScope;
705   }
706 
707   @VisibleForTesting
getSelfSignedJwtCredentialsWithScope()708   JwtCredentials getSelfSignedJwtCredentialsWithScope() {
709     return selfSignedJwtCredentialsWithScope;
710   }
711 
712   @Override
getAccount()713   public String getAccount() {
714     return getClientEmail();
715   }
716 
717   @Override
sign(byte[] toSign)718   public byte[] sign(byte[] toSign) {
719     try {
720       Signature signer = Signature.getInstance(OAuth2Utils.SIGNATURE_ALGORITHM);
721       signer.initSign(getPrivateKey());
722       signer.update(toSign);
723       return signer.sign();
724     } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException ex) {
725       throw new SigningException("Failed to sign the provided bytes", ex);
726     }
727   }
728 
729   /**
730    * Returns a new JwtCredentials instance with modified claims.
731    *
732    * @param newClaims new claims. Any unspecified claim fields will default to the current values.
733    * @return new credentials
734    */
735   @Override
jwtWithClaims(JwtClaims newClaims)736   public JwtCredentials jwtWithClaims(JwtClaims newClaims) {
737     JwtClaims.Builder claimsBuilder =
738         JwtClaims.newBuilder().setIssuer(getIssuer()).setSubject(clientEmail);
739     return JwtCredentials.newBuilder()
740         .setPrivateKey(privateKey)
741         .setPrivateKeyId(privateKeyId)
742         .setJwtClaims(claimsBuilder.build().merge(newClaims))
743         .setClock(clock)
744         .build();
745   }
746 
747   @Override
hashCode()748   public int hashCode() {
749     return Objects.hash(
750         clientId,
751         clientEmail,
752         privateKey,
753         privateKeyId,
754         transportFactoryClassName,
755         tokenServerUri,
756         scopes,
757         defaultScopes,
758         lifetime,
759         useJwtAccessWithScope,
760         defaultRetriesEnabled,
761         super.hashCode());
762   }
763 
764   @Override
toStringHelper()765   protected ToStringHelper toStringHelper() {
766     return super.toStringHelper()
767         .add("clientId", clientId)
768         .add("clientEmail", clientEmail)
769         .add("privateKeyId", privateKeyId)
770         .add("transportFactoryClassName", transportFactoryClassName)
771         .add("tokenServerUri", tokenServerUri)
772         .add("scopes", scopes)
773         .add("defaultScopes", defaultScopes)
774         .add("serviceAccountUser", serviceAccountUser)
775         .add("lifetime", lifetime)
776         .add("useJwtAccessWithScope", useJwtAccessWithScope)
777         .add("defaultRetriesEnabled", defaultRetriesEnabled);
778   }
779 
780   @Override
equals(Object obj)781   public boolean equals(Object obj) {
782     if (!(obj instanceof ServiceAccountCredentials)) {
783       return false;
784     }
785     if (!super.equals(obj)) {
786       return false;
787     }
788 
789     ServiceAccountCredentials other = (ServiceAccountCredentials) obj;
790     return Objects.equals(this.clientId, other.clientId)
791         && Objects.equals(this.clientEmail, other.clientEmail)
792         && Objects.equals(this.privateKey, other.privateKey)
793         && Objects.equals(this.privateKeyId, other.privateKeyId)
794         && Objects.equals(this.transportFactoryClassName, other.transportFactoryClassName)
795         && Objects.equals(this.tokenServerUri, other.tokenServerUri)
796         && Objects.equals(this.scopes, other.scopes)
797         && Objects.equals(this.defaultScopes, other.defaultScopes)
798         && Objects.equals(this.lifetime, other.lifetime)
799         && Objects.equals(this.useJwtAccessWithScope, other.useJwtAccessWithScope)
800         && Objects.equals(this.defaultRetriesEnabled, other.defaultRetriesEnabled);
801   }
802 
createAssertion(JsonFactory jsonFactory, long currentTime)803   String createAssertion(JsonFactory jsonFactory, long currentTime) throws IOException {
804     JsonWebSignature.Header header = new JsonWebSignature.Header();
805     header.setAlgorithm("RS256");
806     header.setType("JWT");
807     header.setKeyId(privateKeyId);
808 
809     JsonWebToken.Payload payload = new JsonWebToken.Payload();
810     payload.setIssuer(getIssuer());
811     payload.setIssuedAtTimeSeconds(currentTime / 1000);
812     payload.setExpirationTimeSeconds(currentTime / 1000 + this.lifetime);
813     payload.setSubject(serviceAccountUser);
814     if (scopes.isEmpty()) {
815       payload.put("scope", Joiner.on(' ').join(defaultScopes));
816     } else {
817       payload.put("scope", Joiner.on(' ').join(scopes));
818     }
819 
820     payload.setAudience(OAuth2Utils.TOKEN_SERVER_URI.toString());
821     String assertion;
822 
823     try {
824       assertion = JsonWebSignature.signUsingRsaSha256(privateKey, jsonFactory, header, payload);
825     } catch (GeneralSecurityException e) {
826       throw new IOException(
827           "Error signing service account access token request with private key.", e);
828     }
829     return assertion;
830   }
831 
832   @VisibleForTesting
createAssertionForIdToken( JsonFactory jsonFactory, long currentTime, String audience, String targetAudience)833   String createAssertionForIdToken(
834       JsonFactory jsonFactory, long currentTime, String audience, String targetAudience)
835       throws IOException {
836     JsonWebSignature.Header header = new JsonWebSignature.Header();
837     header.setAlgorithm("RS256");
838     header.setType("JWT");
839     header.setKeyId(privateKeyId);
840 
841     JsonWebToken.Payload payload = new JsonWebToken.Payload();
842     payload.setIssuer(getIssuer());
843     payload.setIssuedAtTimeSeconds(currentTime / 1000);
844     payload.setExpirationTimeSeconds(currentTime / 1000 + this.lifetime);
845     payload.setSubject(serviceAccountUser);
846 
847     if (audience == null) {
848       payload.setAudience(OAuth2Utils.TOKEN_SERVER_URI.toString());
849     } else {
850       payload.setAudience(audience);
851     }
852 
853     try {
854       payload.set("target_audience", targetAudience);
855 
856       String assertion =
857           JsonWebSignature.signUsingRsaSha256(privateKey, jsonFactory, header, payload);
858       return assertion;
859     } catch (GeneralSecurityException e) {
860       throw new IOException(
861           "Error signing service account access token request with private key.", e);
862     }
863   }
864 
865   /**
866    * Self-signed JWT uses uri as audience, which should have the "https://{host}/" format. For
867    * instance, if the uri is "https://compute.googleapis.com/compute/v1/projects/", then this
868    * function returns "https://compute.googleapis.com/".
869    */
870   @VisibleForTesting
getUriForSelfSignedJWT(URI uri)871   static URI getUriForSelfSignedJWT(URI uri) {
872     if (uri == null || uri.getScheme() == null || uri.getHost() == null) {
873       return uri;
874     }
875     try {
876       return new URI(uri.getScheme(), uri.getHost(), "/", null);
877     } catch (URISyntaxException unused) {
878       return uri;
879     }
880   }
881 
882   @VisibleForTesting
createSelfSignedJwtCredentials(final URI uri)883   JwtCredentials createSelfSignedJwtCredentials(final URI uri) {
884     // Create a JwtCredentials for self-signed JWT. See https://google.aip.dev/auth/4111.
885     JwtClaims.Builder claimsBuilder =
886         JwtClaims.newBuilder().setIssuer(clientEmail).setSubject(clientEmail);
887 
888     if (uri == null) {
889       // If uri is null, use scopes.
890       String scopeClaim = "";
891       if (!scopes.isEmpty()) {
892         scopeClaim = Joiner.on(' ').join(scopes);
893       } else {
894         scopeClaim = Joiner.on(' ').join(defaultScopes);
895       }
896       claimsBuilder.setAdditionalClaims(Collections.singletonMap("scope", scopeClaim));
897     } else {
898       // otherwise, use audience with the uri.
899       claimsBuilder.setAudience(getUriForSelfSignedJWT(uri).toString());
900     }
901     return JwtCredentials.newBuilder()
902         .setPrivateKey(privateKey)
903         .setPrivateKeyId(privateKeyId)
904         .setJwtClaims(claimsBuilder.build())
905         .setClock(clock)
906         .build();
907   }
908 
909   @Override
getRequestMetadata( final URI uri, Executor executor, final RequestMetadataCallback callback)910   public void getRequestMetadata(
911       final URI uri, Executor executor, final RequestMetadataCallback callback) {
912     // For default universe Self-signed JWT could be explicitly disabled with
913     // {@code ServiceAccountCredentials.useJwtAccessWithScope} flag.
914     // If universe is non-default, it only supports self-signed JWT, and it is always allowed.
915     if (this.useJwtAccessWithScope || !isDefaultUniverseDomain()) {
916       // This will call getRequestMetadata(URI uri), which handles self-signed JWT logic.
917       // Self-signed JWT doesn't use network, so here we do a blocking call to improve
918       // efficiency. executor will be ignored since it is intended for async operation.
919       blockingGetToCallback(uri, callback);
920     } else {
921       super.getRequestMetadata(uri, executor, callback);
922     }
923   }
924 
925   /** Provide the request metadata by putting an access JWT directly in the metadata. */
926   @Override
getRequestMetadata(URI uri)927   public Map<String, List<String>> getRequestMetadata(URI uri) throws IOException {
928     if (createScopedRequired() && uri == null) {
929       throw new IOException(
930           "Scopes and uri are not configured for service account. Specify the scopes"
931               + " by calling createScoped or passing scopes to constructor or"
932               + " providing uri to getRequestMetadata.");
933     }
934 
935     if (isDefaultUniverseDomain()) {
936       return getRequestMetadataForGdu(uri);
937     } else {
938       return getRequestMetadataForNonGdu(uri);
939     }
940   }
941 
getRequestMetadataForGdu(URI uri)942   private Map<String, List<String>> getRequestMetadataForGdu(URI uri) throws IOException {
943     // If scopes are provided, but we cannot use self-signed JWT or domain-wide delegation is
944     // configured then use scopes to get access token.
945     if ((!createScopedRequired() && !useJwtAccessWithScope)
946         || isConfiguredForDomainWideDelegation()) {
947       return super.getRequestMetadata(uri);
948     }
949 
950     return getRequestMetadataWithSelfSignedJwt(uri);
951   }
952 
getRequestMetadataForNonGdu(URI uri)953   private Map<String, List<String>> getRequestMetadataForNonGdu(URI uri) throws IOException {
954     // Self Signed JWT is not supported for domain-wide delegation for non-GDU universes
955     if (isConfiguredForDomainWideDelegation()) {
956       throw new IOException(
957           String.format(
958               "Service Account user is configured for the credential. "
959                   + "Domain-wide delegation is not supported in universes different than %s.",
960               Credentials.GOOGLE_DEFAULT_UNIVERSE));
961     }
962 
963     return getRequestMetadataWithSelfSignedJwt(uri);
964   }
965 
966   /** Provide the access JWT for scopes if provided, for uri as aud otherwise */
967   @VisibleForTesting
getRequestMetadataWithSelfSignedJwt(URI uri)968   private Map<String, List<String>> getRequestMetadataWithSelfSignedJwt(URI uri)
969       throws IOException {
970     // If scopes are provided and self-signed JWT can be used, use self-signed JWT with scopes.
971     // Otherwise, use self-signed JWT with uri as the audience.
972     JwtCredentials jwtCredentials;
973     if (!createScopedRequired()) {
974       // Create selfSignedJwtCredentialsWithScope when needed and reuse it for better performance.
975       if (selfSignedJwtCredentialsWithScope == null) {
976         selfSignedJwtCredentialsWithScope = createSelfSignedJwtCredentials(null);
977       }
978       jwtCredentials = selfSignedJwtCredentialsWithScope;
979     } else {
980       // Create JWT credentials with the uri as audience.
981       jwtCredentials = createSelfSignedJwtCredentials(uri);
982     }
983 
984     Map<String, List<String>> requestMetadata = jwtCredentials.getRequestMetadata(null);
985     return addQuotaProjectIdToRequestMetadata(quotaProjectId, requestMetadata);
986   }
987 
988   @SuppressWarnings("unused")
readObject(ObjectInputStream input)989   private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException {
990     // properly deserialize the transient transportFactory
991     input.defaultReadObject();
992     transportFactory = newInstance(transportFactoryClassName);
993   }
994 
newBuilder()995   public static Builder newBuilder() {
996     return new Builder();
997   }
998 
999   @Override
toBuilder()1000   public Builder toBuilder() {
1001     return new Builder(this);
1002   }
1003 
1004   public static class Builder extends GoogleCredentials.Builder {
1005 
1006     private String clientId;
1007     private String clientEmail;
1008     private PrivateKey privateKey;
1009     private String privateKeyId;
1010     private String serviceAccountUser;
1011     private String projectId;
1012     private URI tokenServerUri;
1013     private Collection<String> scopes;
1014     private Collection<String> defaultScopes;
1015     private HttpTransportFactory transportFactory;
1016     private int lifetime = DEFAULT_LIFETIME_IN_SECONDS;
1017     private boolean useJwtAccessWithScope = false;
1018     private boolean defaultRetriesEnabled = true;
1019 
Builder()1020     protected Builder() {}
1021 
Builder(ServiceAccountCredentials credentials)1022     protected Builder(ServiceAccountCredentials credentials) {
1023       super(credentials);
1024       this.clientId = credentials.clientId;
1025       this.clientEmail = credentials.clientEmail;
1026       this.privateKey = credentials.privateKey;
1027       this.privateKeyId = credentials.privateKeyId;
1028       this.scopes = credentials.scopes;
1029       this.defaultScopes = credentials.defaultScopes;
1030       this.transportFactory = credentials.transportFactory;
1031       this.tokenServerUri = credentials.tokenServerUri;
1032       this.serviceAccountUser = credentials.serviceAccountUser;
1033       this.projectId = credentials.projectId;
1034       this.lifetime = credentials.lifetime;
1035       this.useJwtAccessWithScope = credentials.useJwtAccessWithScope;
1036       this.defaultRetriesEnabled = credentials.defaultRetriesEnabled;
1037     }
1038 
1039     @CanIgnoreReturnValue
setClientId(String clientId)1040     public Builder setClientId(String clientId) {
1041       this.clientId = clientId;
1042       return this;
1043     }
1044 
1045     @CanIgnoreReturnValue
setClientEmail(String clientEmail)1046     public Builder setClientEmail(String clientEmail) {
1047       this.clientEmail = clientEmail;
1048       return this;
1049     }
1050 
1051     @CanIgnoreReturnValue
setPrivateKey(PrivateKey privateKey)1052     public Builder setPrivateKey(PrivateKey privateKey) {
1053       this.privateKey = privateKey;
1054       return this;
1055     }
1056 
1057     @CanIgnoreReturnValue
setPrivateKeyString(String privateKeyPkcs8)1058     public Builder setPrivateKeyString(String privateKeyPkcs8) throws IOException {
1059       this.privateKey = OAuth2Utils.privateKeyFromPkcs8(privateKeyPkcs8);
1060       return this;
1061     }
1062 
1063     @CanIgnoreReturnValue
setPrivateKeyId(String privateKeyId)1064     public Builder setPrivateKeyId(String privateKeyId) {
1065       this.privateKeyId = privateKeyId;
1066       return this;
1067     }
1068 
1069     @CanIgnoreReturnValue
setScopes(Collection<String> scopes)1070     public Builder setScopes(Collection<String> scopes) {
1071       this.scopes = scopes;
1072       this.defaultScopes = ImmutableSet.<String>of();
1073       return this;
1074     }
1075 
1076     @CanIgnoreReturnValue
setScopes(Collection<String> scopes, Collection<String> defaultScopes)1077     public Builder setScopes(Collection<String> scopes, Collection<String> defaultScopes) {
1078       this.scopes = scopes;
1079       this.defaultScopes = defaultScopes;
1080       return this;
1081     }
1082 
1083     @CanIgnoreReturnValue
setServiceAccountUser(String serviceAccountUser)1084     public Builder setServiceAccountUser(String serviceAccountUser) {
1085       this.serviceAccountUser = serviceAccountUser;
1086       return this;
1087     }
1088 
1089     @CanIgnoreReturnValue
setProjectId(String projectId)1090     public Builder setProjectId(String projectId) {
1091       this.projectId = projectId;
1092       return this;
1093     }
1094 
1095     @CanIgnoreReturnValue
setTokenServerUri(URI tokenServerUri)1096     public Builder setTokenServerUri(URI tokenServerUri) {
1097       this.tokenServerUri = tokenServerUri;
1098       return this;
1099     }
1100 
1101     @CanIgnoreReturnValue
setHttpTransportFactory(HttpTransportFactory transportFactory)1102     public Builder setHttpTransportFactory(HttpTransportFactory transportFactory) {
1103       this.transportFactory = transportFactory;
1104       return this;
1105     }
1106 
1107     @Override
1108     @CanIgnoreReturnValue
setQuotaProjectId(String quotaProjectId)1109     public Builder setQuotaProjectId(String quotaProjectId) {
1110       super.setQuotaProjectId(quotaProjectId);
1111       return this;
1112     }
1113 
1114     @CanIgnoreReturnValue
setLifetime(int lifetime)1115     public Builder setLifetime(int lifetime) {
1116       this.lifetime = lifetime == 0 ? DEFAULT_LIFETIME_IN_SECONDS : lifetime;
1117       return this;
1118     }
1119 
1120     /**
1121      * Sets the useJwtAccessWithScope flag. This flag will be ignored if universeDomain field is
1122      * different from {@link Credentials.GOOGLE_DEFAULT_UNIVERSE}.
1123      */
1124     @CanIgnoreReturnValue
setUseJwtAccessWithScope(boolean useJwtAccessWithScope)1125     public Builder setUseJwtAccessWithScope(boolean useJwtAccessWithScope) {
1126       this.useJwtAccessWithScope = useJwtAccessWithScope;
1127       return this;
1128     }
1129 
1130     @CanIgnoreReturnValue
setDefaultRetriesEnabled(boolean defaultRetriesEnabled)1131     public Builder setDefaultRetriesEnabled(boolean defaultRetriesEnabled) {
1132       this.defaultRetriesEnabled = defaultRetriesEnabled;
1133       return this;
1134     }
1135 
setUniverseDomain(String universeDomain)1136     public Builder setUniverseDomain(String universeDomain) {
1137       super.universeDomain = universeDomain;
1138       return this;
1139     }
1140 
getClientId()1141     public String getClientId() {
1142       return clientId;
1143     }
1144 
getClientEmail()1145     public String getClientEmail() {
1146       return clientEmail;
1147     }
1148 
getPrivateKey()1149     public PrivateKey getPrivateKey() {
1150       return privateKey;
1151     }
1152 
getPrivateKeyId()1153     public String getPrivateKeyId() {
1154       return privateKeyId;
1155     }
1156 
getScopes()1157     public Collection<String> getScopes() {
1158       return scopes;
1159     }
1160 
getDefaultScopes()1161     public Collection<String> getDefaultScopes() {
1162       return defaultScopes;
1163     }
1164 
getServiceAccountUser()1165     public String getServiceAccountUser() {
1166       return serviceAccountUser;
1167     }
1168 
getProjectId()1169     public String getProjectId() {
1170       return projectId;
1171     }
1172 
getTokenServerUri()1173     public URI getTokenServerUri() {
1174       return tokenServerUri;
1175     }
1176 
getHttpTransportFactory()1177     public HttpTransportFactory getHttpTransportFactory() {
1178       return transportFactory;
1179     }
1180 
getLifetime()1181     public int getLifetime() {
1182       return lifetime;
1183     }
1184 
getUseJwtAccessWithScope()1185     public boolean getUseJwtAccessWithScope() {
1186       return useJwtAccessWithScope;
1187     }
1188 
isDefaultRetriesEnabled()1189     public boolean isDefaultRetriesEnabled() {
1190       return defaultRetriesEnabled;
1191     }
1192 
1193     @Override
build()1194     public ServiceAccountCredentials build() {
1195       return new ServiceAccountCredentials(this);
1196     }
1197   }
1198 }
1199