Skip to content

Commit 0965c25

Browse files
committed
Broker IPC plumbing for onboarding telemetry blob, Fixes AB#3568357 (#3111)
Adds the IPC plumbing for the onboarding telemetry blob to flow client↔broker. Independent of PR #3088 (which adds the recorder/store/constants); both are needed together for end-to-end brokered onboarding telemetry. Linked Feature: [AB#3462876](https://identitydivision.visualstudio.com/fac9d424-53d2-45c0-91b5-ef6ba7a6bf26/_workitems/edit/3462876) Linked PBI: [AB#3568357](https://identitydivision.visualstudio.com/fac9d424-53d2-45c0-91b5-ef6ba7a6bf26/_workitems/edit/3568357) | File | Change | |---|---| | `BrokerRequest.java` | Add `onboarding_seed_json` field (client → broker) | | `BrokerResult.java` | Add `onboarding_blob` field + builder + getter (broker → client) | | `InteractiveTokenCommandParameters.java` | Add `onboardingSeedJson` field on the params builder | | `AcquireTokenResult.java` | Add `onboardingBlob` field for carrying the populated blob through the result chain | | `MsalBrokerRequestAdapter.java` | Serialize `onboardingSeedJson` from command parameters into `BrokerRequest` | | `MsalBrokerResultAdapter.java` | Extract `onboardingBlob` from `BrokerResult` into `AcquireTokenResult` | All additions are pure data fields + adapter wiring. No behavior change for existing callers (fields default to empty/null when not set). - **Direction `client → broker`**: OneAuth/MSAL builds the seed JSON (containing `sessionCorrelationId`, `onboardingMode`, `schema_version`) and attaches it to the interactive request. The broker reads it via the new field on `BrokerInteractiveTokenCommandParameters`, constructs its own `OnboardingTelemetryRecorder` from the seed, and uses the same correlation ID for its onboarding telemetry events. - **Direction `broker → client`**: When the broker emits the populated onboarding blob (after `finalizeBlob()`), it places it into `BrokerResult.onboarding_blob`. The client extracts it into `AcquireTokenResult.onboardingBlob`, where downstream OneAuth code reads it and emits the blob through MATS. See full design: [Mobile Onboarding Telemetry Design](https://identitydivision.visualstudio.com/Engineering/_workitems/edit/3462876) §6.6 (persistence and propagation) and §11.2 (broker → OneAuth blob delivery). - **Pairs with**: PR #3088 (`OnboardingTelemetryRecorder` + `OnboardingTelemetryConstants` + `OnboardingSessionCorrelationStore`). The recorder consumes the seed JSON read out of these IPC fields. - **Consumers**: - OneAuth: [`shared/mobile-onboarding-android-fixes`](https://office.visualstudio.com/OneAuth/_git/OneAuth) updates `BrokerRequestConverter` / `BrokerResultConverter` to read/write these fields. - Broker (`ad-accounts-for-android`): `MsalAndroidBrokerCommandParameterAdapter` reads `onboardingSeedJson`; broker error handler / SSO controller will write `onboarding_blob` once the recorder lifecycle is wired (separate PBI, [AB#3568359](https://identitydivision.visualstudio.com/fac9d424-53d2-45c0-91b5-ef6ba7a6bf26/_workitems/edit/3568359)). - **No breaking changes**: all new optional fields, no modifications to existing serialization order or field semantics. - Round-trip serialization is exercised by existing `BrokerRequest` / `BrokerResult` tests (Gson + Bundle); new fields default to empty/null when not set, so no existing test should regress. - E2E validated locally via OneAuth `local-only-onboarding-telemetry` branch + a combined Common build (`mavenLocal` `0.0.0-zhipan-mot-7`): seed JSON flows OneAuth → BrokerRequest → broker → BrokerResult → OneAuth.
1 parent 543578a commit 0965c25

11 files changed

Lines changed: 329 additions & 5 deletions

File tree

changelog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ vNext
33

