@@ -627,6 +627,8 @@ void stsRequestHandler_exchangeToken_masksSensitiveTokens() throws IOException {
627627 testAppender .stop ();
628628 }
629629
630+ // We specifically test ImpersonatedCredentials here because it constructs its HTTP requests
631+ // using JsonHttpContent, unlike most other credentials which use UrlEncodedContent.
630632 @ Test
631633 void impersonatedCredentials_exchangeToken_masksSensitiveTokens ()
632634 throws IOException , IllegalStateException {
@@ -650,7 +652,21 @@ void impersonatedCredentials_exchangeToken_masksSensitiveTokens()
650652
651653 assertEquals (3 , testAppender .events .size ());
652654
653- // Verify response payload has tokens masked
655+ // 1. Verify request log contains properly formatted payload (JsonHttpContent masking)
656+ ILoggingEvent requestLog = testAppender .events .get (0 );
657+ assertEquals ("Sending request to refresh access token" , requestLog .getMessage ());
658+ String requestPayload = null ;
659+ for (KeyValuePair kvp : requestLog .getKeyValuePairs ()) {
660+ if ("request.payload" .equals (kvp .key )) {
661+ requestPayload = (String ) kvp .value ;
662+ }
663+ }
664+ // When logged at DEBUG level, the request payload should be present and valid JSON.
665+ if (requestPayload != null ) {
666+ assertTrue (isValidJson (requestPayload ), "Request payload should be valid JSON" );
667+ }
668+
669+ // 2. Verify response payload has tokens masked
654670 assertEquals ("Response payload for access token" , testAppender .events .get (2 ).getMessage ());
655671 boolean foundAccessToken = false ;
656672 for (KeyValuePair kvp : testAppender .events .get (2 ).getKeyValuePairs ()) {
@@ -669,4 +685,66 @@ void impersonatedCredentials_exchangeToken_masksSensitiveTokens()
669685 assertTrue (foundAccessToken , "Expected accessToken in response payload logs" );
670686 testAppender .stop ();
671687 }
688+
689+ // We specifically use ImpersonatedCredentials for this test because its request payload
690+ // is formatted using JsonHttpContent, whereas other credentials primarily use UrlEncodedContent.
691+ @ Test
692+ void impersonatedCredentials_requestPayload_masksJsonHttpContentSensitiveKeys ()
693+ throws IOException , IllegalStateException {
694+ // Set DEBUG level to ensure request payloads are logged
695+ Logger logger = LoggerFactory .getLogger (ImpersonatedCredentials .class );
696+ ch .qos .logback .classic .Logger logbackLogger = (ch .qos .logback .classic .Logger ) logger ;
697+ ch .qos .logback .classic .Level previousLevel = logbackLogger .getLevel ();
698+ logbackLogger .setLevel (ch .qos .logback .classic .Level .DEBUG );
699+
700+ TestAppender testAppender = new TestAppender ();
701+ testAppender .start ();
702+ logbackLogger .addAppender (testAppender );
703+
704+ try {
705+ MockIAMCredentialsServiceTransportFactory mockTransportFactory =
706+ new MockIAMCredentialsServiceTransportFactory ();
707+ mockTransportFactory .getTransport ().setTargetPrincipal (IMPERSONATED_CLIENT_EMAIL );
708+ mockTransportFactory .getTransport ().setAccessToken (ACCESS_TOKEN );
709+ mockTransportFactory .getTransport ().setExpireTime (getDefaultExpireTime ());
710+ mockTransportFactory
711+ .getTransport ()
712+ .addStatusCodeAndMessage (HttpStatusCodes .STATUS_CODE_OK , "" );
713+ ImpersonatedCredentials targetCredentials =
714+ ImpersonatedCredentials .create (
715+ ImpersonatedCredentialsTest .getSourceCredentials (),
716+ IMPERSONATED_CLIENT_EMAIL ,
717+ null ,
718+ IMMUTABLE_SCOPES_LIST ,
719+ VALID_LIFETIME ,
720+ mockTransportFactory );
721+
722+ targetCredentials .refreshAccessToken ();
723+
724+ // Find the request log event
725+ ILoggingEvent requestLog = testAppender .events .get (0 );
726+ assertEquals ("Sending request to refresh access token" , requestLog .getMessage ());
727+
728+ // Extract request.payload
729+ String requestPayload = null ;
730+ for (KeyValuePair kvp : requestLog .getKeyValuePairs ()) {
731+ if ("request.payload" .equals (kvp .key )) {
732+ requestPayload = (String ) kvp .value ;
733+ }
734+ }
735+
736+ // At DEBUG level, request payload must be present
737+ assertNotNull (requestPayload , "Request payload should be logged at DEBUG level" );
738+ assertTrue (isValidJson (requestPayload ), "Request payload should be valid JSON" );
739+
740+ // The request payload uses JsonHttpContent with fields: delegates, scope, lifetime. None of
741+ // these are in SENSITIVE_KEYS, so they should appear as-is (not hashed).
742+ assertFalse (
743+ requestPayload .contains ("\" delegates\" :null" ),
744+ "Payload should be properly serialized from JsonHttpContent" );
745+ } finally {
746+ logbackLogger .setLevel (previousLevel );
747+ testAppender .stop ();
748+ }
749+ }
672750}
0 commit comments