4141import com .google .api .client .http .HttpStatusCodes ;
4242import com .google .api .client .json .JsonObjectParser ;
4343import com .google .api .client .util .GenericData ;
44+ import com .google .auth .Credentials ;
45+ import com .google .auth .Retryable ;
4446import com .google .auth .ServiceAccountSigner ;
4547import com .google .auth .http .HttpTransportFactory ;
4648import com .google .common .annotations .VisibleForTesting ;
4749import com .google .common .base .Joiner ;
48- import com .google .common .base .MoreObjects ;
50+ import com .google .common .base .MoreObjects . ToStringHelper ;
4951import com .google .common .collect .ImmutableSet ;
5052import com .google .errorprone .annotations .CanIgnoreReturnValue ;
5153import java .io .BufferedReader ;
@@ -79,12 +81,12 @@ public class ComputeEngineCredentials extends GoogleCredentials
7981 implements ServiceAccountSigner , IdTokenProvider {
8082
8183 // Decrease timing margins on GCE.
82- // This is needed because GCE VMs maintain their own OAuth cache that expires T-5mins , attempting
84+ // This is needed because GCE VMs maintain their own OAuth cache that expires T-4 mins , attempting
8385 // to refresh a token before then, will yield the same stale token. To enable pre-emptive
8486 // refreshes, the margins must be shortened. This shouldn't cause problems since the clock skew
8587 // on the VM and metadata proxy should be non-existent.
8688 static final Duration COMPUTE_EXPIRATION_MARGIN = Duration .ofMinutes (3 );
87- static final Duration COMPUTE_REFRESH_MARGIN = Duration .ofMinutes (4 );
89+ static final Duration COMPUTE_REFRESH_MARGIN = Duration .ofMinutes (3 ). plusSeconds ( 45 );
8890
8991 private static final Logger LOGGER = Logger .getLogger (ComputeEngineCredentials .class .getName ());
9092
@@ -120,34 +122,31 @@ public class ComputeEngineCredentials extends GoogleCredentials
120122 private transient HttpTransportFactory transportFactory ;
121123 private transient String serviceAccountEmail ;
122124
125+ private String universeDomainFromMetadata = null ;
126+
123127 /**
124- * Constructor with overridden transport.
128+ * An internal constructor
125129 *
126- * @param transportFactory HTTP transport factory, creates the transport used to get access
127- * tokens.
128- * @param scopes scope strings for the APIs to be called. May be null or an empty collection.
129- * @param defaultScopes default scope strings for the APIs to be called. May be null or an empty
130- * collection. Default scopes are ignored if scopes are provided.
130+ * @param builder A builder for {@link ComputeEngineCredentials} See {@link
131+ * ComputeEngineCredentials.Builder}
131132 */
132- private ComputeEngineCredentials (
133- HttpTransportFactory transportFactory ,
134- Collection <String > scopes ,
135- Collection <String > defaultScopes ) {
136- super (/* accessToken= */ null , COMPUTE_REFRESH_MARGIN , COMPUTE_EXPIRATION_MARGIN );
133+ private ComputeEngineCredentials (ComputeEngineCredentials .Builder builder ) {
134+ super (builder );
137135
138136 this .transportFactory =
139137 firstNonNull (
140- transportFactory ,
138+ builder . getHttpTransportFactory () ,
141139 getFromServiceLoader (HttpTransportFactory .class , OAuth2Utils .HTTP_TRANSPORT_FACTORY ));
142140 this .transportFactoryClassName = this .transportFactory .getClass ().getName ();
143141 // Use defaultScopes only when scopes don't exist.
144- if (scopes == null || scopes .isEmpty ()) {
145- scopes = defaultScopes ;
142+ Collection <String > scopesToUse = builder .scopes ;
143+ if (scopesToUse == null || scopesToUse .isEmpty ()) {
144+ scopesToUse = builder .getDefaultScopes ();
146145 }
147- if (scopes == null ) {
146+ if (scopesToUse == null ) {
148147 this .scopes = ImmutableSet .<String >of ();
149148 } else {
150- List <String > scopeList = new ArrayList <String >(scopes );
149+ List <String > scopeList = new ArrayList <String >(scopesToUse );
151150 scopeList .removeAll (Arrays .asList ("" , null ));
152151 this .scopes = ImmutableSet .<String >copyOf (scopeList );
153152 }
@@ -156,14 +155,21 @@ private ComputeEngineCredentials(
156155 /** Clones the compute engine account with the specified scopes. */
157156 @ Override
158157 public GoogleCredentials createScoped (Collection <String > newScopes ) {
159- return new ComputeEngineCredentials (this .transportFactory , newScopes , null );
158+ ComputeEngineCredentials .Builder builder =
159+ this .toBuilder ().setHttpTransportFactory (transportFactory ).setScopes (newScopes );
160+ return new ComputeEngineCredentials (builder );
160161 }
161162
162- /** Clones the compute engine account with the specified scopes. */
163+ /** Clones the compute engine account with the specified scopes and default scopes . */
163164 @ Override
164165 public GoogleCredentials createScoped (
165166 Collection <String > newScopes , Collection <String > newDefaultScopes ) {
166- return new ComputeEngineCredentials (this .transportFactory , newScopes , newDefaultScopes );
167+ ComputeEngineCredentials .Builder builder =
168+ ComputeEngineCredentials .newBuilder ()
169+ .setHttpTransportFactory (transportFactory )
170+ .setScopes (newScopes )
171+ .setDefaultScopes (newDefaultScopes );
172+ return new ComputeEngineCredentials (builder );
167173 }
168174
169175 /**
@@ -172,7 +178,7 @@ public GoogleCredentials createScoped(
172178 * @return new ComputeEngineCredentials
173179 */
174180 public static ComputeEngineCredentials create () {
175- return new ComputeEngineCredentials (null , null , null );
181+ return new ComputeEngineCredentials (ComputeEngineCredentials . newBuilder () );
176182 }
177183
178184 public final Collection <String > getScopes () {
@@ -192,6 +198,66 @@ String createTokenUrlWithScopes() {
192198 return tokenUrl .toString ();
193199 }
194200
201+ /**
202+ * Gets the universe domain from the GCE metadata server.
203+ *
204+ * <p>Returns an explicit universe domain if it was provided during credential initialization.
205+ *
206+ * <p>Returns the {@link Credentials#GOOGLE_DEFAULT_UNIVERSE} if universe domain endpoint is not
207+ * found (404) or returns an empty string.
208+ *
209+ * <p>Otherwise, returns universe domain from GCE metadata service.
210+ *
211+ * <p>Any above value is cached for the credential lifetime.
212+ *
213+ * @throws IOException if a call to GCE metadata service was unsuccessful. Check if exception
214+ * implements the {@link Retryable} and {@code isRetryable()} will return true if the
215+ * operation may be retried.
216+ * @return string representing a universe domain in the format some-domain.xyz
217+ */
218+ @ Override
219+ public String getUniverseDomain () throws IOException {
220+ if (isExplicitUniverseDomain ()) {
221+ return super .getUniverseDomain ();
222+ }
223+
224+ synchronized (this ) {
225+ if (this .universeDomainFromMetadata != null ) {
226+ return this .universeDomainFromMetadata ;
227+ }
228+ }
229+
230+ String universeDomainFromMetadata = getUniverseDomainFromMetadata ();
231+ synchronized (this ) {
232+ this .universeDomainFromMetadata = universeDomainFromMetadata ;
233+ }
234+ return universeDomainFromMetadata ;
235+ }
236+
237+ private String getUniverseDomainFromMetadata () throws IOException {
238+ HttpResponse response = getMetadataResponse (getUniverseDomainUrl ());
239+ int statusCode = response .getStatusCode ();
240+ if (statusCode == HttpStatusCodes .STATUS_CODE_NOT_FOUND ) {
241+ return Credentials .GOOGLE_DEFAULT_UNIVERSE ;
242+ }
243+ if (statusCode != HttpStatusCodes .STATUS_CODE_OK ) {
244+ IOException cause =
245+ new IOException (
246+ String .format (
247+ "Unexpected Error code %s trying to get universe domain"
248+ + " from Compute Engine metadata for the default service account: %s" ,
249+ statusCode , response .parseAsString ()));
250+ throw new GoogleAuthException (true , cause );
251+ }
252+ String responseString = response .parseAsString ();
253+
254+ /* Earlier versions of MDS that supports universe_domain return empty string instead of GDU. */
255+ if (responseString .isEmpty ()) {
256+ return Credentials .GOOGLE_DEFAULT_UNIVERSE ;
257+ }
258+ return responseString ;
259+ }
260+
195261 /** Refresh the access token by getting it from the GCE metadata server */
196262 @ Override
197263 public AccessToken refreshAccessToken () throws IOException {
@@ -420,6 +486,11 @@ public static String getTokenServerEncodedUrl() {
420486 return getTokenServerEncodedUrl (DefaultCredentialsProvider .DEFAULT );
421487 }
422488
489+ public static String getUniverseDomainUrl () {
490+ return getMetadataServerUrl (DefaultCredentialsProvider .DEFAULT )
491+ + "/computeMetadata/v1/universe/universe_domain" ;
492+ }
493+
423494 public static String getServiceAccountsUrl () {
424495 return getMetadataServerUrl (DefaultCredentialsProvider .DEFAULT )
425496 + "/computeMetadata/v1/instance/service-accounts/?recursive=true" ;
@@ -436,20 +507,27 @@ public int hashCode() {
436507 }
437508
438509 @ Override
439- public String toString () {
440- return MoreObjects .toStringHelper (this )
441- .add ("transportFactoryClassName" , transportFactoryClassName )
442- .toString ();
510+ protected ToStringHelper toStringHelper () {
511+ synchronized (this ) {
512+ return super .toStringHelper ()
513+ .add ("transportFactoryClassName" , transportFactoryClassName )
514+ .add ("scopes" , scopes )
515+ .add ("universeDomainFromMetadata" , universeDomainFromMetadata );
516+ }
443517 }
444518
445519 @ Override
446520 public boolean equals (Object obj ) {
447521 if (!(obj instanceof ComputeEngineCredentials )) {
448522 return false ;
449523 }
524+ if (!super .equals (obj )) {
525+ return false ;
526+ }
450527 ComputeEngineCredentials other = (ComputeEngineCredentials ) obj ;
451528 return Objects .equals (this .transportFactoryClassName , other .transportFactoryClassName )
452- && Objects .equals (this .scopes , other .scopes );
529+ && Objects .equals (this .scopes , other .scopes )
530+ && Objects .equals (this .universeDomainFromMetadata , other .universeDomainFromMetadata );
453531 }
454532
455533 private void readObject (ObjectInputStream input ) throws IOException , ClassNotFoundException {
@@ -542,10 +620,15 @@ private String getDefaultServiceAccount() throws IOException {
542620 public static class Builder extends GoogleCredentials .Builder {
543621 private HttpTransportFactory transportFactory ;
544622 private Collection <String > scopes ;
623+ private Collection <String > defaultScopes ;
545624
546- protected Builder () {}
625+ protected Builder () {
626+ setRefreshMargin (COMPUTE_REFRESH_MARGIN );
627+ setExpirationMargin (COMPUTE_EXPIRATION_MARGIN );
628+ }
547629
548630 protected Builder (ComputeEngineCredentials credentials ) {
631+ super (credentials );
549632 this .transportFactory = credentials .transportFactory ;
550633 this .scopes = credentials .scopes ;
551634 }
@@ -562,6 +645,24 @@ public Builder setScopes(Collection<String> scopes) {
562645 return this ;
563646 }
564647
648+ @ CanIgnoreReturnValue
649+ public Builder setDefaultScopes (Collection <String > defaultScopes ) {
650+ this .defaultScopes = defaultScopes ;
651+ return this ;
652+ }
653+
654+ @ CanIgnoreReturnValue
655+ public Builder setUniverseDomain (String universeDomain ) {
656+ this .universeDomain = universeDomain ;
657+ return this ;
658+ }
659+
660+ @ CanIgnoreReturnValue
661+ public Builder setQuotaProjectId (String quotaProjectId ) {
662+ super .quotaProjectId = quotaProjectId ;
663+ return this ;
664+ }
665+
565666 public HttpTransportFactory getHttpTransportFactory () {
566667 return transportFactory ;
567668 }
@@ -570,8 +671,12 @@ public Collection<String> getScopes() {
570671 return scopes ;
571672 }
572673
674+ public Collection <String > getDefaultScopes () {
675+ return defaultScopes ;
676+ }
677+
573678 public ComputeEngineCredentials build () {
574- return new ComputeEngineCredentials (transportFactory , scopes , null );
679+ return new ComputeEngineCredentials (this );
575680 }
576681 }
577682}
0 commit comments