@@ -89,14 +89,19 @@ abstract static class CredentialSource {
8989 @ Nullable private final String clientId ;
9090 @ Nullable private final String clientSecret ;
9191
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+
9296 protected transient HttpTransportFactory transportFactory ;
9397
9498 @ Nullable protected final ImpersonatedCredentials impersonatedCredentials ;
9599
96100 private EnvironmentProvider environmentProvider ;
97101
98102 /**
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.
100105 *
101106 * @param transportFactory HTTP transport factory, creates the transport used to get access tokens
102107 * @param audience the STS audience which is usually the fully specified resource name of the
@@ -181,6 +186,49 @@ protected ExternalAccountCredentials(
181186 (scopes == null || scopes .isEmpty ()) ? Arrays .asList (CLOUD_PLATFORM_SCOPE ) : scopes ;
182187 this .environmentProvider =
183188 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+ }
184232
185233 validateTokenUrl (tokenUrl );
186234 if (serviceAccountImpersonationUrl != null ) {
@@ -312,23 +360,21 @@ static ExternalAccountCredentials fromJson(
312360 String userProject = (String ) json .get ("workforce_pool_user_project" );
313361
314362 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 ();
328375 }
329376
330377 return IdentityPoolCredentials .newBuilder ()
331- .setWorkforcePoolUserProject (userProject )
332378 .setHttpTransportFactory (transportFactory )
333379 .setAudience (audience )
334380 .setSubjectTokenType (subjectTokenType )
@@ -339,6 +385,7 @@ static ExternalAccountCredentials fromJson(
339385 .setQuotaProjectId (quotaProjectId )
340386 .setClientId (clientId )
341387 .setClientSecret (clientSecret )
388+ .setWorkforcePoolUserProject (userProject )
342389 .build ();
343390 }
344391
@@ -361,13 +408,25 @@ protected AccessToken exchangeExternalCredentialForAccessToken(
361408 return impersonatedCredentials .refreshAccessToken ();
362409 }
363410
364- StsRequestHandler requestHandler =
411+ StsRequestHandler . Builder requestHandler =
365412 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+ }
369428
370- StsTokenExchangeResponse response = requestHandler .exchangeToken ();
429+ StsTokenExchangeResponse response = requestHandler .build (). exchangeToken ();
371430 return response .getAccessToken ();
372431 }
373432
@@ -427,10 +486,26 @@ public Collection<String> getScopes() {
427486 return scopes ;
428487 }
429488
489+ @ Nullable
490+ public String getWorkforcePoolUserProject () {
491+ return workforcePoolUserProject ;
492+ }
493+
430494 EnvironmentProvider getEnvironmentProvider () {
431495 return environmentProvider ;
432496 }
433497
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+
434509 static void validateTokenUrl (String tokenUrl ) {
435510 List <Pattern > patterns = new ArrayList <>();
436511 patterns .add (Pattern .compile ("^[^\\ .\\ s\\ /\\ \\ ]+\\ .sts\\ .googleapis\\ .com$" ));
@@ -501,6 +576,7 @@ public abstract static class Builder extends GoogleCredentials.Builder {
501576 @ Nullable protected String clientId ;
502577 @ Nullable protected String clientSecret ;
503578 @ Nullable protected Collection <String > scopes ;
579+ @ Nullable protected String workforcePoolUserProject ;
504580
505581 protected Builder () {}
506582
@@ -517,60 +593,95 @@ protected Builder(ExternalAccountCredentials credentials) {
517593 this .clientSecret = credentials .clientSecret ;
518594 this .scopes = credentials .scopes ;
519595 this .environmentProvider = credentials .environmentProvider ;
596+ this .workforcePoolUserProject = credentials .workforcePoolUserProject ;
520597 }
521598
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+ */
522609 public Builder setAudience (String audience ) {
523610 this .audience = audience ;
524611 return this ;
525612 }
526613
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+ */
527618 public Builder setSubjectTokenType (String subjectTokenType ) {
528619 this .subjectTokenType = subjectTokenType ;
529620 return this ;
530621 }
531622
623+ /** Sets the STS token exchange endpoint. */
532624 public Builder setTokenUrl (String tokenUrl ) {
533625 this .tokenUrl = tokenUrl ;
534626 return this ;
535627 }
536628
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 ;
539632 return this ;
540633 }
541634
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+ */
542640 public Builder setServiceAccountImpersonationUrl (String serviceAccountImpersonationUrl ) {
543641 this .serviceAccountImpersonationUrl = serviceAccountImpersonationUrl ;
544642 return this ;
545643 }
546644
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 ;
554651 return this ;
555652 }
556653
654+ /** Sets the optional project used for quota and billing purposes. */
557655 public Builder setQuotaProjectId (String quotaProjectId ) {
558656 this .quotaProjectId = quotaProjectId ;
559657 return this ;
560658 }
561659
660+ /** Sets the optional client ID of the service account from the console. */
562661 public Builder setClientId (String clientId ) {
563662 this .clientId = clientId ;
564663 return this ;
565664 }
566665
666+ /** Sets the optional client secret of the service account from the console. */
567667 public Builder setClientSecret (String clientSecret ) {
568668 this .clientSecret = clientSecret ;
569669 return this ;
570670 }
571671
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 ;
574685 return this ;
575686 }
576687
0 commit comments