@@ -105,6 +105,7 @@ public class ImpersonatedCredentials extends GoogleCredentials
105105 private List <String > scopes ;
106106 private int lifetime ;
107107 private String quotaProjectId ;
108+ private String iamEndpointOverride ;
108109 private final String transportFactoryClassName ;
109110
110111 private transient HttpTransportFactory transportFactory ;
@@ -192,6 +193,54 @@ public static ImpersonatedCredentials create(
192193 .build ();
193194 }
194195
196+ /**
197+ * @param sourceCredentials the source credential used to acquire the impersonated credentials. It
198+ * should be either a user account credential or a service account credential.
199+ * @param targetPrincipal the service account to impersonate
200+ * @param delegates the chained list of delegates required to grant the final access_token. If
201+ * set, the sequence of identities must have "Service Account Token Creator" capability
202+ * granted to the preceding identity. For example, if set to [serviceAccountB,
203+ * serviceAccountC], the sourceCredential must have the Token Creator role on serviceAccountB.
204+ * serviceAccountB must have the Token Creator on serviceAccountC. Finally, C must have Token
205+ * Creator on target_principal. If unset, sourceCredential must have that role on
206+ * targetPrincipal.
207+ * @param scopes scopes to request during the authorization grant
208+ * @param lifetime number of seconds the delegated credential should be valid. By default this
209+ * value should be at most 3600. However, you can follow <a
210+ * href='https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials#sa-credentials-oauth'>these
211+ * instructions</a> to set up the service account and extend the maximum lifetime to 43200 (12
212+ * hours). If the given lifetime is 0, default value 3600 will be used instead when creating
213+ * the credentials.
214+ * @param transportFactory HTTP transport factory that creates the transport used to get access
215+ * tokens.
216+ * @param quotaProjectId the project used for quota and billing purposes. Should be null unless
217+ * the caller wants to use a project different from the one that owns the impersonated
218+ * credential for billing/quota purposes.
219+ * @param iamEndpointOverride The full IAM endpoint override with the target_principal embedded.
220+ * This is useful when supporting impersonation with regional endpoints.
221+ * @return new credentials
222+ */
223+ public static ImpersonatedCredentials create (
224+ GoogleCredentials sourceCredentials ,
225+ String targetPrincipal ,
226+ List <String > delegates ,
227+ List <String > scopes ,
228+ int lifetime ,
229+ HttpTransportFactory transportFactory ,
230+ String quotaProjectId ,
231+ String iamEndpointOverride ) {
232+ return ImpersonatedCredentials .newBuilder ()
233+ .setSourceCredentials (sourceCredentials )
234+ .setTargetPrincipal (targetPrincipal )
235+ .setDelegates (delegates )
236+ .setScopes (scopes )
237+ .setLifetime (lifetime )
238+ .setHttpTransportFactory (transportFactory )
239+ .setQuotaProjectId (quotaProjectId )
240+ .setIamEndpointOverride (iamEndpointOverride )
241+ .build ();
242+ }
243+
195244 /**
196245 * @param sourceCredentials the source credential used to acquire the impersonated credentials. It
197246 * should be either a user account credential or a service account credential.
@@ -257,6 +306,11 @@ public String getQuotaProjectId() {
257306 return this .quotaProjectId ;
258307 }
259308
309+ @ VisibleForTesting
310+ String getIamEndpointOverride () {
311+ return this .iamEndpointOverride ;
312+ }
313+
260314 @ VisibleForTesting
261315 List <String > getDelegates () {
262316 return delegates ;
@@ -320,9 +374,9 @@ static ImpersonatedCredentials fromJson(
320374 String sourceCredentialsType ;
321375 String quotaProjectId ;
322376 String targetPrincipal ;
377+ String serviceAccountImpersonationUrl ;
323378 try {
324- String serviceAccountImpersonationUrl =
325- (String ) json .get ("service_account_impersonation_url" );
379+ serviceAccountImpersonationUrl = (String ) json .get ("service_account_impersonation_url" );
326380 if (json .containsKey ("delegates" )) {
327381 delegates = (List <String >) json .get ("delegates" );
328382 }
@@ -354,6 +408,7 @@ static ImpersonatedCredentials fromJson(
354408 .setLifetime (DEFAULT_LIFETIME_IN_SECONDS )
355409 .setHttpTransportFactory (transportFactory )
356410 .setQuotaProjectId (quotaProjectId )
411+ .setIamEndpointOverride (serviceAccountImpersonationUrl )
357412 .build ();
358413 }
359414
@@ -370,6 +425,7 @@ public GoogleCredentials createScoped(Collection<String> scopes) {
370425 .setDelegates (this .delegates )
371426 .setHttpTransportFactory (this .transportFactory )
372427 .setQuotaProjectId (this .quotaProjectId )
428+ .setIamEndpointOverride (this .iamEndpointOverride )
373429 .build ();
374430 }
375431
@@ -393,6 +449,7 @@ private ImpersonatedCredentials(Builder builder) {
393449 builder .getHttpTransportFactory (),
394450 getFromServiceLoader (HttpTransportFactory .class , OAuth2Utils .HTTP_TRANSPORT_FACTORY ));
395451 this .quotaProjectId = builder .quotaProjectId ;
452+ this .iamEndpointOverride = builder .iamEndpointOverride ;
396453 this .transportFactoryClassName = this .transportFactory .getClass ().getName ();
397454 if (this .delegates == null ) {
398455 this .delegates = new ArrayList <String >();
@@ -424,7 +481,10 @@ public AccessToken refreshAccessToken() throws IOException {
424481 HttpCredentialsAdapter adapter = new HttpCredentialsAdapter (sourceCredentials );
425482 HttpRequestFactory requestFactory = httpTransport .createRequestFactory ();
426483
427- String endpointUrl = String .format (IAM_ACCESS_TOKEN_ENDPOINT , this .targetPrincipal );
484+ String endpointUrl =
485+ this .iamEndpointOverride != null
486+ ? this .iamEndpointOverride
487+ : String .format (IAM_ACCESS_TOKEN_ENDPOINT , this .targetPrincipal );
428488 GenericUrl url = new GenericUrl (endpointUrl );
429489
430490 Map <String , Object > body =
@@ -489,7 +549,13 @@ public IdToken idTokenWithAudience(String targetAudience, List<IdTokenProvider.O
489549 @ Override
490550 public int hashCode () {
491551 return Objects .hash (
492- sourceCredentials , targetPrincipal , delegates , scopes , lifetime , quotaProjectId );
552+ sourceCredentials ,
553+ targetPrincipal ,
554+ delegates ,
555+ scopes ,
556+ lifetime ,
557+ quotaProjectId ,
558+ iamEndpointOverride );
493559 }
494560
495561 @ Override
@@ -502,6 +568,7 @@ public String toString() {
502568 .add ("lifetime" , lifetime )
503569 .add ("transportFactoryClassName" , transportFactoryClassName )
504570 .add ("quotaProjectId" , quotaProjectId )
571+ .add ("iamEndpointOverride" , iamEndpointOverride )
505572 .toString ();
506573 }
507574
@@ -517,7 +584,8 @@ public boolean equals(Object obj) {
517584 && Objects .equals (this .scopes , other .scopes )
518585 && Objects .equals (this .lifetime , other .lifetime )
519586 && Objects .equals (this .transportFactoryClassName , other .transportFactoryClassName )
520- && Objects .equals (this .quotaProjectId , other .quotaProjectId );
587+ && Objects .equals (this .quotaProjectId , other .quotaProjectId )
588+ && Objects .equals (this .iamEndpointOverride , other .iamEndpointOverride );
521589 }
522590
523591 public Builder toBuilder () {
@@ -537,6 +605,7 @@ public static class Builder extends GoogleCredentials.Builder {
537605 private int lifetime = DEFAULT_LIFETIME_IN_SECONDS ;
538606 private HttpTransportFactory transportFactory ;
539607 private String quotaProjectId ;
608+ private String iamEndpointOverride ;
540609
541610 protected Builder () {}
542611
@@ -604,6 +673,11 @@ public Builder setQuotaProjectId(String quotaProjectId) {
604673 return this ;
605674 }
606675
676+ public Builder setIamEndpointOverride (String iamEndpointOverride ) {
677+ this .iamEndpointOverride = iamEndpointOverride ;
678+ return this ;
679+ }
680+
607681 public ImpersonatedCredentials build () {
608682 return new ImpersonatedCredentials (this );
609683 }
0 commit comments