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
@@ -1,6 +1,7 @@
vNext
----------
- [PATCH] Handle app_link Intent redirection by validating broker install links and rejecting unsupported redirect URIs with appropriate error responses (#3102)
- [MINOR] Add onboarding telemetry blob fields to BrokerRequest/BrokerResult and command parameters for client↔broker IPC transport (#3111)
- [PATCH] Extend filter-then-clone optimization to load() and getIdTokensForAccountRecord() in MsalOAuth2TokenCache: when ENABLE_FILTER_THEN_CLONE_IN_MEMORY_CACHE flight is enabled, skip clone-all preload and call direct flight-gated overloads that clone only matching credentials; add new getCredentialsFilteredBy overload with kid support (#3100)
- [MINOR] Add onboarding telemetry recorder, field keys, and session persistence for mobile onboarding flow (#3088)
- [PATCH] Move Multiple Listening apps check to the authorization layer (#3070)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ private static final class SerializedNames {
final static String TENANT_ID = "tenant_id";
final static String REQUEST_TYPE = "request_type";
final static String WEB_APPS_STATE = "web_apps_state";
final static String ONBOARDING_SEED_JSON = "onboarding_seed_json";
}

/**
Expand Down Expand Up @@ -295,4 +296,17 @@ private static final class SerializedNames {
@Nullable
@SerializedName(SerializedNames.REQUEST_TYPE)
private String mRequestType;

/**
* Onboarding telemetry seed JSON blob.
* Direction: client → broker (input only). Contains sessionCorrelationId,
* onboarding_mode, and schema_version, supplied by the client (OneAuth/MSAL)
* so the broker can construct an OnboardingTelemetryRecorder using the same
* correlation id. The broker returns the populated blob (with steps and
* blocking errors) via {@link BrokerResult#getOnboardingBlob()}, not via
* this field.
*/
@Nullable
@SerializedName(SerializedNames.ONBOARDING_SEED_JSON)
private String mOnboardingSeedJson;
Comment thread
wzhipan marked this conversation as resolved.
}
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 ONBOARDING_BLOB = "onboarding_blob";
}

private static final long serialVersionUID = 8606631820514878489L;
Expand Down Expand Up @@ -327,6 +328,13 @@ private static class SerializedNames {
@SerializedName(SerializedNames.BROKER_AAD_DEVICE_ID_RECORD)
private final AadDeviceIdRecord mAadDeviceIdRecord;

/**
* Populated onboarding telemetry blob JSON returned by the broker.
*/
@Nullable
@SerializedName(SerializedNames.ONBOARDING_BLOB)
private final String mOnboardingBlob;

private BrokerResult(@NonNull final Builder builder) {
mAccessToken = builder.mAccessToken;
mIdToken = builder.mIdToken;
Expand Down Expand Up @@ -362,6 +370,7 @@ private BrokerResult(@NonNull final Builder builder) {
mCliTelemSubErrorCode = builder.mCliTelemSubErrorCode;
mExceptionType = builder.mExceptionType;
mAadDeviceIdRecord = builder.mAadDeviceIdRecord;
mOnboardingBlob = builder.mOnboardingBlob;
}

public String getExceptionType() {
Expand Down Expand Up @@ -498,6 +507,11 @@ public AadDeviceIdRecord getAadDeviceIdRecord() {
return mAadDeviceIdRecord;
}

@Nullable
public String getOnboardingBlob() {
return mOnboardingBlob;
}

public static class Builder {
private String mAccessToken;
private String mIdToken;
Expand All @@ -523,6 +537,7 @@ public static class Builder {
private List<ICacheRecord> mTenantProfileData;
private boolean mServicedFromCache;
private AadDeviceIdRecord mAadDeviceIdRecord;
private String mOnboardingBlob;

// Exception parameters
private String mErrorCode;
Expand All @@ -535,7 +550,6 @@ public static class Builder {
private String mCliTelemErrorCode;
private String mCliTelemSubErrorCode;
private String mExceptionType;

public Builder accessToken(@Nullable final String accessToken) {
this.mAccessToken = accessToken;
return this;
Expand Down Expand Up @@ -712,6 +726,11 @@ public Builder exceptionType(String exceptionType) {
this.mExceptionType = exceptionType;
return this;
}

public Builder onboardingBlob(@Nullable final String onboardingBlob) {
this.mOnboardingBlob = onboardingBlob;
return this;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,8 @@ public BrokerRequest brokerRequestFromAcquireTokenParameters(@NonNull final Inte
.preferredBrowser(parameters.getPreferredBrowser())
.preferredAuthMethod(parameters.getPreferredAuthMethod())
.accountTransferToken(parameters.getAccountTransferToken())
.suppressAccountPicker(parameters.isSuppressBrokerAccountPicker());
.suppressAccountPicker(parameters.isSuppressBrokerAccountPicker())
.onboardingSeedJson(parameters.getOnboardingSeedJson());
Comment thread
wzhipan marked this conversation as resolved.

if (parameters instanceof AndroidInteractiveTokenCommandParameters) {
final AndroidInteractiveTokenCommandParameters androidInteractiveTokenCommandParameters = (AndroidInteractiveTokenCommandParameters) parameters;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -465,9 +465,16 @@ public Bundle bundleFromBaseExceptionForWebApps(@NonNull final BaseException exc
@NonNull
@Override
public ILocalAuthenticationResult authenticationResultFromBundle(@NonNull final Bundle resultBundle) throws ClientException {
final String methodTag = TAG + ":authenticationResultFromBundle";
final BrokerResult brokerResult = brokerResultFromBundle(resultBundle);
return authenticationResultFromBrokerResult(brokerResultFromBundle(resultBundle));
}

/**
* Overload that builds the authentication result from an already-deserialized
* {@link BrokerResult}. Use this when the caller has the [BrokerResult] in hand
* to avoid a redundant deserialization of the result bundle.
*/
public ILocalAuthenticationResult authenticationResultFromBrokerResult(@NonNull final BrokerResult brokerResult) throws ClientException {
final String methodTag = TAG + ":authenticationResultFromBrokerResult";
Logger.info(methodTag, "Broker Result returned from Bundle, constructing authentication result");

final List<ICacheRecord> tenantProfileCacheRecords = brokerResult.getTenantProfileData();
Expand Down Expand Up @@ -565,6 +572,39 @@ public BrokerPerformanceMetrics getBrokerPerformanceMetricsFromBundle(@NonNull f
}
}

/**
* Extracts the onboarding telemetry blob (JSON string) from the result bundle.
* Best-effort: returns null if the bundle cannot be deserialized into a BrokerResult
* or if no blob is present. Telemetry failures must never fail an otherwise-successful
* auth result. Blob contents are not logged (may carry sessionCorrelationId).
*
* If the caller has already deserialized the [BrokerResult], prefer the overload
* [getOnboardingBlobFromBundle(BrokerResult)] to avoid a second deserialization.
*/
@Nullable
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
public String getOnboardingBlobFromBundle(@NonNull final Bundle resultBundle) {
Comment thread
wzhipan marked this conversation as resolved.
final String methodTag = TAG + ":getOnboardingBlobFromBundle";
try {
final BrokerResult brokerResult = brokerResultFromBundle(resultBundle);
return brokerResult.getOnboardingBlob();
} catch (final ClientException e) {
Logger.warn(methodTag, "Failed to extract onboarding blob from broker result: " + e.getErrorCode());
return null;
}
}

/**
* Overload that reads the onboarding telemetry blob from a {@link BrokerResult}
* that has already been deserialized by the caller. Use this when you already
* have a [BrokerResult] in hand to avoid a second deserialization of the bundle.
*/
@Nullable
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
public String getOnboardingBlobFromBundle(@NonNull final BrokerResult brokerResult) {
return brokerResult.getOnboardingBlob();
}

@NonNull
@Override
public AcquirePrtSsoTokenResult getAcquirePrtSsoTokenResultFromBundle(Bundle resultBundle) {
Expand Down Expand Up @@ -1017,9 +1057,14 @@ public AcquireTokenResult getDeviceCodeFlowTokenResultFromResultBundle(@NonNull
AcquireTokenResult getAcquireTokenResultFromResultBundle(@NonNull final Bundle resultBundle) throws BaseException {
final MsalBrokerResultAdapter resultAdapter = new MsalBrokerResultAdapter();
if (resultBundle.getBoolean(AuthenticationConstants.Broker.BROKER_REQUEST_V2_SUCCESS)) {
// Deserialize BrokerResult once and reuse for both the local authentication result
// and the onboarding telemetry blob, instead of letting authenticationResultFromBundle
// and getOnboardingBlobFromBundle each pay the deserialization cost.
final BrokerResult brokerResult = resultAdapter.brokerResultFromBundle(resultBundle);

final AcquireTokenResult acquireTokenResult = new AcquireTokenResult();
acquireTokenResult.setLocalAuthenticationResult(
resultAdapter.authenticationResultFromBundle(resultBundle)
resultAdapter.authenticationResultFromBrokerResult(brokerResult)
);
// Set broker performance metrics if available
final BrokerPerformanceMetrics metrics = resultAdapter.getBrokerPerformanceMetricsFromBundle(resultBundle);
Expand All @@ -1038,6 +1083,13 @@ AcquireTokenResult getAcquireTokenResultFromResultBundle(@NonNull final Bundle r
resultBundle.getString(AuthenticationConstants.Broker.BROKER_PACKAGE_NAME)
);
}

// Set onboarding telemetry blob if present (best-effort; never fails the result).
final String onboardingBlob = resultAdapter.getOnboardingBlobFromBundle(brokerResult);
if (onboardingBlob != null) {
acquireTokenResult.setOnboardingBlob(onboardingBlob);
Comment thread
Veena11 marked this conversation as resolved.
}

return acquireTokenResult;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -386,4 +386,73 @@ public void testGetRequestBundleForAadDeviceIdRequest() {
assertEquals(mockRedirectUri, brokerRequest.getRedirect());
assertEquals(mockTenantId, brokerRequest.getTenantId());
}

/**
* Verify that {@code onboardingSeedJson} from {@link InteractiveTokenCommandParameters}
* is propagated into {@link BrokerRequest} by
* {@link MsalBrokerRequestAdapter#brokerRequestFromAcquireTokenParameters(InteractiveTokenCommandParameters)}.
*/
@Test
public void test_brokerRequestFromAcquireTokenParameters_PropagatesOnboardingSeedJson() {
final String seedJson = "{\"schema_version\":\"1.0.0\","
+ "\"session_correlation_id\":\"abc-123\","
+ "\"onboarding_mode\":\"brokered\"}";
final Set<String> scopes = new HashSet<>();
scopes.add("user.read");

final IPlatformComponents components = MockPlatformComponentsFactory.getNonFunctionalBuilder().build();
final AndroidInteractiveTokenCommandParameters params = AndroidInteractiveTokenCommandParameters.builder()
.platformComponents(components)
.correlationId("987d8962-3f4d-4054-a852-ac0c4b6a602e")
.clientId("aClientId")
.redirectUri("msauth://com.example/foo")
.applicationName("com.example")
.applicationVersion("1.0.0")
.sdkType(SdkType.MSAL)
.sdkVersion("5.4.0")
.authority(new AzureActiveDirectoryAuthority())
.scopes(scopes)
.authenticationScheme(new BearerAuthenticationSchemeInternal())
.prompt(OpenIdConnectPromptParameter.LOGIN)
.requiredBrokerProtocolVersion("10.0")
.onboardingSeedJson(seedJson)
.build();

final BrokerRequest brokerRequest =
new MsalBrokerRequestAdapter().brokerRequestFromAcquireTokenParameters(params);

assertEquals(seedJson, brokerRequest.getOnboardingSeedJson());
}

/**
* Verify that when {@code onboardingSeedJson} is not set on the parameters,
* the resulting {@link BrokerRequest} carries a null seed (i.e. no accidental default value).
*/
@Test
public void test_brokerRequestFromAcquireTokenParameters_NoSeedJson_IsNull() {
final Set<String> scopes = new HashSet<>();
scopes.add("user.read");

final IPlatformComponents components = MockPlatformComponentsFactory.getNonFunctionalBuilder().build();
final AndroidInteractiveTokenCommandParameters params = AndroidInteractiveTokenCommandParameters.builder()
.platformComponents(components)
.correlationId("987d8962-3f4d-4054-a852-ac0c4b6a602e")
.clientId("aClientId")
.redirectUri("msauth://com.example/foo")
.applicationName("com.example")
.applicationVersion("1.0.0")
.sdkType(SdkType.MSAL)
.sdkVersion("5.4.0")
.authority(new AzureActiveDirectoryAuthority())
.scopes(scopes)
.authenticationScheme(new BearerAuthenticationSchemeInternal())
.prompt(OpenIdConnectPromptParameter.LOGIN)
.requiredBrokerProtocolVersion("10.0")
.build();

final BrokerRequest brokerRequest =
new MsalBrokerRequestAdapter().brokerRequestFromAcquireTokenParameters(params);

assertNull(brokerRequest.getOnboardingSeedJson());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -658,4 +658,34 @@ class MsalBrokerResultAdapterTests {
resultString.contains(SchemaUtil.MISSING_FROM_THE_TOKEN_RESPONSE)
)
}

@Test
fun testOnboardingBlob_RoundTripsThroughBundle() {
val blobJson = """{"schema_version":"1.0.0","session_correlation_id":"abc-123","onboarding_mode":"brokered","blocking_errors":["BROKER_INSTALLATION_TRIGGERED"]}"""
val brokerResult = BrokerResult.Builder()
.clientId("aClientId")
.correlationId("987d8962-3f4d-4054-a852-ac0c4b6a602e")
.onboardingBlob(blobJson)
.build()

val adapter = getInstance()
val resultBundle = adapter.bundleFromBrokerResult(brokerResult, "10.0")
val deserialized = adapter.brokerResultFromBundle(resultBundle)

assertEquals(blobJson, deserialized.onboardingBlob)
}

@Test
fun testOnboardingBlob_NotSet_DeserializesAsNull() {
val brokerResult = BrokerResult.Builder()
.clientId("aClientId")
.correlationId("987d8962-3f4d-4054-a852-ac0c4b6a602e")
.build()

val adapter = getInstance()
val resultBundle = adapter.bundleFromBrokerResult(brokerResult, "10.0")
val deserialized = adapter.brokerResultFromBundle(resultBundle)

assertNull(deserialized.onboardingBlob)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
import lombok.Getter;
import lombok.experimental.SuperBuilder;

import edu.umd.cs.findbugs.annotations.Nullable;

@Getter
@EqualsAndHashCode(callSuper = true)
@SuperBuilder(toBuilder = true)
Expand Down Expand Up @@ -85,6 +87,13 @@ public class InteractiveTokenCommandParameters extends TokenCommandParameters {
*/
private final boolean suppressBrokerAccountPicker;

/**
* Onboarding telemetry seed JSON blob.
* Passed through IPC to the broker for step recording and blocking error tracking.
*/
@Nullable
private final String onboardingSeedJson;

public boolean getHandleNullTaskAffinity(){
return handleNullTaskAffinity;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,21 @@ public class AcquireTokenResult implements IBrokerPerformanceMetricsProvider, IB

private BrokerPerformanceMetrics mBrokerPerformanceMetrics;

/**
* Populated onboarding telemetry blob JSON returned by the broker.
*/
@Nullable
private String mOnboardingBlob;

public void setOnboardingBlob(@Nullable final String onboardingBlob) {
this.mOnboardingBlob = onboardingBlob;
}

@Nullable
public String getOnboardingBlob() {
Comment thread
wzhipan marked this conversation as resolved.
return this.mOnboardingBlob;
}

public void setLocalAuthenticationResult(ILocalAuthenticationResult result) {
this.mLocalAuthenticationResult = result;
this.mSucceeded = true;
Expand Down
Loading
Loading