Skip to content

Commit a092203

Browse files
committed
fix(oauth2_http): scope sourceCredentials in ImpersonatedCredentials constructor
1 parent 48e5ce5 commit a092203

2 files changed

Lines changed: 96 additions & 8 deletions

File tree

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

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ public class ImpersonatedCredentials extends GoogleCredentials
106106
private static final long serialVersionUID = -2133257318957488431L;
107107
private static final int TWELVE_HOURS_IN_SECONDS = 43200;
108108
private static final int DEFAULT_LIFETIME_IN_SECONDS = 3600;
109-
private GoogleCredentials sourceCredentials;
109+
private final GoogleCredentials sourceCredentials;
110110
private final String targetPrincipal;
111111
private List<String> delegates;
112112
private final List<String> scopes;
@@ -533,7 +533,18 @@ public ImpersonatedCredentials createWithCustomCalendar(Calendar calendar) {
533533

534534
private ImpersonatedCredentials(Builder builder) throws IOException {
535535
super(builder);
536-
this.sourceCredentials = builder.getSourceCredentials();
536+
GoogleCredentials sourceCredentials = builder.getSourceCredentials();
537+
if (sourceCredentials != null
538+
&& !builder.sourceCredentialsScoped
539+
&& sourceCredentials.getAccessToken() == null) {
540+
// Apply the `CLOUD_PLATFORM_SCOPE` to access the iamcredentials endpoint
541+
sourceCredentials =
542+
firstNonNull(
543+
sourceCredentials.createScoped(
544+
Collections.singletonList(OAuth2Utils.CLOUD_PLATFORM_SCOPE)),
545+
sourceCredentials);
546+
}
547+
this.sourceCredentials = sourceCredentials;
537548
this.targetPrincipal = builder.getTargetPrincipal();
538549
this.delegates = builder.getDelegates();
539550
this.scopes = ImmutableList.copyOf(builder.getScopes());
@@ -580,12 +591,6 @@ public String getUniverseDomain() throws IOException {
580591

581592
@Override
582593
public AccessToken refreshAccessToken() throws IOException {
583-
if (this.sourceCredentials.getAccessToken() == null) {
584-
// Apply the `CLOUD_PLATFORM_SCOPE` to access the iamcredentials endpoint
585-
this.sourceCredentials =
586-
this.sourceCredentials.createScoped(
587-
Collections.singletonList(OAuth2Utils.CLOUD_PLATFORM_SCOPE));
588-
}
589594

590595
// skip for SA with SSJ flow because it uses self-signed JWT
591596
// and will get refreshed at initialize request step
@@ -764,6 +769,7 @@ public static Builder newBuilder() {
764769
public static class Builder extends GoogleCredentials.Builder {
765770

766771
private @Nullable GoogleCredentials sourceCredentials;
772+
private boolean sourceCredentialsScoped;
767773
private @Nullable String targetPrincipal;
768774
private @Nullable List<String> delegates;
769775
private @Nullable List<String> scopes;
@@ -789,6 +795,7 @@ protected Builder(GoogleCredentials sourceCredentials, String targetPrincipal) {
789795
protected Builder(ImpersonatedCredentials credentials) {
790796
super(credentials);
791797
this.sourceCredentials = credentials.sourceCredentials;
798+
this.sourceCredentialsScoped = true;
792799
this.targetPrincipal = credentials.targetPrincipal;
793800
this.delegates = credentials.delegates;
794801
this.scopes = credentials.scopes;
@@ -800,6 +807,7 @@ protected Builder(ImpersonatedCredentials credentials) {
800807
@CanIgnoreReturnValue
801808
public Builder setSourceCredentials(GoogleCredentials sourceCredentials) {
802809
this.sourceCredentials = sourceCredentials;
810+
this.sourceCredentialsScoped = false;
803811
return this;
804812
}
805813

google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,19 +47,23 @@
4747
import com.google.api.client.http.HttpStatusCodes;
4848
import com.google.api.client.http.HttpTransport;
4949
import com.google.api.client.json.GenericJson;
50+
import com.google.api.client.json.Json;
5051
import com.google.api.client.json.JsonFactory;
5152
import com.google.api.client.json.JsonGenerator;
5253
import com.google.api.client.json.JsonParser;
5354
import com.google.api.client.json.gson.GsonFactory;
5455
import com.google.api.client.json.webtoken.JsonWebToken.Payload;
56+
import com.google.api.client.testing.http.MockHttpTransport;
5557
import com.google.api.client.testing.http.MockLowLevelHttpRequest;
58+
import com.google.api.client.testing.http.MockLowLevelHttpResponse;
5659
import com.google.api.client.util.Clock;
5760
import com.google.auth.Credentials;
5861
import com.google.auth.ServiceAccountSigner.SigningException;
5962
import com.google.auth.TestUtils;
6063
import com.google.auth.http.HttpTransportFactory;
6164
import com.google.common.collect.ImmutableList;
6265
import com.google.common.collect.ImmutableSet;
66+
import com.google.common.util.concurrent.Uninterruptibles;
6367
import java.io.ByteArrayOutputStream;
6468
import java.io.IOException;
6569
import java.io.InputStream;
@@ -70,9 +74,16 @@
7074
import java.util.ArrayList;
7175
import java.util.Arrays;
7276
import java.util.Calendar;
77+
import java.util.Collection;
7378
import java.util.Date;
7479
import java.util.List;
7580
import java.util.Map;
81+
import java.util.concurrent.CyclicBarrier;
82+
import java.util.concurrent.ExecutorService;
83+
import java.util.concurrent.Executors;
84+
import java.util.concurrent.Future;
85+
import java.util.concurrent.TimeUnit;
86+
import java.util.concurrent.atomic.AtomicInteger;
7687
import org.junit.jupiter.api.BeforeEach;
7788
import org.junit.jupiter.api.Test;
7889

@@ -1262,6 +1273,75 @@ void refreshAccessToken_afterSerialization_success() throws IOException, ClassNo
12621273
assertEquals(ACCESS_TOKEN, token.getTokenValue());
12631274
}
12641275

1276+
@Test
1277+
void refreshAccessToken_concurrentColdStart_scopesAndRefreshesSourceCredentialsOnce()
1278+
throws Exception {
1279+
int numThreads = 16;
1280+
AtomicInteger createScopedCount = new AtomicInteger(0);
1281+
AtomicInteger sourceRefreshCount = new AtomicInteger(0);
1282+
1283+
GoogleCredentials coldSourceCredentials =
1284+
new GoogleCredentials() {
1285+
@Override
1286+
public GoogleCredentials createScoped(Collection<String> scopes) {
1287+
createScopedCount.incrementAndGet();
1288+
return new GoogleCredentials() {
1289+
@Override
1290+
public AccessToken refreshAccessToken() {
1291+
sourceRefreshCount.incrementAndGet();
1292+
Uninterruptibles.sleepUninterruptibly(50, TimeUnit.MILLISECONDS);
1293+
return new AccessToken(
1294+
"source-token", new Date(System.currentTimeMillis() + 3600_000L));
1295+
}
1296+
};
1297+
}
1298+
};
1299+
1300+
ImpersonatedCredentials impersonatedCredentials =
1301+
(ImpersonatedCredentials)
1302+
ImpersonatedCredentials.create(
1303+
coldSourceCredentials,
1304+
IMPERSONATED_CLIENT_EMAIL,
1305+
null,
1306+
ImmutableList.of(),
1307+
VALID_LIFETIME,
1308+
() ->
1309+
new MockHttpTransport.Builder()
1310+
.setLowLevelHttpResponse(
1311+
new MockLowLevelHttpResponse()
1312+
.setContentType(Json.MEDIA_TYPE)
1313+
.setContent(
1314+
String.format(
1315+
"{\"accessToken\":\"%s\",\"expireTime\":\"%s\"}",
1316+
ACCESS_TOKEN, getDefaultExpireTime())))
1317+
.build())
1318+
.createScoped(IMMUTABLE_SCOPES_LIST);
1319+
1320+
assertEquals(1, createScopedCount.get());
1321+
1322+
CyclicBarrier barrier = new CyclicBarrier(numThreads);
1323+
ExecutorService executor = Executors.newFixedThreadPool(numThreads);
1324+
try {
1325+
List<Future<AccessToken>> futures = new ArrayList<>(numThreads);
1326+
for (int i = 0; i < numThreads; i++) {
1327+
futures.add(
1328+
executor.submit(
1329+
() -> {
1330+
barrier.await(5, TimeUnit.SECONDS);
1331+
return impersonatedCredentials.refreshAccessToken();
1332+
}));
1333+
}
1334+
for (Future<AccessToken> future : futures) {
1335+
assertEquals(ACCESS_TOKEN, future.get(10, TimeUnit.SECONDS).getTokenValue());
1336+
}
1337+
} finally {
1338+
executor.shutdownNow();
1339+
}
1340+
1341+
assertEquals(1, createScopedCount.get());
1342+
assertEquals(1, sourceRefreshCount.get());
1343+
}
1344+
12651345
public static String getDefaultExpireTime() {
12661346
return Instant.now().plusSeconds(VALID_LIFETIME).truncatedTo(ChronoUnit.SECONDS).toString();
12671347
}

0 commit comments

Comments
 (0)