@@ -89,14 +89,19 @@ abstract static class CredentialSource {
89
89
@ Nullable private final String clientId ;
90
90
@ Nullable private final String clientSecret ;
91
91
92
+ // This is used for Workforce Pools. It is passed to STS during token exchange in the
93
+ // `options` param and will be embedded in the token by STS.
94
+ @ Nullable private final String workforcePoolUserProject ;
95
+
92
96
protected transient HttpTransportFactory transportFactory ;
93
97
94
98
@ Nullable protected final ImpersonatedCredentials impersonatedCredentials ;
95
99
96
100
private EnvironmentProvider environmentProvider ;
97
101
98
102
/**
99
- * Constructor with minimum identifying information and custom HTTP transport.
103
+ * Constructor with minimum identifying information and custom HTTP transport. Does not support
104
+ * workforce credentials.
100
105
*
101
106
* @param transportFactory HTTP transport factory, creates the transport used to get access tokens
102
107
* @param audience the STS audience which is usually the fully specified resource name of the
@@ -181,6 +186,49 @@ protected ExternalAccountCredentials(
181
186
(scopes == null || scopes .isEmpty ()) ? Arrays .asList (CLOUD_PLATFORM_SCOPE ) : scopes ;
182
187
this .environmentProvider =
183
188
environmentProvider == null ? SystemEnvironmentProvider .getInstance () : environmentProvider ;
189
+ this .workforcePoolUserProject = null ;
190
+
191
+ validateTokenUrl (tokenUrl );
192
+ if (serviceAccountImpersonationUrl != null ) {
193
+ validateServiceAccountImpersonationInfoUrl (serviceAccountImpersonationUrl );
194
+ }
195
+
196
+ this .impersonatedCredentials = initializeImpersonatedCredentials ();
197
+ }
198
+
199
+ /**
200
+ * Internal constructor with minimum identifying information and custom HTTP transport. See {@link
201
+ * ExternalAccountCredentials.Builder}.
202
+ */
203
+ protected ExternalAccountCredentials (ExternalAccountCredentials .Builder builder ) {
204
+ this .transportFactory =
205
+ MoreObjects .firstNonNull (
206
+ builder .transportFactory ,
207
+ getFromServiceLoader (HttpTransportFactory .class , OAuth2Utils .HTTP_TRANSPORT_FACTORY ));
208
+ this .transportFactoryClassName = checkNotNull (this .transportFactory .getClass ().getName ());
209
+ this .audience = checkNotNull (builder .audience );
210
+ this .subjectTokenType = checkNotNull (builder .subjectTokenType );
211
+ this .tokenUrl = checkNotNull (builder .tokenUrl );
212
+ this .credentialSource = checkNotNull (builder .credentialSource );
213
+ this .tokenInfoUrl = builder .tokenInfoUrl ;
214
+ this .serviceAccountImpersonationUrl = builder .serviceAccountImpersonationUrl ;
215
+ this .quotaProjectId = builder .quotaProjectId ;
216
+ this .clientId = builder .clientId ;
217
+ this .clientSecret = builder .clientSecret ;
218
+ this .scopes =
219
+ (builder .scopes == null || builder .scopes .isEmpty ())
220
+ ? Arrays .asList (CLOUD_PLATFORM_SCOPE )
221
+ : builder .scopes ;
222
+ this .environmentProvider =
223
+ builder .environmentProvider == null
224
+ ? SystemEnvironmentProvider .getInstance ()
225
+ : builder .environmentProvider ;
226
+
227
+ this .workforcePoolUserProject = builder .workforcePoolUserProject ;
228
+ if (workforcePoolUserProject != null && !isWorkforcePoolConfiguration ()) {
229
+ throw new IllegalArgumentException (
230
+ "The workforce_pool_user_project parameter should only be provided for a Workforce Pool configuration." );
231
+ }
184
232
185
233
validateTokenUrl (tokenUrl );
186
234
if (serviceAccountImpersonationUrl != null ) {
@@ -312,23 +360,21 @@ static ExternalAccountCredentials fromJson(
312
360
String userProject = (String ) json .get ("workforce_pool_user_project" );
313
361
314
362
if (isAwsCredential (credentialSourceMap )) {
315
- return new AwsCredentials (
316
- transportFactory ,
317
- audience ,
318
- subjectTokenType ,
319
- tokenUrl ,
320
- new AwsCredentialSource (credentialSourceMap ),
321
- tokenInfoUrl ,
322
- serviceAccountImpersonationUrl ,
323
- quotaProjectId ,
324
- clientId ,
325
- clientSecret ,
326
- /* scopes= */ null ,
327
- /* environmentProvider= */ null );
363
+ return AwsCredentials .newBuilder ()
364
+ .setHttpTransportFactory (transportFactory )
365
+ .setAudience (audience )
366
+ .setSubjectTokenType (subjectTokenType )
367
+ .setTokenUrl (tokenUrl )
368
+ .setTokenInfoUrl (tokenInfoUrl )
369
+ .setCredentialSource (new AwsCredentialSource (credentialSourceMap ))
370
+ .setServiceAccountImpersonationUrl (serviceAccountImpersonationUrl )
371
+ .setQuotaProjectId (quotaProjectId )
372
+ .setClientId (clientId )
373
+ .setClientSecret (clientSecret )
374
+ .build ();
328
375
}
329
376
330
377
return IdentityPoolCredentials .newBuilder ()
331
- .setWorkforcePoolUserProject (userProject )
332
378
.setHttpTransportFactory (transportFactory )
333
379
.setAudience (audience )
334
380
.setSubjectTokenType (subjectTokenType )
@@ -339,6 +385,7 @@ static ExternalAccountCredentials fromJson(
339
385
.setQuotaProjectId (quotaProjectId )
340
386
.setClientId (clientId )
341
387
.setClientSecret (clientSecret )
388
+ .setWorkforcePoolUserProject (userProject )
342
389
.build ();
343
390
}
344
391
@@ -361,13 +408,25 @@ protected AccessToken exchangeExternalCredentialForAccessToken(
361
408
return impersonatedCredentials .refreshAccessToken ();
362
409
}
363
410
364
- StsRequestHandler requestHandler =
411
+ StsRequestHandler . Builder requestHandler =
365
412
StsRequestHandler .newBuilder (
366
- tokenUrl , stsTokenExchangeRequest , transportFactory .create ().createRequestFactory ())
367
- .setInternalOptions (stsTokenExchangeRequest .getInternalOptions ())
368
- .build ();
413
+ tokenUrl , stsTokenExchangeRequest , transportFactory .create ().createRequestFactory ());
414
+
415
+ // If this credential was initialized with a Workforce configuration then the
416
+ // workforcePoolUserProject must passed to STS via the the internal options param.
417
+ if (isWorkforcePoolConfiguration ()) {
418
+ GenericJson options = new GenericJson ();
419
+ options .setFactory (OAuth2Utils .JSON_FACTORY );
420
+ options .put ("userProject" , workforcePoolUserProject );
421
+ requestHandler .setInternalOptions (options .toString ());
422
+ }
423
+
424
+ if (stsTokenExchangeRequest .getInternalOptions () != null ) {
425
+ // Overwrite internal options. Let subclass handle setting options.
426
+ requestHandler .setInternalOptions (stsTokenExchangeRequest .getInternalOptions ());
427
+ }
369
428
370
- StsTokenExchangeResponse response = requestHandler .exchangeToken ();
429
+ StsTokenExchangeResponse response = requestHandler .build (). exchangeToken ();
371
430
return response .getAccessToken ();
372
431
}
373
432
@@ -427,10 +486,26 @@ public Collection<String> getScopes() {
427
486
return scopes ;
428
487
}
429
488
489
+ @ Nullable
490
+ public String getWorkforcePoolUserProject () {
491
+ return workforcePoolUserProject ;
492
+ }
493
+
430
494
EnvironmentProvider getEnvironmentProvider () {
431
495
return environmentProvider ;
432
496
}
433
497
498
+ /**
499
+ * Returns whether or not the current configuration is for Workforce Pools (which enable 3p user
500
+ * identities, rather than workloads).
501
+ */
502
+ public boolean isWorkforcePoolConfiguration () {
503
+ Pattern workforceAudiencePattern =
504
+ Pattern .compile ("^//iam.googleapis.com/locations/.+/workforcePools/.+/providers/.+$" );
505
+ return workforcePoolUserProject != null
506
+ && workforceAudiencePattern .matcher (getAudience ()).matches ();
507
+ }
508
+
434
509
static void validateTokenUrl (String tokenUrl ) {
435
510
List <Pattern > patterns = new ArrayList <>();
436
511
patterns .add (Pattern .compile ("^[^\\ .\\ s\\ /\\ \\ ]+\\ .sts\\ .googleapis\\ .com$" ));
@@ -501,6 +576,7 @@ public abstract static class Builder extends GoogleCredentials.Builder {
501
576
@ Nullable protected String clientId ;
502
577
@ Nullable protected String clientSecret ;
503
578
@ Nullable protected Collection <String > scopes ;
579
+ @ Nullable protected String workforcePoolUserProject ;
504
580
505
581
protected Builder () {}
506
582
@@ -517,60 +593,95 @@ protected Builder(ExternalAccountCredentials credentials) {
517
593
this .clientSecret = credentials .clientSecret ;
518
594
this .scopes = credentials .scopes ;
519
595
this .environmentProvider = credentials .environmentProvider ;
596
+ this .workforcePoolUserProject = credentials .workforcePoolUserProject ;
520
597
}
521
598
599
+ /** Sets the HTTP transport factory, creates the transport used to get access tokens. */
600
+ public Builder setHttpTransportFactory (HttpTransportFactory transportFactory ) {
601
+ this .transportFactory = transportFactory ;
602
+ return this ;
603
+ }
604
+
605
+ /**
606
+ * Sets the STS audience which is usually the fully specified resource name of the
607
+ * workload/workforce pool provider.
608
+ */
522
609
public Builder setAudience (String audience ) {
523
610
this .audience = audience ;
524
611
return this ;
525
612
}
526
613
614
+ /**
615
+ * Sets the STS subject token type based on the OAuth 2.0 token exchange spec. Indicates the
616
+ * type of the security token in the credential file.
617
+ */
527
618
public Builder setSubjectTokenType (String subjectTokenType ) {
528
619
this .subjectTokenType = subjectTokenType ;
529
620
return this ;
530
621
}
531
622
623
+ /** Sets the STS token exchange endpoint. */
532
624
public Builder setTokenUrl (String tokenUrl ) {
533
625
this .tokenUrl = tokenUrl ;
534
626
return this ;
535
627
}
536
628
537
- public Builder setTokenInfoUrl (String tokenInfoUrl ) {
538
- this .tokenInfoUrl = tokenInfoUrl ;
629
+ /** Sets the external credential source. */
630
+ public Builder setCredentialSource (CredentialSource credentialSource ) {
631
+ this .credentialSource = credentialSource ;
539
632
return this ;
540
633
}
541
634
635
+ /**
636
+ * Sets the optional URL used for service account impersonation. This is only required when APIs
637
+ * to be accessed have not integrated with UberMint. If this is not available, the STS returned
638
+ * GCP access token is directly used.
639
+ */
542
640
public Builder setServiceAccountImpersonationUrl (String serviceAccountImpersonationUrl ) {
543
641
this .serviceAccountImpersonationUrl = serviceAccountImpersonationUrl ;
544
642
return this ;
545
643
}
546
644
547
- public Builder setCredentialSource (CredentialSource credentialSource ) {
548
- this .credentialSource = credentialSource ;
549
- return this ;
550
- }
551
-
552
- public Builder setScopes (Collection <String > scopes ) {
553
- this .scopes = scopes ;
645
+ /**
646
+ * Sets the optional endpoint used to retrieve account related information. Required for gCloud
647
+ * session account identification.
648
+ */
649
+ public Builder setTokenInfoUrl (String tokenInfoUrl ) {
650
+ this .tokenInfoUrl = tokenInfoUrl ;
554
651
return this ;
555
652
}
556
653
654
+ /** Sets the optional project used for quota and billing purposes. */
557
655
public Builder setQuotaProjectId (String quotaProjectId ) {
558
656
this .quotaProjectId = quotaProjectId ;
559
657
return this ;
560
658
}
561
659
660
+ /** Sets the optional client ID of the service account from the console. */
562
661
public Builder setClientId (String clientId ) {
563
662
this .clientId = clientId ;
564
663
return this ;
565
664
}
566
665
666
+ /** Sets the optional client secret of the service account from the console. */
567
667
public Builder setClientSecret (String clientSecret ) {
568
668
this .clientSecret = clientSecret ;
569
669
return this ;
570
670
}
571
671
572
- public Builder setHttpTransportFactory (HttpTransportFactory transportFactory ) {
573
- this .transportFactory = transportFactory ;
672
+ /** Sets the optional scopes to request during the authorization grant. */
673
+ public Builder setScopes (Collection <String > scopes ) {
674
+ this .scopes = scopes ;
675
+ return this ;
676
+ }
677
+
678
+ /**
679
+ * Sets the optional workforce pool user project number when the credential corresponds to a
680
+ * workforce pool and not a workload identity pool. The underlying principal must still have
681
+ * serviceusage.services.use IAM permission to use the project for billing/quota.
682
+ */
683
+ public Builder setWorkforcePoolUserProject (String workforcePoolUserProject ) {
684
+ this .workforcePoolUserProject = workforcePoolUserProject ;
574
685
return this ;
575
686
}
576
687
0 commit comments