5050import com .google .auth .oauth2 .MetricsUtils .RequestType ;
5151import com .google .common .annotations .VisibleForTesting ;
5252import com .google .common .base .MoreObjects ;
53+ import com .google .common .base .Preconditions ;
54+ import com .google .common .collect .ImmutableList ;
5355import com .google .common .collect .ImmutableMap ;
5456import com .google .errorprone .annotations .CanIgnoreReturnValue ;
5557import java .io .IOException ;
5860import java .text .ParseException ;
5961import java .text .SimpleDateFormat ;
6062import java .util .ArrayList ;
61- import java .util .Arrays ;
6263import java .util .Calendar ;
6364import java .util .Collection ;
65+ import java .util .Collections ;
6466import java .util .Date ;
6567import java .util .List ;
6668import java .util .Map ;
@@ -99,14 +101,12 @@ public class ImpersonatedCredentials extends GoogleCredentials
99101 private static final String RFC3339 = "yyyy-MM-dd'T'HH:mm:ssX" ;
100102 private static final int TWELVE_HOURS_IN_SECONDS = 43200 ;
101103 private static final int DEFAULT_LIFETIME_IN_SECONDS = 3600 ;
102- private static final String CLOUD_PLATFORM_SCOPE =
103- "https://www.googleapis.com/auth/cloud-platform" ;
104104 private GoogleCredentials sourceCredentials ;
105- private String targetPrincipal ;
105+ private final String targetPrincipal ;
106106 private List <String > delegates ;
107- private List <String > scopes ;
108- private int lifetime ;
109- private String iamEndpointOverride ;
107+ private final List <String > scopes ;
108+ private final int lifetime ;
109+ private final String iamEndpointOverride ;
110110 private final String transportFactoryClassName ;
111111 private static final LoggerProvider LOGGER_PROVIDER =
112112 LoggerProvider .forClazz (ImpersonatedCredentials .class );
@@ -390,6 +390,10 @@ static ImpersonatedCredentials fromJson(
390390 String quotaProjectId ;
391391 String targetPrincipal ;
392392 String serviceAccountImpersonationUrl ;
393+ // This applies to the scopes applied for the impersonated token and not the
394+ // underlying source credential. Default to empty list to keep the existing
395+ // behavior (when json file did not populate a scopes field).
396+ List <String > scopes = ImmutableList .of ();
393397 try {
394398 serviceAccountImpersonationUrl = (String ) json .get ("service_account_impersonation_url" );
395399 if (json .containsKey ("delegates" )) {
@@ -399,6 +403,9 @@ static ImpersonatedCredentials fromJson(
399403 sourceCredentialsType = (String ) sourceCredentialsJson .get ("type" );
400404 quotaProjectId = (String ) json .get ("quota_project_id" );
401405 targetPrincipal = extractTargetPrincipal (serviceAccountImpersonationUrl );
406+ if (json .containsKey ("scopes" )) {
407+ scopes = ImmutableList .copyOf ((List <String >) json .get ("scopes" ));
408+ }
402409 } catch (ClassCastException | NullPointerException | IllegalArgumentException e ) {
403410 throw new CredentialFormatException ("An invalid input stream was provided." , e );
404411 }
@@ -421,7 +428,7 @@ static ImpersonatedCredentials fromJson(
421428 .setSourceCredentials (sourceCredentials )
422429 .setTargetPrincipal (targetPrincipal )
423430 .setDelegates (delegates )
424- .setScopes (new ArrayList <>() )
431+ .setScopes (scopes )
425432 .setLifetime (DEFAULT_LIFETIME_IN_SECONDS )
426433 .setHttpTransportFactory (transportFactory )
427434 .setQuotaProjectId (quotaProjectId )
@@ -436,7 +443,7 @@ public boolean createScopedRequired() {
436443
437444 @ Override
438445 public GoogleCredentials createScoped (Collection <String > scopes ) {
439- return toBuilder ().setScopes (new ArrayList <> (scopes )).setAccessToken (null ).build ();
446+ return toBuilder ().setScopes (ImmutableList . copyOf (scopes )).setAccessToken (null ).build ();
440447 }
441448
442449 @ Override
@@ -468,7 +475,7 @@ private ImpersonatedCredentials(Builder builder) throws IOException {
468475 this .sourceCredentials = builder .getSourceCredentials ();
469476 this .targetPrincipal = builder .getTargetPrincipal ();
470477 this .delegates = builder .getDelegates ();
471- this .scopes = builder .getScopes ();
478+ this .scopes = ImmutableList . copyOf ( builder .getScopes () );
472479 this .lifetime = builder .getLifetime ();
473480 this .transportFactory =
474481 firstNonNull (
@@ -480,9 +487,6 @@ private ImpersonatedCredentials(Builder builder) throws IOException {
480487 if (this .delegates == null ) {
481488 this .delegates = new ArrayList <>();
482489 }
483- if (this .scopes == null ) {
484- throw new IllegalStateException ("Scopes cannot be null" );
485- }
486490 if (this .lifetime > TWELVE_HOURS_IN_SECONDS ) {
487491 throw new IllegalStateException ("lifetime must be less than or equal to 43200" );
488492 }
@@ -516,8 +520,10 @@ public String getUniverseDomain() throws IOException {
516520 @ Override
517521 public AccessToken refreshAccessToken () throws IOException {
518522 if (this .sourceCredentials .getAccessToken () == null ) {
523+ // Apply the `CLOUD_PLATFORM_SCOPE` to access the iamcredentials endpoint
519524 this .sourceCredentials =
520- this .sourceCredentials .createScoped (Arrays .asList (CLOUD_PLATFORM_SCOPE ));
525+ this .sourceCredentials .createScoped (
526+ Collections .singletonList (OAuth2Utils .CLOUD_PLATFORM_SCOPE ));
521527 }
522528
523529 // skip for SA with SSJ flow because it uses self-signed JWT
@@ -551,7 +557,7 @@ public AccessToken refreshAccessToken() throws IOException {
551557 GenericUrl url = new GenericUrl (endpointUrl );
552558
553559 Map <String , Object > body =
554- ImmutableMap .< String , Object > of (
560+ ImmutableMap .of (
555561 "delegates" , this .delegates , "scope" , this .scopes , "lifetime" , this .lifetime + "s" );
556562
557563 HttpContent requestContent = new JsonHttpContent (parser .getJsonFactory (), body );
@@ -741,12 +747,22 @@ public List<String> getDelegates() {
741747 return this .delegates ;
742748 }
743749
750+ /**
751+ * Set the scopes to be applied on the impersonated token and not on the source credential. This
752+ * user configuration has precedence over the scopes listed in the source credential json file.
753+ *
754+ * @param scopes List of scopes to apply to the impersonated token
755+ */
744756 @ CanIgnoreReturnValue
745757 public Builder setScopes (List <String > scopes ) {
758+ Preconditions .checkNotNull (scopes , "Scopes cannot be null" );
746759 this .scopes = scopes ;
747760 return this ;
748761 }
749762
763+ /**
764+ * @return List of scopes to be applied to the impersonated token.
765+ */
750766 public List <String > getScopes () {
751767 return this .scopes ;
752768 }
0 commit comments