Skip to content
This repository was archived by the owner on May 12, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ static void logResponse(HttpResponse response, LoggerProvider loggerProvider, St
responseLogDataMap.put("response.status", String.valueOf(response.getStatusCode()));
responseLogDataMap.put("response.status.message", response.getStatusMessage());

Map<String, Object> headers = new HashMap<>(response.getHeaders());
responseLogDataMap.put("response.headers", headers.toString());
Map<String, Object> headers = parseGenericData(response.getHeaders());
responseLogDataMap.put("response.headers", gson.toJson(headers));
Slf4jUtils.log(logger, org.slf4j.event.Level.INFO, responseLogDataMap, message);
}
} catch (Exception e) {
Expand All @@ -138,12 +138,24 @@ static void logResponsePayload(
}
}

/**
* Generic log method for non-standard request/response/payload logging.
*
* <p>Any key in the provided {@code contextMap} that matches the {@code SENSITIVE_KEYS} set will
* have its value masked via SHA-256 hash before being logged.
*
* @param loggerProvider the logger provider for the calling class
* @param level the java.util.logging level to map to SLF4J
* @param contextMap the key-value pairs to log
* @param message the log message
*/
static void log(
LoggerProvider loggerProvider, Level level, Map<String, Object> contextMap, String message) {
try {
Logger logger = loggerProvider.getLogger();
org.slf4j.event.Level slf4jLevel = matchUtilLevelToSLF4JLevel(level);
Slf4jUtils.log(logger, slf4jLevel, contextMap, message);
Map<String, Object> maskedContextMap = parseGenericData(contextMap);
Slf4jUtils.log(logger, slf4jLevel, maskedContextMap, message);
} catch (Exception e) {
// let logging fail silently
}
Expand Down
80 changes: 79 additions & 1 deletion oauth2_http/javatests/com/google/auth/oauth2/LoggingTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,8 @@ void stsRequestHandler_exchangeToken_masksSensitiveTokens() throws IOException {
testAppender.stop();
}

// We specifically test ImpersonatedCredentials here because it constructs its HTTP requests
// using JsonHttpContent, unlike most other credentials which use UrlEncodedContent.
@Test
void impersonatedCredentials_exchangeToken_masksSensitiveTokens()
throws IOException, IllegalStateException {
Expand All @@ -650,7 +652,21 @@ void impersonatedCredentials_exchangeToken_masksSensitiveTokens()

assertEquals(3, testAppender.events.size());

// Verify response payload has tokens masked
// 1. Verify request log contains properly formatted payload (JsonHttpContent masking)
ILoggingEvent requestLog = testAppender.events.get(0);
assertEquals("Sending request to refresh access token", requestLog.getMessage());
String requestPayload = null;
for (KeyValuePair kvp : requestLog.getKeyValuePairs()) {
if ("request.payload".equals(kvp.key)) {
requestPayload = (String) kvp.value;
}
}
// When logged at DEBUG level, the request payload should be present and valid JSON.
if (requestPayload != null) {
assertTrue(isValidJson(requestPayload), "Request payload should be valid JSON");
}

// 2. Verify response payload has tokens masked
assertEquals("Response payload for access token", testAppender.events.get(2).getMessage());
boolean foundAccessToken = false;
for (KeyValuePair kvp : testAppender.events.get(2).getKeyValuePairs()) {
Expand All @@ -669,4 +685,66 @@ void impersonatedCredentials_exchangeToken_masksSensitiveTokens()
assertTrue(foundAccessToken, "Expected accessToken in response payload logs");
testAppender.stop();
}

// We specifically use ImpersonatedCredentials for this test because its request payload
// is formatted using JsonHttpContent, whereas other credentials primarily use UrlEncodedContent.
@Test
void impersonatedCredentials_requestPayload_masksJsonHttpContentSensitiveKeys()
throws IOException, IllegalStateException {
// Set DEBUG level to ensure request payloads are logged
Logger logger = LoggerFactory.getLogger(ImpersonatedCredentials.class);
ch.qos.logback.classic.Logger logbackLogger = (ch.qos.logback.classic.Logger) logger;
ch.qos.logback.classic.Level previousLevel = logbackLogger.getLevel();
logbackLogger.setLevel(ch.qos.logback.classic.Level.DEBUG);

TestAppender testAppender = new TestAppender();
testAppender.start();
logbackLogger.addAppender(testAppender);

try {
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);

targetCredentials.refreshAccessToken();

// Find the request log event
ILoggingEvent requestLog = testAppender.events.get(0);
assertEquals("Sending request to refresh access token", requestLog.getMessage());

// Extract request.payload
String requestPayload = null;
for (KeyValuePair kvp : requestLog.getKeyValuePairs()) {
if ("request.payload".equals(kvp.key)) {
requestPayload = (String) kvp.value;
}
}

// At DEBUG level, request payload must be present
assertNotNull(requestPayload, "Request payload should be logged at DEBUG level");
assertTrue(isValidJson(requestPayload), "Request payload should be valid JSON");

// The request payload uses JsonHttpContent with fields: delegates, scope, lifetime. None of
// these are in SENSITIVE_KEYS, so they should appear as-is (not hashed).
assertFalse(
requestPayload.contains("\"delegates\":null"),
"Payload should be properly serialized from JsonHttpContent");
} finally {
logbackLogger.setLevel(previousLevel);
testAppender.stop();
}
}
}
Loading