@@ -105,6 +105,7 @@ public class ImpersonatedCredentials extends GoogleCredentials
105
105
private List <String > scopes ;
106
106
private int lifetime ;
107
107
private String quotaProjectId ;
108
+ private String iamEndpointOverride ;
108
109
private final String transportFactoryClassName ;
109
110
110
111
private transient HttpTransportFactory transportFactory ;
@@ -192,6 +193,54 @@ public static ImpersonatedCredentials create(
192
193
.build ();
193
194
}
194
195
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
+
195
244
/**
196
245
* @param sourceCredentials the source credential used to acquire the impersonated credentials. It
197
246
* should be either a user account credential or a service account credential.
@@ -257,6 +306,11 @@ public String getQuotaProjectId() {
257
306
return this .quotaProjectId ;
258
307
}
259
308
309
+ @ VisibleForTesting
310
+ String getIamEndpointOverride () {
311
+ return this .iamEndpointOverride ;
312
+ }
313
+
260
314
@ VisibleForTesting
261
315
List <String > getDelegates () {
262
316
return delegates ;
@@ -320,9 +374,9 @@ static ImpersonatedCredentials fromJson(
320
374
String sourceCredentialsType ;
321
375
String quotaProjectId ;
322
376
String targetPrincipal ;
377
+ String serviceAccountImpersonationUrl ;
323
378
try {
324
- String serviceAccountImpersonationUrl =
325
- (String ) json .get ("service_account_impersonation_url" );
379
+ serviceAccountImpersonationUrl = (String ) json .get ("service_account_impersonation_url" );
326
380
if (json .containsKey ("delegates" )) {
327
381
delegates = (List <String >) json .get ("delegates" );
328
382
}
@@ -354,6 +408,7 @@ static ImpersonatedCredentials fromJson(
354
408
.setLifetime (DEFAULT_LIFETIME_IN_SECONDS )
355
409
.setHttpTransportFactory (transportFactory )
356
410
.setQuotaProjectId (quotaProjectId )
411
+ .setIamEndpointOverride (serviceAccountImpersonationUrl )
357
412
.build ();
358
413
}
359
414
@@ -370,6 +425,7 @@ public GoogleCredentials createScoped(Collection<String> scopes) {
370
425
.setDelegates (this .delegates )
371
426
.setHttpTransportFactory (this .transportFactory )
372
427
.setQuotaProjectId (this .quotaProjectId )
428
+ .setIamEndpointOverride (this .iamEndpointOverride )
373
429
.build ();
374
430
}
375
431
@@ -393,6 +449,7 @@ private ImpersonatedCredentials(Builder builder) {
393
449
builder .getHttpTransportFactory (),
394
450
getFromServiceLoader (HttpTransportFactory .class , OAuth2Utils .HTTP_TRANSPORT_FACTORY ));
395
451
this .quotaProjectId = builder .quotaProjectId ;
452
+ this .iamEndpointOverride = builder .iamEndpointOverride ;
396
453
this .transportFactoryClassName = this .transportFactory .getClass ().getName ();
397
454
if (this .delegates == null ) {
398
455
this .delegates = new ArrayList <String >();
@@ -424,7 +481,10 @@ public AccessToken refreshAccessToken() throws IOException {
424
481
HttpCredentialsAdapter adapter = new HttpCredentialsAdapter (sourceCredentials );
425
482
HttpRequestFactory requestFactory = httpTransport .createRequestFactory ();
426
483
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 );
428
488
GenericUrl url = new GenericUrl (endpointUrl );
429
489
430
490
Map <String , Object > body =
@@ -489,7 +549,13 @@ public IdToken idTokenWithAudience(String targetAudience, List<IdTokenProvider.O
489
549
@ Override
490
550
public int hashCode () {
491
551
return Objects .hash (
492
- sourceCredentials , targetPrincipal , delegates , scopes , lifetime , quotaProjectId );
552
+ sourceCredentials ,
553
+ targetPrincipal ,
554
+ delegates ,
555
+ scopes ,
556
+ lifetime ,
557
+ quotaProjectId ,
558
+ iamEndpointOverride );
493
559
}
494
560
495
561
@ Override
@@ -502,6 +568,7 @@ public String toString() {
502
568
.add ("lifetime" , lifetime )
503
569
.add ("transportFactoryClassName" , transportFactoryClassName )
504
570
.add ("quotaProjectId" , quotaProjectId )
571
+ .add ("iamEndpointOverride" , iamEndpointOverride )
505
572
.toString ();
506
573
}
507
574
@@ -517,7 +584,8 @@ public boolean equals(Object obj) {
517
584
&& Objects .equals (this .scopes , other .scopes )
518
585
&& Objects .equals (this .lifetime , other .lifetime )
519
586
&& 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 );
521
589
}
522
590
523
591
public Builder toBuilder () {
@@ -537,6 +605,7 @@ public static class Builder extends GoogleCredentials.Builder {
537
605
private int lifetime = DEFAULT_LIFETIME_IN_SECONDS ;
538
606
private HttpTransportFactory transportFactory ;
539
607
private String quotaProjectId ;
608
+ private String iamEndpointOverride ;
540
609
541
610
protected Builder () {}
542
611
@@ -604,6 +673,11 @@ public Builder setQuotaProjectId(String quotaProjectId) {
604
673
return this ;
605
674
}
606
675
676
+ public Builder setIamEndpointOverride (String iamEndpointOverride ) {
677
+ this .iamEndpointOverride = iamEndpointOverride ;
678
+ return this ;
679
+ }
680
+
607
681
public ImpersonatedCredentials build () {
608
682
return new ImpersonatedCredentials (this );
609
683
}
0 commit comments