From 1200a39361e2a1767ef95306ba3ece1b749e82a8 Mon Sep 17 00:00:00 2001 From: Timur Sadykov Date: Tue, 23 Aug 2022 01:16:16 -0700 Subject: [PATCH] fix: add retries to public key fetch (#983) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: add retries to public key fetch * 🦉 Updates from OwlBot post-processor * fix: update test certificate --- .../com/google/auth/oauth2/TokenVerifier.java | 20 ++++++++++++ .../google/auth/oauth2/TokenVerifierTest.java | 32 ++++++++++++------- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/oauth2_http/java/com/google/auth/oauth2/TokenVerifier.java b/oauth2_http/java/com/google/auth/oauth2/TokenVerifier.java index 12c6af92e..af3bfbf05 100644 --- a/oauth2_http/java/com/google/auth/oauth2/TokenVerifier.java +++ b/oauth2_http/java/com/google/auth/oauth2/TokenVerifier.java @@ -32,6 +32,8 @@ package com.google.auth.oauth2; import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpBackOffUnsuccessfulResponseHandler; +import com.google.api.client.http.HttpBackOffUnsuccessfulResponseHandler.BackOffRequired; import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpResponse; import com.google.api.client.http.HttpTransport; @@ -39,6 +41,7 @@ import com.google.api.client.json.webtoken.JsonWebSignature; import com.google.api.client.util.Base64; import com.google.api.client.util.Clock; +import com.google.api.client.util.ExponentialBackOff; import com.google.api.client.util.Key; import com.google.auth.http.HttpTransportFactory; import com.google.common.base.Preconditions; @@ -275,6 +278,10 @@ public TokenVerifier build() { /** Custom CacheLoader for mapping certificate urls to the contained public keys. */ static class PublicKeyLoader extends CacheLoader> { + private static final int DEFAULT_NUMBER_OF_RETRIES = 2; + private static final int INITIAL_RETRY_INTERVAL_MILLIS = 1000; + private static final double RETRY_RANDOMIZATION_FACTOR = 0.1; + private static final double RETRY_MULTIPLIER = 2; private final HttpTransportFactory httpTransportFactory; /** @@ -319,6 +326,19 @@ public Map load(String certificateUrl) throws Exception { .createRequestFactory() .buildGetRequest(new GenericUrl(certificateUrl)) .setParser(OAuth2Utils.JSON_FACTORY.createJsonObjectParser()); + request.setNumberOfRetries(DEFAULT_NUMBER_OF_RETRIES); + + ExponentialBackOff backoff = + new ExponentialBackOff.Builder() + .setInitialIntervalMillis(INITIAL_RETRY_INTERVAL_MILLIS) + .setRandomizationFactor(RETRY_RANDOMIZATION_FACTOR) + .setMultiplier(RETRY_MULTIPLIER) + .build(); + + request.setUnsuccessfulResponseHandler( + new HttpBackOffUnsuccessfulResponseHandler(backoff) + .setBackOffRequired(BackOffRequired.ALWAYS)); + HttpResponse response = request.execute(); jwks = response.parseAs(JsonWebKeySet.class); diff --git a/oauth2_http/javatests/com/google/auth/oauth2/TokenVerifierTest.java b/oauth2_http/javatests/com/google/auth/oauth2/TokenVerifierTest.java index 1c6e17795..9dcecab5e 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/TokenVerifierTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/TokenVerifierTest.java @@ -44,6 +44,7 @@ import com.google.api.client.util.Clock; import com.google.auth.http.HttpTransportFactory; import com.google.auth.oauth2.GoogleCredentialsTest.MockTokenServerTransportFactory; +import com.google.auth.oauth2.TokenVerifier.VerificationException; import com.google.common.io.CharStreams; import java.io.IOException; import java.io.InputStream; @@ -66,9 +67,9 @@ public class TokenVerifierTest { "https://www.googleapis.com/oauth2/v1/certs"; private static final String SERVICE_ACCOUNT_RS256_TOKEN = - "eyJhbGciOiJSUzI1NiIsImtpZCI6IjJlZjc3YjM4YTFiMDM3MDQ4NzA0MzkxNmFjYmYyN2Q3NGVkZDA4YjEiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL2V4YW1wbGUuY29tL2F1ZGllbmNlIiwiZXhwIjoxNTg3NjMwNTQzLCJpYXQiOjE1ODc2MjY5NDMsImlzcyI6InNvbWUgaXNzdWVyIiwic3ViIjoic29tZSBzdWJqZWN0In0.gGOQW0qQgs4jGUmCsgRV83RqsJLaEy89-ZOG6p1u0Y26FyY06b6Odgd7xXLsSTiiSnch62dl0Lfi9D0x2ByxvsGOCbovmBl2ZZ0zHr1wpc4N0XS9lMUq5RJQbonDibxXG4nC2zroDfvD0h7i-L8KMXeJb9pYwW7LkmrM_YwYfJnWnZ4bpcsDjojmPeUBlACg7tjjOgBFbyQZvUtaERJwSRlaWibvNjof7eCVfZChE0PwBpZc_cGqSqKXv544L4ttqdCnmONjqrTATXwC4gYxruevkjHfYI5ojcQmXoWDJJ0-_jzfyPE4MFFdCFgzLgnfIOwe5ve0MtquKuv2O0pgvg"; + "eyJhbGciOiJSUzI1NiIsImtpZCI6IjE3MjdiNmI0OTQwMmI5Y2Y5NWJlNGU4ZmQzOGFhN2U3YzExNjQ0YjEiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL2Nsb3VkdGFza3MuZ29vZ2xlYXBpcy5jb20vdjIvcHJvamVjdHMvZ2Nsb3VkLWRldmVsL2xvY2F0aW9ucyIsImF6cCI6InN0aW0tdGVzdEBzdGVsbGFyLWRheS0yNTQyMjIuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJlbWFpbCI6InN0aW0tdGVzdEBzdGVsbGFyLWRheS0yNTQyMjIuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiZXhwIjoxNjYwODgwNjczLCJpYXQiOjE2NjA4NzcwNzMsImlzcyI6Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbSIsInN1YiI6IjExMjgxMDY3Mjk2MzcyODM2NjQwNiJ9.Q2tG-hN6UHecbzaCIlg58K9msp58nLZWs03CBGO_D6F3cI4LKQEUzsbcztZqmNGWd0ld4zkrKzIP9cQosa_xold4hEzSX_ORRHYQLimLYaQmP3rKqWPMsbIupPdpnGqBDzAYjc7Pw9pQBzuZJj8e3FEG6a5tblDfMcgeklXZIkwzN7ypWCbFDoDP2STSYJYZ-LQIB0-Zlex7dm2KhyB8QSkMQK60YvpXz4L1OtwG7spk3yUCWxul6hYF76klST0iS6DH03YdaDpt4gRXkTUKyTRfB10h-WhCAKKRzmT6d_IT9ApIyqPhimkgkBHhLNyjK8lgAJdk9CLriSEOgVpsow"; private static final String SERVICE_ACCOUNT_CERT_URL = - "https://www.googleapis.com/robot/v1/metadata/x509/integration-tests%40chingor-test.iam.gserviceaccount.com"; + "https://www.googleapis.com/oauth2/v3/certs"; private static final List ALL_TOKENS = Arrays.asList(ES256_TOKEN, FEDERATED_SIGNON_RS256_TOKEN, SERVICE_ACCOUNT_RS256_TOKEN); @@ -152,6 +153,7 @@ public LowLevelHttpResponse execute() throws IOException { .setClock(FIXED_CLOCK) .setHttpTransportFactory(httpTransportFactory) .build(); + try { tokenVerifier.verify(ES256_TOKEN); fail("Should not be able to continue without exception."); @@ -170,8 +172,7 @@ public void verifyEs256TokenPublicKeyMismatch() { public HttpTransport create() { return new MockHttpTransport() { @Override - public LowLevelHttpRequest buildRequest(String method, String url) - throws IOException { + public LowLevelHttpRequest buildRequest(String method, String url) { return new MockLowLevelHttpRequest() { @Override public LowLevelHttpResponse execute() throws IOException { @@ -200,8 +201,7 @@ public LowLevelHttpResponse execute() throws IOException { } @Test - public void verifyPublicKeyStoreIntermittentError() - throws TokenVerifier.VerificationException, IOException { + public void verifyPublicKeyStoreIntermittentError() throws VerificationException, IOException { // mock responses MockLowLevelHttpResponse response404 = new MockLowLevelHttpResponse() @@ -224,13 +224,15 @@ public void verifyPublicKeyStoreIntermittentError() // Mock HTTP requests MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory(); - transportFactory.transport.addResponseSequence(response404, responseEmpty, responseGood); + transportFactory.transport.addResponseSequence( + response404, response404, response404, responseEmpty, responseGood); TokenVerifier tokenVerifier = TokenVerifier.newBuilder() .setClock(FIXED_CLOCK) .setHttpTransportFactory(transportFactory) .build(); + try { tokenVerifier.verify(ES256_TOKEN); fail("Should not be able to continue without exception."); @@ -249,7 +251,7 @@ public void verifyPublicKeyStoreIntermittentError() } @Test - public void verifyEs256Token() throws TokenVerifier.VerificationException, IOException { + public void verifyEs256Token() throws VerificationException, IOException { HttpTransportFactory httpTransportFactory = mockTransport( "https://www.gstatic.com/iap/verify/public_key-jwk", @@ -263,7 +265,7 @@ public void verifyEs256Token() throws TokenVerifier.VerificationException, IOExc } @Test - public void verifyRs256Token() throws TokenVerifier.VerificationException, IOException { + public void verifyRs256Token() throws VerificationException, IOException { HttpTransportFactory httpTransportFactory = mockTransport( "https://www.googleapis.com/oauth2/v3/certs", @@ -292,11 +294,17 @@ public void verifyRs256TokenWithLegacyCertificateUrlFormat() } @Test - public void verifyServiceAccountRs256Token() - throws TokenVerifier.VerificationException, IOException { + public void verifyServiceAccountRs256Token() throws TokenVerifier.VerificationException { + final Clock clock = + new Clock() { + @Override + public long currentTimeMillis() { + return 1660880573000L; + } + }; TokenVerifier tokenVerifier = TokenVerifier.newBuilder() - .setClock(FIXED_CLOCK) + .setClock(clock) .setCertificatesLocation(SERVICE_ACCOUNT_CERT_URL) .build(); assertNotNull(tokenVerifier.verify(SERVICE_ACCOUNT_RS256_TOKEN));