From a7c08bf96219f8051d6db806b6e3ff256a1256ec Mon Sep 17 00:00:00 2001 From: Hieu Nguyen Date: Wed, 17 Jun 2026 10:03:50 -0700 Subject: [PATCH 01/38] Only attach PRT header to PkeyAuth response for known AAD hosts Restrict the refresh token credential (PRT) header so it is only added to the PkeyAuth challenge response when the submit URL host is a known/trusted AAD host, preventing the PRT from being leaked to untrusted hosts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../challangeHandlers/MSIDPKeyAuthHandler.m | 16 +++++++++--- IdentityCore/tests/MSIDPKeyAuthHandlerTests.m | 26 +++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/IdentityCore/src/webview/embeddedWebview/challangeHandlers/MSIDPKeyAuthHandler.m b/IdentityCore/src/webview/embeddedWebview/challangeHandlers/MSIDPKeyAuthHandler.m index bbf48ea55f..de06225cac 100644 --- a/IdentityCore/src/webview/embeddedWebview/challangeHandlers/MSIDPKeyAuthHandler.m +++ b/IdentityCore/src/webview/embeddedWebview/challangeHandlers/MSIDPKeyAuthHandler.m @@ -35,6 +35,7 @@ #import "MSIDRequestTelemetryConstants.h" #endif #import "MSIDWorkPlaceJoinUtil.h" +#import "MSIDAADNetworkConfiguration.h" @implementation MSIDPKeyAuthHandler @@ -93,12 +94,21 @@ + (BOOL)handleChallenge:(NSString *)challengeUrl [responseReq setValue:currentRequestTelemetryString forHTTPHeaderField:MSID_CURRENT_TELEMETRY_HEADER_NAME]; #endif - // Adding refreshTokenCredential (PRT) header to the challenge response. Header is available in customheaders dictionary + // Adding refreshTokenCredential (PRT) header to the challenge response. Header is available in customheaders dictionary. + // Only attach the PRT header when the challenge is being submitted to a known AAD host, to avoid leaking it to untrusted hosts. NSString *credentialHeader = [customHeaders objectForKey:MSID_REFRESH_TOKEN_CREDENTIAL]; if (credentialHeader) { - MSID_LOG_WITH_CTX(MSIDLogLevelInfo, context, @"Added refresh token to the PkeyAuth response."); - [responseReq setValue:credentialHeader forHTTPHeaderField:MSID_REFRESH_TOKEN_CREDENTIAL]; + NSString *submitHost = [NSURL URLWithString:submitUrl].host.lowercaseString; + if ([[MSIDAADNetworkConfiguration defaultConfiguration] isAADPublicCloud:submitHost]) + { + MSID_LOG_WITH_CTX(MSIDLogLevelInfo, context, @"Added refresh token to the PkeyAuth response."); + [responseReq setValue:credentialHeader forHTTPHeaderField:MSID_REFRESH_TOKEN_CREDENTIAL]; + } + else + { + MSID_LOG_WITH_CTX(MSIDLogLevelInfo, context, @"Skipped adding refresh token to the PkeyAuth response because the submit URL host is not a known AAD host."); + } } else { diff --git a/IdentityCore/tests/MSIDPKeyAuthHandlerTests.m b/IdentityCore/tests/MSIDPKeyAuthHandlerTests.m index 3351d57fe3..252ab21ede 100644 --- a/IdentityCore/tests/MSIDPKeyAuthHandlerTests.m +++ b/IdentityCore/tests/MSIDPKeyAuthHandlerTests.m @@ -143,6 +143,32 @@ - (void)testHandleChallengeWithRefreshToken_happyPath_shouldReturnSuccess XCTAssertTrue(handleResult); } +- (void)testHandleChallengeWithRefreshToken_whenSubmitUrlIsNotKnownAADHost_shouldNotAttachPRTHeader +{ + [self makeAppV2GroupEntitled:YES]; + + __auto_type pkeyUrl = @"urn:http-auth:PKeyAuth?CertAuthorities=OU%3d82dbaca4-3e81-46ca-9c73-0950c1eaca97%2cCN%3dMS-Organization-Access%2cDC%3dwindows%2cDC%3dnet&Version=1.0&Context=SOMECONTEXT&nonce=_bQWemEag2Zze-FR1kw2r-XyrDYxmQB2PftHsshTEJc&SubmitUrl=https%3a%2f%2fcontoso.untrusted.com%2fcommon%2fDeviceAuthPKeyAuth&TenantId=f645ad92-e38d-4d1a-b510-d1b09a74a8ca"; + NSString *value = @"FakeRefreshToken"; + NSDictionary *customHeaders = @{ MSID_REFRESH_TOKEN_CREDENTIAL : value}; + + __auto_type *context = [MSIDInteractiveTokenRequestParameters new]; + context.appRequestMetadata = nil; + context.extraURLQueryParameters = @{@"eqp1": @"val1", @"eqp2": @"val2"}; + __block BOOL callback = NO; + BOOL handleResult = [MSIDPKeyAuthHandler handleChallenge:pkeyUrl + context:context + customHeaders:customHeaders + externalSSOContext:nil + completionHandler:^(NSURLRequest *challengeResponse, NSError *error) { + XCTAssertNotNil(challengeResponse); + XCTAssertNil([[challengeResponse allHTTPHeaderFields] objectForKey:MSID_REFRESH_TOKEN_CREDENTIAL], @"RefreshToken should not be attached for non-AAD hosts"); + XCTAssertNil(error); + callback = YES; + }]; + XCTAssertTrue(callback); + XCTAssertTrue(handleResult); +} + - (void)testHandleChallengeNilRefreshToken_shouldProceedWithSuccess { [self makeAppV2GroupEntitled:YES]; From 06fe47f2bcab1aec3e900a997c8e668107391aa9 Mon Sep 17 00:00:00 2001 From: Hieu Nguyen Date: Wed, 17 Jun 2026 15:16:27 -0700 Subject: [PATCH 02/38] Add changelog entry for PkeyAuth PRT known AAD hosts restriction Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.txt b/changelog.txt index 3ce530ba53..5e5fe8b6f7 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,4 +1,5 @@ TBD +* Only attach the PRT (refresh token credential) header to the PkeyAuth challenge response for known/trusted AAD hosts, preventing PRT leakage to untrusted hosts. * Harden MSIDDIContainer: validate protocol conformance in resolveImplClassForProtocol:orDefault:; NSAssert in debug, log + fall back to default in release. Self-heal cache eviction on conformance failure; release-mode fallback test via NSAssertionHandler swap. Version 1.24.1 From 86701f2adb43e393fccdff8cc428ce01c2921256 Mon Sep 17 00:00:00 2001 From: Antonio Alwan Date: Thu, 18 Jun 2026 12:26:13 -0700 Subject: [PATCH 03/38] Merge Back Main branch into Dev --- changelog.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog.txt b/changelog.txt index 394d2ebb27..f63fcf7cb4 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,5 @@ +TBD + Version 1.25.0 * Harden MSIDDIContainer: validate protocol conformance in resolveImplClassForProtocol:orDefault:; NSAssert in debug, log + fall back to default in release. Self-heal cache eviction on conformance failure; release-mode fallback test via NSAssertionHandler swap. * Hand off openid-vc:// URLs to the registered wallet from the AAD embedded webview without dismissing the webview; append x_ms_caller_redirect_uri/x_ms_caller_bundle_id/x_ms_correlation_id so the wallet can foreground the calling app when the VID flow completes. Add the MSIDOpenIdVcHandling delegate protocol on MSIDAADOAuthEmbeddedWebviewController so hosts (e.g. an SSO extension hosting VID in-process) can intercept openid-vc navigations and present in-process UI instead of bouncing out via openURL. Replaces the prior MSIDWebOpenIdVcResponse / MSIDWebOpenIdVcResponseOperation pipeline that erroneously errored out the interactive request. From 9e4ff921a928d1f0ee2c2ff1b44a75c5b6f0834d Mon Sep 17 00:00:00 2001 From: Hieu Nguyen <65981263+hieunguyenmsft@users.noreply.github.com> Date: Thu, 18 Jun 2026 14:59:45 -0700 Subject: [PATCH 04/38] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../embeddedWebview/challangeHandlers/MSIDPKeyAuthHandler.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IdentityCore/src/webview/embeddedWebview/challangeHandlers/MSIDPKeyAuthHandler.m b/IdentityCore/src/webview/embeddedWebview/challangeHandlers/MSIDPKeyAuthHandler.m index de06225cac..99e63acfe8 100644 --- a/IdentityCore/src/webview/embeddedWebview/challangeHandlers/MSIDPKeyAuthHandler.m +++ b/IdentityCore/src/webview/embeddedWebview/challangeHandlers/MSIDPKeyAuthHandler.m @@ -100,7 +100,7 @@ + (BOOL)handleChallenge:(NSString *)challengeUrl if (credentialHeader) { NSString *submitHost = [NSURL URLWithString:submitUrl].host.lowercaseString; - if ([[MSIDAADNetworkConfiguration defaultConfiguration] isAADPublicCloud:submitHost]) + if (submitHost && [[MSIDAADNetworkConfiguration defaultConfiguration] isAADPublicCloud:submitHost]) { MSID_LOG_WITH_CTX(MSIDLogLevelInfo, context, @"Added refresh token to the PkeyAuth response."); [responseReq setValue:credentialHeader forHTTPHeaderField:MSID_REFRESH_TOKEN_CREDENTIAL]; From ac90645ed06c4500e85b6a471475e3d4ccdd97d1 Mon Sep 17 00:00:00 2001 From: josephpab Date: Thu, 18 Jun 2026 15:44:06 -0700 Subject: [PATCH 05/38] [Minor][Engg]: Final Lab Update (#1870) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## PR Checklist (must be completed before review) - [x] All tests pass locally - [x] PR size is <= 500 LOC per PR Size Check policy - [x] PR is independently mergeable (no hidden dependencies) - [x] Appropriate reviewers are assigned - [x] PR reviewed by code owner (required if Copilot-generated) - [x] SME or Senior IC assigned where required ## Proposed changes Two test-infrastructure updates so the IdentityCore-driven UI / lab tests keep working against current iOS sims and the LAB v2 service: **UI automation hardening** (`IdentityCore/tests/automation/ui_tests_lib/MSIDBaseUITest.m`) — addresses intermittent UI-test failures on iOS 18+ sims: - `aadEnterPassword:` / `tapPasswordSelectionButtonIfPresentInApp:` — scope the "Use your password" / "Other ways to sign in" lookups to `application.webViews` so iOS QuickType / Passwords AutoFill suggestions with the same label don't win the first-match query (which would otherwise open the empty system password picker on CI sims and loop the test). - New `dismissKeyboardIfVerifyEmailPagePresentInApp:` — on the MSA "Verify your email" interstitial the auto-focused email field raises the keyboard, which absorbs taps on the "Use your password" link even though the link reports as hittable. The helper taps the page header to defocus the email field and dismiss the keyboard so the link becomes truly tappable on the next polling tick. - `enterPassword:app:isMainApp:` — require `.isHittable` (not just `.exists`) on the secure text field so back-to-back `acquireToken` calls (e.g. `prompt=force` after a prior sign-in) don't type into a stale field left in the view hierarchy. **Lab API endpoint migration** (`IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomation*APIRequest.m`) — point at the new ID4SLAB2 operations: `CreateTempUser`, `DeleteDevice`, `EnablePolicy`/`DisablePolicy`, and `Reset` are all suffixed `ID4SLAB2`. ## Type of change - [ ] Feature work - [ ] Bug fix - [ ] Documentation - [x] Engineering change - [x] Test - [ ] Logging/Telemetry ## Risk - [ ] High – Errors could cause MAJOR regression of many scenarios. (Example: new large features or high level infrastructure changes) - [ ] Medium – Errors could cause regression of 1 or more scenarios. (Example: somewhat complex bug fixes, small new features) - [x] Small – No issues are expected. (Example: Very small bug fixes, string changes, or configuration settings changes) ## Additional information Scope is test-only — no production source is touched. The four `MSIDAutomation*APIRequest` files only override `-requestOperationPath`, which is consumed exclusively by the lab automation client; `MSIDBaseUITest.m` lives under `IdentityCore/tests/automation/ui_tests_lib/` and is only linked by UI-test targets. No changelog entry is needed (test infrastructure only). --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../automation/ui_tests_lib/MSIDBaseUITest.m | 85 ++++++++++++++++++- .../MSIDAutomationDeleteDeviceAPIRequest.m | 2 +- .../MSIDAutomationPolicyToggleAPIRequest.m | 2 +- .../lab_api/MSIDAutomationResetAPIRequest.m | 2 +- .../MSIDAutomationTemporaryAccountRequest.m | 2 +- 5 files changed, 85 insertions(+), 8 deletions(-) diff --git a/IdentityCore/tests/automation/ui_tests_lib/MSIDBaseUITest.m b/IdentityCore/tests/automation/ui_tests_lib/MSIDBaseUITest.m index 571e5c656a..d9317303e9 100644 --- a/IdentityCore/tests/automation/ui_tests_lib/MSIDBaseUITest.m +++ b/IdentityCore/tests/automation/ui_tests_lib/MSIDBaseUITest.m @@ -301,7 +301,11 @@ - (void)aadEnterPassword:(XCUIApplication *)application { if (![self tapPasswordSelectionButtonIfPresentInApp:application]) { - XCUIElement *useYourPasswordElement = application.staticTexts[@"Use your password"]; + // Same rationale as tapPasswordSelectionButtonIfPresentInApp: stay + // inside the web view so an identically-labeled QuickType / AutoFill + // suggestion never wins the first-match query. The polling loop in + // enterPassword: handles the "not present yet" case. + XCUIElement *useYourPasswordElement = application.webViews.staticTexts[@"Use your password"]; if ([self waitForElementsAndContinueIfNotAppear:useYourPasswordElement timeout:1.0f] == XCTWaiterResultCompleted) { [useYourPasswordElement msidTap]; @@ -350,8 +354,66 @@ - (void)setupPassword:(NSString *)password app:(XCUIApplication *)application is } } +- (BOOL)dismissKeyboardIfVerifyEmailPagePresentInApp:(XCUIApplication *)application +{ + // The MSA "Verify your email" interstitial (shown during B2C / MSA login + // flows when the account needs proof-of-control) auto-focuses the email + // text field, which raises the iOS keyboard. The keyboard covers the + // lower part of the page, including the "Use your password" link we want. + // The link is in the view hierarchy (so .exists is YES) and XCUI may + // even report it as .isHittable, but synthesized taps land on the + // keyboard's hit area and get absorbed — the link never receives the tap + // and the page never progresses. + // + // Dismiss the keyboard by tapping the "Verify your email" header itself. + // Tapping a static text in a webview is a no-op for the page (no link, no + // event handler), but it defocuses the email text field, which causes + // iOS to dismiss the keyboard. After dismissal the page reflows and the + // password link becomes truly tappable. The polling loop in enterPassword: + // re-invokes tapPasswordSelectionButtonIfPresentInApp: on the next tick. + // + // This is more reliable than searching for the keyboard's "Done" accessory + // button — Done lives under different parents (toolbars/keyboards/ + // otherElements) on different iOS versions and surface owners + // (SafariViewController vs WKWebView), and on some iOS 18+ sims isn't + // exposed to XCUI at all. + XCUIElement *header = application.webViews.staticTexts[@"Verify your email"]; + if (!header.exists || !header.isHittable) + { + return NO; + } + + XCUIElement *keyboard = application.keyboards.firstMatch; + if (!keyboard.exists) + { + // Page is showing but no keyboard up — nothing to dismiss. + return NO; + } + + [header msidTap]; + + // Best-effort wait for dismissal to avoid immediately re-hitting the covered link. + NSDate *deadline = [NSDate dateWithTimeIntervalSinceNow:2.0]; + while (keyboard.exists && deadline.timeIntervalSinceNow > 0) + { + [NSThread sleepForTimeInterval:0.1]; + } + + return YES; +} + - (BOOL)tapPasswordSelectionButtonIfPresentInApp:(XCUIApplication *)application { + // If we're on the MSA "Verify your email" interstitial with the keyboard + // up, the "Use your password" link is covered by the keyboard. Dismiss + // the keyboard first so the link is hittable on the next loop iteration. + if ([self dismissKeyboardIfVerifyEmailPagePresentInApp:application]) + { + // Keyboard dismissal is an intentional state transition; avoid attempting + // other password-selection taps until the next polling iteration. + return YES; + } + NSArray *passwordButtonTitles = @[ @"Use my password", @"Use your password", @@ -359,10 +421,18 @@ - (BOOL)tapPasswordSelectionButtonIfPresentInApp:(XCUIApplication *)application @"Other ways to sign in" ]; + // Password-selection buttons only ever appear inside the AAD/MSA/B2C web + // page (rendered in a WKWebView / SFSafariViewController inside the test + // host). Restrict the lookup to web views so we never match an iOS + // QuickType bar / Passwords AutoFill accessory button that happens to + // carry the same label — tapping those opens the empty system password + // picker on CI sims with no saved credentials and the test loops forever. + // The polling loop in enterPassword: retries every second, so returning NO + // here when the web button hasn't rendered yet is the desired behavior. for (NSString *buttonTitle in passwordButtonTitles) { - XCUIElement *button = application.buttons[buttonTitle]; - if (!button.exists) + XCUIElement *button = application.webViews.buttons[buttonTitle]; + if (!button.exists || !button.isHittable) { continue; } @@ -381,7 +451,14 @@ - (void)enterPassword:(NSString *)password app:(XCUIApplication *)application is while (deadline.timeIntervalSinceNow > 0) { - if (passwordSecureTextField.exists) + // Require .isHittable in addition to .exists so we don't tap and type + // into a stale SecureTextField that's still in the view hierarchy from + // a previous sign-in step. Without this, a second acquireToken call + // (e.g. prompt=force with a login_hint after a prior sign-in) can find + // the previous step's password field, send keystrokes that go nowhere, + // and leave the broker waiting on an empty password — the test then + // times out in waitForRedirectToClientApp. + if (passwordSecureTextField.exists && passwordSecureTextField.isHittable) { [self tapElementAndWaitForKeyboardToAppear:passwordSecureTextField app:application]; NSString *passwordString = [NSString stringWithFormat:@"%@\n", password]; diff --git a/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationDeleteDeviceAPIRequest.m b/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationDeleteDeviceAPIRequest.m index 4a8193f531..85ca8d9687 100644 --- a/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationDeleteDeviceAPIRequest.m +++ b/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationDeleteDeviceAPIRequest.m @@ -29,7 +29,7 @@ @implementation MSIDAutomationDeleteDeviceAPIRequest - (NSString *)requestOperationPath { - return @"DeleteDevice"; + return @"DeleteDeviceID4SLAB2"; } - (NSString *)httpMethod diff --git a/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationPolicyToggleAPIRequest.m b/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationPolicyToggleAPIRequest.m index a2c4db7eac..eacd77df63 100644 --- a/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationPolicyToggleAPIRequest.m +++ b/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationPolicyToggleAPIRequest.m @@ -29,7 +29,7 @@ @implementation MSIDAutomationPolicyToggleAPIRequest - (NSString *)requestOperationPath { - return self.policyEnabled ? @"EnablePolicy" : @"DisablePolicy"; + return self.policyEnabled ? @"EnablePolicyID4SLAB2" : @"DisablePolicyID4SLAB2"; } - (NSString *)httpMethod diff --git a/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationResetAPIRequest.m b/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationResetAPIRequest.m index b8d64d2d26..c2027941b9 100644 --- a/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationResetAPIRequest.m +++ b/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationResetAPIRequest.m @@ -29,7 +29,7 @@ @implementation MSIDAutomationResetAPIRequest - (NSString *)requestOperationPath { - return @"Reset"; + return @"ResetID4SLAB2"; } - (NSString *)httpMethod diff --git a/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationTemporaryAccountRequest.m b/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationTemporaryAccountRequest.m index 32d4d4c848..852997d21d 100644 --- a/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationTemporaryAccountRequest.m +++ b/IdentityCore/tests/automation/ui_tests_lib/lab_api/MSIDAutomationTemporaryAccountRequest.m @@ -38,7 +38,7 @@ - (nonnull id)copyWithZone:(nullable NSZone *)zone - (NSString *)requestOperationPath { - return @"CreateTempUser"; + return @"CreateTempUserID4SLAB2"; } - (NSArray *)queryItems From 8ec4eebae11f2ff7aef414ccbaf01a5bbe05ba48 Mon Sep 17 00:00:00 2001 From: Hieu Nguyen Date: Mon, 22 Jun 2026 10:25:14 -0700 Subject: [PATCH 06/38] Record PkeyAuth PRT execution-flow tag when correlation id is available Add MSIDPkeyAuthTag execution-flow tags for the added/skipped PRT-header decision in MSIDPKeyAuthHandler, recorded only when context.correlationId is available. Addresses review feedback on PR #1869. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../MSIDExecutionFlowConstants.h | 10 +++ .../MSIDExecutionFlowConstants.m | 14 ++++ .../challangeHandlers/MSIDPKeyAuthHandler.m | 10 +++ IdentityCore/tests/MSIDPKeyAuthHandlerTests.m | 76 +++++++++++++++++++ changelog.txt | 2 +- 5 files changed, 111 insertions(+), 1 deletion(-) diff --git a/IdentityCore/src/telemetry/execution_flow/MSIDExecutionFlowConstants.h b/IdentityCore/src/telemetry/execution_flow/MSIDExecutionFlowConstants.h index 598446b1ce..174045cf42 100644 --- a/IdentityCore/src/telemetry/execution_flow/MSIDExecutionFlowConstants.h +++ b/IdentityCore/src/telemetry/execution_flow/MSIDExecutionFlowConstants.h @@ -123,3 +123,13 @@ typedef NS_ENUM(NSInteger, MSIDSSORemoteSilentTokenRequestTag) }; /// Returns the string representation for each MSIDSSORemoteSilentTokenRequestTag value. FOUNDATION_EXPORT NSString * _Nonnull MSIDSSORemoteSilentTokenRequestTagToString(MSIDSSORemoteSilentTokenRequestTag state); + +/// An enum of MSIDPkeyAuthTag. +typedef NS_ENUM(NSInteger, MSIDPkeyAuthTag) +{ + MSIDPkeyAuthAddedRefreshTokenCredentialTag = 0, + MSIDPkeyAuthSkippedRefreshTokenCredentialUntrustedHostTag +}; + +/// Returns the string representation for each MSIDPkeyAuthTag value. +FOUNDATION_EXPORT NSString * _Nonnull MSIDPkeyAuthTagToString(MSIDPkeyAuthTag state); diff --git a/IdentityCore/src/telemetry/execution_flow/MSIDExecutionFlowConstants.m b/IdentityCore/src/telemetry/execution_flow/MSIDExecutionFlowConstants.m index 21a4e26cb0..03e6b8e8ac 100644 --- a/IdentityCore/src/telemetry/execution_flow/MSIDExecutionFlowConstants.m +++ b/IdentityCore/src/telemetry/execution_flow/MSIDExecutionFlowConstants.m @@ -150,3 +150,17 @@ return [NSString stringWithFormat:@"MSIDSSORemoteSilentTokenRequestTag(%ld)", (long)state]; } +NSString *MSIDPkeyAuthTagToString(MSIDPkeyAuthTag state) +{ + switch (state) + { + case MSIDPkeyAuthAddedRefreshTokenCredentialTag: + return @"p5e7g"; + case MSIDPkeyAuthSkippedRefreshTokenCredentialUntrustedHostTag: + return @"mi1dp"; + } + // Fallback for any future enum values + return [NSString stringWithFormat:@"MSIDPkeyAuthTag(%ld)", (long)state]; +} + + diff --git a/IdentityCore/src/webview/embeddedWebview/challangeHandlers/MSIDPKeyAuthHandler.m b/IdentityCore/src/webview/embeddedWebview/challangeHandlers/MSIDPKeyAuthHandler.m index 99e63acfe8..da9a412c85 100644 --- a/IdentityCore/src/webview/embeddedWebview/challangeHandlers/MSIDPKeyAuthHandler.m +++ b/IdentityCore/src/webview/embeddedWebview/challangeHandlers/MSIDPKeyAuthHandler.m @@ -36,6 +36,8 @@ #endif #import "MSIDWorkPlaceJoinUtil.h" #import "MSIDAADNetworkConfiguration.h" +#import "MSIDExecutionFlowLogger.h" +#import "MSIDExecutionFlowConstants.h" @implementation MSIDPKeyAuthHandler @@ -104,10 +106,18 @@ + (BOOL)handleChallenge:(NSString *)challengeUrl { MSID_LOG_WITH_CTX(MSIDLogLevelInfo, context, @"Added refresh token to the PkeyAuth response."); [responseReq setValue:credentialHeader forHTTPHeaderField:MSID_REFRESH_TOKEN_CREDENTIAL]; + if (context.correlationId) + { + MSIDExecutionFlowInsertTag(MSIDPkeyAuthTagToString(MSIDPkeyAuthAddedRefreshTokenCredentialTag), nil, context.correlationId); + } } else { MSID_LOG_WITH_CTX(MSIDLogLevelInfo, context, @"Skipped adding refresh token to the PkeyAuth response because the submit URL host is not a known AAD host."); + if (context.correlationId) + { + MSIDExecutionFlowInsertTag(MSIDPkeyAuthTagToString(MSIDPkeyAuthSkippedRefreshTokenCredentialUntrustedHostTag), nil, context.correlationId); + } } } else diff --git a/IdentityCore/tests/MSIDPKeyAuthHandlerTests.m b/IdentityCore/tests/MSIDPKeyAuthHandlerTests.m index 252ab21ede..3be562ec1a 100644 --- a/IdentityCore/tests/MSIDPKeyAuthHandlerTests.m +++ b/IdentityCore/tests/MSIDPKeyAuthHandlerTests.m @@ -31,6 +31,8 @@ #import "MSIDBasicContext.h" #import "MSIDTestSwizzle.h" #import "MSIDInteractiveTokenRequestParameters.h" +#import "MSIDExecutionFlowLogger.h" +#import "MSIDExecutionFlowConstants.h" @interface MSIDPKeyAuthHandlerTests : XCTestCase @@ -224,6 +226,80 @@ - (void)testHandleChallengeWithEmptyCustomHeaders_shouldProceedWithSuccess XCTAssertTrue(handleResult); } +- (void)testHandleChallengeWithRefreshToken_whenKnownAADHost_shouldRecordAddedExecutionFlowTag +{ + [self makeAppV2GroupEntitled:YES]; + + __auto_type pkeyUrl = @"urn:http-auth:PKeyAuth?CertAuthorities=OU%3d82dbaca4-3e81-46ca-9c73-0950c1eaca97%2cCN%3dMS-Organization-Access%2cDC%3dwindows%2cDC%3dnet&Version=1.0&Context=SOMECONTEXT&nonce=_bQWemEag2Zze-FR1kw2r-XyrDYxmQB2PftHsshTEJc&SubmitUrl=https%3a%2f%2flogin.microsoftonline.com%2fcommon%2fDeviceAuthPKeyAuth&TenantId=f645ad92-e38d-4d1a-b510-d1b09a74a8ca"; + NSString *value = @"FakeRefreshToken"; + NSDictionary *customHeaders = @{ MSID_REFRESH_TOKEN_CREDENTIAL : value}; + + __auto_type *context = [MSIDInteractiveTokenRequestParameters new]; + context.appRequestMetadata = nil; + context.correlationId = [NSUUID UUID]; + MSIDExecutionFlowRegister(context.correlationId); + + __block BOOL callback = NO; + BOOL handleResult = [MSIDPKeyAuthHandler handleChallenge:pkeyUrl + context:context + customHeaders:customHeaders + externalSSOContext:nil + completionHandler:^(NSURLRequest *challengeResponse, NSError *error) { + XCTAssertNotNil(challengeResponse); + XCTAssertTrue([[[challengeResponse allHTTPHeaderFields] objectForKey:MSID_REFRESH_TOKEN_CREDENTIAL] isEqual:value]); + XCTAssertNil(error); + callback = YES; + }]; + XCTAssertTrue(callback); + XCTAssertTrue(handleResult); + + XCTestExpectation *flowExpectation = [self expectationWithDescription:@"execution flow should contain the added PRT tag"]; + MSIDExecutionFlowRetrieve(context.correlationId, nil, YES, ^(NSString * _Nullable executionFlow) { + XCTAssertNotNil(executionFlow); + XCTAssertTrue([executionFlow containsString:MSIDPkeyAuthTagToString(MSIDPkeyAuthAddedRefreshTokenCredentialTag)], @"Flow should record the added PRT tag"); + XCTAssertFalse([executionFlow containsString:MSIDPkeyAuthTagToString(MSIDPkeyAuthSkippedRefreshTokenCredentialUntrustedHostTag)], @"Flow should not record the skipped tag"); + [flowExpectation fulfill]; + }); + [self waitForExpectationsWithTimeout:1 handler:nil]; +} + +- (void)testHandleChallengeWithRefreshToken_whenUntrustedHost_shouldRecordSkippedExecutionFlowTag +{ + [self makeAppV2GroupEntitled:YES]; + + __auto_type pkeyUrl = @"urn:http-auth:PKeyAuth?CertAuthorities=OU%3d82dbaca4-3e81-46ca-9c73-0950c1eaca97%2cCN%3dMS-Organization-Access%2cDC%3dwindows%2cDC%3dnet&Version=1.0&Context=SOMECONTEXT&nonce=_bQWemEag2Zze-FR1kw2r-XyrDYxmQB2PftHsshTEJc&SubmitUrl=https%3a%2f%2fcontoso.untrusted.com%2fcommon%2fDeviceAuthPKeyAuth&TenantId=f645ad92-e38d-4d1a-b510-d1b09a74a8ca"; + NSString *value = @"FakeRefreshToken"; + NSDictionary *customHeaders = @{ MSID_REFRESH_TOKEN_CREDENTIAL : value}; + + __auto_type *context = [MSIDInteractiveTokenRequestParameters new]; + context.appRequestMetadata = nil; + context.correlationId = [NSUUID UUID]; + MSIDExecutionFlowRegister(context.correlationId); + + __block BOOL callback = NO; + BOOL handleResult = [MSIDPKeyAuthHandler handleChallenge:pkeyUrl + context:context + customHeaders:customHeaders + externalSSOContext:nil + completionHandler:^(NSURLRequest *challengeResponse, NSError *error) { + XCTAssertNotNil(challengeResponse); + XCTAssertNil([[challengeResponse allHTTPHeaderFields] objectForKey:MSID_REFRESH_TOKEN_CREDENTIAL]); + XCTAssertNil(error); + callback = YES; + }]; + XCTAssertTrue(callback); + XCTAssertTrue(handleResult); + + XCTestExpectation *flowExpectation = [self expectationWithDescription:@"execution flow should contain the skipped PRT tag"]; + MSIDExecutionFlowRetrieve(context.correlationId, nil, YES, ^(NSString * _Nullable executionFlow) { + XCTAssertNotNil(executionFlow); + XCTAssertTrue([executionFlow containsString:MSIDPkeyAuthTagToString(MSIDPkeyAuthSkippedRefreshTokenCredentialUntrustedHostTag)], @"Flow should record the skipped PRT tag"); + XCTAssertFalse([executionFlow containsString:MSIDPkeyAuthTagToString(MSIDPkeyAuthAddedRefreshTokenCredentialTag)], @"Flow should not record the added tag"); + [flowExpectation fulfill]; + }); + [self waitForExpectationsWithTimeout:1 handler:nil]; +} + - (void)makeAppV2GroupEntitled:(BOOL)entitled { [MSIDTestSwizzle classMethod:@selector(v2AccessGroupAllowedWithContext:) diff --git a/changelog.txt b/changelog.txt index 140b8df180..3a078c6f94 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,5 @@ TBD -* Only attach the PRT (refresh token credential) header to the PkeyAuth challenge response for known/trusted AAD hosts, preventing PRT leakage to untrusted hosts. +* Only attach the PRT (refresh token credential) header to the PkeyAuth challenge response for known/trusted AAD hosts, preventing PRT leakage to untrusted hosts. Record an execution-flow tag for the added/skipped decision when a correlation id is available. Version 1.25.0 * Harden MSIDDIContainer: validate protocol conformance in resolveImplClassForProtocol:orDefault:; NSAssert in debug, log + fall back to default in release. Self-heal cache eviction on conformance failure; release-mode fallback test via NSAssertionHandler swap. From cb4f05a168ad1e5f2fee91cae183cd91938a77b7 Mon Sep 17 00:00:00 2001 From: Swasti Gupta Date: Tue, 23 Jun 2026 10:28:52 -0700 Subject: [PATCH 07/38] Add MSIDMobileOnboardingState for shared-reference flag propagation (#1868) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem `isNewMobileOnboardingFlow` is a `BOOL` (value type) on `MSIDInteractiveRequestParameters`. When the webview sets it to `YES` on encountering `msauth://enroll`, that mutation only affects the current params instance. Broker actions create **new** params objects from `ADBrokerRequest` — the flag is lost because BOOLs are copied, not shared. This is the same class of problem that `MSIDOnboardingBlobBuilder` solved for the onboarding blob — reference types are shared by pointer, value types are copied. ## Solution Introduce `MSIDMobileOnboardingState` — a minimal reference-type wrapper (same pattern as `MSIDOnboardingBlobBuilder`): ```objc @interface MSIDMobileOnboardingState : NSObject @property (nonatomic) BOOL isNewMobileOnboardingFlow; @end ``` ### How it works 1. **`ADBrokerRequest`** creates one `MSIDMobileOnboardingState` per auth session, seeded from the IPC payload 2. **Every `MSIDInteractiveRequestParameters`** gets the same pointer: `params.mobileOnboardingState = request.mobileOnboardingState` 3. **Webview** sets `params.isNewMobileOnboardingFlow = YES` → computed setter writes to the shared state object 4. **All consumers** (serializers, follow-up actions) see the mutation immediately — no sync-back needed 5. **SSO extension hop** — the flag is serialized to JSON by `MSIDBrokerOperationInteractiveTokenRequest`, deserialized on the other side, and seeds a new `MSIDMobileOnboardingState` ### Why not just a BOOL? | Object | Persists across actions? | Webview can write? | |--------|------------------------|--------------------| | `ADBrokerRequest.isNewMobileOnboardingFlow` (BOOL) | ✅ | ❌ (webview has no access) | | `MSIDInteractiveRequestParameters.isNewMobileOnboardingFlow` (BOOL) | ❌ (recreated per action) | ✅ | | `MSIDMobileOnboardingState` (reference) | ✅ (shared pointer) | ✅ (via params) | No other BOOL on params has this requirement — all others (`forceUI`, `instanceAware`, `showHeadsUp`) are set once from IPC and never mutated mid-session. ## Changes - **New**: `MSIDMobileOnboardingState.h/.m` — shared mutable state holder - **Modified**: `MSIDInteractiveRequestParameters.h/.m` — added `mobileOnboardingState` property, `isNewMobileOnboardingFlow` is now a computed accessor forwarding to the state object Companion Broker4 PR: (pending) --------- Co-authored-by: Swasti Gupta Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../IdentityCore.xcodeproj/project.pbxproj | 69 +++++++++++-------- ...IDBrokerOperationInteractiveTokenRequest.h | 4 +- .../MSIDInteractiveRequestParameters.h | 12 ++-- .../MSIDInteractiveRequestParameters.m | 17 +++++ .../parameters/MSIDMobileOnboardingState.h | 35 ++++++++++ .../parameters/MSIDMobileOnboardingState.m | 28 ++++++++ .../ios/MSIDBrokerTokenRequestTests.m | 34 +++++++++ 7 files changed, 163 insertions(+), 36 deletions(-) create mode 100644 IdentityCore/src/parameters/MSIDMobileOnboardingState.h create mode 100644 IdentityCore/src/parameters/MSIDMobileOnboardingState.m diff --git a/IdentityCore/IdentityCore.xcodeproj/project.pbxproj b/IdentityCore/IdentityCore.xcodeproj/project.pbxproj index e4b25509ec..ef5acda78c 100644 --- a/IdentityCore/IdentityCore.xcodeproj/project.pbxproj +++ b/IdentityCore/IdentityCore.xcodeproj/project.pbxproj @@ -19,6 +19,7 @@ 0570FE80219B8C8C00958ECF /* MSIDCredentialCacheItem+MSIDBaseToken.h in Headers */ = {isa = PBXBuildFile; fileRef = 0570FE7D219B8C8C00958ECF /* MSIDCredentialCacheItem+MSIDBaseToken.h */; }; 0570FE81219E33FB00958ECF /* MSIDCredentialCacheItem+MSIDBaseToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 0570FE7C219B8C8C00958ECF /* MSIDCredentialCacheItem+MSIDBaseToken.m */; }; 0CC830A9C664A75FFE7C032C /* MSIDDIContainerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2E356C396133919B12FB4F01 /* MSIDDIContainerTests.m */; }; + 131989824FC049AFB96F9E3E /* MSIDThrottlingRefreshing.h in Headers */ = {isa = PBXBuildFile; fileRef = CACE5D66E6234506B9936BC5 /* MSIDThrottlingRefreshing.h */; }; 1E00D281248F27ED006E4BAE /* MSIDAuthScheme.h in Headers */ = {isa = PBXBuildFile; fileRef = 1E00D27F248F27ED006E4BAE /* MSIDAuthScheme.h */; }; 1E00D282248F27ED006E4BAE /* MSIDAuthScheme.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E00D280248F27ED006E4BAE /* MSIDAuthScheme.m */; }; 1E00D283248F27ED006E4BAE /* MSIDAuthScheme.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E00D280248F27ED006E4BAE /* MSIDAuthScheme.m */; }; @@ -597,6 +598,7 @@ 2A886D702ECBE3D600675D31 /* MSIDGCDStarvationDetector.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A886D6D2ECBE3D600675D31 /* MSIDGCDStarvationDetector.m */; }; 2AADDAC72DADB84D00CB7740 /* MSIDSSOXpcSilentTokenRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AADDAC62DADB84D00CB7740 /* MSIDSSOXpcSilentTokenRequest.m */; }; 2AADDAC82DADB84D00CB7740 /* MSIDSSOXpcSilentTokenRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 2AADDAC52DADB84D00CB7740 /* MSIDSSOXpcSilentTokenRequest.h */; }; + 2D9C4F18A6B941579F0D8C36 /* MSIDThrottlingMetaDataReading.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A3F1B92D04C45E8A9C16384 /* MSIDThrottlingMetaDataReading.h */; }; 3A7F0C6025AB453BB48081FB /* MSIDDeviceTokenResponseHandlerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DB2B5B443CD84503A7A0A8F5 /* MSIDDeviceTokenResponseHandlerTests.m */; }; 3AA5A4B540B84C95881A3197 /* MSIDDeviceTokenGrantRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 96B39696EBC44368B865FB1E /* MSIDDeviceTokenGrantRequestTests.m */; }; 43F7552B93054DDE99785519 /* MSIDDeviceTokenGrantRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 96B39696EBC44368B865FB1E /* MSIDDeviceTokenGrantRequestTests.m */; }; @@ -724,8 +726,6 @@ 606830102098E94100CCA6AB /* MSIDCertificateChooser.m in Sources */ = {isa = PBXBuildFile; fileRef = 6068300F2098E94100CCA6AB /* MSIDCertificateChooser.m */; }; 6068303A20A3560F00CCA6AB /* MSIDPKeyAuthHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 6068303820A33A9000CCA6AB /* MSIDPKeyAuthHandler.m */; }; 606B108C20D084B600B34224 /* MSIDAADV1WebviewFactoryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 606B108A20D084B600B34224 /* MSIDAADV1WebviewFactoryTests.m */; }; - B427657B2F3EC2A100F79587 /* MSIDWebviewNavigationHandlerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B427657A2F3EC2A100F79587 /* MSIDWebviewNavigationHandlerTests.m */; }; - B427657C2F3EC2A100F79587 /* MSIDWebviewNavigationHandlerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B427657A2F3EC2A100F79587 /* MSIDWebviewNavigationHandlerTests.m */; }; 606B108E20D08C9500B34224 /* MSIDOAuth2EmbeddedWebviewControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 606B108D20D08C9500B34224 /* MSIDOAuth2EmbeddedWebviewControllerTests.m */; }; 606B108F20D08C9500B34224 /* MSIDOAuth2EmbeddedWebviewControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 606B108D20D08C9500B34224 /* MSIDOAuth2EmbeddedWebviewControllerTests.m */; }; 607123C1210FCAAD00B91068 /* MSIDAADAuthorityValidationRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 607123C0210FCAAD00B91068 /* MSIDAADAuthorityValidationRequest.m */; }; @@ -871,8 +871,6 @@ 72D961B02DE12F30005DED66 /* MSIDCachedNonce.m in Sources */ = {isa = PBXBuildFile; fileRef = 72D961AF2DE12F2E005DED66 /* MSIDCachedNonce.m */; }; 72D961B12DE12F30005DED66 /* MSIDCachedNonce.m in Sources */ = {isa = PBXBuildFile; fileRef = 72D961AF2DE12F2E005DED66 /* MSIDCachedNonce.m */; }; 73D7C94FBD27C5CA3480B739 /* MSIDDIContainer.h in Headers */ = {isa = PBXBuildFile; fileRef = 30CDFCBD388F4440556637F9 /* MSIDDIContainer.h */; settings = {ATTRIBUTES = (Project, ); }; }; - AE6838E26F5648EFAB74A481 /* MSIDThrottlingRefreshing.h in Headers */ = {isa = PBXBuildFile; fileRef = CACE5D66E6234506B9936BC5 /* MSIDThrottlingRefreshing.h */; }; - B5E8A92C146D4F38B5C92E17 /* MSIDThrottlingMetaDataReading.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A3F1B92D04C45E8A9C16384 /* MSIDThrottlingMetaDataReading.h */; }; 740340B92460E5C400DFCF27 /* MSIDCurrentRequestTelemetrySerializedItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 740340B72460E5C400DFCF27 /* MSIDCurrentRequestTelemetrySerializedItem.h */; }; 740340BA2460E5C400DFCF27 /* MSIDCurrentRequestTelemetrySerializedItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 740340B82460E5C400DFCF27 /* MSIDCurrentRequestTelemetrySerializedItem.m */; }; 740340BB2460E5C400DFCF27 /* MSIDCurrentRequestTelemetrySerializedItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 740340B82460E5C400DFCF27 /* MSIDCurrentRequestTelemetrySerializedItem.m */; }; @@ -1042,6 +1040,7 @@ A0E541D425CDDAB30016E167 /* MSIDThrottlingMetaDataCache.h in Headers */ = {isa = PBXBuildFile; fileRef = A0E541D325CDDAB30016E167 /* MSIDThrottlingMetaDataCache.h */; }; A0E541EE25CDDAFD0016E167 /* MSIDThrottlingMetaDataCache.m in Sources */ = {isa = PBXBuildFile; fileRef = A0E541ED25CDDAFD0016E167 /* MSIDThrottlingMetaDataCache.m */; }; A0E541EF25CDDAFD0016E167 /* MSIDThrottlingMetaDataCache.m in Sources */ = {isa = PBXBuildFile; fileRef = A0E541ED25CDDAFD0016E167 /* MSIDThrottlingMetaDataCache.m */; }; + AE6838E26F5648EFAB74A481 /* MSIDThrottlingRefreshing.h in Headers */ = {isa = PBXBuildFile; fileRef = CACE5D66E6234506B9936BC5 /* MSIDThrottlingRefreshing.h */; }; B2000C8D20EC62D70092790A /* MSIDAADV1IdTokenClaims.m in Sources */ = {isa = PBXBuildFile; fileRef = B2CDB5841FE3427F003A4B5C /* MSIDAADV1IdTokenClaims.m */; }; B2000C8E20EC62DF0092790A /* MSIDAADV1IdTokenClaims.m in Sources */ = {isa = PBXBuildFile; fileRef = B2CDB5841FE3427F003A4B5C /* MSIDAADV1IdTokenClaims.m */; }; B2000C8F20EC63210092790A /* MSIDDefaultCredentialCacheKey.m in Sources */ = {isa = PBXBuildFile; fileRef = B251CC1F2040F6C6005E0179 /* MSIDDefaultCredentialCacheKey.m */; }; @@ -1457,7 +1456,6 @@ B286B9A52389DD07007833AD /* MSIDAuthorizeWebRequestConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 96A2D5A5209D102900F80E3A /* MSIDAuthorizeWebRequestConfiguration.h */; }; B286B9A62389DD1E007833AD /* MSIDSystemWebviewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 96A3E9B7208941D700BE5262 /* MSIDSystemWebviewController.h */; }; B286B9A72389DD2E007833AD /* MSIDAADOAuthEmbeddedWebviewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 6057EE8E20B5FCF8007976EB /* MSIDAADOAuthEmbeddedWebviewController.h */; }; - VC00000000000000000F2001 /* MSIDOpenIdVcHandling.h in Headers */ = {isa = PBXBuildFile; fileRef = VC00000000000000000F2003 /* MSIDOpenIdVcHandling.h */; }; B286B9A82389DD34007833AD /* MSIDWebviewUIController.h in Headers */ = {isa = PBXBuildFile; fileRef = 60B3855C20A96DAA00D546D0 /* MSIDWebviewUIController.h */; }; B286B9A92389DD37007833AD /* MSIDNTLMUIPrompt.h in Headers */ = {isa = PBXBuildFile; fileRef = 600D199C20963AD50004CD43 /* MSIDNTLMUIPrompt.h */; }; B286B9AA2389DD43007833AD /* MSIDCertificateChooser.h in Headers */ = {isa = PBXBuildFile; fileRef = 6068300E2098E92E00CCA6AB /* MSIDCertificateChooser.h */; }; @@ -1935,6 +1933,10 @@ B41163B929BAC9BF00E64619 /* MSIDWKNavigationActionMock.m in Sources */ = {isa = PBXBuildFile; fileRef = B41163B829BAC9BF00E64619 /* MSIDWKNavigationActionMock.m */; }; B41163BA29BAC9BF00E64619 /* MSIDWKNavigationActionMock.m in Sources */ = {isa = PBXBuildFile; fileRef = B41163B829BAC9BF00E64619 /* MSIDWKNavigationActionMock.m */; }; B41163BC29BAC9EE00E64619 /* MSIDWKNavigationActionMock.h in Headers */ = {isa = PBXBuildFile; fileRef = B41163BB29BAC9DE00E64619 /* MSIDWKNavigationActionMock.h */; }; + B4134C1A2FEA3E410037FE68 /* MSIDMobileOnboardingState.h in Headers */ = {isa = PBXBuildFile; fileRef = B4134C182FEA3E410037FE68 /* MSIDMobileOnboardingState.h */; }; + B4134C1B2FEA3E410037FE68 /* MSIDMobileOnboardingState.m in Sources */ = {isa = PBXBuildFile; fileRef = B4134C192FEA3E410037FE68 /* MSIDMobileOnboardingState.m */; }; + B4134C1C2FEA3E410037FE68 /* MSIDMobileOnboardingState.h in Headers */ = {isa = PBXBuildFile; fileRef = B4134C182FEA3E410037FE68 /* MSIDMobileOnboardingState.h */; }; + B4134C1D2FEA3E410037FE68 /* MSIDMobileOnboardingState.m in Sources */ = {isa = PBXBuildFile; fileRef = B4134C192FEA3E410037FE68 /* MSIDMobileOnboardingState.m */; }; B41DD0A22FB4187500F81A9A /* MSIDIntuneDeviceIdCache.h in Headers */ = {isa = PBXBuildFile; fileRef = B41DD0A12FB4187200F81A9A /* MSIDIntuneDeviceIdCache.h */; }; B41DD0A42FB4187B00F81A9A /* MSIDIntuneDeviceIdCache.m in Sources */ = {isa = PBXBuildFile; fileRef = B41DD0A32FB4187900F81A9A /* MSIDIntuneDeviceIdCache.m */; }; B41DD0A52FB4187B00F81A9A /* MSIDIntuneDeviceIdCache.m in Sources */ = {isa = PBXBuildFile; fileRef = B41DD0A32FB4187900F81A9A /* MSIDIntuneDeviceIdCache.m */; }; @@ -1943,14 +1945,16 @@ B41F0CD62F871F260029E631 /* MSIDWebMDMEnrollmentCompletionResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = B41F0CD52F871F230029E631 /* MSIDWebMDMEnrollmentCompletionResponse.h */; }; B41F0CD82F871F320029E631 /* MSIDWebMDMEnrollmentCompletionResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = B41F0CD72F871F2E0029E631 /* MSIDWebMDMEnrollmentCompletionResponse.m */; }; B41F0CD92F871F320029E631 /* MSIDWebMDMEnrollmentCompletionResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = B41F0CD72F871F2E0029E631 /* MSIDWebMDMEnrollmentCompletionResponse.m */; }; - B427654F2F3EC29200F79587 /* MSIDWebviewNavigationHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = B427654E2F3EC27600F79587 /* MSIDWebviewNavigationHandler.h */; }; - B42765512F3EC2A100F79587 /* MSIDWebviewNavigationHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = B42765502F3EC29F00F79587 /* MSIDWebviewNavigationHandler.m */; }; - B42765522F3EC2A100F79587 /* MSIDWebviewNavigationHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = B42765502F3EC29F00F79587 /* MSIDWebviewNavigationHandler.m */; }; B42558B62F57A6620024523D /* MSIDOnboardingBlobBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = B42558B52F57A65A0024523D /* MSIDOnboardingBlobBuilder.h */; }; B42558B82F57ADFD0024523D /* MSIDOnboardingBlobBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = B42558B72F57ADFD0024523D /* MSIDOnboardingBlobBuilder.m */; }; B42558B92F57ADFD0024523D /* MSIDOnboardingBlobBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = B42558B72F57ADFD0024523D /* MSIDOnboardingBlobBuilder.m */; }; B42558BB2F57AE340024523D /* MSIDOnboardingBlobBuilderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B42558BA2F57AE340024523D /* MSIDOnboardingBlobBuilderTests.m */; }; B42558BC2F57AE340024523D /* MSIDOnboardingBlobBuilderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B42558BA2F57AE340024523D /* MSIDOnboardingBlobBuilderTests.m */; }; + B427654F2F3EC29200F79587 /* MSIDWebviewNavigationHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = B427654E2F3EC27600F79587 /* MSIDWebviewNavigationHandler.h */; }; + B42765512F3EC2A100F79587 /* MSIDWebviewNavigationHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = B42765502F3EC29F00F79587 /* MSIDWebviewNavigationHandler.m */; }; + B42765522F3EC2A100F79587 /* MSIDWebviewNavigationHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = B42765502F3EC29F00F79587 /* MSIDWebviewNavigationHandler.m */; }; + B427657B2F3EC2A100F79587 /* MSIDWebviewNavigationHandlerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B427657A2F3EC2A100F79587 /* MSIDWebviewNavigationHandlerTests.m */; }; + B427657C2F3EC2A100F79587 /* MSIDWebviewNavigationHandlerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B427657A2F3EC2A100F79587 /* MSIDWebviewNavigationHandlerTests.m */; }; B42C16012CE7E54800553316 /* MSIDFamilyRefreshToken.h in Headers */ = {isa = PBXBuildFile; fileRef = B42C16002CE7E53800553316 /* MSIDFamilyRefreshToken.h */; }; B42C16032CE7E55200553316 /* MSIDFamilyRefreshToken.m in Sources */ = {isa = PBXBuildFile; fileRef = B42C16022CE7E54C00553316 /* MSIDFamilyRefreshToken.m */; }; B42C16042CE7E55200553316 /* MSIDFamilyRefreshToken.m in Sources */ = {isa = PBXBuildFile; fileRef = B42C16022CE7E54C00553316 /* MSIDFamilyRefreshToken.m */; }; @@ -2041,6 +2045,13 @@ B4EC850F2EF65C58005567DA /* MSIDJweResponse+EcdhAesGcm.m in Sources */ = {isa = PBXBuildFile; fileRef = B4EC850C2EF65C58005567DA /* MSIDJweResponse+EcdhAesGcm.m */; }; B4EC85102EF65C58005567DA /* MSIDJweResponse+EcdhAesGcm.h in Headers */ = {isa = PBXBuildFile; fileRef = B4EC850B2EF65C58005567DA /* MSIDJweResponse+EcdhAesGcm.h */; }; B4EC85122EF65C58005567DA /* MSIDJweResponse+EcdhAesGcm.m in Sources */ = {isa = PBXBuildFile; fileRef = B4EC850C2EF65C58005567DA /* MSIDJweResponse+EcdhAesGcm.m */; }; + B4FBF0332EF33A7600EDE1E9 /* MSIDOnboardingStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = B4FBF0322EF33A7600EDE1E9 /* MSIDOnboardingStatus.m */; }; + B4FBF0342EF33A7600EDE1E9 /* MSIDOnboardingStatus.h in Headers */ = {isa = PBXBuildFile; fileRef = B4FBF0312EF33A7600EDE1E9 /* MSIDOnboardingStatus.h */; }; + B4FBF0352EF33A7600EDE1E9 /* MSIDOnboardingStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = B4FBF0322EF33A7600EDE1E9 /* MSIDOnboardingStatus.m */; }; + B4FBF0372EF33AAA00EDE1E9 /* MSIDOnboardingStatusTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B4FBF0362EF33AAA00EDE1E9 /* MSIDOnboardingStatusTests.m */; }; + B4FBF0382EF33AAA00EDE1E9 /* MSIDOnboardingStatusTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B4FBF0362EF33AAA00EDE1E9 /* MSIDOnboardingStatusTests.m */; }; + B4FBF0402EF44BBB00EDE1E9 /* MSIDOnboardingStatusCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B4FBF03F2EF44BBB00EDE1E9 /* MSIDOnboardingStatusCacheTests.m */; }; + B4FBF0412EF44BBB00EDE1E9 /* MSIDOnboardingStatusCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B4FBF03F2EF44BBB00EDE1E9 /* MSIDOnboardingStatusCacheTests.m */; }; B4FDC6F72F3EFFD300091B6C /* MSIDWebviewNavigationDecisionResolver.h in Headers */ = {isa = PBXBuildFile; fileRef = B4FDC6F62F3EFFB600091B6C /* MSIDWebviewNavigationDecisionResolver.h */; }; B4FDC6F92F3EFFDB00091B6C /* MSIDWebviewNavigationDecisionResolver.m in Sources */ = {isa = PBXBuildFile; fileRef = B4FDC6F82F3EFFD800091B6C /* MSIDWebviewNavigationDecisionResolver.m */; }; B500F7322F1144A900E64911 /* MSIDBrokerOperationGetDefaultAccountRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B500F7302F1144A900E64911 /* MSIDBrokerOperationGetDefaultAccountRequestTests.m */; }; @@ -2058,13 +2069,7 @@ B5AAE11E2F03DADD0026B21B /* MSIDBrokerOperationGetDefaultAccountRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = B5AAE11D2F03DADD0026B21B /* MSIDBrokerOperationGetDefaultAccountRequest.m */; }; B5AAE11F2F03DADD0026B21B /* MSIDBrokerOperationGetDefaultAccountRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = B5AAE11C2F03DADD0026B21B /* MSIDBrokerOperationGetDefaultAccountRequest.h */; }; B5AAE1202F03DADD0026B21B /* MSIDBrokerOperationGetDefaultAccountRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = B5AAE11D2F03DADD0026B21B /* MSIDBrokerOperationGetDefaultAccountRequest.m */; }; - B4FBF0332EF33A7600EDE1E9 /* MSIDOnboardingStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = B4FBF0322EF33A7600EDE1E9 /* MSIDOnboardingStatus.m */; }; - B4FBF0342EF33A7600EDE1E9 /* MSIDOnboardingStatus.h in Headers */ = {isa = PBXBuildFile; fileRef = B4FBF0312EF33A7600EDE1E9 /* MSIDOnboardingStatus.h */; }; - B4FBF0352EF33A7600EDE1E9 /* MSIDOnboardingStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = B4FBF0322EF33A7600EDE1E9 /* MSIDOnboardingStatus.m */; }; - B4FBF0372EF33AAA00EDE1E9 /* MSIDOnboardingStatusTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B4FBF0362EF33AAA00EDE1E9 /* MSIDOnboardingStatusTests.m */; }; - B4FBF0382EF33AAA00EDE1E9 /* MSIDOnboardingStatusTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B4FBF0362EF33AAA00EDE1E9 /* MSIDOnboardingStatusTests.m */; }; - B4FBF0402EF44BBB00EDE1E9 /* MSIDOnboardingStatusCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B4FBF03F2EF44BBB00EDE1E9 /* MSIDOnboardingStatusCacheTests.m */; }; - B4FBF0412EF44BBB00EDE1E9 /* MSIDOnboardingStatusCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B4FBF03F2EF44BBB00EDE1E9 /* MSIDOnboardingStatusCacheTests.m */; }; + B5E8A92C146D4F38B5C92E17 /* MSIDThrottlingMetaDataReading.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A3F1B92D04C45E8A9C16384 /* MSIDThrottlingMetaDataReading.h */; }; B86FA7D42383757100E5195A /* MSIDMacACLKeychainAccessorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B86FA7C62383748000E5195A /* MSIDMacACLKeychainAccessorTests.m */; }; B86FA7D52383757600E5195A /* MSIDMacTokenCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B86FA7C72383748000E5195A /* MSIDMacTokenCacheTests.m */; }; B86FA7D62383757A00E5195A /* MSIDMacKeychainTokenCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B86FA7C82383748000E5195A /* MSIDMacKeychainTokenCacheTests.m */; }; @@ -2074,8 +2079,6 @@ B8F16E90245B548D0047457F /* MSIDWebViewPlatformParams.h in Headers */ = {isa = PBXBuildFile; fileRef = B8F16E8F245B548D0047457F /* MSIDWebViewPlatformParams.h */; }; B8F16E92245B572C0047457F /* MSIDWebViewPlatformParams.m in Sources */ = {isa = PBXBuildFile; fileRef = B8F16E91245B572C0047457F /* MSIDWebViewPlatformParams.m */; }; C2E599251DB14C46D8DD6261 /* MSIDDIContainer.h in Headers */ = {isa = PBXBuildFile; fileRef = 30CDFCBD388F4440556637F9 /* MSIDDIContainer.h */; settings = {ATTRIBUTES = (Project, ); }; }; - 131989824FC049AFB96F9E3E /* MSIDThrottlingRefreshing.h in Headers */ = {isa = PBXBuildFile; fileRef = CACE5D66E6234506B9936BC5 /* MSIDThrottlingRefreshing.h */; }; - 2D9C4F18A6B941579F0D8C36 /* MSIDThrottlingMetaDataReading.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A3F1B92D04C45E8A9C16384 /* MSIDThrottlingMetaDataReading.h */; }; D11DF760D8901DDC5186BC7A /* MSIDDIContainerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2E356C396133919B12FB4F01 /* MSIDDIContainerTests.m */; }; D62600131FBD380500EE4487 /* NSString+MSIDExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = D626000F1FBD380500EE4487 /* NSString+MSIDExtensions.m */; }; D62600141FBD380500EE4487 /* NSString+MSIDExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = D626000F1FBD380500EE4487 /* NSString+MSIDExtensions.m */; }; @@ -2132,6 +2135,7 @@ F7AB2212E4C82BF809D126F6 /* MSIDWPJMetadata.m in Sources */ = {isa = PBXBuildFile; fileRef = F7AB272FEBCF984722F26558 /* MSIDWPJMetadata.m */; }; F7AB25B36873F2E237D26F68 /* MSIDWPJMetadata.m in Sources */ = {isa = PBXBuildFile; fileRef = F7AB272FEBCF984722F26558 /* MSIDWPJMetadata.m */; }; F7AB29D8B906BEA5B6EB8F8C /* MSIDWPJMetadata.h in Headers */ = {isa = PBXBuildFile; fileRef = F7AB2563ADFC286EF6D81445 /* MSIDWPJMetadata.h */; }; + VC00000000000000000F2001 /* MSIDOpenIdVcHandling.h in Headers */ = {isa = PBXBuildFile; fileRef = VC00000000000000000F2003 /* MSIDOpenIdVcHandling.h */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -2700,8 +2704,6 @@ 2E356C396133919B12FB4F01 /* MSIDDIContainerTests.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = MSIDDIContainerTests.m; sourceTree = ""; }; 305240E0A18733A71978A8A4 /* MSIDOnboardingBlobFieldKeys.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = MSIDOnboardingBlobFieldKeys.h; sourceTree = ""; }; 30CDFCBD388F4440556637F9 /* MSIDDIContainer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = MSIDDIContainer.h; sourceTree = ""; }; - CACE5D66E6234506B9936BC5 /* MSIDThrottlingRefreshing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDThrottlingRefreshing.h; sourceTree = ""; }; - 7A3F1B92D04C45E8A9C16384 /* MSIDThrottlingMetaDataReading.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDThrottlingMetaDataReading.h; sourceTree = ""; }; 4B6D22252E831AEA00546EC8 /* MSIDFlightManagerQueryKeyDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDFlightManagerQueryKeyDelegate.h; sourceTree = ""; }; 4B6D222A2E8342C200546EC8 /* MSIDFlightManagerQueryKeyType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDFlightManagerQueryKeyType.h; sourceTree = ""; }; 4B6D222B2E8342C200546EC8 /* MSIDFlightManagerQueryKeyType.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDFlightManagerQueryKeyType.m; sourceTree = ""; }; @@ -2794,7 +2796,6 @@ 602CD4E123739B3C00A4D7F3 /* MSIDBrokerOperationGetAccountsRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSIDBrokerOperationGetAccountsRequest.m; sourceTree = ""; }; 6035CD8B207EA67300369E69 /* MSIDTelemetryIntegrationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDTelemetryIntegrationTests.m; sourceTree = ""; }; 6057EE8E20B5FCF8007976EB /* MSIDAADOAuthEmbeddedWebviewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDAADOAuthEmbeddedWebviewController.h; sourceTree = ""; }; - VC00000000000000000F2003 /* MSIDOpenIdVcHandling.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDOpenIdVcHandling.h; sourceTree = ""; }; 6057EE8F20B5FDF8007976EB /* MSIDAADOAuthEmbeddedWebviewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDAADOAuthEmbeddedWebviewController.m; sourceTree = ""; }; 606830032098ACC100CCA6AB /* MSIDNegotiateHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDNegotiateHandler.h; sourceTree = ""; }; 606830042098ACED00CCA6AB /* MSIDNegotiateHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDNegotiateHandler.m; sourceTree = ""; }; @@ -2805,7 +2806,6 @@ 6068303720A33A7400CCA6AB /* MSIDPKeyAuthHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDPKeyAuthHandler.h; sourceTree = ""; }; 6068303820A33A9000CCA6AB /* MSIDPKeyAuthHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDPKeyAuthHandler.m; sourceTree = ""; }; 606B108A20D084B600B34224 /* MSIDAADV1WebviewFactoryTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDAADV1WebviewFactoryTests.m; sourceTree = ""; }; - B427657A2F3EC2A100F79587 /* MSIDWebviewNavigationHandlerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDWebviewNavigationHandlerTests.m; sourceTree = ""; }; 606B108D20D08C9500B34224 /* MSIDOAuth2EmbeddedWebviewControllerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDOAuth2EmbeddedWebviewControllerTests.m; sourceTree = ""; }; 607123BF210FCA7400B91068 /* MSIDAADAuthorityValidationRequest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDAADAuthorityValidationRequest.h; sourceTree = ""; }; 607123C0210FCAAD00B91068 /* MSIDAADAuthorityValidationRequest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDAADAuthorityValidationRequest.m; sourceTree = ""; }; @@ -2935,10 +2935,10 @@ 74F04D47246C8AC000094017 /* MSIDLastRequestTelemetrySerializedItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDLastRequestTelemetrySerializedItem.h; sourceTree = ""; }; 74F04D48246C8AC000094017 /* MSIDLastRequestTelemetrySerializedItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDLastRequestTelemetrySerializedItem.m; sourceTree = ""; }; 74F04D4C246CB5B100094017 /* MSIDCurrentRequestTelemetrySerializedItem+Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MSIDCurrentRequestTelemetrySerializedItem+Internal.h"; sourceTree = ""; }; + 7A3F1B92D04C45E8A9C16384 /* MSIDThrottlingMetaDataReading.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDThrottlingMetaDataReading.h; sourceTree = ""; }; 80878AED247A7BBF000BC522 /* MSIDWorkPlaceJoinUtilBase.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDWorkPlaceJoinUtilBase.h; sourceTree = ""; }; 80878AEE247A84C1000BC522 /* MSIDWorkPlaceJoinUtilBase.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDWorkPlaceJoinUtilBase.m; sourceTree = ""; }; 809B38212480C3C8001DF9D4 /* MSIDWorkPlaceJoinUtilBase+Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MSIDWorkPlaceJoinUtilBase+Internal.h"; sourceTree = ""; }; - B7A1F4D2C8E94F1B9D6E3A21 /* MSIDWorkPlaceJoinUtilProviding.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDWorkPlaceJoinUtilProviding.h; sourceTree = ""; }; 80B6BF3B2480A3E30031BFE8 /* MSIDWorkPlaceJoinUtilTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDWorkPlaceJoinUtilTests.m; sourceTree = ""; }; 886F516829CCA68A00F09471 /* MSIDCIAMAuthority.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDCIAMAuthority.h; sourceTree = ""; }; 886F516A29CCA6B800F09471 /* MSIDCIAMAuthority.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDCIAMAuthority.m; sourceTree = ""; }; @@ -3589,16 +3589,19 @@ B41163B529BAC20000E64619 /* MSIDAADOAuthEmbeddedWebviewControllerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDAADOAuthEmbeddedWebviewControllerTests.m; sourceTree = ""; }; B41163B829BAC9BF00E64619 /* MSIDWKNavigationActionMock.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDWKNavigationActionMock.m; sourceTree = ""; }; B41163BB29BAC9DE00E64619 /* MSIDWKNavigationActionMock.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDWKNavigationActionMock.h; sourceTree = ""; }; + B4134C182FEA3E410037FE68 /* MSIDMobileOnboardingState.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDMobileOnboardingState.h; sourceTree = ""; }; + B4134C192FEA3E410037FE68 /* MSIDMobileOnboardingState.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDMobileOnboardingState.m; sourceTree = ""; }; B41DD0A12FB4187200F81A9A /* MSIDIntuneDeviceIdCache.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDIntuneDeviceIdCache.h; sourceTree = ""; }; B41DD0A32FB4187900F81A9A /* MSIDIntuneDeviceIdCache.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDIntuneDeviceIdCache.m; sourceTree = ""; }; B41DD0A62FB41A0000F81A9A /* MSIDIntuneDeviceIdCacheTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDIntuneDeviceIdCacheTests.m; sourceTree = ""; }; B41F0CD52F871F230029E631 /* MSIDWebMDMEnrollmentCompletionResponse.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDWebMDMEnrollmentCompletionResponse.h; sourceTree = ""; }; B41F0CD72F871F2E0029E631 /* MSIDWebMDMEnrollmentCompletionResponse.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDWebMDMEnrollmentCompletionResponse.m; sourceTree = ""; }; - B427654E2F3EC27600F79587 /* MSIDWebviewNavigationHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDWebviewNavigationHandler.h; sourceTree = ""; }; - B42765502F3EC29F00F79587 /* MSIDWebviewNavigationHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDWebviewNavigationHandler.m; sourceTree = ""; }; B42558B52F57A65A0024523D /* MSIDOnboardingBlobBuilder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDOnboardingBlobBuilder.h; sourceTree = ""; }; B42558B72F57ADFD0024523D /* MSIDOnboardingBlobBuilder.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDOnboardingBlobBuilder.m; sourceTree = ""; }; B42558BA2F57AE340024523D /* MSIDOnboardingBlobBuilderTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDOnboardingBlobBuilderTests.m; sourceTree = ""; }; + B427654E2F3EC27600F79587 /* MSIDWebviewNavigationHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDWebviewNavigationHandler.h; sourceTree = ""; }; + B42765502F3EC29F00F79587 /* MSIDWebviewNavigationHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDWebviewNavigationHandler.m; sourceTree = ""; }; + B427657A2F3EC2A100F79587 /* MSIDWebviewNavigationHandlerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDWebviewNavigationHandlerTests.m; sourceTree = ""; }; B42C16002CE7E53800553316 /* MSIDFamilyRefreshToken.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDFamilyRefreshToken.h; sourceTree = ""; }; B42C16022CE7E54C00553316 /* MSIDFamilyRefreshToken.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDFamilyRefreshToken.m; sourceTree = ""; }; B431B5222AF040450020CD3D /* MSIDBrokerOperationPasskeyAssertionRequestTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDBrokerOperationPasskeyAssertionRequestTests.m; sourceTree = ""; }; @@ -3658,6 +3661,10 @@ B4EC850A2EF65C58005567DA /* MSIDAesGcmDecryptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSIDAesGcmDecryptor.swift; sourceTree = ""; }; B4EC850B2EF65C58005567DA /* MSIDJweResponse+EcdhAesGcm.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MSIDJweResponse+EcdhAesGcm.h"; sourceTree = ""; }; B4EC850C2EF65C58005567DA /* MSIDJweResponse+EcdhAesGcm.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "MSIDJweResponse+EcdhAesGcm.m"; sourceTree = ""; }; + B4FBF0312EF33A7600EDE1E9 /* MSIDOnboardingStatus.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDOnboardingStatus.h; sourceTree = ""; }; + B4FBF0322EF33A7600EDE1E9 /* MSIDOnboardingStatus.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDOnboardingStatus.m; sourceTree = ""; }; + B4FBF0362EF33AAA00EDE1E9 /* MSIDOnboardingStatusTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDOnboardingStatusTests.m; sourceTree = ""; }; + B4FBF03F2EF44BBB00EDE1E9 /* MSIDOnboardingStatusCacheTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDOnboardingStatusCacheTests.m; sourceTree = ""; }; B4FDC6F62F3EFFB600091B6C /* MSIDWebviewNavigationDecisionResolver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDWebviewNavigationDecisionResolver.h; sourceTree = ""; }; B4FDC6F82F3EFFD800091B6C /* MSIDWebviewNavigationDecisionResolver.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDWebviewNavigationDecisionResolver.m; sourceTree = ""; }; B500F7302F1144A900E64911 /* MSIDBrokerOperationGetDefaultAccountRequestTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDBrokerOperationGetDefaultAccountRequestTests.m; sourceTree = ""; }; @@ -3671,10 +3678,7 @@ B5AAE1182F03D7AA0026B21B /* MSIDBrokerOperationGetDefaultAccountResponse.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDBrokerOperationGetDefaultAccountResponse.m; sourceTree = ""; }; B5AAE11C2F03DADD0026B21B /* MSIDBrokerOperationGetDefaultAccountRequest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDBrokerOperationGetDefaultAccountRequest.h; sourceTree = ""; }; B5AAE11D2F03DADD0026B21B /* MSIDBrokerOperationGetDefaultAccountRequest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDBrokerOperationGetDefaultAccountRequest.m; sourceTree = ""; }; - B4FBF0312EF33A7600EDE1E9 /* MSIDOnboardingStatus.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDOnboardingStatus.h; sourceTree = ""; }; - B4FBF0322EF33A7600EDE1E9 /* MSIDOnboardingStatus.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDOnboardingStatus.m; sourceTree = ""; }; - B4FBF0362EF33AAA00EDE1E9 /* MSIDOnboardingStatusTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDOnboardingStatusTests.m; sourceTree = ""; }; - B4FBF03F2EF44BBB00EDE1E9 /* MSIDOnboardingStatusCacheTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDOnboardingStatusCacheTests.m; sourceTree = ""; }; + B7A1F4D2C8E94F1B9D6E3A21 /* MSIDWorkPlaceJoinUtilProviding.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDWorkPlaceJoinUtilProviding.h; sourceTree = ""; }; B86FA7C62383748000E5195A /* MSIDMacACLKeychainAccessorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSIDMacACLKeychainAccessorTests.m; sourceTree = ""; }; B86FA7C72383748000E5195A /* MSIDMacTokenCacheTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSIDMacTokenCacheTests.m; sourceTree = ""; }; B86FA7C82383748000E5195A /* MSIDMacKeychainTokenCacheTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSIDMacKeychainTokenCacheTests.m; sourceTree = ""; }; @@ -3684,6 +3688,7 @@ B8DBEF632395CA4800A16651 /* MSIDKeychainTokenCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSIDKeychainTokenCache.h; sourceTree = ""; }; B8F16E8F245B548D0047457F /* MSIDWebViewPlatformParams.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDWebViewPlatformParams.h; sourceTree = ""; }; B8F16E91245B572C0047457F /* MSIDWebViewPlatformParams.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDWebViewPlatformParams.m; sourceTree = ""; }; + CACE5D66E6234506B9936BC5 /* MSIDThrottlingRefreshing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDThrottlingRefreshing.h; sourceTree = ""; }; D626000F1FBD380500EE4487 /* NSString+MSIDExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+MSIDExtensions.m"; sourceTree = ""; }; D62600101FBD380500EE4487 /* NSDictionary+MSIDExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+MSIDExtensions.h"; sourceTree = ""; }; D62600111FBD380500EE4487 /* NSDictionary+MSIDExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+MSIDExtensions.m"; sourceTree = ""; }; @@ -3746,6 +3751,7 @@ E7B67293257ED6E30053773F /* MSIDLRUCache.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDLRUCache.h; sourceTree = ""; }; F7AB2563ADFC286EF6D81445 /* MSIDWPJMetadata.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSIDWPJMetadata.h; sourceTree = ""; }; F7AB272FEBCF984722F26558 /* MSIDWPJMetadata.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSIDWPJMetadata.m; sourceTree = ""; }; + VC00000000000000000F2003 /* MSIDOpenIdVcHandling.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDOpenIdVcHandling.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -5518,6 +5524,8 @@ B2AF1D3D218BD02F0080C1A0 /* parameters */ = { isa = PBXGroup; children = ( + B4134C182FEA3E410037FE68 /* MSIDMobileOnboardingState.h */, + B4134C192FEA3E410037FE68 /* MSIDMobileOnboardingState.m */, 72978AF12E4C2C3300DEA46D /* MSIDBoundRefreshTokenRedemptionParameters.m */, 72978AEF2E4C2A1E00DEA46D /* MSIDBoundRefreshTokenRedemptionParameters.h */, B2968C8322F3C3E8005AFC33 /* MSIDBrokerInvocationOptions.h */, @@ -6397,6 +6405,7 @@ files = ( 73D7C94FBD27C5CA3480B739 /* MSIDDIContainer.h in Headers */, AE6838E26F5648EFAB74A481 /* MSIDThrottlingRefreshing.h in Headers */, + B4134C1C2FEA3E410037FE68 /* MSIDMobileOnboardingState.h in Headers */, B5E8A92C146D4F38B5C92E17 /* MSIDThrottlingMetaDataReading.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -6808,6 +6817,7 @@ B297E1EB20A1388E00F370EC /* MSIDDefaultAccountCacheQuery.h in Headers */, B2C0748B246B71300008D701 /* MSIDAssymetricKeyPairWithCert.h in Headers */, B286B9562385F01A007833AD /* MSIDOIDCSignoutRequest.h in Headers */, + B4134C1A2FEA3E410037FE68 /* MSIDMobileOnboardingState.h in Headers */, B2C708B0219A614C00D917B8 /* MSIDDefaultBrokerTokenRequest.h in Headers */, B286B9D52389DF2E007833AD /* MSIDRegistrationInformation.h in Headers */, A0C7DD7C25D1E98D00F5B5B6 /* NSError+MSIDThrottlingExtension.h in Headers */, @@ -7775,6 +7785,7 @@ 23C10AA02B40D9350063D97C /* MSIDBrowserNativeMessageSignOutResponse.m in Sources */, 7209A3D62EB581BE0050CB13 /* MSIDJweResponse.m in Sources */, B251CC3A2041058D005E0179 /* MSIDLegacySingleResourceToken.m in Sources */, + B4134C1B2FEA3E410037FE68 /* MSIDMobileOnboardingState.m in Sources */, 233E96F822652D3A007FCE2A /* MSIDAggregatedDispatcher.m in Sources */, B2A3C2812145D04E0082525C /* MSIDAuthorityCacheRecord.m in Sources */, B2C708B2219A620700D917B8 /* MSIDBrokerKeyProvider.m in Sources */, @@ -8041,7 +8052,6 @@ B286B98D2389DC34007833AD /* MSIDBrokerOperationTokenResponse.m in Sources */, 2394F1FA2D4890BD00E44F6E /* MSIDWebOAuth2AuthCodeOperation.m in Sources */, B4CC96612F982FEA007F281A /* MSIDSessionCachePersistence.m in Sources */, - 724C9E322E6FAB170039BAA0 /* MSIDConcatKdfProvider.swift in Sources */, B286B9832389DC15007833AD /* MSIDBrokerOperationInteractiveTokenRequest.m in Sources */, 1EE42FF1248825CE00899491 /* MSIDAccessTokenWithAuthScheme.m in Sources */, 606830062098ACED00CCA6AB /* MSIDNegotiateHandler.m in Sources */, @@ -8929,6 +8939,7 @@ 60F7BE8B21DA4E2900F1BBA1 /* MSIDPrimaryRefreshToken.m in Sources */, B214C39F1FE854FE0070C4F2 /* MSIDLegacyTokenCacheAccessor.m in Sources */, D62600161FBD380500EE4487 /* NSDictionary+MSIDExtensions.m in Sources */, + B4134C1D2FEA3E410037FE68 /* MSIDMobileOnboardingState.m in Sources */, B2B1D579204369D600DD81F0 /* MSIDAccountType.m in Sources */, B2C707EE21924C8300D917B8 /* MSIDTokenResponseValidator.m in Sources */, 609E74C7228DCEA1005E3FED /* MSIDAccountMetadata.m in Sources */, diff --git a/IdentityCore/src/broker_operation/request/interactive_token_request/MSIDBrokerOperationInteractiveTokenRequest.h b/IdentityCore/src/broker_operation/request/interactive_token_request/MSIDBrokerOperationInteractiveTokenRequest.h index 3a8ad7c178..2034e643ee 100644 --- a/IdentityCore/src/broker_operation/request/interactive_token_request/MSIDBrokerOperationInteractiveTokenRequest.h +++ b/IdentityCore/src/broker_operation/request/interactive_token_request/MSIDBrokerOperationInteractiveTokenRequest.h @@ -43,9 +43,7 @@ NS_ASSUME_NONNULL_BEGIN /// blob via `MSIDBrokerOperationTokenResponse.onboardingBlob`. @property (nonatomic, copy, nullable) NSString *onboardingBlob; -/// Mirrors `MSIDInteractiveRequestParameters.isNewMobileOnboardingFlow`. -/// Round-tripped across the SSO extension IPC boundary so the broker can -/// branch on whether the request originated from the new mobile onboarding flow. +// Indicates the new mobile onboarding flow. Serialized across SSO extension IPC. @property (nonatomic) BOOL isNewMobileOnboardingFlow; + (instancetype)tokenRequestWithParameters:(MSIDInteractiveTokenRequestParameters *)parameters diff --git a/IdentityCore/src/parameters/MSIDInteractiveRequestParameters.h b/IdentityCore/src/parameters/MSIDInteractiveRequestParameters.h index e621c843a1..7bddfd93fe 100644 --- a/IdentityCore/src/parameters/MSIDInteractiveRequestParameters.h +++ b/IdentityCore/src/parameters/MSIDInteractiveRequestParameters.h @@ -27,10 +27,13 @@ #import "MSIDConstants.h" @class WKWebView; +@class MSIDMobileOnboardingState; #if TARGET_OS_IPHONE @class UIViewController; #endif +NS_ASSUME_NONNULL_BEGIN + @interface MSIDInteractiveRequestParameters : MSIDRequestParameters @property (nonatomic) MSIDWebviewType webviewType; @@ -47,10 +50,11 @@ @property (nonatomic) BOOL prefersEphemeralWebBrowserSession; @property (nonatomic) NSString *telemetryWebviewType; -/* Marks the current request as part of the new mobile onboarding flow. - Set to YES when the server issues `msauth://enroll` during the embedded - webview leg, so the bit survives the hop into the broker SSO extension - where the broker can branch on it during device-registration bootstrap. */ +// Shared mutable onboarding state, passed by reference across recreated params. +@property (nonatomic, nullable) MSIDMobileOnboardingState *mobileOnboardingState; + @property (nonatomic) BOOL isNewMobileOnboardingFlow; @end + +NS_ASSUME_NONNULL_END diff --git a/IdentityCore/src/parameters/MSIDInteractiveRequestParameters.m b/IdentityCore/src/parameters/MSIDInteractiveRequestParameters.m index edd171f9f7..a6a48eca34 100644 --- a/IdentityCore/src/parameters/MSIDInteractiveRequestParameters.m +++ b/IdentityCore/src/parameters/MSIDInteractiveRequestParameters.m @@ -24,7 +24,24 @@ #import "MSIDInteractiveRequestParameters.h" #import "NSOrderedSet+MSIDExtensions.h" #import "MSIDClaimsRequest.h" +#import "MSIDMobileOnboardingState.h" @implementation MSIDInteractiveRequestParameters +@dynamic isNewMobileOnboardingFlow; + +- (BOOL)isNewMobileOnboardingFlow +{ + return self.mobileOnboardingState.isNewMobileOnboardingFlow; +} + +- (void)setIsNewMobileOnboardingFlow:(BOOL)isNewMobileOnboardingFlow +{ + if (!self.mobileOnboardingState) + { + self.mobileOnboardingState = [MSIDMobileOnboardingState new]; + } + self.mobileOnboardingState.isNewMobileOnboardingFlow = isNewMobileOnboardingFlow; +} + @end diff --git a/IdentityCore/src/parameters/MSIDMobileOnboardingState.h b/IdentityCore/src/parameters/MSIDMobileOnboardingState.h new file mode 100644 index 0000000000..c59e8e5309 --- /dev/null +++ b/IdentityCore/src/parameters/MSIDMobileOnboardingState.h @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +NS_ASSUME_NONNULL_BEGIN + +// Shared mutable state for mobile onboarding flow. +@interface MSIDMobileOnboardingState : NSObject + +@property (nonatomic) BOOL isNewMobileOnboardingFlow; + +@end + +NS_ASSUME_NONNULL_END diff --git a/IdentityCore/src/parameters/MSIDMobileOnboardingState.m b/IdentityCore/src/parameters/MSIDMobileOnboardingState.m new file mode 100644 index 0000000000..724721bf91 --- /dev/null +++ b/IdentityCore/src/parameters/MSIDMobileOnboardingState.m @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "MSIDMobileOnboardingState.h" + +@implementation MSIDMobileOnboardingState + +@end diff --git a/IdentityCore/tests/integration/ios/MSIDBrokerTokenRequestTests.m b/IdentityCore/tests/integration/ios/MSIDBrokerTokenRequestTests.m index 7690ad62ce..35d5aef5d2 100644 --- a/IdentityCore/tests/integration/ios/MSIDBrokerTokenRequestTests.m +++ b/IdentityCore/tests/integration/ios/MSIDBrokerTokenRequestTests.m @@ -39,6 +39,7 @@ #import "MSIDBartFeatureUtil.h" #import "MSIDDefaultBrokerTokenRequest.h" #import "MSIDBrokerConstants.h" +#import "MSIDMobileOnboardingState.h" @interface MSIDBrokerTokenRequestTests : XCTestCase @end @@ -729,6 +730,39 @@ - (void)testInitBrokerRequest_whenNewMobileOnboardingFlowNotSet_shouldNotInclude XCTAssertNil(queryParams[MSID_BROKER_NEW_MOBILE_ONBOARDING_FLOW_KEY]); } +- (void)testMobileOnboardingState_whenSharedAcrossParams_shouldReflectMutations +{ + MSIDMobileOnboardingState *state = [MSIDMobileOnboardingState new]; + state.isNewMobileOnboardingFlow = NO; + + MSIDInteractiveTokenRequestParameters *params1 = [self defaultTestParameters]; + params1.mobileOnboardingState = state; + + MSIDInteractiveTokenRequestParameters *params2 = [self defaultTestParameters]; + params2.mobileOnboardingState = state; + + XCTAssertFalse(params1.isNewMobileOnboardingFlow); + XCTAssertFalse(params2.isNewMobileOnboardingFlow); + + // Simulate webview setting the flag on params1 + params1.isNewMobileOnboardingFlow = YES; + + // params2 should see the mutation through the shared state + XCTAssertTrue(params2.isNewMobileOnboardingFlow); + XCTAssertTrue(state.isNewMobileOnboardingFlow); +} + +- (void)testMobileOnboardingState_whenSetWithoutState_shouldCreateStateLazily +{ + MSIDInteractiveTokenRequestParameters *params = [self defaultTestParameters]; + XCTAssertNil(params.mobileOnboardingState); + + params.isNewMobileOnboardingFlow = YES; + + XCTAssertNotNil(params.mobileOnboardingState); + XCTAssertTrue(params.isNewMobileOnboardingFlow); +} + - (void)setBoundAppRefreshTokenFlight { [[MSIDBartFeatureUtil sharedInstance] setBartSupportInAppCache:YES]; From 7d6e4384ade99749d24414b6ae5cc95870d288fd Mon Sep 17 00:00:00 2001 From: Ameya Patil Date: Tue, 23 Jun 2026 18:33:49 -0700 Subject: [PATCH 08/38] [minor][tests]: Add a automation health tracker (#1865) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## PR Checklist (must be completed before review) - [ ] All tests pass locally - [ ] PR size is <= 500 LOC per PR Size Check policy - [ ] PR is independently mergeable (no hidden dependencies) - [ ] Appropriate reviewers are assigned - [ ] PR reviewed by code owner (required if Copilot-generated) - [ ] SME or Senior IC assigned where required ## PR Title Format **Required Format:** `[Keyword1] [Keyword2]: Description` - **Keyword1:** `major`, `minor`, or `patch` (case-insensitive) - **Keyword2:** `feature`, `bugfix`, `engg`, or `tests` (case-insensitive) **Examples:** - `[MAJOR] [Feature]: new API` - `[minor] [bugfix]: fix crash` - `[PATCH][tests]:add coverage` ## Proposed changes Describe what this PR is trying to do. ## Type of change - [ ] Feature work - [ ] Bug fix - [ ] Documentation - [ ] Engineering change - [x] Test - [ ] Logging/Telemetry ## Risk - [ ] High – Errors could cause MAJOR regression of many scenarios. (Example: new large features or high level infrastructure changes) - [ ] Medium – Errors could cause regression of 1 or more scenarios. (Example: somewhat complex bug fixes, small new features) - [x] Small – No issues are expected. (Example: Very small bug fixes, string changes, or configuration settings changes) ## Additional information --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --- azure_pipelines/pr-validation.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/azure_pipelines/pr-validation.yml b/azure_pipelines/pr-validation.yml index 8284783a66..a6dfcd3547 100644 --- a/azure_pipelines/pr-validation.yml +++ b/azure_pipelines/pr-validation.yml @@ -10,8 +10,22 @@ pr: # Not triggering for CI since it is triggered for PRs trigger: none +resources: + repositories: + - repository: automationHealthTracker + type: git + name: IDDP/MSAL-ObjC-Pipelines + ref: main + # Define parallel jobs that run build script for specified targets jobs: +- job: AutomationHealth + pool: { vmImage: 'ubuntu-latest' } + displayName: Automation pipeline health check + steps: + - template: Pipeline YAMLs/automation-health-tracker.yml@automationHealthTracker + parameters: + automationPipelineIds: [1956, 1284] - job: 'Validate_Pull_Request' strategy: maxParallel: 2 From 4d6abd177b108b33bf099296804bc9f9d4bf01ce Mon Sep 17 00:00:00 2001 From: agubuzomaximus Date: Thu, 25 Jun 2026 16:23:39 -0700 Subject: [PATCH 09/38] [patch] [bugfix]: Fix MSIDFlightManager provider assignment race (#1876) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## PR Checklist (must be completed before review) - [X] All tests pass locally - [X] PR size is <= 500 LOC per PR Size Check policy - [ ] PR is independently mergeable (no hidden dependencies) - [ ] Appropriate reviewers are assigned - [ ] PR reviewed by code owner (required if Copilot-generated) - [ ] SME or Senior IC assigned where required ## Proposed changes The proposed change updates  MSIDFlightManager  so assigning  flightProvider  completes immediately before the setter returns. Before the change,  setFlightProvider:  used  dispatch_barrier_async . That means the assignment was placed onto the synchronization queue, but the caller did not wait for it to run. Code could set  flightProvider  and then immediately ask for a flight value, while the provider assignment was still waiting in the queue. In that case,  MSIDFlightManager  could still see  flightProvider  as  nil  and return the default value, usually  NO . After the change,  setFlightProvider:  uses  dispatch_barrier_sync . The assignment still goes through the same barrier queue, so the thread-safety model stays the same. The difference is that the caller now waits until the assignment is finished. Once  flightProvider = provider  returns, the provider is definitely stored and available to later reads. This fixes the CI flake because the failing tests inject a mock flight provider and immediately call code that reads from it. The old async setter allowed the read to happen too early. ## Type of change - [ ] Feature work - [X] Bug fix - [ ] Documentation - [ ] Engineering change - [ ] Test - [ ] Logging/Telemetry ## Risk - [ ] High – Errors could cause MAJOR regression of many scenarios. (Example: new large features or high level infrastructure changes) - [ ] Medium – Errors could cause regression of 1 or more scenarios. (Example: somewhat complex bug fixes, small new features) - [X] Small – No issues are expected. (Example: Very small bug fixes, string changes, or configuration settings changes) ## Additional information Co-authored-by: Maximus Agubuzo Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- IdentityCore/src/MSIDFlightManager.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IdentityCore/src/MSIDFlightManager.m b/IdentityCore/src/MSIDFlightManager.m index 425a2325dc..bd67f23f59 100644 --- a/IdentityCore/src/MSIDFlightManager.m +++ b/IdentityCore/src/MSIDFlightManager.m @@ -113,7 +113,7 @@ - (dispatch_queue_t)initializeDispatchQueue - (void)setFlightProvider:(id)flightProvider { - dispatch_barrier_async(self.synchronizationQueue, ^{ + dispatch_barrier_sync(self.synchronizationQueue, ^{ self->_flightProvider = flightProvider; }); } From 04f62805cd384e1999a2b70b3991fe3b21af7338 Mon Sep 17 00:00:00 2001 From: Swasti Gupta Date: Thu, 25 Jun 2026 20:01:20 -0700 Subject: [PATCH 10/38] Mob Onboarding Notifcation Auth CallbackUX (#1874) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## PR Checklist (must be completed before review) - [ ] All tests pass locally - [ ] PR size is <= 500 LOC per PR Size Check policy - [ ] PR is independently mergeable (no hidden dependencies) - [ ] Appropriate reviewers are assigned - [ ] PR reviewed by code owner (required if Copilot-generated) - [ ] SME or Senior IC assigned where required ## PR Title Format **Required Format:** `[Keyword1] [Keyword2]: Description` - **Keyword1:** `major`, `minor`, or `patch` (case-insensitive) - **Keyword2:** `feature`, `bugfix`, `engg`, or `tests` (case-insensitive) **Examples:** - `[MAJOR] [Feature]: new API` - `[minor] [bugfix]: fix crash` - `[PATCH][tests]:add coverage` ## Proposed changes Describe what this PR is trying to do. ## Type of change - [ ] Feature work - [ ] Bug fix - [ ] Documentation - [ ] Engineering change - [ ] Test - [ ] Logging/Telemetry ## Risk - [ ] High – Errors could cause MAJOR regression of many scenarios. (Example: new large features or high level infrastructure changes) - [ ] Medium – Errors could cause regression of 1 or more scenarios. (Example: somewhat complex bug fixes, small new features) - [ ] Small – No issues are expected. (Example: Very small bug fixes, string changes, or configuration settings changes) ## Additional information --------- Co-authored-by: Swasti Gupta --- .../IdentityCore.xcodeproj/project.pbxproj | 28 ++++ IdentityCore/src/MSIDConstants.h | 7 + IdentityCore/src/MSIDConstants.m | 4 + IdentityCore/src/MSIDUXCallbackProtocol.h | 44 ++++++ IdentityCore/src/MSIDUXCallbackProvider.h | 39 +++++ IdentityCore/src/MSIDUXCallbackProvider.m | 44 ++++++ .../MSIDWebviewNavigationDecisionResolver.m | 23 +++ ...IDWebviewNavigationDecisionResolverTests.m | 137 ++++++++++++++++++ .../tests/mocks/MSIDMockUXCallbackProvider.h | 38 +++++ .../tests/mocks/MSIDMockUXCallbackProvider.m | 40 +++++ changelog.txt | 1 + 11 files changed, 405 insertions(+) create mode 100644 IdentityCore/src/MSIDUXCallbackProtocol.h create mode 100644 IdentityCore/src/MSIDUXCallbackProvider.h create mode 100644 IdentityCore/src/MSIDUXCallbackProvider.m create mode 100644 IdentityCore/tests/mocks/MSIDMockUXCallbackProvider.h create mode 100644 IdentityCore/tests/mocks/MSIDMockUXCallbackProvider.m diff --git a/IdentityCore/IdentityCore.xcodeproj/project.pbxproj b/IdentityCore/IdentityCore.xcodeproj/project.pbxproj index ef5acda78c..cc293b5071 100644 --- a/IdentityCore/IdentityCore.xcodeproj/project.pbxproj +++ b/IdentityCore/IdentityCore.xcodeproj/project.pbxproj @@ -1937,6 +1937,9 @@ B4134C1B2FEA3E410037FE68 /* MSIDMobileOnboardingState.m in Sources */ = {isa = PBXBuildFile; fileRef = B4134C192FEA3E410037FE68 /* MSIDMobileOnboardingState.m */; }; B4134C1C2FEA3E410037FE68 /* MSIDMobileOnboardingState.h in Headers */ = {isa = PBXBuildFile; fileRef = B4134C182FEA3E410037FE68 /* MSIDMobileOnboardingState.h */; }; B4134C1D2FEA3E410037FE68 /* MSIDMobileOnboardingState.m in Sources */ = {isa = PBXBuildFile; fileRef = B4134C192FEA3E410037FE68 /* MSIDMobileOnboardingState.m */; }; + B4134C442FEC495C0037FE68 /* MSIDMockUXCallbackProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = B4134C422FEC495C0037FE68 /* MSIDMockUXCallbackProvider.h */; }; + B4134C452FEC495C0037FE68 /* MSIDMockUXCallbackProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = B4134C432FEC495C0037FE68 /* MSIDMockUXCallbackProvider.m */; }; + B4134C462FEC495C0037FE68 /* MSIDMockUXCallbackProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = B4134C432FEC495C0037FE68 /* MSIDMockUXCallbackProvider.m */; }; B41DD0A22FB4187500F81A9A /* MSIDIntuneDeviceIdCache.h in Headers */ = {isa = PBXBuildFile; fileRef = B41DD0A12FB4187200F81A9A /* MSIDIntuneDeviceIdCache.h */; }; B41DD0A42FB4187B00F81A9A /* MSIDIntuneDeviceIdCache.m in Sources */ = {isa = PBXBuildFile; fileRef = B41DD0A32FB4187900F81A9A /* MSIDIntuneDeviceIdCache.m */; }; B41DD0A52FB4187B00F81A9A /* MSIDIntuneDeviceIdCache.m in Sources */ = {isa = PBXBuildFile; fileRef = B41DD0A32FB4187900F81A9A /* MSIDIntuneDeviceIdCache.m */; }; @@ -2045,6 +2048,12 @@ B4EC850F2EF65C58005567DA /* MSIDJweResponse+EcdhAesGcm.m in Sources */ = {isa = PBXBuildFile; fileRef = B4EC850C2EF65C58005567DA /* MSIDJweResponse+EcdhAesGcm.m */; }; B4EC85102EF65C58005567DA /* MSIDJweResponse+EcdhAesGcm.h in Headers */ = {isa = PBXBuildFile; fileRef = B4EC850B2EF65C58005567DA /* MSIDJweResponse+EcdhAesGcm.h */; }; B4EC85122EF65C58005567DA /* MSIDJweResponse+EcdhAesGcm.m in Sources */ = {isa = PBXBuildFile; fileRef = B4EC850C2EF65C58005567DA /* MSIDJweResponse+EcdhAesGcm.m */; }; + B4F104BF2FE3A16500EBEB5F /* MSIDUXCallbackProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = B4F104BE2FE3A16500EBEB5F /* MSIDUXCallbackProvider.m */; }; + B4F104C02FE3A16500EBEB5F /* MSIDUXCallbackProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = B4F104BC2FE3A16500EBEB5F /* MSIDUXCallbackProtocol.h */; }; + B4F104C12FE3A16500EBEB5F /* MSIDUXCallbackProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = B4F104BD2FE3A16500EBEB5F /* MSIDUXCallbackProvider.h */; }; + B4F104C22FE3A16500EBEB5F /* MSIDUXCallbackProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = B4F104BE2FE3A16500EBEB5F /* MSIDUXCallbackProvider.m */; }; + B4F104C32FE3A16500EBEB5F /* MSIDUXCallbackProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = B4F104BC2FE3A16500EBEB5F /* MSIDUXCallbackProtocol.h */; }; + B4F104C42FE3A16500EBEB5F /* MSIDUXCallbackProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = B4F104BD2FE3A16500EBEB5F /* MSIDUXCallbackProvider.h */; }; B4FBF0332EF33A7600EDE1E9 /* MSIDOnboardingStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = B4FBF0322EF33A7600EDE1E9 /* MSIDOnboardingStatus.m */; }; B4FBF0342EF33A7600EDE1E9 /* MSIDOnboardingStatus.h in Headers */ = {isa = PBXBuildFile; fileRef = B4FBF0312EF33A7600EDE1E9 /* MSIDOnboardingStatus.h */; }; B4FBF0352EF33A7600EDE1E9 /* MSIDOnboardingStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = B4FBF0322EF33A7600EDE1E9 /* MSIDOnboardingStatus.m */; }; @@ -3591,6 +3600,8 @@ B41163BB29BAC9DE00E64619 /* MSIDWKNavigationActionMock.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDWKNavigationActionMock.h; sourceTree = ""; }; B4134C182FEA3E410037FE68 /* MSIDMobileOnboardingState.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDMobileOnboardingState.h; sourceTree = ""; }; B4134C192FEA3E410037FE68 /* MSIDMobileOnboardingState.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDMobileOnboardingState.m; sourceTree = ""; }; + B4134C422FEC495C0037FE68 /* MSIDMockUXCallbackProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDMockUXCallbackProvider.h; sourceTree = ""; }; + B4134C432FEC495C0037FE68 /* MSIDMockUXCallbackProvider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDMockUXCallbackProvider.m; sourceTree = ""; }; B41DD0A12FB4187200F81A9A /* MSIDIntuneDeviceIdCache.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDIntuneDeviceIdCache.h; sourceTree = ""; }; B41DD0A32FB4187900F81A9A /* MSIDIntuneDeviceIdCache.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDIntuneDeviceIdCache.m; sourceTree = ""; }; B41DD0A62FB41A0000F81A9A /* MSIDIntuneDeviceIdCacheTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDIntuneDeviceIdCacheTests.m; sourceTree = ""; }; @@ -3661,6 +3672,9 @@ B4EC850A2EF65C58005567DA /* MSIDAesGcmDecryptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSIDAesGcmDecryptor.swift; sourceTree = ""; }; B4EC850B2EF65C58005567DA /* MSIDJweResponse+EcdhAesGcm.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MSIDJweResponse+EcdhAesGcm.h"; sourceTree = ""; }; B4EC850C2EF65C58005567DA /* MSIDJweResponse+EcdhAesGcm.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "MSIDJweResponse+EcdhAesGcm.m"; sourceTree = ""; }; + B4F104BC2FE3A16500EBEB5F /* MSIDUXCallbackProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDUXCallbackProtocol.h; sourceTree = ""; }; + B4F104BD2FE3A16500EBEB5F /* MSIDUXCallbackProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDUXCallbackProvider.h; sourceTree = ""; }; + B4F104BE2FE3A16500EBEB5F /* MSIDUXCallbackProvider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDUXCallbackProvider.m; sourceTree = ""; }; B4FBF0312EF33A7600EDE1E9 /* MSIDOnboardingStatus.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDOnboardingStatus.h; sourceTree = ""; }; B4FBF0322EF33A7600EDE1E9 /* MSIDOnboardingStatus.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDOnboardingStatus.m; sourceTree = ""; }; B4FBF0362EF33AAA00EDE1E9 /* MSIDOnboardingStatusTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDOnboardingStatusTests.m; sourceTree = ""; }; @@ -4099,6 +4113,8 @@ 23642AB32187D88C00F97009 /* mocks */ = { isa = PBXGroup; children = ( + B4134C422FEC495C0037FE68 /* MSIDMockUXCallbackProvider.h */, + B4134C432FEC495C0037FE68 /* MSIDMockUXCallbackProvider.m */, 722AC5182F0EF275005BE6A5 /* MSIDTestBoundAppRefreshTokenRequest.m */, 722AC5162F0EF1AC005BE6A5 /* MSIDTestBoundAppRefreshTokenRequest.h */, 729357EE2DDBCBAB0001D03C /* MSIDNonceTokenRequestMock.m */, @@ -6064,6 +6080,9 @@ D6DA89721FBA6A4E004C56C7 /* src */ = { isa = PBXGroup; children = ( + B4F104BC2FE3A16500EBEB5F /* MSIDUXCallbackProtocol.h */, + B4F104BD2FE3A16500EBEB5F /* MSIDUXCallbackProvider.h */, + B4F104BE2FE3A16500EBEB5F /* MSIDUXCallbackProvider.m */, 7252BB772F2AE53700B2287F /* MSIDSwiftBridgingHeader.h */, 7209A3D42EB581BB0050CB13 /* MSIDJweResponse.m */, 7209A3D22EB581A90050CB13 /* MSIDJweResponse.h */, @@ -6407,6 +6426,8 @@ AE6838E26F5648EFAB74A481 /* MSIDThrottlingRefreshing.h in Headers */, B4134C1C2FEA3E410037FE68 /* MSIDMobileOnboardingState.h in Headers */, B5E8A92C146D4F38B5C92E17 /* MSIDThrottlingMetaDataReading.h in Headers */, + B4F104C02FE3A16500EBEB5F /* MSIDUXCallbackProtocol.h in Headers */, + B4F104C12FE3A16500EBEB5F /* MSIDUXCallbackProvider.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -6837,6 +6858,8 @@ B5AAE11B2F03D7AA0026B21B /* MSIDBrokerOperationGetDefaultAccountResponse.h in Headers */, B26CEAE723653C62009E6E54 /* MSIDASWebAuthenticationSessionHandler.h in Headers */, 2338ECCE208A675D00809B9E /* MSIDAADRequestErrorHandler.h in Headers */, + B4F104C32FE3A16500EBEB5F /* MSIDUXCallbackProtocol.h in Headers */, + B4F104C42FE3A16500EBEB5F /* MSIDUXCallbackProvider.h in Headers */, B26A0B8C2071B763006BD95A /* MSIDAADV1Oauth2Factory.h in Headers */, B2C708B4219A620E00D917B8 /* MSIDBrokerCryptoProvider.h in Headers */, B2CDB5791FE33A46003A4B5C /* MSIDAccount.h in Headers */, @@ -6979,6 +7002,7 @@ B217861823A57ED800839CE8 /* MSIDAuthorizationControllerMock.h in Headers */, B2E4A07B24DDE5D7007CE642 /* NSUUID+MSIDTestUtil.h in Headers */, B217862923A5839300839CE8 /* MSIDSSOExtensionSignoutRequestMock.h in Headers */, + B4134C442FEC495C0037FE68 /* MSIDMockUXCallbackProvider.h in Headers */, 2A0278A32D6E3787005655B4 /* MSIDLastRequestTelemetry+Tests.h in Headers */, 969CCB5622A9EB0300A55515 /* MSIDTestCacheDataSource.h in Headers */, B28AC66421A0BB9D00A1FC4A /* MSIDTestBrokerResponseHelper.h in Headers */, @@ -7964,6 +7988,7 @@ D6D9A4521FBD3FB800EFA430 /* NSURL+MSIDExtensions.m in Sources */, B23ECEF11FF2F6270015FC1D /* MSIDAADV2IdTokenClaims.m in Sources */, 239EED6A242D8FAD00162F0F /* MSIDAADTokenRequestServerTelemetry.m in Sources */, + B4F104C22FE3A16500EBEB5F /* MSIDUXCallbackProvider.m in Sources */, B2C0748D246B71300008D701 /* MSIDAssymetricKeyPairWithCert.m in Sources */, B4B591B42F3AC90600CBA6A9 /* MSIDOnboardingStatusCache.m in Sources */, 9641B5251FCF3EEF00AFA0EC /* MSIDMacTokenCache.m in Sources */, @@ -8487,6 +8512,7 @@ D626FFF71FBD200A00EE4487 /* MSIDTestURLSessionDataTask.m in Sources */, 6078EB50226DA97100235498 /* MSIDTestCacheUtil.m in Sources */, B2E2A94D239320B100BA2EA3 /* MSIDTestParametersProvider.m in Sources */, + B4134C462FEC495C0037FE68 /* MSIDMockUXCallbackProvider.m in Sources */, 961ACE0522A1FA8200B9266C /* MSIDApplicationTestUtil.m in Sources */, D626FFF41FBD200A00EE4487 /* MSIDTestURLSession.m in Sources */, D6D9A44D1FBD3EEA00EFA430 /* NSDictionary+MSIDTestUtil.m in Sources */, @@ -8522,6 +8548,7 @@ B216826223AB09C300F4897A /* MSIDSSOExtensionGetAccountsRequestMock.m in Sources */, B2E4A07424DDE576007CE642 /* MSIDTestTelemetryEventsObserver.m in Sources */, 5898429F252544900075DFED /* MSIDAccountMetadataCacheMockGetAuthorityParameters.m in Sources */, + B4134C452FEC495C0037FE68 /* MSIDMockUXCallbackProvider.m in Sources */, B2E2A94E239320B100BA2EA3 /* MSIDTestParametersProvider.m in Sources */, 969CCB5822A9EB7D00A55515 /* MSIDTestCacheDataSource.m in Sources */, 96290E5721489BB800FDD5C8 /* NSString+MSIDTestUtil.m in Sources */, @@ -8604,6 +8631,7 @@ 72C1EBFE2DEA81A1004C40A4 /* MSIDBoundRefreshTokenCacheItem.m in Sources */, A0C7DDA425D1EA0D00F5B5B6 /* NSError+MSIDThrottlingExtension.m in Sources */, 235480C720DDF81000246F72 /* MSIDAuthorityFactory.m in Sources */, + B4F104BF2FE3A16500EBEB5F /* MSIDUXCallbackProvider.m in Sources */, 239DF9C920E05847002D428B /* MSIDAADRequestConfigurator.m in Sources */, E733EDFD25C0A4B100ACB79A /* MSIDThumbprintCalculator.m in Sources */, 23B39A8220993302000AA905 /* MSIDAadAuthorityResolver.m in Sources */, diff --git a/IdentityCore/src/MSIDConstants.h b/IdentityCore/src/MSIDConstants.h index f4c1605e5b..ad930eaa71 100644 --- a/IdentityCore/src/MSIDConstants.h +++ b/IdentityCore/src/MSIDConstants.h @@ -287,4 +287,11 @@ extern NSString * _Nonnull const MSID_FLIGHT_DISABLE_OPEN_NEW_WINDOW_IN_BROWSER; /// Default: OFF extern NSString * _Nonnull const MSID_FLIGHT_DISABLE_MOBILE_ONBOARDING; +/// Flight key for MDM profile install notification delay (seconds). +/// Owner: swagup +extern NSString * _Nonnull const MSID_FLIGHT_MDM_PROFILE_INSTALLED_NOTIFICATION_DELAY; + +/// Default delay (in seconds) before the MDM profile install notification fires. +extern NSTimeInterval const MSIDMDMProfileInstalledNotificationDefaultDelay; + #define METHODANDLINE [NSString stringWithFormat:@"%s [Line %d]", __PRETTY_FUNCTION__, __LINE__] diff --git a/IdentityCore/src/MSIDConstants.m b/IdentityCore/src/MSIDConstants.m index 755f47a637..d81278ff6b 100644 --- a/IdentityCore/src/MSIDConstants.m +++ b/IdentityCore/src/MSIDConstants.m @@ -131,4 +131,8 @@ NSString *const MSID_FLIGHT_DISABLE_MOBILE_ONBOARDING = @"disable_mobile_onboarding"; +NSString *const MSID_FLIGHT_MDM_PROFILE_INSTALLED_NOTIFICATION_DELAY = @"mdm_profile_installed_notification_delay"; + +NSTimeInterval const MSIDMDMProfileInstalledNotificationDefaultDelay = 60.0; + #define METHODANDLINE [NSString stringWithFormat:@"%s [Line %d]", __PRETTY_FUNCTION__, __LINE__] diff --git a/IdentityCore/src/MSIDUXCallbackProtocol.h b/IdentityCore/src/MSIDUXCallbackProtocol.h new file mode 100644 index 0000000000..ec3745b243 --- /dev/null +++ b/IdentityCore/src/MSIDUXCallbackProtocol.h @@ -0,0 +1,44 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol MSIDUXCallbackProtocol + +/// Called when the webview loads a profile install URL during MDM onboarding. +/// The host app should schedule a local notification after the given delay. +- (void)scheduleMDMProfileInstalledNotificationWithDelay:(NSTimeInterval)delay; + +/// Called when enrollment completes successfully. The host app should cancel +/// any previously scheduled MDM profile installed notification. +- (void)cancelMDMProfileInstalledNotification; + +@end + +NS_ASSUME_NONNULL_END diff --git a/IdentityCore/src/MSIDUXCallbackProvider.h b/IdentityCore/src/MSIDUXCallbackProvider.h new file mode 100644 index 0000000000..3ab4b34a8e --- /dev/null +++ b/IdentityCore/src/MSIDUXCallbackProvider.h @@ -0,0 +1,39 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +#import +#import "MSIDUXCallbackProtocol.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MSIDUXCallbackProvider : NSObject + +@property (nonatomic, class, nullable) id uxCallbackProvider; + +@end + +NS_ASSUME_NONNULL_END diff --git a/IdentityCore/src/MSIDUXCallbackProvider.m b/IdentityCore/src/MSIDUXCallbackProvider.m new file mode 100644 index 0000000000..807534053c --- /dev/null +++ b/IdentityCore/src/MSIDUXCallbackProvider.m @@ -0,0 +1,44 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +//------------------------------------------------------------------------------ + +#import "MSIDUXCallbackProvider.h" + +static id s_uxCallbackProvider; + +@implementation MSIDUXCallbackProvider + ++ (id)uxCallbackProvider +{ + return s_uxCallbackProvider; +} + ++ (void)setUxCallbackProvider:(id)uxCallbackProvider +{ + s_uxCallbackProvider = uxCallbackProvider; +} + +@end diff --git a/IdentityCore/src/webview/embeddedWebview/MSIDWebviewNavigationDecisionResolver.m b/IdentityCore/src/webview/embeddedWebview/MSIDWebviewNavigationDecisionResolver.m index 71569b085f..0af5a50c35 100644 --- a/IdentityCore/src/webview/embeddedWebview/MSIDWebviewNavigationDecisionResolver.m +++ b/IdentityCore/src/webview/embeddedWebview/MSIDWebviewNavigationDecisionResolver.m @@ -29,6 +29,8 @@ #import "MSIDConstants.h" #import "MSIDIntuneDeviceIdCache.h" #import "MSIDVersion.h" +#import "MSIDUXCallbackProvider.h" +#import "MSIDFlightManager.h" #if !MSID_EXCLUDE_WEBKIT @@ -316,6 +318,20 @@ - (MSIDWebviewNavigationDecision *)decisionForProfileDownloadComplete:(NSDiction } MSID_LOG_WITH_CTX(MSIDLogLevelInfo, nil, @"[ProfileDownload] Built profile install request for host '%@'.", profileURL.host); + + NSString *delayString = [[MSIDFlightManager sharedInstance] stringForKey:MSID_FLIGHT_MDM_PROFILE_INSTALLED_NOTIFICATION_DELAY]; + NSTimeInterval delay = delayString.length > 0 ? delayString.doubleValue : MSIDMDMProfileInstalledNotificationDefaultDelay; + if (delay <= 0) + { + delay = MSIDMDMProfileInstalledNotificationDefaultDelay; + } + + id provider = MSIDUXCallbackProvider.uxCallbackProvider; + if (provider) + { + [provider scheduleMDMProfileInstalledNotificationWithDelay:delay]; + } + return [MSIDWebviewNavigationDecision loadRequest:[NSURLRequest requestWithURL:profileURL]]; } @@ -325,6 +341,13 @@ - (MSIDWebviewNavigationDecision *)decisionForEnrollmentCompletionURL:(NSURL *)U { MSID_LOG_WITH_CTX(MSIDLogLevelInfo, nil, @"[EnrollmentCompletion] Processing enrollment completion redirect."); + // Cancel any previously scheduled MDM profile installed notification + id provider = MSIDUXCallbackProvider.uxCallbackProvider; + if (provider) + { + [provider cancelMDMProfileInstalledNotification]; + } + // Check if SSO extension can perform request if ([MSIDSSOExtensionInteractiveTokenRequestController canPerformRequest]) { diff --git a/IdentityCore/tests/MSIDWebviewNavigationDecisionResolverTests.m b/IdentityCore/tests/MSIDWebviewNavigationDecisionResolverTests.m index c40ee1957f..2ea400797c 100644 --- a/IdentityCore/tests/MSIDWebviewNavigationDecisionResolverTests.m +++ b/IdentityCore/tests/MSIDWebviewNavigationDecisionResolverTests.m @@ -34,12 +34,19 @@ #import "MSIDTestCacheDataSource.h" #import "MSIDTestSwizzle.h" #import "MSIDVersion.h" +#import "MSIDUXCallbackProvider.h" +#import "MSIDUXCallbackProtocol.h" +#import "MSIDFlightManager.h" +#import "MSIDFlightManagerMockProvider.h" +#import "MSIDConstants.h" +#import "MSIDMockUXCallbackProvider.h" @interface MSIDWebviewNavigationDecisionResolverTests : XCTestCase @property (nonatomic) MSIDWebviewNavigationDecisionResolver *resolver; @property (nonatomic) MSIDTestCacheDataSource *dataSource; @property (nonatomic) MSIDIntuneDeviceIdCache *deviceIdCache; +@property (nonatomic) MSIDFlightManagerMockProvider *flightProvider; @end @@ -55,12 +62,17 @@ - (void)setUp self.dataSource = [MSIDTestCacheDataSource new]; self.deviceIdCache = [[MSIDIntuneDeviceIdCache alloc] initWithDataSource:self.dataSource]; [MSIDIntuneDeviceIdCache setSharedCache:self.deviceIdCache]; + + self.flightProvider = [MSIDFlightManagerMockProvider new]; + MSIDFlightManager.sharedInstance.flightProvider = self.flightProvider; } - (void)tearDown { [MSIDTestSwizzle reset]; [self.dataSource reset]; + MSIDUXCallbackProvider.uxCallbackProvider = nil; + MSIDFlightManager.sharedInstance.flightProvider = nil; [super tearDown]; } @@ -600,6 +612,131 @@ - (void)testComplianceURL_externalBlockNotInvoked_whenWebviewControllerIsNil XCTAssertEqual(decision.type, MSIDWebviewNavigationDecisionLoadRequest); } +#pragma mark - UX Callback Tests + +- (void)testProfileDownloadComplete_whenProviderSet_shouldInvokeCallbackWithDefaultDelay +{ + MSIDMockUXCallbackProvider *mockProvider = [MSIDMockUXCallbackProvider new]; + MSIDUXCallbackProvider.uxCallbackProvider = mockProvider; + + NSString *profileURL = @"https://manage.microsoft.com/profile.mobileconfig"; + NSString *encodedURL = [profileURL stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; + NSString *urlString = [NSString stringWithFormat:@"msauth://%@?%@=device123&%@=%@", + MSID_MDM_PROFILE_DOWNLOAD_COMPLETE_HOST, + MSID_INTUNE_DEVICE_ID_KEY, + MSID_INTUNE_PROFILE_INSTALL_URL_KEY, + encodedURL]; + NSURL *url = [NSURL URLWithString:urlString]; + + [self.resolver resolveDecisionForURL:url embeddedWebviewController:nil]; + + XCTAssertTrue(mockProvider.scheduleCalled, @"UX callback should be invoked on profile download complete."); + XCTAssertEqualWithAccuracy(mockProvider.receivedDelay, MSIDMDMProfileInstalledNotificationDefaultDelay, 0.01); +} + +- (void)testProfileDownloadComplete_whenFlightConfiguresDelay_shouldPassFlightDelay +{ + MSIDMockUXCallbackProvider *mockProvider = [MSIDMockUXCallbackProvider new]; + MSIDUXCallbackProvider.uxCallbackProvider = mockProvider; + + self.flightProvider.stringForKeyContainer = @{ MSID_FLIGHT_MDM_PROFILE_INSTALLED_NOTIFICATION_DELAY: @"300" }; + + NSString *profileURL = @"https://manage.microsoft.com/profile.mobileconfig"; + NSString *encodedURL = [profileURL stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; + NSString *urlString = [NSString stringWithFormat:@"msauth://%@?%@=device123&%@=%@", + MSID_MDM_PROFILE_DOWNLOAD_COMPLETE_HOST, + MSID_INTUNE_DEVICE_ID_KEY, + MSID_INTUNE_PROFILE_INSTALL_URL_KEY, + encodedURL]; + NSURL *url = [NSURL URLWithString:urlString]; + + [self.resolver resolveDecisionForURL:url embeddedWebviewController:nil]; + + XCTAssertTrue(mockProvider.scheduleCalled); + XCTAssertEqualWithAccuracy(mockProvider.receivedDelay, 300.0, 0.01); +} + +- (void)testProfileDownloadComplete_whenFlightDelayIsNegative_shouldFallbackToDefault +{ + MSIDMockUXCallbackProvider *mockProvider = [MSIDMockUXCallbackProvider new]; + MSIDUXCallbackProvider.uxCallbackProvider = mockProvider; + + self.flightProvider.stringForKeyContainer = @{ MSID_FLIGHT_MDM_PROFILE_INSTALLED_NOTIFICATION_DELAY: @"-5" }; + + NSString *profileURL = @"https://manage.microsoft.com/profile.mobileconfig"; + NSString *encodedURL = [profileURL stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; + NSString *urlString = [NSString stringWithFormat:@"msauth://%@?%@=device123&%@=%@", + MSID_MDM_PROFILE_DOWNLOAD_COMPLETE_HOST, + MSID_INTUNE_DEVICE_ID_KEY, + MSID_INTUNE_PROFILE_INSTALL_URL_KEY, + encodedURL]; + NSURL *url = [NSURL URLWithString:urlString]; + + [self.resolver resolveDecisionForURL:url embeddedWebviewController:nil]; + + XCTAssertTrue(mockProvider.scheduleCalled); + XCTAssertEqualWithAccuracy(mockProvider.receivedDelay, MSIDMDMProfileInstalledNotificationDefaultDelay, 0.01); +} + +- (void)testProfileDownloadComplete_whenProviderIsNil_shouldNotCrash +{ + MSIDUXCallbackProvider.uxCallbackProvider = nil; + + NSString *profileURL = @"https://manage.microsoft.com/profile.mobileconfig"; + NSString *encodedURL = [profileURL stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; + NSString *urlString = [NSString stringWithFormat:@"msauth://%@?%@=device123&%@=%@", + MSID_MDM_PROFILE_DOWNLOAD_COMPLETE_HOST, + MSID_INTUNE_DEVICE_ID_KEY, + MSID_INTUNE_PROFILE_INSTALL_URL_KEY, + encodedURL]; + NSURL *url = [NSURL URLWithString:urlString]; + + MSIDWebviewNavigationDecision *decision = [self.resolver resolveDecisionForURL:url embeddedWebviewController:nil]; + XCTAssertNotNil(decision); + XCTAssertEqual(decision.type, MSIDWebviewNavigationDecisionLoadRequest); +} + +#pragma mark - Cancel Notification on Enrollment Completion + +- (void)testEnrollmentCompletion_whenProviderSet_shouldCancelNotification +{ + MSIDMockUXCallbackProvider *mockProvider = [MSIDMockUXCallbackProvider new]; + MSIDUXCallbackProvider.uxCallbackProvider = mockProvider; + + [MSIDTestSwizzle classMethod:@selector(canPerformRequest) + class:[MSIDSSOExtensionInteractiveTokenRequestController class] + block:(id)^(void) + { + return YES; + }]; + + NSString *urlString = [NSString stringWithFormat:@"msauth://%@", MSID_MDM_ENROLLMENT_COMPLETION_HOST]; + NSURL *url = [NSURL URLWithString:urlString]; + + [self.resolver resolveDecisionForURL:url embeddedWebviewController:nil]; + + XCTAssertTrue(mockProvider.cancelCalled, @"Cancel should be invoked on enrollment completion."); +} + +- (void)testEnrollmentCompletion_whenProviderIsNil_shouldNotCrash +{ + MSIDUXCallbackProvider.uxCallbackProvider = nil; + + [MSIDTestSwizzle classMethod:@selector(canPerformRequest) + class:[MSIDSSOExtensionInteractiveTokenRequestController class] + block:(id)^(void) + { + return YES; + }]; + + NSString *urlString = [NSString stringWithFormat:@"msauth://%@", MSID_MDM_ENROLLMENT_COMPLETION_HOST]; + NSURL *url = [NSURL URLWithString:urlString]; + + MSIDWebviewNavigationDecision *decision = [self.resolver resolveDecisionForURL:url embeddedWebviewController:nil]; + XCTAssertNotNil(decision); + XCTAssertEqual(decision.type, MSIDWebviewNavigationDecisionCompleteWithURL); +} + @end #endif // !MSID_EXCLUDE_WEBKIT diff --git a/IdentityCore/tests/mocks/MSIDMockUXCallbackProvider.h b/IdentityCore/tests/mocks/MSIDMockUXCallbackProvider.h new file mode 100644 index 0000000000..f64f9779a4 --- /dev/null +++ b/IdentityCore/tests/mocks/MSIDMockUXCallbackProvider.h @@ -0,0 +1,38 @@ +// +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#import "MSIDUXCallbackProtocol.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MSIDMockUXCallbackProvider : NSObject + +@property (nonatomic) BOOL scheduleCalled; +@property (nonatomic) NSTimeInterval receivedDelay; +@property (nonatomic) BOOL cancelCalled; + +@end + +NS_ASSUME_NONNULL_END diff --git a/IdentityCore/tests/mocks/MSIDMockUXCallbackProvider.m b/IdentityCore/tests/mocks/MSIDMockUXCallbackProvider.m new file mode 100644 index 0000000000..8c678f9c86 --- /dev/null +++ b/IdentityCore/tests/mocks/MSIDMockUXCallbackProvider.m @@ -0,0 +1,40 @@ +// +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "MSIDMockUXCallbackProvider.h" + +@implementation MSIDMockUXCallbackProvider + +- (void)scheduleMDMProfileInstalledNotificationWithDelay:(NSTimeInterval)delay +{ + self.scheduleCalled = YES; + self.receivedDelay = delay; +} + +- (void)cancelMDMProfileInstalledNotification +{ + self.cancelCalled = YES; +} + +@end diff --git a/changelog.txt b/changelog.txt index 3a078c6f94..f2cc006acd 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,4 +1,5 @@ TBD +* Add MSIDUXCallbackProtocol and MSIDUXCallbackProvider to allow host apps to receive UX callbacks (e.g., schedule and cancel MDM profile installed notification) from CommonCore navigation layer. Add delay validation for flight-configured notification delay. * Only attach the PRT (refresh token credential) header to the PkeyAuth challenge response for known/trusted AAD hosts, preventing PRT leakage to untrusted hosts. Record an execution-flow tag for the added/skipped decision when a correlation id is available. Version 1.25.0 From 16c26a474ff5e98dbdc618caf834b187d56e2370 Mon Sep 17 00:00:00 2001 From: agubuzomaximus Date: Fri, 26 Jun 2026 14:23:57 -0700 Subject: [PATCH 11/38] [minor] [feature]: Add MSIDBoundTokenProvider for in-process browser-native GetToken (POC) (#1872) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Proposed changes Adds `MSIDBoundTokenProvider`, a Common Core seam that services a browser-native-message `GetToken` request **in-process** for a host such as OneAuth (embedded in Edge). On unmanaged iOS the platform SSO Extension is unavailable, so the host cannot silently invoke the broker through `ASAuthorizationSingleSignOnProvider`. Instead the host hands the typed `MSIDBrowserNativeMessageGetTokenRequest` to this provider, which owns the orchestration that would otherwise live behind the SSO Extension (silent BART SPA redemption, or an interactive broker flip). This PR is a **POC** that wires up the seam and proves the routing end-to-end; the real silent-redemption / interactive-broker-flip orchestration is layered on top of this provider in follow-up work. I have code changes in OneAuth that prove functionality. This provider must be created to support OneAuth when SSO EXT is not an option. This is for iOS only not Mac OS. In Edge native app when Browser sends BrowserNativeMessagingRequest OneAuth will check if SSO Ext is available. if SSO EXT is absent then OneAuth will use the path of MSIDBoundTokenProvider via Common Core to pass the request in. This class will later hold real validation logic and routing for the response to be obtained either through silent or interactive flow. A Similar class exist in Broker (thats how this works with SSO ext), a later PR will be created to see how the broker code can be broken down since this class is now required in Common core. I have discussed this with Sergei before he went OOF. ### What's included - `IdentityCore/src/oauth2/token/MSIDBoundTokenProvider.{h,m}` — the provider. Validates the request (nil → `MSIDErrorInvalidInternalParameter`; blank `clientId`/`redirectUri` → `MSIDErrorInvalidDeveloperParameter`) and returns a serialized browser-native-message response payload (`transport: in_proc_common_core`). - `IdentityCore/tests/MSIDBoundTokenProviderTests.m` — unit tests built from the real `MSIDBrowserNativeMessageGetTokenRequest` properties, covering the in-process success path and the missing-`clientId` validation path. ## Type of change - [x] Feature work - [ ] Bug fix - [ ] Documentation - [ ] Engineering change - [ ] Test - [ ] Logging/Telemetry ## Risk - [ ] High – Errors could cause MAJOR regression of many scenarios. (Example: new large features or high level infrastructure changes) - [ ] Medium – Errors could cause regression of 1 or more scenarios. (Example: somewhat complex bug fixes, small new features) - [x] Small – No issues are expected. Additive, self-contained POC seam with no existing call sites; consumed only by the parallel OneAuth POC. ## Additional information Paired with the OneAuth-side POC that routes `BrowserNativeMessagingGetTokenRequest` to this provider when the SSO Extension is unavailable. Branch: `maagubuzo/un_tb/poc/msidboundtokenprovider`, targeting `dev`. --------- Co-authored-by: Swasti Gupta Co-authored-by: Swasti Gupta Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Maximus Agubuzo --- .../IdentityCore.xcodeproj/project.pbxproj | 16 ++ .../src/oauth2/token/MSIDBoundTokenProvider.h | 59 +++++++ .../src/oauth2/token/MSIDBoundTokenProvider.m | 113 +++++++++++++ .../tests/MSIDBoundTokenProviderTests.m | 152 ++++++++++++++++++ 4 files changed, 340 insertions(+) create mode 100644 IdentityCore/src/oauth2/token/MSIDBoundTokenProvider.h create mode 100644 IdentityCore/src/oauth2/token/MSIDBoundTokenProvider.m create mode 100644 IdentityCore/tests/MSIDBoundTokenProviderTests.m diff --git a/IdentityCore/IdentityCore.xcodeproj/project.pbxproj b/IdentityCore/IdentityCore.xcodeproj/project.pbxproj index cc293b5071..398c3c9a8a 100644 --- a/IdentityCore/IdentityCore.xcodeproj/project.pbxproj +++ b/IdentityCore/IdentityCore.xcodeproj/project.pbxproj @@ -859,6 +859,9 @@ 72C1EBF52DE91AC8004C40A4 /* MSIDBoundRefreshToken.h in Headers */ = {isa = PBXBuildFile; fileRef = 72C1EBF42DE91ABE004C40A4 /* MSIDBoundRefreshToken.h */; }; 72C1EBF72DE91AD0004C40A4 /* MSIDBoundRefreshToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 72C1EBF62DE91ACC004C40A4 /* MSIDBoundRefreshToken.m */; }; 72C1EBF82DE91AD0004C40A4 /* MSIDBoundRefreshToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 72C1EBF62DE91ACC004C40A4 /* MSIDBoundRefreshToken.m */; }; + BA000000000000000000C001 /* MSIDBoundTokenProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = BA000000000000000000B001 /* MSIDBoundTokenProvider.h */; }; + BA000000000000000000C002 /* MSIDBoundTokenProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = BA000000000000000000B002 /* MSIDBoundTokenProvider.m */; }; + BA000000000000000000C003 /* MSIDBoundTokenProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = BA000000000000000000B002 /* MSIDBoundTokenProvider.m */; }; 72C1EBFC2DEA8199004C40A4 /* MSIDBoundRefreshTokenCacheItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 72C1EBFB2DEA8185004C40A4 /* MSIDBoundRefreshTokenCacheItem.h */; }; 72C1EBFE2DEA81A1004C40A4 /* MSIDBoundRefreshTokenCacheItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 72C1EBFD2DEA819E004C40A4 /* MSIDBoundRefreshTokenCacheItem.m */; }; 72C1EBFF2DEA81A1004C40A4 /* MSIDBoundRefreshTokenCacheItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 72C1EBFD2DEA819E004C40A4 /* MSIDBoundRefreshTokenCacheItem.m */; }; @@ -867,6 +870,8 @@ 72C6EF0E2EB192D800AF9AD0 /* MSIDBartFeatureUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 72C6EF0C2EB192D600AF9AD0 /* MSIDBartFeatureUtil.m */; }; 72C764F92E09CFB800043AB1 /* MSIDBoundRefreshTokenTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 72C764F82E09CFA400043AB1 /* MSIDBoundRefreshTokenTests.m */; }; 72C764FA2E09CFB800043AB1 /* MSIDBoundRefreshTokenTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 72C764F82E09CFA400043AB1 /* MSIDBoundRefreshTokenTests.m */; }; + BA000000000000000000D101 /* MSIDBoundTokenProviderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BA000000000000000000D001 /* MSIDBoundTokenProviderTests.m */; }; + BA000000000000000000D102 /* MSIDBoundTokenProviderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BA000000000000000000D001 /* MSIDBoundTokenProviderTests.m */; }; 72D961AE2DE12F1F005DED66 /* MSIDCachedNonce.h in Headers */ = {isa = PBXBuildFile; fileRef = 72D961AD2DE12F19005DED66 /* MSIDCachedNonce.h */; }; 72D961B02DE12F30005DED66 /* MSIDCachedNonce.m in Sources */ = {isa = PBXBuildFile; fileRef = 72D961AF2DE12F2E005DED66 /* MSIDCachedNonce.m */; }; 72D961B12DE12F30005DED66 /* MSIDCachedNonce.m in Sources */ = {isa = PBXBuildFile; fileRef = 72D961AF2DE12F2E005DED66 /* MSIDCachedNonce.m */; }; @@ -2929,11 +2934,14 @@ 72978AF12E4C2C3300DEA46D /* MSIDBoundRefreshTokenRedemptionParameters.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDBoundRefreshTokenRedemptionParameters.m; sourceTree = ""; }; 72C1EBF42DE91ABE004C40A4 /* MSIDBoundRefreshToken.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDBoundRefreshToken.h; sourceTree = ""; }; 72C1EBF62DE91ACC004C40A4 /* MSIDBoundRefreshToken.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDBoundRefreshToken.m; sourceTree = ""; }; + BA000000000000000000B001 /* MSIDBoundTokenProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDBoundTokenProvider.h; sourceTree = ""; }; + BA000000000000000000B002 /* MSIDBoundTokenProvider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDBoundTokenProvider.m; sourceTree = ""; }; 72C1EBFB2DEA8185004C40A4 /* MSIDBoundRefreshTokenCacheItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDBoundRefreshTokenCacheItem.h; sourceTree = ""; }; 72C1EBFD2DEA819E004C40A4 /* MSIDBoundRefreshTokenCacheItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDBoundRefreshTokenCacheItem.m; sourceTree = ""; }; 72C6EF0A2EB191E700AF9AD0 /* MSIDBartFeatureUtil.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDBartFeatureUtil.h; sourceTree = ""; }; 72C6EF0C2EB192D600AF9AD0 /* MSIDBartFeatureUtil.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDBartFeatureUtil.m; sourceTree = ""; }; 72C764F82E09CFA400043AB1 /* MSIDBoundRefreshTokenTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDBoundRefreshTokenTests.m; sourceTree = ""; }; + BA000000000000000000D001 /* MSIDBoundTokenProviderTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDBoundTokenProviderTests.m; sourceTree = ""; }; 72D961AD2DE12F19005DED66 /* MSIDCachedNonce.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDCachedNonce.h; sourceTree = ""; }; 72D961AF2DE12F2E005DED66 /* MSIDCachedNonce.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSIDCachedNonce.m; sourceTree = ""; }; 740340B72460E5C400DFCF27 /* MSIDCurrentRequestTelemetrySerializedItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSIDCurrentRequestTelemetrySerializedItem.h; sourceTree = ""; }; @@ -5310,6 +5318,8 @@ 72978AEA2E4C240B00DEA46D /* MSIDBoundRefreshToken+Redemption.h */, 72C1EBF62DE91ACC004C40A4 /* MSIDBoundRefreshToken.m */, 72C1EBF42DE91ABE004C40A4 /* MSIDBoundRefreshToken.h */, + BA000000000000000000B002 /* MSIDBoundTokenProvider.m */, + BA000000000000000000B001 /* MSIDBoundTokenProvider.h */, B2675689228CE6FC000F01D7 /* protocols */, B251CC4E204105AD005E0179 /* MSIDCredentialType.h */, B251CC4F204105AD005E0179 /* MSIDCredentialType.m */, @@ -6221,6 +6231,7 @@ 2321532C1FDF4FD800C6960D /* MSIDBaseTokenTests.m */, 724C9DD32E6906270039BAA0 /* MSIDBoundRefreshTokenRedemptionTests.m */, 72C764F82E09CFA400043AB1 /* MSIDBoundRefreshTokenTests.m */, + BA000000000000000000D001 /* MSIDBoundTokenProviderTests.m */, B48FC0612D7A90F4007B80DB /* MSIDBrokerFlightProviderTests.m */, B2E97FB22914CC4500AFD558 /* MSIDBrokerNativeAppOperationResponseTests.m */, 2318D7882E12B8E800A5A46E /* MSIDBrokerOperationBrowserNativeMessageMATSReportTests.m */, @@ -6882,6 +6893,7 @@ B443F0002AD6327700782168 /* MSIDBrokerOperationPasskeyCredentialRequest.h in Headers */, 23AE20982342D3BF00108F76 /* MSIDSilentController+Internal.h in Headers */, 72C1EBF52DE91AC8004C40A4 /* MSIDBoundRefreshToken.h in Headers */, + BA000000000000000000C001 /* MSIDBoundTokenProvider.h in Headers */, 23B39A8620993572000AA905 /* MSIDAADAuthorityMetadataRequest.h in Headers */, B28D90AA218FD1F800E230D6 /* MSIDDefaultTokenResponseValidator.h in Headers */, B2F671E82467A34400649855 /* MSIDAuthorizationCodeResult.h in Headers */, @@ -7629,6 +7641,7 @@ B286B9F12389F866007833AD /* MSIDWebviewFactoryTests.m in Sources */, B252913B2096698100E78695 /* MSIDAADIdTokenClaimsFactoryTests.m in Sources */, 72C764FA2E09CFB800043AB1 /* MSIDBoundRefreshTokenTests.m in Sources */, + BA000000000000000000D101 /* MSIDBoundTokenProviderTests.m in Sources */, B2BE923121A0EFB100F5AB8C /* MSIDDefaultTokenRequestProviderTests.m in Sources */, 729357F42DDBD3F80001D03C /* MSIDNonceTokenRequestTest.m in Sources */, 23FB5C20225516FB002BF1EB /* MSIDClaimsRequestTests.m in Sources */, @@ -7883,6 +7896,7 @@ B286B97F2389DC08007833AD /* MSIDBrokerOperationRequest.m in Sources */, 23FB5C3122551866002BF1EB /* MSIDClaimsRequest+ClientCapabilities.m in Sources */, 72C1EBF72DE91AD0004C40A4 /* MSIDBoundRefreshToken.m in Sources */, + BA000000000000000000C002 /* MSIDBoundTokenProvider.m in Sources */, 23B39ACD209CF317000AA905 /* MSIDAADNetworkConfiguration.m in Sources */, B5AAE11A2F03D7AA0026B21B /* MSIDBrokerOperationGetDefaultAccountResponse.m in Sources */, 23FB5C462255A135002BF1EB /* MSIDIndividualClaimRequest.m in Sources */, @@ -8376,6 +8390,7 @@ B2BE923521A0F80100F5AB8C /* MSIDLegacyTokenRequestProviderTests.m in Sources */, B48FC0632D7A90FA007B80DB /* MSIDBrokerFlightProviderTests.m in Sources */, 72C764F92E09CFB800043AB1 /* MSIDBoundRefreshTokenTests.m in Sources */, + BA000000000000000000D102 /* MSIDBoundTokenProviderTests.m in Sources */, 23FB5C21225516FB002BF1EB /* MSIDClaimsRequestTests.m in Sources */, E75DD02625D5E474007664A6 /* MSIDThrottlingServiceIntegrationTests.m in Sources */, B286BA07238A110A007833AD /* MSIDOIDCSignoutRequestTests.m in Sources */, @@ -8875,6 +8890,7 @@ 72978AF32E4C2C3500DEA46D /* MSIDBoundRefreshTokenRedemptionParameters.m in Sources */, B2C708182195283500D917B8 /* MSIDBrokerTokenRequest.m in Sources */, 72C1EBF82DE91AD0004C40A4 /* MSIDBoundRefreshToken.m in Sources */, + BA000000000000000000C003 /* MSIDBoundTokenProvider.m in Sources */, 232173E22182A998009852C6 /* NSDictionary+MSIDJsonSerializable.m in Sources */, B2C707F42192524700D917B8 /* MSIDDefaultTokenRequestProvider.m in Sources */, 724C9E332E6FAB170039BAA0 /* MSIDConcatKdfProvider.swift in Sources */, diff --git a/IdentityCore/src/oauth2/token/MSIDBoundTokenProvider.h b/IdentityCore/src/oauth2/token/MSIDBoundTokenProvider.h new file mode 100644 index 0000000000..896483ba7d --- /dev/null +++ b/IdentityCore/src/oauth2/token/MSIDBoundTokenProvider.h @@ -0,0 +1,59 @@ +// +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#import "MSIDRequestContext.h" + +@class MSIDBrowserNativeMessageGetTokenRequest; + +NS_ASSUME_NONNULL_BEGIN + +/// Completion block for a bound-token acquisition. +/// @param response Serialized browser-native-message response payload (JSON string) on success, otherwise nil. +/// @param error Populated when acquisition fails, otherwise nil. +typedef void (^MSIDBoundTokenProviderCompletionBlock)(NSString *_Nullable response, NSError *_Nullable error); + +/// Common Core orchestrator that services a browser-native-message GetToken request for a host such as +/// OneAuth (embedded in Edge). +/// +/// On unmanaged iOS the platform SSO Extension is unavailable, so the host cannot silently invoke the +/// broker through `ASAuthorizationSingleSignOnProvider`. Instead the host hands the GetToken request to +/// this provider, which owns the orchestration that would otherwise live behind the SSO Extension: +/// - transforms `MSIDBrowserNativeMessageGetTokenRequest` into the parameters used across Common Core, +/// - decides whether to service the request silently or interactively, +/// - silent path: redeems a cached BART SPA against ESTS in-process (no broker flip), +/// - interactive path: flips to the broker (Authenticator) via URL scheme to mint the initial token. +@interface MSIDBoundTokenProvider : NSObject + +/// Acquire a bound token for the supplied browser-native-message GetToken request. +/// @param request The GetToken request constructed by the host (e.g. OneAuth). +/// @param context Optional request context used for correlation and logging. +/// @param completionBlock Invoked with the serialized response payload or an error. +- (void)acquireBoundTokenWithRequest:(MSIDBrowserNativeMessageGetTokenRequest *)request + context:(nullable id)context + completionBlock:(MSIDBoundTokenProviderCompletionBlock)completionBlock; + +@end + +NS_ASSUME_NONNULL_END diff --git a/IdentityCore/src/oauth2/token/MSIDBoundTokenProvider.m b/IdentityCore/src/oauth2/token/MSIDBoundTokenProvider.m new file mode 100644 index 0000000000..8a160c0e58 --- /dev/null +++ b/IdentityCore/src/oauth2/token/MSIDBoundTokenProvider.m @@ -0,0 +1,113 @@ +// +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "MSIDBoundTokenProvider.h" +#import "MSIDBrowserNativeMessageGetTokenRequest.h" +#import "MSIDError.h" +#import "MSIDLogger+Internal.h" +#import "NSString+MSIDExtensions.h" + +NSString *const MSID_BOUND_TOKEN_PROVIDER_LOG_PREFIX = @"[MSIDBoundTokenProvider]"; + +@implementation MSIDBoundTokenProvider + +- (void)acquireBoundTokenWithRequest:(MSIDBrowserNativeMessageGetTokenRequest *)request + context:(nullable id)context + completionBlock:(MSIDBoundTokenProviderCompletionBlock)completionBlock +{ + NSParameterAssert(completionBlock); + if (!completionBlock) return; + + if (![self validateRequest:request context:context completionBlock:completionBlock]) + { + return; + } + + MSID_LOG_WITH_CTX(MSIDLogLevelInfo, context, + @"%@ Servicing GetToken request in-process (no SSO extension). clientId: %@", + MSID_BOUND_TOKEN_PROVIDER_LOG_PREFIX, request.clientId); + + // Stub seam: the real silent-redemption / interactive-broker-flip orchestration is layered on top of + // this provider. Returns the serialized browser-native-message response payload. + NSString *responsePayload = [self stubResponsePayloadForRequest:request]; + + MSID_LOG_WITH_CTX(MSIDLogLevelInfo, context, + @"%@ In-process GetToken request completed.", MSID_BOUND_TOKEN_PROVIDER_LOG_PREFIX); + + completionBlock(responsePayload, nil); +} + +#pragma mark - Private + +- (BOOL)validateRequest:(MSIDBrowserNativeMessageGetTokenRequest *)request + context:(nullable id)context + completionBlock:(MSIDBoundTokenProviderCompletionBlock)completionBlock +{ + if (!request) + { + NSError *error = MSIDCreateError(MSIDErrorDomain, MSIDErrorInvalidInternalParameter, + @"A GetToken request is required.", nil, nil, nil, + context.correlationId, nil, NO); + completionBlock(nil, error); + return NO; + } + + if ([NSString msidIsStringNilOrBlank:request.clientId] || + [NSString msidIsStringNilOrBlank:request.redirectUri]) + { + NSError *error = MSIDCreateError(MSIDErrorDomain, MSIDErrorInvalidDeveloperParameter, + @"clientId and redirectUri are required to acquire a bound token.", + nil, nil, nil, context.correlationId, nil, NO); + completionBlock(nil, error); + return NO; + } + + return YES; +} + +- (NSString *)stubResponsePayloadForRequest:(MSIDBrowserNativeMessageGetTokenRequest *)request +{ + NSMutableDictionary *payload = [NSMutableDictionary new]; + payload[@"clientId"] = request.clientId ?: @""; + payload[@"redirectUri"] = request.redirectUri ?: @""; + payload[@"scope"] = request.scopes ?: @""; + payload[@"servicedBy"] = @"MSIDBoundTokenProvider"; + payload[@"transport"] = @"in_proc_common_core"; + if (request.state) + { + payload[@"state"] = request.state; + } + + NSError *serializationError = nil; + NSData *data = [NSJSONSerialization dataWithJSONObject:payload options:0 error:&serializationError]; + if (serializationError || !data) + { + MSID_LOG_WITH_CTX(MSIDLogLevelError, nil, @"%@ Failed to serialize bound token payload: %@", MSID_BOUND_TOKEN_PROVIDER_LOG_PREFIX, serializationError); + return @"{}"; + } + + return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; +} + +@end diff --git a/IdentityCore/tests/MSIDBoundTokenProviderTests.m b/IdentityCore/tests/MSIDBoundTokenProviderTests.m new file mode 100644 index 0000000000..4a366c41fb --- /dev/null +++ b/IdentityCore/tests/MSIDBoundTokenProviderTests.m @@ -0,0 +1,152 @@ +// +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#import "MSIDBoundTokenProvider.h" +#import "MSIDBrowserNativeMessageGetTokenRequest.h" +#import "MSIDError.h" + +@interface MSIDBoundTokenProviderTests : XCTestCase + +@end + +@implementation MSIDBoundTokenProviderTests + +// A production-shaped GetToken request built from the real MSIDBrowserNativeMessageGetTokenRequest properties. +- (MSIDBrowserNativeMessageGetTokenRequest *)validRequest +{ + MSIDBrowserNativeMessageGetTokenRequest *request = [MSIDBrowserNativeMessageGetTokenRequest new]; + request.clientId = @"00000000-0000-0000-0000-000000000001"; + request.redirectUri = @"brk-com.microsoft.test://auth"; + request.scopes = @"user.read"; + request.state = @"test-state"; + request.prompt = MSIDPromptTypeDefault; + request.canShowUI = YES; + request.isSts = NO; + request.nonce = @"test-nonce"; + request.loginHint = @"user@contoso.com"; + request.instanceAware = NO; + request.platformSequence = @"oneauth|1.2.3,msal|1.0.0"; + request.extraParameters = @{ @"foo": @"bar" }; + return request; +} + +- (NSDictionary *)payloadDictionaryFromResponse:(NSString *)response +{ + NSData *data = [response dataUsingEncoding:NSUTF8StringEncoding]; + XCTAssertNotNil(data); + + NSError *jsonError = nil; + NSDictionary *payload = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; + XCTAssertNil(jsonError); + XCTAssertTrue([payload isKindOfClass:NSDictionary.class]); + + return payload; +} + +// A GetToken request handed to the provider is serviced entirely in-process, +// returning a payload, with no SSO extension / ASAuthorization involvement. +- (void)testAcquireBoundToken_inProc_returnsPayload +{ + MSIDBoundTokenProvider *provider = [MSIDBoundTokenProvider new]; + XCTestExpectation *expectation = [self expectationWithDescription:@"in-proc completion"]; + + [provider acquireBoundTokenWithRequest:[self validRequest] + context:nil + completionBlock:^(NSString *response, NSError *error) { + XCTAssertNil(error); + XCTAssertNotNil(response); + + NSDictionary *payload = [self payloadDictionaryFromResponse:response]; + XCTAssertEqualObjects(payload[@"clientId"], @"00000000-0000-0000-0000-000000000001"); + XCTAssertEqualObjects(payload[@"redirectUri"], @"brk-com.microsoft.test://auth"); + XCTAssertEqualObjects(payload[@"scope"], @"user.read"); + XCTAssertEqualObjects(payload[@"state"], @"test-state"); + XCTAssertEqualObjects(payload[@"transport"], @"in_proc_common_core"); + XCTAssertEqualObjects(payload[@"servicedBy"], @"MSIDBoundTokenProvider"); + [expectation fulfill]; + }]; + + [self waitForExpectations:@[expectation] timeout:5.0]; +} + +- (void)testAcquireBoundToken_nilRequest_returnsInvalidInternalParameterError +{ + MSIDBoundTokenProvider *provider = [MSIDBoundTokenProvider new]; + MSIDBrowserNativeMessageGetTokenRequest *nilRequest = nil; + XCTestExpectation *expectation = [self expectationWithDescription:@"nil request error"]; + + [provider acquireBoundTokenWithRequest:nilRequest + context:nil + completionBlock:^(NSString *response, NSError *error) { + XCTAssertNil(response); + XCTAssertEqualObjects(error.domain, MSIDErrorDomain); + XCTAssertEqual(error.code, MSIDErrorInvalidInternalParameter); + [expectation fulfill]; + }]; + + [self waitForExpectations:@[expectation] timeout:5.0]; +} + +- (void)testAcquireBoundToken_missingClientId_returnsError +{ + MSIDBoundTokenProvider *provider = [MSIDBoundTokenProvider new]; + MSIDBrowserNativeMessageGetTokenRequest *request = [self validRequest]; + request.clientId = @""; + + XCTestExpectation *expectation = [self expectationWithDescription:@"validation error"]; + + [provider acquireBoundTokenWithRequest:request + context:nil + completionBlock:^(NSString *response, NSError *error) { + XCTAssertNil(response); + XCTAssertEqualObjects(error.domain, MSIDErrorDomain); + XCTAssertEqual(error.code, MSIDErrorInvalidDeveloperParameter); + [expectation fulfill]; + }]; + + [self waitForExpectations:@[expectation] timeout:5.0]; +} + +- (void)testAcquireBoundToken_missingRedirectUri_returnsInvalidDeveloperParameterError +{ + MSIDBoundTokenProvider *provider = [MSIDBoundTokenProvider new]; + MSIDBrowserNativeMessageGetTokenRequest *request = [self validRequest]; + request.redirectUri = @""; + + XCTestExpectation *expectation = [self expectationWithDescription:@"redirect validation error"]; + + [provider acquireBoundTokenWithRequest:request + context:nil + completionBlock:^(NSString *response, NSError *error) { + XCTAssertNil(response); + XCTAssertEqualObjects(error.domain, MSIDErrorDomain); + XCTAssertEqual(error.code, MSIDErrorInvalidDeveloperParameter); + [expectation fulfill]; + }]; + + [self waitForExpectations:@[expectation] timeout:5.0]; +} + +@end From 658e465139252e4e54f6d2ade116c58e91daff43 Mon Sep 17 00:00:00 2001 From: Ameya Patil Date: Mon, 6 Jul 2026 15:12:01 -0700 Subject: [PATCH 12/38] [minor][tests]: Refactor ACES pipelines to consume shared aces-macos-job template (#1882) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert pr-validation, msal_submodule_check and broker_submodule_check to use the central Pipeline YAMLs/shared/aces-macos-job.yml template for pool + Xcode + tool setup. Split broker steps into broker_build_steps.yml (repo-specific) so the hosted visionOS consumer keeps its own tool setup via broker_submodule_steps.yml. ## PR Checklist (must be completed before review) - [ ] All tests pass locally - [ ] PR size is <= 500 LOC per PR Size Check policy - [ ] PR is independently mergeable (no hidden dependencies) - [ ] Appropriate reviewers are assigned - [ ] PR reviewed by code owner (required if Copilot-generated) - [ ] SME or Senior IC assigned where required ## PR Title Format **Required Format:** `[Keyword1] [Keyword2]: Description` - **Keyword1:** `major`, `minor`, or `patch` (case-insensitive) - **Keyword2:** `feature`, `bugfix`, `engg`, or `tests` (case-insensitive) **Examples:** - `[MAJOR] [Feature]: new API` - `[minor] [bugfix]: fix crash` - `[PATCH][tests]:add coverage` ## Proposed changes Describe what this PR is trying to do. ## Type of change - [ ] Feature work - [ ] Bug fix - [ ] Documentation - [ ] Engineering change - [ ] Test - [ ] Logging/Telemetry ## Risk - [ ] High – Errors could cause MAJOR regression of many scenarios. (Example: new large features or high level infrastructure changes) - [ ] Medium – Errors could cause regression of 1 or more scenarios. (Example: somewhat complex bug fixes, small new features) - [ ] Small – No issues are expected. (Example: Very small bug fixes, string changes, or configuration settings changes) ## Additional information --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- azure_pipelines/broker_build_steps.yml | 196 ++++++++++++++++++ azure_pipelines/broker_submodule_check.yml | 56 +++-- azure_pipelines/broker_submodule_steps.yml | 181 ++--------------- azure_pipelines/msal_submodule_check.yaml | 183 +++++++++-------- azure_pipelines/pr-validation.yml | 226 ++++++++++----------- 5 files changed, 456 insertions(+), 386 deletions(-) create mode 100644 azure_pipelines/broker_build_steps.yml diff --git a/azure_pipelines/broker_build_steps.yml b/azure_pipelines/broker_build_steps.yml new file mode 100644 index 0000000000..38cc291070 --- /dev/null +++ b/azure_pipelines/broker_build_steps.yml @@ -0,0 +1,196 @@ +# ============================================================================= +# Broker repo-specific build steps (checkouts, submodules, CocoaPods, build). +# +# These are the steps that are unique to the Broker submodule validation. The +# generic macOS tool setup (pool, Xcode, Python, Ruby gems) is intentionally +# NOT here so it can be provided by whichever environment runs these steps: +# +# * ACES pipelines -> Pipeline YAMLs/shared/aces-macos-job.yml provides the +# pool + Xcode + tooling, then injects these steps. +# * visionOS (hosted) -> broker_submodule_steps.yml provides its own tool +# setup (and selectLocalXcode below) and then includes +# these steps. +# +# Parameters: +# target build.py target (e.g. ios_library, mac_library, vision_library). +# selectLocalXcode when true, run the Broker repo's select_xcode.sh (used by +# consumers that do NOT get Xcode from the shared job template). +# localXcodeVersion Xcode version passed to select_xcode.sh when selectLocalXcode. +# ============================================================================= +parameters: +- name: target + type: string +- name: selectLocalXcode + type: boolean + default: false +- name: localXcodeVersion + type: string + default: '16.4' + +steps: +- checkout: azure-activedirectory-tokenbroker-for-objc + displayName: 'Checkout Broker' + clean: false + submodules: false + fetchTags: true + persistCredentials: true + +- task: Bash@3 + displayName: 'Checkout MSAL, ADAL, and submodules' + inputs: + workingDirectory: $(Pipeline.Workspace)/s + targetType: 'inline' + script: | + cd azure-activedirectory-tokenbroker-for-objc + git submodule update --init --recursive ADAuthenticationBroker/Frameworks/adal + git submodule update --init ADAuthenticationBroker/Frameworks/microsoft-authentication-library-for-objc + +- checkout: self + displayName: 'Checkout IdentityCore' + clean: false + submodules: false + fetchTags: true + path: 's/azure-activedirectory-tokenbroker-for-objc/ADAuthenticationBroker/Frameworks/microsoft-authentication-library-for-objc/MSAL/IdentityCore' + persistCredentials: true + +- checkout: WorkplaceJoin-for-iOS + displayName: 'Checkout WPJ' + clean: false + submodules: false + fetchTags: true + path: 's/azure-activedirectory-tokenbroker-for-objc/ADAuthenticationBroker/Frameworks/WorkplaceJoin-for-iOS' + persistCredentials: true + +- task: AzureCLI@2 + inputs: + azureSubscription: 'AuthSdkResourceManager' + scriptType: 'pscore' + scriptLocation: 'inlineScript' + inlineScript: | + # if this fails, check out this bash script that includes diagnostics: + # https://gist.github.com/johnterickson/19f80a3e969e39f1000d118739176e62 + # uncomment these for more debugging spew + # GIT_TRACE=1 + # GIT_CURL_VERBOSE=1 + + # Note that the resource is specified to limit the token to Azure DevOps + $token = az account get-access-token --query accessToken --resource 499b84ac-1321-427f-aa17-267ca6975798 -o tsv + Write-Host "##vso[task.setvariable variable=aadToken;issecret=true]$token" +- task: Bash@3 + displayName: 'Checkout NGC Submodules' + env: + AccessToken: $(MSAzureToken_encoded) + inputs: + workingDirectory: $(Pipeline.Workspace)/s + targetType: 'inline' + script: | + cd azure-activedirectory-tokenbroker-for-objc/ADAuthenticationBroker/Frameworks + git -c http.https://msazure.visualstudio.com/DefaultCollection/One/_git/AD-MFA-NGCAuthentication.extraheader="AUTHORIZATION: bearer $(aadToken)" submodule update --init AD-MFA-NGCAuthentication + cd AD-MFA-NGCAuthentication + git -c http.https://msazure.visualstudio.com/DefaultCollection/One/_git/AD-MFA-NGCKeyProvider-ios.extraheader="AUTHORIZATION: bearer $(aadToken)" submodule update --init NGCKeyProvider + git -c http.https://msazure.visualstudio.com/DefaultCollection/One/_git/AD-MFA-MSAuthNetworking.extraheader="AUTHORIZATION: bearer $(aadToken)" submodule update --init MSAuthNetworking + +- task: Bash@3 + displayName: 'Checkout WPJ openssl-msft submodule' + inputs: + workingDirectory: $(Pipeline.Workspace)/s + targetType: 'inline' + script: | + cd azure-activedirectory-tokenbroker-for-objc/ADAuthenticationBroker/Frameworks/WorkplaceJoin-for-iOS + git -c http.https://msazure.visualstudio.com/DefaultCollection/PlatformCrypto/_git/openssl-msft.extraheader="AUTHORIZATION: bearer $(aadToken)" submodule update --init Frameworks/openssl-msft + +- task: Bash@3 + displayName: 'Update WPJ submodules' + inputs: + workingDirectory: $(Pipeline.Workspace)/s + targetType: 'inline' + script: | + cd azure-activedirectory-tokenbroker-for-objc/ADAuthenticationBroker/Frameworks/WorkplaceJoin-for-iOS + git submodule update --init --recursive Frameworks/microsoft-authentication-library-for-objc + +- ${{ if eq(parameters.target, 'mac_library') }}: + - task: Cache@2 + displayName: 'Cache CocoaPods' + inputs: + key: 'cocoapods | "$(Agent.OS)" | azure-activedirectory-tokenbroker-for-objc/ADAuthenticationBroker/Podfile.lock' + path: '$(Pipeline.Workspace)/s/azure-activedirectory-tokenbroker-for-objc/ADAuthenticationBroker/Pods' + + - task: Bash@3 + displayName: 'Install CocoaPods (if needed)' + inputs: + targetType: 'inline' + script: | + if ! command -v pod >/dev/null 2>&1; then + export HOMEBREW_NO_AUTO_UPDATE=1 + if command -v brew >/dev/null 2>&1; then + brew install cocoapods + else + sudo gem install cocoapods -N + fi + fi + + - task: Bash@3 + displayName: 'Install CocoaPods dependencies' + env: + AAD_TOKEN: $(aadToken) + inputs: + workingDirectory: $(Pipeline.Workspace)/s/azure-activedirectory-tokenbroker-for-objc/ADAuthenticationBroker + targetType: 'inline' + script: | + # CocoaPods requires a UTF-8 locale; otherwise Ruby's unicode_normalize + # raises Encoding::CompatibilityError on ASCII-8BIT paths. + export LANG=en_US.UTF-8 + export LC_ALL=en_US.UTF-8 + GIT_CONFIG_COUNT=1 \ + GIT_CONFIG_KEY_0=http.https://office.visualstudio.com/.extraheader \ + GIT_CONFIG_VALUE_0="AUTHORIZATION: bearer $AAD_TOKEN" \ + pod install + retryCountOnTaskFailure: 1 + +# Xcode selection for consumers that do not get Xcode from the shared ACES job +# template (e.g. the hosted visionOS broker stage). Runs after checkout because +# select_xcode.sh lives inside the Broker repo. +- ${{ if eq(parameters.selectLocalXcode, true) }}: + - task: Bash@3 + displayName: 'Select Xcode version' + inputs: + targetType: 'inline' + script: 'bash $(Pipeline.Workspace)/s/azure-activedirectory-tokenbroker-for-objc/scripts/select_xcode.sh ${{ parameters.localXcodeVersion }}' + +- task: Bash@3 + displayName: 'Run a python script for Broker' + inputs: + targetType: 'inline' + script: | + cd azure-activedirectory-tokenbroker-for-objc + echo "executing build:./build.py" + # Anchor the log path to the actual build cwd so the cleanup step + # cannot drift to a different location than what we tee'd to. + mkdir -p "$PWD/build" + LOG_FILE="$PWD/build/build_output.log" + echo "BROKER_BUILD_LOG=$LOG_FILE" + echo "##vso[task.setvariable variable=BROKER_BUILD_LOG]$LOG_FILE" + ./build.py --show-build-settings --target ${{ parameters.target }} 2>&1 | tee "$LOG_FILE" + final_status=$(<./build/status.txt) + echo "FINAL STATUS = ${final_status}" + + if [ $final_status != "0" ]; then + echo "Build & Testing Failed!" >&2 + fi + failOnStderr: true + +- task: Bash@3 + condition: always() + displayName: Cleanup + inputs: + targetType: 'inline' + script: | + LOG_FILE="${BROKER_BUILD_LOG:-$(Pipeline.Workspace)/s/azure-activedirectory-tokenbroker-for-objc/build/build_output.log}" + echo "Looking for build log at: $LOG_FILE" + echo "Failed tests summary:" + if [ -f "$LOG_FILE" ]; then + grep -E "Test Case '-\\[.*\\]' failed|\\*\\* TEST FAILED \\*\\*" "$LOG_FILE" | sort -u || echo "No failed test lines found." + else + echo "No build log found at $LOG_FILE (build step likely did not run)." + fi + rm -rf $(Pipeline.Workspace)/s/azure-activedirectory-tokenbroker-for-objc/build/status.txt diff --git a/azure_pipelines/broker_submodule_check.yml b/azure_pipelines/broker_submodule_check.yml index c1d0a33d31..a1db84bf55 100644 --- a/azure_pipelines/broker_submodule_check.yml +++ b/azure_pipelines/broker_submodule_check.yml @@ -16,11 +16,6 @@ pr: - '*' drafts: true -pool: - name: 'AcesShared' - demands: - - ImageOverride -equals ACES_VM_SharedPool_Sequoia - # Pipeline parameter: supply the branch of azure-activedirectory-tokenbroker-for-objc to validate against. # Defaults to 'dev'. Can be overridden when manually queuing the pipeline or via /azp run. parameters: @@ -42,38 +37,41 @@ resources: endpoint: 'MSAL ObjC Service Connection' name: AzureAD/WorkplaceJoin-for-iOS + # Shared ACES macOS job template (pool + Xcode + tool setup) — source of truth. + # TODO: switch ref to 'main' once ameyapat/common-aces-config is merged. + - repository: pipelinesShared + type: git + name: IDDP/MSAL-ObjC-Pipelines + ref: refs/heads/main + stages: - stage: Stage_IOS displayName: "Validate iOS" dependsOn: [] jobs: - - job: Validate_Pull_Request_IOS - displayName: Validate Pull Request (iOS) - # When 0 is specified, the maximum limit is used. On Microsoft-hosted agents, jobs can't run longer than this interval, regardless of any job level timeouts specified in the job. - timeoutInMinutes: 0 - pool: - name: 'AcesShared' - demands: - - ImageOverride -equals ACES_VM_SharedPool_Sequoia - variables: - target: "ios_library" - steps: - - template: broker_submodule_steps.yml + - template: Pipeline YAMLs/shared/aces-macos-job.yml@pipelinesShared + parameters: + jobName: Validate_Pull_Request_IOS + displayName: Validate Pull Request (iOS) + # 0 = use the maximum limit allowed by the pool. + timeoutInMinutes: 0 + steps: + - template: /azure_pipelines/broker_build_steps.yml + parameters: + target: ios_library - stage: Stage_MAC displayName: "Validate Mac" dependsOn: [] jobs: - - job: Validate_Pull_Request_MAC - displayName: Validate Pull Request (Mac) - # When 0 is specified, the maximum limit is used. On Microsoft-hosted agents, jobs can't run longer than this interval, regardless of any job level timeouts specified in the job. - timeoutInMinutes: 0 - pool: - name: 'AcesShared' - demands: - - ImageOverride -equals ACES_VM_SharedPool_Sequoia - variables: - target: "mac_library" - steps: - - template: broker_submodule_steps.yml + - template: Pipeline YAMLs/shared/aces-macos-job.yml@pipelinesShared + parameters: + jobName: Validate_Pull_Request_MAC + displayName: Validate Pull Request (Mac) + # 0 = use the maximum limit allowed by the pool. + timeoutInMinutes: 0 + steps: + - template: /azure_pipelines/broker_build_steps.yml + parameters: + target: mac_library diff --git a/azure_pipelines/broker_submodule_steps.yml b/azure_pipelines/broker_submodule_steps.yml index b7b0b7d456..023b970255 100644 --- a/azure_pipelines/broker_submodule_steps.yml +++ b/azure_pipelines/broker_submodule_steps.yml @@ -1,125 +1,15 @@ +# ============================================================================= +# Broker tool setup + build steps for consumers that do NOT use the shared ACES +# macOS job template (currently only the hosted visionOS broker stage). +# +# The ACES Broker pipeline (broker_submodule_check.yml) instead gets its pool + +# Xcode + Ruby/Python tooling from Pipeline YAMLs/shared/aces-macos-job.yml and +# consumes broker_build_steps.yml directly, so the tool-install steps below are +# only needed here for the hosted image. +# +# The consuming job must set a `target` variable (e.g. vision_library). +# ============================================================================= steps: -- checkout: azure-activedirectory-tokenbroker-for-objc - displayName: 'Checkout Broker' - clean: false - submodules: false - fetchTags: true - persistCredentials: true - -- task: Bash@3 - displayName: 'Checkout MSAL, ADAL, and submodules' - inputs: - workingDirectory: $(Pipeline.Workspace)/s - targetType: 'inline' - script: | - cd azure-activedirectory-tokenbroker-for-objc - git submodule update --init --recursive ADAuthenticationBroker/Frameworks/adal - git submodule update --init ADAuthenticationBroker/Frameworks/microsoft-authentication-library-for-objc - -- checkout: self - displayName: 'Checkout IdentityCore' - clean: false - submodules: false - fetchTags: true - path: 's/azure-activedirectory-tokenbroker-for-objc/ADAuthenticationBroker/Frameworks/microsoft-authentication-library-for-objc/MSAL/IdentityCore' - persistCredentials: true - -- checkout: WorkplaceJoin-for-iOS - displayName: 'Checkout WPJ' - clean: false - submodules: false - fetchTags: true - path: 's/azure-activedirectory-tokenbroker-for-objc/ADAuthenticationBroker/Frameworks/WorkplaceJoin-for-iOS' - persistCredentials: true - -- task: AzureCLI@2 - inputs: - azureSubscription: 'AuthSdkResourceManager' - scriptType: 'pscore' - scriptLocation: 'inlineScript' - inlineScript: | - # if this fails, check out this bash script that includes diagnostics: - # https://gist.github.com/johnterickson/19f80a3e969e39f1000d118739176e62 - # uncomment these for more debugging spew - # GIT_TRACE=1 - # GIT_CURL_VERBOSE=1 - - # Note that the resource is specified to limit the token to Azure DevOps - $token = az account get-access-token --query accessToken --resource 499b84ac-1321-427f-aa17-267ca6975798 -o tsv - Write-Host "##vso[task.setvariable variable=aadToken;issecret=true]$token" -- task: Bash@3 - displayName: 'Checkout NGC Submodules' - env: - AccessToken: $(MSAzureToken_encoded) - inputs: - workingDirectory: $(Pipeline.Workspace)/s - targetType: 'inline' - script: | - cd azure-activedirectory-tokenbroker-for-objc/ADAuthenticationBroker/Frameworks - git -c http.https://msazure.visualstudio.com/DefaultCollection/One/_git/AD-MFA-NGCAuthentication.extraheader="AUTHORIZATION: bearer $(aadToken)" submodule update --init AD-MFA-NGCAuthentication - cd AD-MFA-NGCAuthentication - git -c http.https://msazure.visualstudio.com/DefaultCollection/One/_git/AD-MFA-NGCKeyProvider-ios.extraheader="AUTHORIZATION: bearer $(aadToken)" submodule update --init NGCKeyProvider - git -c http.https://msazure.visualstudio.com/DefaultCollection/One/_git/AD-MFA-MSAuthNetworking.extraheader="AUTHORIZATION: bearer $(aadToken)" submodule update --init MSAuthNetworking - -- task: Bash@3 - displayName: 'Checkout WPJ openssl-msft submodule' - inputs: - workingDirectory: $(Pipeline.Workspace)/s - targetType: 'inline' - script: | - cd azure-activedirectory-tokenbroker-for-objc/ADAuthenticationBroker/Frameworks/WorkplaceJoin-for-iOS - git -c http.https://msazure.visualstudio.com/DefaultCollection/PlatformCrypto/_git/openssl-msft.extraheader="AUTHORIZATION: bearer $(aadToken)" submodule update --init Frameworks/openssl-msft - -- task: Bash@3 - displayName: 'Update WPJ submodules' - inputs: - workingDirectory: $(Pipeline.Workspace)/s - targetType: 'inline' - script: | - cd azure-activedirectory-tokenbroker-for-objc/ADAuthenticationBroker/Frameworks/WorkplaceJoin-for-iOS - git submodule update --init --recursive Frameworks/microsoft-authentication-library-for-objc - -- task: Cache@2 - displayName: 'Cache CocoaPods' - condition: eq(variables['target'], 'mac_library') - inputs: - key: 'cocoapods | "$(Agent.OS)" | azure-activedirectory-tokenbroker-for-objc/ADAuthenticationBroker/Podfile.lock' - path: '$(Pipeline.Workspace)/s/azure-activedirectory-tokenbroker-for-objc/ADAuthenticationBroker/Pods' - -- task: Bash@3 - displayName: 'Install CocoaPods (if needed)' - condition: eq(variables['target'], 'mac_library') - inputs: - targetType: 'inline' - script: | - if ! command -v pod >/dev/null 2>&1; then - export HOMEBREW_NO_AUTO_UPDATE=1 - if command -v brew >/dev/null 2>&1; then - brew install cocoapods - else - sudo gem install cocoapods -N - fi - fi - -- task: Bash@3 - displayName: 'Install CocoaPods dependencies' - condition: eq(variables['target'], 'mac_library') - env: - AAD_TOKEN: $(aadToken) - inputs: - workingDirectory: $(Pipeline.Workspace)/s/azure-activedirectory-tokenbroker-for-objc/ADAuthenticationBroker - targetType: 'inline' - script: | - # CocoaPods requires a UTF-8 locale; otherwise Ruby's unicode_normalize - # raises Encoding::CompatibilityError on ASCII-8BIT paths. - export LANG=en_US.UTF-8 - export LC_ALL=en_US.UTF-8 - GIT_CONFIG_COUNT=1 \ - GIT_CONFIG_KEY_0=http.https://office.visualstudio.com/.extraheader \ - GIT_CONFIG_VALUE_0="AUTHORIZATION: bearer $AAD_TOKEN" \ - pod install - retryCountOnTaskFailure: 1 - - script: 'gem uninstall xcpretty -I --version 0.4.0' displayName: 'Uninstall xcpretty v0.4.0' @@ -144,46 +34,9 @@ steps: - task: UsePythonVersion@0 displayName: 'Use Python 3.x' -- task: Bash@3 - displayName: 'Select Xcode version' - inputs: - targetType: 'inline' - script: 'bash $(Pipeline.Workspace)/s/azure-activedirectory-tokenbroker-for-objc/scripts/select_xcode.sh 16.4' - -- task: Bash@3 - displayName: 'Run a python script for Broker' - inputs: - targetType: 'inline' - script: | - cd azure-activedirectory-tokenbroker-for-objc - echo "executing build:./build.py" - # Anchor the log path to the actual build cwd so the cleanup step - # cannot drift to a different location than what we tee'd to. - mkdir -p "$PWD/build" - LOG_FILE="$PWD/build/build_output.log" - echo "BROKER_BUILD_LOG=$LOG_FILE" - echo "##vso[task.setvariable variable=BROKER_BUILD_LOG]$LOG_FILE" - ./build.py --show-build-settings --target $(target) 2>&1 | tee "$LOG_FILE" - final_status=$(<./build/status.txt) - echo "FINAL STATUS = ${final_status}" - - if [ $final_status != "0" ]; then - echo "Build & Testing Failed!" >&2 - fi - failOnStderr: true - -- task: Bash@3 - condition: always() - displayName: Cleanup - inputs: - targetType: 'inline' - script: | - LOG_FILE="${BROKER_BUILD_LOG:-$(Pipeline.Workspace)/s/azure-activedirectory-tokenbroker-for-objc/build/build_output.log}" - echo "Looking for build log at: $LOG_FILE" - echo "Failed tests summary:" - if [ -f "$LOG_FILE" ]; then - grep -E "Test Case '-\\[.*\\]' failed|\\*\\* TEST FAILED \\*\\*" "$LOG_FILE" | sort -u || echo "No failed test lines found." - else - echo "No build log found at $LOG_FILE (build step likely did not run)." - fi - rm -rf ./build/status.txt +# Repo checkouts, CocoaPods, Xcode selection (16.4) and build. +- template: broker_build_steps.yml + parameters: + target: $(target) + selectLocalXcode: true + localXcodeVersion: '16.4' diff --git a/azure_pipelines/msal_submodule_check.yaml b/azure_pipelines/msal_submodule_check.yaml index 84a0c02df3..bce706c280 100644 --- a/azure_pipelines/msal_submodule_check.yaml +++ b/azure_pipelines/msal_submodule_check.yaml @@ -34,88 +34,111 @@ resources: name: AzureAD/microsoft-authentication-library-for-objc ref: refs/heads/${{ parameters.msalBranch }} + # Shared ACES macOS job template (pool + Xcode + tool setup) — source of truth. + # TODO: switch ref to 'main' once ameyapat/common-aces-config is merged. + - repository: pipelinesShared + type: git + name: IDDP/MSAL-ObjC-Pipelines + ref: refs/heads/main + # Define parallel jobs that run build script for specified targets jobs: -- job: 'Validate_Pull_Request' - # When 0 is specified, the maximum limit is used. On Microsoft-hosted agents, jobs can't run longer than this interval, regardless of any job level timeouts specified in the job. - timeoutInMinutes: 0 - strategy: - maxParallel: 2 - matrix: - IOS_FRAMEWORK: - target: "iosFramework iosTestApp sampleIosApp sampleIosAppSwift" - MAC_FRAMEWORK: - target: "macFramework" - displayName: Validate Pull Request - pool: - name: 'AcesShared' - demands: - - ImageOverride -equals ACES_VM_SharedPool_Sequoia - - steps: - - - task: CmdLine@2 - displayName: Uninstalling xcpretty v0.4.0 - inputs: - script: | - sudo gem uninstall xcpretty -I --version 0.4.0 - failOnStderr: false - - - task: CmdLine@2 - displayName: Installing xcpretty v0.3.0 - inputs: - script: | - sudo gem install xcpretty -N -v 0.3.0 - failOnStderr: true - - - checkout: microsoft-authentication-library-for-objc - displayName: 'Checkout MSAL' - clean: true - submodules: true - fetchTags: true - persistCredentials: true - - - checkout: self - clean: true - submodules: false - fetchDepth: 1 - path: 's/microsoft-authentication-library-for-objc/MSAL/IdentityCore' - persistCredentials: false - - - task: UsePythonVersion@0 - displayName: 'Use Python 3.x' - inputs: - versionSpec: '3.x' - - - task: Bash@3 - displayName: Run Build script & check for Errors - inputs: - targetType: 'inline' - script: | - cd $(Agent.BuildDirectory)/s/microsoft-authentication-library-for-objc - { output=$(./build.py --target $(target) 2>&1 1>&3-) ;} 3>&1 - final_status=$(<./build/status.txt) - echo "FINAL STATUS = ${final_status}" - echo "POSSIBLE ERRORS: ${output}" +- template: Pipeline YAMLs/shared/aces-macos-job.yml@pipelinesShared + parameters: + jobName: Validate_Pull_Request_iOS + displayName: Validate Pull Request (iOS) + # 0 = use the maximum limit allowed by the pool. + timeoutInMinutes: 0 + steps: + - checkout: microsoft-authentication-library-for-objc + displayName: 'Checkout MSAL' + clean: true + submodules: true + fetchTags: true + persistCredentials: true + - checkout: self + clean: true + submodules: false + fetchDepth: 1 + path: 's/microsoft-authentication-library-for-objc/MSAL/IdentityCore' + persistCredentials: false + - task: Bash@3 + displayName: Run Build script & check for Errors + inputs: + targetType: 'inline' + script: | + cd $(Agent.BuildDirectory)/s/microsoft-authentication-library-for-objc + { output=$(./build.py --target iosFramework iosTestApp sampleIosApp sampleIosAppSwift 2>&1 1>&3-) ;} 3>&1 + final_status=$(<./build/status.txt) + echo "FINAL STATUS = ${final_status}" + echo "POSSIBLE ERRORS: ${output}" - if [ $final_status != "0" ]; then - echo "Build & Testing Failed! \n ${output}" >&2 - fi - failOnStderr: true + if [ $final_status != "0" ]; then + echo "Build & Testing Failed! \n ${output}" >&2 + fi + failOnStderr: true + - task: Bash@3 + condition: always() + displayName: Cleanup + inputs: + targetType: 'inline' + script: | + rm -rf $(Agent.BuildDirectory)/s/build/status.txt + - task: PublishTestResults@2 + condition: always() + displayName: Publish Test Report + inputs: + testResultsFormat: 'JUnit' + testResultsFiles: '$(Agent.BuildDirectory)/s/microsoft-authentication-library-for-objc/build/reports/*' + failTaskOnFailedTests: true + testRunTitle: 'Test Run - iosFramework' - - task: Bash@3 - condition: always() - displayName: Cleanup - inputs: - targetType: 'inline' - script: | - rm -rf $(Agent.BuildDirectory)/s/build/status.txt +- template: Pipeline YAMLs/shared/aces-macos-job.yml@pipelinesShared + parameters: + jobName: Validate_Pull_Request_macOS + displayName: Validate Pull Request (macOS) + # 0 = use the maximum limit allowed by the pool. + timeoutInMinutes: 0 + steps: + - checkout: microsoft-authentication-library-for-objc + displayName: 'Checkout MSAL' + clean: true + submodules: true + fetchTags: true + persistCredentials: true + - checkout: self + clean: true + submodules: false + fetchDepth: 1 + path: 's/microsoft-authentication-library-for-objc/MSAL/IdentityCore' + persistCredentials: false + - task: Bash@3 + displayName: Run Build script & check for Errors + inputs: + targetType: 'inline' + script: | + cd $(Agent.BuildDirectory)/s/microsoft-authentication-library-for-objc + { output=$(./build.py --target macFramework 2>&1 1>&3-) ;} 3>&1 + final_status=$(<./build/status.txt) + echo "FINAL STATUS = ${final_status}" + echo "POSSIBLE ERRORS: ${output}" - - task: PublishTestResults@2 - condition: always() - displayName: Publish Test Report - inputs: - testResultsFormat: 'JUnit' - testResultsFiles: '$(Agent.BuildDirectory)/s/build/reports/*' - failTaskOnFailedTests: true - testRunTitle: 'Test Run - $(target)' + if [ $final_status != "0" ]; then + echo "Build & Testing Failed! \n ${output}" >&2 + fi + failOnStderr: true + - task: Bash@3 + condition: always() + displayName: Cleanup + inputs: + targetType: 'inline' + script: | + rm -rf $(Agent.BuildDirectory)/s/microsoft-authentication-library-for-objc/build/status.txt + - task: PublishTestResults@2 + condition: always() + displayName: Publish Test Report + inputs: + testResultsFormat: 'JUnit' + testResultsFiles: '$(Agent.BuildDirectory)/s/microsoft-authentication-library-for-objc/build/reports/*' + failTaskOnFailedTests: true + testRunTitle: 'Test Run - macFramework' diff --git a/azure_pipelines/pr-validation.yml b/azure_pipelines/pr-validation.yml index a6dfcd3547..3c36c4d801 100644 --- a/azure_pipelines/pr-validation.yml +++ b/azure_pipelines/pr-validation.yml @@ -16,6 +16,12 @@ resources: type: git name: IDDP/MSAL-ObjC-Pipelines ref: main + # Shared ACES macOS job template (pool + Xcode + tool setup) — source of truth. + # TODO: switch ref to 'main' once ameyapat/common-aces-config is merged. + - repository: pipelinesShared + type: git + name: IDDP/MSAL-ObjC-Pipelines + ref: refs/heads/main # Define parallel jobs that run build script for specified targets jobs: @@ -26,118 +32,112 @@ jobs: - template: Pipeline YAMLs/automation-health-tracker.yml@automationHealthTracker parameters: automationPipelineIds: [1956, 1284] -- job: 'Validate_Pull_Request' - strategy: - maxParallel: 2 - matrix: - IOS_LIB: - target: "ios_library" - MAC_LIB: - target: "mac_library" - displayName: Validate Pull Request - pool: - name: 'AcesShared' - demands: - - ImageOverride -equals ACES_VM_SharedPool_Sequoia - timeOutInMinutes: 30 - steps: - - task: CmdLine@2 - displayName: Uninstalling xcpretty v0.4.0 - inputs: - script: | - sudo gem uninstall xcpretty -I --version 0.4.0 - failOnStderr: false - - task: CmdLine@2 - displayName: Installing xcpretty v0.3.0 - inputs: - script: | - sudo gem install xcpretty -N -v 0.3.0 - failOnStderr: true - - script: | - # System Ruby on macOS agents is 2.6, too old for current slather/bundler. - # Install a modern Ruby via Homebrew and prepend it to PATH for all subsequent steps. - export HOMEBREW_NO_AUTO_UPDATE=1 - export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 - brew list ruby >/dev/null 2>&1 || brew install ruby - RUBY_BIN="$(brew --prefix ruby)/bin" - echo "##vso[task.prependpath]$GEM_HOME/bin" - echo "##vso[task.prependpath]$RUBY_BIN" - "$RUBY_BIN/ruby" --version - displayName: 'Install modern Ruby' - - task: CmdLine@2 - displayName: Installing slather - inputs: - script: | - gem install slather bundler -N - failOnStderr: true - - checkout: self - clean: true - submodules: true - fetchDepth: 1 - persistCredentials: false - - task: UsePythonVersion@0 - displayName: 'Use Python 3.x' - inputs: - versionSpec: '3.x' - - task: Bash@3 - displayName: 'Harden Python toolcache permissions (silence Ruby insecure PATH warning)' - inputs: - targetType: 'inline' - script: | - # /opt/hostedtoolcache/Python is created world-writable (mode 0777), - # which makes Ruby (used by xcpretty/slather) emit a warning to stderr - # whenever it loads rbconfig.rb. That warning trips `failOnStderr: true` - # on later steps. Strip the world-write bit from the toolcache tree. - if [ -d /opt/hostedtoolcache ]; then - sudo chmod -R o-w /opt/hostedtoolcache || true - fi - failOnStderr: false - - task: Bash@3 - displayName: 'Select Xcode version' - inputs: - targetType: 'inline' - script: '/bin/bash -c "sudo xcode-select -s /Applications/Xcode_16.4.app"' - - task: Bash@3 - displayName: Removing any lingering codecov files. These can cause issues when the xcode version changes - inputs: - targetType: 'inline' - script: | - find . -name "*.gcda" -print0 | xargs -0 rm - - task: Bash@3 - displayName: Run CPP script - inputs: - targetType: 'inline' - script: | - python3 ./scripts/update_xcode_config_cpp_checks.py - failOnStderr: true - - task: Bash@3 - displayName: Run Build script & check for Errors - inputs: - targetType: 'inline' - script: | - { output=$(./build.py --no-clean --show-build-settings --target $(target) 2>&1 1>&3-) ;} 3>&1 - final_status=$(<./build/status.txt) - echo "FINAL STATUS = ${final_status}" - echo "POSSIBLE ERRORS: ${output}" - - if [ $final_status != "0" ]; then - echo "Build & Testing Failed! \n ${output}" >&2 - fi - failOnStderr: true - - task: Bash@3 - condition: always() - displayName: Cleanup - inputs: - targetType: 'inline' - script: | - rm -rf ./build/status.txt - - task: PublishTestResults@2 - condition: always() - displayName: Publish Test Report - inputs: - testResultsFormat: 'JUnit' - testResultsFiles: '$(Agent.BuildDirectory)/s/build/reports/*' - failTaskOnFailedTests: true - testRunTitle: 'Test Run - $(target)' +- template: Pipeline YAMLs/shared/aces-macos-job.yml@pipelinesShared + parameters: + jobName: Validate_Pull_Request_iOS + displayName: Validate Pull Request (iOS) + timeoutInMinutes: 30 + steps: + - checkout: self + clean: true + submodules: true + fetchDepth: 1 + persistCredentials: false + - task: Bash@3 + displayName: Removing any lingering codecov files. These can cause issues when the xcode version changes + inputs: + targetType: 'inline' + script: | + find . -name "*.gcda" -print0 | xargs -0 rm + - task: Bash@3 + displayName: Run CPP script + inputs: + targetType: 'inline' + script: | + python3 ./scripts/update_xcode_config_cpp_checks.py + failOnStderr: true + - task: Bash@3 + displayName: Run Build script & check for Errors + inputs: + targetType: 'inline' + script: | + { output=$(./build.py --no-clean --show-build-settings --target ios_library 2>&1 1>&3-) ;} 3>&1 + final_status=$(<./build/status.txt) + echo "FINAL STATUS = ${final_status}" + echo "POSSIBLE ERRORS: ${output}" + + if [ $final_status != "0" ]; then + echo "Build & Testing Failed! \n ${output}" >&2 + fi + failOnStderr: true + - task: Bash@3 + condition: always() + displayName: Cleanup + inputs: + targetType: 'inline' + script: | + rm -rf ./build/status.txt + - task: PublishTestResults@2 + condition: always() + displayName: Publish Test Report + inputs: + testResultsFormat: 'JUnit' + testResultsFiles: '$(Agent.BuildDirectory)/s/build/reports/*' + failTaskOnFailedTests: true + testRunTitle: 'Test Run - ios_library' + +- template: Pipeline YAMLs/shared/aces-macos-job.yml@pipelinesShared + parameters: + jobName: Validate_Pull_Request_macOS + displayName: Validate Pull Request (macOS) + timeoutInMinutes: 30 + steps: + - checkout: self + clean: true + submodules: true + fetchDepth: 1 + persistCredentials: false + - task: Bash@3 + displayName: Removing any lingering codecov files. These can cause issues when the xcode version changes + inputs: + targetType: 'inline' + script: | + find . -name "*.gcda" -print0 | xargs -0 rm + - task: Bash@3 + displayName: Run CPP script + inputs: + targetType: 'inline' + script: | + python3 ./scripts/update_xcode_config_cpp_checks.py + failOnStderr: true + - task: Bash@3 + displayName: Run Build script & check for Errors + inputs: + targetType: 'inline' + script: | + { output=$(./build.py --no-clean --show-build-settings --target mac_library 2>&1 1>&3-) ;} 3>&1 + final_status=$(<./build/status.txt) + echo "FINAL STATUS = ${final_status}" + echo "POSSIBLE ERRORS: ${output}" + + if [ $final_status != "0" ]; then + echo "Build & Testing Failed! \n ${output}" >&2 + fi + failOnStderr: true + - task: Bash@3 + condition: always() + displayName: Cleanup + inputs: + targetType: 'inline' + script: | + rm -rf ./build/status.txt + - task: PublishTestResults@2 + condition: always() + displayName: Publish Test Report + inputs: + testResultsFormat: 'JUnit' + testResultsFiles: '$(Agent.BuildDirectory)/s/build/reports/*' + failTaskOnFailedTests: true + testRunTitle: 'Test Run - mac_library' From 25e8a4534275fefd10919138013e60ce39a70c33 Mon Sep 17 00:00:00 2001 From: Juan Arias Date: Tue, 7 Jul 2026 14:27:34 -0700 Subject: [PATCH 13/38] [patch] [bugfix]: Fix flaky automation action-button taps on iOS 26 simulators (#1885) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## PR Checklist (must be completed before review) - [x] All tests pass locally - [x] PR size is <= 500 LOC per PR Size Check policy - [x] PR is independently mergeable (no hidden dependencies) - [ ] Appropriate reviewers are assigned - [ ] PR reviewed by code owner (required if Copilot-generated) - [ ] SME or Senior IC assigned where required ## PR Title Format **Required Format:** `[Keyword1] [Keyword2]: Description` - **Keyword1:** `major`, `minor`, or `patch` (case-insensitive) - **Keyword2:** `feature`, `bugfix`, `engg`, or `tests` (case-insensitive) ## Proposed changes `ADBTokenBindingUITests` failures downstream (e.g. `testADRSDeviceRegistration_havingCAWithTokenBinding_andHavingSSOExtSecStorageDisabled_validateDeviceJoinIsECC`) were traced to `performAction:config:application:` tapping the automation host app's action buttons (e.g. "Acquire Token") without the touch ever actually reaching the button's real UIKit target-action handler. Confirmed via the simulator's unified log (`xcrun simctl ... log show`) — **not** just the XCTest driver's own captured log, which never surfaces output from the app under test: Between requesting a tap and XCTest synthesizing it, XCTest runs its own "make frontmost" dance (`Check for interrupting elements affecting