diff --git a/libs/SalesforceSDKCore/SalesforceSDKCore.xcodeproj/project.pbxproj b/libs/SalesforceSDKCore/SalesforceSDKCore.xcodeproj/project.pbxproj index e560953667..565f941f96 100644 --- a/libs/SalesforceSDKCore/SalesforceSDKCore.xcodeproj/project.pbxproj +++ b/libs/SalesforceSDKCore/SalesforceSDKCore.xcodeproj/project.pbxproj @@ -79,7 +79,7 @@ 4F3139682331C5C7007B3705 /* SFSDKAuthRootController.h in Headers */ = {isa = PBXBuildFile; fileRef = 4F3139672331C5B9007B3705 /* SFSDKAuthRootController.h */; }; 4F3ECD8A2EBBD150005020A6 /* SFOAuthCoordinatorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4F3ECD892EBBD150005020A6 /* SFOAuthCoordinatorTests.m */; }; 4F3ECD8C2EBBD182005020A6 /* SFOAuthInfoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4F3ECD8B2EBBD182005020A6 /* SFOAuthInfoTests.m */; }; - 4FA1B2C32F0E000000000001 /* LoginForAdminTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA1B2C32F0E000000000002 /* LoginForAdminTests.m */; }; + 4FA1B2C32F0E000000000001 /* LoginForAdminTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA1B2C32F0E000000000002 /* LoginForAdminTests.swift */; }; 4F5727E327F27F1A0008CDA4 /* SFSDKPrimingRecordsResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 4F5727DC27F27F1A0008CDA4 /* SFSDKPrimingRecordsResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4F5727E427F27F1A0008CDA4 /* SFSDKPrimingRecordsResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 4F5727E227F27F1A0008CDA4 /* SFSDKPrimingRecordsResponse.m */; }; 4F5A49502E98711600C89DDD /* ScopeParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F5A494F2E98711600C89DDD /* ScopeParser.swift */; }; diff --git a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/UserAccount/SFUserAccountManager.h b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/UserAccount/SFUserAccountManager.h index b22720b347..b328e839a0 100644 --- a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/UserAccount/SFUserAccountManager.h +++ b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/UserAccount/SFUserAccountManager.h @@ -31,6 +31,7 @@ #import @class SFSDKSPConfig; +@class SFLoginViewController; NS_ASSUME_NONNULL_BEGIN @@ -625,6 +626,34 @@ Use this method to stop/clear any authentication which is has already been start statusUpdate:(void(^)(SFSPLoginStatus))statusBlock failure:(void(^)(SFSPLoginError))failureBlock; +/** + Triggers the "Login for Admin" flow for the active login session associated with the given + login view controller. Forces browser-based (advanced) authentication via ASWebAuthenticationSession, + regardless of org configuration, to support phishing-resistant MFA. + + This performs the same action as selecting "Login for Admin" from the login screen's settings + menu. Apps that hide the settings menu (e.g. by setting `showSettingsIcon = NO`) can call this + to expose the feature from their own UI. + + To obtain a reference to the currently presented `SFLoginViewController`, either: + + 1. Capture it from `SFSDKLoginViewControllerConfig.loginViewControllerCreationBlock`, which + the SDK invokes each time it constructs the login view: + + UserAccountManager.shared.loginViewControllerConfig.loginViewControllerCreationBlock = { [weak self] in + let vc = SalesforceLoginViewController() + self?.currentLoginVC = vc // retain weakly for later + return vc + } + + 2. Walk the key window's view hierarchy and locate the `SFLoginViewController` presented + inside the SDK's navigation controller. + + @param loginViewController The login view controller whose scene's active auth session should + switch to "Login for Admin". Its window's scene is used to locate the session. + */ +- (void)loginViewControllerDidSelectLoginForAdmin:(SFLoginViewController *)loginViewController NS_SWIFT_NAME(loginViewControllerDidSelectLoginForAdmin(_:)) SFSDK_DEPRECATED(13.2.1, 14.0, "Will be removed in 14.0 when a permanent solution is provided."); + @end NS_ASSUME_NONNULL_END diff --git a/libs/SalesforceSDKCore/SalesforceSDKCoreTests/LoginForAdminTests.swift b/libs/SalesforceSDKCore/SalesforceSDKCoreTests/LoginForAdminTests.swift index 5459f22dbd..60497edcae 100644 --- a/libs/SalesforceSDKCore/SalesforceSDKCoreTests/LoginForAdminTests.swift +++ b/libs/SalesforceSDKCore/SalesforceSDKCoreTests/LoginForAdminTests.swift @@ -482,6 +482,40 @@ class LoginForAdminTests: XCTestCase { window.rootViewController = nil } + @available(*, deprecated, message: "Exercises deprecated public API") + func testGivenAuthSession_whenPublicLoginForAdminCalled_thenLoginAsAdminSet() { + let uam = UserAccountManager.shared + + guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else { + XCTFail("Test requires a UIWindowScene from the running test app") + return + } + let sceneId = windowScene.session.persistentIdentifier + + let request = makeAuthRequest() + request.loginAsAdmin = false + let session = SFSDKAuthSession(request, credentials: nil) + uam.authSessions[sceneId as NSString] = session + + XCTAssertFalse(session.oauthRequest.loginAsAdmin, "loginAsAdmin should be false before invoking public API") + + let loginVC = SalesforceLoginViewController() + let window = windowScene.windows.first ?? UIWindow(windowScene: windowScene) + window.rootViewController = loginVC + window.makeKeyAndVisible() + loginVC.loadViewIfNeeded() + + // Invoke directly via the public Swift binding (not performSelector) to confirm the + // method is exposed on UserAccountManager for SDK consumers. + uam.loginViewControllerDidSelectLoginForAdmin(loginVC) + + XCTAssertTrue(session.oauthRequest.loginAsAdmin, + "loginAsAdmin should be true after calling the public loginViewControllerDidSelectLoginForAdmin") + + uam.authSessions.removeObject(sceneId as NSString) + window.rootViewController = nil + } + // MARK: - Private Helpers private func makeAuthRequest() -> SFSDKAuthRequest {