Skip to content

Commit c8cd68b

Browse files
committed
fix token endpoint parsing
1 parent 28adbb7 commit c8cd68b

2 files changed

Lines changed: 74 additions & 5 deletions

File tree

common4j/src/main/com/microsoft/identity/common/java/providers/microsoft/microsoftsts/AbstractMicrosoftStsTokenResponseHandler.java

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import com.microsoft.identity.common.java.exception.ClientException;
3232
import com.microsoft.identity.common.java.flighting.CommonFlight;
3333
import com.microsoft.identity.common.java.flighting.CommonFlightsManager;
34+
import com.microsoft.identity.common.java.logging.Logger;
3435
import com.microsoft.identity.common.java.net.HttpResponse;
3536
import com.microsoft.identity.common.java.opentelemetry.AttributeName;
3637
import com.microsoft.identity.common.java.opentelemetry.SpanExtension;
@@ -43,6 +44,7 @@
4344
import com.microsoft.identity.common.java.util.HeaderSerializationUtil;
4445
import com.microsoft.identity.common.java.util.ObjectMapper;
4546
import com.microsoft.identity.common.java.util.ResultUtil;
47+
import com.microsoft.identity.common.java.util.StringUtil;
4648

4749
import java.net.HttpURLConnection;
4850
import java.util.HashMap;
@@ -109,11 +111,25 @@ public TokenResult handleTokenResponse(@NonNull final HttpResponse response) thr
109111
}
110112

111113
final String clientDataHeader = response.getHeaderValue(X_MS_CLIENTDATA, 0);
112-
if (CommonFlightsManager.INSTANCE.getFlightsProvider().isFlightEnabled(CommonFlight.ENABLE_SERVER_CLIENT_DATA_TELEMETRY)) {
113-
final ClientDataInfo clientDataInfo = ClientDataInfo.fromPipeDelimited(clientDataHeader);
114-
if (null != clientDataInfo) {
115-
clientDataInfo.emitToSpan();
116-
result.setClientDataInfo(clientDataInfo);
114+
if (!StringUtil.isNullOrEmpty(clientDataHeader)
115+
&& CommonFlightsManager.INSTANCE.getFlightsProvider().isFlightEnabled(CommonFlight.ENABLE_SERVER_CLIENT_DATA_TELEMETRY)) {
116+
// eSTS URL-encodes the pipe-delimited value in the response header (e.g. "m%7C0x800482A5%7C%7C...").
117+
// ClientDataInfo.fromPipeDelimited expects an already-decoded value (its contract matches the
118+
// authorize-endpoint path, where UrlUtil#getParameters has already decoded the query param).
119+
// Decode here so the token-endpoint header path produces the same shape.
120+
String decodedClientDataHeader = null;
121+
try {
122+
decodedClientDataHeader = StringUtil.urlFormDecode(clientDataHeader);
123+
} catch (final Exception e) {
124+
// Malformed percent-encoding shouldn't break token parsing; swallow and fall through.
125+
Logger.warn(methodTag, "Failed to URL-decode x-ms-clientdata header: " + e.getMessage());
126+
}
127+
if (decodedClientDataHeader != null) {
128+
final ClientDataInfo clientDataInfo = ClientDataInfo.fromPipeDelimited(decodedClientDataHeader);
129+
if (null != clientDataInfo) {
130+
clientDataInfo.emitToSpan();
131+
result.setClientDataInfo(clientDataInfo);
132+
}
117133
}
118134
}
119135

common4j/src/test/com/microsoft/identity/common/java/providers/microsoft/microsoftsts/MicrosoftStsTokenResponseHandlerTest.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,59 @@ public void testHandleTokenResponse_withClientDataHeader_attributesEmitted() {
139139
}
140140
}
141141

142+
@SneakyThrows
143+
@Test
144+
public void testHandleTokenResponse_withUrlEncodedClientDataHeader_attributesEmitted() {
145+
// eSTS URL-encodes pipe separators in the response header (e.g. "%7C" for "|").
146+
// Real-world example: x-ms-clientdata=[m%7C0x800482A5%7C%7Cmicrosoftonline.com%7Cnone]
147+
final String encodedClientDataHeader = "m%7C0x800482A5%7C%7Cmicrosoftonline.com%7Cnone";
148+
149+
final HashMap<String, List<String>> headers = new HashMap<>();
150+
headers.put("Content-Type", Collections.singletonList("application/json; charset=utf-8"));
151+
headers.put(HttpConstants.HeaderField.X_MS_CLIENTDATA,
152+
Collections.singletonList(encodedClientDataHeader));
153+
154+
final HttpResponse response = new HttpResponse(200, MOCK_TOKEN_SUCCESS_RESPONSE, headers);
155+
final MicrosoftStsTokenResponseHandler handler = new MicrosoftStsTokenResponseHandler();
156+
157+
final Span mockSpan = mock(Span.class);
158+
when(mockSpan.setAttribute(Mockito.anyString(), Mockito.anyString())).thenReturn(mockSpan);
159+
160+
try (MockedStatic<SpanExtension> mockedExtension = Mockito.mockStatic(SpanExtension.class)) {
161+
mockedExtension.when(SpanExtension::current).thenReturn(mockSpan);
162+
163+
final TokenResult tokenResult = handler.handleTokenResponse(response);
164+
165+
Assert.assertNotNull(tokenResult);
166+
Assert.assertTrue(tokenResult.getSuccess());
167+
Assert.assertNotNull(tokenResult.getClientDataInfo());
168+
verify(mockSpan).setAttribute(AttributeName.server_error.name(), "0x800482A5");
169+
verify(mockSpan).setAttribute(AttributeName.account_type.name(), "MSA");
170+
}
171+
}
172+
173+
@SneakyThrows
174+
@Test
175+
public void testHandleTokenResponse_withMalformedPercentEncoding_doesNotCrash() {
176+
// Lone '%' is invalid percent-encoding; decode should fail gracefully and skip ClientDataInfo.
177+
final String malformedHeader = "e|AADSTS50058|%ZZ|us|public";
178+
179+
final HashMap<String, List<String>> headers = new HashMap<>();
180+
headers.put("Content-Type", Collections.singletonList("application/json; charset=utf-8"));
181+
headers.put(HttpConstants.HeaderField.X_MS_CLIENTDATA,
182+
Collections.singletonList(malformedHeader));
183+
184+
final HttpResponse response = new HttpResponse(200, MOCK_TOKEN_SUCCESS_RESPONSE, headers);
185+
final MicrosoftStsTokenResponseHandler handler = new MicrosoftStsTokenResponseHandler();
186+
187+
final TokenResult tokenResult = handler.handleTokenResponse(response);
188+
189+
Assert.assertNotNull(tokenResult);
190+
Assert.assertTrue(tokenResult.getSuccess());
191+
// Malformed encoding => null ClientDataInfo, but token result still valid.
192+
Assert.assertNull(tokenResult.getClientDataInfo());
193+
}
194+
142195
@SneakyThrows
143196
@Test
144197
public void testHandleTokenResponse_noClientDataHeader_doesNotCrash() {

0 commit comments

Comments
 (0)