44
Version 24.2.1-RC1
55
----------
6+
- [MINOR] Add onboarding telemetry blob fields to BrokerRequest/BrokerResult and command parameters for client↔broker IPC transport (#3111)
67
- [MINOR] Add onboarding telemetry recorder, field keys, and session persistence for mobile onboarding flow (#3088)
78

89
Version 24.2.0

common/src/main/java/com/microsoft/identity/common/internal/broker/BrokerRequest.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ private static final class SerializedNames {
8787
final static String TENANT_ID = "tenant_id";
8888
final static String REQUEST_TYPE = "request_type";
8989
final static String WEB_APPS_STATE = "web_apps_state";
90+
final static String ONBOARDING_SEED_JSON = "onboarding_seed_json";
9091
}
9192

9293
/**
@@ -295,4 +296,17 @@ private static final class SerializedNames {
295296
@Nullable
296297
@SerializedName(SerializedNames.REQUEST_TYPE)
297298
private String mRequestType;
299+
300+
/**
301+
* Onboarding telemetry seed JSON blob.
302+
* Direction: client → broker (input only). Contains sessionCorrelationId,
303+
* onboarding_mode, and schema_version, supplied by the client (OneAuth/MSAL)
304+
* so the broker can construct an OnboardingTelemetryRecorder using the same
305+
* correlation id. The broker returns the populated blob (with steps and
306+
* blocking errors) via {@link BrokerResult#getOnboardingBlob()}, not via
307+
* this field.
308+
*/
309+
@Nullable
310+
@SerializedName(SerializedNames.ONBOARDING_SEED_JSON)
311+
private String mOnboardingSeedJson;
298312
}

common/src/main/java/com/microsoft/identity/common/internal/broker/BrokerResult.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ private static class SerializedNames {
9797
static final String SPE_RING = "spe_ring";
9898
static final String CLI_TELEM_ERRORCODE = "cli_telem_error_code";
9999
static final String CLI_TELEM_SUB_ERROR_CODE = "cli_telem_suberror_code";
100+
static final String ONBOARDING_BLOB = "onboarding_blob";
100101
}
101102

102103
private static final long serialVersionUID = 8606631820514878489L;
@@ -327,6 +328,13 @@ private static class SerializedNames {
327328
@SerializedName(SerializedNames.BROKER_AAD_DEVICE_ID_RECORD)
328329
private final AadDeviceIdRecord mAadDeviceIdRecord;
329330

331+
/**
332+
* Populated onboarding telemetry blob JSON returned by the broker.
333+
*/
334+
@Nullable
335+
@SerializedName(SerializedNames.ONBOARDING_BLOB)
336+
private final String mOnboardingBlob;
337+
330338
private BrokerResult(@NonNull final Builder builder) {
331339
mAccessToken = builder.mAccessToken;
332340
mIdToken = builder.mIdToken;
@@ -362,6 +370,7 @@ private BrokerResult(@NonNull final Builder builder) {
362370
mCliTelemSubErrorCode = builder.mCliTelemSubErrorCode;
363371
mExceptionType = builder.mExceptionType;
364372
mAadDeviceIdRecord = builder.mAadDeviceIdRecord;
373+
mOnboardingBlob = builder.mOnboardingBlob;
365374
}
366375

367376
public String getExceptionType() {
@@ -498,6 +507,11 @@ public AadDeviceIdRecord getAadDeviceIdRecord() {
498507
return mAadDeviceIdRecord;
499508
}
500509

510+
@Nullable
511+
public String getOnboardingBlob() {
512+
return mOnboardingBlob;
513+
}
514+
501515
public static class Builder {
502516
private String mAccessToken;
503517
private String mIdToken;
@@ -523,6 +537,7 @@ public static class Builder {
523537
private List<ICacheRecord> mTenantProfileData;
524538
private boolean mServicedFromCache;
525539
private AadDeviceIdRecord mAadDeviceIdRecord;
540+
private String mOnboardingBlob;
526541

527542
// Exception parameters
528543
private String mErrorCode;
@@ -535,7 +550,6 @@ public static class Builder {
535550
private String mCliTelemErrorCode;
536551
private String mCliTelemSubErrorCode;
537552
private String mExceptionType;
538-
539553
public Builder accessToken(@Nullable final String accessToken) {
540554
this.mAccessToken = accessToken;
541555
return this;
@@ -712,6 +726,11 @@ public Builder exceptionType(String exceptionType) {
712726
this.mExceptionType = exceptionType;
713727
return this;
714728
}
729+
730+
public Builder onboardingBlob(@Nullable final String onboardingBlob) {
731+
this.mOnboardingBlob = onboardingBlob;
732+
return this;
733+
}
715734
}
716735

717736
}

common/src/main/java/com/microsoft/identity/common/internal/request/MsalBrokerRequestAdapter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,8 @@ public BrokerRequest brokerRequestFromAcquireTokenParameters(@NonNull final Inte
139139
.preferredBrowser(parameters.getPreferredBrowser())
140140
.preferredAuthMethod(parameters.getPreferredAuthMethod())
141141
.accountTransferToken(parameters.getAccountTransferToken())
142-
.suppressAccountPicker(parameters.isSuppressBrokerAccountPicker());
142+
.suppressAccountPicker(parameters.isSuppressBrokerAccountPicker())
143+
.onboardingSeedJson(parameters.getOnboardingSeedJson());
143144

144145
if (parameters instanceof AndroidInteractiveTokenCommandParameters) {
145146
final AndroidInteractiveTokenCommandParameters androidInteractiveTokenCommandParameters = (AndroidInteractiveTokenCommandParameters) parameters;

common/src/main/java/com/microsoft/identity/common/internal/result/MsalBrokerResultAdapter.java

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -465,9 +465,16 @@ public Bundle bundleFromBaseExceptionForWebApps(@NonNull final BaseException exc
465465
@NonNull
466466
@Override
467467
public ILocalAuthenticationResult authenticationResultFromBundle(@NonNull final Bundle resultBundle) throws ClientException {
468-
final String methodTag = TAG + ":authenticationResultFromBundle";
469-
final BrokerResult brokerResult = brokerResultFromBundle(resultBundle);
468+
return authenticationResultFromBrokerResult(brokerResultFromBundle(resultBundle));
469+
}
470470

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

473480
final List<ICacheRecord> tenantProfileCacheRecords = brokerResult.getTenantProfileData();
@@ -565,6 +572,39 @@ public BrokerPerformanceMetrics getBrokerPerformanceMetricsFromBundle(@NonNull f
565572
}
566573
}
567574

575+
/**
576+
* Extracts the onboarding telemetry blob (JSON string) from the result bundle.
577+
* Best-effort: returns null if the bundle cannot be deserialized into a BrokerResult
578+
* or if no blob is present. Telemetry failures must never fail an otherwise-successful
579+
* auth result. Blob contents are not logged (may carry sessionCorrelationId).
580+
*
581+
* If the caller has already deserialized the [BrokerResult], prefer the overload
582+
* [getOnboardingBlobFromBundle(BrokerResult)] to avoid a second deserialization.
583+
*/
584+
@Nullable
585+
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
586+
public String getOnboardingBlobFromBundle(@NonNull final Bundle resultBundle) {
587+
final String methodTag = TAG + ":getOnboardingBlobFromBundle";
588+
try {
589+
final BrokerResult brokerResult = brokerResultFromBundle(resultBundle);
590+
return brokerResult.getOnboardingBlob();
591+
} catch (final ClientException e) {
592+
Logger.warn(methodTag, "Failed to extract onboarding blob from broker result: " + e.getErrorCode());
593+
return null;
594+
}
595+
}
596+
597+
/**
598+
* Overload that reads the onboarding telemetry blob from a {@link BrokerResult}
599+
* that has already been deserialized by the caller. Use this when you already
600+
* have a [BrokerResult] in hand to avoid a second deserialization of the bundle.
601+
*/
602+
@Nullable
603+
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
604+
public String getOnboardingBlobFromBundle(@NonNull final BrokerResult brokerResult) {
605+
return brokerResult.getOnboardingBlob();
606+
}
607+
568608
@NonNull
569609
@Override
570610
public AcquirePrtSsoTokenResult getAcquirePrtSsoTokenResultFromBundle(Bundle resultBundle) {
@@ -1017,9 +1057,14 @@ public AcquireTokenResult getDeviceCodeFlowTokenResultFromResultBundle(@NonNull
10171057
AcquireTokenResult getAcquireTokenResultFromResultBundle(@NonNull final Bundle resultBundle) throws BaseException {
10181058
final MsalBrokerResultAdapter resultAdapter = new MsalBrokerResultAdapter();
10191059
if (resultBundle.getBoolean(AuthenticationConstants.Broker.BROKER_REQUEST_V2_SUCCESS)) {
1060+
// Deserialize BrokerResult once and reuse for both the local authentication result
1061+
// and the onboarding telemetry blob, instead of letting authenticationResultFromBundle
1062+
// and getOnboardingBlobFromBundle each pay the deserialization cost.
1063+
final BrokerResult brokerResult = resultAdapter.brokerResultFromBundle(resultBundle);
1064+
10201065
final AcquireTokenResult acquireTokenResult = new AcquireTokenResult();
10211066
acquireTokenResult.setLocalAuthenticationResult(
1022-
resultAdapter.authenticationResultFromBundle(resultBundle)
1067+
resultAdapter.authenticationResultFromBrokerResult(brokerResult)
10231068
);
10241069
// Set broker performance metrics if available
10251070
final BrokerPerformanceMetrics metrics = resultAdapter.getBrokerPerformanceMetricsFromBundle(resultBundle);
@@ -1038,6 +1083,13 @@ AcquireTokenResult getAcquireTokenResultFromResultBundle(@NonNull final Bundle r
10381083
resultBundle.getString(AuthenticationConstants.Broker.BROKER_PACKAGE_NAME)
10391084
);
10401085
}
1086+
1087+
// Set onboarding telemetry blob if present (best-effort; never fails the result).
1088+
final String onboardingBlob = resultAdapter.getOnboardingBlobFromBundle(brokerResult);
1089+
if (onboardingBlob != null) {
1090+
acquireTokenResult.setOnboardingBlob(onboardingBlob);
1091+
}
1092+
10411093
return acquireTokenResult;
10421094
}
10431095

common/src/test/java/com/microsoft/identity/common/internal/request/MsalBrokerRequestAdapterTests.java

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,4 +386,73 @@ public void testGetRequestBundleForAadDeviceIdRequest() {
386386
assertEquals(mockRedirectUri, brokerRequest.getRedirect());
387387
assertEquals(mockTenantId, brokerRequest.getTenantId());
388388
}
389+
390+
/**
391+
* Verify that {@code onboardingSeedJson} from {@link InteractiveTokenCommandParameters}
392+
* is propagated into {@link BrokerRequest} by
393+
* {@link MsalBrokerRequestAdapter#brokerRequestFromAcquireTokenParameters(InteractiveTokenCommandParameters)}.
394+
*/
395+
@Test
396+
public void test_brokerRequestFromAcquireTokenParameters_PropagatesOnboardingSeedJson() {
397+
final String seedJson = "{\"schema_version\":\"1.0.0\","
398+
+ "\"session_correlation_id\":\"abc-123\","
399+
+ "\"onboarding_mode\":\"brokered\"}";
400+
final Set<String> scopes = new HashSet<>();
401+
scopes.add("user.read");
402+
403+
final IPlatformComponents components = MockPlatformComponentsFactory.getNonFunctionalBuilder().build();
404+
final AndroidInteractiveTokenCommandParameters params = AndroidInteractiveTokenCommandParameters.builder()
405+
.platformComponents(components)
406+
.correlationId("987d8962-3f4d-4054-a852-ac0c4b6a602e")
407+
.clientId("aClientId")
408+
.redirectUri("msauth://com.example/foo")
409+
.applicationName("com.example")
410+
.applicationVersion("1.0.0")
411+
.sdkType(SdkType.MSAL)
412+
.sdkVersion("5.4.0")
413+
.authority(new AzureActiveDirectoryAuthority())
414+
.scopes(scopes)
415+
.authenticationScheme(new BearerAuthenticationSchemeInternal())
416+
.prompt(OpenIdConnectPromptParameter.LOGIN)
417+
.requiredBrokerProtocolVersion("10.0")
418+
.onboardingSeedJson(seedJson)
419+
.build();
420+
421+
final BrokerRequest brokerRequest =
422+
new MsalBrokerRequestAdapter().brokerRequestFromAcquireTokenParameters(params);
423+
424+
assertEquals(seedJson, brokerRequest.getOnboardingSeedJson());
425+
}
426+
427+
/**
428+
* Verify that when {@code onboardingSeedJson} is not set on the parameters,
429+
* the resulting {@link BrokerRequest} carries a null seed (i.e. no accidental default value).
430+
*/
431+
@Test
432+
public void test_brokerRequestFromAcquireTokenParameters_NoSeedJson_IsNull() {
433+
final Set<String> scopes = new HashSet<>();
434+
scopes.add("user.read");
435+
436+
final IPlatformComponents components = MockPlatformComponentsFactory.getNonFunctionalBuilder().build();
437+
final AndroidInteractiveTokenCommandParameters params = AndroidInteractiveTokenCommandParameters.builder()
438+
.platformComponents(components)
439+
.correlationId("987d8962-3f4d-4054-a852-ac0c4b6a602e")
440+
.clientId("aClientId")
441+
.redirectUri("msauth://com.example/foo")
442+
.applicationName("com.example")
443+
.applicationVersion("1.0.0")
444+
.sdkType(SdkType.MSAL)
445+
.sdkVersion("5.4.0")
446+
.authority(new AzureActiveDirectoryAuthority())
447+
.scopes(scopes)
448+
.authenticationScheme(new BearerAuthenticationSchemeInternal())
449+
.prompt(OpenIdConnectPromptParameter.LOGIN)
450+
.requiredBrokerProtocolVersion("10.0")
451+
.build();
452+
453+
final BrokerRequest brokerRequest =
454+
new MsalBrokerRequestAdapter().brokerRequestFromAcquireTokenParameters(params);
455+
456+
assertNull(brokerRequest.getOnboardingSeedJson());
457+
}
389458
}

common/src/test/java/com/microsoft/identity/common/internal/request/MsalBrokerResultAdapterTests.kt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,4 +658,34 @@ class MsalBrokerResultAdapterTests {
658658
resultString.contains(SchemaUtil.MISSING_FROM_THE_TOKEN_RESPONSE)
659659
)
660660
}
661+
662+
@Test
663+
fun testOnboardingBlob_RoundTripsThroughBundle() {
664+
val blobJson = """{"schema_version":"1.0.0","session_correlation_id":"abc-123","onboarding_mode":"brokered","blocking_errors":["BROKER_INSTALLATION_TRIGGERED"]}"""
665+
val brokerResult = BrokerResult.Builder()
666+
.clientId("aClientId")
667+
.correlationId("987d8962-3f4d-4054-a852-ac0c4b6a602e")
668+
.onboardingBlob(blobJson)
669+
.build()
670+
671+
val adapter = getInstance()
672+
val resultBundle = adapter.bundleFromBrokerResult(brokerResult, "10.0")
673+
val deserialized = adapter.brokerResultFromBundle(resultBundle)
674+
675+
assertEquals(blobJson, deserialized.onboardingBlob)
676+
}
677+
678+
@Test
679+
fun testOnboardingBlob_NotSet_DeserializesAsNull() {
680+
val brokerResult = BrokerResult.Builder()
681+
.clientId("aClientId")
682+
.correlationId("987d8962-3f4d-4054-a852-ac0c4b6a602e")
683+
.build()
684+
685+
val adapter = getInstance()
686+
val resultBundle = adapter.bundleFromBrokerResult(brokerResult, "10.0")
687+
val deserialized = adapter.brokerResultFromBundle(resultBundle)
688+
689+
assertNull(deserialized.onboardingBlob)
690+
}
661691
}

common4j/src/main/com/microsoft/identity/common/java/commands/parameters/InteractiveTokenCommandParameters.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
import lombok.Getter;
3939
import lombok.experimental.SuperBuilder;
4040

41+
import edu.umd.cs.findbugs.annotations.Nullable;
42+
4143
@Getter
4244
@EqualsAndHashCode(callSuper = true)
4345
@SuperBuilder(toBuilder = true)
@@ -85,6 +87,13 @@ public class InteractiveTokenCommandParameters extends TokenCommandParameters {
8587
*/
8688
private final boolean suppressBrokerAccountPicker;
8789

90+
/**
91+
* Onboarding telemetry seed JSON blob.
92+
* Passed through IPC to the broker for step recording and blocking error tracking.
93+
*/
94+
@Nullable
95+
private final String onboardingSeedJson;
96+
8897
public boolean getHandleNullTaskAffinity(){
8998
return handleNullTaskAffinity;
9099
}

common4j/src/main/com/microsoft/identity/common/java/result/AcquireTokenResult.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,21 @@ public class AcquireTokenResult implements IBrokerPerformanceMetricsProvider, IB
4949

5050
private BrokerPerformanceMetrics mBrokerPerformanceMetrics;
5151

52+
/**
53+
* Populated onboarding telemetry blob JSON returned by the broker.
54+
*/
55+
@Nullable
56+
private String mOnboardingBlob;
57+
58+
public void setOnboardingBlob(@Nullable final String onboardingBlob) {
59+
this.mOnboardingBlob = onboardingBlob;
60+
}
61+
62+
@Nullable
63+
public String getOnboardingBlob() {
64+
return this.mOnboardingBlob;
65+
}
66+
5267
public void setLocalAuthenticationResult(ILocalAuthenticationResult result) {
5368
this.mLocalAuthenticationResult = result;
5469
this.mSucceeded = true;

0 commit comments

Comments
 (0)