From 50d3a5c539ab59831ab714df4f47eac4fe889744 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Fern=C3=A1ndez=20Navarro?= Date: Wed, 22 Jun 2022 17:36:43 +0200 Subject: [PATCH 1/6] fix: refreshAccessToken method of ImpersonatedCredentials class now obtains the given expiration time date's timezone correctly instead of using local timezone --- .../auth/oauth2/ImpersonatedCredentials.java | 2 +- .../oauth2/ImpersonatedCredentialsTest.java | 76 ++++++++++++++++++- 2 files changed, 75 insertions(+), 3 deletions(-) diff --git a/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java index 9694619df..7b6acbd04 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java @@ -91,7 +91,7 @@ public class ImpersonatedCredentials extends GoogleCredentials implements ServiceAccountSigner, IdTokenProvider, QuotaProjectIdProvider { private static final long serialVersionUID = -2133257318957488431L; - private static final String RFC3339 = "yyyy-MM-dd'T'HH:mm:ss'Z'"; + private static final String RFC3339 = "yyyy-MM-dd'T'HH:mm:ssX"; private static final int TWELVE_HOURS_IN_SECONDS = 43200; private static final int DEFAULT_LIFETIME_IN_SECONDS = 3600; private static final String CLOUD_PLATFORM_SCOPE = diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java index ec718d898..6a3dcf07b 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java @@ -60,7 +60,9 @@ import java.io.InputStream; import java.nio.charset.Charset; import java.security.PrivateKey; +import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -68,6 +70,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TimeZone; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -118,7 +122,7 @@ class ImpersonatedCredentialsTest extends BaseSerializationTest { private static final int INVALID_LIFETIME = 43210; private static JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance(); - private static final String RFC3339 = "yyyy-MM-dd'T'HH:mm:ss'Z'"; + private static final String RFC3339 = "yyyy-MM-dd'T'HH:mm:ssX"; public static final String DEFAULT_IMPERSONATION_URL = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/" + IMPERSONATED_CLIENT_EMAIL @@ -562,6 +566,54 @@ void refreshAccessToken_delegates_success() throws IOException, IllegalStateExce assertEquals(ACCESS_TOKEN, targetCredentials.refreshAccessToken().getTokenValue()); } + @Test + void refreshAccessToken_GMT_dateParsedCorrectly() throws IOException, IllegalStateException { + + Calendar c = Calendar.getInstance(); + c.add(Calendar.SECOND, VALID_LIFETIME); + + mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); + mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN); + mockTransportFactory.transport.setExpireTime(getFormattedTime(c.getTime())); + ImpersonatedCredentials targetCredentials = + ImpersonatedCredentials.create( + sourceCredentials, + IMPERSONATED_CLIENT_EMAIL, + null, + IMMUTABLE_SCOPES_LIST, + VALID_LIFETIME, + mockTransportFactory); + + assertEquals(ACCESS_TOKEN, targetCredentials.refreshAccessToken().getTokenValue()); + assertEquals(c.getTime().toInstant().truncatedTo(ChronoUnit.SECONDS).toEpochMilli(), + targetCredentials.refreshAccessToken().getExpirationTimeMillis()); + assertEquals(DEFAULT_IMPERSONATION_URL, mockTransportFactory.transport.getRequest().getUrl()); + } + + @Test + void refreshAccessToken_PDT_dateParsedCorrectly() throws IOException, IllegalStateException { + + Calendar c = Calendar.getInstance(); + c.add(Calendar.SECOND, VALID_LIFETIME); + + mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); + mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN); + mockTransportFactory.transport.setExpireTime(getFormattedTime(c.getTime(), TimeZone.getTimeZone("PDT"))); + ImpersonatedCredentials targetCredentials = + ImpersonatedCredentials.create( + sourceCredentials, + IMPERSONATED_CLIENT_EMAIL, + null, + IMMUTABLE_SCOPES_LIST, + VALID_LIFETIME, + mockTransportFactory); + + assertEquals(ACCESS_TOKEN, targetCredentials.refreshAccessToken().getTokenValue()); + assertEquals(c.getTime().toInstant().truncatedTo(ChronoUnit.SECONDS).toEpochMilli(), + targetCredentials.refreshAccessToken().getExpirationTimeMillis()); + assertEquals(DEFAULT_IMPERSONATION_URL, mockTransportFactory.transport.getRequest().getUrl()); + } + @Test void refreshAccessToken_invalidDate() throws IllegalStateException { @@ -926,7 +978,27 @@ void serialize() throws IOException, ClassNotFoundException { public static String getDefaultExpireTime() { Calendar c = Calendar.getInstance(); c.add(Calendar.SECOND, VALID_LIFETIME); - return new SimpleDateFormat(RFC3339).format(c.getTime()); + return getFormattedTime(c.getTime()); + } + + /** + Given a {@link Date}, it will return a string of the date + formatted like yyyy-MM-dd'T'HH:mm:ss'Z' + */ + private static String getFormattedTime(final Date date) { + //Set timezone to GMT since that's the TZ used in the response from the service impersonation token exchange + return getFormattedTime(date, TimeZone.getTimeZone("GMT")); + } + + /** + Given a {@link Date} and a desired {@link TimeZone}, it will return a string of the date + formatted like yyyy-MM-dd'T'HH:mm:ssX' where X represents a timezone + code following RFC3339 standard + */ + private static String getFormattedTime(final Date date, final TimeZone timeZone) { + final DateFormat formatter = new SimpleDateFormat(RFC3339); + formatter.setTimeZone(timeZone); + return formatter.format(date); } private String generateErrorJson( From 8ad0f772f8aadd17feeccef278671c07a06512c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Fern=C3=A1ndez=20Navarro?= Date: Wed, 22 Jun 2022 17:36:43 +0200 Subject: [PATCH 2/6] Fix bug in ImpersonatedCredentials class where the date received as expirationTime was being incorrectly parsed, assuming the date was in the default JVM timezone instead of reading from the date string what timezone it was. From bafd0acc0bbbad86702e12f1eafd56f0b3a92e7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Fern=C3=A1ndez=20Navarro?= Date: Thu, 23 Jun 2022 13:02:27 +0200 Subject: [PATCH 3/6] fix: formatting --- .../oauth2/ImpersonatedCredentialsTest.java | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java index 6a3dcf07b..c829208ce 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java @@ -71,7 +71,6 @@ import java.util.Map; import java.util.Set; import java.util.TimeZone; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -585,7 +584,8 @@ void refreshAccessToken_GMT_dateParsedCorrectly() throws IOException, IllegalSta mockTransportFactory); assertEquals(ACCESS_TOKEN, targetCredentials.refreshAccessToken().getTokenValue()); - assertEquals(c.getTime().toInstant().truncatedTo(ChronoUnit.SECONDS).toEpochMilli(), + assertEquals( + c.getTime().toInstant().truncatedTo(ChronoUnit.SECONDS).toEpochMilli(), targetCredentials.refreshAccessToken().getExpirationTimeMillis()); assertEquals(DEFAULT_IMPERSONATION_URL, mockTransportFactory.transport.getRequest().getUrl()); } @@ -598,7 +598,8 @@ void refreshAccessToken_PDT_dateParsedCorrectly() throws IOException, IllegalSta mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN); - mockTransportFactory.transport.setExpireTime(getFormattedTime(c.getTime(), TimeZone.getTimeZone("PDT"))); + mockTransportFactory.transport.setExpireTime( + getFormattedTime(c.getTime(), TimeZone.getTimeZone("PDT"))); ImpersonatedCredentials targetCredentials = ImpersonatedCredentials.create( sourceCredentials, @@ -609,7 +610,8 @@ void refreshAccessToken_PDT_dateParsedCorrectly() throws IOException, IllegalSta mockTransportFactory); assertEquals(ACCESS_TOKEN, targetCredentials.refreshAccessToken().getTokenValue()); - assertEquals(c.getTime().toInstant().truncatedTo(ChronoUnit.SECONDS).toEpochMilli(), + assertEquals( + c.getTime().toInstant().truncatedTo(ChronoUnit.SECONDS).toEpochMilli(), targetCredentials.refreshAccessToken().getExpirationTimeMillis()); assertEquals(DEFAULT_IMPERSONATION_URL, mockTransportFactory.transport.getRequest().getUrl()); } @@ -982,18 +984,19 @@ public static String getDefaultExpireTime() { } /** - Given a {@link Date}, it will return a string of the date - formatted like yyyy-MM-dd'T'HH:mm:ss'Z' + * Given a {@link Date}, it will return a string of the date formatted like + * yyyy-MM-dd'T'HH:mm:ss'Z' */ private static String getFormattedTime(final Date date) { - //Set timezone to GMT since that's the TZ used in the response from the service impersonation token exchange + // Set timezone to GMT since that's the TZ used in the response from the service impersonation + // token exchange return getFormattedTime(date, TimeZone.getTimeZone("GMT")); } /** - Given a {@link Date} and a desired {@link TimeZone}, it will return a string of the date - formatted like yyyy-MM-dd'T'HH:mm:ssX' where X represents a timezone - code following RFC3339 standard + * Given a {@link Date} and a desired {@link TimeZone}, it will return a string of the date + * formatted like yyyy-MM-dd'T'HH:mm:ssX' where X represents a timezone code following + * RFC3339 standard */ private static String getFormattedTime(final Date date, final TimeZone timeZone) { final DateFormat formatter = new SimpleDateFormat(RFC3339); From 3576e93a5819cb6475dca1c634e1033ab11f78c2 Mon Sep 17 00:00:00 2001 From: ivan-f-n Date: Wed, 29 Jun 2022 17:41:03 +0200 Subject: [PATCH 4/6] test: tests now change the timezone of the calendar used when parsing the date instead of returning dates in different timezones Expose to tests the calendar object used by the SimpleDateFormat object of the refreshAccessToken method in the ImpersonatedCredentials class That way we stop relying on returning dates in different timezones in the expireTime returned by the mockTransportFactory --- .../auth/oauth2/ImpersonatedCredentials.java | 4 ++++ .../auth/oauth2/ImpersonatedCredentialsTest.java | 15 ++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java index 7b6acbd04..9a86babc9 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java @@ -55,6 +55,7 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; +import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.List; @@ -110,6 +111,8 @@ public class ImpersonatedCredentials extends GoogleCredentials private transient HttpTransportFactory transportFactory; + @VisibleForTesting transient Calendar calendar = Calendar.getInstance(); + /** * @param sourceCredentials the source credential used to acquire the impersonated credentials. It * should be either a user account credential or a service account credential. @@ -512,6 +515,7 @@ public AccessToken refreshAccessToken() throws IOException { OAuth2Utils.validateString(responseData, "expireTime", "Expected to find an expireTime"); DateFormat format = new SimpleDateFormat(RFC3339); + format.setCalendar(calendar); try { Date date = format.parse(expireTime); return new AccessToken(accessToken, date); diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java index c829208ce..999b8ac21 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java @@ -582,24 +582,22 @@ void refreshAccessToken_GMT_dateParsedCorrectly() throws IOException, IllegalSta IMMUTABLE_SCOPES_LIST, VALID_LIFETIME, mockTransportFactory); - - assertEquals(ACCESS_TOKEN, targetCredentials.refreshAccessToken().getTokenValue()); + // Set system timezone to GMT + targetCredentials.calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT")); assertEquals( c.getTime().toInstant().truncatedTo(ChronoUnit.SECONDS).toEpochMilli(), targetCredentials.refreshAccessToken().getExpirationTimeMillis()); - assertEquals(DEFAULT_IMPERSONATION_URL, mockTransportFactory.transport.getRequest().getUrl()); } @Test - void refreshAccessToken_PDT_dateParsedCorrectly() throws IOException, IllegalStateException { + void refreshAccessToken_nonGMT_dateParsedCorrectly() throws IOException, IllegalStateException { Calendar c = Calendar.getInstance(); c.add(Calendar.SECOND, VALID_LIFETIME); mockTransportFactory.transport.setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL); mockTransportFactory.transport.setAccessToken(ACCESS_TOKEN); - mockTransportFactory.transport.setExpireTime( - getFormattedTime(c.getTime(), TimeZone.getTimeZone("PDT"))); + mockTransportFactory.transport.setExpireTime(getFormattedTime(c.getTime())); ImpersonatedCredentials targetCredentials = ImpersonatedCredentials.create( sourceCredentials, @@ -608,12 +606,11 @@ void refreshAccessToken_PDT_dateParsedCorrectly() throws IOException, IllegalSta IMMUTABLE_SCOPES_LIST, VALID_LIFETIME, mockTransportFactory); - - assertEquals(ACCESS_TOKEN, targetCredentials.refreshAccessToken().getTokenValue()); + // Set system timezone to one different than GMT + targetCredentials.calendar = Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles")); assertEquals( c.getTime().toInstant().truncatedTo(ChronoUnit.SECONDS).toEpochMilli(), targetCredentials.refreshAccessToken().getExpirationTimeMillis()); - assertEquals(DEFAULT_IMPERSONATION_URL, mockTransportFactory.transport.getRequest().getUrl()); } @Test From 4e12ce82f483c5a9d32287add3fd43113ca726a0 Mon Sep 17 00:00:00 2001 From: ivan-f-n Date: Thu, 30 Jun 2022 13:16:55 +0200 Subject: [PATCH 5/6] chore: remove unnecessary method and add blank line between setup and asserts --- .../auth/oauth2/ImpersonatedCredentialsTest.java | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java index 999b8ac21..a02158873 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java @@ -584,6 +584,7 @@ void refreshAccessToken_GMT_dateParsedCorrectly() throws IOException, IllegalSta mockTransportFactory); // Set system timezone to GMT targetCredentials.calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + assertEquals( c.getTime().toInstant().truncatedTo(ChronoUnit.SECONDS).toEpochMilli(), targetCredentials.refreshAccessToken().getExpirationTimeMillis()); @@ -608,6 +609,7 @@ void refreshAccessToken_nonGMT_dateParsedCorrectly() throws IOException, Illegal mockTransportFactory); // Set system timezone to one different than GMT targetCredentials.calendar = Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles")); + assertEquals( c.getTime().toInstant().truncatedTo(ChronoUnit.SECONDS).toEpochMilli(), targetCredentials.refreshAccessToken().getExpirationTimeMillis()); @@ -987,17 +989,8 @@ public static String getDefaultExpireTime() { private static String getFormattedTime(final Date date) { // Set timezone to GMT since that's the TZ used in the response from the service impersonation // token exchange - return getFormattedTime(date, TimeZone.getTimeZone("GMT")); - } - - /** - * Given a {@link Date} and a desired {@link TimeZone}, it will return a string of the date - * formatted like yyyy-MM-dd'T'HH:mm:ssX' where X represents a timezone code following - * RFC3339 standard - */ - private static String getFormattedTime(final Date date, final TimeZone timeZone) { final DateFormat formatter = new SimpleDateFormat(RFC3339); - formatter.setTimeZone(timeZone); + formatter.setTimeZone(TimeZone.getTimeZone("GMT")); return formatter.format(date); } From 62d7b081772492bba99a50584b9f0e62debe9286 Mon Sep 17 00:00:00 2001 From: ivan-f-n Date: Thu, 14 Jul 2022 13:00:00 +0200 Subject: [PATCH 6/6] fix: make calendar field private and modify it through builder --- .../auth/oauth2/ImpersonatedCredentials.java | 32 ++++++++++++++++- .../oauth2/ImpersonatedCredentialsTest.java | 36 +++++++++---------- 2 files changed, 49 insertions(+), 19 deletions(-) diff --git a/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java index 9a86babc9..375d957a4 100644 --- a/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java +++ b/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java @@ -111,7 +111,7 @@ public class ImpersonatedCredentials extends GoogleCredentials private transient HttpTransportFactory transportFactory; - @VisibleForTesting transient Calendar calendar = Calendar.getInstance(); + private transient Calendar calendar; /** * @param sourceCredentials the source credential used to acquire the impersonated credentials. It @@ -432,6 +432,25 @@ public GoogleCredentials createScoped(Collection scopes) { .build(); } + /** + * Clones the impersonated credentials with a new calendar. + * + * @param calendar the calendar that will be used by the new ImpersonatedCredentials instance when + * parsing the received expiration time of the refreshed access token + * @return the cloned impersonated credentials with the given custom calendar + */ + public ImpersonatedCredentials createWithCustomCalendar(Calendar calendar) { + return toBuilder() + .setScopes(this.scopes) + .setLifetime(this.lifetime) + .setDelegates(this.delegates) + .setHttpTransportFactory(this.transportFactory) + .setQuotaProjectId(this.quotaProjectId) + .setIamEndpointOverride(this.iamEndpointOverride) + .setCalendar(calendar) + .build(); + } + @Override protected Map> getAdditionalHeaders() { Map> headers = super.getAdditionalHeaders(); @@ -454,6 +473,7 @@ private ImpersonatedCredentials(Builder builder) { this.quotaProjectId = builder.quotaProjectId; this.iamEndpointOverride = builder.iamEndpointOverride; this.transportFactoryClassName = this.transportFactory.getClass().getName(); + this.calendar = builder.getCalendar(); if (this.delegates == null) { this.delegates = new ArrayList(); } @@ -610,6 +630,7 @@ public static class Builder extends GoogleCredentials.Builder { private HttpTransportFactory transportFactory; private String quotaProjectId; private String iamEndpointOverride; + private Calendar calendar = Calendar.getInstance(); protected Builder() {} @@ -682,6 +703,15 @@ public Builder setIamEndpointOverride(String iamEndpointOverride) { return this; } + public Builder setCalendar(Calendar calendar) { + this.calendar = calendar; + return this; + } + + public Calendar getCalendar() { + return this.calendar; + } + public ImpersonatedCredentials build() { return new ImpersonatedCredentials(this); } diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java index a02158873..4cbacb12d 100644 --- a/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java +++ b/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java @@ -567,7 +567,6 @@ void refreshAccessToken_delegates_success() throws IOException, IllegalStateExce @Test void refreshAccessToken_GMT_dateParsedCorrectly() throws IOException, IllegalStateException { - Calendar c = Calendar.getInstance(); c.add(Calendar.SECOND, VALID_LIFETIME); @@ -576,14 +575,15 @@ void refreshAccessToken_GMT_dateParsedCorrectly() throws IOException, IllegalSta mockTransportFactory.transport.setExpireTime(getFormattedTime(c.getTime())); ImpersonatedCredentials targetCredentials = ImpersonatedCredentials.create( - sourceCredentials, - IMPERSONATED_CLIENT_EMAIL, - null, - IMMUTABLE_SCOPES_LIST, - VALID_LIFETIME, - mockTransportFactory); - // Set system timezone to GMT - targetCredentials.calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + sourceCredentials, + IMPERSONATED_CLIENT_EMAIL, + null, + IMMUTABLE_SCOPES_LIST, + VALID_LIFETIME, + mockTransportFactory) + .createWithCustomCalendar( + // Set system timezone to GMT + Calendar.getInstance(TimeZone.getTimeZone("GMT"))); assertEquals( c.getTime().toInstant().truncatedTo(ChronoUnit.SECONDS).toEpochMilli(), @@ -592,7 +592,6 @@ void refreshAccessToken_GMT_dateParsedCorrectly() throws IOException, IllegalSta @Test void refreshAccessToken_nonGMT_dateParsedCorrectly() throws IOException, IllegalStateException { - Calendar c = Calendar.getInstance(); c.add(Calendar.SECOND, VALID_LIFETIME); @@ -601,14 +600,15 @@ void refreshAccessToken_nonGMT_dateParsedCorrectly() throws IOException, Illegal mockTransportFactory.transport.setExpireTime(getFormattedTime(c.getTime())); ImpersonatedCredentials targetCredentials = ImpersonatedCredentials.create( - sourceCredentials, - IMPERSONATED_CLIENT_EMAIL, - null, - IMMUTABLE_SCOPES_LIST, - VALID_LIFETIME, - mockTransportFactory); - // Set system timezone to one different than GMT - targetCredentials.calendar = Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles")); + sourceCredentials, + IMPERSONATED_CLIENT_EMAIL, + null, + IMMUTABLE_SCOPES_LIST, + VALID_LIFETIME, + mockTransportFactory) + .createWithCustomCalendar( + // Set system timezone to one different than GMT + Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles"))); assertEquals( c.getTime().toInstant().truncatedTo(ChronoUnit.SECONDS).toEpochMilli(),