Skip to content
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
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ vNext

Version 24.2.1-RC1
----------
- [PATCH] Wire ClientDataInfo through AcquireTokenResult, exceptions (#3109)
- [MINOR] Add additional step ID and blocking error constants for full onboarding telemetry coverage (#3117)
- [MINOR] Add onboarding telemetry blob fields to BrokerRequest/BrokerResult and command parameters for client↔broker IPC transport (#3111)
- [MINOR] Add onboarding telemetry recorder, field keys, and session persistence for mobile onboarding flow (#3088)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ private static class SerializedNames {
static final String SPE_RING = "spe_ring";
static final String CLI_TELEM_ERRORCODE = "cli_telem_error_code";
static final String CLI_TELEM_SUB_ERROR_CODE = "cli_telem_suberror_code";
static final String CLIENT_DATA_INFO = "client_data_info";
static final String ONBOARDING_BLOB = "onboarding_blob";
}

Expand Down Expand Up @@ -237,6 +238,13 @@ private static class SerializedNames {
@SerializedName(SerializedNames.REFRESH_TOKEN_AGE)
private String mRefreshTokenAge;

/**
* Server client data info from x-ms-clientdata response header (pipe-delimited format).
*/
@Nullable
@SerializedName(SerializedNames.CLIENT_DATA_INFO)
private String mClientDataInfoRaw;

/**
* Boolean to indicate if the request succeeded without exceptions.
*/
Expand Down Expand Up @@ -355,6 +363,7 @@ private BrokerResult(@NonNull final Builder builder) {
mCachedAt = builder.mCachedAt;
mSpeRing = builder.mSpeRing;
mRefreshTokenAge = builder.mRefreshTokenAge;
mClientDataInfoRaw = builder.mClientDataInfoRaw;
mSuccess = builder.mSuccess;
mTenantProfileData = builder.mTenantProfileData;
mServicedFromCache = builder.mServicedFromCache;
Expand Down Expand Up @@ -435,6 +444,11 @@ public String getSpeRing() {
return mSpeRing;
}

@Nullable
public String getClientDataInfoRaw() {
return mClientDataInfoRaw;
}

public long getCachedAt() {
return mCachedAt;
}
Expand Down Expand Up @@ -532,6 +546,7 @@ public static class Builder {
private long mCachedAt;
private String mSpeRing;
private String mRefreshTokenAge;
private String mClientDataInfoRaw;
private boolean mSuccess;
private String mNegotiatedBrokerProtocolVersion;
private List<ICacheRecord> mTenantProfileData;
Expand Down Expand Up @@ -645,6 +660,11 @@ public Builder refreshTokenAge(final String refreshTokenAge) {
return this;
}

public Builder clientDataInfoRaw(@Nullable final String clientDataInfoRaw) {
this.mClientDataInfoRaw = clientDataInfoRaw;
return this;
}

public Builder success(boolean success) {
this.mSuccess = success;
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,15 @@
import com.microsoft.identity.common.java.result.AcquireTokenResult;
import com.microsoft.identity.common.java.result.GenerateShrResult;
import com.microsoft.identity.common.java.result.LocalAuthenticationResult;
import com.microsoft.identity.common.java.telemetry.ClientDataInfo;
import com.microsoft.identity.common.java.ui.PreferredAuthMethod;
import com.microsoft.identity.common.java.util.ThreadUtils;
import com.microsoft.identity.common.java.flighting.CommonFlight;
import com.microsoft.identity.common.java.flighting.CommonFlightsManager;
import com.microsoft.identity.common.java.providers.RawAuthorizationResult;
import com.microsoft.identity.common.java.providers.microsoft.microsoftsts.MicrosoftStsAuthorizationRequest;
import com.microsoft.identity.common.java.providers.microsoft.microsoftsts.MicrosoftStsAuthorizationResponse;
import com.microsoft.identity.common.java.providers.microsoft.microsoftsts.MicrosoftStsAuthorizationResult;
import com.microsoft.identity.common.java.providers.microsoft.microsoftsts.MicrosoftStsTokenRequest;
import com.microsoft.identity.common.java.providers.oauth2.AuthorizationRequest;
import com.microsoft.identity.common.java.providers.oauth2.AuthorizationResult;
Expand Down Expand Up @@ -205,6 +209,23 @@ public AcquireTokenResult acquireToken(
false
)
);

// Set ClientDataInfo on the LocalAuthenticationResult for IPC propagation.
// Prefer the token-endpoint value (later, more authoritative); fall back to the
// authorize-endpoint value.
final LocalAuthenticationResult localResult =
(LocalAuthenticationResult) acquireTokenResult.getLocalAuthenticationResult();
if (CommonFlightsManager.INSTANCE.getFlightsProvider().isFlightEnabled(CommonFlight.ENABLE_SERVER_CLIENT_DATA_TELEMETRY)) {
if (tokenResult != null && tokenResult.getClientDataInfo() != null) {
localResult.setClientDataInfo(tokenResult.getClientDataInfo());
} else if (result instanceof MicrosoftStsAuthorizationResult) {
final ClientDataInfo authClientData =
((MicrosoftStsAuthorizationResult) result).getClientDataInfo();
if (authClientData != null) {
localResult.setClientDataInfo(authClientData);
}
}
}
}
}

Expand Down Expand Up @@ -764,6 +785,13 @@ public AcquireTokenResult acquireDeviceCodeFlowToken(
false
)
);

// Set ClientDataInfo on the LocalAuthenticationResult for IPC propagation
if (tokenResult != null && CommonFlightsManager.INSTANCE.getFlightsProvider().isFlightEnabled(CommonFlight.ENABLE_SERVER_CLIENT_DATA_TELEMETRY)) {
final LocalAuthenticationResult localResult =
(LocalAuthenticationResult) acquireTokenResult.getLocalAuthenticationResult();
localResult.setClientDataInfo(tokenResult.getClientDataInfo());
}
} catch (Exception error) {
Telemetry.emit(
new ApiEndEvent()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
import com.microsoft.identity.common.java.result.GenerateShrResult;
import com.microsoft.identity.common.java.result.ILocalAuthenticationResult;
import com.microsoft.identity.common.java.result.LocalAuthenticationResult;
import com.microsoft.identity.common.java.telemetry.ClientDataInfo;
import com.microsoft.identity.common.java.ui.PreferredAuthMethod;
import com.microsoft.identity.common.java.util.BrokerProtocolVersionUtil;
import com.microsoft.identity.common.java.util.HeaderSerializationUtil;
Expand Down Expand Up @@ -284,6 +285,18 @@ public Bundle bundleFromAuthenticationResultForWebApps(@NonNull final ILocalAuth
.success(true)
.servicedFromCache(authenticationResult.isServicedFromCache());

// Serialize ClientDataInfo as raw pipe-delimited string for IPC transfer.
// The raw field is populated by ClientDataInfo.fromPipeDelimited(), the only
// path that populates the parsed fields, so it is safe to ship as-is.
if (CommonFlightsManager.INSTANCE.getFlightsProvider().isFlightEnabled(CommonFlight.ENABLE_SERVER_CLIENT_DATA_TELEMETRY)
&& authenticationResult instanceof LocalAuthenticationResult) {
final ClientDataInfo clientDataInfo =
((LocalAuthenticationResult) authenticationResult).getClientDataInfo();
if (clientDataInfo != null) {
brokerResultBuilder.clientDataInfoRaw(clientDataInfo.getRaw());
}
}

if (shouldRemoveRefreshTokenFromResult(authenticationResult, negotiatedBrokerProtocolVersion)){
brokerResultBuilder.tenantProfileRecords(
removeRefreshTokenFromCacheRecords(
Expand Down Expand Up @@ -411,6 +424,13 @@ public Bundle bundleFromBaseException(@NonNull final BaseException exception,
.speRing(exception.getSpeRing())
.refreshTokenAge(exception.getRefreshTokenAge());

// Serialize ClientDataInfo (server telemetry from x-ms-clientdata) so it
// survives the broker IPC boundary on error paths.
if (exception.getClientDataInfo() != null
&& CommonFlightsManager.INSTANCE.getFlightsProvider().isFlightEnabled(CommonFlight.ENABLE_SERVER_CLIENT_DATA_TELEMETRY)) {
builder.clientDataInfoRaw(exception.getClientDataInfo().getRaw());
}

if (exception instanceof ServiceException) {
final ServiceException serviceException = (ServiceException) exception;
builder.subErrorCode(serviceException.getSubErrorCode())
Expand Down Expand Up @@ -483,12 +503,23 @@ public ILocalAuthenticationResult authenticationResultFromBrokerResult(@NonNull
throw new ClientException(INVALID_BROKER_BUNDLE, "getTenantProfileData is null.");
}

return new LocalAuthenticationResult(
final LocalAuthenticationResult localAuthResult = new LocalAuthenticationResult(
tenantProfileCacheRecords.get(0),
tenantProfileCacheRecords,
SdkType.MSAL,
brokerResult.isServicedFromCache()
);

// Deserialize ClientDataInfo from the broker result if available
if (CommonFlightsManager.INSTANCE.getFlightsProvider().isFlightEnabled(CommonFlight.ENABLE_SERVER_CLIENT_DATA_TELEMETRY)) {
final ClientDataInfo clientDataInfo =
ClientDataInfo.fromPipeDelimited(brokerResult.getClientDataInfoRaw());
if (clientDataInfo != null) {
localAuthResult.setClientDataInfo(clientDataInfo);
}
}

return localAuthResult;
}

@NonNull
Expand Down Expand Up @@ -523,6 +554,15 @@ public BaseException getBaseExceptionFromBundle(@NonNull final Bundle resultBund
baseException.setBrokerPerformanceMetrics(metrics);
}

// Restore ClientDataInfo (server telemetry) from the broker result so callers
// catching the exception can inspect server-side error context.
if (!StringUtil.isNullOrEmpty(brokerResult.getClientDataInfoRaw())
&& CommonFlightsManager.INSTANCE.getFlightsProvider().isFlightEnabled(CommonFlight.ENABLE_SERVER_CLIENT_DATA_TELEMETRY)) {
baseException.setClientDataInfo(
ClientDataInfo.fromPipeDelimited(brokerResult.getClientDataInfoRaw())
);
}

// Set broker app info if available
if (resultBundle.containsKey(AuthenticationConstants.Broker.BROKER_VERSION)) {
baseException.setBrokerAppVersion(
Expand Down Expand Up @@ -1034,7 +1074,9 @@ public AcquireTokenResult getDeviceCodeFlowTokenResultFromResultBundle(@NonNull

if (resultBundle.getBoolean(AuthenticationConstants.Broker.BROKER_REQUEST_V2_SUCCESS)) {
final AcquireTokenResult acquireTokenResult = new AcquireTokenResult();
acquireTokenResult.setLocalAuthenticationResult(authenticationResultFromBundle(resultBundle));
final ILocalAuthenticationResult authResult = authenticationResultFromBundle(resultBundle);
acquireTokenResult.setLocalAuthenticationResult(authResult);

span.setStatus(StatusCode.OK);
return acquireTokenResult;
} else if (brokerResult.getErrorCode().equals(ErrorStrings.DEVICE_CODE_FLOW_AUTHORIZATION_PENDING_ERROR_CODE)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import com.microsoft.identity.common.java.exception.ClientException
import com.microsoft.identity.common.java.exception.UiRequiredException
import com.microsoft.identity.common.java.request.SdkType
import com.microsoft.identity.common.java.result.LocalAuthenticationResult
import com.microsoft.identity.common.java.telemetry.ClientDataInfo
import com.microsoft.identity.common.java.util.SchemaUtil
import com.microsoft.identity.internal.testutils.MockRecords
import lombok.SneakyThrows
Expand Down Expand Up @@ -659,6 +660,83 @@ class MsalBrokerResultAdapterTests {
)
}

// ==================== ClientDataInfo IPC round-trip tests (PR #3109) ====================

private val clientDataRaw = "m|AADSTS50058|login_required|us|public"

private fun newCacheRecord() = CacheRecord.builder()
.account(MockRecords.getMockAccountRecord_AAD())
.idToken(MockRecords.getMockIdTokenRecord_AAD())
.accessToken(MockRecords.getMockAccessTokenRecord_AAD())
.refreshToken(MockRecords.getMockRefreshTokenRecord_AAD())
.build()

@Test
fun testClientDataInfo_RoundTripsThroughBrokerResult_OnSuccess() {
val cacheRecord = newCacheRecord()
val cacheRecords: MutableList<ICacheRecord> = arrayListOf(cacheRecord)
val authResult = LocalAuthenticationResult(cacheRecord, cacheRecords, SdkType.MSAL, false)
authResult.clientDataInfo = ClientDataInfo.fromPipeDelimited(clientDataRaw)

val brokerResult = getInstance().buildBrokerResultFromAuthenticationResult(authResult, "16.0")
assertEquals("Raw payload should be serialized into BrokerResult", clientDataRaw, brokerResult.clientDataInfoRaw)
}

@Test
fun testClientDataInfo_NullOnLocalAuthResult_ResultsInNullOnBrokerResult() {
val cacheRecord = newCacheRecord()
val cacheRecords: MutableList<ICacheRecord> = arrayListOf(cacheRecord)
val authResult = LocalAuthenticationResult(cacheRecord, cacheRecords, SdkType.MSAL, false)
// No ClientDataInfo set

val brokerResult = getInstance().buildBrokerResultFromAuthenticationResult(authResult, "16.0")
assertNull(brokerResult.clientDataInfoRaw)
}

@Test
fun testClientDataInfo_RoundTripsThroughBaseExceptionBundle() {
val exception = ClientException("invalid_grant", "token failure")
exception.clientDataInfo = ClientDataInfo.fromPipeDelimited(clientDataRaw)

val resultAdapter = MsalBrokerResultAdapter()
val resultBundle = resultAdapter.bundleFromBaseException(exception, null)
val brokerResult = resultAdapter.brokerResultFromBundle(resultBundle)
assertEquals(clientDataRaw, brokerResult.clientDataInfoRaw)

val received = resultAdapter.getBaseExceptionFromBundle(resultBundle)
assertNotNull("ClientDataInfo should be reconstructed on the exception", received.clientDataInfo)
assertEquals("AADSTS50058", received.clientDataInfo!!.error)
assertEquals("login_required", received.clientDataInfo!!.subError)
assertEquals(clientDataRaw, received.clientDataInfo!!.raw)
}

@Test
fun testClientDataInfo_NullOnException_NotInBundle() {
val exception = ClientException("invalid_grant", "token failure")
// No ClientDataInfo set

val resultAdapter = MsalBrokerResultAdapter()
val resultBundle = resultAdapter.bundleFromBaseException(exception, null)
val received = resultAdapter.getBaseExceptionFromBundle(resultBundle)
assertNull(received.clientDataInfo)
}

@Test
fun testClientDataInfo_RoundTripsThroughGetAcquireTokenResultFromResultBundle() {
val cacheRecord = newCacheRecord()
val cacheRecords: MutableList<ICacheRecord> = arrayListOf(cacheRecord)
val authResult = LocalAuthenticationResult(cacheRecord, cacheRecords, SdkType.MSAL, false)
authResult.clientDataInfo = ClientDataInfo.fromPipeDelimited(clientDataRaw)

val resultAdapter = MsalBrokerResultAdapter()
val resultBundle = resultAdapter.bundleFromAuthenticationResult(authResult, "16.0")

val acquireTokenResult = resultAdapter.getAcquireTokenResultFromResultBundle(resultBundle)
assertNotNull("ClientDataInfo should be present on AcquireTokenResult", acquireTokenResult.clientDataInfo)
assertEquals("AADSTS50058", acquireTokenResult.clientDataInfo!!.error)
assertEquals(clientDataRaw, acquireTokenResult.clientDataInfo!!.raw)
}

@Test
fun testOnboardingBlob_RoundTripsThroughBundle() {
val blobJson = """{"schema_version":"1.0.0","session_correlation_id":"abc-123","onboarding_mode":"brokered","blocking_errors":["BROKER_INSTALLATION_TRIGGERED"]}"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
import com.microsoft.identity.common.java.exception.ClientException;
import com.microsoft.identity.common.java.exception.ErrorStrings;
import com.microsoft.identity.common.java.exception.ServiceException;
import com.microsoft.identity.common.java.flighting.CommonFlight;
import com.microsoft.identity.common.java.flighting.CommonFlightsManager;
import com.microsoft.identity.common.java.foci.FociQueryUtilities;
import com.microsoft.identity.common.java.logging.DiagnosticContext;
import com.microsoft.identity.common.java.logging.Logger;
Expand Down Expand Up @@ -251,6 +253,11 @@ public AcquireTokenResult acquireTokenWithPassword(@NonNull final RopcTokenComma
Telemetry.emit(new CacheEndEvent());
}

// Set server client data info on the authentication result for IPC propagation
if (CommonFlightsManager.INSTANCE.getFlightsProvider().isFlightEnabled(CommonFlight.ENABLE_SERVER_CLIENT_DATA_TELEMETRY)) {
authenticationResult.setClientDataInfo(tokenResult.getClientDataInfo());
}

// Set the AuthenticationResult on the final result object
acquireTokenResult.setLocalAuthenticationResult(authenticationResult);
}
Expand Down Expand Up @@ -528,6 +535,11 @@ protected void renewAccessToken(@NonNull final SilentTokenCommandParameters para
Telemetry.emit(new CacheEndEvent());
}

// Set server client data info on the authentication result for IPC propagation
if (CommonFlightsManager.INSTANCE.getFlightsProvider().isFlightEnabled(CommonFlight.ENABLE_SERVER_CLIENT_DATA_TELEMETRY)) {
authenticationResult.setClientDataInfo(tokenResult.getClientDataInfo());
}

// Set the AuthenticationResult on the final result object
acquireTokenSilentResult.setLocalAuthenticationResult(authenticationResult);
} else {
Expand Down
Loading
Loading