MAM broker-install request resume — Common engine (flag off) [AB#3676213], Fixes AB#3676213 - #3189
Draft
wzhipan wants to merge 9 commits into
Draft
MAM broker-install request resume — Common engine (flag off) [AB#3676213], Fixes AB#3676213#3189wzhipan wants to merge 9 commits into
wzhipan wants to merge 9 commits into
Conversation
Adds the foundational, flag-gated building blocks for the MAM broker-install request-resume engine (Feature AB#3676213, PBI AB#3686093). No behavior change when the flight is off. - CommonFlight.ENABLE_BROKER_INSTALL_RESUME (ECS "EnableBrokerInstallResume", default off) - single gate for the whole engine. - SpanName.BrokerInstallResume + broker_install_resume_* AttributeName entries for the parked -> referrer-fired -> resume-received -> retry-success -> delivered funnel (+ resume-received-no-park). NOTE: mirror the new AttributeName values in broker4j (separate broker-repo change). - BrokerInstallationRequiredException carrying the WPJ username (UPN) + install link; ExceptionAdapter returns it for broker_needs_to_be_installed ONLY when the flight is on, otherwise the pre-existing ServiceException is unchanged. - ParkedRecord + BrokerInstallResumeRegistry: in-memory, per-process, cid-keyed park store with single-use match, 7-min TTL sweep, and a single-resolution guard so the parked sink fires exactly once. Unit tests: registry/record semantics (single-use, concurrency, expiry, sweep, single-resolution), the exception, and ExceptionAdapter flag on/off gating. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Wires the in-memory park store into the command pipeline so a Conditional-Access
"install broker" (Company Portal) interactive request is parked instead of
surfacing the terminal install-required error (Feature AB#3676213, PBI AB#3686093).
Flight-gated; no behavior change when ENABLE_BROKER_INSTALL_RESUME is off.
- BrokerInstallResumeParker: extracted, unit-testable decision logic.
* parkIfEligible(): parks when flight on + InteractiveTokenCommand + ERROR
result carrying BrokerInstallationRequiredException; stashes the UPN and a
7-min expiry. Silent commands and non-broker-install errors are never parked.
* isCallbackSuppressed(): true while a matching parked record exists.
- CommandDispatcher: park in executeCommand() after the result is finalized;
suppress the terminal callback in returnCommandResult() for parked cids so
the app's sink stays pending (to be resumed/expired in Phase 4).
Unit tests: parker eligibility (flight on/off, interactive vs silent, broker-
install vs other error, completed) and callback suppression. Full controllers +
commands package suites pass (54 tests) - no regression.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the production Play Store install-referrer builder for the MAM broker-install request-resume flow (Feature AB#3676213, PBI AB#3686094), graduating the earlier proof-of-concept into a tested common4j component that both the MSAL launch path and the prod broker-WPJ launch path consume. - MamInstallReferrerBuilder (common4j): builds the frozen wire contract src=mamca&originPkg=..&redirectUri=..&cid=.. ; decorates the eSTS app_link with a single, allow-list-safe referrer parameter (primary); builds the market://details?id=CP&referrer=.. fallback; and parses the referrer back (Company Portal blueprint). decorateAppLinkWithReferrer is null-safe: it returns the original app_link unchanged if any input is missing, so the existing install flow can never be broken. Routing data only - no UPN/secrets. - Removes the superseded MamResumeReferrerPocTest (its logic is now production code with fuller coverage). Unit tests: packed-referrer shape/length, allow-list compatibility against the real BrokerInstallLinkValidator, exactly-one-referrer-param, byte-for-byte round-trip of a base64 redirect URI (+ / =), null-safety, and the market:// fallback. Full providers package passes (193 tests). Note: the concrete launch-site call (supplying cid + redirectUri from the request context) lands in the prod broker-side WPJ path (cross-repo, D4). getThreadCorrelationId is not a safe cid source at the MSAL WebView thread (it fabricates a UUID when unset), so the fragile MSAL-WebView wiring is intentionally not forced here. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the pure-Java detection primitive for the Company Portal resume redirect (Feature AB#3676213, PBI AB#3686095). When CP finishes install it redirects back to the calling app via msauth://<app>/<hash>?mam_resume=<cid>; this classifies that redirect and exposes the parked correlation id for the resume engine (Phase 4). - AuthenticationConstants.AAD.MAM_RESUME_KEY = "mam_resume". - RawAuthorizationResult.ResultCode.BROKER_INSTALL_RESUME(2012). - getResultCodeFromFinalRedirectUri(): on the msauth redirect scheme, presence of mam_resume is the discriminator and takes precedence over other classifications. - RawAuthorizationResult.getMamResumeCorrelationId(): extracts the cid (or null). Unit tests: mam_resume classification, cid extraction, precedence over app_link, scheme-scoping, and a regression asserting the existing broker-install-required classification is unchanged. Full providers package passes (27 tests). Note: the Android routing of this result code into the resume path, plus the caller == Company Portal signature validation, land with the resume engine in Phase 4 (they consume this primitive and the Phase 1 registry). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Closes the resume loop on the common side (Feature AB#3676213, PBI AB#3686097): project the parked interactive request to a silent broker retry, deliver the result to the app's original callback exactly once, and emit the funnel. The live silent submit + force-fresh discovery (getActiveBroker(shouldSkipCache=true)) are driven by the platform/OneAuth glue that holds the controller factory; this change provides the common-side, testable guarantees they build on. - BrokerInstallResumeParamsFactory.toSilentParameters(): projects InteractiveTokenCommandParameters -> SilentTokenCommandParameters (all shared CommandParameters + TokenCommandParameters fields) and sets login_hint = UPN (preserving the existing hint when no UPN is available). - BrokerInstallResumeEngine: deliverSuccess/deliverError fire the parked callback exactly once via ParkedRecord.tryResolve() (single-resolution guard); sweepAndResolveExpired resolves TTL-expired parks with the original install-required error so a parked request never hangs. - BrokerInstallResumeTelemetryHelper: stateful funnel span (parked -> referrer_fired -> resume_received -> retry_success -> delivered, plus resume_received_no_park), OK on delivery / ERROR + recordException on failure, span ended exactly once. Modeled on CertBasedAuthTelemetryHelper. Unit tests: single-resolution races (success/error/after-success), TTL sweep resolution, the full param projection + login_hint behavior, and the funnel lifecycle on success/failure paths. Full commands + opentelemetry packages pass (48 tests). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the common4j resume orchestration spine that ties the park registry, params projection, engine, and funnel together behind one entry point: - BrokerInstallResumeCoordinator.resume(record, submitter, telemetry): projects the parked interactive request to silent params (login_hint = WPJ UPN), invokes a platform-supplied ISilentResumeSubmitter (force-fresh broker discovery + silent submit), and delivers the outcome to the app's original callback exactly once via BrokerInstallResumeEngine. Emits the resume funnel when a telemetry helper is supplied. The submit is delegated because IControllerFactory exposes no cache-skip knob and the parked command's controllers were resolved before Company Portal was installed. - BrokerInstallResumeRegistry.claimAllPending(): foreground-fallback lookup for the cid-less resume path (CP redirect that only foregrounds the app). Claims and removes every still-parked record single-use. Unit tests: BrokerInstallResumeCoordinatorTest (success projection/delivery, submitter-throws error delivery, already-resolved no-op, missing-params guard), plus claimAllPending cases in BrokerInstallResumeRegistryTest. Flight-gated end-to-end via CommonFlight.ENABLE_BROKER_INSTALL_RESUME. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Wires the common-engine primitives into the Android common module so a parked broker-install request can actually be resumed on-device, and tags the Company Portal install launch with the calling app package. Android (common): - BrokerInstallResumeManager: Android orchestrator with two triggers - onResumeRedirect (automatic; capability trust = unguessable cid match + Company Portal installed-and-valid, since the browser redirect does not expose the caller package) and onAppForegrounded (foreground fallback; resumes any parked request once Company Portal is a valid broker). It validates Company Portal via BrokerValidator, and delegates the actual force-fresh discovery + silent submit to a platform-registered ISilentResumeSubmitter (MSAL/OneAuth). With no submitter registered it resolves the parked sink with the original install-required error. - BrokerInstallResumeForegroundObserver: dependency-free ActivityLifecycleCallbacks that drives the foreground-fallback resume on app foreground. install(Application) registers it once per process. This is the trigger that makes the flow testable without any Company Portal change. - BrowserAuthorizationFragment / CurrentTaskBrowserAuthorizationFragment: inject the install referrer (bare origin package, the CP-confirmed &referrer=<originPkg> pattern) onto the app_link launch, and route a BROKER_INSTALL_RESUME redirect to the manager. Both flight-gated. - AzureActiveDirectoryWebViewClient: same referrer injection on the WebView install-launch path. common4j: - MamInstallReferrerBuilder.decorateAppLinkWithOriginReferrer: CP-compatible bare-origin referrer form (Veena, 2026-07-17), kept on the install-link allow-list. - RawAuthorizationResult: gate the mam_resume classification behind the flight so flight-off behavior is byte-for-byte unchanged. Tests: BrokerInstallResumeManagerTest (flight/trust/foreground gates, capability trust, resume delivery, no-submitter fallback), new decorateAppLinkWithOriginReferrer cases, and a flight-off classification regression in RawAuthorizationResultMamResumeTest. All flight-gated behind CommonFlight.ENABLE_BROKER_INSTALL_RESUME. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Hardens BrokerInstallResumeManager so a resume trigger carrying an empty/absent correlation id returns false without touching the registry (avoids ConcurrentHashMap.remove(null) on the browser-redirect path where the mam_resume value could be empty). Adds a regression test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…n force-fresh getActiveBroker(shouldSkipCache=true) previously updated only the persistent active-broker cache, leaving the in-memory cachedData pinned to CachedBrokerData(null) for the process lifetime. With BROKER_DISCOVERY_CACHE on, the eligibility path (getActiveBrokerWithInMemoryCache) then kept returning 'no broker', so a broker installed mid-process (MAM broker-install resume: Company Portal) was never surfaced and the resumed request could not complete through it. Fix: a force-fresh discovery now also overwrites the in-memory cache with its authoritative result. Adds a regression test (broker installed mid-process -> force-fresh -> in-memory read returns it). AB#3696097 / AB#3676213. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
✅ Work item link check complete. Description contains link AB#3676213 to an Azure Boards work item. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Adds the Common engine for MAM broker‑install request resume (Feature AB#3676213, under Epic 3676182). When Conditional Access blocks interactive sign‑in and requires Company Portal (CP), this parks the interactive request, injects the Play Store install referrer, and — once CP is installed and the app returns to the foreground — force‑fresh re‑discovers the broker and resumes the parked request so a token is delivered on the app's original callback, with no re‑authentication. This matches the iOS behavior.
Entirely behind a feature flag that is OFF by default (
CommonFlight.ENABLE_BROKER_INSTALL_RESUME = false) — no behavior change in production until the flight is enabled.What's in this PR (Common only)
BrokerInstallResumeManager/…Registry/…Parker/ParkedRecord: in‑memory park of the blocked interactive request keyed by correlation id, stashing the UPN and keeping the broker sink pending.MamInstallReferrerBuilder: injects&referrer=<originAppPackage>into the Play Store install launch (the calling‑app side of the CP redirect story).BrowserAuthorizationFragment/CurrentTaskBrowserAuthorizationFragment/AzureActiveDirectoryWebViewClient: detect themam_resumeredirect and validate the caller.BrokerInstallResumeEngine/…Coordinator/…ParamsFactory: project the parked interactive request to a silent, force‑fresh,login_hint=UPNretry and deliver the outcome once on the original callback.BrokerInstallResumeForegroundObserver.BrokerDiscoveryClient: a force‑fresh discovery (getActiveBroker(shouldSkipCache=true)) now also refreshes the in‑memory cache, so a broker installed mid‑process is picked up by the eligibility path (previously it stayed pinned to the stale "no broker" result when theBROKER_DISCOVERY_CACHEflight is on). This is the fix that lets the resume actually route through the freshly‑installed CP.BrokerInstallationRequiredException,ExceptionAdaptermapping, newSpanName/AttributeNameentries,BrokerInstallResumeTelemetryHelper.Verification
com.microsoft.Officeconfig, MAM‑CA account): request parked across CP install → on foreground the request resumed → force‑fresh discovery re‑ran and found CP → broker eligibility passed (request_eligible_for_broker=true,broker_app_used=true) → CP'sBrokerAuthorizationActivitylaunched to service the resumed request. Before the discovery fix this same path bailed immediately withRequiredBrokerMissing/broker_app_used=false.Why draft / what's NOT covered here
FLAG_SECURE; also needs real Intune enrollment). It is downstream of this code (CP/Intune), not a defect here.Cross‑repo (follow‑ups, not required for this PR to be correct)
broker4jtelemetry mirror of the newAttributeNames (cross‑repo consistency, §6.5) — held as a paired change.Work items
Feature AB#3676213; PBIs AB#3686093 / AB#3686094 / AB#3686095 / AB#3686097.
Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com