Skip to content

Commit 56336b2

Browse files
committed
test(auth): add caching call-count assertions and fix mock expiration bug
Other client libraries (such as Rust and .NET) safely verify caching logic by asserting exactly one outbound HTTP request is made regardless of the number of credential calls. Added `getRequestMetadata_multipleCalls_usesCachedToken` to enforce this. Adding this test exposed a long-standing bug in the test framework: `MockMetadataServerTransport` was erroneously returning `expires_in` as 3,600,000 (mistakenly assuming it was milliseconds instead of seconds). This caused a 32-bit integer overflow in the parser (`expiresInSeconds * 1000` > 2.14B), which instantly expired the mock token and silently bypassed the cache during tests. Fixed the mock to correctly return 3600 seconds, and defensively promoted the production code parser multiplication to a 64-bit `long` to prevent any potential future overflows.
1 parent 1674b80 commit 56336b2

4 files changed

Lines changed: 31 additions & 3 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,7 @@ public AccessToken refreshAccessToken() throws IOException {
453453
OAuth2Utils.validateString(responseData, "access_token", PARSE_ERROR_PREFIX);
454454
int expiresInSeconds =
455455
OAuth2Utils.validateInt32(responseData, "expires_in", PARSE_ERROR_PREFIX);
456-
long expiresAtMilliseconds = clock.currentTimeMillis() + expiresInSeconds * 1000;
456+
long expiresAtMilliseconds = clock.currentTimeMillis() + expiresInSeconds * 1000L;
457457

458458
return new AccessToken(accessToken, new Date(expiresAtMilliseconds));
459459
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ public AccessToken refreshAccessToken() throws IOException {
193193
OAuth2Utils.validateString(responseData, "access_token", PARSE_ERROR_PREFIX);
194194
int expiresInSeconds =
195195
OAuth2Utils.validateInt32(responseData, "expires_in", PARSE_ERROR_PREFIX);
196-
long expiresAtMilliseconds = clock.currentTimeMillis() + expiresInSeconds * 1000;
196+
long expiresAtMilliseconds = clock.currentTimeMillis() + expiresInSeconds * 1000L;
197197
String scopes =
198198
OAuth2Utils.validateOptionalString(
199199
responseData, OAuth2Utils.TOKEN_RESPONSE_SCOPE, PARSE_ERROR_PREFIX);

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,34 @@ void getRequestMetadata_shouldInvalidateAccessTokenWhenScoped_newAccessTokenFrom
419419
TestUtils.assertNotContainsBearerToken(metadataForCopiedCredentials, ACCESS_TOKEN);
420420
}
421421

422+
@Test
423+
void getRequestMetadata_multipleCalls_usesCachedToken() throws IOException {
424+
final int[] requestCount = new int[1];
425+
MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
426+
transportFactory.transport =
427+
new MockMetadataServerTransport(SCOPE_TO_ACCESS_TOKEN_MAP) {
428+
@Override
429+
public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
430+
if (url.startsWith(ComputeEngineCredentials.getTokenServerEncodedUrl())) {
431+
requestCount[0]++;
432+
}
433+
return super.buildRequest(method, url);
434+
}
435+
};
436+
transportFactory.transport.setServiceAccountEmail(SA_CLIENT_EMAIL);
437+
438+
ComputeEngineCredentials credentials =
439+
ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
440+
441+
Map<String, List<String>> metadata = credentials.getRequestMetadata(CALL_URI);
442+
TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN);
443+
assertEquals(1, requestCount[0]);
444+
445+
Map<String, List<String>> metadata2 = credentials.getRequestMetadata(CALL_URI);
446+
TestUtils.assertContainsBearerToken(metadata2, ACCESS_TOKEN);
447+
assertEquals(1, requestCount[0]);
448+
}
449+
422450
@Test
423451
void getRequestMetadata_missingServiceAccount_throws() {
424452
MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ public LowLevelHttpResponse execute() throws IOException {
213213
refreshContents.put(
214214
"access_token", scopesToAccessToken.get("[" + urlParsed.get(1) + "]"));
215215
}
216-
refreshContents.put("expires_in", 3600000);
216+
refreshContents.put("expires_in", 3600);
217217
refreshContents.put("token_type", "Bearer");
218218
String refreshText = refreshContents.toPrettyString();
219219

0 commit comments

Comments
 (0)