diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index b91fa381f..005ae5177 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -36,6 +36,22 @@ jobs:
- run: .kokoro/build.sh
env:
JOB_TYPE: test
+ units-logging:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ java: [11, 17, 21]
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-java@v4
+ with:
+ distribution: temurin
+ java-version: ${{matrix.java}}
+ - run: java -version
+ - run: .kokoro/build.sh
+ env:
+ JOB_TYPE: test-logging
units-java8:
# Building using Java 17 and run the tests with Java 8 runtime
name: "units (8)"
diff --git a/.github/workflows/sonar.yaml b/.github/workflows/sonar.yaml
index 9d29e5a31..4d8f793c1 100644
--- a/.github/workflows/sonar.yaml
+++ b/.github/workflows/sonar.yaml
@@ -38,9 +38,11 @@ jobs:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: |
mvn -B verify -Dcheckstyle.skip \
+ -Djacoco.skip=true \
-DenableFullTestCoverage \
-Dsonar.coverage.jacoco.xmlReportPaths=oauth2_http/target/site/jacoco/jacoco.xml \
org.sonarsource.scanner.maven:sonar-maven-plugin:sonar \
-Dsonar.projectKey=googleapis_google-auth-library-java \
-Dsonar.organization=googleapis \
-Dsonar.host.url=https://sonarcloud.io
+
diff --git a/.kokoro/build.sh b/.kokoro/build.sh
index 78daeba8d..ef485eac0 100755
--- a/.kokoro/build.sh
+++ b/.kokoro/build.sh
@@ -51,6 +51,11 @@ test)
mvn test -B -ntp -Dclirr.skip=true -Denforcer.skip=true ${SUREFIRE_JVM_OPT}
RETURN_CODE=$?
;;
+test-logging)
+ echo "SUREFIRE_JVM_OPT: ${SUREFIRE_JVM_OPT}"
+ mvn clean test -P '!slf4j2x,slf4j2x-test' -B -ntp -Dclirr.skip=true -Denforcer.skip=true ${SUREFIRE_JVM_OPT}
+ RETURN_CODE=$?
+ ;;
lint)
mvn com.coveo:fmt-maven-plugin:check -B -ntp
RETURN_CODE=$?
@@ -66,6 +71,7 @@ integration)
-DtrimStackTrace=false \
-Dclirr.skip=true \
-Denforcer.skip=true \
+ -Djacoco.skip=true \
-fae \
verify
RETURN_CODE=$?
@@ -74,14 +80,14 @@ graalvmA)
# Run Unit and Integration Tests with Native Image
bash .kokoro/populate-secrets.sh
export GOOGLE_APPLICATION_CREDENTIALS="${KOKORO_GFILE_DIR}/secret_manager/java-it-service-account"
- mvn -B ${INTEGRATION_TEST_ARGS} -ntp -Pnative -Pnative-test test -pl 'oauth2_http'
+ mvn -B ${INTEGRATION_TEST_ARGS} -ntp -Pnative -Pnative-test -Pslf4j2x test -pl 'oauth2_http'
RETURN_CODE=$?
;;
graalvmB)
# Run Unit and Integration Tests with Native Image
bash .kokoro/populate-secrets.sh
export GOOGLE_APPLICATION_CREDENTIALS="${KOKORO_GFILE_DIR}/secret_manager/java-it-service-account"
- mvn -B ${INTEGRATION_TEST_ARGS} -ntp -Pnative -Pnative-test test -pl 'oauth2_http'
+ mvn -B ${INTEGRATION_TEST_ARGS} -ntp -Pnative -Pnative-test -Pslf4j2x test -pl 'oauth2_http'
RETURN_CODE=$?
;;
samples)
diff --git a/.kokoro/dependencies.sh b/.kokoro/dependencies.sh
index bd8960246..775319222 100755
--- a/.kokoro/dependencies.sh
+++ b/.kokoro/dependencies.sh
@@ -54,6 +54,7 @@ retry_with_backoff 3 10 \
mvn install -B -V -ntp \
-DskipTests=true \
-Dmaven.javadoc.skip=true \
+ -Djacoco.skip=true \
-Dclirr.skip=true
mvn -B dependency:analyze -DfailOnWarning=true
diff --git a/.kokoro/presubmit/graalvm-native-a.cfg b/.kokoro/presubmit/graalvm-native-a.cfg
index bc084c51d..7950a4287 100644
--- a/.kokoro/presubmit/graalvm-native-a.cfg
+++ b/.kokoro/presubmit/graalvm-native-a.cfg
@@ -44,6 +44,6 @@ env_vars: {
}
container_properties {
- docker_image: "us-docker.pkg.dev/java-graalvm-ci-prod/graalvm-integration-testing/graalvm_a:1.14.2"
+ docker_image: "us-docker.pkg.dev/java-graalvm-ci-prod/graalvm-integration-testing/graalvm_a:1.14.3"
}
diff --git a/.kokoro/presubmit/graalvm-native-b.cfg b/.kokoro/presubmit/graalvm-native-b.cfg
index bd1f7b481..bd66e22a9 100644
--- a/.kokoro/presubmit/graalvm-native-b.cfg
+++ b/.kokoro/presubmit/graalvm-native-b.cfg
@@ -44,5 +44,5 @@ env_vars: {
}
container_properties {
- docker_image: "us-docker.pkg.dev/java-graalvm-ci-prod/graalvm-integration-testing/graalvm_b:1.14.2"
+ docker_image: "us-docker.pkg.dev/java-graalvm-ci-prod/graalvm-integration-testing/graalvm_b:1.14.3"
}
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index cb8c32fee..5334cb411 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "1.32.1"
+ ".": "1.33.0"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e7807749b..36961b80f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,17 @@
# Changelog
+## [1.33.0](https://github.com/googleapis/google-auth-library-java/compare/v1.32.1...v1.33.0) (2025-02-24)
+
+
+### Features
+
+* Add client logging with slf4j ([#1586](https://github.com/googleapis/google-auth-library-java/issues/1586)) ([24761d6](https://github.com/googleapis/google-auth-library-java/commit/24761d6cc3590c4b08c56c8c992b740e235b31c5))
+
+
+### Dependencies
+
+* Update dependency com.google.http-client:google-http-client-bom to v1.46.1 ([96a5ad8](https://github.com/googleapis/google-auth-library-java/commit/96a5ad88a7b187e1a0d472dca06ff39d74804d61))
+
## [1.32.1](https://github.com/googleapis/google-auth-library-java/compare/v1.32.0...v1.32.1) (2025-02-07)
diff --git a/appengine/pom.xml b/appengine/pom.xml
index 348cfc66b..700412c67 100644
--- a/appengine/pom.xml
+++ b/appengine/pom.xml
@@ -5,7 +5,7 @@
com.google.auth
google-auth-library-parent
- 1.32.1
+ 1.33.0
../pom.xml
diff --git a/bom/pom.xml b/bom/pom.xml
index d8dc03ede..b4804c038 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -3,7 +3,7 @@
4.0.0
com.google.auth
google-auth-library-bom
- 1.32.1
+ 1.33.0
pom
Google Auth Library for Java BOM
diff --git a/cab-token-generator/pom.xml b/cab-token-generator/pom.xml
index aec572687..b30d3a33c 100644
--- a/cab-token-generator/pom.xml
+++ b/cab-token-generator/pom.xml
@@ -6,7 +6,7 @@
com.google.auth
google-auth-library-parent
- 1.32.1
+ 1.33.0
google-auth-library-cab-token-generator
diff --git a/credentials/pom.xml b/credentials/pom.xml
index ae0535dd9..ae06ca46b 100644
--- a/credentials/pom.xml
+++ b/credentials/pom.xml
@@ -4,7 +4,7 @@
com.google.auth
google-auth-library-parent
- 1.32.1
+ 1.33.0
../pom.xml
diff --git a/oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java
index f7c183099..320ee4290 100644
--- a/oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java
+++ b/oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java
@@ -51,6 +51,7 @@
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects.ToStringHelper;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.BufferedReader;
@@ -94,6 +95,8 @@ public class ComputeEngineCredentials extends GoogleCredentials
static final Duration COMPUTE_REFRESH_MARGIN = Duration.ofMinutes(3).plusSeconds(45);
private static final Logger LOGGER = Logger.getLogger(ComputeEngineCredentials.class.getName());
+ private static final LoggerProvider LOGGER_PROVIDER =
+ LoggerProvider.forClazz(ComputeEngineCredentials.class);
static final String DEFAULT_METADATA_SERVER_URL = "http://metadata.google.internal";
@@ -371,11 +374,14 @@ public AccessToken refreshAccessToken() throws IOException {
throw new IOException(METADATA_RESPONSE_EMPTY_CONTENT_ERROR_MESSAGE);
}
GenericData responseData = response.parseAs(GenericData.class);
+ LoggingUtils.logResponsePayload(
+ responseData, LOGGER_PROVIDER, "Response payload for access token");
String accessToken =
OAuth2Utils.validateString(responseData, "access_token", PARSE_ERROR_PREFIX);
int expiresInSeconds =
OAuth2Utils.validateInt32(responseData, "expires_in", PARSE_ERROR_PREFIX);
long expiresAtMilliseconds = clock.currentTimeMillis() + expiresInSeconds * 1000;
+
return new AccessToken(accessToken, new Date(expiresAtMilliseconds));
}
@@ -430,6 +436,12 @@ public IdToken idTokenWithAudience(String targetAudience, List defaultAccount =
OAuth2Utils.validateMap(responseData, "default", PARSE_ERROR_ACCOUNT);
return OAuth2Utils.validateString(defaultAccount, "email", PARSE_ERROR_ACCOUNT);
diff --git a/oauth2_http/java/com/google/auth/oauth2/IamUtils.java b/oauth2_http/java/com/google/auth/oauth2/IamUtils.java
index 571d7f668..cd99a8903 100644
--- a/oauth2_http/java/com/google/auth/oauth2/IamUtils.java
+++ b/oauth2_http/java/com/google/auth/oauth2/IamUtils.java
@@ -72,6 +72,7 @@ class IamUtils {
"https://iamcredentials.%s/v1/projects/-/serviceAccounts/%s:signBlob";
private static final String PARSE_ERROR_MESSAGE = "Error parsing error message response. ";
private static final String PARSE_ERROR_SIGNATURE = "Error parsing signature response. ";
+ private static final LoggerProvider LOGGER_PROVIDER = LoggerProvider.forClazz(IamUtils.class);
// Following guidance for IAM retries:
// https://cloud.google.com/iam/docs/retry-strategy#errors-to-retry
@@ -154,7 +155,11 @@ private static String getSignature(
IamUtils.IAM_RETRYABLE_STATUS_CODES.contains(response.getStatusCode())));
request.setIOExceptionHandler(new HttpBackOffIOExceptionHandler(backoff));
+ LoggingUtils.logRequest(
+ request, LOGGER_PROVIDER, "Sending request to get signature to sign the blob");
HttpResponse response = request.execute();
+ LoggingUtils.logResponse(
+ response, LOGGER_PROVIDER, "Received response for signature to sign the blob");
int statusCode = response.getStatusCode();
if (statusCode >= 400 && statusCode < HttpStatusCodes.STATUS_CODE_SERVER_ERROR) {
GenericData responseError = response.parseAs(GenericData.class);
@@ -181,6 +186,8 @@ private static String getSignature(
}
GenericData responseData = response.parseAs(GenericData.class);
+ LoggingUtils.logResponsePayload(
+ responseData, LOGGER_PROVIDER, "Response payload for sign blob");
return OAuth2Utils.validateString(responseData, "signedBlob", PARSE_ERROR_SIGNATURE);
}
@@ -234,7 +241,10 @@ static IdToken getIdToken(
MetricsUtils.getGoogleCredentialsMetricsHeader(
RequestType.ID_TOKEN_REQUEST, credentialTypeForMetrics));
+ LoggingUtils.logRequest(request, LOGGER_PROVIDER, "Sending request to get ID token");
HttpResponse response = request.execute();
+
+ LoggingUtils.logResponse(response, LOGGER_PROVIDER, "Received response for ID token request");
int statusCode = response.getStatusCode();
if (statusCode >= 400 && statusCode < HttpStatusCodes.STATUS_CODE_SERVER_ERROR) {
GenericData responseError = response.parseAs(GenericData.class);
@@ -259,6 +269,8 @@ static IdToken getIdToken(
}
GenericJson responseData = response.parseAs(GenericJson.class);
+ LoggingUtils.logResponsePayload(
+ responseData, LOGGER_PROVIDER, "Response payload for ID token request");
String rawToken = OAuth2Utils.validateString(responseData, "token", PARSE_ERROR_MESSAGE);
return IdToken.create(rawToken);
}
diff --git a/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java
index e5ea4d923..ba32eba29 100644
--- a/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java
+++ b/oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java
@@ -110,6 +110,8 @@ public class ImpersonatedCredentials extends GoogleCredentials
private int lifetime;
private String iamEndpointOverride;
private final String transportFactoryClassName;
+ private static final LoggerProvider LOGGER_PROVIDER =
+ LoggerProvider.forClazz(ImpersonatedCredentials.class);
private transient HttpTransportFactory transportFactory;
@@ -553,12 +555,17 @@ public AccessToken refreshAccessToken() throws IOException {
HttpResponse response = null;
try {
+ LoggingUtils.logRequest(request, LOGGER_PROVIDER, "Sending request to refresh access token");
response = request.execute();
+ LoggingUtils.logResponse(
+ response, LOGGER_PROVIDER, "Received response for refresh access token");
} catch (IOException e) {
throw new IOException("Error requesting access token", e);
}
GenericData responseData = response.parseAs(GenericData.class);
+ LoggingUtils.logResponsePayload(
+ responseData, LOGGER_PROVIDER, "Response payload for access token");
response.disconnect();
String accessToken =
diff --git a/oauth2_http/java/com/google/auth/oauth2/LoggerProvider.java b/oauth2_http/java/com/google/auth/oauth2/LoggerProvider.java
new file mode 100644
index 000000000..c093901db
--- /dev/null
+++ b/oauth2_http/java/com/google/auth/oauth2/LoggerProvider.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * * Neither the name of Google LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.google.auth.oauth2;
+
+import org.slf4j.Logger;
+
+class LoggerProvider {
+
+ private Logger logger;
+ private final Class> clazz;
+
+ private LoggerProvider(Class> clazz) {
+ this.clazz = clazz;
+ }
+
+ static LoggerProvider forClazz(Class> clazz) {
+ return new LoggerProvider(clazz);
+ }
+
+ Logger getLogger() {
+ if (logger == null) {
+ this.logger = Slf4jUtils.getLogger(clazz);
+ }
+ return logger;
+ }
+}
diff --git a/oauth2_http/java/com/google/auth/oauth2/LoggingUtils.java b/oauth2_http/java/com/google/auth/oauth2/LoggingUtils.java
new file mode 100644
index 000000000..346846943
--- /dev/null
+++ b/oauth2_http/java/com/google/auth/oauth2/LoggingUtils.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * * Neither the name of Google LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.google.auth.oauth2;
+
+import com.google.api.client.http.HttpRequest;
+import com.google.api.client.http.HttpResponse;
+import com.google.api.client.util.GenericData;
+import java.util.Map;
+import java.util.logging.Level;
+
+class LoggingUtils {
+
+ static final String GOOGLE_SDK_JAVA_LOGGING = "GOOGLE_SDK_JAVA_LOGGING";
+ private static EnvironmentProvider environmentProvider =
+ SystemEnvironmentProvider.getInstance(); // this may be reset for testing purpose
+
+ private static boolean loggingEnabled = isLoggingEnabled();
+
+ // expose this setter only for testing purposes
+ static void setEnvironmentProvider(EnvironmentProvider provider) {
+ environmentProvider = provider;
+ // Recalculate LOGGING_ENABLED after setting the new provider
+ loggingEnabled = isLoggingEnabled();
+ }
+
+ static boolean isLoggingEnabled() {
+ String enableLogging = environmentProvider.getEnv(GOOGLE_SDK_JAVA_LOGGING);
+ return "true".equalsIgnoreCase(enableLogging);
+ }
+
+ static void logRequest(HttpRequest request, LoggerProvider loggerProvider, String message) {
+ if (loggingEnabled) {
+ Slf4jLoggingHelpers.logRequest(request, loggerProvider, message);
+ }
+ }
+
+ static void logResponse(HttpResponse response, LoggerProvider loggerProvider, String message) {
+ if (loggingEnabled) {
+ Slf4jLoggingHelpers.logResponse(response, loggerProvider, message);
+ }
+ }
+
+ static void logResponsePayload(
+ GenericData genericData, LoggerProvider loggerProvider, String message) {
+ if (loggingEnabled) {
+ Slf4jLoggingHelpers.logResponsePayload(genericData, loggerProvider, message);
+ }
+ }
+
+ // generic log method to use when not logging standard request, response and payload
+ static void log(
+ LoggerProvider loggerProvider, Level level, Map contextMap, String message) {
+ if (loggingEnabled) {
+ Slf4jLoggingHelpers.log(loggerProvider, level, contextMap, message);
+ }
+ }
+
+ private LoggingUtils() {}
+}
diff --git a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java
index 6e1b59974..8fccecbbe 100644
--- a/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java
+++ b/oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java
@@ -96,6 +96,8 @@ public class ServiceAccountCredentials extends GoogleCredentials
private static final String PARSE_ERROR_PREFIX = "Error parsing token refresh response. ";
private static final int TWELVE_HOURS_IN_SECONDS = 43200;
private static final int DEFAULT_LIFETIME_IN_SECONDS = 3600;
+ private static final LoggerProvider LOGGER_PROVIDER =
+ LoggerProvider.forClazz(ServiceAccountCredentials.class);
private final String clientId;
private final String clientEmail;
@@ -503,6 +505,11 @@ boolean isConfiguredForDomainWideDelegation() {
return serviceAccountUser != null && serviceAccountUser.length() > 0;
}
+ private GenericData parseResponseAs(HttpResponse response) throws IOException {
+ GenericData genericData = response.parseAs(GenericData.class);
+ LoggingUtils.logResponsePayload(genericData, LOGGER_PROVIDER, "Response payload");
+ return genericData;
+ }
/**
* Refreshes the OAuth2 access token by getting a new access token using a JSON Web Token (JWT).
*/
@@ -531,6 +538,7 @@ public AccessToken refreshAccessToken() throws IOException {
}
request.setParser(new JsonObjectParser(jsonFactory));
+ LoggingUtils.logRequest(request, LOGGER_PROVIDER, "Sending request to refresh access token");
ExponentialBackOff backoff =
new ExponentialBackOff.Builder()
.setInitialIntervalMillis(OAuth2Utils.INITIAL_RETRY_INTERVAL_MILLIS)
@@ -553,6 +561,8 @@ public AccessToken refreshAccessToken() throws IOException {
try {
response = request.execute();
+ LoggingUtils.logResponse(
+ response, LOGGER_PROVIDER, "Received response for refresh access token");
} catch (HttpResponseException re) {
String message = String.format(errorTemplate, re.getMessage(), getIssuer());
throw GoogleAuthException.createWithTokenEndpointResponseException(re, message);
@@ -561,7 +571,7 @@ public AccessToken refreshAccessToken() throws IOException {
e, String.format(errorTemplate, e.getMessage(), getIssuer()));
}
- GenericData responseData = response.parseAs(GenericData.class);
+ GenericData responseData = parseResponseAs(response);
String accessToken =
OAuth2Utils.validateString(responseData, "access_token", PARSE_ERROR_PREFIX);
int expiresInSeconds =
@@ -611,9 +621,12 @@ private IdToken getIdTokenOauthEndpoint(String targetAudience) throws IOExceptio
MetricsUtils.getGoogleCredentialsMetricsHeader(
RequestType.ID_TOKEN_REQUEST, getMetricsCredentialType()));
+ LoggingUtils.logRequest(request, LOGGER_PROVIDER, "Sending request to get ID token");
HttpResponse httpResponse = executeRequest(request);
- GenericData responseData = httpResponse.parseAs(GenericData.class);
+ LoggingUtils.logResponse(
+ httpResponse, LOGGER_PROVIDER, "Received response for ID token request");
+ GenericData responseData = parseResponseAs(httpResponse);
String rawToken = OAuth2Utils.validateString(responseData, "id_token", PARSE_ERROR_PREFIX);
return IdToken.create(rawToken);
}
@@ -654,9 +667,13 @@ private IdToken getIdTokenIamEndpoint(String targetAudience) throws IOException
HttpRequest request = buildIdTokenRequest(iamIdTokenUri, transportFactory, content);
// Use the Access Token from the SSJWT to request the ID Token from IAM Endpoint
request.setHeaders(new HttpHeaders().set(AuthHttpConstants.AUTHORIZATION, accessToken));
+
+ LoggingUtils.logRequest(request, LOGGER_PROVIDER, "Sending request to get ID token");
HttpResponse httpResponse = executeRequest(request);
+ LoggingUtils.logResponse(
+ httpResponse, LOGGER_PROVIDER, "Received response for ID token request");
- GenericData responseData = httpResponse.parseAs(GenericData.class);
+ GenericData responseData = parseResponseAs(httpResponse);
// IAM Endpoint returns `token` instead of `id_token`
String rawToken = OAuth2Utils.validateString(responseData, "token", PARSE_ERROR_PREFIX);
return IdToken.create(rawToken);
diff --git a/oauth2_http/java/com/google/auth/oauth2/Slf4jLoggingHelpers.java b/oauth2_http/java/com/google/auth/oauth2/Slf4jLoggingHelpers.java
new file mode 100644
index 000000000..1939e8867
--- /dev/null
+++ b/oauth2_http/java/com/google/auth/oauth2/Slf4jLoggingHelpers.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * * Neither the name of Google LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.google.auth.oauth2;
+
+import com.google.api.client.http.HttpRequest;
+import com.google.api.client.http.HttpResponse;
+import com.google.api.client.http.UrlEncodedContent;
+import com.google.api.client.http.json.JsonHttpContent;
+import com.google.api.client.util.GenericData;
+import com.google.gson.Gson;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.logging.Level;
+import org.slf4j.Logger;
+
+/** Contains helper methods to log auth requests and responses */
+class Slf4jLoggingHelpers {
+ private static final Gson gson = new Gson();
+ private static final Set SENSITIVE_KEYS = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
+
+ static {
+ SENSITIVE_KEYS.addAll(
+ Arrays.asList(
+ "token",
+ "assertion",
+ "access_token",
+ "client_secret",
+ "refresh_token",
+ "signedBlob",
+ "authorization"));
+ }
+
+ static void logRequest(HttpRequest request, LoggerProvider loggerProvider, String message) {
+ try {
+ Logger logger = loggerProvider.getLogger();
+ if (logger.isInfoEnabled()) {
+ Map loggingDataMap = new HashMap<>();
+ loggingDataMap.put("request.method", request.getRequestMethod());
+ loggingDataMap.put("request.url", request.getUrl().toString());
+
+ Map headers = new HashMap<>();
+ request
+ .getHeaders()
+ .forEach(
+ (key, val) -> {
+ if (SENSITIVE_KEYS.contains(key)) {
+ String hashedVal = calculateSHA256Hash(String.valueOf(val));
+ headers.put(key, hashedVal);
+ } else {
+ headers.put(key, val);
+ }
+ });
+ loggingDataMap.put("request.headers", gson.toJson(headers));
+
+ if (request.getContent() != null && logger.isDebugEnabled()) {
+ // are payload always GenericData? If so, can parse and store in json
+ if (request.getContent() instanceof UrlEncodedContent) {
+ // this is parsed to GenericData because that is how it is constructed.
+ GenericData data = (GenericData) ((UrlEncodedContent) request.getContent()).getData();
+ Map contextMap = parseGenericData(data);
+ loggingDataMap.put("request.payload", gson.toJson(contextMap));
+ } else if (request.getContent() instanceof JsonHttpContent) {
+ String jsonData = gson.toJson(((JsonHttpContent) request.getContent()).getData());
+ loggingDataMap.put("request.payload", jsonData);
+ }
+
+ Slf4jUtils.log(logger, org.slf4j.event.Level.DEBUG, loggingDataMap, message);
+ } else {
+
+ Slf4jUtils.log(logger, org.slf4j.event.Level.INFO, loggingDataMap, message);
+ }
+ }
+ } catch (Exception e) {
+ // let logging fail silently
+ }
+ }
+
+ /** Logs response status code, status.message, and headers */
+ static void logResponse(HttpResponse response, LoggerProvider loggerProvider, String message) {
+ try {
+ Logger logger = loggerProvider.getLogger();
+ if (logger.isInfoEnabled()) {
+ Map responseLogDataMap = new HashMap<>();
+ responseLogDataMap.put("response.status", String.valueOf(response.getStatusCode()));
+ responseLogDataMap.put("response.status.message", response.getStatusMessage());
+
+ Map headers = new HashMap<>(response.getHeaders());
+ responseLogDataMap.put("response.headers", headers.toString());
+ Slf4jUtils.log(logger, org.slf4j.event.Level.INFO, responseLogDataMap, message);
+ }
+ } catch (Exception e) {
+ // let logging fail silently
+ }
+ }
+
+ /** Logs parsed response payload */
+ static void logResponsePayload(
+ GenericData genericData, LoggerProvider loggerProvider, String message) {
+ try {
+
+ Logger logger = loggerProvider.getLogger();
+ if (logger.isDebugEnabled()) {
+ Map contextMap = parseGenericData(genericData);
+ Slf4jUtils.log(logger, org.slf4j.event.Level.DEBUG, contextMap, message);
+ }
+ } catch (Exception e) {
+ // let logging fail silently
+ }
+ }
+
+ static void log(
+ LoggerProvider loggerProvider, Level level, Map contextMap, String message) {
+ try {
+ Logger logger = loggerProvider.getLogger();
+ org.slf4j.event.Level slf4jLevel = matchUtilLevelToSLF4JLevel(level);
+ Slf4jUtils.log(logger, slf4jLevel, contextMap, message);
+ } catch (Exception e) {
+ // let logging fail silently
+ }
+ }
+
+ static org.slf4j.event.Level matchUtilLevelToSLF4JLevel(Level level) {
+ if (level == Level.SEVERE) {
+ return org.slf4j.event.Level.ERROR;
+ } else if (level == Level.WARNING) {
+ return org.slf4j.event.Level.WARN;
+ } else if (level == Level.INFO) {
+ return org.slf4j.event.Level.INFO;
+ } else if (level == Level.FINE) {
+ return org.slf4j.event.Level.DEBUG;
+ } else {
+ return org.slf4j.event.Level.TRACE;
+ }
+ }
+
+ private static Map parseGenericData(GenericData genericData) {
+ Map contextMap = new HashMap<>();
+ genericData.forEach(
+ (key, val) -> {
+ if (SENSITIVE_KEYS.contains(key)) {
+ String secretString = String.valueOf(val);
+ String hashedVal = calculateSHA256Hash(secretString);
+ contextMap.put(key, hashedVal);
+ } else {
+ contextMap.put(key, val.toString());
+ }
+ });
+ return contextMap;
+ }
+
+ // calculate SHA256Hash so we do not print secrets directly to log
+ private static String calculateSHA256Hash(String data) {
+ try {
+ MessageDigest digest = MessageDigest.getInstance("SHA-256");
+ byte[] inputBytes = data.getBytes(StandardCharsets.UTF_8);
+ byte[] hashBytes = digest.digest(inputBytes);
+ return bytesToHex(hashBytes);
+ } catch (NoSuchAlgorithmException e) {
+ return "Error calculating SHA-256 hash."; // do not fail for logging failures
+ }
+ }
+
+ private static String bytesToHex(byte[] hash) {
+ StringBuilder hexString = new StringBuilder(2 * hash.length);
+ for (byte b : hash) {
+ String hex = Integer.toHexString(0xff & b);
+ if (hex.length() == 1) {
+ hexString.append('0');
+ }
+ hexString.append(hex);
+ }
+ return hexString.toString();
+ }
+
+ private Slf4jLoggingHelpers() {}
+}
diff --git a/oauth2_http/java/com/google/auth/oauth2/Slf4jUtils.java b/oauth2_http/java/com/google/auth/oauth2/Slf4jUtils.java
new file mode 100644
index 000000000..887221d61
--- /dev/null
+++ b/oauth2_http/java/com/google/auth/oauth2/Slf4jUtils.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * * Neither the name of Google LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.google.auth.oauth2;
+
+import com.google.gson.Gson;
+import java.util.Map;
+import java.util.Map.Entry;
+import org.slf4j.ILoggerFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.MDC;
+import org.slf4j.spi.LoggingEventBuilder;
+
+/** Contains util methods to get SLF4J logger and log conditionally based SLF4J major version */
+class Slf4jUtils {
+
+ private static final Logger NO_OP_LOGGER = org.slf4j.helpers.NOPLogger.NOP_LOGGER;
+ private static final Gson gson = new Gson();
+ private static boolean isSLF4J2x;
+
+ static {
+ // this class was added as part of the Fluent API introduced since v2.0.0
+ // (https://www.slf4j.org/manual.html#fluent), not available in v1.7.36
+ // see
+ // https://github.com/qos-ch/slf4j/commits/v_2.0.0/slf4j-api/src/main/java/org/slf4j/event/KeyValuePair.java
+ isSLF4J2x = checkIfClazzAvailable("org.slf4j.event.KeyValuePair");
+ }
+
+ static boolean checkIfClazzAvailable(String clazzName) {
+ try {
+ Class.forName(clazzName);
+ return true;
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ }
+
+ private Slf4jUtils() {}
+
+ static Logger getLogger(Class> clazz) {
+ return getLogger(clazz, new DefaultLoggerFactoryProvider());
+ }
+
+ // constructor with LoggerFactoryProvider to make testing easier
+ static Logger getLogger(Class> clazz, LoggerFactoryProvider factoryProvider) {
+ if (LoggingUtils.isLoggingEnabled()) {
+ ILoggerFactory loggerFactory = factoryProvider.getLoggerFactory();
+ return loggerFactory.getLogger(clazz.getName());
+ } else {
+ // use SLF4j's NOP logger regardless of bindings
+ return NO_OP_LOGGER;
+ }
+ }
+
+ static void log(
+ Logger logger, org.slf4j.event.Level level, Map contextMap, String message) {
+ if (isSLF4J2x) {
+ logWithKeyValuePair(logger, level, contextMap, message);
+ } else {
+ logWithMDC(logger, level, contextMap, message);
+ }
+ }
+
+ // exposed for testing
+ static void logWithMDC(
+ Logger logger, org.slf4j.event.Level level, Map contextMap, String message) {
+ if (!contextMap.isEmpty()) {
+ for (Entry entry : contextMap.entrySet()) {
+ String key = entry.getKey();
+ Object value = entry.getValue();
+
+ MDC.put(key, value instanceof String ? (String) value : gson.toJson(value));
+ }
+ }
+ switch (level) {
+ case TRACE:
+ logger.trace(message);
+ break;
+ case DEBUG:
+ logger.debug(message);
+ break;
+ case INFO:
+ logger.info(message);
+ break;
+ case WARN:
+ logger.warn(message);
+ break;
+ case ERROR:
+ logger.error(message);
+ break;
+ default:
+ logger.debug(message);
+ // Default to DEBUG level
+ }
+ if (!contextMap.isEmpty()) {
+ // MDC carries contextual information in log messages.
+ // It is tied to thread, and is safer to clear it as we intend to tie info to log entries.
+ MDC.clear();
+ }
+ }
+
+ private static void logWithKeyValuePair(
+ Logger logger, org.slf4j.event.Level level, Map contextMap, String message) {
+ LoggingEventBuilder loggingEventBuilder;
+ switch (level) {
+ case TRACE:
+ loggingEventBuilder = logger.atTrace();
+ break;
+ case DEBUG:
+ loggingEventBuilder = logger.atDebug();
+ break;
+ case INFO:
+ loggingEventBuilder = logger.atInfo();
+ break;
+ case WARN:
+ loggingEventBuilder = logger.atWarn();
+ break;
+ case ERROR:
+ loggingEventBuilder = logger.atError();
+ break;
+ default:
+ loggingEventBuilder = logger.atDebug();
+ // Default to DEBUG level
+ }
+ contextMap.forEach(loggingEventBuilder::addKeyValue);
+ loggingEventBuilder.log(message);
+ }
+
+ interface LoggerFactoryProvider {
+ ILoggerFactory getLoggerFactory();
+ }
+
+ static class DefaultLoggerFactoryProvider implements LoggerFactoryProvider {
+ @Override
+ public ILoggerFactory getLoggerFactory() {
+ return LoggerFactory.getILoggerFactory();
+ }
+ }
+}
diff --git a/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java b/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java
index 453ee5ec8..0aa0fe567 100644
--- a/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java
+++ b/oauth2_http/java/com/google/auth/oauth2/UserCredentials.java
@@ -68,6 +68,8 @@ public class UserCredentials extends GoogleCredentials implements IdTokenProvide
private static final String GRANT_TYPE = "refresh_token";
private static final String PARSE_ERROR_PREFIX = "Error parsing token refresh response. ";
private static final long serialVersionUID = -4800758775038679176L;
+ private static final LoggerProvider LOGGER_PROVIDER =
+ LoggerProvider.forClazz(UserCredentials.class);
private final String clientId;
private final String clientSecret;
@@ -283,14 +285,20 @@ private GenericData doRefreshAccessToken() throws IOException {
HttpResponse response;
try {
+ LoggingUtils.logRequest(request, LOGGER_PROVIDER, "Sending request to refresh access token");
response = request.execute();
+ LoggingUtils.logResponse(
+ response, LOGGER_PROVIDER, "Received response for refresh access token");
} catch (HttpResponseException re) {
throw GoogleAuthException.createWithTokenEndpointResponseException(re);
} catch (IOException e) {
throw GoogleAuthException.createWithTokenEndpointIOException(e);
}
- return response.parseAs(GenericData.class);
+ GenericData data = response.parseAs(GenericData.class);
+
+ LoggingUtils.logResponsePayload(data, LOGGER_PROVIDER, "Response payload for access token");
+ return data;
}
/**
diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java
index 2eca3c5be..656585dbd 100644
--- a/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java
+++ b/oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java
@@ -120,8 +120,8 @@ public class ImpersonatedCredentialsTest extends BaseSerializationTest {
private static final String PROJECT_ID = "project-id";
public static final String IMPERSONATED_CLIENT_EMAIL =
"impersonated-account@iam.gserviceaccount.com";
- private static final List IMMUTABLE_SCOPES_LIST = ImmutableList.of("scope1", "scope2");
- private static final int VALID_LIFETIME = 300;
+ static final List IMMUTABLE_SCOPES_LIST = ImmutableList.of("scope1", "scope2");
+ static final int VALID_LIFETIME = 300;
private static final int INVALID_LIFETIME = 43210;
private static JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance();
@@ -164,7 +164,7 @@ public void setup() throws IOException {
mockTransportFactory = new MockIAMCredentialsServiceTransportFactory();
}
- private GoogleCredentials getSourceCredentials() throws IOException {
+ static GoogleCredentials getSourceCredentials() throws IOException {
MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
ServiceAccountCredentials sourceCredentials =
diff --git a/oauth2_http/javatests/com/google/auth/oauth2/LoggingTest.java b/oauth2_http/javatests/com/google/auth/oauth2/LoggingTest.java
new file mode 100644
index 000000000..0a76dee7d
--- /dev/null
+++ b/oauth2_http/javatests/com/google/auth/oauth2/LoggingTest.java
@@ -0,0 +1,522 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * * Neither the name of Google LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.google.auth.oauth2;
+
+import static com.google.auth.TestUtils.getDefaultExpireTime;
+import static com.google.auth.oauth2.ImpersonatedCredentialsTest.DEFAULT_IMPERSONATION_URL;
+import static com.google.auth.oauth2.ImpersonatedCredentialsTest.IMMUTABLE_SCOPES_LIST;
+import static com.google.auth.oauth2.ImpersonatedCredentialsTest.IMPERSONATED_CLIENT_EMAIL;
+import static com.google.auth.oauth2.ImpersonatedCredentialsTest.TOKEN_WITH_EMAIL;
+import static com.google.auth.oauth2.ImpersonatedCredentialsTest.VALID_LIFETIME;
+import static com.google.auth.oauth2.ServiceAccountCredentialsTest.ACCESS_TOKEN;
+import static com.google.auth.oauth2.ServiceAccountCredentialsTest.CALL_URI;
+import static com.google.auth.oauth2.ServiceAccountCredentialsTest.CLIENT_EMAIL;
+import static com.google.auth.oauth2.ServiceAccountCredentialsTest.DEFAULT_ID_TOKEN;
+import static com.google.auth.oauth2.ServiceAccountCredentialsTest.SCOPES;
+import static com.google.auth.oauth2.ServiceAccountCredentialsTest.createDefaultBuilder;
+import static com.google.auth.oauth2.UserCredentialsTest.CLIENT_ID;
+import static com.google.auth.oauth2.UserCredentialsTest.CLIENT_SECRET;
+import static com.google.auth.oauth2.UserCredentialsTest.REFRESH_TOKEN;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.google.api.client.http.HttpStatusCodes;
+import com.google.api.client.json.webtoken.JsonWebToken.Payload;
+import com.google.api.client.util.ArrayMap;
+import com.google.auth.TestUtils;
+import com.google.auth.oauth2.ComputeEngineCredentialsTest.MockMetadataServerTransportFactory;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonSyntaxException;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.event.KeyValuePair;
+
+/**
+ * This class contains tests for logging events in each credentials workflow Tests are copied from
+ * credentials test classes with addition of test logging appender setup and test logic for logging.
+ * This duplicates tests setups, but centralizes logging test setup in this class.
+ */
+public class LoggingTest {
+
+ private TestAppender setupTestLogger(Class> clazz) {
+ TestAppender testAppender = new TestAppender();
+ testAppender.start();
+ Logger logger = LoggerFactory.getLogger(clazz);
+ ((ch.qos.logback.classic.Logger) logger).addAppender(testAppender);
+ return testAppender;
+ }
+
+ @BeforeClass
+ public static void setup() {
+ // mimic GOOGLE_SDK_JAVA_LOGGING = true
+ TestEnvironmentProvider testEnvironmentProvider = new TestEnvironmentProvider();
+ testEnvironmentProvider.setEnv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING, "true");
+ LoggingUtils.setEnvironmentProvider(testEnvironmentProvider);
+ }
+
+ @Test
+ public void userCredentials_getRequestMetadata_fromRefreshToken_hasAccessToken()
+ throws IOException {
+ TestAppender testAppender = setupTestLogger(UserCredentials.class);
+ MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
+ transportFactory.transport.addClient(CLIENT_ID, CLIENT_SECRET);
+ transportFactory.transport.addRefreshToken(REFRESH_TOKEN, ACCESS_TOKEN);
+ UserCredentials userCredentials =
+ UserCredentials.newBuilder()
+ .setClientId(CLIENT_ID)
+ .setClientSecret(CLIENT_SECRET)
+ .setRefreshToken(REFRESH_TOKEN)
+ .setHttpTransportFactory(transportFactory)
+ .build();
+
+ Map> metadata = userCredentials.getRequestMetadata(CALL_URI);
+
+ TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN);
+
+ assertEquals(3, testAppender.events.size());
+ assertEquals(
+ "Sending request to refresh access token", testAppender.events.get(0).getMessage());
+ assertEquals(4, testAppender.events.get(0).getKeyValuePairs().size());
+ for (KeyValuePair kvp : testAppender.events.get(0).getKeyValuePairs()) {
+ assertTrue(
+ kvp.key.equals("request.headers")
+ || kvp.key.equals("request.payload")
+ || kvp.key.equals("request.method")
+ || kvp.key.equals("request.url"));
+ if (kvp.key.equals("request.payload")) {
+ assertTrue(isValidJson((String) kvp.value));
+ }
+ }
+ assertEquals(
+ "Received response for refresh access token", testAppender.events.get(1).getMessage());
+ assertEquals(3, testAppender.events.get(1).getKeyValuePairs().size());
+ for (KeyValuePair kvp : testAppender.events.get(1).getKeyValuePairs()) {
+ assertTrue(
+ kvp.key.equals("response.headers")
+ || kvp.key.equals("response.status")
+ || kvp.key.equals("response.status.message"));
+ }
+ assertEquals("Response payload for access token", testAppender.events.get(2).getMessage());
+ assertEquals(4, testAppender.events.get(2).getKeyValuePairs().size());
+ for (KeyValuePair kvp : testAppender.events.get(2).getKeyValuePairs()) {
+ assertTrue(
+ kvp.key.equals("access_token")
+ || kvp.key.equals("refresh_token")
+ || kvp.key.equals("token_type")
+ || kvp.key.equals("expires_in"));
+ }
+ testAppender.stop();
+ }
+
+ boolean isValidJson(String jsonString) {
+ try {
+ JsonParser.parseString(jsonString);
+ return true;
+ } catch (JsonSyntaxException e) {
+ return false;
+ }
+ }
+
+ @Test
+ public void serviceAccountCredentials_getRequestMetadata_hasAccessToken() throws IOException {
+ TestAppender testAppender = setupTestLogger(ServiceAccountCredentials.class);
+ GoogleCredentials credentials =
+ ServiceAccountCredentialsTest.createDefaultBuilderWithToken(ACCESS_TOKEN)
+ .setScopes(SCOPES)
+ .build();
+ Map> metadata = credentials.getRequestMetadata(CALL_URI);
+ TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN);
+
+ assertEquals(3, testAppender.events.size());
+
+ assertEquals(
+ "Sending request to refresh access token", testAppender.events.get(0).getMessage());
+ assertEquals(4, testAppender.events.get(0).getKeyValuePairs().size());
+ for (KeyValuePair kvp : testAppender.events.get(0).getKeyValuePairs()) {
+ assertTrue(
+ kvp.key.equals("request.headers")
+ || kvp.key.equals("request.payload")
+ || kvp.key.equals("request.method")
+ || kvp.key.equals("request.url"));
+ if (kvp.key.equals("request.payload")) {
+ // ensure correctly formatted as JSON
+ assertTrue(isValidJson((String) kvp.value));
+ }
+ }
+ assertEquals(
+ "Received response for refresh access token", testAppender.events.get(1).getMessage());
+ assertEquals(3, testAppender.events.get(1).getKeyValuePairs().size());
+ for (KeyValuePair kvp : testAppender.events.get(1).getKeyValuePairs()) {
+ assertTrue(
+ kvp.key.equals("response.headers")
+ || kvp.key.equals("response.status")
+ || kvp.key.equals("response.status.message"));
+ }
+ assertEquals("Response payload", testAppender.events.get(2).getMessage());
+ assertEquals(3, testAppender.events.get(2).getKeyValuePairs().size());
+ for (KeyValuePair kvp : testAppender.events.get(2).getKeyValuePairs()) {
+ assertTrue(
+ kvp.key.equals("access_token")
+ || kvp.key.equals("token_type")
+ || kvp.key.equals("expires_in"));
+ }
+ testAppender.stop();
+ }
+
+ @Test
+ public void serviceAccountCredentials_idTokenWithAudience_iamFlow_targetAudienceMatchesAudClaim()
+ throws IOException {
+ TestAppender testAppender = setupTestLogger(ServiceAccountCredentials.class);
+ String nonGDU = "test.com";
+ MockIAMCredentialsServiceTransportFactory transportFactory =
+ new MockIAMCredentialsServiceTransportFactory(nonGDU);
+ transportFactory.getTransport().setTargetPrincipal(CLIENT_EMAIL);
+ transportFactory.getTransport().setIdToken(DEFAULT_ID_TOKEN);
+ transportFactory.getTransport().addStatusCodeAndMessage(HttpStatusCodes.STATUS_CODE_OK, "");
+ ServiceAccountCredentials credentials =
+ createDefaultBuilder()
+ .setScopes(SCOPES)
+ .setHttpTransportFactory(transportFactory)
+ .setUniverseDomain(nonGDU)
+ .build();
+
+ String targetAudience = "https://foo.bar";
+ IdTokenCredentials tokenCredential =
+ IdTokenCredentials.newBuilder()
+ .setIdTokenProvider(credentials)
+ .setTargetAudience(targetAudience)
+ .build();
+ tokenCredential.refresh();
+ assertEquals(DEFAULT_ID_TOKEN, tokenCredential.getAccessToken().getTokenValue());
+ assertEquals(DEFAULT_ID_TOKEN, tokenCredential.getIdToken().getTokenValue());
+
+ // ID Token's aud claim is `https://foo.bar`
+ assertEquals(
+ targetAudience,
+ tokenCredential.getIdToken().getJsonWebSignature().getPayload().getAudience());
+
+ assertEquals(3, testAppender.events.size());
+
+ assertEquals("Sending request to get ID token", testAppender.events.get(0).getMessage());
+ assertEquals(4, testAppender.events.get(0).getKeyValuePairs().size());
+ for (KeyValuePair kvp : testAppender.events.get(0).getKeyValuePairs()) {
+ assertTrue(
+ kvp.key.equals("request.headers")
+ || kvp.key.equals("request.payload")
+ || kvp.key.equals("request.method")
+ || kvp.key.equals("request.url"));
+ if (kvp.key.equals("request.headers") || kvp.key.equals("request.payload")) {
+ assertTrue(isValidJson((String) kvp.value));
+ }
+ }
+ assertEquals("Received response for ID token request", testAppender.events.get(1).getMessage());
+ assertEquals(3, testAppender.events.get(1).getKeyValuePairs().size());
+ for (KeyValuePair kvp : testAppender.events.get(1).getKeyValuePairs()) {
+ assertTrue(
+ kvp.key.equals("response.headers")
+ || kvp.key.equals("response.status")
+ || kvp.key.equals("response.status.message"));
+ }
+ assertEquals("Response payload", testAppender.events.get(2).getMessage());
+ assertEquals(1, testAppender.events.get(2).getKeyValuePairs().size());
+ testAppender.stop();
+ }
+
+ @Test()
+ public void impersonatedCredentials_refreshAccessToken_success()
+ throws IOException, IllegalStateException {
+ TestAppender testAppender = setupTestLogger(ImpersonatedCredentials.class);
+ MockIAMCredentialsServiceTransportFactory mockTransportFactory =
+ new MockIAMCredentialsServiceTransportFactory();
+ mockTransportFactory.getTransport().setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL);
+ mockTransportFactory.getTransport().setAccessToken(ACCESS_TOKEN);
+ mockTransportFactory.getTransport().setExpireTime(getDefaultExpireTime());
+ mockTransportFactory.getTransport().addStatusCodeAndMessage(HttpStatusCodes.STATUS_CODE_OK, "");
+ ImpersonatedCredentials targetCredentials =
+ ImpersonatedCredentials.create(
+ ImpersonatedCredentialsTest.getSourceCredentials(),
+ IMPERSONATED_CLIENT_EMAIL,
+ null,
+ IMMUTABLE_SCOPES_LIST,
+ VALID_LIFETIME,
+ mockTransportFactory);
+
+ assertEquals(ACCESS_TOKEN, targetCredentials.refreshAccessToken().getTokenValue());
+ assertEquals(
+ DEFAULT_IMPERSONATION_URL, mockTransportFactory.getTransport().getRequest().getUrl());
+
+ assertEquals(3, testAppender.events.size());
+
+ assertEquals(
+ "Sending request to refresh access token", testAppender.events.get(0).getMessage());
+ assertEquals(4, testAppender.events.get(0).getKeyValuePairs().size());
+ for (KeyValuePair kvp : testAppender.events.get(0).getKeyValuePairs()) {
+ assertTrue(
+ kvp.key.equals("request.headers")
+ || kvp.key.equals("request.payload")
+ || kvp.key.equals("request.method")
+ || kvp.key.equals("request.url"));
+ if (kvp.key.equals("request.payload")) {
+ assertTrue(isValidJson((String) kvp.value));
+ }
+ }
+ assertEquals(
+ "Received response for refresh access token", testAppender.events.get(1).getMessage());
+ assertEquals(3, testAppender.events.get(1).getKeyValuePairs().size());
+ for (KeyValuePair kvp : testAppender.events.get(1).getKeyValuePairs()) {
+ assertTrue(
+ kvp.key.equals("response.headers")
+ || kvp.key.equals("response.status")
+ || kvp.key.equals("response.status.message"));
+ }
+ assertEquals("Response payload for access token", testAppender.events.get(2).getMessage());
+ assertEquals(2, testAppender.events.get(2).getKeyValuePairs().size());
+
+ testAppender.stop();
+ }
+
+ @Test
+ public void idTokenWithAudience_withEmail() throws IOException {
+ TestAppender testAppender = setupTestLogger(IamUtils.class);
+ MockIAMCredentialsServiceTransportFactory mockTransportFactory =
+ new MockIAMCredentialsServiceTransportFactory();
+ mockTransportFactory.getTransport().setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL);
+ mockTransportFactory.getTransport().setAccessToken(ACCESS_TOKEN);
+ mockTransportFactory.getTransport().setExpireTime(getDefaultExpireTime());
+ mockTransportFactory.getTransport().addStatusCodeAndMessage(HttpStatusCodes.STATUS_CODE_OK, "");
+
+ ImpersonatedCredentials targetCredentials =
+ ImpersonatedCredentials.create(
+ ImpersonatedCredentialsTest.getSourceCredentials(),
+ IMPERSONATED_CLIENT_EMAIL,
+ null,
+ IMMUTABLE_SCOPES_LIST,
+ VALID_LIFETIME,
+ mockTransportFactory);
+
+ mockTransportFactory.getTransport().setIdToken(TOKEN_WITH_EMAIL);
+
+ String targetAudience = "https://foo.bar";
+ IdTokenCredentials tokenCredential =
+ IdTokenCredentials.newBuilder()
+ .setIdTokenProvider(targetCredentials)
+ .setTargetAudience(targetAudience)
+ .setOptions(Arrays.asList(IdTokenProvider.Option.INCLUDE_EMAIL))
+ .build();
+ tokenCredential.refresh();
+ assertEquals(TOKEN_WITH_EMAIL, tokenCredential.getAccessToken().getTokenValue());
+ Payload p = tokenCredential.getIdToken().getJsonWebSignature().getPayload();
+ assertTrue(p.containsKey("email"));
+
+ assertEquals(3, testAppender.events.size());
+
+ assertEquals("Sending request to get ID token", testAppender.events.get(0).getMessage());
+ assertEquals(4, testAppender.events.get(0).getKeyValuePairs().size());
+ for (KeyValuePair kvp : testAppender.events.get(0).getKeyValuePairs()) {
+ assertTrue(
+ kvp.key.equals("request.headers")
+ || kvp.key.equals("request.payload")
+ || kvp.key.equals("request.method")
+ || kvp.key.equals("request.url"));
+ if (kvp.key.equals("request.headers") || kvp.key.equals("request.payload")) {
+ assertTrue(isValidJson((String) kvp.value));
+ }
+ }
+ assertEquals("Received response for ID token request", testAppender.events.get(1).getMessage());
+ assertEquals(3, testAppender.events.get(1).getKeyValuePairs().size());
+ for (KeyValuePair kvp : testAppender.events.get(1).getKeyValuePairs()) {
+ assertTrue(
+ kvp.key.equals("response.headers")
+ || kvp.key.equals("response.status")
+ || kvp.key.equals("response.status.message"));
+ }
+ assertEquals("Response payload for ID token request", testAppender.events.get(2).getMessage());
+ assertEquals(1, testAppender.events.get(2).getKeyValuePairs().size());
+
+ testAppender.stop();
+ }
+
+ @Test
+ public void sign_sameAs() throws IOException {
+ TestAppender testAppender = setupTestLogger(IamUtils.class);
+ MockIAMCredentialsServiceTransportFactory mockTransportFactory =
+ new MockIAMCredentialsServiceTransportFactory();
+ mockTransportFactory.getTransport().setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL);
+ mockTransportFactory.getTransport().setAccessToken(ACCESS_TOKEN);
+ mockTransportFactory.getTransport().setExpireTime(getDefaultExpireTime());
+ mockTransportFactory.getTransport().addStatusCodeAndMessage(HttpStatusCodes.STATUS_CODE_OK, "");
+ ImpersonatedCredentials targetCredentials =
+ ImpersonatedCredentials.create(
+ ImpersonatedCredentialsTest.getSourceCredentials(),
+ IMPERSONATED_CLIENT_EMAIL,
+ null,
+ IMMUTABLE_SCOPES_LIST,
+ VALID_LIFETIME,
+ mockTransportFactory);
+
+ byte[] expectedSignature = {0xD, 0xE, 0xA, 0xD};
+
+ mockTransportFactory.getTransport().setTargetPrincipal(IMPERSONATED_CLIENT_EMAIL);
+ mockTransportFactory.getTransport().setSignedBlob(expectedSignature);
+
+ assertArrayEquals(expectedSignature, targetCredentials.sign(expectedSignature));
+
+ assertEquals(3, testAppender.events.size());
+
+ assertEquals(
+ "Sending request to get signature to sign the blob",
+ testAppender.events.get(0).getMessage());
+ assertEquals(4, testAppender.events.get(0).getKeyValuePairs().size());
+ for (KeyValuePair kvp : testAppender.events.get(0).getKeyValuePairs()) {
+ assertTrue(
+ kvp.key.equals("request.headers")
+ || kvp.key.equals("request.payload")
+ || kvp.key.equals("request.method")
+ || kvp.key.equals("request.url"));
+ if (kvp.key.equals("request.headers") || kvp.key.equals("request.payload")) {
+ assertTrue(isValidJson((String) kvp.value));
+ }
+ }
+ assertEquals(
+ "Received response for signature to sign the blob",
+ testAppender.events.get(1).getMessage());
+ assertEquals(3, testAppender.events.get(1).getKeyValuePairs().size());
+ for (KeyValuePair kvp : testAppender.events.get(1).getKeyValuePairs()) {
+ assertTrue(
+ kvp.key.equals("response.headers")
+ || kvp.key.equals("response.status")
+ || kvp.key.equals("response.status.message"));
+ }
+ assertEquals("Response payload for sign blob", testAppender.events.get(2).getMessage());
+ assertEquals(1, testAppender.events.get(2).getKeyValuePairs().size());
+
+ testAppender.stop();
+ }
+
+ @Test
+ public void getRequestMetadata_hasAccessToken() throws IOException {
+ TestAppender testAppender = setupTestLogger(ComputeEngineCredentials.class);
+ MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
+ ComputeEngineCredentials credentials =
+ ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
+ Map> metadata = credentials.getRequestMetadata(CALL_URI);
+
+ TestUtils.assertContainsBearerToken(metadata, ACCESS_TOKEN);
+
+ assertEquals(3, testAppender.events.size());
+
+ assertEquals(
+ "Sending request to refresh access token", testAppender.events.get(0).getMessage());
+ assertEquals(3, testAppender.events.get(0).getKeyValuePairs().size());
+ for (KeyValuePair kvp : testAppender.events.get(0).getKeyValuePairs()) {
+ assertTrue(
+ kvp.key.equals("request.headers")
+ || kvp.key.equals("request.method")
+ || kvp.key.equals("request.url"));
+ if (kvp.key.equals("request.headers")) {
+ assertTrue(isValidJson((String) kvp.value));
+ }
+ }
+ assertEquals(
+ "Received response for refresh access token", testAppender.events.get(1).getMessage());
+ assertEquals(3, testAppender.events.get(1).getKeyValuePairs().size());
+ for (KeyValuePair kvp : testAppender.events.get(1).getKeyValuePairs()) {
+ assertTrue(
+ kvp.key.equals("response.headers")
+ || kvp.key.equals("response.status")
+ || kvp.key.equals("response.status.message"));
+ }
+ assertEquals("Response payload for access token", testAppender.events.get(2).getMessage());
+ assertEquals(3, testAppender.events.get(2).getKeyValuePairs().size());
+
+ testAppender.stop();
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void idTokenWithAudience_full() throws IOException {
+ TestAppender testAppender = setupTestLogger(ComputeEngineCredentials.class);
+ MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
+ ComputeEngineCredentials credentials =
+ ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
+
+ String targetAudience = "https://foo.bar";
+ IdTokenCredentials tokenCredential =
+ IdTokenCredentials.newBuilder()
+ .setIdTokenProvider(credentials)
+ .setTargetAudience(targetAudience)
+ .setOptions(Arrays.asList(IdTokenProvider.Option.FORMAT_FULL))
+ .build();
+ tokenCredential.refresh();
+ Payload p = tokenCredential.getIdToken().getJsonWebSignature().getPayload();
+ assertTrue("Full ID Token format not provided", p.containsKey("google"));
+ ArrayMap googleClaim = (ArrayMap) p.get("google");
+ assertTrue(googleClaim.containsKey("compute_engine"));
+
+ assertEquals(3, testAppender.events.size());
+
+ assertEquals("Sending request to get ID token", testAppender.events.get(0).getMessage());
+ assertEquals(3, testAppender.events.get(0).getKeyValuePairs().size());
+ for (KeyValuePair kvp : testAppender.events.get(0).getKeyValuePairs()) {
+ assertTrue(
+ kvp.key.equals("request.headers")
+ || kvp.key.equals("request.method")
+ || kvp.key.equals("request.url"));
+ if (kvp.key.equals("request.headers")) {
+ assertTrue(isValidJson((String) kvp.value));
+ }
+ }
+ assertEquals("Received response for ID token request", testAppender.events.get(1).getMessage());
+ assertEquals(3, testAppender.events.get(1).getKeyValuePairs().size());
+ for (KeyValuePair kvp : testAppender.events.get(1).getKeyValuePairs()) {
+ assertTrue(
+ kvp.key.equals("response.headers")
+ || kvp.key.equals("response.status")
+ || kvp.key.equals("response.status.message"));
+ }
+
+ assertEquals("Response Payload for ID token", testAppender.events.get(2).getMessage());
+ assertEquals(1, testAppender.events.get(2).getKeyValuePairs().size());
+ assertEquals("idToken", testAppender.events.get(2).getKeyValuePairs().get(0).key);
+ assertEquals(
+ ComputeEngineCredentialsTest.FULL_ID_TOKEN,
+ testAppender.events.get(2).getKeyValuePairs().get(0).value);
+ testAppender.stop();
+ }
+}
diff --git a/oauth2_http/javatests/com/google/auth/oauth2/LoggingUtilsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/LoggingUtilsTest.java
new file mode 100644
index 000000000..abad61f32
--- /dev/null
+++ b/oauth2_http/javatests/com/google/auth/oauth2/LoggingUtilsTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * * Neither the name of Google LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.google.auth.oauth2;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class LoggingUtilsTest {
+
+ private TestEnvironmentProvider testEnvironmentProvider;
+
+ @Before
+ public void setup() {
+ testEnvironmentProvider = new TestEnvironmentProvider();
+ }
+
+ @Test
+ public void testIsLoggingEnabled_true() {
+ testEnvironmentProvider.setEnv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING, "true");
+ LoggingUtils.setEnvironmentProvider(testEnvironmentProvider);
+ assertTrue(LoggingUtils.isLoggingEnabled());
+ testEnvironmentProvider.setEnv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING, "TRUE");
+ LoggingUtils.setEnvironmentProvider(testEnvironmentProvider);
+ assertTrue(LoggingUtils.isLoggingEnabled());
+ testEnvironmentProvider.setEnv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING, "True");
+ LoggingUtils.setEnvironmentProvider(testEnvironmentProvider);
+ assertTrue(LoggingUtils.isLoggingEnabled());
+ }
+
+ @Test
+ public void testIsLoggingEnabled_defaultToFalse() {
+ LoggingUtils.setEnvironmentProvider(testEnvironmentProvider);
+ assertFalse(LoggingUtils.isLoggingEnabled());
+ }
+}
diff --git a/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountCredentialsTest.java
index e32a74292..0fa1f6672 100644
--- a/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountCredentialsTest.java
+++ b/oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountCredentialsTest.java
@@ -87,7 +87,7 @@
@RunWith(JUnit4.class)
public class ServiceAccountCredentialsTest extends BaseSerializationTest {
- private static final String CLIENT_EMAIL =
+ static final String CLIENT_EMAIL =
"36680232662-vrd7ji19qe3nelgchd0ah2csanun6bnr@developer.gserviceaccount.com";
private static final String CLIENT_ID =
"36680232662-vrd7ji19qe3nelgchd0ah2csanun6bnr.apps.googleusercontent.com";
@@ -105,14 +105,14 @@ public class ServiceAccountCredentialsTest extends BaseSerializationTest {
+ "gidhycxS86dxpEljnOMCw8CKoUBd5I880IUahEiUltk7OLJYS/Ts1wbn3kPOVX3wyJs8WBDtBkFrDHW2ezth2QJ"
+ "ADj3e1YhMVdjJW5jqwlD/VNddGjgzyunmiZg0uOXsHXbytYmsA545S8KRQFaJKFXYYFo2kOjqOiC1T2cAzMDjCQ"
+ "==\n-----END PRIVATE KEY-----\n";
- private static final String ACCESS_TOKEN = "1/MkSJoj1xsli0AccessToken_NKPY2";
- private static final Collection SCOPES = Collections.singletonList("dummy.scope");
+ static final String ACCESS_TOKEN = "1/MkSJoj1xsli0AccessToken_NKPY2";
+ static final Collection SCOPES = Collections.singletonList("dummy.scope");
private static final Collection DEFAULT_SCOPES =
Collections.singletonList("dummy.default.scope");
private static final String USER = "user@example.com";
private static final String PROJECT_ID = "project-id";
private static final Collection EMPTY_SCOPES = Collections.emptyList();
- private static final URI CALL_URI = URI.create("http://googleapis.com/testapi/v1/foo");
+ static final URI CALL_URI = URI.create("http://googleapis.com/testapi/v1/foo");
private static final String JWT_AUDIENCE = "http://googleapis.com/";
private static final HttpTransportFactory DUMMY_TRANSPORT_FACTORY =
new MockTokenServerTransportFactory();
@@ -127,19 +127,20 @@ public class ServiceAccountCredentialsTest extends BaseSerializationTest {
private static final int INVALID_LIFETIME = 43210;
private static final String JWT_ACCESS_PREFIX = "Bearer ";
- private ServiceAccountCredentials.Builder createDefaultBuilderWithToken(String accessToken)
+ static ServiceAccountCredentials.Builder createDefaultBuilderWithToken(String accessToken)
throws IOException {
MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
transportFactory.transport.addServiceAccount(CLIENT_EMAIL, accessToken);
return createDefaultBuilder().setHttpTransportFactory(transportFactory);
}
- private ServiceAccountCredentials.Builder createDefaultBuilderWithScopes(
+ private static ServiceAccountCredentials.Builder createDefaultBuilderWithScopes(
Collection scopes) throws IOException {
return createDefaultBuilder().setScopes(scopes);
}
- private ServiceAccountCredentials.Builder createDefaultBuilderWithKey(PrivateKey privateKey) {
+ private static ServiceAccountCredentials.Builder createDefaultBuilderWithKey(
+ PrivateKey privateKey) {
ServiceAccountCredentials.Builder builder =
ServiceAccountCredentials.newBuilder()
.setClientId(CLIENT_ID)
@@ -153,7 +154,7 @@ private ServiceAccountCredentials.Builder createDefaultBuilderWithKey(PrivateKey
return builder;
}
- private ServiceAccountCredentials.Builder createDefaultBuilder() throws IOException {
+ static ServiceAccountCredentials.Builder createDefaultBuilder() throws IOException {
PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8);
return createDefaultBuilderWithKey(privateKey);
}
@@ -884,7 +885,7 @@ public void idTokenWithAudience_oauthFlow_targetAudienceMatchesAudClaim() throws
targetAudience,
tokenCredential.getIdToken().getJsonWebSignature().getPayload().getAudience());
- // verify id token request metrics headers
+ // verify ID token request metrics headers
Map> idTokenRequestHeader =
transportFactory.transport.getRequest().getHeaders();
com.google.auth.oauth2.TestUtils.validateMetricsHeader(idTokenRequestHeader, "it", "sa");
diff --git a/oauth2_http/javatests/com/google/auth/oauth2/Slf4jUtilsLogbackTest.java b/oauth2_http/javatests/com/google/auth/oauth2/Slf4jUtilsLogbackTest.java
new file mode 100644
index 000000000..68d1edc96
--- /dev/null
+++ b/oauth2_http/javatests/com/google/auth/oauth2/Slf4jUtilsLogbackTest.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * * Neither the name of Google LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.google.auth.oauth2;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import com.google.api.client.http.GenericUrl;
+import com.google.api.client.http.HttpRequest;
+import com.google.api.client.http.HttpRequestFactory;
+import com.google.api.client.http.UrlEncodedContent;
+import com.google.api.client.util.GenericData;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonSyntaxException;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.event.KeyValuePair;
+import org.slf4j.event.Level;
+
+// part of Slf4jUtils test that needs logback dependency
+public class Slf4jUtilsLogbackTest {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(Slf4jUtilsLogbackTest.class);
+
+ private TestEnvironmentProvider testEnvironmentProvider;
+
+ @Before
+ public void setup() {
+ testEnvironmentProvider = new TestEnvironmentProvider();
+ }
+
+ @Test
+ public void testLogWithMDC_slf4jLogger() {
+
+ TestAppender testAppender = setupTestLogger();
+
+ Map contextMap = new HashMap<>();
+ contextMap.put("key1", "value1");
+ contextMap.put("key2", "value2");
+ Slf4jUtils.logWithMDC(LOGGER, Level.DEBUG, contextMap, "test message");
+
+ assertEquals(1, testAppender.events.size());
+ assertEquals("test message", testAppender.events.get(0).getMessage());
+
+ // Verify MDC content
+ ILoggingEvent event = testAppender.events.get(0);
+ assertEquals(2, event.getMDCPropertyMap().size());
+ assertEquals(ch.qos.logback.classic.Level.DEBUG, event.getLevel());
+ assertEquals("value1", event.getMDCPropertyMap().get("key1"));
+ assertEquals("value2", event.getMDCPropertyMap().get("key2"));
+
+ testAppender.stop();
+ }
+
+ @Test
+ public void testLogWithMDC_INFO() {
+ TestAppender testAppender = setupTestLogger();
+ Slf4jUtils.logWithMDC(LOGGER, Level.INFO, new HashMap<>(), "test message");
+
+ assertEquals(1, testAppender.events.size());
+ assertEquals(ch.qos.logback.classic.Level.INFO, testAppender.events.get(0).getLevel());
+ testAppender.stop();
+ }
+
+ @Test
+ public void testLogWithMDC_TRACE_notEnabled() {
+ TestAppender testAppender = setupTestLogger();
+ Slf4jUtils.logWithMDC(LOGGER, Level.TRACE, new HashMap<>(), "test message");
+
+ assertEquals(0, testAppender.events.size());
+ testAppender.stop();
+ }
+
+ @Test
+ public void testLogWithMDC_WARN() {
+ TestAppender testAppender = setupTestLogger();
+ Slf4jUtils.logWithMDC(LOGGER, Level.WARN, new HashMap<>(), "test message");
+
+ assertEquals(1, testAppender.events.size());
+ assertEquals(ch.qos.logback.classic.Level.WARN, testAppender.events.get(0).getLevel());
+ testAppender.stop();
+ }
+
+ @Test
+ public void testLogWithMDC_ERROR() {
+ TestAppender testAppender = setupTestLogger();
+ Slf4jUtils.logWithMDC(LOGGER, Level.ERROR, new HashMap<>(), "test message");
+
+ assertEquals(1, testAppender.events.size());
+ assertEquals(ch.qos.logback.classic.Level.ERROR, testAppender.events.get(0).getLevel());
+ testAppender.stop();
+ }
+
+ @Test
+ public void testLogGenericData() {
+ // mimic GOOGLE_SDK_JAVA_LOGGING = true
+ testEnvironmentProvider.setEnv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING, "true");
+ LoggingUtils.setEnvironmentProvider(testEnvironmentProvider);
+
+ TestAppender testAppender = setupTestLogger();
+
+ GenericData data = new GenericData();
+ data.put("key1", "value1");
+ data.put("token", "value2");
+
+ LoggerProvider loggerProvider = mock(LoggerProvider.class);
+ when(loggerProvider.getLogger()).thenReturn(LOGGER);
+ LoggingUtils.logResponsePayload(data, loggerProvider, "test generic data");
+
+ assertEquals(1, testAppender.events.size());
+ List keyValuePairs = testAppender.events.get(0).getKeyValuePairs();
+ assertEquals(2, keyValuePairs.size());
+ for (KeyValuePair kvp : keyValuePairs) {
+
+ assertTrue(
+ "Key should be either 'key1' or 'token'",
+ kvp.key.equals("key1") || kvp.key.equals("token"));
+ }
+
+ testAppender.stop();
+ }
+
+ @Test
+ public void testLogRequest() throws IOException {
+ // mimic GOOGLE_SDK_JAVA_LOGGING = true
+ testEnvironmentProvider.setEnv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING, "true");
+ LoggingUtils.setEnvironmentProvider(testEnvironmentProvider);
+
+ TestAppender testAppender = setupTestLogger();
+
+ GenericData tokenRequest = new GenericData();
+ tokenRequest.set("client_id", "clientId");
+ tokenRequest.set("client_secret", "clientSecret");
+ UrlEncodedContent content = new UrlEncodedContent(tokenRequest);
+
+ MockHttpTransportFactory mockHttpTransportFactory = new MockHttpTransportFactory();
+
+ HttpRequestFactory requestFactory = mockHttpTransportFactory.create().createRequestFactory();
+ HttpRequest request =
+ requestFactory.buildPostRequest(new GenericUrl(OAuth2Utils.TOKEN_SERVER_URI), content);
+
+ LoggerProvider loggerProvider = mock(LoggerProvider.class);
+ when(loggerProvider.getLogger()).thenReturn(LOGGER);
+ LoggingUtils.logRequest(request, loggerProvider, "test log request");
+
+ assertEquals(1, testAppender.events.size());
+ assertEquals("test log request", testAppender.events.get(0).getMessage());
+ List keyValuePairs = testAppender.events.get(0).getKeyValuePairs();
+ assertEquals(4, keyValuePairs.size());
+ for (KeyValuePair kvp : testAppender.events.get(0).getKeyValuePairs()) {
+ assertTrue(
+ kvp.key.equals("request.headers")
+ || kvp.key.equals("request.payload")
+ || kvp.key.equals("request.method")
+ || kvp.key.equals("request.url"));
+ if (kvp.key.equals("request.headers") || kvp.key.equals("request.payload")) {
+ assertTrue(isValidJson((String) kvp.value));
+ }
+ }
+ testAppender.stop();
+ }
+
+ boolean isValidJson(String jsonString) {
+ try {
+ JsonParser.parseString(jsonString);
+ return true;
+ } catch (JsonSyntaxException e) {
+ return false;
+ }
+ }
+
+ private TestAppender setupTestLogger() {
+ TestAppender testAppender = new TestAppender();
+ testAppender.start();
+ ((ch.qos.logback.classic.Logger) LOGGER).addAppender(testAppender);
+ return testAppender;
+ }
+}
diff --git a/oauth2_http/javatests/com/google/auth/oauth2/Slf4jUtilsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/Slf4jUtilsTest.java
new file mode 100644
index 000000000..7251f3301
--- /dev/null
+++ b/oauth2_http/javatests/com/google/auth/oauth2/Slf4jUtilsTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * * Neither the name of Google LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.google.auth.oauth2;
+
+import static org.junit.Assert.*;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.auth.oauth2.Slf4jUtils.LoggerFactoryProvider;
+import java.util.logging.Level;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.ILoggerFactory;
+import org.slf4j.Logger;
+import org.slf4j.helpers.NOPLogger;
+
+public class Slf4jUtilsTest {
+
+ private TestEnvironmentProvider testEnvironmentProvider;
+
+ @Before
+ public void setup() {
+ testEnvironmentProvider = new TestEnvironmentProvider();
+ }
+ // This test mimics GOOGLE_SDK_JAVA_LOGGING != true
+ @Test
+ public void testGetLogger_loggingDisabled_shouldGetNOPLogger() {
+ testEnvironmentProvider.setEnv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING, "false");
+ LoggingUtils.setEnvironmentProvider(testEnvironmentProvider);
+ Logger logger = Slf4jUtils.getLogger(Slf4jUtilsTest.class);
+
+ assertEquals(NOPLogger.class, logger.getClass());
+ assertFalse(logger.isInfoEnabled());
+ assertFalse(logger.isDebugEnabled());
+ }
+
+ // This test require binding (e.g. logback) be present
+ @Test
+ public void testGetLogger_loggingEnabled_slf4jBindingPresent() {
+ testEnvironmentProvider.setEnv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING, "true");
+ LoggingUtils.setEnvironmentProvider(testEnvironmentProvider);
+ Logger logger = Slf4jUtils.getLogger(LoggingUtilsTest.class);
+ assertNotNull(logger);
+ assertNotEquals(NOPLogger.class, logger.getClass());
+ }
+
+ @Test
+ public void testGetLogger_loggingEnabled_noBinding() {
+ testEnvironmentProvider.setEnv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING, "true");
+ LoggingUtils.setEnvironmentProvider(testEnvironmentProvider);
+ // Create a mock LoggerFactoryProvider
+ LoggerFactoryProvider mockLoggerFactoryProvider = mock(LoggerFactoryProvider.class);
+ ILoggerFactory mockLoggerFactory = mock(ILoggerFactory.class);
+ when(mockLoggerFactoryProvider.getLoggerFactory()).thenReturn(mockLoggerFactory);
+ when(mockLoggerFactory.getLogger(anyString()))
+ .thenReturn(org.slf4j.helpers.NOPLogger.NOP_LOGGER);
+
+ // Use the mock LoggerFactoryProvider in getLogger()
+ Logger logger = Slf4jUtils.getLogger(LoggingUtilsTest.class, mockLoggerFactoryProvider);
+
+ // Assert that the returned logger is a NOPLogger
+ assertTrue(logger instanceof org.slf4j.helpers.NOPLogger);
+ }
+
+ @Test
+ public void testCheckIfClazzAvailable() {
+ assertFalse(Slf4jUtils.checkIfClazzAvailable("fake.class.should.not.be.in.classpath"));
+ assertTrue(Slf4jUtils.checkIfClazzAvailable("org.slf4j.event.KeyValuePair"));
+ }
+
+ @Test
+ public void testMatchLevelSevere() {
+ assertEquals(
+ org.slf4j.event.Level.ERROR, Slf4jLoggingHelpers.matchUtilLevelToSLF4JLevel(Level.SEVERE));
+ assertEquals(
+ org.slf4j.event.Level.WARN, Slf4jLoggingHelpers.matchUtilLevelToSLF4JLevel(Level.WARNING));
+ assertEquals(
+ org.slf4j.event.Level.INFO, Slf4jLoggingHelpers.matchUtilLevelToSLF4JLevel(Level.INFO));
+ assertEquals(
+ org.slf4j.event.Level.DEBUG, Slf4jLoggingHelpers.matchUtilLevelToSLF4JLevel(Level.FINE));
+ assertEquals(
+ org.slf4j.event.Level.TRACE, Slf4jLoggingHelpers.matchUtilLevelToSLF4JLevel(Level.FINER));
+ }
+}
diff --git a/oauth2_http/javatests/com/google/auth/oauth2/TestAppender.java b/oauth2_http/javatests/com/google/auth/oauth2/TestAppender.java
new file mode 100644
index 000000000..66fd046fd
--- /dev/null
+++ b/oauth2_http/javatests/com/google/auth/oauth2/TestAppender.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * * Neither the name of Google LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.google.auth.oauth2;
+
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.AppenderBase;
+import java.util.ArrayList;
+import java.util.List;
+
+/** Logback appender used to set up tests. */
+public class TestAppender extends AppenderBase {
+ public List events = new ArrayList<>();
+
+ @Override
+ protected void append(ILoggingEvent eventObject) {
+ // triggering Logback to capture the current MDC context and store it with the log event
+ eventObject.getMDCPropertyMap();
+ events.add(eventObject);
+ }
+
+ public void clearEvents() {
+ events.clear();
+ }
+}
diff --git a/oauth2_http/javatests/com/google/auth/oauth2/UserCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/UserCredentialsTest.java
index 254e6f550..d50190bc3 100644
--- a/oauth2_http/javatests/com/google/auth/oauth2/UserCredentialsTest.java
+++ b/oauth2_http/javatests/com/google/auth/oauth2/UserCredentialsTest.java
@@ -71,9 +71,9 @@
@RunWith(JUnit4.class)
public class UserCredentialsTest extends BaseSerializationTest {
- private static final String CLIENT_SECRET = "jakuaL9YyieakhECKL2SwZcu";
- private static final String CLIENT_ID = "ya29.1.AADtN_UtlxN3PuGAxrN2XQnZTVRvDyVWnYq4I6dws";
- private static final String REFRESH_TOKEN = "1/Tl6awhpFjkMkSJoj1xsli0H2eL5YsMgU_NKPY2TyGWY";
+ static final String CLIENT_SECRET = "jakuaL9YyieakhECKL2SwZcu";
+ static final String CLIENT_ID = "ya29.1.AADtN_UtlxN3PuGAxrN2XQnZTVRvDyVWnYq4I6dws";
+ static final String REFRESH_TOKEN = "1/Tl6awhpFjkMkSJoj1xsli0H2eL5YsMgU_NKPY2TyGWY";
private static final String ACCESS_TOKEN = "1/MkSJoj1xsli0AccessToken_NKPY2";
private static final String QUOTA_PROJECT = "sample-quota-project-id";
private static final Collection SCOPES = Collections.singletonList("dummy.scope");
@@ -767,7 +767,7 @@ public void IdTokenCredentials_WithUserEmailScope_success() throws IOException {
assertEquals(DEFAULT_ID_TOKEN, tokenCredential.getAccessToken().getTokenValue());
assertEquals(DEFAULT_ID_TOKEN, tokenCredential.getIdToken().getTokenValue());
- // verify id token request metrics headers, same as access token request
+ // verify ID token request metrics headers, same as access token request
Map> idTokenRequestHeader =
transportFactory.transport.getRequest().getHeaders();
com.google.auth.oauth2.TestUtils.validateMetricsHeader(idTokenRequestHeader, "untracked", "u");
diff --git a/oauth2_http/pom.xml b/oauth2_http/pom.xml
index 056abab35..fb6b0ab53 100644
--- a/oauth2_http/pom.xml
+++ b/oauth2_http/pom.xml
@@ -7,7 +7,7 @@
com.google.auth
google-auth-library-parent
- 1.32.1
+ 1.33.0
../pom.xml
@@ -68,8 +68,94 @@
+
+ maven-compiler-plugin
+ 3.14.0
+
+ 1.8
+ 1.8
+ UTF-8
+
+
+ **/Slf4jUtilsLogbackTest.java
+ **/Slf4jUtils1xTest.java
+ **/Slf4jUtilsTest.java
+ **/TestAppender.java
+ **/LoggingTest.java
+
+
+
+
+
+
+
+ slf4j2x
+
+ true
+
+
+
+
+ maven-compiler-plugin
+ 3.14.0
+
+ 1.8
+ 1.8
+ UTF-8
+
+
+
+ **/Slf4jUtilsLogbackTest.java
+ **/Slf4jUtils1xTest.java
+ **/Slf4jUtilsTest.java
+ **/TestAppender.java
+ **/LoggingTest.java
+
+
+
+
+
+ org.slf4j
+ slf4j-api
+ ${project.slf4j.version}
+ true
+
+
+ com.google.code.gson
+ gson
+ ${project.gson.version}
+
+
+
+
+ slf4j2x-test
+
+
+
+
+ org.slf4j
+ slf4j-api
+ ${project.slf4j.version}
+
+
+
+ ch.qos.logback
+ logback-classic
+ 1.5.16
+ test
+
+
+ ch.qos.logback
+ logback-core
+ 1.5.16
+ test
+
+
diff --git a/pom.xml b/pom.xml
index fc6203a50..be506b5ed 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
4.0.0
com.google.auth
google-auth-library-parent
- 1.32.1
+ 1.33.0
pom
Google Auth Library for Java
Client libraries providing authentication and
@@ -16,7 +16,7 @@
com.google.cloud
google-cloud-shared-config
- 1.14.2
+ 1.14.3
@@ -74,16 +74,18 @@
UTF-8
- 1.46.0
+ 1.46.2
4.13.2
33.4.0-android
- 2.0.32
+ 2.0.33
3.0.2
false
2.36.0
3.25.5
0.9.0-proto3
1.15.0
+ 2.0.16
+ 2.12.1
@@ -278,7 +280,7 @@
maven-compiler-plugin
- 3.13.0
+ 3.14.0
1.8
1.8
@@ -419,6 +421,35 @@
+
+ slf4j2x-test
+
+
+
+
+
+
+ maven-compiler-plugin
+ 3.14.0
+
+ 1.8
+ 1.8
+ UTF-8
+
+
+ **/Slf4jUtilsLogbackTest.java
+ **/Slf4jUtils1xTest.java
+ **/Slf4jUtilsTest.java
+ **/TestAppender.java
+ **/LoggingTest.java
+
+
+
+
+
+
release-sign-artifacts
diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml
index 023113d1e..ec78365dc 100644
--- a/samples/snippets/pom.xml
+++ b/samples/snippets/pom.xml
@@ -30,7 +30,7 @@
com.google.cloud
libraries-bom
- 26.54.0
+ 26.55.0
pom
import
@@ -43,14 +43,14 @@
com.google.auth
google-auth-library-oauth2-http
- 1.32.0
+ 1.32.1
com.google.cloud
google-iam-admin
- 3.52.0
+ 3.53.0
diff --git a/versions.txt b/versions.txt
index 7f688a632..01c7e4438 100644
--- a/versions.txt
+++ b/versions.txt
@@ -1,9 +1,9 @@
# Format:
# module:released-version:current-version
-google-auth-library:1.32.1:1.32.1
-google-auth-library-bom:1.32.1:1.32.1
-google-auth-library-parent:1.32.1:1.32.1
-google-auth-library-appengine:1.32.1:1.32.1
-google-auth-library-credentials:1.32.1:1.32.1
-google-auth-library-oauth2-http:1.32.1:1.32.1
+google-auth-library:1.33.0:1.33.0
+google-auth-library-bom:1.33.0:1.33.0
+google-auth-library-parent:1.33.0:1.33.0
+google-auth-library-appengine:1.33.0:1.33.0
+google-auth-library-credentials:1.33.0:1.33.0
+google-auth-library-oauth2-http:1.33.0:1.33.0