Skip to content

Commit 84fc566

Browse files
authored
feat: Support user defined or json defined scopes for impersonated token (#1815)
* feat: Support user defined or json defined scopes for impersonated token * chore: Remove wildcard imports * chore: Clean up PR * chore: Create CLOUD_PLATFORM_SCOPE constant in Oauth2Utils * chore: Fix constant usage in tests * chore: Fix lint issues
1 parent 2abedcf commit 84fc566

13 files changed

+134
-76
lines changed

oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@
4848
import java.net.URI;
4949
import java.nio.charset.StandardCharsets;
5050
import java.util.ArrayList;
51-
import java.util.Arrays;
5251
import java.util.Collection;
52+
import java.util.Collections;
5353
import java.util.HashMap;
5454
import java.util.List;
5555
import java.util.Locale;
@@ -68,9 +68,6 @@ public abstract class ExternalAccountCredentials extends GoogleCredentials {
6868

6969
private static final long serialVersionUID = 8049126194174465023L;
7070

71-
private static final String CLOUD_PLATFORM_SCOPE =
72-
"https://www.googleapis.com/auth/cloud-platform";
73-
7471
static final String EXECUTABLE_SOURCE_KEY = "executable";
7572

7673
static final String DEFAULT_TOKEN_URL = "https://sts.{UNIVERSE_DOMAIN}/v1/token";
@@ -200,7 +197,9 @@ protected ExternalAccountCredentials(
200197
this.clientId = clientId;
201198
this.clientSecret = clientSecret;
202199
this.scopes =
203-
(scopes == null || scopes.isEmpty()) ? Arrays.asList(CLOUD_PLATFORM_SCOPE) : scopes;
200+
(scopes == null || scopes.isEmpty())
201+
? Collections.singletonList(OAuth2Utils.CLOUD_PLATFORM_SCOPE)
202+
: scopes;
204203
this.environmentProvider =
205204
environmentProvider == null ? SystemEnvironmentProvider.getInstance() : environmentProvider;
206205
this.workforcePoolUserProject = null;
@@ -245,7 +244,7 @@ protected ExternalAccountCredentials(ExternalAccountCredentials.Builder builder)
245244

246245
this.scopes =
247246
(builder.scopes == null || builder.scopes.isEmpty())
248-
? Arrays.asList(CLOUD_PLATFORM_SCOPE)
247+
? Collections.singletonList(OAuth2Utils.CLOUD_PLATFORM_SCOPE)
249248
: builder.scopes;
250249
this.environmentProvider =
251250
builder.environmentProvider == null

oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050
import com.google.auth.oauth2.MetricsUtils.RequestType;
5151
import com.google.common.annotations.VisibleForTesting;
5252
import com.google.common.base.MoreObjects;
53+
import com.google.common.base.Preconditions;
54+
import com.google.common.collect.ImmutableList;
5355
import com.google.common.collect.ImmutableMap;
5456
import com.google.errorprone.annotations.CanIgnoreReturnValue;
5557
import java.io.IOException;
@@ -58,9 +60,9 @@
5860
import java.text.ParseException;
5961
import java.text.SimpleDateFormat;
6062
import java.util.ArrayList;
61-
import java.util.Arrays;
6263
import java.util.Calendar;
6364
import java.util.Collection;
65+
import java.util.Collections;
6466
import java.util.Date;
6567
import java.util.List;
6668
import 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
}

oauth2_http/java/com/google/auth/oauth2/OAuth2Utils.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ public class OAuth2Utils {
9595
static final URI TOKEN_REVOKE_URI = URI.create("https://oauth2.googleapis.com/revoke");
9696
static final URI USER_AUTH_URI = URI.create("https://accounts.google.com/o/oauth2/auth");
9797

98+
public static final String CLOUD_PLATFORM_SCOPE =
99+
"https://www.googleapis.com/auth/cloud-platform";
100+
98101
static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
99102

100103
public static final HttpTransportFactory HTTP_TRANSPORT_FACTORY =

oauth2_http/javatests/com/google/auth/oauth2/DownscopedCredentialsTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ public void builder_noTransport_defaults() throws IOException {
236236
.build();
237237

238238
GoogleCredentials scopedSourceCredentials =
239-
sourceCredentials.createScoped("https://www.googleapis.com/auth/cloud-platform");
239+
sourceCredentials.createScoped(OAuth2Utils.CLOUD_PLATFORM_SCOPE);
240240
assertEquals(scopedSourceCredentials, credentials.getSourceCredentials());
241241
assertEquals(CREDENTIAL_ACCESS_BOUNDARY, credentials.getCredentialAccessBoundary());
242242
assertEquals(OAuth2Utils.HTTP_TRANSPORT_FACTORY, credentials.getTransportFactory());
@@ -254,7 +254,7 @@ public void builder_noUniverseDomain_defaults() throws IOException {
254254
.build();
255255

256256
GoogleCredentials scopedSourceCredentials =
257-
sourceCredentials.createScoped("https://www.googleapis.com/auth/cloud-platform");
257+
sourceCredentials.createScoped(OAuth2Utils.CLOUD_PLATFORM_SCOPE);
258258
assertEquals(OAuth2Utils.HTTP_TRANSPORT_FACTORY, credentials.getTransportFactory());
259259
assertEquals(scopedSourceCredentials, credentials.getSourceCredentials());
260260
assertEquals(CREDENTIAL_ACCESS_BOUNDARY, credentials.getCredentialAccessBoundary());
@@ -320,7 +320,7 @@ private static GoogleCredentials getServiceAccountSourceCredentials(boolean canR
320320
transportFactory.transport.setError(new IOException());
321321
}
322322

323-
return sourceCredentials.createScoped("https://www.googleapis.com/auth/cloud-platform");
323+
return sourceCredentials.createScoped(OAuth2Utils.CLOUD_PLATFORM_SCOPE);
324324
}
325325

326326
private static GoogleCredentials getUserSourceCredentials() {

oauth2_http/javatests/com/google/auth/oauth2/GoogleCredentialsTest.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,8 @@ public void fromStream_Impersonation_providesToken_WithQuotaProject() throws IOE
619619
ImpersonatedCredentialsTest.writeImpersonationCredentialsStream(
620620
ImpersonatedCredentialsTest.IMPERSONATION_OVERRIDE_URL,
621621
ImpersonatedCredentialsTest.DELEGATES,
622-
ImpersonatedCredentialsTest.QUOTA_PROJECT_ID);
622+
ImpersonatedCredentialsTest.QUOTA_PROJECT_ID,
623+
ImpersonatedCredentialsTest.IMMUTABLE_SCOPES_LIST);
623624

624625
ImpersonatedCredentials credentials =
625626
(ImpersonatedCredentials)
@@ -649,7 +650,8 @@ public void fromStream_Impersonation_defaultUniverse() throws IOException {
649650
ImpersonatedCredentialsTest.writeImpersonationCredentialsStream(
650651
ImpersonatedCredentialsTest.IMPERSONATION_OVERRIDE_URL,
651652
ImpersonatedCredentialsTest.DELEGATES,
652-
ImpersonatedCredentialsTest.QUOTA_PROJECT_ID);
653+
ImpersonatedCredentialsTest.QUOTA_PROJECT_ID,
654+
ImpersonatedCredentialsTest.IMMUTABLE_SCOPES_LIST);
653655

654656
ImpersonatedCredentials credentials =
655657
(ImpersonatedCredentials)
@@ -684,7 +686,8 @@ public void fromStream_Impersonation_providesToken_WithoutQuotaProject() throws
684686
ImpersonatedCredentialsTest.writeImpersonationCredentialsStream(
685687
ImpersonatedCredentialsTest.IMPERSONATION_OVERRIDE_URL,
686688
ImpersonatedCredentialsTest.DELEGATES,
687-
null);
689+
null,
690+
ImpersonatedCredentialsTest.IMMUTABLE_SCOPES_LIST);
688691

689692
ImpersonatedCredentials credentials =
690693
(ImpersonatedCredentials)
@@ -916,7 +919,8 @@ public void getCredentialInfo_impersonatedServiceAccount() throws IOException {
916919
ImpersonatedCredentialsTest.writeImpersonationCredentialsStream(
917920
ImpersonatedCredentialsTest.IMPERSONATION_OVERRIDE_URL,
918921
ImpersonatedCredentialsTest.DELEGATES,
919-
null);
922+
null,
923+
ImpersonatedCredentialsTest.IMMUTABLE_SCOPES_LIST);
920924

921925
ImpersonatedCredentials credentials =
922926
(ImpersonatedCredentials) GoogleCredentials.fromStream(impersonationCredentialsStream);

oauth2_http/javatests/com/google/auth/oauth2/ITDownscopingTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ public AccessToken refreshAccessToken() throws IOException {
102102
ServiceAccountCredentials credentials =
103103
(ServiceAccountCredentials)
104104
GoogleCredentials.getApplicationDefault()
105-
.createScoped("https://www.googleapis.com/auth/cloud-platform");
105+
.createScoped(OAuth2Utils.CLOUD_PLATFORM_SCOPE);
106106

107107
DownscopedCredentials downscopedCredentials =
108108
DownscopedCredentials.newBuilder()

oauth2_http/javatests/com/google/auth/oauth2/ITWorkloadIdentityFederationTest.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -408,8 +408,7 @@ private void callGcs(GoogleCredentials credentials) throws IOException {
408408
*/
409409
private String generateGoogleIdToken(String audience) throws IOException {
410410
GoogleCredentials googleCredentials =
411-
GoogleCredentials.getApplicationDefault()
412-
.createScoped("https://www.googleapis.com/auth/cloud-platform");
411+
GoogleCredentials.getApplicationDefault().createScoped(OAuth2Utils.CLOUD_PLATFORM_SCOPE);
413412

414413
HttpCredentialsAdapter credentialsAdapter = new HttpCredentialsAdapter(googleCredentials);
415414
HttpRequestFactory requestFactory =

0 commit comments

Comments
 (0)