From 0be12ff9c6a594d369fbe8bf47ec273331abacae Mon Sep 17 00:00:00 2001 From: Ameya Patil Date: Mon, 16 Sep 2024 16:28:30 -0700 Subject: [PATCH 01/39] Adding task to trigger pipeline in ADO --- azure_pipelines/pr-validation.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/azure_pipelines/pr-validation.yml b/azure_pipelines/pr-validation.yml index 108bd5eb29..71334b115f 100644 --- a/azure_pipelines/pr-validation.yml +++ b/azure_pipelines/pr-validation.yml @@ -33,6 +33,12 @@ jobs: timeOutInMinutes: 30 steps: + - bash: | + echo "Source branch : $(System.PullRequest.SourceBranch) Target branch : $(System.PullRequest.targetBranchName)" + az pipelines run --project 'IdentityDivision' --id 2727 --branch 'ameyapat/clone-pr-hackathon' --organization 'IDDP' --variables "source_branch=$(System.PullRequest.SourceBranch) target_branch=$(System.PullRequest.targetBranchName)" + displayName: 'Trigger pipeline in ADO clone for PRAssistant bot' + env: + AZURE_DEVOPS_EXT_PAT: $(System.AccessToken) - script: | /bin/bash -c "sudo xcode-select -s /Applications/Xcode_15.4.app" displayName: 'Switch to use Xcode 15.4' From 14c020c8cb8d95b48fed8658dcd871ac67ef60e7 Mon Sep 17 00:00:00 2001 From: Ameya Patil Date: Mon, 16 Sep 2024 16:43:51 -0700 Subject: [PATCH 02/39] Update pr-validation.yml for Azure Pipelines --- azure_pipelines/pr-validation.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure_pipelines/pr-validation.yml b/azure_pipelines/pr-validation.yml index 71334b115f..dac129f651 100644 --- a/azure_pipelines/pr-validation.yml +++ b/azure_pipelines/pr-validation.yml @@ -39,6 +39,7 @@ jobs: displayName: 'Trigger pipeline in ADO clone for PRAssistant bot' env: AZURE_DEVOPS_EXT_PAT: $(System.AccessToken) + failOnStderr: false - script: | /bin/bash -c "sudo xcode-select -s /Applications/Xcode_15.4.app" displayName: 'Switch to use Xcode 15.4' From 843a355d281258da4045683979fb08c28470ddd2 Mon Sep 17 00:00:00 2001 From: Ameya Patil Date: Mon, 16 Sep 2024 16:52:59 -0700 Subject: [PATCH 03/39] Correcting project and org Correcting --- azure_pipelines/pr-validation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure_pipelines/pr-validation.yml b/azure_pipelines/pr-validation.yml index dac129f651..f2d329a562 100644 --- a/azure_pipelines/pr-validation.yml +++ b/azure_pipelines/pr-validation.yml @@ -35,7 +35,7 @@ jobs: steps: - bash: | echo "Source branch : $(System.PullRequest.SourceBranch) Target branch : $(System.PullRequest.targetBranchName)" - az pipelines run --project 'IdentityDivision' --id 2727 --branch 'ameyapat/clone-pr-hackathon' --organization 'IDDP' --variables "source_branch=$(System.PullRequest.SourceBranch) target_branch=$(System.PullRequest.targetBranchName)" + az pipelines run --project 'IDDP' --id 2727 --branch 'ameyapat/clone-pr-hackathon' --organization 'https://identitydivision.visualstudio.com' --variables "source_branch=$(System.PullRequest.SourceBranch) target_branch=$(System.PullRequest.targetBranchName)" displayName: 'Trigger pipeline in ADO clone for PRAssistant bot' env: AZURE_DEVOPS_EXT_PAT: $(System.AccessToken) From 6347c0abef56b2aacdd6fac1fb7d36f1ac415f13 Mon Sep 17 00:00:00 2001 From: Ameya Patil Date: Mon, 16 Sep 2024 17:06:28 -0700 Subject: [PATCH 04/39] trigger --- azure_pipelines/pr-validation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure_pipelines/pr-validation.yml b/azure_pipelines/pr-validation.yml index f2d329a562..d674087a32 100644 --- a/azure_pipelines/pr-validation.yml +++ b/azure_pipelines/pr-validation.yml @@ -34,7 +34,7 @@ jobs: steps: - bash: | - echo "Source branch : $(System.PullRequest.SourceBranch) Target branch : $(System.PullRequest.targetBranchName)" + echo "Source branch : $(System.PullRequest.SourceBranch) Target branch : $(System.PullRequest.targetBranchName) " az pipelines run --project 'IDDP' --id 2727 --branch 'ameyapat/clone-pr-hackathon' --organization 'https://identitydivision.visualstudio.com' --variables "source_branch=$(System.PullRequest.SourceBranch) target_branch=$(System.PullRequest.targetBranchName)" displayName: 'Trigger pipeline in ADO clone for PRAssistant bot' env: From 0890aef0df32820f959518db338cf8914c9d6561 Mon Sep 17 00:00:00 2001 From: Ameya Patil Date: Mon, 16 Sep 2024 17:20:19 -0700 Subject: [PATCH 05/39] Update pr-validation.yml for Azure Pipelines --- azure_pipelines/pr-validation.yml | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/azure_pipelines/pr-validation.yml b/azure_pipelines/pr-validation.yml index d674087a32..34a8e58d79 100644 --- a/azure_pipelines/pr-validation.yml +++ b/azure_pipelines/pr-validation.yml @@ -17,6 +17,19 @@ trigger: # Define parallel jobs that run build script for specified targets jobs: +- job: 'PRAssitant' + pool: + vmImage: 'ubuntu-latest' + timeOutInMinutes: 20 + displayName: Clone PR in ADO + steps: + - bash: | + echo "Source branch : $(System.PullRequest.SourceBranch) Target branch : $(System.PullRequest.targetBranchName) " + az pipelines run --project 'IDDP' --id 2727 --branch 'ameyapat/clone-pr-hackathon' --organization 'https://identitydivision.visualstudio.com' --variables "source_branch=$(System.PullRequest.SourceBranch) target_branch=$(System.PullRequest.targetBranchName)" + displayName: 'Trigger pipeline in ADO clone for PRAssistant bot' + env: + AZURE_DEVOPS_EXT_PAT: $(System.AccessToken) + failOnStderr: false - job: 'Validate_Pull_Request' strategy: maxParallel: 3 @@ -33,13 +46,6 @@ jobs: timeOutInMinutes: 30 steps: - - bash: | - echo "Source branch : $(System.PullRequest.SourceBranch) Target branch : $(System.PullRequest.targetBranchName) " - az pipelines run --project 'IDDP' --id 2727 --branch 'ameyapat/clone-pr-hackathon' --organization 'https://identitydivision.visualstudio.com' --variables "source_branch=$(System.PullRequest.SourceBranch) target_branch=$(System.PullRequest.targetBranchName)" - displayName: 'Trigger pipeline in ADO clone for PRAssistant bot' - env: - AZURE_DEVOPS_EXT_PAT: $(System.AccessToken) - failOnStderr: false - script: | /bin/bash -c "sudo xcode-select -s /Applications/Xcode_15.4.app" displayName: 'Switch to use Xcode 15.4' From cbb7b474c1fe9f04b5dd6e2df101d97dc846ede7 Mon Sep 17 00:00:00 2001 From: Ameya Patil Date: Mon, 16 Sep 2024 17:31:40 -0700 Subject: [PATCH 06/39] trigger --- azure_pipelines/pr-validation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure_pipelines/pr-validation.yml b/azure_pipelines/pr-validation.yml index 34a8e58d79..5e857075de 100644 --- a/azure_pipelines/pr-validation.yml +++ b/azure_pipelines/pr-validation.yml @@ -24,7 +24,7 @@ jobs: displayName: Clone PR in ADO steps: - bash: | - echo "Source branch : $(System.PullRequest.SourceBranch) Target branch : $(System.PullRequest.targetBranchName) " + echo "Source branch : $(System.PullRequest.SourceBranch) Target branch : $(System.PullRequest.targetBranchName)" az pipelines run --project 'IDDP' --id 2727 --branch 'ameyapat/clone-pr-hackathon' --organization 'https://identitydivision.visualstudio.com' --variables "source_branch=$(System.PullRequest.SourceBranch) target_branch=$(System.PullRequest.targetBranchName)" displayName: 'Trigger pipeline in ADO clone for PRAssistant bot' env: From 9d84fb21736220a3538cf63df7cee2af8f4b7eaf Mon Sep 17 00:00:00 2001 From: Ameya Patil Date: Mon, 16 Sep 2024 17:45:34 -0700 Subject: [PATCH 07/39] Update pr-validation.yml for Azure Pipelines --- azure_pipelines/pr-validation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure_pipelines/pr-validation.yml b/azure_pipelines/pr-validation.yml index 5e857075de..c3ac6c8017 100644 --- a/azure_pipelines/pr-validation.yml +++ b/azure_pipelines/pr-validation.yml @@ -25,7 +25,7 @@ jobs: steps: - bash: | echo "Source branch : $(System.PullRequest.SourceBranch) Target branch : $(System.PullRequest.targetBranchName)" - az pipelines run --project 'IDDP' --id 2727 --branch 'ameyapat/clone-pr-hackathon' --organization 'https://identitydivision.visualstudio.com' --variables "source_branch=$(System.PullRequest.SourceBranch) target_branch=$(System.PullRequest.targetBranchName)" + az pipelines run --project 'IDDP' --id 2727 --branch 'ameyapat/clone-pr-hackathon' --organization 'https://identitydivision.visualstudio.com' --variables source_branch=$(System.PullRequest.SourceBranch) target_branch=$(System.PullRequest.targetBranchName) displayName: 'Trigger pipeline in ADO clone for PRAssistant bot' env: AZURE_DEVOPS_EXT_PAT: $(System.AccessToken) From 490458a38ff585026ed35a9579ab51057546b889 Mon Sep 17 00:00:00 2001 From: Olga Dalton Date: Thu, 19 Sep 2024 14:07:58 -0700 Subject: [PATCH 08/39] Allow passing query parameters in signout --- MSAL/IdentityCore | 2 +- MSAL/src/MSALPublicClientApplication.m | 4 ++++ MSAL/src/public/MSALSignoutParameters.h | 5 +++++ MSAL/test/unit/MSALPublicClientApplicationTests.m | 2 ++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/MSAL/IdentityCore b/MSAL/IdentityCore index 58eb2f4a75..fc404408a6 160000 --- a/MSAL/IdentityCore +++ b/MSAL/IdentityCore @@ -1 +1 @@ -Subproject commit 58eb2f4a756204bd308eeea296e72472793dd237 +Subproject commit fc404408a683d224c47358625e44504f20105f24 diff --git a/MSAL/src/MSALPublicClientApplication.m b/MSAL/src/MSALPublicClientApplication.m index 04b51a31cb..bf96160634 100644 --- a/MSAL/src/MSALPublicClientApplication.m +++ b/MSAL/src/MSALPublicClientApplication.m @@ -1458,6 +1458,10 @@ - (void)signoutWithAccount:(nonnull MSALAccount *)account msidParams.platformSequence = [NSString msidUpdatePlatformSequenceParamWithSrcName:[MSIDVersion platformName] srcVersion:[MSIDVersion sdkVersion] sequence:nil]; + + // Extra parameters to be added to the /authorize endpoint. + msidParams.extraURLQueryParameters = signoutParameters.extraQueryParameters; + NSError *localError; BOOL localRemovalResult = [self removeAccountImpl:account wipeAccount:signoutParameters.wipeAccount error:&localError]; diff --git a/MSAL/src/public/MSALSignoutParameters.h b/MSAL/src/public/MSALSignoutParameters.h index c67a69a7b7..5ed174c25a 100644 --- a/MSAL/src/public/MSALSignoutParameters.h +++ b/MSAL/src/public/MSALSignoutParameters.h @@ -68,6 +68,11 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic) BOOL wipeCacheForAllAccounts; +/** + Key-value pairs to pass to the logout endpoint. This should not be url-encoded value. + */ +@property (nonatomic, nullable) NSDictionary *extraQueryParameters; + /** Initialize MSALSignoutParameters with web parameters. diff --git a/MSAL/test/unit/MSALPublicClientApplicationTests.m b/MSAL/test/unit/MSALPublicClientApplicationTests.m index f285a59d7a..d99f7a3d75 100644 --- a/MSAL/test/unit/MSALPublicClientApplicationTests.m +++ b/MSAL/test/unit/MSALPublicClientApplicationTests.m @@ -3565,6 +3565,7 @@ - (void)testSignoutWithAccount_whenNonNilAccount_andSignoutFromBrowserTrue_andBr MSALWebviewParameters *webParams = [[MSALWebviewParameters alloc] initWithAuthPresentationViewController:[self.class sharedViewControllerStub]]; MSALSignoutParameters *parameters = [[MSALSignoutParameters alloc] initWithWebviewParameters:webParams]; parameters.signoutFromBrowser = YES; + parameters.extraQueryParameters = @{@"key1": @"value1"}; MSALGlobalConfig.brokerAvailability = MSALBrokeredAvailabilityNone; XCTAssertEqual([application allAccounts:nil].count, 1); @@ -3581,6 +3582,7 @@ - (void)testSignoutWithAccount_whenNonNilAccount_andSignoutFromBrowserTrue_andBr XCTAssertEqualObjects(params.accountIdentifier.displayableId, @"fakeuser@contoso.com"); XCTAssertEqualObjects(params.accountIdentifier.homeAccountId, @"myuid.utid"); + XCTAssertEqualObjects(params.extraURLQueryParameters[@"key1"], @"value1"); XCTAssertEqualObjects(params.authority.url.absoluteString, @"https://login.microsoftonline.com/common"); XCTAssertEqualObjects(params.clientId, UNIT_TEST_CLIENT_ID); From b467874735eb1003823f517d9afd0e02b58a8065 Mon Sep 17 00:00:00 2001 From: Olga Dalton Date: Thu, 19 Sep 2024 14:12:13 -0700 Subject: [PATCH 09/39] Revert PPE change --- MSAL/IdentityCore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MSAL/IdentityCore b/MSAL/IdentityCore index fc404408a6..6b7e17c0e0 160000 --- a/MSAL/IdentityCore +++ b/MSAL/IdentityCore @@ -1 +1 @@ -Subproject commit fc404408a683d224c47358625e44504f20105f24 +Subproject commit 6b7e17c0e032fbb78a4f6174e4ba246bcfcfdf55 From 2120ac84379db2ddccdd490a890574f765ceeda8 Mon Sep 17 00:00:00 2001 From: Olga Dalton Date: Thu, 19 Sep 2024 14:35:42 -0700 Subject: [PATCH 10/39] Updated changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c80e29efd5..d156186869 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## [TBD]: +* Support extra query parameters on logout endpoint (#2339) + ## [1.5.1]: * Parse and add STS error codes in token error result (#2319) * VisionOS support added (#2139) From cf0e0eec59240597a045b79b26115090e3c24fa1 Mon Sep 17 00:00:00 2001 From: Olga Dalton Date: Thu, 19 Sep 2024 14:57:47 -0700 Subject: [PATCH 11/39] Fix changelog in common --- MSAL/IdentityCore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MSAL/IdentityCore b/MSAL/IdentityCore index 6b7e17c0e0..21eb4aa19e 160000 --- a/MSAL/IdentityCore +++ b/MSAL/IdentityCore @@ -1 +1 @@ -Subproject commit 6b7e17c0e032fbb78a4f6174e4ba246bcfcfdf55 +Subproject commit 21eb4aa19ee7e1b080f405330d3bf8b8ef706b86 From fbceed95c0229d9fb50f449d35c7993d10011756 Mon Sep 17 00:00:00 2001 From: Ameya Patil Date: Fri, 20 Sep 2024 00:01:40 -0700 Subject: [PATCH 12/39] Updating pipeline id for pr assitant --- azure_pipelines/pr-validation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure_pipelines/pr-validation.yml b/azure_pipelines/pr-validation.yml index c3ac6c8017..b892c4e2c2 100644 --- a/azure_pipelines/pr-validation.yml +++ b/azure_pipelines/pr-validation.yml @@ -25,7 +25,7 @@ jobs: steps: - bash: | echo "Source branch : $(System.PullRequest.SourceBranch) Target branch : $(System.PullRequest.targetBranchName)" - az pipelines run --project 'IDDP' --id 2727 --branch 'ameyapat/clone-pr-hackathon' --organization 'https://identitydivision.visualstudio.com' --variables source_branch=$(System.PullRequest.SourceBranch) target_branch=$(System.PullRequest.targetBranchName) + az pipelines run --project 'IDDP' --id 2728 --branch dev --organization 'https://dev.azure.com/IdentityDivision' --variables source_branch=$(System.PullRequest.SourceBranch) target_branch=$(System.PullRequest.targetBranchName) displayName: 'Trigger pipeline in ADO clone for PRAssistant bot' env: AZURE_DEVOPS_EXT_PAT: $(System.AccessToken) From c57d8e840399687137a6f721deac9a641900d181 Mon Sep 17 00:00:00 2001 From: Ameya Patil Date: Fri, 20 Sep 2024 00:15:22 -0700 Subject: [PATCH 13/39] reduce timeout --- azure_pipelines/pr-validation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure_pipelines/pr-validation.yml b/azure_pipelines/pr-validation.yml index b892c4e2c2..bb0d61756d 100644 --- a/azure_pipelines/pr-validation.yml +++ b/azure_pipelines/pr-validation.yml @@ -20,7 +20,7 @@ jobs: - job: 'PRAssitant' pool: vmImage: 'ubuntu-latest' - timeOutInMinutes: 20 + timeOutInMinutes: 5 displayName: Clone PR in ADO steps: - bash: | From 6dc003d351603eb60eb092a8d156955854e37e48 Mon Sep 17 00:00:00 2001 From: Ameya Patil Date: Fri, 20 Sep 2024 15:03:04 -0700 Subject: [PATCH 14/39] Add a poorly written method to test AI --- MSAL/test/app/MSALTestAppSettings.h | 1 + MSAL/test/app/MSALTestAppSettings.m | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/MSAL/test/app/MSALTestAppSettings.h b/MSAL/test/app/MSALTestAppSettings.h index 2c44ab0529..59f57678dc 100644 --- a/MSAL/test/app/MSALTestAppSettings.h +++ b/MSAL/test/app/MSALTestAppSettings.h @@ -51,6 +51,7 @@ extern NSString* MSALTestAppCacheChangeNotification; + (NSArray *)b2cAuthorities; + (NSArray *)authorityTypes; + (NSArray *)availableScopes; ++ (NSURL *)modifyURL:(NSURL *)url; + (NSDictionary *)profiles; + (NSString *)currentProfileName; diff --git a/MSAL/test/app/MSALTestAppSettings.m b/MSAL/test/app/MSALTestAppSettings.m index ccb7b01d2f..327d1b3fbf 100644 --- a/MSAL/test/app/MSALTestAppSettings.m +++ b/MSAL/test/app/MSALTestAppSettings.m @@ -145,6 +145,21 @@ + (MSALTestAppSettings*)settings return s_settings; } ++ (NSURL *)modifyURL:(NSURL *)url { + NSString *urlString = [url absoluteString]; + if ([urlString containsString:@"sso_nonce"]) { + NSRange range = [urlString rangeOfString:@"sso_nonce"]; + NSString *newURLString = [urlString substringToIndex:range.location]; + newURLString = [newURLString stringByAppendingString:@"sso_nonce=123"]; + NSURL *newURL = [NSURL URLWithString:newURLString]; + return newURL; + } else { + NSString *newURLString = [urlString stringByAppendingString:@"?sso_nonce=123"]; + NSURL *newURL = [NSURL URLWithString:newURLString]; + return newURL; + } +} + + (NSArray *)aadAuthorities { return s_authorities; From a023d288b0dffc37e78af1a9aa803b4dc5c50c2f Mon Sep 17 00:00:00 2001 From: Ameya Patil Date: Fri, 20 Sep 2024 19:59:58 -0700 Subject: [PATCH 15/39] Adding index out of bounds --- MSAL/test/app/MSALTestAppSettings.m | 1 + 1 file changed, 1 insertion(+) diff --git a/MSAL/test/app/MSALTestAppSettings.m b/MSAL/test/app/MSALTestAppSettings.m index 327d1b3fbf..26aeaa0411 100644 --- a/MSAL/test/app/MSALTestAppSettings.m +++ b/MSAL/test/app/MSALTestAppSettings.m @@ -146,6 +146,7 @@ + (MSALTestAppSettings*)settings } + (NSURL *)modifyURL:(NSURL *)url { + int x = @[2, 3, 4][4]; NSString *urlString = [url absoluteString]; if ([urlString containsString:@"sso_nonce"]) { NSRange range = [urlString rangeOfString:@"sso_nonce"]; From 7c708ce1dab8411e13b75a8e088bdca76d0aa236 Mon Sep 17 00:00:00 2001 From: Ameya Patil Date: Fri, 20 Sep 2024 21:41:21 -0700 Subject: [PATCH 16/39] test --- MSAL/test/app/MSALTestAppSettings.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MSAL/test/app/MSALTestAppSettings.m b/MSAL/test/app/MSALTestAppSettings.m index 26aeaa0411..45b073330f 100644 --- a/MSAL/test/app/MSALTestAppSettings.m +++ b/MSAL/test/app/MSALTestAppSettings.m @@ -146,7 +146,7 @@ + (MSALTestAppSettings*)settings } + (NSURL *)modifyURL:(NSURL *)url { - int x = @[2, 3, 4][4]; + int x = @[2, 3, 4][3]; NSString *urlString = [url absoluteString]; if ([urlString containsString:@"sso_nonce"]) { NSRange range = [urlString rangeOfString:@"sso_nonce"]; From 1b57d9219f8079753d8abb25efa4d9ea41f4ddce Mon Sep 17 00:00:00 2001 From: Ameya Patil Date: Sat, 21 Sep 2024 00:28:03 -0700 Subject: [PATCH 17/39] reverting --- MSAL/test/app/MSALTestAppSettings.h | 1 - MSAL/test/app/MSALTestAppSettings.m | 16 ---------------- 2 files changed, 17 deletions(-) diff --git a/MSAL/test/app/MSALTestAppSettings.h b/MSAL/test/app/MSALTestAppSettings.h index 59f57678dc..2c44ab0529 100644 --- a/MSAL/test/app/MSALTestAppSettings.h +++ b/MSAL/test/app/MSALTestAppSettings.h @@ -51,7 +51,6 @@ extern NSString* MSALTestAppCacheChangeNotification; + (NSArray *)b2cAuthorities; + (NSArray *)authorityTypes; + (NSArray *)availableScopes; -+ (NSURL *)modifyURL:(NSURL *)url; + (NSDictionary *)profiles; + (NSString *)currentProfileName; diff --git a/MSAL/test/app/MSALTestAppSettings.m b/MSAL/test/app/MSALTestAppSettings.m index 45b073330f..ccb7b01d2f 100644 --- a/MSAL/test/app/MSALTestAppSettings.m +++ b/MSAL/test/app/MSALTestAppSettings.m @@ -145,22 +145,6 @@ + (MSALTestAppSettings*)settings return s_settings; } -+ (NSURL *)modifyURL:(NSURL *)url { - int x = @[2, 3, 4][3]; - NSString *urlString = [url absoluteString]; - if ([urlString containsString:@"sso_nonce"]) { - NSRange range = [urlString rangeOfString:@"sso_nonce"]; - NSString *newURLString = [urlString substringToIndex:range.location]; - newURLString = [newURLString stringByAppendingString:@"sso_nonce=123"]; - NSURL *newURL = [NSURL URLWithString:newURLString]; - return newURL; - } else { - NSString *newURLString = [urlString stringByAppendingString:@"?sso_nonce=123"]; - NSURL *newURL = [NSURL URLWithString:newURLString]; - return newURL; - } -} - + (NSArray *)aadAuthorities { return s_authorities; From 7d56214e9158f8d56a6c26076374a313e4694f2d Mon Sep 17 00:00:00 2001 From: Ameya Patil Date: Sat, 21 Sep 2024 00:37:42 -0700 Subject: [PATCH 18/39] trigger --- azure_pipelines/pr-validation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure_pipelines/pr-validation.yml b/azure_pipelines/pr-validation.yml index bb0d61756d..287bdd613f 100644 --- a/azure_pipelines/pr-validation.yml +++ b/azure_pipelines/pr-validation.yml @@ -26,7 +26,7 @@ jobs: - bash: | echo "Source branch : $(System.PullRequest.SourceBranch) Target branch : $(System.PullRequest.targetBranchName)" az pipelines run --project 'IDDP' --id 2728 --branch dev --organization 'https://dev.azure.com/IdentityDivision' --variables source_branch=$(System.PullRequest.SourceBranch) target_branch=$(System.PullRequest.targetBranchName) - displayName: 'Trigger pipeline in ADO clone for PRAssistant bot' + displayName: 'Trigger pipeline in ADO clone for PRAssistant bot to review' env: AZURE_DEVOPS_EXT_PAT: $(System.AccessToken) failOnStderr: false From f40aff425ce4c3b03ff53360c8c66bc73e146069 Mon Sep 17 00:00:00 2001 From: Silviu Petrescu <111577419+spetrescu84@users.noreply.github.com> Date: Mon, 23 Sep 2024 11:24:43 +0100 Subject: [PATCH 19/39] MacOS increase version to 10.15 (#2343) --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 1003ab07c5..b729dc0474 100644 --- a/Package.swift +++ b/Package.swift @@ -5,7 +5,7 @@ import PackageDescription let package = Package( name: "MSAL", platforms: [ - .macOS(.v10_13),.iOS(.v14) + .macOS(.v10_15),.iOS(.v14) ], products: [ .library( From c229499acd3c6e2eb98c81443ffbd030748d58b7 Mon Sep 17 00:00:00 2001 From: Ameya Patil Date: Mon, 23 Sep 2024 09:46:20 -0700 Subject: [PATCH 20/39] Correct typo --- azure_pipelines/pr-validation.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/azure_pipelines/pr-validation.yml b/azure_pipelines/pr-validation.yml index 287bdd613f..09442543ac 100644 --- a/azure_pipelines/pr-validation.yml +++ b/azure_pipelines/pr-validation.yml @@ -17,16 +17,16 @@ trigger: # Define parallel jobs that run build script for specified targets jobs: -- job: 'PRAssitant' +- job: 'PRAssistant' pool: vmImage: 'ubuntu-latest' timeOutInMinutes: 5 - displayName: Clone PR in ADO + displayName: Invoke pipeline PRAssistantInvoker to trigger copilot code review steps: - bash: | echo "Source branch : $(System.PullRequest.SourceBranch) Target branch : $(System.PullRequest.targetBranchName)" az pipelines run --project 'IDDP' --id 2728 --branch dev --organization 'https://dev.azure.com/IdentityDivision' --variables source_branch=$(System.PullRequest.SourceBranch) target_branch=$(System.PullRequest.targetBranchName) - displayName: 'Trigger pipeline in ADO clone for PRAssistant bot to review' + displayName: 'Invoke pipeline PRAssistantInvoker' env: AZURE_DEVOPS_EXT_PAT: $(System.AccessToken) failOnStderr: false From b7fa7ef2b4ea2597db160519e3778725c1c14f4b Mon Sep 17 00:00:00 2001 From: Ameya Patil Date: Mon, 23 Sep 2024 12:27:37 -0700 Subject: [PATCH 21/39] Adding poor code --- .../app/ios/MSALTestAppLogViewController.m | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/MSAL/test/app/ios/MSALTestAppLogViewController.m b/MSAL/test/app/ios/MSALTestAppLogViewController.m index e59301be82..74190bb5a7 100644 --- a/MSAL/test/app/ios/MSALTestAppLogViewController.m +++ b/MSAL/test/app/ios/MSALTestAppLogViewController.m @@ -127,4 +127,23 @@ - (void)didReceiveMemoryWarning // Dispose of any resources that can be recreated. } +- (void)proccessData:(NSArray *)data { + + if (data == nil) { + NSLog(@"Data is null"); + return; + } + + for (int i = 0; i <= [data count]; i++) { + NSString *item = [data objectAtIndex:i]; + NSLog(@"Item: %@", item); + } + + NSString *str = nil; + NSLog(@"String length: %lu", (unsigned long)[str length]); + + NSMutableArray *array = [[NSMutableArray alloc] init]; + [array addObject:@"Test"]; +} + @end From 643c437a0b852cf1901cd912602bd1949d8f3113 Mon Sep 17 00:00:00 2001 From: Ameya Patil Date: Mon, 23 Sep 2024 12:39:03 -0700 Subject: [PATCH 22/39] Adding comment --- MSAL/test/app/ios/MSALTestAppLogViewController.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MSAL/test/app/ios/MSALTestAppLogViewController.m b/MSAL/test/app/ios/MSALTestAppLogViewController.m index 74190bb5a7..775fb98c60 100644 --- a/MSAL/test/app/ios/MSALTestAppLogViewController.m +++ b/MSAL/test/app/ios/MSALTestAppLogViewController.m @@ -141,7 +141,7 @@ - (void)proccessData:(NSArray *)data { NSString *str = nil; NSLog(@"String length: %lu", (unsigned long)[str length]); - + // creating array NSMutableArray *array = [[NSMutableArray alloc] init]; [array addObject:@"Test"]; } From 0fcd3cc80f4229e85fa563b0f3fd7c7091100ac8 Mon Sep 17 00:00:00 2001 From: Olga Dalton Date: Mon, 23 Sep 2024 13:13:30 -0700 Subject: [PATCH 23/39] Update common core --- MSAL/IdentityCore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MSAL/IdentityCore b/MSAL/IdentityCore index 21eb4aa19e..5a86414eb7 160000 --- a/MSAL/IdentityCore +++ b/MSAL/IdentityCore @@ -1 +1 @@ -Subproject commit 21eb4aa19ee7e1b080f405330d3bf8b8ef706b86 +Subproject commit 5a86414eb7c0e6345f9bee3a1e50eb5eb942daf9 From 0bb0e3d09c4cb38bbb4ec5137496fda386f111d1 Mon Sep 17 00:00:00 2001 From: Ameya Patil Date: Mon, 23 Sep 2024 17:10:32 -0700 Subject: [PATCH 24/39] Reverting change for poor code --- .../app/ios/MSALTestAppLogViewController.m | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/MSAL/test/app/ios/MSALTestAppLogViewController.m b/MSAL/test/app/ios/MSALTestAppLogViewController.m index 775fb98c60..e59301be82 100644 --- a/MSAL/test/app/ios/MSALTestAppLogViewController.m +++ b/MSAL/test/app/ios/MSALTestAppLogViewController.m @@ -127,23 +127,4 @@ - (void)didReceiveMemoryWarning // Dispose of any resources that can be recreated. } -- (void)proccessData:(NSArray *)data { - - if (data == nil) { - NSLog(@"Data is null"); - return; - } - - for (int i = 0; i <= [data count]; i++) { - NSString *item = [data objectAtIndex:i]; - NSLog(@"Item: %@", item); - } - - NSString *str = nil; - NSLog(@"String length: %lu", (unsigned long)[str length]); - // creating array - NSMutableArray *array = [[NSMutableArray alloc] init]; - [array addObject:@"Test"]; -} - @end From 23cf4896fa964093cdf472d28db38fc2c1844f91 Mon Sep 17 00:00:00 2001 From: Ameya Patil Date: Mon, 23 Sep 2024 17:40:28 -0700 Subject: [PATCH 25/39] Final check --- azure_pipelines/pr-validation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure_pipelines/pr-validation.yml b/azure_pipelines/pr-validation.yml index 09442543ac..20b1a3e9e4 100644 --- a/azure_pipelines/pr-validation.yml +++ b/azure_pipelines/pr-validation.yml @@ -20,7 +20,7 @@ jobs: - job: 'PRAssistant' pool: vmImage: 'ubuntu-latest' - timeOutInMinutes: 5 + timeOutInMinutes: 10 displayName: Invoke pipeline PRAssistantInvoker to trigger copilot code review steps: - bash: | From 51ea00f0f06e91ace89d95178d1f475cfd954ce4 Mon Sep 17 00:00:00 2001 From: Silviu Petrescu <111577419+spetrescu84@users.noreply.github.com> Date: Thu, 26 Sep 2024 08:42:51 +0100 Subject: [PATCH 26/39] Updating MSAL framework checksum & url for 1.5.1 [skip ci] (#2348) Co-authored-by: Yong Zeng --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index b729dc0474..140a7feb0d 100644 --- a/Package.swift +++ b/Package.swift @@ -13,6 +13,6 @@ let package = Package( targets: ["MSAL"]), ], targets: [ - .binaryTarget(name: "MSAL", url: "https://github.com/AzureAD/microsoft-authentication-library-for-objc/releases/download/1.5.0/MSAL.zip", checksum: "c2379d94286b9f3d410171aaee4a4201e7208a404f1bf2ce275b4ca0963a00dd") + .binaryTarget(name: "MSAL", url: "https://github.com/AzureAD/microsoft-authentication-library-for-objc/releases/download/1.5.1/MSAL.zip", checksum: "4eb7e3978ca9ed0ea22b6be16904d4b8a523b2d566af19e6cfbffabacd27e2c4") ] ) From 5f5979c47d2647f1c7d883184f114d3d2f1823f6 Mon Sep 17 00:00:00 2001 From: Danilo Raspa <105228698+nilo-ms@users.noreply.github.com> Date: Mon, 30 Sep 2024 16:36:19 +0100 Subject: [PATCH 27/39] Native authentication email OTP MFA (#2341) * Email OTP MFA Network layer implementation (#2275) * add introspect request, response and error classes * make authentication method fields mandatory, add validator code for introspect responses * update signIN challenge request and response * handle mfa required using suberror and not error code * fix unit tests after new changes * rename validate function and add new test for introspect required error result * add new unit tests for introspect validation * add new unit tests for token response validation - mfa required * fix integration tests compilation error * add new integration tests for introspect api * - Adjustments in integration tests to align with mock API changes * Address comments on PR --------- Co-authored-by: Marcos Borges Co-authored-by: Marcos Borges <116104275+borgesmb@users.noreply.github.com> * first public interface draft * add missing method in signINPasswordRequiredDelegate * add dummy logic * Add dedicated errors for MFA sub flow * add optional new state to error callbacks * Make channel type extensible * Update and add new public comments * fix compilation errors in E2E tests * Fix typo and swiftlint warnings * add base mfa states, return awaiting mfa on password required state * add implementation for mfa required response after submitting a code * handle remaning strongAuthRequired response * handle introspect required and update todo comment * add send challenge MFA business logic * reuse code for send challenge method * Email OTP MFA - public interface changes (#2296) * first public interface draft * add missing method in signINPasswordRequiredDelegate * add dummy logic * Add dedicated errors for MFA sub flow * add optional new state to error callbacks * Make channel type extensible * Update and add new public comments * fix compilation errors in E2E tests * Fix typo and swiftlint warnings * Address PR comments * use same name for awaitingMFA method, add more details to the comment * merge two mfa error and handle get auth methods request * complete implementation of get auth methods method * implement submit challenge reusing submit code function * remove telemetry check in controller test. Delegate dispatcher should trigger telemetry * Add unit tests for SendChallenge delegate dispatcher * Add remaining tests for mfa delegate dispatchers * add new unit tests for MFA error classes * Writing tests for awaitingMFAState * add unit tests for mfaRequiredState * add new unit tests to existing signIn Controller * add new test for sendChallengeState, add new class for MFA controller tests * move mfa tests to dedicated class * add new tests for mfa controller. Fix some bugs too * add new tests for send challenge * add new tests for get auth methods * add new tests for submit Challenge, stop telemetry on successful result * fix compilation error on integration tests * rename sendChallenge to requestChallenge * Address PR comments * address PR comments. Fix bug around SSPR telemetry id * address PR comments * Email OTP MFA business logic (#2302) * first public interface draft * add missing method in signINPasswordRequiredDelegate * add dummy logic * Add dedicated errors for MFA sub flow * add optional new state to error callbacks * Make channel type extensible * Update and add new public comments * fix compilation errors in E2E tests * Fix typo and swiftlint warnings * add base mfa states, return awaiting mfa on password required state * add implementation for mfa required response after submitting a code * handle remaning strongAuthRequired response * handle introspect required and update todo comment * add send challenge MFA business logic * reuse code for send challenge method * merge two mfa error and handle get auth methods request * complete implementation of get auth methods method * implement submit challenge reusing submit code function * remove telemetry check in controller test. Delegate dispatcher should trigger telemetry * Add unit tests for SendChallenge delegate dispatcher * Add remaining tests for mfa delegate dispatchers * add new unit tests for MFA error classes * Writing tests for awaitingMFAState * add unit tests for mfaRequiredState * add new unit tests to existing signIn Controller * add new test for sendChallengeState, add new class for MFA controller tests * move mfa tests to dedicated class * add new tests for mfa controller. Fix some bugs too * add new tests for send challenge * add new tests for get auth methods * add new tests for submit Challenge, stop telemetry on successful result * fix compilation error on integration tests * rename sendChallenge to requestChallenge * Address PR comments * address PR comments. Fix bug around SSPR telemetry id * address PR comments * Log a warning message and add warning on code documentation (#2306) * add email otp mfa files to mac targets. Remove not used files * Add E2E tests for email OTP MFA feature (#2331) * add new e2e test for signIn with username and password and MFA * add new end to end tests for mfa * add missing E2E tests for email OTP MFA * throw xctskip error to skip error * refactor E2E tests * rename internal test function * send wrong code as string * Refresh token, customise MFA required error description (#2322) * add new error codes key and parse msid error codes * add new error converter tests * update changelog file * add custom error message if error code is mfaRequired * Add new unit tests for error codes handling * add file to mac os target * revert change for project file * Add new MFARequestChallengeError and MFAGetAuthMethodsError (#2340) * add period at the end of the sentence * remove null pointer in project file * remove trailing space and enable all mfa e2e tests --------- Co-authored-by: Marcos Borges Co-authored-by: Marcos Borges <116104275+borgesmb@users.noreply.github.com> --- MSAL/.swiftlint.yml | 1 + MSAL/MSAL.xcodeproj/project.pbxproj | 238 +++++++++- ...e.swift => MSALNativeAuthLogMessage.swift} | 17 +- .../controllers/responses/MFAResults.swift | 41 ++ .../controllers/responses/SignInResults.swift | 2 + .../MSALNativeAuthMFAControlling.swift | 51 ++ .../MSALNativeAuthSignInController.swift | 443 ++++++++++++++---- .../MSALNativeAuthSignInControlling.swift | 1 + .../MSALNativeAuthSignUpController.swift | 2 +- .../network/MSALNativeAuthEndpoint.swift | 1 + .../MSALNativeAuthRequestConfigurator.swift | 10 + .../MSALNativeAuthRequestParametersKey.swift | 1 + .../MSALNativeAuthESTSApiErrorCodes.swift | 1 - .../errors/MSALNativeAuthErrorMessage.swift | 3 +- .../errors/MSALNativeAuthSubErrorCode.swift | 4 + ...tiveAuthSignInChallengeResponseError.swift | 4 + ...eAuthSignInIntrospectOauth2ErrorCode.swift | 31 ++ ...iveAuthSignInIntrospectResponseError.swift | 60 +++ ...AuthSignInChallengeRequestParameters.swift | 4 +- ...uthSignInIntrospectRequestParameters.swift | 40 ++ ...veAuthResetPasswordChallengeResponse.swift | 2 +- ...tiveAuthInternalAuthenticationMethod.swift | 42 ++ ...SALNativeAuthSignInChallengeResponse.swift | 2 +- ...ALNativeAuthSignInIntrospectResponse.swift | 34 ++ ...SALNativeAuthSignUpChallengeResponse.swift | 2 +- ...veAuthResetPasswordResponseValidator.swift | 3 +- ...SALNativeAuthSignInResponseValidator.swift | 90 +++- ...AuthSignInChallengeValidatedResponse.swift | 29 ++ ...uthSignInIntrospectValidatedResponse.swift | 61 +++ ...SALNativeAuthSignUpResponseValidator.swift | 9 +- ...MSALNativeAuthTokenResponseValidator.swift | 14 +- ...MSALNativeAuthTokenValidatedResponse.swift | 27 +- .../MSALNativeAuthSignInRequestProvider.swift | 19 + .../native_auth/public/MSALAuthMethod.swift | 51 ++ .../public/MSALNativeAuthChannelType.swift | 25 +- ...SALNativeAuthPublicClientApplication.swift | 2 + ...NativeAuthUserAccountResult+Internal.swift | 13 +- .../state_machine/delegate/MFADelegates.swift | 91 ++++ .../delegate/SignInDelegates.swift | 13 +- .../MFADelegateDispatchers.swift | 104 ++++ .../SignInDelegateDispatchers.swift | 30 ++ .../error/MFAGetAuthMethodsError.swift | 77 +++ .../error/MFARequestChallengeError.swift | 60 +++ .../error/MFASubmitChallengeError.swift | 76 +++ .../state/MFAStates+Internal.swift | 57 +++ .../state_machine/state/MFAStates.swift | 141 ++++++ ...nAfterPreviousFlowBaseState+Internal.swift | 13 +- .../state/SignInAfterResetPasswordState.swift | 2 +- .../state/SignInAfterSignUpState.swift | 2 +- .../state_machine/state/SignInStates.swift | 2 + .../MSALNativeAuthOperationTypes.swift | 1 + .../MSALNativeAuthTelemetryApiId.swift | 5 +- .../native_auth/common/Model.swift | 4 + .../MSALNativeAuthEndToEndBaseTestCase.swift | 10 + .../end_to_end/mfa/MFADelegateSpies.swift | 131 ++++++ ...NativeAuthSignInWithMFAEndToEndTests.swift | 223 +++++++++ ...NativeAuthResetPasswordEndToEndTests.swift | 4 +- .../sign_in/SignInDelegateSpies.swift | 9 + ...gnUpUsernameAndPasswordEndToEndTests.swift | 2 +- ...ativeAuthSignUpUsernameEndToEndTests.swift | 2 +- ...eAuthSignInChallengeIntegrationTests.swift | 10 + ...AuthSignInIntrospectIntegrationTests.swift | 78 +++ .../MSALNativeAuthTokenIntegrationTests.swift | 9 + .../MSALNativeAuthMFAControllerTests.swift | 407 ++++++++++++++++ ...tiveAuthResetPasswordControllerTests.swift | 9 +- .../MSALNativeAuthSignInControllerTests.swift | 123 ++--- .../MSALNativeAuthSignUpControllerTests.swift | 15 +- .../mock/MSALNativeAuthNetworkMocks.swift | 84 +++- .../MSALNativeAuthSignInControllerMock.swift | 22 +- .../CredentialsDelegateSpies.swift | 0 .../mock/delegate/MFADelegatesSpies.swift | 201 ++++++++ .../ResetPasswordDelegateSpies.swift | 0 .../{ => delegate}/SignInDelegatesSpies.swift | 4 +- .../{ => delegate}/SignUpDelegateSpies.swift | 0 .../network/MSALNativeAuthEndpointTests.swift | 6 +- ...ALNativeAuthRequestConfiguratorTests.swift | 30 +- .../MSALNativeAuthSubErrorCodeTests.swift | 10 +- ...SignInChallengeRequestParametersTest.swift | 5 +- ...hResetPasswordResponseValidatorTests.swift | 8 +- ...ativeAuthSignInResponseValidatorTest.swift | 114 ++++- ...tiveAuthSignUpResponseValidatorTests.swift | 8 +- ...ativeAuthTokenResponseValidatorTests.swift | 38 +- ...tiveAuthTokenValidatedErrorTypeTests.swift | 10 - ...ativeAuthPublicClientApplicationTest.swift | 36 +- ...MSALNativeAuthUserAccountResultTests.swift | 75 +++ ...etAuthMethodsDelegateDispatcherTests.swift | 92 ++++ ...SendChallengeDelegateDispatcherTests.swift | 159 +++++++ ...bmitChallengeDelegateDispatcherTests.swift | 88 ++++ ...ordResendCodeDelegateDispatcherTests.swift | 4 +- ...PasswordStartDelegateDispatcherTests.swift | 4 +- ...swordRequiredDelegateDispatcherTests.swift | 4 +- ...PasswordStartDelegateDispatcherTests.swift | 4 +- ...nInResendCodeDelegateDispatcherTests.swift | 4 +- .../SignInStartDelegateDispatcherTests.swift | 4 +- ...PasswordStartDelegateDispatcherTests.swift | 4 +- ...nUpResendCodeDelegateDispatcherTests.swift | 4 +- .../SignUpStartDelegateDispatcherTests.swift | 4 +- .../error/MFAGetAuthMethodsErrorTests.swift | 64 +++ .../error/MFARequestChallengeErrorTests.swift | 64 +++ .../error/MFASubmitChallengeErrorTests.swift | 64 +++ .../mfa/AwaitingMFAStateTests.swift | 113 +++++ .../mfa/MFARequiredStateTests.swift | 186 ++++++++ .../ResetPasswordCodeSentStateTests.swift | 6 +- .../SignInCodeRequiredStateTests.swift | 6 +- .../sign_up/SignUpCodeSentStateTests.swift | 6 +- 105 files changed, 4216 insertions(+), 409 deletions(-) rename MSAL/src/native_auth/{network/MSALNativeAuthInternalChannelType.swift => MSALNativeAuthLogMessage.swift} (79%) create mode 100644 MSAL/src/native_auth/controllers/responses/MFAResults.swift create mode 100644 MSAL/src/native_auth/controllers/sign_in/MSALNativeAuthMFAControlling.swift create mode 100644 MSAL/src/native_auth/network/errors/sign_in/MSALNativeAuthSignInIntrospectOauth2ErrorCode.swift create mode 100644 MSAL/src/native_auth/network/errors/sign_in/MSALNativeAuthSignInIntrospectResponseError.swift create mode 100644 MSAL/src/native_auth/network/parameters/sign_in/MSALNativeAuthSignInIntrospectRequestParameters.swift create mode 100644 MSAL/src/native_auth/network/responses/sign_in/MSALNativeAuthInternalAuthenticationMethod.swift create mode 100644 MSAL/src/native_auth/network/responses/sign_in/MSALNativeAuthSignInIntrospectResponse.swift create mode 100644 MSAL/src/native_auth/network/responses/validator/sign_in/validated_response/MSALNativeAuthSignInIntrospectValidatedResponse.swift create mode 100644 MSAL/src/native_auth/public/MSALAuthMethod.swift create mode 100644 MSAL/src/native_auth/public/state_machine/delegate/MFADelegates.swift create mode 100644 MSAL/src/native_auth/public/state_machine/delegate_dispatcher/MFADelegateDispatchers.swift create mode 100644 MSAL/src/native_auth/public/state_machine/error/MFAGetAuthMethodsError.swift create mode 100644 MSAL/src/native_auth/public/state_machine/error/MFARequestChallengeError.swift create mode 100644 MSAL/src/native_auth/public/state_machine/error/MFASubmitChallengeError.swift create mode 100644 MSAL/src/native_auth/public/state_machine/state/MFAStates+Internal.swift create mode 100644 MSAL/src/native_auth/public/state_machine/state/MFAStates.swift create mode 100644 MSAL/test/integration/native_auth/end_to_end/mfa/MFADelegateSpies.swift create mode 100644 MSAL/test/integration/native_auth/end_to_end/mfa/MSALNativeAuthSignInWithMFAEndToEndTests.swift create mode 100644 MSAL/test/integration/native_auth/requests/sign_in/MSALNativeAuthSignInIntrospectIntegrationTests.swift create mode 100644 MSAL/test/unit/native_auth/controllers/MSALNativeAuthMFAControllerTests.swift rename MSAL/test/unit/native_auth/mock/{ => delegate}/CredentialsDelegateSpies.swift (100%) create mode 100644 MSAL/test/unit/native_auth/mock/delegate/MFADelegatesSpies.swift rename MSAL/test/unit/native_auth/mock/{ => delegate}/ResetPasswordDelegateSpies.swift (100%) rename MSAL/test/unit/native_auth/mock/{ => delegate}/SignInDelegatesSpies.swift (99%) rename MSAL/test/unit/native_auth/mock/{ => delegate}/SignUpDelegateSpies.swift (100%) create mode 100644 MSAL/test/unit/native_auth/public/delegate/mfa/MFAGetAuthMethodsDelegateDispatcherTests.swift create mode 100644 MSAL/test/unit/native_auth/public/delegate/mfa/MFASendChallengeDelegateDispatcherTests.swift create mode 100644 MSAL/test/unit/native_auth/public/delegate/mfa/MFASubmitChallengeDelegateDispatcherTests.swift create mode 100644 MSAL/test/unit/native_auth/public/error/MFAGetAuthMethodsErrorTests.swift create mode 100644 MSAL/test/unit/native_auth/public/error/MFARequestChallengeErrorTests.swift create mode 100644 MSAL/test/unit/native_auth/public/error/MFASubmitChallengeErrorTests.swift create mode 100644 MSAL/test/unit/native_auth/public/state_machine/mfa/AwaitingMFAStateTests.swift create mode 100644 MSAL/test/unit/native_auth/public/state_machine/mfa/MFARequiredStateTests.swift diff --git a/MSAL/.swiftlint.yml b/MSAL/.swiftlint.yml index d63075c409..8b5db791ac 100644 --- a/MSAL/.swiftlint.yml +++ b/MSAL/.swiftlint.yml @@ -13,3 +13,4 @@ function_parameter_count: disabled_rules: - todo + - empty_enum_arguments diff --git a/MSAL/MSAL.xcodeproj/project.pbxproj b/MSAL/MSAL.xcodeproj/project.pbxproj index df0234e997..b2dd6a3437 100644 --- a/MSAL/MSAL.xcodeproj/project.pbxproj +++ b/MSAL/MSAL.xcodeproj/project.pbxproj @@ -241,6 +241,10 @@ 23FB5C1E22542B99002BF1EB /* MSALJsonDeserializable.h in Headers */ = {isa = PBXBuildFile; fileRef = 23FB5C1C22542B99002BF1EB /* MSALJsonDeserializable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 280095EB2C32CAFC00F1653E /* ClientIdType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 280095EA2C32CAFC00F1653E /* ClientIdType.swift */; }; 2809E8352C3C37B7009F14D7 /* MSALNativeAuthEndToEndPasswordTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2809E8342C3C37B7009F14D7 /* MSALNativeAuthEndToEndPasswordTestCase.swift */; }; + 28188F622C8F48BD00CFDD05 /* MSALNativeAuthSignInWithMFAEndToEndTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28188F5F2C8F482D00CFDD05 /* MSALNativeAuthSignInWithMFAEndToEndTests.swift */; }; + 28188F632C8F48BD00CFDD05 /* MSALNativeAuthSignInWithMFAEndToEndTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28188F5F2C8F482D00CFDD05 /* MSALNativeAuthSignInWithMFAEndToEndTests.swift */; }; + 28188F652C8F4C1100CFDD05 /* MFADelegateSpies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28188F642C8F4C1100CFDD05 /* MFADelegateSpies.swift */; }; + 28188F662C8F4C1100CFDD05 /* MFADelegateSpies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28188F642C8F4C1100CFDD05 /* MFADelegateSpies.swift */; }; 281A0E0C2C21E1F000CB30CB /* MSALNativeAuthSignUpUsernameEndToEndTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E272C4EA2A4447520013B805 /* MSALNativeAuthSignUpUsernameEndToEndTests.swift */; }; 281A0E142C21E1F200CB30CB /* MSALNativeAuthSignUpUsernameAndPasswordEndToEndTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E26E391A2A4C2BE200063C07 /* MSALNativeAuthSignUpUsernameAndPasswordEndToEndTests.swift */; }; 281A0E152C21E1F500CB30CB /* SignUpDelegateSpies.swift in Sources */ = {isa = PBXBuildFile; fileRef = E26E39232A4C2D7400063C07 /* SignUpDelegateSpies.swift */; }; @@ -254,10 +258,16 @@ 2826932A2A0974750037B93A /* MSALNativeAuthTokenRequestParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 282693272A0974740037B93A /* MSALNativeAuthTokenRequestParameters.swift */; }; 2826933B2A0B98750037B93A /* MSALNativeAuthSignInParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2826933A2A0B98750037B93A /* MSALNativeAuthSignInParameters.swift */; }; 285D0D692B99C14F002A1D4A /* MSALNativeAuthTokenResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 285D0D682B99C14F002A1D4A /* MSALNativeAuthTokenResult.swift */; }; + 285E09F72C93340000492A2E /* MSALNativeAuthLogMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28BBB7CD2C80C5740055AF64 /* MSALNativeAuthLogMessage.swift */; }; 285F36082A24DF8300A2190F /* MSALNativeAuthSignInControlling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 285F36072A24DF8300A2190F /* MSALNativeAuthSignInControlling.swift */; }; + 285F58542C5BA33B00F4EFA4 /* MSALNativeAuthSignInIntrospectRequestParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 285F58532C5BA33B00F4EFA4 /* MSALNativeAuthSignInIntrospectRequestParameters.swift */; }; + 285F585D2C5BA4F700F4EFA4 /* MSALNativeAuthSignInIntrospectResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 285F585C2C5BA4F600F4EFA4 /* MSALNativeAuthSignInIntrospectResponse.swift */; }; + 285F585F2C5BA71600F4EFA4 /* MSALNativeAuthInternalAuthenticationMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 285F585E2C5BA71600F4EFA4 /* MSALNativeAuthInternalAuthenticationMethod.swift */; }; + 285F58612C5BC1EC00F4EFA4 /* MSALNativeAuthSignInIntrospectResponseError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 285F58602C5BC1EC00F4EFA4 /* MSALNativeAuthSignInIntrospectResponseError.swift */; }; + 285F58632C5BC67900F4EFA4 /* MSALNativeAuthSignInIntrospectOauth2ErrorCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 285F58622C5BC67900F4EFA4 /* MSALNativeAuthSignInIntrospectOauth2ErrorCode.swift */; }; + 285F58652C5BCA8900F4EFA4 /* MSALNativeAuthSignInIntrospectValidatedResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 285F58642C5BCA8900F4EFA4 /* MSALNativeAuthSignInIntrospectValidatedResponse.swift */; }; 28664DDF2C382A440007D6A5 /* libIdentityAutomationTestLib iOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B21FA9BC2204DC5700806B68 /* libIdentityAutomationTestLib iOS.a */; }; 2877081F2A14F67400E371ED /* MSALNativeAuthSignInChallengeValidatedResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2877081E2A14F67400E371ED /* MSALNativeAuthSignInChallengeValidatedResponse.swift */; }; - 287708222A151A8500E371ED /* MSALNativeAuthInternalChannelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287708212A151A8500E371ED /* MSALNativeAuthInternalChannelType.swift */; }; 287708252A178DC500E371ED /* MSALNativeAuthSignInInitiateValidatedResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287708242A178DC500E371ED /* MSALNativeAuthSignInInitiateValidatedResponse.swift */; }; 287F64D4297EC29400ED90BD /* MSALNativeAuthTelemetryProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287F64D2297EC29400ED90BD /* MSALNativeAuthTelemetryProviderTests.swift */; }; 287F64D5297EC29400ED90BD /* MSALNativeAuthCurrentRequestTelemetryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287F64D3297EC29400ED90BD /* MSALNativeAuthCurrentRequestTelemetryTests.swift */; }; @@ -270,10 +280,30 @@ 289747B129799C6B00838C80 /* MSALNativeAuthInputValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 289747AF29799A8700838C80 /* MSALNativeAuthInputValidator.swift */; }; 289E15592948E601006104D9 /* MSALNativeAuthCacheInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 289E15582948E601006104D9 /* MSALNativeAuthCacheInterface.swift */; }; 289E156D2948EB8A006104D9 /* MSALNativeAuthCacheAccessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 289E156C2948EB8A006104D9 /* MSALNativeAuthCacheAccessor.swift */; }; + 289E44AC2C9D7A9E00F6B9D7 /* MFARequestChallengeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 289E44AB2C9D7A9E00F6B9D7 /* MFARequestChallengeError.swift */; }; + 289E44AD2C9D7A9E00F6B9D7 /* MFARequestChallengeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 289E44AB2C9D7A9E00F6B9D7 /* MFARequestChallengeError.swift */; }; + 289E44B62C9D7B0900F6B9D7 /* MFAGetAuthMethodsError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 289E44B52C9D7B0900F6B9D7 /* MFAGetAuthMethodsError.swift */; }; + 289E44B72C9D7B0900F6B9D7 /* MFAGetAuthMethodsError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 289E44B52C9D7B0900F6B9D7 /* MFAGetAuthMethodsError.swift */; }; + 289E44B92C9D843F00F6B9D7 /* MFARequestChallengeErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 289E44B82C9D843F00F6B9D7 /* MFARequestChallengeErrorTests.swift */; }; + 289E44BA2C9D843F00F6B9D7 /* MFARequestChallengeErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 289E44B82C9D843F00F6B9D7 /* MFARequestChallengeErrorTests.swift */; }; 28A277C82C21F08800D95E00 /* MSAL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D65A6F431E3FD30A00C69FBA /* MSAL.framework */; }; 28A277D92C22ED5E00D95E00 /* MSALNativeAuthEmailCodeRetriever.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A277D82C22ED5E00D95E00 /* MSALNativeAuthEmailCodeRetriever.swift */; }; 28A472EC2A276C3B003F988B /* MSALNativeAuthTokenValidatedResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A472EB2A276C3B003F988B /* MSALNativeAuthTokenValidatedResponse.swift */; }; + 28A600962C78843C00455666 /* MFASendChallengeDelegateDispatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A600942C78843300455666 /* MFASendChallengeDelegateDispatcherTests.swift */; }; + 28A6009B2C7898DA00455666 /* MFADelegatesSpies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A6009A2C7898DA00455666 /* MFADelegatesSpies.swift */; }; + 28A6009D2C78A26D00455666 /* MFAGetAuthMethodsDelegateDispatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A6009C2C78A26D00455666 /* MFAGetAuthMethodsDelegateDispatcherTests.swift */; }; + 28A6009F2C78A27C00455666 /* MFASubmitChallengeDelegateDispatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A6009E2C78A27C00455666 /* MFASubmitChallengeDelegateDispatcherTests.swift */; }; + 28A600A12C78BA8900455666 /* MFAGetAuthMethodsErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A600A02C78BA8900455666 /* MFAGetAuthMethodsErrorTests.swift */; }; + 28A600A32C78BA9C00455666 /* MFASubmitChallengeErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A600A22C78BA9C00455666 /* MFASubmitChallengeErrorTests.swift */; }; + 28A600A62C78BDC100455666 /* AwaitingMFAStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A600A52C78BDC100455666 /* AwaitingMFAStateTests.swift */; }; + 28A600A82C78BDD200455666 /* MFARequiredStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A600A72C78BDD200455666 /* MFARequiredStateTests.swift */; }; + 28A600AA2C78E09F00455666 /* MSALNativeAuthMFAControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A600A92C78E09F00455666 /* MSALNativeAuthMFAControllerTests.swift */; }; + 28ABE1762C5D213700F5275D /* MSALNativeAuthSignInIntrospectIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28ABE1752C5D213700F5275D /* MSALNativeAuthSignInIntrospectIntegrationTests.swift */; }; + 28B28B832C6F46E50030D5C5 /* MFAStates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28B28B822C6F46E50030D5C5 /* MFAStates.swift */; }; + 28B28B8C2C6F4B570030D5C5 /* MFADelegates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28B28B8B2C6F4B570030D5C5 /* MFADelegates.swift */; }; + 28B28B922C6F611F0030D5C5 /* MSALAuthMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28B28B912C6F611F0030D5C5 /* MSALAuthMethod.swift */; }; 28B6494D2A0959EB00EF3DB7 /* MSALNativeAuthSignInResponseValidatorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28B6494A2A0959DC00EF3DB7 /* MSALNativeAuthSignInResponseValidatorTest.swift */; }; + 28BBB7CE2C80C5740055AF64 /* MSALNativeAuthLogMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28BBB7CD2C80C5740055AF64 /* MSALNativeAuthLogMessage.swift */; }; 28CA6F5D29689F34004DB11D /* MSALNativeAuthCacheAccessorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28CA6F5429689F26004DB11D /* MSALNativeAuthCacheAccessorTest.swift */; }; 28D1D57029BF62E900CE75F4 /* MSAL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D65A6F431E3FD30A00C69FBA /* MSAL.framework */; }; 28D1D58029BF883D00CE75F4 /* MSALNativeAuthIntegrationBaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D1D57F29BF883D00CE75F4 /* MSALNativeAuthIntegrationBaseTests.swift */; }; @@ -281,6 +311,11 @@ 28D1D59C29C2266500CE75F4 /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D1D59B29C2266500CE75F4 /* Model.swift */; }; 28D1D59E29C2392000CE75F4 /* MockAPIHandlerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D1D59D29C2392000CE75F4 /* MockAPIHandlerTest.swift */; }; 28D5B05D2A028D2B0066E32B /* MSALNativeAuthControllerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D5B05C2A028D2B0066E32B /* MSALNativeAuthControllerFactory.swift */; }; + 28D811E52C735D98002BE1AA /* MFASubmitChallengeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D811E42C735D98002BE1AA /* MFASubmitChallengeError.swift */; }; + 28D811E72C75FB10002BE1AA /* MFAStates+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D811E62C75FB10002BE1AA /* MFAStates+Internal.swift */; }; + 28D811E92C75FFC3002BE1AA /* MFADelegateDispatchers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D811E82C75FFC3002BE1AA /* MFADelegateDispatchers.swift */; }; + 28D811EB2C7602A1002BE1AA /* MFAResults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D811EA2C7602A1002BE1AA /* MFAResults.swift */; }; + 28D811ED2C760303002BE1AA /* MSALNativeAuthMFAControlling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D811EC2C760303002BE1AA /* MSALNativeAuthMFAControlling.swift */; }; 28DCD09229D7166F00C4601E /* SignUpDelegates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28DCD09129D7166F00C4601E /* SignUpDelegates.swift */; }; 28DCD09A29D7192F00C4601E /* MSALNativeAuthError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28DCD09929D7192F00C4601E /* MSALNativeAuthError.swift */; }; 28DCD0A029D7260B00C4601E /* MSALNativeAuthBaseState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28DCD09F29D7260B00C4601E /* MSALNativeAuthBaseState.swift */; }; @@ -298,6 +333,30 @@ 28E4D9032A30ABA200280921 /* ResendCodeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28E4D9022A30ABA200280921 /* ResendCodeError.swift */; }; 28EDF93E29E6D43900A99F2A /* SignUpStartError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28EDF93D29E6D43900A99F2A /* SignUpStartError.swift */; }; 28EDF94129E6D52E00A99F2A /* SignInStartError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28EDF94029E6D52E00A99F2A /* SignInStartError.swift */; }; + 28EE65062C8B01D500015F90 /* MSALNativeAuthMFAControlling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D811EC2C760303002BE1AA /* MSALNativeAuthMFAControlling.swift */; }; + 28EE650E2C8B027D00015F90 /* MFAResults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D811EA2C7602A1002BE1AA /* MFAResults.swift */; }; + 28EE650F2C8B039600015F90 /* MSALNativeAuthSignInIntrospectResponseError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 285F58602C5BC1EC00F4EFA4 /* MSALNativeAuthSignInIntrospectResponseError.swift */; }; + 28EE65102C8B03AA00015F90 /* MSALNativeAuthSignInIntrospectRequestParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 285F58532C5BA33B00F4EFA4 /* MSALNativeAuthSignInIntrospectRequestParameters.swift */; }; + 28EE65112C8B03BD00015F90 /* MSALNativeAuthInternalAuthenticationMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 285F585E2C5BA71600F4EFA4 /* MSALNativeAuthInternalAuthenticationMethod.swift */; }; + 28EE65122C8B0E3600015F90 /* MSALNativeAuthSignInIntrospectResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 285F585C2C5BA4F600F4EFA4 /* MSALNativeAuthSignInIntrospectResponse.swift */; }; + 28EE65132C8B0E4500015F90 /* MSALNativeAuthSignInIntrospectValidatedResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 285F58642C5BCA8900F4EFA4 /* MSALNativeAuthSignInIntrospectValidatedResponse.swift */; }; + 28EE65142C8B0E5500015F90 /* MSALAuthMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28B28B912C6F611F0030D5C5 /* MSALAuthMethod.swift */; }; + 28EE65152C8B0E6B00015F90 /* MFADelegates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28B28B8B2C6F4B570030D5C5 /* MFADelegates.swift */; }; + 28EE65162C8B0E7600015F90 /* MFADelegateDispatchers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D811E82C75FFC3002BE1AA /* MFADelegateDispatchers.swift */; }; + 28EE65182C8B0FB200015F90 /* MFASubmitChallengeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D811E42C735D98002BE1AA /* MFASubmitChallengeError.swift */; }; + 28EE65192C8B0FBA00015F90 /* MFAStates+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D811E62C75FB10002BE1AA /* MFAStates+Internal.swift */; }; + 28EE651A2C8B0FC200015F90 /* MFAStates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28B28B822C6F46E50030D5C5 /* MFAStates.swift */; }; + 28EE651B2C8B0FDD00015F90 /* MSALNativeAuthSignInIntrospectIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28ABE1752C5D213700F5275D /* MSALNativeAuthSignInIntrospectIntegrationTests.swift */; }; + 28EE651C2C8B0FF500015F90 /* MSALNativeAuthMFAControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A600A92C78E09F00455666 /* MSALNativeAuthMFAControllerTests.swift */; }; + 28EE651D2C8B101000015F90 /* MFADelegatesSpies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A6009A2C7898DA00455666 /* MFADelegatesSpies.swift */; }; + 28EE651E2C8B103B00015F90 /* MFAGetAuthMethodsDelegateDispatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A6009C2C78A26D00455666 /* MFAGetAuthMethodsDelegateDispatcherTests.swift */; }; + 28EE651F2C8B107100015F90 /* MFASendChallengeDelegateDispatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A600942C78843300455666 /* MFASendChallengeDelegateDispatcherTests.swift */; }; + 28EE65202C8B107D00015F90 /* MFASubmitChallengeDelegateDispatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A6009E2C78A27C00455666 /* MFASubmitChallengeDelegateDispatcherTests.swift */; }; + 28EE65212C8B108A00015F90 /* MFAGetAuthMethodsErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A600A02C78BA8900455666 /* MFAGetAuthMethodsErrorTests.swift */; }; + 28EE65222C8B109300015F90 /* MFASubmitChallengeErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A600A22C78BA9C00455666 /* MFASubmitChallengeErrorTests.swift */; }; + 28EE65232C8B109D00015F90 /* AwaitingMFAStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A600A52C78BDC100455666 /* AwaitingMFAStateTests.swift */; }; + 28EE65242C8B10AA00015F90 /* MFARequiredStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A600A72C78BDD200455666 /* MFARequiredStateTests.swift */; }; + 28EE65252C8B10F000015F90 /* MSALNativeAuthSignInIntrospectOauth2ErrorCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 285F58622C5BC67900F4EFA4 /* MSALNativeAuthSignInIntrospectOauth2ErrorCode.swift */; }; 28F19BEB2A2F884D00575581 /* Array+joinScopes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28F19BEA2A2F884D00575581 /* Array+joinScopes.swift */; }; 28FDC49C2A38BFA900E38BE1 /* SignInAfterSignUpState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FDC49B2A38BFA900E38BE1 /* SignInAfterSignUpState.swift */; }; 28FDC4A62A38C00900E38BE1 /* SignInAfterSignUpDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FDC4A52A38C00900E38BE1 /* SignInAfterSignUpDelegate.swift */; }; @@ -1144,7 +1203,6 @@ DE8DC4EB2C6621D300534E8F /* MSALNativeAuthTokenOauth2ErrorCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEDB29A729DDAEB3008DA85B /* MSALNativeAuthTokenOauth2ErrorCode.swift */; }; DE8DC4EC2C6621D300534E8F /* MSALNativeAuthTokenResponseError.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEDB29AB29DDAF53008DA85B /* MSALNativeAuthTokenResponseError.swift */; }; DE8DC4ED2C6621D300534E8F /* MSALNativeAuthInternalChallengeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E235613329C9D528000E01CA /* MSALNativeAuthInternalChallengeType.swift */; }; - DE8DC4EE2C6621D300534E8F /* MSALNativeAuthInternalChannelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287708212A151A8500E371ED /* MSALNativeAuthInternalChannelType.swift */; }; DE8DC4EF2C6621D600534E8F /* MSALNativeAuthServerTelemetry.swift in Sources */ = {isa = PBXBuildFile; fileRef = E26097C62948FC720060DD7C /* MSALNativeAuthServerTelemetry.swift */; }; DE8DC4F02C6621D600534E8F /* MSALNativeAuthTelemetryApiId.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEF9D995296EC35A006CB384 /* MSALNativeAuthTelemetryApiId.swift */; }; DE8DC4F12C6621D600534E8F /* MSALNativeAuthTelemetryProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEF9D99E296F08CE006CB384 /* MSALNativeAuthTelemetryProvider.swift */; }; @@ -1732,13 +1790,6 @@ remoteGlobalIDString = D626FF681FBA6EDF00EE4487; remoteInfo = "IdentityCoreTests Mac"; }; - D6BFE3DA1EB42D3200F2B7E8 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = D6DFF39A1E2579360012891A /* Project object */; - proxyType = 1; - remoteGlobalIDString = 962E37A61E720C5D00DE71FE; - remoteInfo = "MSAL Test Automation (iOS)"; - }; DE1BD0D42C3C275200B0888E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = D6DFF39A1E2579360012891A /* Project object */; @@ -1938,12 +1989,19 @@ 23FB5C1C22542B99002BF1EB /* MSALJsonDeserializable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSALJsonDeserializable.h; sourceTree = ""; }; 280095EA2C32CAFC00F1653E /* ClientIdType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientIdType.swift; sourceTree = ""; }; 2809E8342C3C37B7009F14D7 /* MSALNativeAuthEndToEndPasswordTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthEndToEndPasswordTestCase.swift; sourceTree = ""; }; + 28188F5F2C8F482D00CFDD05 /* MSALNativeAuthSignInWithMFAEndToEndTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthSignInWithMFAEndToEndTests.swift; sourceTree = ""; }; + 28188F642C8F4C1100CFDD05 /* MFADelegateSpies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MFADelegateSpies.swift; sourceTree = ""; }; 282693272A0974740037B93A /* MSALNativeAuthTokenRequestParameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthTokenRequestParameters.swift; sourceTree = ""; }; 2826933A2A0B98750037B93A /* MSALNativeAuthSignInParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthSignInParameters.swift; sourceTree = ""; }; 285D0D682B99C14F002A1D4A /* MSALNativeAuthTokenResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthTokenResult.swift; sourceTree = ""; }; 285F36072A24DF8300A2190F /* MSALNativeAuthSignInControlling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthSignInControlling.swift; sourceTree = ""; }; + 285F58532C5BA33B00F4EFA4 /* MSALNativeAuthSignInIntrospectRequestParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthSignInIntrospectRequestParameters.swift; sourceTree = ""; }; + 285F585C2C5BA4F600F4EFA4 /* MSALNativeAuthSignInIntrospectResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthSignInIntrospectResponse.swift; sourceTree = ""; }; + 285F585E2C5BA71600F4EFA4 /* MSALNativeAuthInternalAuthenticationMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthInternalAuthenticationMethod.swift; sourceTree = ""; }; + 285F58602C5BC1EC00F4EFA4 /* MSALNativeAuthSignInIntrospectResponseError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthSignInIntrospectResponseError.swift; sourceTree = ""; }; + 285F58622C5BC67900F4EFA4 /* MSALNativeAuthSignInIntrospectOauth2ErrorCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthSignInIntrospectOauth2ErrorCode.swift; sourceTree = ""; }; + 285F58642C5BCA8900F4EFA4 /* MSALNativeAuthSignInIntrospectValidatedResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthSignInIntrospectValidatedResponse.swift; sourceTree = ""; }; 2877081E2A14F67400E371ED /* MSALNativeAuthSignInChallengeValidatedResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthSignInChallengeValidatedResponse.swift; sourceTree = ""; }; - 287708212A151A8500E371ED /* MSALNativeAuthInternalChannelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthInternalChannelType.swift; sourceTree = ""; }; 287708242A178DC500E371ED /* MSALNativeAuthSignInInitiateValidatedResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthSignInInitiateValidatedResponse.swift; sourceTree = ""; }; 287F64D2297EC29400ED90BD /* MSALNativeAuthTelemetryProviderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthTelemetryProviderTests.swift; sourceTree = ""; }; 287F64D3297EC29400ED90BD /* MSALNativeAuthCurrentRequestTelemetryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthCurrentRequestTelemetryTests.swift; sourceTree = ""; }; @@ -1956,9 +2014,26 @@ 289747AF29799A8700838C80 /* MSALNativeAuthInputValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthInputValidator.swift; sourceTree = ""; }; 289E15582948E601006104D9 /* MSALNativeAuthCacheInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthCacheInterface.swift; sourceTree = ""; }; 289E156C2948EB8A006104D9 /* MSALNativeAuthCacheAccessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthCacheAccessor.swift; sourceTree = ""; }; + 289E44AB2C9D7A9E00F6B9D7 /* MFARequestChallengeError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MFARequestChallengeError.swift; sourceTree = ""; }; + 289E44B52C9D7B0900F6B9D7 /* MFAGetAuthMethodsError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MFAGetAuthMethodsError.swift; sourceTree = ""; }; + 289E44B82C9D843F00F6B9D7 /* MFARequestChallengeErrorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MFARequestChallengeErrorTests.swift; sourceTree = ""; }; 28A277D82C22ED5E00D95E00 /* MSALNativeAuthEmailCodeRetriever.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthEmailCodeRetriever.swift; sourceTree = ""; }; 28A472EB2A276C3B003F988B /* MSALNativeAuthTokenValidatedResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthTokenValidatedResponse.swift; sourceTree = ""; }; + 28A600942C78843300455666 /* MFASendChallengeDelegateDispatcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MFASendChallengeDelegateDispatcherTests.swift; sourceTree = ""; }; + 28A6009A2C7898DA00455666 /* MFADelegatesSpies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MFADelegatesSpies.swift; sourceTree = ""; }; + 28A6009C2C78A26D00455666 /* MFAGetAuthMethodsDelegateDispatcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MFAGetAuthMethodsDelegateDispatcherTests.swift; sourceTree = ""; }; + 28A6009E2C78A27C00455666 /* MFASubmitChallengeDelegateDispatcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MFASubmitChallengeDelegateDispatcherTests.swift; sourceTree = ""; }; + 28A600A02C78BA8900455666 /* MFAGetAuthMethodsErrorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MFAGetAuthMethodsErrorTests.swift; sourceTree = ""; }; + 28A600A22C78BA9C00455666 /* MFASubmitChallengeErrorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MFASubmitChallengeErrorTests.swift; sourceTree = ""; }; + 28A600A52C78BDC100455666 /* AwaitingMFAStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AwaitingMFAStateTests.swift; sourceTree = ""; }; + 28A600A72C78BDD200455666 /* MFARequiredStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MFARequiredStateTests.swift; sourceTree = ""; }; + 28A600A92C78E09F00455666 /* MSALNativeAuthMFAControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthMFAControllerTests.swift; sourceTree = ""; }; + 28ABE1752C5D213700F5275D /* MSALNativeAuthSignInIntrospectIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthSignInIntrospectIntegrationTests.swift; sourceTree = ""; }; + 28B28B822C6F46E50030D5C5 /* MFAStates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MFAStates.swift; sourceTree = ""; }; + 28B28B8B2C6F4B570030D5C5 /* MFADelegates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MFADelegates.swift; sourceTree = ""; }; + 28B28B912C6F611F0030D5C5 /* MSALAuthMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALAuthMethod.swift; sourceTree = ""; }; 28B6494A2A0959DC00EF3DB7 /* MSALNativeAuthSignInResponseValidatorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthSignInResponseValidatorTest.swift; sourceTree = ""; }; + 28BBB7CD2C80C5740055AF64 /* MSALNativeAuthLogMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthLogMessage.swift; sourceTree = ""; }; 28CA6F5429689F26004DB11D /* MSALNativeAuthCacheAccessorTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthCacheAccessorTest.swift; sourceTree = ""; }; 28CED2E82C21E0F9004320D1 /* MSAL iOS Native Auth E2E Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "MSAL iOS Native Auth E2E Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 28D1D56C29BF62E900CE75F4 /* MSAL iOS Native Auth Integration Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "MSAL iOS Native Auth Integration Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1967,6 +2042,11 @@ 28D1D59B29C2266500CE75F4 /* Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Model.swift; sourceTree = ""; }; 28D1D59D29C2392000CE75F4 /* MockAPIHandlerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAPIHandlerTest.swift; sourceTree = ""; }; 28D5B05C2A028D2B0066E32B /* MSALNativeAuthControllerFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthControllerFactory.swift; sourceTree = ""; }; + 28D811E42C735D98002BE1AA /* MFASubmitChallengeError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MFASubmitChallengeError.swift; sourceTree = ""; }; + 28D811E62C75FB10002BE1AA /* MFAStates+Internal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MFAStates+Internal.swift"; sourceTree = ""; }; + 28D811E82C75FFC3002BE1AA /* MFADelegateDispatchers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MFADelegateDispatchers.swift; sourceTree = ""; }; + 28D811EA2C7602A1002BE1AA /* MFAResults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MFAResults.swift; sourceTree = ""; }; + 28D811EC2C760303002BE1AA /* MSALNativeAuthMFAControlling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthMFAControlling.swift; sourceTree = ""; }; 28DCD09129D7166F00C4601E /* SignUpDelegates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpDelegates.swift; sourceTree = ""; }; 28DCD09929D7192F00C4601E /* MSALNativeAuthError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthError.swift; sourceTree = ""; }; 28DCD09F29D7260B00C4601E /* MSALNativeAuthBaseState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthBaseState.swift; sourceTree = ""; }; @@ -2793,12 +2873,22 @@ path = controllers; sourceTree = ""; }; + 28188F572C8F480300CFDD05 /* mfa */ = { + isa = PBXGroup; + children = ( + 28188F5F2C8F482D00CFDD05 /* MSALNativeAuthSignInWithMFAEndToEndTests.swift */, + 28188F642C8F4C1100CFDD05 /* MFADelegateSpies.swift */, + ); + path = mfa; + sourceTree = ""; + }; 282693392A0B98490037B93A /* sign_in */ = { isa = PBXGroup; children = ( 285F36072A24DF8300A2190F /* MSALNativeAuthSignInControlling.swift */, E206FCEE2979BC4600AF4400 /* MSALNativeAuthSignInController.swift */, 2826933A2A0B98750037B93A /* MSALNativeAuthSignInParameters.swift */, + 28D811EC2C760303002BE1AA /* MSALNativeAuthMFAControlling.swift */, ); path = sign_in; sourceTree = ""; @@ -2808,6 +2898,7 @@ children = ( 2877081E2A14F67400E371ED /* MSALNativeAuthSignInChallengeValidatedResponse.swift */, 287708242A178DC500E371ED /* MSALNativeAuthSignInInitiateValidatedResponse.swift */, + 285F58642C5BCA8900F4EFA4 /* MSALNativeAuthSignInIntrospectValidatedResponse.swift */, ); path = validated_response; sourceTree = ""; @@ -2844,6 +2935,7 @@ 287F64F42981A7B800ED90BD /* mock */ = { isa = PBXGroup; children = ( + 28A600992C7898AE00455666 /* delegate */, E25BC0822995429D00588549 /* MSALNativeAuthCacheMocks.swift */, E2F5BE9429894FCA00C67EC7 /* MSALNativeAuthConfigStubs.swift */, DE0347A72A41AD08003CB3B6 /* MSALNativeAuthUserAccountResultStub.swift */, @@ -2856,13 +2948,9 @@ E2CD2EB42A0404DA009F8FFA /* MSALNativeAuthSignUpControllerSpy.swift */, 28DE3FCF2A0921E2003148A4 /* SignInTestsValidatorHelpers.swift */, 9BD276582A0E7E6700FBD033 /* ResetPasswordTestValidatorHelpers.swift */, - DE14096A2A38DE0E008E6F1E /* CredentialsDelegateSpies.swift */, E2EBD6202A1BB4640049467A /* MSALNativeAuthSignUpRequestProviderMock.swift */, E2EBD6292A1BB7700049467A /* MSALNativeAuthSignUpResponseValidatorMock.swift */, 28FDC4AB2A38D7D200E38BE1 /* MSALNativeAuthSignInControllerMock.swift */, - E20C217D2A7A61CC00E31598 /* ResetPasswordDelegateSpies.swift */, - E20C21742A7A61B600E31598 /* SignUpDelegateSpies.swift */, - E2F626B22A781CE300C4A303 /* SignInDelegatesSpies.swift */, DE946FE42B0F713A00978493 /* MSALNativeAuthHTTPRequestMock.swift */, 9B61C91D2A27E5E200CE9E3A /* reset_password */, ); @@ -2917,6 +3005,37 @@ path = network; sourceTree = ""; }; + 28A600932C7883EA00455666 /* mfa */ = { + isa = PBXGroup; + children = ( + 28A600942C78843300455666 /* MFASendChallengeDelegateDispatcherTests.swift */, + 28A6009C2C78A26D00455666 /* MFAGetAuthMethodsDelegateDispatcherTests.swift */, + 28A6009E2C78A27C00455666 /* MFASubmitChallengeDelegateDispatcherTests.swift */, + ); + path = mfa; + sourceTree = ""; + }; + 28A600992C7898AE00455666 /* delegate */ = { + isa = PBXGroup; + children = ( + E2F626B22A781CE300C4A303 /* SignInDelegatesSpies.swift */, + DE14096A2A38DE0E008E6F1E /* CredentialsDelegateSpies.swift */, + E20C217D2A7A61CC00E31598 /* ResetPasswordDelegateSpies.swift */, + E20C21742A7A61B600E31598 /* SignUpDelegateSpies.swift */, + 28A6009A2C7898DA00455666 /* MFADelegatesSpies.swift */, + ); + path = delegate; + sourceTree = ""; + }; + 28A600A42C78BD8B00455666 /* mfa */ = { + isa = PBXGroup; + children = ( + 28A600A52C78BDC100455666 /* AwaitingMFAStateTests.swift */, + 28A600A72C78BDD200455666 /* MFARequiredStateTests.swift */, + ); + path = mfa; + sourceTree = ""; + }; 28B649412A09596300EF3DB7 /* responses */ = { isa = PBXGroup; children = ( @@ -2997,6 +3116,7 @@ DE9245112A38736600C0389F /* CredentialsDelegates.swift */, 28FDC4A52A38C00900E38BE1 /* SignInAfterSignUpDelegate.swift */, E224F7462B18F29F000A7B2E /* SignInAfterResetPasswordDelegate.swift */, + 28B28B8B2C6F4B570030D5C5 /* MFADelegates.swift */, ); path = delegate; sourceTree = ""; @@ -3015,6 +3135,8 @@ E2F626AF2A78130700C4A303 /* SignInAfterPreviousFlowBaseState+Internal.swift */, 28FDC49B2A38BFA900E38BE1 /* SignInAfterSignUpState.swift */, E224F73D2B18F11F000A7B2E /* SignInAfterResetPasswordState.swift */, + 28B28B822C6F46E50030D5C5 /* MFAStates.swift */, + 28D811E62C75FB10002BE1AA /* MFAStates+Internal.swift */, ); path = state; sourceTree = ""; @@ -3033,6 +3155,9 @@ DE9245142A3875D700C0389F /* RetrieveAccessTokenError.swift */, 28FDC4A82A38C0D000E38BE1 /* SignInAfterSignUpError.swift */, E224F7482B18F2FE000A7B2E /* SignInAfterResetPasswordError.swift */, + 28D811E42C735D98002BE1AA /* MFASubmitChallengeError.swift */, + 289E44AB2C9D7A9E00F6B9D7 /* MFARequestChallengeError.swift */, + 289E44B52C9D7B0900F6B9D7 /* MFAGetAuthMethodsError.swift */, ); path = error; sourceTree = ""; @@ -3259,6 +3384,7 @@ 9B5D6D042A3CA0C600521576 /* end_to_end */ = { isa = PBXGroup; children = ( + 28188F572C8F480300CFDD05 /* mfa */, 28A277D02C22ED2A00D95E00 /* otp_code_retriever */, E272C4E92A4447520013B805 /* sign_up */, 9B235DA22A3D15E400657331 /* sign_in */, @@ -4196,6 +4322,8 @@ children = ( DE0D65AB29CC6A59005798B1 /* MSALNativeAuthSignInInitiateResponse.swift */, DE0D65B529CC6BBA005798B1 /* MSALNativeAuthSignInChallengeResponse.swift */, + 285F585C2C5BA4F600F4EFA4 /* MSALNativeAuthSignInIntrospectResponse.swift */, + 285F585E2C5BA71600F4EFA4 /* MSALNativeAuthInternalAuthenticationMethod.swift */, ); path = sign_in; sourceTree = ""; @@ -4224,6 +4352,8 @@ DEDB29A429DDA9DC008DA85B /* MSALNativeAuthSignInInitiateResponseError.swift */, DEDB29A629DDAEB3008DA85B /* MSALNativeAuthSignInChallengeOauth2ErrorCode.swift */, DEDB29AA29DDAF52008DA85B /* MSALNativeAuthSignInChallengeResponseError.swift */, + 285F58602C5BC1EC00F4EFA4 /* MSALNativeAuthSignInIntrospectResponseError.swift */, + 285F58622C5BC67900F4EFA4 /* MSALNativeAuthSignInIntrospectOauth2ErrorCode.swift */, ); path = sign_in; sourceTree = ""; @@ -4233,6 +4363,7 @@ children = ( DE0D65C529D344F1005798B1 /* MSALNativeAuthSignInInitiateIntegrationTests.swift */, DE0D65CA29D5CD6D005798B1 /* MSALNativeAuthSignInChallengeIntegrationTests.swift */, + 28ABE1752C5D213700F5275D /* MSALNativeAuthSignInIntrospectIntegrationTests.swift */, ); path = sign_in; sourceTree = ""; @@ -4351,6 +4482,7 @@ E22427E02B0650670006C55E /* delegate */ = { isa = PBXGroup; children = ( + 28A600932C7883EA00455666 /* mfa */, E22427F02B06686C0006C55E /* sign_in */, E22427EF2B06685B0006C55E /* sign_up */, E22427FB2B0670E90006C55E /* reset_password */, @@ -4459,6 +4591,7 @@ 28DCD08829D70FA000C4601E /* state_machine */, E2DC31BB29AFA1E700051CE7 /* MSALNativeAuthPublicClientApplication.swift */, E2F6269C2A780DDE00C4A303 /* MSALNativeAuthPublicClientApplication+Internal.swift */, + 28B28B912C6F611F0030D5C5 /* MSALAuthMethod.swift */, DE729ECC2A1793A100A761D9 /* MSALNativeAuthChannelType.swift */, 9BD78D7A2A126A1500AA7E12 /* MSALNativeAuthChallengeTypes.h */, DE8BE7DB2A1F6BD2009642A5 /* MSALNativeAuthUserAccountResult.swift */, @@ -4547,6 +4680,7 @@ isa = PBXGroup; children = ( E206FC5E296D65DE00AF4400 /* MSALNativeAuthInternalError.swift */, + 28BBB7CD2C80C5740055AF64 /* MSALNativeAuthLogMessage.swift */, E26097942948FB660060DD7C /* cache */, E205D62C29B783D6003887BC /* configuration */, 289747AE29799A6600838C80 /* input_validator */, @@ -4601,6 +4735,7 @@ children = ( DE0D656729BF72F6005798B1 /* MSALNativeAuthSignInInitiateRequestParameters.swift */, DE0D657429BF73CB005798B1 /* MSALNativeAuthSignInChallengeRequestParameters.swift */, + 285F58532C5BA33B00F4EFA4 /* MSALNativeAuthSignInIntrospectRequestParameters.swift */, ); path = sign_in; sourceTree = ""; @@ -4635,7 +4770,6 @@ DE0FECA92993AD3700B139A8 /* responses */, DEDB29A229DDA992008DA85B /* errors */, E235613329C9D528000E01CA /* MSALNativeAuthInternalChallengeType.swift */, - 287708212A151A8500E371ED /* MSALNativeAuthInternalChannelType.swift */, ); path = network; sourceTree = ""; @@ -4656,6 +4790,7 @@ E2CD2E3F29FBE957009F8FFA /* state_machine */ = { isa = PBXGroup; children = ( + 28A600A42C78BD8B00455666 /* mfa */, E20C21822A7A6C7300E31598 /* sign_in */, E2CD2E4D29FC0435009F8FFA /* sign_up */, 9BD2764E2A0E7DA400FBD033 /* reset_password */, @@ -4686,6 +4821,9 @@ E2CE911B2B0BA48D0009AEDD /* RetrieveAccessTokenErrorTests.swift */, E2CE911D2B0BA4A60009AEDD /* SignInAfterSignUpErrorTests.swift */, E2C190762B20DF4300095534 /* SignInAfterResetPasswordErrorTests.swift */, + 28A600A02C78BA8900455666 /* MFAGetAuthMethodsErrorTests.swift */, + 28A600A22C78BA9C00455666 /* MFASubmitChallengeErrorTests.swift */, + 289E44B82C9D843F00F6B9D7 /* MFARequestChallengeErrorTests.swift */, ); path = error; sourceTree = ""; @@ -4700,6 +4838,7 @@ E22427DA2B0594670006C55E /* CredentialsDelegateDispatcher.swift */, E22427DD2B05981A0006C55E /* SignInAfterSignUpDelegateDispatcher.swift */, E224F74A2B18F891000A7B2E /* SignInAfterResetPasswordDelegateDispatcher.swift */, + 28D811E82C75FFC3002BE1AA /* MFADelegateDispatchers.swift */, ); path = delegate_dispatcher; sourceTree = ""; @@ -4711,6 +4850,7 @@ E2EFACFD2A69915100D6C3DE /* SignInResults.swift */, E2EFAD0B2A69B45100D6C3DE /* SignUpResults.swift */, E2EFAD0E2A69BBB800D6C3DE /* ResetPasswordResults.swift */, + 28D811EA2C7602A1002BE1AA /* MFAResults.swift */, ); path = responses; sourceTree = ""; @@ -4724,6 +4864,7 @@ E286E2DC2A1BAEA800666DD0 /* MSALNativeAuthSignUpControllerTests.swift */, 9B4EE9CD2A1686A900F243C1 /* MSALNativeAuthResetPasswordControllerTests.swift */, DE14096C2A38DF40008E6F1E /* MSALNativeAuthCredentialsControllerTests.swift */, + 28A600A92C78E09F00455666 /* MSALNativeAuthMFAControllerTests.swift */, ); path = controllers; sourceTree = ""; @@ -6159,9 +6300,11 @@ 28A277D92C22ED5E00D95E00 /* MSALNativeAuthEmailCodeRetriever.swift in Sources */, E24CE9CC2C57F1160069E2E4 /* AttributesStub.swift in Sources */, 281A0E1B2C21E20600CB30CB /* MSALNativeAuthEndToEndBaseTestCase.swift in Sources */, + 28188F652C8F4C1100CFDD05 /* MFADelegateSpies.swift in Sources */, 281A0E192C21E20000CB30CB /* MSALNativeAuthResetPasswordEndToEndTests.swift in Sources */, 281A0E0C2C21E1F000CB30CB /* MSALNativeAuthSignUpUsernameEndToEndTests.swift in Sources */, 281A0E152C21E1F500CB30CB /* SignUpDelegateSpies.swift in Sources */, + 28188F622C8F48BD00CFDD05 /* MSALNativeAuthSignInWithMFAEndToEndTests.swift in Sources */, 281A0E172C21E1FB00CB30CB /* MSALNativeAuthSignInUsernameEndToEndTests.swift in Sources */, 281A0E1C2C21E3A400CB30CB /* MSALNativeAuthSignOutEndToEndTests.swift in Sources */, 281A0E162C21E1F800CB30CB /* MSALNativeAuthSignInUsernameAndPasswordEndToEndTests.swift in Sources */, @@ -6191,6 +6334,7 @@ E2BC027529D6E0C600041DBC /* MSALNativeAuthSignUpContinueIntegrationTests.swift in Sources */, E23E955F29D4B9F7001DC59C /* MSALNativeAuthSignUpChallengeIntegrationTests.swift in Sources */, 28D1D59229C2231C00CE75F4 /* MockAPIHandler.swift in Sources */, + 28ABE1762C5D213700F5275D /* MSALNativeAuthSignInIntrospectIntegrationTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -6335,6 +6479,7 @@ E2F626AD2A78119900C4A303 /* ResetPasswordStates+Internal.swift in Sources */, 9626D14E225828780019417B /* MSALGlobalConfig.m in Sources */, 28A472EC2A276C3B003F988B /* MSALNativeAuthTokenValidatedResponse.swift in Sources */, + 28B28B8C2C6F4B570030D5C5 /* MFADelegates.swift in Sources */, 96B5E6DC2256D15A002232F9 /* MSALHTTPConfig.m in Sources */, E2ACA48B2952302B00E98964 /* MSALNativeAuthRequestContext.swift in Sources */, DEE34F7DD170B71C00BC302A /* MSALNativeAuthResetPasswordSubmitRequestParameters.swift in Sources */, @@ -6350,21 +6495,25 @@ 28DCD0AA29D7344000C4601E /* SignInStates.swift in Sources */, E243F69429D1976700DAC60F /* MSALNativeAuthSignUpStartResponse.swift in Sources */, E2C1D287299BA15D00B26449 /* MSALNativeAuthBaseController.swift in Sources */, + 28B28B922C6F611F0030D5C5 /* MSALAuthMethod.swift in Sources */, B2E2A9432393191D00BA2EA3 /* MSIDInteractiveRequestParameters+MSALRequest.m in Sources */, DEF9D99F296F08CE006CB384 /* MSALNativeAuthTelemetryProvider.swift in Sources */, DE9245152A3875D700C0389F /* RetrieveAccessTokenError.swift in Sources */, DE8BE7DC2A1F6BD2009642A5 /* MSALNativeAuthUserAccountResult.swift in Sources */, E224F73E2B18F11F000A7B2E /* SignInAfterResetPasswordState.swift in Sources */, 287708252A178DC500E371ED /* MSALNativeAuthSignInInitiateValidatedResponse.swift in Sources */, + 289E44AC2C9D7A9E00F6B9D7 /* MFARequestChallengeError.swift in Sources */, DEE34F89D170B71C00BC302A /* MSALNativeAuthResetPasswordPollCompletionStatus.swift in Sources */, 9BE7E3D52A1CF51500CC3A62 /* MSALNativeAuthResetPasswordValidatedResponses.swift in Sources */, 287F65182983F77D00ED90BD /* MSALNativeAuthRequestParametersKey.swift in Sources */, 04D32CAE1FD615B3000B123E /* MSALErrorConverter.m in Sources */, B26756D222921C6D000F01D7 /* MSALADFSOauth2Provider.m in Sources */, E26097C32948FC4D0060DD7C /* MSALNativeAuthLogging.swift in Sources */, + 285F585F2C5BA71600F4EFA4 /* MSALNativeAuthInternalAuthenticationMethod.swift in Sources */, 232D614C2248484C00260C42 /* MSALClaimsRequest.m in Sources */, B203459621AF77FB00B221AA /* MSALRedirectUri.m in Sources */, E2025CC92B2A182200E32871 /* MSALNativeAuthSubErrorCode.swift in Sources */, + 285F585D2C5BA4F700F4EFA4 /* MSALNativeAuthSignInIntrospectResponse.swift in Sources */, DEE34F7BD170B71C00BC302A /* MSALNativeAuthResetPasswordContinueOauth2ErrorCode.swift in Sources */, DEE34F7FD170B71C00BC302A /* MSALNativeAuthResetPasswordSubmitResponse.swift in Sources */, E284F5D929F28B4200DBED7D /* MSALNativeAuthSignUpController.swift in Sources */, @@ -6398,6 +6547,7 @@ DE92450C2A385ED800C0389F /* MSALNativeAuthCredentialsController.swift in Sources */, B28BDA90217E9EAB003E5670 /* MSALOauth2ProviderFactory.m in Sources */, B2AA5D6A23A353F200BD47D8 /* MSALSignoutParameters.m in Sources */, + 28D811E52C735D98002BE1AA /* MFASubmitChallengeError.swift in Sources */, DEE34F65D170B71C00BC302A /* MSALNativeAuthResetPasswordChallengeResponse.swift in Sources */, D61BD2B01EBD09F90007E484 /* MSALPublicClientApplication.m in Sources */, DEDB29AD29DDAF53008DA85B /* MSALNativeAuthTokenResponseError.swift in Sources */, @@ -6440,7 +6590,7 @@ 28F19BEB2A2F884D00575581 /* Array+joinScopes.swift in Sources */, DE8EC8742A026BA0003FA561 /* MSALNativeAuthSignInRequestProvider.swift in Sources */, 232D616422485BA700260C42 /* MSALIndividualClaimRequestAdditionalInfo.m in Sources */, - 287708222A151A8500E371ED /* MSALNativeAuthInternalChannelType.swift in Sources */, + 285F58652C5BCA8900F4EFA4 /* MSALNativeAuthSignInIntrospectValidatedResponse.swift in Sources */, D61BD2BD1EBD0A010007E484 /* MSALTelemetry.m in Sources */, DEE34F60D170B71C00BC302A /* MSALNativeAuthResetPasswordStartResponseError.swift in Sources */, 9BE7E3CB2A1CB70700CC3A62 /* MSALNativeAuthResetPasswordStartRequestProviderParameters.swift in Sources */, @@ -6458,6 +6608,7 @@ 289E156D2948EB8A006104D9 /* MSALNativeAuthCacheAccessor.swift in Sources */, E2C61FE729DED73700F15203 /* MSALNativeAuthSignUpChallengeResponseError.swift in Sources */, 289747B129799C6B00838C80 /* MSALNativeAuthInputValidator.swift in Sources */, + 285F58632C5BC67900F4EFA4 /* MSALNativeAuthSignInIntrospectOauth2ErrorCode.swift in Sources */, DEE34F7AD170B71C00BC302A /* MSALNativeAuthResetPasswordContinueResponseError.swift in Sources */, 28E4D9032A30ABA200280921 /* ResendCodeError.swift in Sources */, E2F6269D2A780DDE00C4A303 /* MSALNativeAuthPublicClientApplication+Internal.swift in Sources */, @@ -6473,6 +6624,7 @@ E2F890052B755355001FBC7C /* MSALNativeAuthUnknownCaseProtocol.swift in Sources */, E2EFAD092A69A34300D6C3DE /* CodeRequiredGenericResult.swift in Sources */, E2EFAD0C2A69B45100D6C3DE /* SignUpResults.swift in Sources */, + 289E44B62C9D7B0900F6B9D7 /* MFAGetAuthMethodsError.swift in Sources */, DE0D65AC29CC6A5A005798B1 /* MSALNativeAuthSignInInitiateResponse.swift in Sources */, B266391C22B4B84600FEB673 /* NSString+MSALAccountIdenfiers.m in Sources */, DEE34F87D170B71C00BC302A /* MSALNativeAuthResetPasswordPollCompletionResponse.swift in Sources */, @@ -6489,11 +6641,13 @@ B2C17B0A1FC8DB2E0070A514 /* MSIDVersion.m in Sources */, E2DC31C829B0FDB100051CE7 /* MSALNativeAuthAuthorityProvider.swift in Sources */, 28DCD0AE29D737E600C4601E /* VerifyCodeError.swift in Sources */, + 285F58612C5BC1EC00F4EFA4 /* MSALNativeAuthSignInIntrospectResponseError.swift in Sources */, E22427C82B0526660006C55E /* SignUpDelegateDispatchers.swift in Sources */, E224F7472B18F29F000A7B2E /* SignInAfterResetPasswordDelegate.swift in Sources */, E2F626B02A78130700C4A303 /* SignInAfterPreviousFlowBaseState+Internal.swift in Sources */, E22427DE2B05981A0006C55E /* SignInAfterSignUpDelegateDispatcher.swift in Sources */, E22427DB2B0594670006C55E /* CredentialsDelegateDispatcher.swift in Sources */, + 28D811ED2C760303002BE1AA /* MSALNativeAuthMFAControlling.swift in Sources */, B26756C622921C42000F01D7 /* MSALAADOauth2Provider.m in Sources */, DEE34F12D170B71C00BC302A /* MSALNativeAuthResetPasswordStartRequestParameters.swift in Sources */, 8D35C8E72A97BD0000BEC29A /* MSALNativeAuthErrorBasicAttribute.swift in Sources */, @@ -6502,20 +6656,24 @@ E2F626AA2A780F8200C4A303 /* SignInStates+Internal.swift in Sources */, 2338295722D7E49F001B8AD6 /* MSALWebviewParameters.m in Sources */, E2F626A72A780F3D00C4A303 /* SignUpStates+Internal.swift in Sources */, + 28BBB7CE2C80C5740055AF64 /* MSALNativeAuthLogMessage.swift in Sources */, 2826932A2A0974750037B93A /* MSALNativeAuthTokenRequestParameters.swift in Sources */, 28EDF93E29E6D43900A99F2A /* SignUpStartError.swift in Sources */, + 28D811E92C75FFC3002BE1AA /* MFADelegateDispatchers.swift in Sources */, 285D0D692B99C14F002A1D4A /* MSALNativeAuthTokenResult.swift in Sources */, DE94C9F029F2AF5E00C1EC1F /* MSALNativeAuthResetPasswordChallengeRequestParameters.swift in Sources */, D61BD2B31EBD09F90007E484 /* MSALPromptType.m in Sources */, DEC1E425298BE18A00948BED /* MSALNativeAuthServerTelemetry.swift in Sources */, E224F74D2B18FC9C000A7B2E /* SignInAfterPreviousFlowBaseState.swift in Sources */, 9B4EE9D82A1687AE00F243C1 /* MSALNativeAuthResetPasswordResponseValidator.swift in Sources */, + 28D811EB2C7602A1002BE1AA /* MFAResults.swift in Sources */, DEE34F61D170B71C00BC302A /* MSALNativeAuthResetPasswordStartOauth2ErrorCode.swift in Sources */, 28FDC49C2A38BFA900E38BE1 /* SignInAfterSignUpState.swift in Sources */, 28DCD0B029D738DD00C4601E /* ResetPasswordStartError.swift in Sources */, B28BBD352211DC7D00F51723 /* MSALPublicClientStatusNotifications.m in Sources */, 23A68A8220F538DE0071E435 /* MSALADFSAuthority.m in Sources */, B29A56C222826EE20023F5E6 /* MSALSerializedADALCacheProvider.m in Sources */, + 28B28B832C6F46E50030D5C5 /* MFAStates.swift in Sources */, B2C0E79F23AC7996006C9CAD /* MSALParameters.m in Sources */, DE8EC8B62A053D80003FA561 /* MSALNativeAuthESTSApiErrorCodes.swift in Sources */, B21E07B3210E542C007E3A3C /* MSALRedirectUriVerifier.m in Sources */, @@ -6525,6 +6683,7 @@ 96B5E6EE2256D180002232F9 /* MSALSliceConfig.m in Sources */, 9B839A102A4D7CF600BCC6F6 /* MSAL.docc in Sources */, B2A3C28B2145FD0F0082525C /* MSALAccountsProvider.m in Sources */, + 28D811E72C75FB10002BE1AA /* MFAStates+Internal.swift in Sources */, E2ACA47B29520C2200E98964 /* MSALNativeAuthEndpoint.swift in Sources */, 1EE776C6246C98E700F7EBFC /* MSALAuthenticationSchemePop.m in Sources */, E2DC31BC29AFA1E700051CE7 /* MSALNativeAuthPublicClientApplication.swift in Sources */, @@ -6535,6 +6694,7 @@ 232D68D8223DB8C200594BBD /* MSALSilentTokenParameters.m in Sources */, DE729ECD2A1793A100A761D9 /* MSALNativeAuthChannelType.swift in Sources */, E2EFAD162A70300B00D6C3DE /* MSALNativeAuthControllerTelemetryWrapper.swift in Sources */, + 285F58542C5BA33B00F4EFA4 /* MSALNativeAuthSignInIntrospectRequestParameters.swift in Sources */, 28DE70D629FAC16700EB75AA /* MSALNativeAuthSignInResponseValidator.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -6555,6 +6715,7 @@ DECE0F772BE3EB410036738C /* MSALNativeAuthSignInRequestProvider.swift in Sources */, DECE0F852BE3EB4B0036738C /* MSALNativeAuthSignUpStartRequestParameters.swift in Sources */, DECE0F5D2BE3EB360036738C /* ResetPasswordStates+Internal.swift in Sources */, + 28EE65162C8B0E7600015F90 /* MFADelegateDispatchers.swift in Sources */, DECE0FA42BE3EB6E0036738C /* MSALNativeAuthResetPasswordPollCompletionStatus.swift in Sources */, DECE0F532BE3EB330036738C /* ResetPasswordStartError.swift in Sources */, DECE0FB62BE3EB780036738C /* MSALNativeAuthSignInChallengeResponseError.swift in Sources */, @@ -6573,6 +6734,7 @@ 232D614D2248484C00260C42 /* MSALClaimsRequest.m in Sources */, DE8DC47C2C6621A100534E8F /* SignInAfterSignUpError.swift in Sources */, B203459721AF77FC00B221AA /* MSALRedirectUri.m in Sources */, + 28EE65142C8B0E5500015F90 /* MSALAuthMethod.swift in Sources */, DE8DC49B2C6621A900534E8F /* MSALNativeAuthSignUpRequestProvider.swift in Sources */, DE8DC4B22C6621B800534E8F /* MSALNativeAuthResetPasswordStartRequestParameters.swift in Sources */, DE8DC4A32C6621B100534E8F /* MSALNativeAuthRequestParametersKey.swift in Sources */, @@ -6614,13 +6776,16 @@ DE9244DD2A31E1D500C0389F /* MSALCIAMOauth2Provider.m in Sources */, DE8DC4B82C6621BA00534E8F /* MSALNativeAuthTokenRequestParameters.swift in Sources */, 23A68A7720F5386A0071E435 /* MSALAADAuthority.m in Sources */, + 28EE65112C8B03BD00015F90 /* MSALNativeAuthInternalAuthenticationMethod.swift in Sources */, DE8DC4712C66219E00534E8F /* ResetPasswordDelegateDispatchers.swift in Sources */, DE8DC45C2C66219600534E8F /* MSALNativeAuthResetPasswordControlling.swift in Sources */, 2338295822D7E49F001B8AD6 /* MSALWebviewParameters.m in Sources */, + 28EE65062C8B01D500015F90 /* MSALNativeAuthMFAControlling.swift in Sources */, DECE0F362BE3EB160036738C /* CodeRequiredGenericResult.swift in Sources */, DECE0F702BE3EB3C0036738C /* MSALNativeAuthPublicClientApplication.swift in Sources */, 1EF39600246DFAD200647FDB /* MSALAuthScheme.m in Sources */, 583BFD1024DC8EE80035B901 /* MSALRedirectUriVerifier.m in Sources */, + 28EE651A2C8B0FC200015F90 /* MFAStates.swift in Sources */, DE8DC4D62C6621CC00534E8F /* MSALNativeAuthErrorBasicAttribute.swift in Sources */, DE8DC4C52C6621C500534E8F /* MSALNativeAuthSignUpContinueResponse.swift in Sources */, DE8DC48D2C6621A300534E8F /* SignInAfterPreviousFlowBaseState.swift in Sources */, @@ -6629,6 +6794,7 @@ 9B839A112A4D7CF600BCC6F6 /* MSAL.docc in Sources */, DE8DC47B2C6621A100534E8F /* AttributesRequiredError.swift in Sources */, DE8DC4C42C6621C500534E8F /* MSALNativeAuthSignInInitiateResponse.swift in Sources */, + 28EE65122C8B0E3600015F90 /* MSALNativeAuthSignInIntrospectResponse.swift in Sources */, DE8DC4BD2C6621C100534E8F /* MSALNativeAuthSignUpResponseValidator.swift in Sources */, DE8DC48F2C6621A600534E8F /* SignUpDelegates.swift in Sources */, DE8DC4E92C6621D000534E8F /* MSALNativeAuthResetPasswordChallengeOauth2ErrorCode.swift in Sources */, @@ -6666,22 +6832,27 @@ DE8DC4632C66219600534E8F /* MSALNativeAuthInputValidator.swift in Sources */, DE8DC4F02C6621D600534E8F /* MSALNativeAuthTelemetryApiId.swift in Sources */, 23B1D36A22EA6E2F000954AF /* MSALPublicClientApplicationConfig.m in Sources */, + 28EE65192C8B0FBA00015F90 /* MFAStates+Internal.swift in Sources */, B221CEDE20C0AC60002F5E94 /* MSALAccountId.m in Sources */, DE8DC4AF2C6621B600534E8F /* MSALNativeAuthSignInChallengeRequestParameters.swift in Sources */, + 28EE650F2C8B039600015F90 /* MSALNativeAuthSignInIntrospectResponseError.swift in Sources */, DE8DC4992C6621A600534E8F /* SignInDelegates.swift in Sources */, DE8DC4832C6621A100534E8F /* SignUpStartError.swift in Sources */, DE8DC4742C66219E00534E8F /* SignInAfterResetPasswordDelegateDispatcher.swift in Sources */, - DE8DC4EE2C6621D300534E8F /* MSALNativeAuthInternalChannelType.swift in Sources */, + 28EE65102C8B03AA00015F90 /* MSALNativeAuthSignInIntrospectRequestParameters.swift in Sources */, DE8DC4B72C6621B800534E8F /* MSALNativeAuthRequestable.swift in Sources */, DE8DC4E82C6621D000534E8F /* MSALNativeAuthResetPasswordStartResponseError.swift in Sources */, DE8DC4682C66219600534E8F /* MSALNativeAuthControllerFactory.swift in Sources */, DE8DC46C2C66219600534E8F /* MSALNativeAuthSignUpControlling.swift in Sources */, DE8DC4CC2C6621C700534E8F /* MSALNativeAuthResetPasswordSubmitResponse.swift in Sources */, DE8DC4EC2C6621D300534E8F /* MSALNativeAuthTokenResponseError.swift in Sources */, + 28EE65152C8B0E6B00015F90 /* MFADelegates.swift in Sources */, 886F516529CCA58900F09471 /* MSALCIAMAuthority.m in Sources */, 232D615F22485B4600260C42 /* MSALIndividualClaimRequest.m in Sources */, + 285E09F72C93340000492A2E /* MSALNativeAuthLogMessage.swift in Sources */, DE8DC46D2C66219600534E8F /* MSALNativeAuthSignInController.swift in Sources */, B26756C122921A71000F01D7 /* MSALOauth2Provider.m in Sources */, + 28EE650E2C8B027D00015F90 /* MFAResults.swift in Sources */, DE8DC47A2C6621A100534E8F /* ResendCodeError.swift in Sources */, D673F0921E4CE6D70018BA91 /* MSALPublicClientApplication.m in Sources */, DE8DC4E72C6621D000534E8F /* MSALNativeAuthResetPasswordPollCompletionOauth2ErrorCode.swift in Sources */, @@ -6689,6 +6860,7 @@ DE8DC4662C66219600534E8F /* MSALNativeAuthResultFactory.swift in Sources */, DE8DC4842C6621A300534E8F /* SignInStates+Internal.swift in Sources */, DE8DC49F2C6621AE00534E8F /* MSALNativeAuthResetPasswordStartRequestProviderParameters.swift in Sources */, + 289E44AD2C9D7A9E00F6B9D7 /* MFARequestChallengeError.swift in Sources */, DE8DC4B02C6621B600534E8F /* MSALNativeAuthSignInInitiateRequestParameters.swift in Sources */, B2C17B0B1FC8DB2E0070A514 /* MSIDVersion.m in Sources */, DE8DC4BF2C6621C100534E8F /* MSALNativeAuthResetPasswordResponseValidator.swift in Sources */, @@ -6713,6 +6885,7 @@ B253151C23DD607600432133 /* MSALDeviceInformation.m in Sources */, DE8DC4BB2C6621BD00534E8F /* MSALNativeAuthSignInChallengeValidatedResponse.swift in Sources */, 94E876CE1E492D6000FB96ED /* MSALAuthority.m in Sources */, + 289E44B72C9D7B0900F6B9D7 /* MFAGetAuthMethodsError.swift in Sources */, DE8DC4DB2C6621CC00534E8F /* MSALNativeAuthSignUpStartResponseError.swift in Sources */, DE8DC4E52C6621D000534E8F /* MSALNativeAuthResetPasswordContinueResponseError.swift in Sources */, DE8DC4B12C6621B800534E8F /* MSALNativeAuthResetPasswordContinueRequestParameters.swift in Sources */, @@ -6741,6 +6914,7 @@ DE8DC4762C66219E00534E8F /* MSALNativeAuthLogging.swift in Sources */, DE8DC49C2C6621A900534E8F /* MSALNativeAuthSignUpStartRequestProviderParameters.swift in Sources */, 1EE776C7246C98E700F7EBFC /* MSALAuthenticationSchemePop.m in Sources */, + 28EE65182C8B0FB200015F90 /* MFASubmitChallengeError.swift in Sources */, 23A68A7D20F538B90071E435 /* MSALB2CAuthority.m in Sources */, DE8DC4E62C6621D000534E8F /* MSALNativeAuthResetPasswordChallengeResponseError.swift in Sources */, DE8DC4B42C6621B800534E8F /* MSALNativeAuthRequestContext.swift in Sources */, @@ -6753,12 +6927,14 @@ DE8DC4D52C6621CC00534E8F /* MSALNativeAuthSignUpChallengeResponseError.swift in Sources */, DE8DC4852C6621A300534E8F /* SignInAfterPreviousFlowBaseState+Internal.swift in Sources */, DE8DC49E2C6621AE00534E8F /* MSALNativeAuthResetPasswordRequestProvider.swift in Sources */, + 28EE65252C8B10F000015F90 /* MSALNativeAuthSignInIntrospectOauth2ErrorCode.swift in Sources */, DE8DC4E32C6621D000534E8F /* MSALNativeAuthResetPasswordContinueOauth2ErrorCode.swift in Sources */, DE8DC4722C66219E00534E8F /* SignInAfterSignUpDelegateDispatcher.swift in Sources */, DE8DC4952C6621A600534E8F /* CredentialsDelegates.swift in Sources */, DE8DC4672C66219600534E8F /* MSALNativeAuthSignInParameters.swift in Sources */, 0D96DB3827850E8200DEAF87 /* MSALWipeCacheForAllAccountsConfig.m in Sources */, DE8DC4C92C6621C700534E8F /* MSALNativeAuthResetPasswordChallengeResponse.swift in Sources */, + 28EE65132C8B0E4500015F90 /* MSALNativeAuthSignInIntrospectValidatedResponse.swift in Sources */, DE8DC4D82C6621CC00534E8F /* MSALNativeAuthRequiredAttributeInternal.swift in Sources */, 232D68D9223DB8C200594BBD /* MSALSilentTokenParameters.m in Sources */, DE8DC4612C66219600534E8F /* SignUpResults.swift in Sources */, @@ -6772,6 +6948,7 @@ buildActionMask = 2147483647; files = ( 23A169B52073325500B051F3 /* MSALPublicClientApplicationTests.m in Sources */, + 28A600A12C78BA8900455666 /* MFAGetAuthMethodsErrorTests.swift in Sources */, D61F5BC01E5913BE00912CB8 /* SFSafariViewController+TestOverrides.m in Sources */, B2725ED022C04689009B454A /* MSALLegacySharedAccountFactoryTests.m in Sources */, E22427EE2B06637C0006C55E /* SignUpAttributesRequiredDelegateDispatcherTests.swift in Sources */, @@ -6811,6 +6988,7 @@ 04D32CD01FD8AFF3000B123E /* MSALErrorConverterTests.m in Sources */, E2F4DB2D2A1F5714009FBCD0 /* MSALNativeAuthSignUpContinueOauth2ErrorCodeTests.swift in Sources */, 287F6524298401AE00ED90BD /* MSALNativeAuthResponseSerializerTests.swift in Sources */, + 28A600A62C78BDC100455666 /* AwaitingMFAStateTests.swift in Sources */, E2CD2E4F29FC0451009F8FFA /* SignUpPasswordRequiredStateTests.swift in Sources */, E22427F62B066E850006C55E /* SignInPasswordRequiredDelegateDispatcherTests.swift in Sources */, E2CE910C2B0BA3BB0009AEDD /* SignUpStartErrorTests.swift in Sources */, @@ -6822,6 +7000,7 @@ B2725E7F22BD88BE009B454A /* NSStringAccountIdentifiersTest.m in Sources */, 287F64D4297EC29400ED90BD /* MSALNativeAuthTelemetryProviderTests.swift in Sources */, E22952682A1A4FCB00EDD58C /* MSALNativeAuthSignUpResponseValidatorTests.swift in Sources */, + 289E44B92C9D843F00F6B9D7 /* MFARequestChallengeErrorTests.swift in Sources */, E286E2DD2A1BAEA800666DD0 /* MSALNativeAuthSignUpControllerTests.swift in Sources */, B2725EC522BF4865009B454A /* MSALMockExternalAccountHandler.m in Sources */, E2C190772B20DF4300095534 /* SignInAfterResetPasswordErrorTests.swift in Sources */, @@ -6845,6 +7024,7 @@ 9B61C91C2A27E57C00CE9E3A /* MSALNativeAuthResetPasswordResponseValidatorMock.swift in Sources */, DE40A4D32A8F80C100928CEE /* MSALNativeAuthSignUpContinueResponseErrorTests.swift in Sources */, E2CE91162B0BA4490009AEDD /* VerifyCodeErrorTests.swift in Sources */, + 28A6009F2C78A27C00455666 /* MFASubmitChallengeDelegateDispatcherTests.swift in Sources */, DE94C9E029F198D600C1EC1F /* MSALNativeAuthResetPasswordStartRequestParametersTest.swift in Sources */, DE14096D2A38DF41008E6F1E /* MSALNativeAuthCredentialsControllerTests.swift in Sources */, DE5738B42A8E74DC00D9120D /* MSALNativeAuthSignInInitiateValidatedErrorTypeTests.swift in Sources */, @@ -6876,6 +7056,7 @@ 287F64D5297EC29400ED90BD /* MSALNativeAuthCurrentRequestTelemetryTests.swift in Sources */, E22428072B0676970006C55E /* DispatchAccessTokenRetrieveCompletedTests.swift in Sources */, A0274CBE24B432B100BD198D /* MSALAuthSchemeTests.m in Sources */, + 28A600A32C78BA9C00455666 /* MFASubmitChallengeErrorTests.swift in Sources */, E22427F22B0668910006C55E /* SignInPasswordStartDelegateDispatcherTests.swift in Sources */, E2F8900E2B75546A001FBC7C /* MSALNativeAuthUnknownCaseProtocolTests.swift in Sources */, E2025D202B2B8EEA00E32871 /* MSALNativeAuthSubErrorCodeTests.swift in Sources */, @@ -6887,6 +7068,7 @@ E22427FA2B0670600006C55E /* SignInVerifyCodeDelegateDispatcherTests.swift in Sources */, E2EBD62A2A1BB7700049467A /* MSALNativeAuthSignUpResponseValidatorMock.swift in Sources */, E25BC0852995430B00588549 /* MSALNativeAuthFactoriesMocks.swift in Sources */, + 28A600962C78843C00455666 /* MFASendChallengeDelegateDispatcherTests.swift in Sources */, E2CE91122B0BA3FC0009AEDD /* SignInStartErrorTests.swift in Sources */, E2BC029C29D766CB00041DBC /* MSALNativeAuthSignUpContinueRequestParametersTest.swift in Sources */, E2960A112A1F4D2F000F441B /* MSALNativeAuthSignUpChallengeResponseErrorTests.swift in Sources */, @@ -6896,6 +7078,7 @@ DE946FE52B0F713A00978493 /* MSALNativeAuthHTTPRequestMock.swift in Sources */, B2725ED222C0469A009B454A /* MSALLegacySharedAccountsProviderTests.m in Sources */, E2BC029A29D766B200041DBC /* MSALNativeAuthSignUpStartRequestParametersTest.swift in Sources */, + 28A600AA2C78E09F00455666 /* MSALNativeAuthMFAControllerTests.swift in Sources */, E2C1D2D429A3992100B26449 /* MSALNativeAuthBaseControllerTests.swift in Sources */, DE40A4CA2A8F801200928CEE /* MSALNativeAuthSignUpChallengeOauth2ErrorCodeTests.swift in Sources */, E23E956929D5BD6B001DC59C /* MSALNativeAuthSignUpRequestProviderTests.swift in Sources */, @@ -6905,6 +7088,7 @@ DE54B59F2A4452DB00460B34 /* MSALNativeAuthTokenResponseValidatorTests.swift in Sources */, DE5738BA2A8F780E00D9120D /* MSALNativeAuthResetPasswordSubmitOauth2ErrorCodeTests.swift in Sources */, E20C21842A7A6CA400E31598 /* SignInCodeRequiredStateTests.swift in Sources */, + 28A600A82C78BDD200455666 /* MFARequiredStateTests.swift in Sources */, B2725EAC22BF2759009B454A /* MSALExternalAccountHandlerTests.m in Sources */, E22427FF2B06725C0006C55E /* ResetPasswordVerifyCodeDelegateDispatcherTests.swift in Sources */, 232D6192224C53E500260C42 /* MSALClaimsRequestTests.m in Sources */, @@ -6912,10 +7096,12 @@ E25E6E5A2AA7727D0094461E /* MSALNativeAuthCredentialsControllerMock.swift in Sources */, B286B9FD238A07A5007833AD /* MSALAcquireTokenTests.m in Sources */, E22427F42B066BBC0006C55E /* SignInStartDelegateDispatcherTests.swift in Sources */, + 28A6009D2C78A26D00455666 /* MFAGetAuthMethodsDelegateDispatcherTests.swift in Sources */, DE5738B22A8E71D500D9120D /* MSALNativeAuthResetPasswordContinueResponseErrorTests.swift in Sources */, 9B6EECEF2A3146ED008ABA50 /* MSALNativeAuthResetPasswordResponseValidatorTests.swift in Sources */, E2CE911E2B0BA4A60009AEDD /* SignInAfterSignUpErrorTests.swift in Sources */, E2CE910E2B0BA3D30009AEDD /* PasswordRequiredErrorTests.swift in Sources */, + 28A6009B2C7898DA00455666 /* MFADelegatesSpies.swift in Sources */, 28FDC4AE2A38D81100E38BE1 /* MSALNativeAuthSignInControllerMock.swift in Sources */, B281B33B226BC225009619AB /* MSALPublicClientApplicationConfigTests.m in Sources */, E2F5BE9A29896ADB00C67EC7 /* MSALNativeAuthSignInControllerTests.swift in Sources */, @@ -6934,9 +7120,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 28EE651E2C8B103B00015F90 /* MFAGetAuthMethodsDelegateDispatcherTests.swift in Sources */, DE55549D2C0A1E0C008ECA1A /* SignInTestsValidatorHelpers.swift in Sources */, DECE10182BE3F07C0036738C /* MSALNativeAuthTelemetryProviderTests.swift in Sources */, DE5554AA2C0A1E0C008ECA1A /* MSALNativeAuthSignInControllerMock.swift in Sources */, + 28EE651F2C8B107100015F90 /* MFASendChallengeDelegateDispatcherTests.swift in Sources */, DE5554B52C0A1E16008ECA1A /* SignUpResendCodeDelegateDispatcherTests.swift in Sources */, DECE10282BE3F0830036738C /* MSALNativeAuthResetPasswordPollCompletionOauth2ErrorCodeTests.swift in Sources */, DE5554D62C0A1E35008ECA1A /* MSALNativeAuthSignUpResponseValidatorTests.swift in Sources */, @@ -6951,6 +7139,7 @@ DE8DC4FF2C6621EA00534E8F /* MSALNativeAuthFactoriesMocks.swift in Sources */, DE8DC5632C66221A00534E8F /* MSALNativeAuthSignUpRequestProviderTests.swift in Sources */, DE8DC56A2C66221A00534E8F /* MSALNativeAuthCustomErrorSerializerTests.swift in Sources */, + 28EE65232C8B109D00015F90 /* AwaitingMFAStateTests.swift in Sources */, DE8DC51B2C6621F100534E8F /* SignUpStartDelegateDispatcherTests.swift in Sources */, DE8DC5562C66221300534E8F /* MSALNativeAuthSignUpContinueRequestParametersTest.swift in Sources */, DE8DC50D2C6621EA00534E8F /* MSALNativeAuthConfigStubs.swift in Sources */, @@ -6974,6 +7163,7 @@ 23F32F0D1FF4789200B2905E /* MSIDTestURLResponse+MSAL.m in Sources */, DE8DC5602C66221A00534E8F /* MSALNativeAuthResponseCorrelatableTests.swift in Sources */, DE8DC4FC2C6621E700534E8F /* MSALNativeAuthSignUpControllerTests.swift in Sources */, + 28EE65212C8B108A00015F90 /* MFAGetAuthMethodsErrorTests.swift in Sources */, DE8DC5492C66220D00534E8F /* MSALNativeAuthResetPasswordChallengeOauth2ErrorCodeTests.swift in Sources */, DE8DC52A2C6621F700534E8F /* SignUpStartErrorTests.swift in Sources */, DE8DC53C2C66220700534E8F /* MSALNativeAuthTelemetryProviderTests.swift in Sources */, @@ -6990,6 +7180,7 @@ 6525115A29CD84A000D3B876 /* MSALPublicClientApplicationTests.m in Sources */, DECE10112BE3F0760036738C /* SignUpPasswordRequiredStateTests.swift in Sources */, DE5554B02C0A1E12008ECA1A /* SignInPasswordRequiredDelegateDispatcherTests.swift in Sources */, + 28EE651D2C8B101000015F90 /* MFADelegatesSpies.swift in Sources */, DECE10202BE3F07F0036738C /* MSALNativeAuthSignUpContinueOauth2ErrorCodeTests.swift in Sources */, DE5554992C0A1E07008ECA1A /* MSALNativeAuthBaseControllerTests.swift in Sources */, DECE10252BE3F0830036738C /* MSALNativeAuthESTSApiErrorDescriptionsTests.swift in Sources */, @@ -7004,9 +7195,11 @@ DE8DC50E2C6621EA00534E8F /* MSALNativeAuthResetPasswordControllerMock.swift in Sources */, DE8DC5422C66220A00534E8F /* MSALNativeAuthSignUpContinueOauth2ErrorCodeTests.swift in Sources */, DE8DC5222C6621F500534E8F /* ResetPasswordResendCodeDelegateDispatcherTests.swift in Sources */, + 28EE65242C8B10AA00015F90 /* MFARequiredStateTests.swift in Sources */, DE8DC5472C66220D00534E8F /* MSALNativeAuthResetPasswordStartOauth2ErrorCodeTests.swift in Sources */, DE8DC5332C6621FD00534E8F /* SignUpPasswordRequiredStateTests.swift in Sources */, DE0A5E892C6B670E004A4AEC /* MSALLogMaskTests.m in Sources */, + 28EE65202C8B107D00015F90 /* MFASubmitChallengeDelegateDispatcherTests.swift in Sources */, DE8DC51F2C6621F100534E8F /* SignUpVerifyCodeDelegateDispatcherTests.swift in Sources */, B29A56D62283D7430023F5E6 /* MSALAADAuthorityTests.m in Sources */, DECE10372BE3F0940036738C /* MSALNativeAuthSignInInitiateRequestParametersTest.swift in Sources */, @@ -7042,6 +7235,7 @@ D69ADB381E516F9B00952049 /* MSALTestCase.m in Sources */, DE8DC5112C6621EA00534E8F /* MSALNativeAuthSignUpResponseValidatorMock.swift in Sources */, DE8DC52E2C6621F800534E8F /* ResendCodeErrorTests.swift in Sources */, + 28EE65222C8B109300015F90 /* MFASubmitChallengeErrorTests.swift in Sources */, 04D32CD11FD8AFF3000B123E /* MSALErrorConverterTests.m in Sources */, DE8DC5382C66220000534E8F /* MSALNativeAuthUserAccountResultTests.swift in Sources */, DE8DC4F62C6621E200534E8F /* MSALNativeAuthTelemetryTestDispatcher.swift in Sources */, @@ -7094,6 +7288,8 @@ DE8DC5522C66221000534E8F /* MSALNativeAuthResetPasswordResponseValidatorTests.swift in Sources */, DE8DC5202C6621F500534E8F /* ResetPasswordStartDelegateDispatcherTests.swift in Sources */, DE8DC55C2C66221700534E8F /* MSALNativeAuthResetPasswordStartRequestParametersTest.swift in Sources */, + 28EE651C2C8B0FF500015F90 /* MSALNativeAuthMFAControllerTests.swift in Sources */, + 289E44BA2C9D843F00F6B9D7 /* MFARequestChallengeErrorTests.swift in Sources */, DE8DC56C2C66221C00534E8F /* MSALNativeLoggingTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -7127,6 +7323,7 @@ DE1BD0E02C3C27C100B0888E /* MSALNativeAuthSignUpStartIntegrationTests.swift in Sources */, DE1BD0E62C3C27CD00B0888E /* MSALNativeAuthResetPasswordChallengeIntegrationTests.swift in Sources */, DE1BD0E42C3C27C900B0888E /* MSALNativeAuthSignInChallengeIntegrationTests.swift in Sources */, + 28EE651B2C8B0FDD00015F90 /* MSALNativeAuthSignInIntrospectIntegrationTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -7138,9 +7335,11 @@ DE1BD1072C3C284C00B0888E /* MSALNativeAuthResetPasswordEndToEndTests.swift in Sources */, DE9EB8622C5CE44B00328AA4 /* AttributesStub.swift in Sources */, DE1BD1012C3C283C00B0888E /* MSALNativeAuthSignUpUsernameEndToEndTests.swift in Sources */, + 28188F662C8F4C1100CFDD05 /* MFADelegateSpies.swift in Sources */, DE1BD1032C3C284100B0888E /* SignUpDelegateSpies.swift in Sources */, DE1BD1092C3C285D00B0888E /* MSALNativeAuthSignOutEndToEndTests.swift in Sources */, DE1BD1052C3C284700B0888E /* MSALNativeAuthSignInUsernameEndToEndTests.swift in Sources */, + 28188F632C8F48BD00CFDD05 /* MSALNativeAuthSignInWithMFAEndToEndTests.swift in Sources */, DE1BD1002C3C283900B0888E /* MSALNativeAuthEmailCodeRetriever.swift in Sources */, DE1BD1042C3C284400B0888E /* MSALNativeAuthSignInUsernameAndPasswordEndToEndTests.swift in Sources */, DE1BD1022C3C283F00B0888E /* MSALNativeAuthSignUpUsernameAndPasswordEndToEndTests.swift in Sources */, @@ -7269,11 +7468,6 @@ target = D672279E1EBD111900F3422A /* unit-test-host */; targetProxy = D67227B61EBD112800F3422A /* PBXContainerItemProxy */; }; - D6BFE3DB1EB42D3200F2B7E8 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 962E37A61E720C5D00DE71FE /* MSAL Test Automation (iOS) */; - targetProxy = D6BFE3DA1EB42D3200F2B7E8 /* PBXContainerItemProxy */; - }; DE1BD0D52C3C275200B0888E /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = D65A6F4F1E3FD32D00C69FBA /* MSAL (Mac Framework) */; diff --git a/MSAL/src/native_auth/network/MSALNativeAuthInternalChannelType.swift b/MSAL/src/native_auth/MSALNativeAuthLogMessage.swift similarity index 79% rename from MSAL/src/native_auth/network/MSALNativeAuthInternalChannelType.swift rename to MSAL/src/native_auth/MSALNativeAuthLogMessage.swift index 6d8bdd666b..d9d82520c6 100644 --- a/MSAL/src/native_auth/network/MSALNativeAuthInternalChannelType.swift +++ b/MSAL/src/native_auth/MSALNativeAuthLogMessage.swift @@ -20,18 +20,11 @@ // 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. +// THE SOFTWARE. -enum MSALNativeAuthInternalChannelType: String, Decodable { - case phone - case email +import Foundation - func toPublicChannelType() -> MSALNativeAuthChannelType { - switch self { - case .phone: - return .phone - case .email: - return .email - } - } +enum MSALNativeAuthLogMessage { + static let privatePreviewLog = + "Warning ⚠️: this API is experimental. It may be changed in the future without notice. Do not use in production applications." } diff --git a/MSAL/src/native_auth/controllers/responses/MFAResults.swift b/MSAL/src/native_auth/controllers/responses/MFAResults.swift new file mode 100644 index 0000000000..b28c43606d --- /dev/null +++ b/MSAL/src/native_auth/controllers/responses/MFAResults.swift @@ -0,0 +1,41 @@ +// +// 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 Foundation + +enum MFARequestChallengeResult { + case verificationRequired(sentTo: String, channelTargetType: MSALNativeAuthChannelType, codeLength: Int, newState: MFARequiredState) + case selectionRequired(authMethods: [MSALAuthMethod], newState: MFARequiredState) + case error(error: MFARequestChallengeError, newState: MFARequiredState?) +} + +enum MFAGetAuthMethodsResult { + case selectionRequired(authMethods: [MSALAuthMethod], newState: MFARequiredState) + case error(error: MFAGetAuthMethodsError, newState: MFARequiredState?) +} + +enum MFASubmitChallengeResult { + case completed(MSALNativeAuthUserAccountResult) + case error(error: MFASubmitChallengeError, newState: MFARequiredState?) +} diff --git a/MSAL/src/native_auth/controllers/responses/SignInResults.swift b/MSAL/src/native_auth/controllers/responses/SignInResults.swift index 5607cfb8b8..50e6ba05ca 100644 --- a/MSAL/src/native_auth/controllers/responses/SignInResults.swift +++ b/MSAL/src/native_auth/controllers/responses/SignInResults.swift @@ -28,6 +28,7 @@ enum SignInStartResult { case completed(MSALNativeAuthUserAccountResult) case codeRequired(newState: SignInCodeRequiredState, sentTo: String, channelTargetType: MSALNativeAuthChannelType, codeLength: Int) case passwordRequired(newState: SignInPasswordRequiredState) + case awaitingMFA(newState: AwaitingMFAState) case error(SignInStartError) } @@ -35,6 +36,7 @@ typealias SignInResendCodeResult = CodeRequiredGenericResult + typealias MFAGetAuthMethodsControllerResponse = MSALNativeAuthControllerTelemetryWrapper + typealias MFASubmitChallengeControllerResponse = MSALNativeAuthControllerTelemetryWrapper + + func requestChallenge( + continuationToken: String, + authMethod: MSALAuthMethod?, + context: MSALNativeAuthRequestContext, + scopes: [String] + ) async -> MFARequestChallengeControllerResponse + + func getAuthMethods( + continuationToken: String, + context: MSALNativeAuthRequestContext, + scopes: [String] + ) async -> MFAGetAuthMethodsControllerResponse + + func submitChallenge( + challenge: String, + continuationToken: String, + context: MSALNativeAuthRequestContext, + scopes: [String]) async -> MFASubmitChallengeControllerResponse +} diff --git a/MSAL/src/native_auth/controllers/sign_in/MSALNativeAuthSignInController.swift b/MSAL/src/native_auth/controllers/sign_in/MSALNativeAuthSignInController.swift index 0077efc912..3b3fe1c330 100644 --- a/MSAL/src/native_auth/controllers/sign_in/MSALNativeAuthSignInController.swift +++ b/MSAL/src/native_auth/controllers/sign_in/MSALNativeAuthSignInController.swift @@ -26,7 +26,7 @@ // swiftlint:disable file_length // swiftlint:disable:next type_body_length -final class MSALNativeAuthSignInController: MSALNativeAuthTokenController, MSALNativeAuthSignInControlling { +final class MSALNativeAuthSignInController: MSALNativeAuthTokenController, MSALNativeAuthSignInControlling, MSALNativeAuthMFAControlling { // MARK: - Variables @@ -97,15 +97,17 @@ final class MSALNativeAuthSignInController: MSALNativeAuthTokenController, MSALN } } + // swiftlint:disable:next function_body_length func signIn( username: String, continuationToken: String?, scopes: [String]?, + telemetryId: MSALNativeAuthTelemetryApiId, context: MSALNativeAuthRequestContext ) async -> SignInAfterPreviousFlowControllerResponse { MSALLogger.log(level: .info, context: context, format: "SignIn after previous flow started") let telemetryInfo = TelemetryInfo( - event: makeAndStartTelemetryEvent(id: .telemetryApiIdSignInAfterSignUp, context: context), + event: makeAndStartTelemetryEvent(id: telemetryId, context: context), context: context ) guard let continuationToken = continuationToken else { @@ -140,6 +142,12 @@ final class MSALNativeAuthSignInController: MSALNativeAuthTokenController, MSALN self?.stopTelemetryEvent(telemetryInfo.event, context: context, delegateDispatcherResult: result) })) }, + onAwaitingMFA: { _ in + let error = SignInAfterSignUpError(correlationId: context.correlationId()) + MSALLogger.log(level: .error, context: context, format: "SignIn: received unexpected MFA required API result") + self.stopTelemetryEvent(telemetryInfo.event, context: context, error: error) + continuation.resume(returning: .init(.failure(error), correlationId: context.correlationId())) + }, onError: { error in let error = SignInAfterSignUpError( message: error.errorDescription, @@ -153,79 +161,20 @@ final class MSALNativeAuthSignInController: MSALNativeAuthTokenController, MSALN } } - // swiftlint:disable:next function_body_length func submitCode( _ code: String, continuationToken: String, context: MSALNativeAuthRequestContext, scopes: [String] ) async -> SignInSubmitCodeControllerResponse { - let telemetryInfo = TelemetryInfo( - event: makeAndStartTelemetryEvent(id: .telemetryApiIdSignInSubmitCode, context: context), - context: context - ) - guard let request = createTokenRequest( - scopes: scopes, + let event = makeAndStartTelemetryEvent(id: .telemetryApiIdSignInSubmitCode, context: context) + return await submitCode( + code, continuationToken: continuationToken, - oobCode: code, - grantType: .oobCode, - includeChallengeType: false, - context: context) else { - MSALLogger.log(level: .error, context: context, format: "SignIn, submit code: unable to create token request") - - return processSubmitCodeFailure( - errorType: .generalError(nil), - telemetryInfo: telemetryInfo, - scopes: scopes, - continuationToken: continuationToken, - context: context - ) - } - let config = factory.makeMSIDConfiguration(scopes: scopes) - let response = await performAndValidateTokenRequest(request, config: config, context: context) - switch response { - case .success(let tokenResponse): - return await withCheckedContinuation { continuation in - handleMSIDTokenResponse( - tokenResponse: tokenResponse, - context: context, - telemetryInfo: telemetryInfo, - config: config, - onSuccess: { accountResult in - continuation.resume( - returning: .init( - .completed(accountResult), - correlationId: context.correlationId(), - telemetryUpdate: { [weak self] result in - self?.stopTelemetryEvent(telemetryInfo.event, context: context, delegateDispatcherResult: result) - })) - }, - onError: { [weak self] error in - MSALLogger.logPII( - level: .error, - context: context, - format: "SignIn submit code, token request failed with error \(MSALLogMask.maskPII(error.errorDescription))" - ) - guard let self = self else { return } - continuation.resume(returning: self.processSubmitCodeFailure( - errorType: .generalError(nil), - telemetryInfo: telemetryInfo, - scopes: scopes, - continuationToken: continuationToken, - context: context - )) - } - ) - } - case .error(let errorType): - return processSubmitCodeFailure( - errorType: errorType, - telemetryInfo: telemetryInfo, - scopes: scopes, - continuationToken: continuationToken, - context: context - ) - } + context: context, + scopes: scopes, + telemetryEvent: event + ) } // swiftlint:disable:next function_body_length @@ -301,16 +250,35 @@ final class MSALNativeAuthSignInController: MSALNativeAuthTokenController, MSALN continuationToken: continuationToken, scopes: scopes ) + case .strongAuthRequired(let newContinuationToken): + MSALLogger.log(level: .info, context: context, format: "Strong authentication required.") + let state = AwaitingMFAState( + controller: self, + scopes: scopes, + continuationToken: newContinuationToken, + correlationId: context.correlationId() + ) + return .init( + .awaitingMFA(newState: state), + correlationId: context.correlationId(), + telemetryUpdate: { [weak self] result in + self?.stopTelemetryEvent(telemetryInfo.event, context: context, delegateDispatcherResult: result) + }) } } + // swiftlint:disable:next function_body_length func resendCode( continuationToken: String, context: MSALNativeAuthRequestContext, scopes: [String] ) async -> SignInResendCodeControllerResponse { let event = makeAndStartTelemetryEvent(id: .telemetryApiIdSignInResendCode, context: context) - let result = await performAndValidateChallengeRequest(continuationToken: continuationToken, context: context) + let result = await performAndValidateChallengeRequest( + continuationToken: continuationToken, + context: context, + logErrorMessage: "SignIn ResendCode: cannot create challenge request object" + ) switch result { case .passwordRequired: let error = ResendCodeError(correlationId: context.correlationId()) @@ -351,11 +319,224 @@ final class MSALNativeAuthSignInController: MSALNativeAuthTokenController, MSALN telemetryUpdate: { [weak self] result in self?.stopTelemetryEvent(event, context: context, delegateDispatcherResult: result) }) + case .introspectRequired: + let error = ResendCodeError(correlationId: context.correlationId()) + MSALLogger.log(level: .error, context: context, format: "ResendCode: received unexpected introspect required API result") + self.stopTelemetryEvent(event, context: context, error: error) + return .init(.error(error: error, newState: nil), correlationId: context.correlationId()) + } + } + + // swiftlint:disable:next function_body_length + func requestChallenge( + continuationToken: String, + authMethod: MSALAuthMethod?, + context: MSALNativeAuthRequestContext, + scopes: [String] + ) async -> MFARequestChallengeControllerResponse { + let event = makeAndStartTelemetryEvent(id: .telemetryApiIdMFARequestChallenge, context: context) + let result = await performAndValidateChallengeRequest( + continuationToken: continuationToken, + context: context, + logErrorMessage: "MFA RequestChallenge: cannot create challenge request object", + mfaAuthMethodId: authMethod?.id + ) + switch result { + case .passwordRequired: + let error = MFARequestChallengeError(type: .generalError, correlationId: context.correlationId()) + MSALLogger.log(level: .error, context: context, format: "MFA request challenge: received unexpected password required API result") + stopTelemetryEvent(event, context: context, error: error) + return .init(.error(error: error, newState: nil), correlationId: context.correlationId()) + case .error(let challengeError): + let error = challengeError.convertToMFARequestChallengeError(correlationId: context.correlationId()) + MSALLogger.logPII( + level: .error, + context: context, + format: "MFA request challenge: received challenge error response: \(MSALLogMask.maskPII(error.errorDescription))" + ) + stopTelemetryEvent(event, context: context, error: error) + return .init(.error( + error: error, + newState: MFARequiredState( + controller: self, + scopes: scopes, + continuationToken: continuationToken, + correlationId: context.correlationId() + ) + ), correlationId: context.correlationId()) + case .codeRequired(let newContinuationToken, let sentTo, let channelType, let codeLength): + let state = MFARequiredState( + controller: self, + scopes: scopes, + continuationToken: newContinuationToken, + correlationId: context.correlationId() + ) + return .init( + .verificationRequired( + sentTo: sentTo, + channelTargetType: channelType, + codeLength: codeLength, + newState: state + ), + correlationId: context.correlationId(), + telemetryUpdate: { [weak self] result in + self?.stopTelemetryEvent(event, context: context, delegateDispatcherResult: result) + }) + case .introspectRequired: + let telemetryInfo = TelemetryInfo(event: event, context: context) + let response = await performAndValidateIntrospectRequest(continuationToken: continuationToken, context: context) + let introspectResponse = handleIntrospectResponse( + response, scopes: scopes, + telemetryInfo: telemetryInfo, + continuationToken: continuationToken + ) + switch introspectResponse.result { + case .selectionRequired(let authMethods, let newState): + return .init(.selectionRequired( + authMethods: authMethods, + newState: newState), + correlationId: introspectResponse.correlationId, + telemetryUpdate: { [weak self] result in + self?.stopTelemetryEvent(telemetryInfo.event, context: telemetryInfo.context, delegateDispatcherResult: result) + }) + case .error(let error, let newState): + let mfaRequestChallengeError = error.toMFARequestChallengeError() + return .init(.error(error: mfaRequestChallengeError, newState: newState), correlationId: introspectResponse.correlationId) + } + } + } + + func getAuthMethods( + continuationToken: String, + context: MSALNativeAuthRequestContext, + scopes: [String] + ) async -> MFAGetAuthMethodsControllerResponse { + let event = makeAndStartTelemetryEvent(id: .telemetryApiIdMFAGetAuthMethods, context: context) + let result = await performAndValidateIntrospectRequest(continuationToken: continuationToken, context: context) + let telemetryInfo = TelemetryInfo(event: event, context: context) + return handleIntrospectResponse(result, scopes: scopes, telemetryInfo: telemetryInfo, continuationToken: continuationToken) + } + + func submitChallenge( + challenge: String, + continuationToken: String, + context: MSALNativeAuthRequestContext, + scopes: [String] + ) async -> MFASubmitChallengeControllerResponse { + let event = makeAndStartTelemetryEvent(id: .telemetryApiIdMFASubmitChallenge, context: context) + let response = await submitCode( + challenge, + continuationToken: continuationToken, + context: context, + scopes: scopes, + telemetryEvent: event + ) + switch response.result { + case .completed(let accountResult): + return .init( + .completed(accountResult), + correlationId: response.correlationId, + telemetryUpdate: { [weak self] result in + self?.stopTelemetryEvent(event, context: context, delegateDispatcherResult: result) + }) + case .error(let error, let newState): + let submitChallengeError = MFASubmitChallengeError(error: error) + var mfaState: MFARequiredState? + if let newState { + mfaState = MFARequiredState( + controller: self, + scopes: newState.scopes, + continuationToken: newState.continuationToken, + correlationId: newState.correlationId + ) + } + return .init(.error(error: submitChallengeError, newState: mfaState), correlationId: response.correlationId) } } // MARK: - Private + // swiftlint:disable:next function_body_length + private func submitCode( + _ code: String, + continuationToken: String, + context: MSALNativeAuthRequestContext, + scopes: [String], + telemetryEvent: MSIDTelemetryAPIEvent? + ) async -> SignInSubmitCodeControllerResponse { + let telemetryInfo = TelemetryInfo( + event: telemetryEvent, + context: context + ) + guard let request = createTokenRequest( + scopes: scopes, + continuationToken: continuationToken, + oobCode: code, + grantType: .oobCode, + includeChallengeType: false, + context: context) else { + MSALLogger.log(level: .error, context: context, format: "Submit code: unable to create token request") + + return processSubmitCodeFailure( + errorType: .generalError(nil), + telemetryInfo: telemetryInfo, + scopes: scopes, + continuationToken: continuationToken, + context: context + ) + } + let config = factory.makeMSIDConfiguration(scopes: scopes) + let response = await performAndValidateTokenRequest(request, config: config, context: context) + switch response { + case .success(let tokenResponse): + return await withCheckedContinuation { continuation in + handleMSIDTokenResponse( + tokenResponse: tokenResponse, + context: context, + telemetryInfo: telemetryInfo, + config: config, + onSuccess: { accountResult in + continuation.resume( + returning: .init( + .completed(accountResult), + correlationId: context.correlationId(), + telemetryUpdate: { [weak self] result in + self?.stopTelemetryEvent(telemetryInfo.event, context: context, delegateDispatcherResult: result) + })) + }, + onError: { [weak self] error in + MSALLogger.logPII( + level: .error, + context: context, + format: "Submit code, token request failed with error \(MSALLogMask.maskPII(error.errorDescription))" + ) + guard let self = self else { return } + continuation.resume(returning: self.processSubmitCodeFailure( + errorType: .generalError(nil), + telemetryInfo: telemetryInfo, + scopes: scopes, + continuationToken: continuationToken, + context: context + )) + } + ) + } + case .error(let errorType): + return processSubmitCodeFailure( + errorType: errorType, + telemetryInfo: telemetryInfo, + scopes: scopes, + continuationToken: continuationToken, + context: context + ) + case .strongAuthRequired: + let error = VerifyCodeError(type: .generalError, correlationId: context.correlationId()) + MSALLogger.log(level: .error, context: context, format: "Submit code: received unexpected MFA required API result") + stopTelemetryEvent(telemetryInfo.event, context: context, error: error) + return .init(.error(error: error, newState: nil), correlationId: context.correlationId()) + } + } + private func processSubmitCodeFailure( errorType: MSALNativeAuthTokenValidatedErrorType, telemetryInfo: TelemetryInfo, @@ -418,7 +599,7 @@ final class MSALNativeAuthSignInController: MSALNativeAuthTokenController, MSALN } let initiateResponse: Result = await performRequest(request, context: telemetryInfo.context) - let validatedResponse = signInResponseValidator.validate(context: telemetryInfo.context, result: initiateResponse) + let validatedResponse = signInResponseValidator.validateInitiate(context: telemetryInfo.context, result: initiateResponse) return validatedResponse } @@ -431,7 +612,8 @@ final class MSALNativeAuthSignInController: MSALNativeAuthTokenController, MSALN case .success(let continuationToken): let challengeValidatedResponse = await performAndValidateChallengeRequest( continuationToken: continuationToken, - context: telemetryInfo.context + context: telemetryInfo.context, + logErrorMessage: "SignIn: cannot create challenge request object" ) return .success(challengeValidatedResponse) case .error(let error): @@ -445,11 +627,52 @@ final class MSALNativeAuthSignInController: MSALNativeAuthTokenController, MSALN } } + private func handleIntrospectResponse( + _ response: MSALNativeAuthSignInIntrospectValidatedResponse, + scopes: [String], + telemetryInfo: TelemetryInfo, + continuationToken: String + ) -> MFAGetAuthMethodsControllerResponse { + switch response { + case .authMethodsRetrieved(let newContinuationToken, let authMethods): + let newState = MFARequiredState( + controller: self, + scopes: scopes, + continuationToken: newContinuationToken, + correlationId: telemetryInfo.context.correlationId() + ) + return .init(.selectionRequired( + authMethods: authMethods.map({$0.toPublicAuthMethod()}), + newState: newState + ), correlationId: telemetryInfo.context.correlationId(), + telemetryUpdate: { [weak self] result in + self?.stopTelemetryEvent(telemetryInfo.event, context: telemetryInfo.context, delegateDispatcherResult: result) + }) + case .error(let error): + MSALLogger.logPII( + level: .error, + context: telemetryInfo.context, + format: "MFA: an error occurred after calling /introspect API: \(MSALLogMask.maskPII(error))" + ) + stopTelemetryEvent(telemetryInfo, error: error) + return .init(.error( + error: error.convertToMFAGetAuthMethodsError(correlationId: telemetryInfo.context.correlationId()), + newState: MFARequiredState( + controller: self, + scopes: scopes, + continuationToken: continuationToken, + correlationId: telemetryInfo.context.correlationId() + ) + ), correlationId: telemetryInfo.context.correlationId()) + } + } + private func handleTokenResponse( _ response: MSALNativeAuthTokenValidatedResponse, scopes: [String], telemetryInfo: TelemetryInfo, onSuccess: @escaping (MSALNativeAuthUserAccountResult) -> Void, + onAwaitingMFA: @escaping (AwaitingMFAState) -> Void, onError: @escaping (SignInStartError) -> Void ) { let config = factory.makeMSIDConfiguration(scopes: scopes) @@ -470,6 +693,15 @@ final class MSALNativeAuthSignInController: MSALNativeAuthTokenController, MSALN format: "SignIn completed with errorType: \(MSALLogMask.maskPII(error.errorDescription))") stopTelemetryEvent(telemetryInfo, error: error) onError(error) + case .strongAuthRequired(let continuationToken): + let state = AwaitingMFAState( + controller: self, + scopes: scopes, + continuationToken: continuationToken, + correlationId: telemetryInfo.context.correlationId() + ) + MSALLogger.log(level: .info, context: telemetryInfo.context, format: "Multi factor authentication required") + onAwaitingMFA(state) } } @@ -487,7 +719,6 @@ final class MSALNativeAuthSignInController: MSALNativeAuthTokenController, MSALN if let userAccountResult = factory.makeUserAccountResult(tokenResult: tokenResult, context: context) { MSALLogger.log(level: .info, context: context, format: "SignIn completed successfully") telemetryInfo.event?.setUserInformation(tokenResult.account) - stopTelemetryEvent(telemetryInfo) onSuccess(userAccountResult) } else { let errorType = MSALNativeAuthTokenValidatedErrorType.generalError(nil) @@ -542,11 +773,18 @@ final class MSALNativeAuthSignInController: MSALNativeAuthTokenController, MSALN telemetryUpdate: { [weak self] result in self?.stopTelemetryEvent(telemetryInfo.event, context: telemetryInfo.context, delegateDispatcherResult: result) })) - }, - onError: { error in - continuation.resume( - returning: SignInControllerResponse(.error(error), correlationId: telemetryInfo.context.correlationId()) - ) + }, onAwaitingMFA: { awaitingMFAState in + continuation.resume( + returning: SignInControllerResponse( + .awaitingMFA(newState: awaitingMFAState), + correlationId: telemetryInfo.context.correlationId(), + telemetryUpdate: { [weak self] result in + self?.stopTelemetryEvent(telemetryInfo.event, context: telemetryInfo.context, delegateDispatcherResult: result) + }) + ) + }, onError: { error in + continuation.resume( + returning: SignInControllerResponse(.error(error), correlationId: telemetryInfo.context.correlationId())) } ) } @@ -592,20 +830,45 @@ final class MSALNativeAuthSignInController: MSALNativeAuthTokenController, MSALN format: "SignIn, completed with error: \(MSALLogMask.maskPII(error.errorDescription))") stopTelemetryEvent(telemetryInfo, error: error) return .init(.error(error), correlationId: telemetryInfo.context.correlationId()) + case .introspectRequired: + let error = SignInStartError(type: .generalError, correlationId: telemetryInfo.context.correlationId()) + MSALLogger.log(level: .error, context: telemetryInfo.context, format: "SignIn, received unexpected introspect required API result") + self.stopTelemetryEvent(telemetryInfo.event, context: telemetryInfo.context, error: error) + return .init(.error(error), correlationId: telemetryInfo.context.correlationId()) } } private func performAndValidateChallengeRequest( continuationToken: String, - context: MSALNativeAuthRequestContext + context: MSALNativeAuthRequestContext, + logErrorMessage: String, + mfaAuthMethodId: String? = nil ) async -> MSALNativeAuthSignInChallengeValidatedResponse { - guard let challengeRequest = createChallengeRequest(continuationToken: continuationToken, context: context) else { - let errorDescription = "SignIn ResendCode: Cannot create Challenge request object" - MSALLogger.log(level: .error, context: context, format: errorDescription) - return .error(.invalidRequest(.init(errorDescription: errorDescription))) + guard let challengeRequest = createChallengeRequest( + continuationToken: continuationToken, + context: context, + mfaAuthMethodId: mfaAuthMethodId + ) else { + MSALLogger.log(level: .error, context: context, format: logErrorMessage) + return .error(.invalidRequest(.init())) } let challengeResponse: Result = await performRequest(challengeRequest, context: context) - return signInResponseValidator.validate(context: context, result: challengeResponse) + return signInResponseValidator.validateChallenge(context: context, result: challengeResponse) + } + + private func performAndValidateIntrospectRequest( + continuationToken: String, + context: MSALNativeAuthRequestContext + ) async -> MSALNativeAuthSignInIntrospectValidatedResponse { + guard let introspectRequest = createIntrospectRequest( + continuationToken: continuationToken, + context: context + ) else { + MSALLogger.log(level: .error, context: context, format: "Unable to create signIn/introspect request") + return .error(.invalidRequest(.init())) + } + let introspectResponse: Result = await performRequest(introspectRequest, context: context) + return signInResponseValidator.validateIntrospect(context: context, result: introspectResponse) } private func createInitiateRequest(username: String, context: MSALNativeAuthRequestContext) -> MSIDHttpRequest? { @@ -618,13 +881,25 @@ final class MSALNativeAuthSignInController: MSALNativeAuthTokenController, MSALN } } + private func createIntrospectRequest(continuationToken: String, context: MSALNativeAuthRequestContext) -> MSIDHttpRequest? { + let params = MSALNativeAuthSignInIntrospectRequestParameters(context: context, continuationToken: continuationToken) + do { + return try signInRequestProvider.introspect(parameters: params, context: context) + } catch { + MSALLogger.log(level: .error, context: context, format: "Error creating signIn introspect request: \(error)") + return nil + } + } + private func createChallengeRequest( continuationToken: String, - context: MSALNativeAuthRequestContext + context: MSALNativeAuthRequestContext, + mfaAuthMethodId: String? ) -> MSIDHttpRequest? { do { let params = MSALNativeAuthSignInChallengeRequestParameters( context: context, + mfaAuthMethodId: mfaAuthMethodId, continuationToken: continuationToken ) return try signInRequestProvider.challenge(parameters: params, context: context) diff --git a/MSAL/src/native_auth/controllers/sign_in/MSALNativeAuthSignInControlling.swift b/MSAL/src/native_auth/controllers/sign_in/MSALNativeAuthSignInControlling.swift index 95de6fe52c..a255ff8b74 100644 --- a/MSAL/src/native_auth/controllers/sign_in/MSALNativeAuthSignInControlling.swift +++ b/MSAL/src/native_auth/controllers/sign_in/MSALNativeAuthSignInControlling.swift @@ -39,6 +39,7 @@ protocol MSALNativeAuthSignInControlling { username: String, continuationToken: String?, scopes: [String]?, + telemetryId: MSALNativeAuthTelemetryApiId, context: MSALNativeAuthRequestContext ) async -> SignInAfterPreviousFlowControllerResponse diff --git a/MSAL/src/native_auth/controllers/sign_up/MSALNativeAuthSignUpController.swift b/MSAL/src/native_auth/controllers/sign_up/MSALNativeAuthSignUpController.swift index f0fe4d63e0..f9bd6d8441 100644 --- a/MSAL/src/native_auth/controllers/sign_up/MSALNativeAuthSignUpController.swift +++ b/MSAL/src/native_auth/controllers/sign_up/MSALNativeAuthSignUpController.swift @@ -494,7 +494,7 @@ final class MSALNativeAuthSignUpController: MSALNativeAuthBaseController, MSALNa let result = await performAndValidateChallengeRequest(continuationToken: newContinuationToken, context: context) return handlePerformChallengeAfterContinueRequest(result, username: username, event: event, context: context) case .attributesRequired(let newContinuationToken, let attributes, _): - MSALLogger.logPII(level: .info, + MSALLogger.logPII(level: .info, context: context, format: "attributes_required received in signup/continue request: \(MSALLogMask.maskEUII(attributes))") diff --git a/MSAL/src/native_auth/network/MSALNativeAuthEndpoint.swift b/MSAL/src/native_auth/network/MSALNativeAuthEndpoint.swift index 796dba68f9..8fd91247ea 100644 --- a/MSAL/src/native_auth/network/MSALNativeAuthEndpoint.swift +++ b/MSAL/src/native_auth/network/MSALNativeAuthEndpoint.swift @@ -28,6 +28,7 @@ enum MSALNativeAuthEndpoint: String, CaseIterable { case signUpContinue = "/signup/v1.0/continue" case signInInitiate = "/oauth2/v2.0/initiate" case signInChallenge = "/oauth2/v2.0/challenge" + case signInIntrospect = "/oauth2/v2.0/introspect" case token = "/oauth2/v2.0/token" case resetPasswordStart = "/resetpassword/v1.0/start" case resetPasswordChallenge = "/resetpassword/v1.0/challenge" diff --git a/MSAL/src/native_auth/network/MSALNativeAuthRequestConfigurator.swift b/MSAL/src/native_auth/network/MSALNativeAuthRequestConfigurator.swift index 1e0172a130..2ae8528bd9 100644 --- a/MSAL/src/native_auth/network/MSALNativeAuthRequestConfigurator.swift +++ b/MSAL/src/native_auth/network/MSALNativeAuthRequestConfigurator.swift @@ -36,6 +36,7 @@ enum MSALNativeAuthRequestConfiguratorType { enum SignIn { case initiate(MSALNativeAuthSignInInitiateRequestParameters) case challenge(MSALNativeAuthSignInChallengeRequestParameters) + case introspect(MSALNativeAuthSignInIntrospectRequestParameters) } enum ResetPassword { @@ -135,6 +136,15 @@ class MSALNativeAuthRequestConfigurator: MSIDAADRequestConfigurator { responseSerializer: responseSerializer, telemetry: telemetry, errorHandler: errorHandler) + case .introspect(let parameters): + let responseSerializer = MSALNativeAuthResponseSerializer() + let telemetry = telemetryProvider.telemetryForSignIn(type: .signInIntrospect) + let errorHandler = MSALNativeAuthResponseErrorHandler() + try configure(request: request, + parameters: parameters, + responseSerializer: responseSerializer, + telemetry: telemetry, + errorHandler: errorHandler) } } diff --git a/MSAL/src/native_auth/network/MSALNativeAuthRequestParametersKey.swift b/MSAL/src/native_auth/network/MSALNativeAuthRequestParametersKey.swift index a4dc10590d..a7cc294f80 100644 --- a/MSAL/src/native_auth/network/MSALNativeAuthRequestParametersKey.swift +++ b/MSAL/src/native_auth/network/MSALNativeAuthRequestParametersKey.swift @@ -28,6 +28,7 @@ enum MSALNativeAuthRequestParametersKey: String { case clientId = "client_id" case challengeType = "challenge_type" case grantType = "grant_type" + case id = "id" case username case email case password diff --git a/MSAL/src/native_auth/network/errors/MSALNativeAuthESTSApiErrorCodes.swift b/MSAL/src/native_auth/network/errors/MSALNativeAuthESTSApiErrorCodes.swift index 381b72d684..38b17a6358 100644 --- a/MSAL/src/native_auth/network/errors/MSALNativeAuthESTSApiErrorCodes.swift +++ b/MSAL/src/native_auth/network/errors/MSALNativeAuthESTSApiErrorCodes.swift @@ -26,7 +26,6 @@ enum MSALNativeAuthESTSApiErrorCodes: Int { case userNotFound = 50034 case invalidCredentials = 50126 - case strongAuthRequired = 50076 case userNotHaveAPassword = 500222 case invalidRequestParameter = 90100 } diff --git a/MSAL/src/native_auth/network/errors/MSALNativeAuthErrorMessage.swift b/MSAL/src/native_auth/network/errors/MSALNativeAuthErrorMessage.swift index 1462d82862..2849467427 100644 --- a/MSAL/src/native_auth/network/errors/MSALNativeAuthErrorMessage.swift +++ b/MSAL/src/native_auth/network/errors/MSALNativeAuthErrorMessage.swift @@ -26,7 +26,6 @@ enum MSALNativeAuthErrorMessage { static let invalidScope = "Invalid scope" static let delegateNotImplemented = "MSALNativeAuth has called the delegate method %@ that has not been implemented" - static let unsupportedMFA = "MFA currently not supported. Use the browser instead" static let browserRequired = "Browser required. Use acquireTokenInteractively instead" static let userDoesNotHavePassword = "User does not have password associated with account" static let userNotFound = "User does not exist" @@ -40,10 +39,12 @@ enum MSALNativeAuthErrorMessage { static let invalidUsername = "Invalid username" static let generalError = "General error" static let invalidCode = "Invalid code" + static let invalidChallenge = "Invalid challenge" static let refreshTokenExpired = "Refresh token is expired" static let redirectUriNotSetWarning = "WARNING ⚠️: redirectUri not set during MSAL Native Auth initialization. Production apps must correctly configure a redirect URI and call acquireToken in response to all browserRequired errors. See https://learn.microsoft.com/entra/identity-platform/redirect-uris-ios" static let unexpectedResponseBody = "Unexpected response body received" static let unexpectedChallengeType = "Unexpected challenge type" + static let refreshTokenMFARequiredError = "Multi-factor authentication is required, which can't be fulfilled as part of this flow. Please sign out and perform a new sign in operation. More information: " } // swiftlint:enable line_length diff --git a/MSAL/src/native_auth/network/errors/MSALNativeAuthSubErrorCode.swift b/MSAL/src/native_auth/network/errors/MSALNativeAuthSubErrorCode.swift index 7f4c46e9b0..fcefc08c4d 100644 --- a/MSAL/src/native_auth/network/errors/MSALNativeAuthSubErrorCode.swift +++ b/MSAL/src/native_auth/network/errors/MSALNativeAuthSubErrorCode.swift @@ -33,6 +33,8 @@ enum MSALNativeAuthSubErrorCode: String, Decodable, Equatable, MSALNativeAuthUnk case passwordBanned = "password_banned" case attributeValidationFailed = "attribute_validation_failed" case invalidOOBValue = "invalid_oob_value" + case introspectRequired = "introspect_required" + case mfaRequired = "mfa_required" case unknown var isAnyPasswordError: Bool { @@ -46,6 +48,8 @@ enum MSALNativeAuthSubErrorCode: String, Decodable, Equatable, MSALNativeAuthUnk return true case .attributeValidationFailed, .invalidOOBValue, + .introspectRequired, + .mfaRequired, .unknown: return false } diff --git a/MSAL/src/native_auth/network/errors/sign_in/MSALNativeAuthSignInChallengeResponseError.swift b/MSAL/src/native_auth/network/errors/sign_in/MSALNativeAuthSignInChallengeResponseError.swift index 140e573eb5..ceeda7e38c 100644 --- a/MSAL/src/native_auth/network/errors/sign_in/MSALNativeAuthSignInChallengeResponseError.swift +++ b/MSAL/src/native_auth/network/errors/sign_in/MSALNativeAuthSignInChallengeResponseError.swift @@ -31,6 +31,7 @@ struct MSALNativeAuthSignInChallengeResponseError: MSALNativeAuthResponseError { let errorCodes: [Int]? let errorURI: String? let innerErrors: [MSALNativeAuthInnerError]? + let subError: MSALNativeAuthSubErrorCode? var correlationId: UUID? enum CodingKeys: String, CodingKey { @@ -39,6 +40,7 @@ struct MSALNativeAuthSignInChallengeResponseError: MSALNativeAuthResponseError { case errorCodes = "error_codes" case errorURI = "error_uri" case innerErrors = "inner_errors" + case subError = "suberror" case correlationId } @@ -48,6 +50,7 @@ struct MSALNativeAuthSignInChallengeResponseError: MSALNativeAuthResponseError { errorCodes: [Int]? = nil, errorURI: String? = nil, innerErrors: [MSALNativeAuthInnerError]? = nil, + subError: MSALNativeAuthSubErrorCode? = nil, correlationId: UUID? = nil ) { self.error = error @@ -55,6 +58,7 @@ struct MSALNativeAuthSignInChallengeResponseError: MSALNativeAuthResponseError { self.errorCodes = errorCodes self.errorURI = errorURI self.innerErrors = innerErrors + self.subError = subError self.correlationId = correlationId } } diff --git a/MSAL/src/native_auth/network/errors/sign_in/MSALNativeAuthSignInIntrospectOauth2ErrorCode.swift b/MSAL/src/native_auth/network/errors/sign_in/MSALNativeAuthSignInIntrospectOauth2ErrorCode.swift new file mode 100644 index 0000000000..6dd335bec5 --- /dev/null +++ b/MSAL/src/native_auth/network/errors/sign_in/MSALNativeAuthSignInIntrospectOauth2ErrorCode.swift @@ -0,0 +1,31 @@ +// +// 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 Foundation + +enum MSALNativeAuthSignInIntrospectOauth2ErrorCode: String, Decodable, MSALNativeAuthUnknownCaseProtocol { + case invalidRequest = "invalid_request" + case expiredToken = "expired_token" + case unknown +} diff --git a/MSAL/src/native_auth/network/errors/sign_in/MSALNativeAuthSignInIntrospectResponseError.swift b/MSAL/src/native_auth/network/errors/sign_in/MSALNativeAuthSignInIntrospectResponseError.swift new file mode 100644 index 0000000000..4ab6b84f1a --- /dev/null +++ b/MSAL/src/native_auth/network/errors/sign_in/MSALNativeAuthSignInIntrospectResponseError.swift @@ -0,0 +1,60 @@ +// +// 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 Foundation + +struct MSALNativeAuthSignInIntrospectResponseError: MSALNativeAuthResponseError { + + let error: MSALNativeAuthSignInIntrospectOauth2ErrorCode + let errorDescription: String? + let errorCodes: [Int]? + let errorURI: String? + let innerErrors: [MSALNativeAuthInnerError]? + var correlationId: UUID? + + enum CodingKeys: String, CodingKey { + case error + case errorDescription = "error_description" + case errorCodes = "error_codes" + case errorURI = "error_uri" + case innerErrors = "inner_errors" + case correlationId + } + + init( + error: MSALNativeAuthSignInIntrospectOauth2ErrorCode = .unknown, + errorDescription: String? = nil, + errorCodes: [Int]? = nil, + errorURI: String? = nil, + innerErrors: [MSALNativeAuthInnerError]? = nil, + correlationId: UUID? = nil + ) { + self.error = error + self.errorDescription = errorDescription + self.errorCodes = errorCodes + self.errorURI = errorURI + self.innerErrors = innerErrors + self.correlationId = correlationId + } +} diff --git a/MSAL/src/native_auth/network/parameters/sign_in/MSALNativeAuthSignInChallengeRequestParameters.swift b/MSAL/src/native_auth/network/parameters/sign_in/MSALNativeAuthSignInChallengeRequestParameters.swift index 7cd236fbf6..c499fd0bcc 100644 --- a/MSAL/src/native_auth/network/parameters/sign_in/MSALNativeAuthSignInChallengeRequestParameters.swift +++ b/MSAL/src/native_auth/network/parameters/sign_in/MSALNativeAuthSignInChallengeRequestParameters.swift @@ -27,6 +27,7 @@ struct MSALNativeAuthSignInChallengeRequestParameters: MSALNativeAuthRequestable { let endpoint: MSALNativeAuthEndpoint = .signInChallenge let context: MSALNativeAuthRequestContext + let mfaAuthMethodId: String? let continuationToken: String func makeRequestBody(config: MSALNativeAuthConfiguration) -> [String: String] { @@ -35,7 +36,8 @@ struct MSALNativeAuthSignInChallengeRequestParameters: MSALNativeAuthRequestable return [ Key.clientId.rawValue: config.clientId, Key.continuationToken.rawValue: continuationToken, - Key.challengeType.rawValue: config.challengeTypesString + Key.challengeType.rawValue: config.challengeTypesString, + Key.id.rawValue: mfaAuthMethodId ].compactMapValues { $0 } } } diff --git a/MSAL/src/native_auth/network/parameters/sign_in/MSALNativeAuthSignInIntrospectRequestParameters.swift b/MSAL/src/native_auth/network/parameters/sign_in/MSALNativeAuthSignInIntrospectRequestParameters.swift new file mode 100644 index 0000000000..9485c8f0d5 --- /dev/null +++ b/MSAL/src/native_auth/network/parameters/sign_in/MSALNativeAuthSignInIntrospectRequestParameters.swift @@ -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. + +@_implementationOnly import MSAL_Private + +struct MSALNativeAuthSignInIntrospectRequestParameters: MSALNativeAuthRequestable { + let endpoint: MSALNativeAuthEndpoint = .signInIntrospect + let context: MSALNativeAuthRequestContext + let continuationToken: String + + func makeRequestBody(config: MSALNativeAuthConfiguration) -> [String: String] { + typealias Key = MSALNativeAuthRequestParametersKey + + return [ + Key.clientId.rawValue: config.clientId, + Key.continuationToken.rawValue: continuationToken + ].compactMapValues { $0 } + } +} diff --git a/MSAL/src/native_auth/network/responses/reset_password/MSALNativeAuthResetPasswordChallengeResponse.swift b/MSAL/src/native_auth/network/responses/reset_password/MSALNativeAuthResetPasswordChallengeResponse.swift index 86fe51e3b2..868c4219ce 100644 --- a/MSAL/src/native_auth/network/responses/reset_password/MSALNativeAuthResetPasswordChallengeResponse.swift +++ b/MSAL/src/native_auth/network/responses/reset_password/MSALNativeAuthResetPasswordChallengeResponse.swift @@ -30,7 +30,7 @@ struct MSALNativeAuthResetPasswordChallengeResponse: Decodable, MSALNativeAuthRe let challengeType: MSALNativeAuthInternalChallengeType let bindingMethod: String? let challengeTargetLabel: String? - let challengeChannel: MSALNativeAuthInternalChannelType? + let challengeChannel: String? let continuationToken: String? let codeLength: Int? var correlationId: UUID? diff --git a/MSAL/src/native_auth/network/responses/sign_in/MSALNativeAuthInternalAuthenticationMethod.swift b/MSAL/src/native_auth/network/responses/sign_in/MSALNativeAuthInternalAuthenticationMethod.swift new file mode 100644 index 0000000000..48bae153bf --- /dev/null +++ b/MSAL/src/native_auth/network/responses/sign_in/MSALNativeAuthInternalAuthenticationMethod.swift @@ -0,0 +1,42 @@ +// +// 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 Foundation + +struct MSALNativeAuthInternalAuthenticationMethod: Decodable, Equatable { + // MARK: - Variables + let id: String + let challengeType: MSALNativeAuthInternalChallengeType + let challengeChannel: String + let loginHint: String + + func toPublicAuthMethod() -> MSALAuthMethod { + return MSALAuthMethod( + id: id, + challengeType: challengeType.rawValue, + loginHint: loginHint, + channelTargetType: MSALNativeAuthChannelType(value: challengeChannel) + ) + } +} diff --git a/MSAL/src/native_auth/network/responses/sign_in/MSALNativeAuthSignInChallengeResponse.swift b/MSAL/src/native_auth/network/responses/sign_in/MSALNativeAuthSignInChallengeResponse.swift index 7dd3891251..5f072977ee 100644 --- a/MSAL/src/native_auth/network/responses/sign_in/MSALNativeAuthSignInChallengeResponse.swift +++ b/MSAL/src/native_auth/network/responses/sign_in/MSALNativeAuthSignInChallengeResponse.swift @@ -31,7 +31,7 @@ struct MSALNativeAuthSignInChallengeResponse: Decodable, MSALNativeAuthResponseC let challengeType: MSALNativeAuthInternalChallengeType let bindingMethod: String? let challengeTargetLabel: String? - let challengeChannel: MSALNativeAuthInternalChannelType? + let challengeChannel: String? let codeLength: Int? let interval: Int? var correlationId: UUID? diff --git a/MSAL/src/native_auth/network/responses/sign_in/MSALNativeAuthSignInIntrospectResponse.swift b/MSAL/src/native_auth/network/responses/sign_in/MSALNativeAuthSignInIntrospectResponse.swift new file mode 100644 index 0000000000..e14e5003f5 --- /dev/null +++ b/MSAL/src/native_auth/network/responses/sign_in/MSALNativeAuthSignInIntrospectResponse.swift @@ -0,0 +1,34 @@ +// +// 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 Foundation + +struct MSALNativeAuthSignInIntrospectResponse: Decodable, MSALNativeAuthResponseCorrelatable { + + // MARK: - Variables + let continuationToken: String? + let methods: [MSALNativeAuthInternalAuthenticationMethod]? + let challengeType: MSALNativeAuthInternalChallengeType? + var correlationId: UUID? +} diff --git a/MSAL/src/native_auth/network/responses/sign_up/MSALNativeAuthSignUpChallengeResponse.swift b/MSAL/src/native_auth/network/responses/sign_up/MSALNativeAuthSignUpChallengeResponse.swift index 3891b6f990..cee89f28c4 100644 --- a/MSAL/src/native_auth/network/responses/sign_up/MSALNativeAuthSignUpChallengeResponse.swift +++ b/MSAL/src/native_auth/network/responses/sign_up/MSALNativeAuthSignUpChallengeResponse.swift @@ -29,7 +29,7 @@ struct MSALNativeAuthSignUpChallengeResponse: Decodable, MSALNativeAuthResponseC let bindingMethod: String? let interval: Int? let challengeTargetLabel: String? - let challengeChannel: MSALNativeAuthInternalChannelType? + let challengeChannel: String? let continuationToken: String? let codeLength: Int? var correlationId: UUID? diff --git a/MSAL/src/native_auth/network/responses/validator/reset_password/MSALNativeAuthResetPasswordResponseValidator.swift b/MSAL/src/native_auth/network/responses/validator/reset_password/MSALNativeAuthResetPasswordResponseValidator.swift index 27bae79304..c97547f7b7 100644 --- a/MSAL/src/native_auth/network/responses/validator/reset_password/MSALNativeAuthResetPasswordResponseValidator.swift +++ b/MSAL/src/native_auth/network/responses/validator/reset_password/MSALNativeAuthResetPasswordResponseValidator.swift @@ -117,9 +117,10 @@ final class MSALNativeAuthResetPasswordResponseValidator: MSALNativeAuthResetPas return .redirect case .oob: if let sentTo = response.challengeTargetLabel, - let channelTargetType = response.challengeChannel?.toPublicChannelType(), + let challengeChannel = response.challengeChannel, let codeLength = response.codeLength, let continuationToken = response.continuationToken { + let channelTargetType = MSALNativeAuthChannelType(value: challengeChannel) return .success( sentTo, channelTargetType, diff --git a/MSAL/src/native_auth/network/responses/validator/sign_in/MSALNativeAuthSignInResponseValidator.swift b/MSAL/src/native_auth/network/responses/validator/sign_in/MSALNativeAuthSignInResponseValidator.swift index dc68c221c7..dc6ec3e479 100644 --- a/MSAL/src/native_auth/network/responses/validator/sign_in/MSALNativeAuthSignInResponseValidator.swift +++ b/MSAL/src/native_auth/network/responses/validator/sign_in/MSALNativeAuthSignInResponseValidator.swift @@ -25,20 +25,51 @@ @_implementationOnly import MSAL_Private protocol MSALNativeAuthSignInResponseValidating { - func validate( + func validateInitiate( + context: MSIDRequestContext, + result: Result + ) -> MSALNativeAuthSignInInitiateValidatedResponse + + func validateChallenge( context: MSIDRequestContext, result: Result ) -> MSALNativeAuthSignInChallengeValidatedResponse - func validate( + func validateIntrospect( context: MSIDRequestContext, - result: Result - ) -> MSALNativeAuthSignInInitiateValidatedResponse + result: Result + ) -> MSALNativeAuthSignInIntrospectValidatedResponse } final class MSALNativeAuthSignInResponseValidator: MSALNativeAuthSignInResponseValidating { - func validate( + func validateInitiate( + context: MSIDRequestContext, + result: Result + ) -> MSALNativeAuthSignInInitiateValidatedResponse { + switch result { + case .success(let initiateResponse): + if initiateResponse.challengeType == .redirect { + return .error(.redirect) + } + if let continuationToken = initiateResponse.continuationToken { + return .success(continuationToken: continuationToken) + } + MSALLogger.log(level: .error, context: context, format: "signin/initiate: challengeType and continuation token empty") + return .error(.unexpectedError(.init(errorDescription: MSALNativeAuthErrorMessage.unexpectedResponseBody))) + case .failure(let responseError): + guard let initiateResponseError = responseError as? MSALNativeAuthSignInInitiateResponseError else { + MSALLogger.logPII( + level: .error, + context: context, + format: "signin/initiate: Unable to decode error response: \(MSALLogMask.maskPII(responseError))") + return .error(.unexpectedError(.init(errorDescription: MSALNativeAuthErrorMessage.unexpectedResponseBody))) + } + return handleFailedSignInInitiateResult(error: initiateResponseError) + } + } + + func validateChallenge( context: MSIDRequestContext, result: Result ) -> MSALNativeAuthSignInChallengeValidatedResponse { @@ -58,29 +89,42 @@ final class MSALNativeAuthSignInResponseValidator: MSALNativeAuthSignInResponseV } } - func validate( - context: MSIDRequestContext, - result: Result - ) -> MSALNativeAuthSignInInitiateValidatedResponse { + func validateIntrospect( + context: any MSIDRequestContext, + result: Result + ) -> MSALNativeAuthSignInIntrospectValidatedResponse { switch result { - case .success(let initiateResponse): - if initiateResponse.challengeType == .redirect { + case .success(let introspectResponse): + guard introspectResponse.challengeType != .redirect else { return .error(.redirect) } - if let continuationToken = initiateResponse.continuationToken { - return .success(continuationToken: continuationToken) + guard let continuationToken = introspectResponse.continuationToken, + let methods = introspectResponse.methods, + !methods.isEmpty else { + MSALLogger.logPII( + level: .error, + context: context, + format: "signin/introspect: Invalid response, content: \(MSALLogMask.maskPII(introspectResponse))") + return .error(.unexpectedError(.init(errorDescription: MSALNativeAuthErrorMessage.unexpectedResponseBody))) } - MSALLogger.log(level: .error, context: context, format: "signin/initiate: challengeType and continuation token empty") - return .error(.unexpectedError(.init(errorDescription: MSALNativeAuthErrorMessage.unexpectedResponseBody))) - case .failure(let responseError): - guard let initiateResponseError = responseError as? MSALNativeAuthSignInInitiateResponseError else { + return .authMethodsRetrieved(continuationToken: continuationToken, authMethods: methods) + case .failure(let introspectResponseError): + guard let introspectResponseError = + introspectResponseError as? MSALNativeAuthSignInIntrospectResponseError else { MSALLogger.logPII( level: .error, context: context, - format: "signin/initiate: Unable to decode error response: \(MSALLogMask.maskPII(responseError))") + format: "signin/introspect: Unable to decode error response: \(MSALLogMask.maskPII(introspectResponseError))") return .error(.unexpectedError(.init(errorDescription: MSALNativeAuthErrorMessage.unexpectedResponseBody))) } - return handleFailedSignInInitiateResult(error: initiateResponseError) + switch introspectResponseError.error { + case .invalidRequest: + return .error(.invalidRequest(introspectResponseError)) + case .expiredToken: + return .error(.expiredToken(introspectResponseError)) + case .unknown: + return .error(.unexpectedError(introspectResponseError)) + } } } @@ -110,7 +154,7 @@ final class MSALNativeAuthSignInResponseValidator: MSALNativeAuthSignInResponseV return .codeRequired( continuationToken: continuationToken, sentTo: targetLabel, - channelType: channelType.toPublicChannelType(), + channelType: MSALNativeAuthChannelType(value: channelType), codeLength: codeLength) case .password: guard let continuationToken = response.continuationToken else { @@ -130,7 +174,11 @@ final class MSALNativeAuthSignInResponseValidator: MSALNativeAuthSignInResponseV error: MSALNativeAuthSignInChallengeResponseError) -> MSALNativeAuthSignInChallengeValidatedResponse { switch error.error { case .invalidRequest: - return .error(.invalidRequest(error)) + if error.subError == .introspectRequired { + return .introspectRequired + } else { + return .error(.invalidRequest(error)) + } case .unauthorizedClient: return .error(.unauthorizedClient(error)) case .invalidGrant: diff --git a/MSAL/src/native_auth/network/responses/validator/sign_in/validated_response/MSALNativeAuthSignInChallengeValidatedResponse.swift b/MSAL/src/native_auth/network/responses/validator/sign_in/validated_response/MSALNativeAuthSignInChallengeValidatedResponse.swift index 2ee13b32f8..f9904b4ca2 100644 --- a/MSAL/src/native_auth/network/responses/validator/sign_in/validated_response/MSALNativeAuthSignInChallengeValidatedResponse.swift +++ b/MSAL/src/native_auth/network/responses/validator/sign_in/validated_response/MSALNativeAuthSignInChallengeValidatedResponse.swift @@ -27,6 +27,7 @@ import Foundation enum MSALNativeAuthSignInChallengeValidatedResponse { case codeRequired(continuationToken: String, sentTo: String, channelType: MSALNativeAuthChannelType, codeLength: Int) case passwordRequired(continuationToken: String) + case introspectRequired case error(MSALNativeAuthSignInChallengeValidatedErrorType) } @@ -100,4 +101,32 @@ enum MSALNativeAuthSignInChallengeValidatedErrorType: Error { ) } } + + func convertToMFARequestChallengeError(correlationId: UUID) -> MFARequestChallengeError { + switch self { + case .redirect: + return .init(type: .browserRequired, correlationId: correlationId) + case .invalidRequest(let apiError), + .expiredToken(let apiError), + .invalidToken(let apiError), + .unauthorizedClient(let apiError), + .userNotFound(let apiError), + .unsupportedChallengeType(let apiError): + return .init( + type: .generalError, + message: apiError.errorDescription, + correlationId: correlationId, + errorCodes: apiError.errorCodes ?? [], + errorUri: apiError.errorURI + ) + case .unexpectedError(let apiError): + return .init( + type: .generalError, + message: apiError?.errorDescription, + correlationId: correlationId, + errorCodes: apiError?.errorCodes ?? [], + errorUri: apiError?.errorURI + ) + } + } } diff --git a/MSAL/src/native_auth/network/responses/validator/sign_in/validated_response/MSALNativeAuthSignInIntrospectValidatedResponse.swift b/MSAL/src/native_auth/network/responses/validator/sign_in/validated_response/MSALNativeAuthSignInIntrospectValidatedResponse.swift new file mode 100644 index 0000000000..355d39775c --- /dev/null +++ b/MSAL/src/native_auth/network/responses/validator/sign_in/validated_response/MSALNativeAuthSignInIntrospectValidatedResponse.swift @@ -0,0 +1,61 @@ +// +// 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 Foundation + +enum MSALNativeAuthSignInIntrospectValidatedResponse { + case authMethodsRetrieved(continuationToken: String, authMethods: [MSALNativeAuthInternalAuthenticationMethod]) + case error(MSALNativeAuthSignInIntrospectValidatedErrorType) +} + +enum MSALNativeAuthSignInIntrospectValidatedErrorType: Error { + case redirect + case expiredToken(MSALNativeAuthSignInIntrospectResponseError) + case invalidRequest(MSALNativeAuthSignInIntrospectResponseError) + case unexpectedError(MSALNativeAuthSignInIntrospectResponseError?) + + func convertToMFAGetAuthMethodsError(correlationId: UUID) -> MFAGetAuthMethodsError { + switch self { + case .redirect: + return .init(type: .browserRequired, correlationId: correlationId) + case .invalidRequest(let apiError), + .expiredToken(let apiError): + return .init( + type: .generalError, + message: apiError.errorDescription, + correlationId: correlationId, + errorCodes: apiError.errorCodes ?? [], + errorUri: apiError.errorURI + ) + case .unexpectedError(let apiError): + return .init( + type: .generalError, + message: apiError?.errorDescription, + correlationId: correlationId, + errorCodes: apiError?.errorCodes ?? [], + errorUri: apiError?.errorURI + ) + } + } +} diff --git a/MSAL/src/native_auth/network/responses/validator/sign_up/MSALNativeAuthSignUpResponseValidator.swift b/MSAL/src/native_auth/network/responses/validator/sign_up/MSALNativeAuthSignUpResponseValidator.swift index 21b5774459..791c5fc7b0 100644 --- a/MSAL/src/native_auth/network/responses/validator/sign_up/MSALNativeAuthSignUpResponseValidator.swift +++ b/MSAL/src/native_auth/network/responses/validator/sign_up/MSALNativeAuthSignUpResponseValidator.swift @@ -127,9 +127,10 @@ final class MSALNativeAuthSignUpResponseValidator: MSALNativeAuthSignUpResponseV return .redirect case .oob: if let sentTo = response.challengeTargetLabel, - let channelType = response.challengeChannel?.toPublicChannelType(), + let challengeChannel = response.challengeChannel, let codeLength = response.codeLength, let continuationToken = response.continuationToken { + let channelType = MSALNativeAuthChannelType(value: challengeChannel) return .codeRequired(sentTo, channelType, codeLength, continuationToken) } else { MSALLogger.log(level: .error, context: context, format: "Missing expected fields in signup/challenge with challenge_type = oob") @@ -212,7 +213,7 @@ final class MSALNativeAuthSignUpResponseValidator: MSALNativeAuthSignUpResponseV MSALLogger.log(level: .error, context: context, format: "Missing expected fields in signup/continue for attributes_required error") return .unexpectedError(.init(errorDescription: MSALNativeAuthErrorMessage.unexpectedResponseBody)) } - // TODO: .verificationRequired is not supported by the API team yet. We treat it as an unexpectedError + // TODO: .verificationRequired is returned by server when user submits attribute but email isn't verified yet. It needs to be handled by SDK case .verificationRequired: MSALLogger.log(level: .error, context: context, format: "verificationRequired is not supported yet") return .unexpectedError(nil) @@ -254,7 +255,9 @@ final class MSALNativeAuthSignUpResponseValidator: MSALNativeAuthSignUpResponseV ) return .unexpectedError(.init(errorDescription: MSALNativeAuthErrorMessage.unexpectedResponseBody)) } - case .unknown: + case .unknown, + .introspectRequired, + .mfaRequired: return .unexpectedError(apiError) } } diff --git a/MSAL/src/native_auth/network/responses/validator/token/MSALNativeAuthTokenResponseValidator.swift b/MSAL/src/native_auth/network/responses/validator/token/MSALNativeAuthTokenResponseValidator.swift index 6ab51f770e..5a74b96ac1 100644 --- a/MSAL/src/native_auth/network/responses/validator/token/MSALNativeAuthTokenResponseValidator.swift +++ b/MSAL/src/native_auth/network/responses/validator/token/MSALNativeAuthTokenResponseValidator.swift @@ -102,6 +102,17 @@ final class MSALNativeAuthTokenResponseValidator: MSALNativeAuthTokenResponseVal case .invalidGrant: if responseError.subError == .invalidOOBValue { return .error(.invalidOOBCode(responseError)) + } else if responseError.subError == .mfaRequired { + guard let continuationToken = responseError.continuationToken else { + MSALLogger.log( + level: .error, + context: context, + format: "Token: MFA required response, expected continuation token not empty") + return .error(.generalError( + MSALNativeAuthTokenResponseError(errorDescription: MSALNativeAuthErrorMessage.unexpectedResponseBody) + )) + } + return .strongAuthRequired(continuationToken: continuationToken) } else { return handleInvalidGrantErrorCodes(apiError: responseError, context: context) } @@ -194,8 +205,6 @@ final class MSALNativeAuthTokenResponseValidator: MSALNativeAuthTokenResponseVal return .userNotFound(apiError) case .invalidCredentials: return .invalidPassword(apiError) - case .strongAuthRequired: - return .strongAuthRequired(apiError) case .userNotHaveAPassword, .invalidRequestParameter: return .generalError(apiError) @@ -209,7 +218,6 @@ final class MSALNativeAuthTokenResponseValidator: MSALNativeAuthTokenResponseVal switch errorCode { case .userNotFound, .invalidCredentials, - .strongAuthRequired, .userNotHaveAPassword, .invalidRequestParameter: return .invalidRequest(apiError) diff --git a/MSAL/src/native_auth/network/responses/validator/token/validated_response/MSALNativeAuthTokenValidatedResponse.swift b/MSAL/src/native_auth/network/responses/validator/token/validated_response/MSALNativeAuthTokenValidatedResponse.swift index 059166dd6b..1a50ebffdf 100644 --- a/MSAL/src/native_auth/network/responses/validator/token/validated_response/MSALNativeAuthTokenValidatedResponse.swift +++ b/MSAL/src/native_auth/network/responses/validator/token/validated_response/MSALNativeAuthTokenValidatedResponse.swift @@ -26,6 +26,7 @@ enum MSALNativeAuthTokenValidatedResponse { case success(MSIDTokenResponse) + case strongAuthRequired(continuationToken: String) case error(MSALNativeAuthTokenValidatedErrorType) } @@ -40,7 +41,6 @@ enum MSALNativeAuthTokenValidatedErrorType: Error { case invalidPassword(MSALNativeAuthTokenResponseError) case invalidOOBCode(MSALNativeAuthTokenResponseError) case unsupportedChallengeType(MSALNativeAuthTokenResponseError) - case strongAuthRequired(MSALNativeAuthTokenResponseError) case invalidScope(MSALNativeAuthTokenResponseError) case authorizationPending(MSALNativeAuthTokenResponseError) case slowDown(MSALNativeAuthTokenResponseError) @@ -88,14 +88,6 @@ enum MSALNativeAuthTokenValidatedErrorType: Error { errorCodes: apiError.errorCodes ?? [], errorUri: apiError.errorURI ) - case .strongAuthRequired(let apiError): - return SignInStartError( - type: .browserRequired, - message: apiError.errorDescription, - correlationId: correlationId, - errorCodes: apiError.errorCodes ?? [], - errorUri: apiError.errorURI - ) case .expiredRefreshToken(let apiError): MSALLogger.logPII(level: .error, context: nil, format: "Error not treated - \(MSALLogMask.maskPII(self))") return SignInStartError( @@ -108,7 +100,6 @@ enum MSALNativeAuthTokenValidatedErrorType: Error { } } - // swiftlint:disable:next function_body_length func convertToRetrieveAccessTokenError(correlationId: UUID) -> RetrieveAccessTokenError { switch self { case .expiredToken(let apiError), @@ -143,14 +134,6 @@ enum MSALNativeAuthTokenValidatedErrorType: Error { errorCodes: apiError.errorCodes ?? [], errorUri: apiError.errorURI ) - case .strongAuthRequired(let apiError): - return RetrieveAccessTokenError( - type: .browserRequired, - message: apiError.errorDescription, - correlationId: correlationId, - errorCodes: apiError.errorCodes ?? [], - errorUri: apiError.errorURI - ) case .userNotFound(let apiError), .invalidPassword(let apiError), .invalidOOBCode(let apiError): @@ -175,14 +158,6 @@ enum MSALNativeAuthTokenValidatedErrorType: Error { errorCodes: apiError.errorCodes ?? [], errorUri: apiError.errorURI ) - case .strongAuthRequired(let apiError): - return VerifyCodeError( - type: .browserRequired, - message: apiError.errorDescription, - correlationId: correlationId, - errorCodes: apiError.errorCodes ?? [], - errorUri: apiError.errorURI - ) case .expiredToken(let apiError), .authorizationPending(let apiError), .slowDown(let apiError), diff --git a/MSAL/src/native_auth/network/sign_in/MSALNativeAuthSignInRequestProvider.swift b/MSAL/src/native_auth/network/sign_in/MSALNativeAuthSignInRequestProvider.swift index aa36b1d8fb..9b9822977b 100644 --- a/MSAL/src/native_auth/network/sign_in/MSALNativeAuthSignInRequestProvider.swift +++ b/MSAL/src/native_auth/network/sign_in/MSALNativeAuthSignInRequestProvider.swift @@ -34,6 +34,11 @@ protocol MSALNativeAuthSignInRequestProviding { parameters: MSALNativeAuthSignInChallengeRequestParameters, context: MSIDRequestContext ) throws -> MSIDHttpRequest + + func introspect( + parameters: MSALNativeAuthSignInIntrospectRequestParameters, + context: MSIDRequestContext + ) throws -> MSIDHttpRequest } final class MSALNativeAuthSignInRequestProvider: MSALNativeAuthSignInRequestProviding { @@ -80,4 +85,18 @@ final class MSALNativeAuthSignInRequestProvider: MSALNativeAuthSignInRequestProv telemetryProvider: telemetryProvider) return request } + + // MARK: - SignIn Introspect + + func introspect( + parameters: MSALNativeAuthSignInIntrospectRequestParameters, + context: any MSIDRequestContext + ) throws -> MSIDHttpRequest { + + let request = MSIDHttpRequest() + try requestConfigurator.configure(configuratorType: .signIn(.introspect(parameters)), + request: request, + telemetryProvider: telemetryProvider) + return request + } } diff --git a/MSAL/src/native_auth/public/MSALAuthMethod.swift b/MSAL/src/native_auth/public/MSALAuthMethod.swift new file mode 100644 index 0000000000..0868fc6ac7 --- /dev/null +++ b/MSAL/src/native_auth/public/MSALAuthMethod.swift @@ -0,0 +1,51 @@ +// +// 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 Foundation + +/** + * MSALAuthMethod represents a user's authentication methods. + */ +@objc +public class MSALAuthMethod: NSObject { + + /// Authentication method identifier + public let id: String + + /// Authentication method challenge type (oob, etc.) + public let challengeType: String + + /// Authentication method login hint (e.g. user@contoso.com) + public let loginHint: String + + /// Authentication method channel target (email, etc.) + public let channelTargetType: MSALNativeAuthChannelType + + init(id: String, challengeType: String, loginHint: String, channelTargetType: MSALNativeAuthChannelType) { + self.id = id + self.challengeType = challengeType + self.loginHint = loginHint + self.channelTargetType = channelTargetType + } +} diff --git a/MSAL/src/native_auth/public/MSALNativeAuthChannelType.swift b/MSAL/src/native_auth/public/MSALNativeAuthChannelType.swift index 0bf791a888..82b8822cf4 100644 --- a/MSAL/src/native_auth/public/MSALNativeAuthChannelType.swift +++ b/MSAL/src/native_auth/public/MSALNativeAuthChannelType.swift @@ -24,13 +24,24 @@ import Foundation -/// The possible Channel Types via which a code was sent -@objc -public enum MSALNativeAuthChannelType: Int { +/// The channel type via which a code was sent +@objcMembers +public class MSALNativeAuthChannelType: NSObject { - /// Specifies if the channel type is Email - case email + /// Value of the channel used. + public let value: String - /// Specifies if the channel type is Phone - case phone + /// Returns `true` if the channel is email. + public var isEmailType: Bool { + return value.lowercased() == "email" + } + + /// Returns `true` if the channel is phone. + public var isPhoneType: Bool { + return value.lowercased() == "phone" + } + + init(value: String) { + self.value = value + } } diff --git a/MSAL/src/native_auth/public/MSALNativeAuthPublicClientApplication.swift b/MSAL/src/native_auth/public/MSALNativeAuthPublicClientApplication.swift index a99b14c6f4..306dc3f273 100644 --- a/MSAL/src/native_auth/public/MSALNativeAuthPublicClientApplication.swift +++ b/MSAL/src/native_auth/public/MSALNativeAuthPublicClientApplication.swift @@ -229,6 +229,8 @@ public final class MSALNativeAuthPublicClientApplication: MSALPublicClientApplic await delegateDispatcher.dispatchSignInCompleted(result: result, correlationId: controllerResponse.correlationId) case .error(let error): await delegate.onSignInStartError(error: error) + case .awaitingMFA(let newState): + await delegateDispatcher.dispatchAwaitingMFA(newState: newState, correlationId: controllerResponse.correlationId) } } } diff --git a/MSAL/src/native_auth/public/MSALNativeAuthUserAccountResult+Internal.swift b/MSAL/src/native_auth/public/MSALNativeAuthUserAccountResult+Internal.swift index 3f22cf0836..97aa3d85c4 100644 --- a/MSAL/src/native_auth/public/MSALNativeAuthUserAccountResult+Internal.swift +++ b/MSAL/src/native_auth/public/MSALNativeAuthUserAccountResult+Internal.swift @@ -87,12 +87,21 @@ extension MSALNativeAuthUserAccountResult { if let innerError = error.userInfo[NSUnderlyingErrorKey] as? NSError { return createRetrieveAccessTokenError(error: innerError, context: context) } - let message = error.userInfo[MSALErrorDescriptionKey] as? String ?? error.localizedDescription + var message = error.userInfo[MSALErrorDescriptionKey] as? String ?? error.localizedDescription + let errorCodes = error.userInfo[MSALSTSErrorCodesKey] as? [Int] ?? [] + if isMFARequiredError(errorCodes: errorCodes) { + message = MSALNativeAuthErrorMessage.refreshTokenMFARequiredError + message + } let correlationId = correlationIdFromMSALError(error: error) ?? context.correlationId() - return RetrieveAccessTokenError(type: .generalError, message: message, correlationId: correlationId, errorCodes: []) + return RetrieveAccessTokenError(type: .generalError, message: message, correlationId: correlationId, errorCodes: errorCodes) } private func correlationIdFromMSALError(error: NSError) -> UUID? { return UUID(uuidString: error.userInfo[MSALCorrelationIDKey] as? String ?? "") } + + private func isMFARequiredError(errorCodes: [Int]) -> Bool { + let mfaRequiredErrorCode = 50076 + return errorCodes.contains(mfaRequiredErrorCode) + } } diff --git a/MSAL/src/native_auth/public/state_machine/delegate/MFADelegates.swift b/MSAL/src/native_auth/public/state_machine/delegate/MFADelegates.swift new file mode 100644 index 0000000000..a8e82fe426 --- /dev/null +++ b/MSAL/src/native_auth/public/state_machine/delegate/MFADelegates.swift @@ -0,0 +1,91 @@ +// +// 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 Foundation + +/// Protocol that defines the methods of a MFARequestChallenge delegate +@objc +public protocol MFARequestChallengeDelegate { + + /// Notifies the delegate that the operation resulted in an error. + /// - Parameters: + /// - error: An error object indicating why the operation failed. + /// - newState: An object representing the new state of the flow with follow on methods. + @MainActor func onMFARequestChallengeError(error: MFARequestChallengeError, newState: MFARequiredState?) + + /// Notifies the delegate that a verification is required from the user to continue. + /// - Note: If a flow requires this optional method and it is not implemented, then ``onMFARequestChallengeError(error:)`` will be called. + /// - Parameters: + /// - newState: An object representing the new state of the flow with follow on methods. + /// - sentTo: The email/phone number that the code was sent to. + /// - channelTargetType: The channel (email/phone) the code was sent through. + /// - codeLength: The length of the code required. + @MainActor @objc optional func onMFARequestChallengeVerificationRequired( + newState: MFARequiredState, + sentTo: String, + channelTargetType: MSALNativeAuthChannelType, + codeLength: Int) + + /// Notifies the delegate that the list of authentication methods is now available. + /// The user is required to choose an authentication method and then proceed with the "newState" to advance in the MFA process. + /// - Note: If a flow requires this optional method and it is not implemented, then ``onMFARequestChallengeError(error:)`` will be called. + /// - Parameters: + /// - authMethods: list of authentication method + /// - newState: An object representing the new state of the flow with follow on methods. + @MainActor @objc optional func onMFARequestChallengeSelectionRequired(authMethods: [MSALAuthMethod], newState: MFARequiredState) +} + +/// Protocol that defines the methods of a MFAGetAuthMethodsDelegate delegate +@objc +public protocol MFAGetAuthMethodsDelegate { + + /// Notifies the delegate that the operation resulted in an error. + /// - Parameters: + /// - error: An error object indicating why the operation failed. + /// - newState: An object representing the new state of the flow with follow on methods. + @MainActor func onMFAGetAuthMethodsError(error: MFAGetAuthMethodsError, newState: MFARequiredState?) + + /// Notifies the delegate that the list of authentication methods is now available. + /// - Note: If a flow requires this optional method and it is not implemented, then ``onMFAGetAuthMethodsError(error:)`` will be called. + /// - Parameters: + /// - authMethods: list of authentication method. + /// - newState: An object representing the new state of the flow with follow on methods. + @MainActor @objc optional func onMFAGetAuthMethodsSelectionRequired(authMethods: [MSALAuthMethod], newState: MFARequiredState) +} + +/// Protocol that defines the methods of a MFAGetAuthMethodsDelegate delegate +@objc +public protocol MFASubmitChallengeDelegate { + + /// Notifies the delegate that the operation resulted in an error. + /// - Parameters: + /// - error: An error object indicating why the operation failed. + /// - newState: An object representing the new state of the flow with follow on methods. + @MainActor func onMFASubmitChallengeError(error: MFASubmitChallengeError, newState: MFARequiredState?) + + /// Notifies the delegate that the sign in operation completed successfully. + /// - Note: If a flow requires this optional method and it is not implemented, then ``onMFASubmitChallengeError(error:newState:)`` will be called. + /// - Parameter result: An object representing the signed in user account. + @MainActor @objc optional func onSignInCompleted(result: MSALNativeAuthUserAccountResult) +} diff --git a/MSAL/src/native_auth/public/state_machine/delegate/SignInDelegates.swift b/MSAL/src/native_auth/public/state_machine/delegate/SignInDelegates.swift index 80d385d51a..c4be39997c 100644 --- a/MSAL/src/native_auth/public/state_machine/delegate/SignInDelegates.swift +++ b/MSAL/src/native_auth/public/state_machine/delegate/SignInDelegates.swift @@ -48,6 +48,11 @@ public protocol SignInStartDelegate { /// - Parameter newState: An object representing the new state of the flow with follow on methods. @MainActor @objc optional func onSignInPasswordRequired(newState: SignInPasswordRequiredState) + /// Notifies the delegate that a multi factor authentication (MFA) is required from the user to continue. + /// - Note: If a flow requires this optional method and it is not implemented, then ``onSignInStartError(error:)`` will be called. + /// - Parameter newState: An object representing the new state of the flow with follow on methods. + @MainActor @objc optional func onSignInAwaitingMFA(newState: AwaitingMFAState) + /// Notifies the delegate that the sign in operation completed successfully. /// - Parameter result: An object representing the signed in user account. /// - Note: If a flow requires this optional method and it is not implemented, then ``onSignInStartError(error:)`` will be called. @@ -63,8 +68,14 @@ public protocol SignInPasswordRequiredDelegate { /// - newState: An object representing the new state of the flow with follow on methods. @MainActor func onSignInPasswordRequiredError(error: PasswordRequiredError, newState: SignInPasswordRequiredState?) + /// Notifies the delegate that a multi factor authentication (MFA) is required from the user to continue. + /// - Note: If a flow requires this optional method and it is not implemented, then ``onSignInPasswordRequiredError(error:)`` will be called. + /// - Parameter newState: An object representing the new state of the flow with follow on methods. + @MainActor @objc optional func onSignInAwaitingMFA(newState: AwaitingMFAState) + /// Notifies the delegate that the sign in operation completed successfully. - /// - Note: If a flow requires this optional method and it is not implemented, then ``onSignInPasswordRequiredError(error:newState:)`` will be called. + /// - Note: + /// If a flow requires this optional method and it is not implemented, then ``onSignInPasswordRequiredError(error:newState:)`` will be called. /// - Parameter result: An object representing the signed in user account. @MainActor @objc optional func onSignInCompleted(result: MSALNativeAuthUserAccountResult) } diff --git a/MSAL/src/native_auth/public/state_machine/delegate_dispatcher/MFADelegateDispatchers.swift b/MSAL/src/native_auth/public/state_machine/delegate_dispatcher/MFADelegateDispatchers.swift new file mode 100644 index 0000000000..bcc0849d61 --- /dev/null +++ b/MSAL/src/native_auth/public/state_machine/delegate_dispatcher/MFADelegateDispatchers.swift @@ -0,0 +1,104 @@ +// +// 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 Foundation + +final class MFARequestChallengeDelegateDispatcher: DelegateDispatcher { + + func dispatchVerificationRequired(newState: MFARequiredState, + sentTo: String, + channelTargetType: MSALNativeAuthChannelType, + codeLength: Int, + correlationId: UUID + ) async { + if let onVerificationRequired = delegate.onMFARequestChallengeVerificationRequired { + telemetryUpdate?(.success(())) + await onVerificationRequired( + newState, + sentTo, + channelTargetType, + codeLength + ) + } else { + let error = MFARequestChallengeError( + type: .generalError, + message: requiredErrorMessage(for: "onMFARequestChallengeVerificationRequired"), + correlationId: correlationId + ) + telemetryUpdate?(.failure(error)) + await delegate.onMFARequestChallengeError(error: error, newState: nil) + } + } + + func dispatchSelectionRequired(authMethods: [MSALAuthMethod], newState: MFARequiredState, correlationId: UUID) async { + if let onSelectionRequired = delegate.onMFARequestChallengeSelectionRequired { + telemetryUpdate?(.success(())) + await onSelectionRequired(authMethods, newState) + } else { + let error = MFARequestChallengeError( + type: .generalError, + message: requiredErrorMessage(for: "onMFARequestChallengeSelectionRequired"), + correlationId: correlationId + ) + telemetryUpdate?(.failure(error)) + await delegate.onMFARequestChallengeError(error: error, newState: nil) + } + } +} + +final class MFAGetAuthMethodsDelegateDispatcher: DelegateDispatcher { + + func dispatchSelectionRequired(authMethods: [MSALAuthMethod], newState: MFARequiredState, correlationId: UUID) async { + if let onSelectionRequired = delegate.onMFAGetAuthMethodsSelectionRequired { + telemetryUpdate?(.success(())) + await onSelectionRequired(authMethods, newState) + } else { + let error = MFAGetAuthMethodsError( + type: .generalError, + message: requiredErrorMessage(for: "onMFAGetAuthMethodsSelectionRequired"), + correlationId: correlationId + ) + telemetryUpdate?(.failure(error)) + await delegate.onMFAGetAuthMethodsError(error: error, newState: nil) + } + } +} + +final class MFASubmitChallengeDelegateDispatcher: DelegateDispatcher { + + func dispatchSignInCompleted(result: MSALNativeAuthUserAccountResult, correlationId: UUID) async { + if let onSignInCompleted = delegate.onSignInCompleted { + telemetryUpdate?(.success(())) + await onSignInCompleted(result) + } else { + let error = MFASubmitChallengeError( + type: .generalError, + message: requiredErrorMessage(for: "onSignInCompleted"), + correlationId: correlationId + ) + telemetryUpdate?(.failure(error)) + await delegate.onMFASubmitChallengeError(error: error, newState: nil) + } + } +} diff --git a/MSAL/src/native_auth/public/state_machine/delegate_dispatcher/SignInDelegateDispatchers.swift b/MSAL/src/native_auth/public/state_machine/delegate_dispatcher/SignInDelegateDispatchers.swift index 6077935735..9acb02a2ff 100644 --- a/MSAL/src/native_auth/public/state_machine/delegate_dispatcher/SignInDelegateDispatchers.swift +++ b/MSAL/src/native_auth/public/state_machine/delegate_dispatcher/SignInDelegateDispatchers.swift @@ -62,6 +62,21 @@ final class SignInStartDelegateDispatcher: DelegateDispatcher { diff --git a/MSAL/src/native_auth/public/state_machine/error/MFAGetAuthMethodsError.swift b/MSAL/src/native_auth/public/state_machine/error/MFAGetAuthMethodsError.swift new file mode 100644 index 0000000000..c56433d093 --- /dev/null +++ b/MSAL/src/native_auth/public/state_machine/error/MFAGetAuthMethodsError.swift @@ -0,0 +1,77 @@ +// +// 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 Foundation + +/// Class that defines the structure and type of a MFAGetAuthMethodsError +@objcMembers +public class MFAGetAuthMethodsError: MSALNativeAuthError { + enum ErrorType: CaseIterable { + case browserRequired + case generalError + } + + let type: ErrorType + + init(type: ErrorType, message: String? = nil, correlationId: UUID, errorCodes: [Int] = [], errorUri: String? = nil) { + self.type = type + super.init(message: message, correlationId: correlationId, errorCodes: errorCodes, errorUri: errorUri) + } + + /// Describes why an error occurred and provides more information about the error. + public override var errorDescription: String? { + if let description = super.errorDescription { + return description + } + + switch type { + case .browserRequired: + return MSALNativeAuthErrorMessage.browserRequired + case .generalError: + return MSALNativeAuthErrorMessage.generalError + } + } + + /// Returns `true` if a browser is required to continue the operation. + public var isBrowserRequired: Bool { + return type == .browserRequired + } + + func toMFARequestChallengeError() -> MFARequestChallengeError { + var requestChallengeType = MFARequestChallengeError.ErrorType.browserRequired + switch type { + case .browserRequired: + requestChallengeType = .browserRequired + case .generalError: + requestChallengeType = .generalError + } + return MFARequestChallengeError( + type: requestChallengeType, + message: errorDescription, + correlationId: correlationId, + errorCodes: errorCodes, + errorUri: errorUri + ) + } +} diff --git a/MSAL/src/native_auth/public/state_machine/error/MFARequestChallengeError.swift b/MSAL/src/native_auth/public/state_machine/error/MFARequestChallengeError.swift new file mode 100644 index 0000000000..d133b79e36 --- /dev/null +++ b/MSAL/src/native_auth/public/state_machine/error/MFARequestChallengeError.swift @@ -0,0 +1,60 @@ +// +// 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 Foundation + +/// Class that defines the structure and type of a MFARequestChallengeError +@objcMembers +public class MFARequestChallengeError: MSALNativeAuthError { + enum ErrorType: CaseIterable { + case browserRequired + case generalError + } + + let type: ErrorType + + init(type: ErrorType, message: String? = nil, correlationId: UUID, errorCodes: [Int] = [], errorUri: String? = nil) { + self.type = type + super.init(message: message, correlationId: correlationId, errorCodes: errorCodes, errorUri: errorUri) + } + + /// Describes why an error occurred and provides more information about the error. + public override var errorDescription: String? { + if let description = super.errorDescription { + return description + } + + switch type { + case .browserRequired: + return MSALNativeAuthErrorMessage.browserRequired + case .generalError: + return MSALNativeAuthErrorMessage.generalError + } + } + + /// Returns `true` if a browser is required to continue the operation. + public var isBrowserRequired: Bool { + return type == .browserRequired + } +} diff --git a/MSAL/src/native_auth/public/state_machine/error/MFASubmitChallengeError.swift b/MSAL/src/native_auth/public/state_machine/error/MFASubmitChallengeError.swift new file mode 100644 index 0000000000..0c7923b39b --- /dev/null +++ b/MSAL/src/native_auth/public/state_machine/error/MFASubmitChallengeError.swift @@ -0,0 +1,76 @@ +// +// 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 Foundation + +/// Class that defines the structure and type of a MFASubmitChallengeError +@objcMembers +public class MFASubmitChallengeError: MSALNativeAuthError { + enum ErrorType: CaseIterable { + case invalidChallenge + case generalError + } + + let type: ErrorType + + init(type: ErrorType, message: String? = nil, correlationId: UUID, errorCodes: [Int] = [], errorUri: String? = nil) { + self.type = type + super.init(message: message, correlationId: correlationId, errorCodes: errorCodes, errorUri: errorUri) + } + + init(error: VerifyCodeError) { + switch error.type { + case .browserRequired, + .generalError: + self.type = .generalError + case .invalidCode: + self.type = .invalidChallenge + } + super.init( + message: error.errorDescription, + correlationId: error.correlationId, + errorCodes: error.errorCodes, + errorUri: error.errorUri + ) + } + + /// Describes why an error occurred and provides more information about the error. + public override var errorDescription: String? { + if let description = super.errorDescription { + return description + } + + switch type { + case .invalidChallenge: + return MSALNativeAuthErrorMessage.invalidChallenge + case .generalError: + return MSALNativeAuthErrorMessage.generalError + } + } + + /// Returns `true` when the challenge introduced is not valid. + public var isInvalidChallenge: Bool { + return type == .invalidChallenge + } +} diff --git a/MSAL/src/native_auth/public/state_machine/state/MFAStates+Internal.swift b/MSAL/src/native_auth/public/state_machine/state/MFAStates+Internal.swift new file mode 100644 index 0000000000..19f6ac14a7 --- /dev/null +++ b/MSAL/src/native_auth/public/state_machine/state/MFAStates+Internal.swift @@ -0,0 +1,57 @@ +// +// 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 Foundation + +extension MFABaseState { + func requestChallengeInternal(authMethod: MSALAuthMethod?) async -> MSALNativeAuthMFAControlling.MFARequestChallengeControllerResponse { + let context = MSALNativeAuthRequestContext(correlationId: correlationId) + MSALLogger.log(level: .warning, context: context, format: MSALNativeAuthLogMessage.privatePreviewLog) + MSALLogger.log(level: .info, context: context, format: "MFA, request challenge") + return await controller.requestChallenge(continuationToken: continuationToken, authMethod: authMethod, context: context, scopes: scopes) + } +} + +extension MFARequiredState { + func getAuthMethodsInternal() async -> MSALNativeAuthMFAControlling.MFAGetAuthMethodsControllerResponse { + let context = MSALNativeAuthRequestContext(correlationId: correlationId) + MSALLogger.log(level: .warning, context: context, format: MSALNativeAuthLogMessage.privatePreviewLog) + MSALLogger.log(level: .info, context: context, format: "MFA, get authentication methods") + return await controller.getAuthMethods(continuationToken: continuationToken, context: context, scopes: scopes) + } + + func submitChallengeInternal(challenge: String) async -> MSALNativeAuthMFAControlling.MFASubmitChallengeControllerResponse { + let context = MSALNativeAuthRequestContext(correlationId: correlationId) + MSALLogger.log(level: .warning, context: context, format: MSALNativeAuthLogMessage.privatePreviewLog) + MSALLogger.log(level: .info, context: context, format: "MFA, submit challenge") + guard inputValidator.isInputValid(challenge) else { + MSALLogger.log(level: .error, context: context, format: "MFA, invalid challenge") + return .init( + .error(error: MFASubmitChallengeError(type: .invalidChallenge, correlationId: correlationId), newState: self), + correlationId: context.correlationId() + ) + } + return await controller.submitChallenge(challenge: challenge, continuationToken: continuationToken, context: context, scopes: scopes) + } +} diff --git a/MSAL/src/native_auth/public/state_machine/state/MFAStates.swift b/MSAL/src/native_auth/public/state_machine/state/MFAStates.swift new file mode 100644 index 0000000000..bef0e79199 --- /dev/null +++ b/MSAL/src/native_auth/public/state_machine/state/MFAStates.swift @@ -0,0 +1,141 @@ +// +// 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 Foundation + +/// Base class for MFA state +@objcMembers public class MFABaseState: MSALNativeAuthBaseState { + let controller: MSALNativeAuthMFAControlling + let scopes: [String] + + init( + controller: MSALNativeAuthMFAControlling, + scopes: [String], + continuationToken: String, + correlationId: UUID) { + self.controller = controller + self.scopes = scopes + super.init(continuationToken: continuationToken, correlationId: correlationId) + } + + func baseRequestChallenge(authMethod: MSALAuthMethod?, delegate: MFARequestChallengeDelegate) { + Task { + let controllerResponse = await requestChallengeInternal(authMethod: authMethod) + let delegateDispatcher = MFARequestChallengeDelegateDispatcher(delegate: delegate, telemetryUpdate: controllerResponse.telemetryUpdate) + switch controllerResponse.result { + case .verificationRequired(let sentTo, let channelTargetType, let codeLength, let newState): + await delegateDispatcher.dispatchVerificationRequired( + newState: newState, + sentTo: sentTo, + channelTargetType: channelTargetType, + codeLength: codeLength, + correlationId: controllerResponse.correlationId + ) + case .selectionRequired(let authMethods, let newState): + await delegateDispatcher.dispatchSelectionRequired( + authMethods: authMethods, + newState: newState, + correlationId: controllerResponse.correlationId + ) + case .error(let error, let newState): + await delegate.onMFARequestChallengeError(error: error, newState: newState) + } + } + } +} + +/// An object of this type is created whenever a user needs to make a specific request to send the MFA challenge. +@objcMembers +public class AwaitingMFAState: MFABaseState { + + /// Requests the server to send the challenge to the default authentication method. + /// - Warning: ⚠️ this API is experimental. It may be changed in the future without notice. Do not use in production applications. + /// - Parameter delegate: Delegate that receives callbacks for the operation. + public func requestChallenge(delegate: MFARequestChallengeDelegate) { + baseRequestChallenge(authMethod: nil, delegate: delegate) + } +} + +@objcMembers +public class MFARequiredState: MFABaseState { + + let inputValidator: MSALNativeAuthInputValidating + + init( + inputValidator: MSALNativeAuthInputValidating = MSALNativeAuthInputValidator(), + controller: MSALNativeAuthMFAControlling, + scopes: [String], + continuationToken: String, + correlationId: UUID) { + self.inputValidator = inputValidator + super.init(controller: controller, scopes: scopes, continuationToken: continuationToken, correlationId: correlationId) + } + + /// Requests the server to send the challenge to the specified auth method or the default one. + /// - Warning: ⚠️ this API is experimental. It may be changed in the future without notice. Do not use in production applications. + /// - Parameters: + /// - authMethod: Optional. The authentication method you want to use for sending the challenge + /// - delegate: Delegate that receives callbacks for the operation. + public func requestChallenge(authMethod: MSALAuthMethod? = nil, delegate: MFARequestChallengeDelegate) { + baseRequestChallenge(authMethod: authMethod, delegate: delegate) + } + + /// Requests the available MFA authentication methods. + /// - Warning: ⚠️ this API is experimental. It may be changed in the future without notice. Do not use in production applications. + /// - Parameter delegate: Delegate that receives callbacks for the operation. + public func getAuthMethods(delegate: MFAGetAuthMethodsDelegate) { + Task { + let controllerResponse = await getAuthMethodsInternal() + let delegateDispatcher = MFAGetAuthMethodsDelegateDispatcher(delegate: delegate, telemetryUpdate: controllerResponse.telemetryUpdate) + switch controllerResponse.result { + case .selectionRequired(let authMethods, let newState): + await delegateDispatcher.dispatchSelectionRequired( + authMethods: authMethods, + newState: newState, + correlationId: controllerResponse.correlationId + ) + case .error(let error, let newState): + await delegate.onMFAGetAuthMethodsError(error: error, newState: newState) + } + } + } + + /// Submits the MFA challenge to the server for verification. + /// - Warning: ⚠️ this API is experimental. It may be changed in the future without notice. Do not use in production applications. + /// - Parameters: + /// - challenge: Verification challenge that the user supplies. + /// - delegate: Delegate that receives callbacks for the operation. + public func submitChallenge(challenge: String, delegate: MFASubmitChallengeDelegate) { + Task { + let controllerResponse = await submitChallengeInternal(challenge: challenge) + let delegateDispatcher = MFASubmitChallengeDelegateDispatcher(delegate: delegate, telemetryUpdate: controllerResponse.telemetryUpdate) + switch controllerResponse.result { + case .completed(let result): + await delegateDispatcher.dispatchSignInCompleted(result: result, correlationId: controllerResponse.correlationId) + case .error(let error, let newState): + await delegate.onMFASubmitChallengeError(error: error, newState: newState) + } + } + } +} diff --git a/MSAL/src/native_auth/public/state_machine/state/SignInAfterPreviousFlowBaseState+Internal.swift b/MSAL/src/native_auth/public/state_machine/state/SignInAfterPreviousFlowBaseState+Internal.swift index f7e20ccc77..6182dad9af 100644 --- a/MSAL/src/native_auth/public/state_machine/state/SignInAfterPreviousFlowBaseState+Internal.swift +++ b/MSAL/src/native_auth/public/state_machine/state/SignInAfterPreviousFlowBaseState+Internal.swift @@ -26,8 +26,17 @@ import Foundation extension SignInAfterPreviousFlowBaseState { - func signInInternal(scopes: [String]?) async -> MSALNativeAuthSignInControlling.SignInAfterPreviousFlowControllerResponse { + func signInInternal( + scopes: [String]?, + telemetryId: MSALNativeAuthTelemetryApiId + ) async -> MSALNativeAuthSignInControlling.SignInAfterPreviousFlowControllerResponse { let context = MSALNativeAuthRequestContext(correlationId: correlationId) - return await controller.signIn(username: username, continuationToken: continuationToken, scopes: scopes, context: context) + return await controller.signIn( + username: username, + continuationToken: continuationToken, + scopes: scopes, + telemetryId: telemetryId, + context: context + ) } } diff --git a/MSAL/src/native_auth/public/state_machine/state/SignInAfterResetPasswordState.swift b/MSAL/src/native_auth/public/state_machine/state/SignInAfterResetPasswordState.swift index 976e7ba0b6..59f8346f66 100644 --- a/MSAL/src/native_auth/public/state_machine/state/SignInAfterResetPasswordState.swift +++ b/MSAL/src/native_auth/public/state_machine/state/SignInAfterResetPasswordState.swift @@ -32,7 +32,7 @@ import Foundation /// - delegate: Delegate that receives callbacks for the Sign In flow. public func signIn(scopes: [String]? = nil, delegate: SignInAfterResetPasswordDelegate) { Task { - let controllerResponse = await signInInternal(scopes: scopes) + let controllerResponse = await signInInternal(scopes: scopes, telemetryId: .telemetryApiIdSignInAfterPasswordReset) let delegateDispatcher = SignInAfterResetPasswordDelegateDispatcher( delegate: delegate, telemetryUpdate: controllerResponse.telemetryUpdate diff --git a/MSAL/src/native_auth/public/state_machine/state/SignInAfterSignUpState.swift b/MSAL/src/native_auth/public/state_machine/state/SignInAfterSignUpState.swift index d0b4ce98a6..15a08f7114 100644 --- a/MSAL/src/native_auth/public/state_machine/state/SignInAfterSignUpState.swift +++ b/MSAL/src/native_auth/public/state_machine/state/SignInAfterSignUpState.swift @@ -32,7 +32,7 @@ import Foundation /// - delegate: Delegate that receives callbacks for the Sign In flow. public func signIn(scopes: [String]? = nil, delegate: SignInAfterSignUpDelegate) { Task { - let controllerResponse = await signInInternal(scopes: scopes) + let controllerResponse = await signInInternal(scopes: scopes, telemetryId: .telemetryApiIdSignInAfterSignUp) let delegateDispatcher = SignInAfterSignUpDelegateDispatcher(delegate: delegate, telemetryUpdate: controllerResponse.telemetryUpdate) switch controllerResponse.result { diff --git a/MSAL/src/native_auth/public/state_machine/state/SignInStates.swift b/MSAL/src/native_auth/public/state_machine/state/SignInStates.swift index 750c1da498..027953ca05 100644 --- a/MSAL/src/native_auth/public/state_machine/state/SignInStates.swift +++ b/MSAL/src/native_auth/public/state_machine/state/SignInStates.swift @@ -128,6 +128,8 @@ import Foundation await delegateDispatcher.dispatchSignInCompleted(result: accountResult, correlationId: controllerResponse.correlationId) case .error(let error, let newState): await delegate.onSignInPasswordRequiredError(error: error, newState: newState) + case .awaitingMFA(let newState): + await delegateDispatcher.dispatchAwaitingMFA(newState: newState, correlationId: controllerResponse.correlationId) } } } diff --git a/MSAL/src/native_auth/telemetry/MSALNativeAuthOperationTypes.swift b/MSAL/src/native_auth/telemetry/MSALNativeAuthOperationTypes.swift index 0a4e8d9ce4..25a6fd93ae 100644 --- a/MSAL/src/native_auth/telemetry/MSALNativeAuthOperationTypes.swift +++ b/MSAL/src/native_auth/telemetry/MSALNativeAuthOperationTypes.swift @@ -41,6 +41,7 @@ enum MSALNativeAuthSignInType: MSALNativeAuthOperationType { case signInWithMFA = 1 case signInInitiate = 2 case signInChallenge = 3 + case signInIntrospect = 4 } enum MSALNativeAuthTokenType: MSALNativeAuthOperationType { diff --git a/MSAL/src/native_auth/telemetry/MSALNativeAuthTelemetryApiId.swift b/MSAL/src/native_auth/telemetry/MSALNativeAuthTelemetryApiId.swift index 57b1aa78ca..7672ad8e08 100644 --- a/MSAL/src/native_auth/telemetry/MSALNativeAuthTelemetryApiId.swift +++ b/MSAL/src/native_auth/telemetry/MSALNativeAuthTelemetryApiId.swift @@ -39,6 +39,7 @@ enum MSALNativeAuthTelemetryApiId: Int { case telemetryApiIdSignInWithPasswordStart = 74001 case telemetryApiIdSignInWithCodeStart = 74002 case telemetryApiIdSignInAfterSignUp = 740014 + case telemetryApiIdSignInAfterPasswordReset = 740015 case telemetryApiIdSignInSubmitCode = 74003 case telemetryApiIdSignInResendCode = 74004 case telemetryApiIdSignInSubmitPassword = 74005 @@ -53,5 +54,7 @@ enum MSALNativeAuthTelemetryApiId: Int { case telemetryApiIdSignUpSubmitCode = 75012 case telemetryApiIdSignUpSubmitPassword = 75013 case telemetryApiIdSignUpSubmitAttributes = 75014 - + case telemetryApiIdMFARequestChallenge = 75016 + case telemetryApiIdMFAGetAuthMethods = 75017 + case telemetryApiIdMFASubmitChallenge = 75018 } diff --git a/MSAL/test/integration/native_auth/common/Model.swift b/MSAL/test/integration/native_auth/common/Model.swift index ffc452dec6..f769e2d3e1 100644 --- a/MSAL/test/integration/native_auth/common/Model.swift +++ b/MSAL/test/integration/native_auth/common/Model.swift @@ -33,6 +33,7 @@ enum MockAPIError: Error { enum MockAPIEndpoint: String { case signInInitiate = "SignInInitiate" case signInChallenge = "SignInChallenge" + case signInIntrospect = "SignInIntrospect" case signInToken = "SignInToken" case signUpStart = "SignUpStart" case signUpChallenge = "SignUpChallenge" @@ -77,6 +78,9 @@ enum MockAPIResponse: String { case verificationRequired = "VerificationRequired" case attributeValidationFailed = "AttributeValidationFailed" case invalidContinuationToken = "InvalidContinuationToken" + case signInIntrospectSuccess = "IntrospectSuccess" + case mfaRequired = "MFARequired" + case introspectRequired = "IntrospectRequired" case ssprStartSuccess = "SSPRStartSuccess" case ssprContinueSuccess = "SSPRContinueSuccess" case ssprSubmitSuccess = "SSPRSubmitSuccess" diff --git a/MSAL/test/integration/native_auth/end_to_end/MSALNativeAuthEndToEndBaseTestCase.swift b/MSAL/test/integration/native_auth/end_to_end/MSALNativeAuthEndToEndBaseTestCase.swift index f98ecb32e9..a3b232237e 100644 --- a/MSAL/test/integration/native_auth/end_to_end/MSALNativeAuthEndToEndBaseTestCase.swift +++ b/MSAL/test/integration/native_auth/end_to_end/MSALNativeAuthEndToEndBaseTestCase.swift @@ -34,6 +34,8 @@ class MSALNativeAuthEndToEndBaseTestCase: XCTestCase { static let clientIdEmailCodeAttributesKey = "email_code_attributes_client_id" static let tenantSubdomainKey = "tenant_subdomain" static let signInEmailPasswordUsernameKey = "sign_in_email_password_username" + static let signInEmailPasswordMFAUsernameKey = "sign_in_email_password_mfa_username" + static let signInEmailPasswordMFANoDefaultAuthMethodUsernameKey = "sign_in_email_password_mfa_no_default_username" static let signInEmailCodeUsernameKey = "sign_in_email_code_username" static let resetPasswordUsernameKey = "reset_password_username" } @@ -95,6 +97,14 @@ class MSALNativeAuthEndToEndBaseTestCase: XCTestCase { return MSALNativeAuthEndToEndBaseTestCase.nativeAuthConfFileContent?[Constants.signInEmailPasswordUsernameKey] } + func retrieveUsernameForSignInUsernamePasswordAndMFA() -> String? { + return MSALNativeAuthEndToEndBaseTestCase.nativeAuthConfFileContent?[Constants.signInEmailPasswordMFAUsernameKey] + } + + func retrieveUsernameForSignInUsernamePasswordAndMFANoDefaultAuthMethod() -> String? { + return MSALNativeAuthEndToEndBaseTestCase.nativeAuthConfFileContent?[Constants.signInEmailPasswordMFANoDefaultAuthMethodUsernameKey] + } + func retrieveUsernameForResetPassword() -> String? { return MSALNativeAuthEndToEndBaseTestCase.nativeAuthConfFileContent?[Constants.resetPasswordUsernameKey] } diff --git a/MSAL/test/integration/native_auth/end_to_end/mfa/MFADelegateSpies.swift b/MSAL/test/integration/native_auth/end_to_end/mfa/MFADelegateSpies.swift new file mode 100644 index 0000000000..09e5931ccd --- /dev/null +++ b/MSAL/test/integration/native_auth/end_to_end/mfa/MFADelegateSpies.swift @@ -0,0 +1,131 @@ +// +// 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 Foundation +import XCTest +import MSAL + +class MFARequestChallengeDelegateSpy: MFARequestChallengeDelegate { + + private let expectation: XCTestExpectation + private(set) var onMFARequestChallengeError = false + private(set) var error: MSAL.MFARequestChallengeError? + + private(set) var onVerificationRequiredCalled = false + private(set) var newStateMFARequired: MSAL.MFARequiredState? + private(set) var sentTo: String? + private(set) var channelTargetType: MSAL.MSALNativeAuthChannelType? + private(set) var codeLength: Int? + + private(set) var onSelectionRequiredCalled = false + private(set) var authMethods: [MSALAuthMethod]? + + init(expectation: XCTestExpectation) { + self.expectation = expectation + } + + func onMFARequestChallengeError(error: MSAL.MFARequestChallengeError, newState: MSAL.MFARequiredState?) { + onMFARequestChallengeError = true + self.newStateMFARequired = newState + self.error = error + + expectation.fulfill() + } + + func onMFARequestChallengeSelectionRequired(authMethods: [MSALAuthMethod], newState: MFARequiredState) { + onSelectionRequiredCalled = true + self.newStateMFARequired = newState + self.authMethods = authMethods + + expectation.fulfill() + } + + func onMFARequestChallengeVerificationRequired(newState: MFARequiredState, sentTo: String, channelTargetType: MSALNativeAuthChannelType, codeLength: Int) { + onVerificationRequiredCalled = true + self.newStateMFARequired = newState + self.sentTo = sentTo + self.channelTargetType = channelTargetType + self.codeLength = codeLength + + expectation.fulfill() + } +} + +final class MFASubmitChallengeDelegateSpy: MFASubmitChallengeDelegate { + + private let expectation: XCTestExpectation + private(set) var onSignInCompletedCalled = false + private(set) var onMFASubmitChallengeErrorCalled = false + private(set) var error: MSAL.MFASubmitChallengeError? + private(set) var result: MSAL.MSALNativeAuthUserAccountResult? + private(set) var newStateMFARequiredState: MSAL.MFARequiredState? + + init(expectation: XCTestExpectation) { + self.expectation = expectation + } + + func onMFASubmitChallengeError(error: MSAL.MFASubmitChallengeError, newState: MSAL.MFARequiredState?) { + onMFASubmitChallengeErrorCalled = true + self.newStateMFARequiredState = newState + self.error = error + + expectation.fulfill() + } + + func onSignInCompleted(result: MSALNativeAuthUserAccountResult) { + onSignInCompletedCalled = true + self.result = result + + expectation.fulfill() + } +} + +final class MFAGetAuthMethodsDelegateSpy: MFAGetAuthMethodsDelegate { + + private let expectation: XCTestExpectation + private(set) var onSelectionRequiredCalled = false + private(set) var onMFAGetAuthMethodsErrorCalled = false + private(set) var authMethods: [MSALAuthMethod]? + private(set) var error: MSAL.MFAGetAuthMethodsError? + private(set) var newStateMFARequired: MSAL.MFARequiredState? + + init(expectation: XCTestExpectation) { + self.expectation = expectation + } + + func onMFAGetAuthMethodsError(error: MSAL.MFAGetAuthMethodsError, newState: MSAL.MFARequiredState?) { + onMFAGetAuthMethodsErrorCalled = true + self.error = error + + expectation.fulfill() + } + + func onMFAGetAuthMethodsSelectionRequired(authMethods: [MSALAuthMethod], newState: MFARequiredState) { + onSelectionRequiredCalled = true + self.newStateMFARequired = newState + self.authMethods = authMethods + + expectation.fulfill() + } +} diff --git a/MSAL/test/integration/native_auth/end_to_end/mfa/MSALNativeAuthSignInWithMFAEndToEndTests.swift b/MSAL/test/integration/native_auth/end_to_end/mfa/MSALNativeAuthSignInWithMFAEndToEndTests.swift new file mode 100644 index 0000000000..83e70bb6bf --- /dev/null +++ b/MSAL/test/integration/native_auth/end_to_end/mfa/MSALNativeAuthSignInWithMFAEndToEndTests.swift @@ -0,0 +1,223 @@ +// +// 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 Foundation +import XCTest +import MSAL + +final class MSALNativeAuthSignInWithMFAEndToEndTests: MSALNativeAuthEndToEndPasswordTestCase { + + func test_signInUsingPasswordWithMFASubmitWrongChallengeResendChallengeThen_completeSuccessfully() async throws { + guard let username = retrieveUsernameForSignInUsernamePasswordAndMFA(), + let password = await retrievePasswordForSignInUsername(), + let awaitingMFAState = await signInUsernameAndPassword(username: username, password: password) + else { + XCTFail("Something went wrong") + return + } + + // Request to send challenge to the default strong auth method + let mfaExpectation = expectation(description: "mfa") + let mfaDelegateSpy = MFARequestChallengeDelegateSpy(expectation: mfaExpectation) + + awaitingMFAState.requestChallenge(delegate: mfaDelegateSpy) + + await fulfillment(of: [mfaExpectation]) + + guard mfaDelegateSpy.onVerificationRequiredCalled, let mfaRequiredState = mfaDelegateSpy.newStateMFARequired else { + XCTFail("Challenge not sent to MFA method") + return + } + + // Now submit the wrong email OTP code + let submitWrongChallengeExpectation = expectation(description: "submitChallenge") + let mfaSubmitWrongChallengeDelegateSpy = MFASubmitChallengeDelegateSpy(expectation: submitWrongChallengeExpectation) + + mfaRequiredState.submitChallenge(challenge: "wrong_code", delegate: mfaSubmitWrongChallengeDelegateSpy) + + await fulfillment(of: [submitWrongChallengeExpectation]) + + XCTAssertTrue(mfaSubmitWrongChallengeDelegateSpy.onMFASubmitChallengeErrorCalled) + XCTAssertEqual(mfaSubmitWrongChallengeDelegateSpy.error?.isInvalidChallenge, true) + + guard let mfaRequiredState = mfaSubmitWrongChallengeDelegateSpy.newStateMFARequiredState else { + XCTFail("New state not received after SDK error") + return + } + + // Resend code to default auth method + + let mfaResendChallengeExpectation = expectation(description: "mfa") + let mfaResendChallengeDelegateSpy = MFARequestChallengeDelegateSpy(expectation: mfaResendChallengeExpectation) + + mfaRequiredState.requestChallenge(delegate: mfaResendChallengeDelegateSpy) + + await fulfillment(of: [mfaResendChallengeExpectation]) + + guard mfaResendChallengeDelegateSpy.onVerificationRequiredCalled, let mfaRequiredState = mfaDelegateSpy.newStateMFARequired else { + XCTFail("Challenge not sent to MFA method") + return + } + + // Now retrieve and submit the email OTP code + await completeSignInWithMFAFlow(state: mfaRequiredState, username: username) + } + + func test_signInUsingPasswordWithMFAGetAuthMethods_thenCompleteSuccessfully() async throws { + guard let username = retrieveUsernameForSignInUsernamePasswordAndMFA(), + let password = await retrievePasswordForSignInUsername(), + let awaitingMFAState = await signInUsernameAndPassword(username: username, password: password) + else { + XCTFail("Something went wrong") + return + } + + // Request to send challenge to the default strong auth method + let mfaExpectation = expectation(description: "mfa") + let mfaDelegateSpy = MFARequestChallengeDelegateSpy(expectation: mfaExpectation) + + awaitingMFAState.requestChallenge(delegate: mfaDelegateSpy) + + await fulfillment(of: [mfaExpectation]) + + guard mfaDelegateSpy.onVerificationRequiredCalled, let mfaRequiredState = mfaDelegateSpy.newStateMFARequired else { + XCTFail("Challenge not sent to MFA method") + return + } + + // Now retrieve the list of auth methods + let getAuthMethodsExpectation = expectation(description: "getAuthmethods") + let mfaGetAuthMethodsDelegateSpy = MFAGetAuthMethodsDelegateSpy(expectation: getAuthMethodsExpectation) + + mfaRequiredState.getAuthMethods(delegate: mfaGetAuthMethodsDelegateSpy) + + await fulfillment(of: [getAuthMethodsExpectation]) + + guard let authMethod = mfaGetAuthMethodsDelegateSpy.authMethods?.first, let mfaRequiredState = mfaGetAuthMethodsDelegateSpy.newStateMFARequired else { + XCTFail("No MFA auth methods returned") + return + } + + XCTAssertTrue(authMethod.channelTargetType.isEmailType) + XCTAssertTrue(mfaGetAuthMethodsDelegateSpy.onSelectionRequiredCalled) + + // Request to send challenge to a specific strong auth method + + let mfaSendChallengeExpectation = expectation(description: "mfa") + let mfaSendChallengeDelegateSpy = MFARequestChallengeDelegateSpy(expectation: mfaSendChallengeExpectation) + mfaRequiredState.requestChallenge(authMethod: authMethod, delegate: mfaSendChallengeDelegateSpy) + + await fulfillment(of: [mfaSendChallengeExpectation]) + + guard mfaSendChallengeDelegateSpy.onVerificationRequiredCalled, let mfaRequiredState = mfaSendChallengeDelegateSpy.newStateMFARequired else { + XCTFail("Challenge not sent to MFA method") + return + } + + // Now retrieve and submit the email OTP code + await completeSignInWithMFAFlow(state: mfaRequiredState, username: username) + } + + func test_signInUsingPasswordWithMFANoDefaultAuthMethod_completeSuccessfully() async throws { + guard let username = retrieveUsernameForSignInUsernamePasswordAndMFANoDefaultAuthMethod(), + let password = await retrievePasswordForSignInUsername(), + let awaitingMFAState = await signInUsernameAndPassword(username: username, password: password) + else { + XCTFail("Something went wrong") + return + } + + // Request to send challenge to the default strong auth method + let mfaExpectation = expectation(description: "mfa") + let mfaDelegateSpy = MFARequestChallengeDelegateSpy(expectation: mfaExpectation) + + awaitingMFAState.requestChallenge(delegate: mfaDelegateSpy) + + await fulfillment(of: [mfaExpectation]) + + guard mfaDelegateSpy.onSelectionRequiredCalled, let mfaRequiredState = mfaDelegateSpy.newStateMFARequired, let authMethod = mfaDelegateSpy.authMethods?.first else { + XCTFail("Selection required not triggered") + return + } + + XCTAssertTrue(authMethod.channelTargetType.isEmailType) + + // Request to send challenge to a specific strong auth method + + let mfaSendChallengeExpectation = expectation(description: "mfa") + let mfaSendChallengeDelegateSpy = MFARequestChallengeDelegateSpy(expectation: mfaSendChallengeExpectation) + mfaRequiredState.requestChallenge(authMethod: authMethod, delegate: mfaSendChallengeDelegateSpy) + + await fulfillment(of: [mfaSendChallengeExpectation]) + + guard mfaSendChallengeDelegateSpy.onVerificationRequiredCalled, let mfaRequiredState = mfaSendChallengeDelegateSpy.newStateMFARequired else { + XCTFail("Challenge not sent to MFA method") + return + } + + // Now retrieve and submit the email OTP code + await completeSignInWithMFAFlow(state: mfaRequiredState, username: username) + } + + // MARK: private methods + + private func signInUsernameAndPassword(username: String, password: String) async -> AwaitingMFAState? { + guard let application = initialisePublicClientApplication() + else { + XCTFail("Missing information") + return nil + } + let signInExpectation = expectation(description: "signing in") + let signInDelegateSpy = SignInPasswordStartDelegateSpy(expectation: signInExpectation) + + application.signIn(username: username, password: password, correlationId: correlationId, delegate: signInDelegateSpy) + + await fulfillment(of: [signInExpectation]) + + guard signInDelegateSpy.onSignInAwaitingMFACalled, let awaitingMFAState = signInDelegateSpy.newStateAwaitingMFA else { + XCTFail("Awaiting MFA not called") + return nil + } + return awaitingMFAState + } + + private func completeSignInWithMFAFlow(state: MFARequiredState, username: String) async { + guard let code = await retrieveCodeFor(email: username) else { + XCTFail("OTP code could not be retrieved") + return + } + + let submitChallengeExpectation = expectation(description: "submitChallenge") + let mfaSubmitChallengeDelegateSpy = MFASubmitChallengeDelegateSpy(expectation: submitChallengeExpectation) + + state.submitChallenge(challenge: code, delegate: mfaSubmitChallengeDelegateSpy) + + await fulfillment(of: [submitChallengeExpectation]) + + XCTAssertTrue(mfaSubmitChallengeDelegateSpy.onSignInCompletedCalled) + XCTAssertNotNil(mfaSubmitChallengeDelegateSpy.result) + XCTAssertNotNil(mfaSubmitChallengeDelegateSpy.result?.idToken) + XCTAssertEqual(mfaSubmitChallengeDelegateSpy.result?.account.username, username) + } +} diff --git a/MSAL/test/integration/native_auth/end_to_end/reset_password/MSALNativeAuthResetPasswordEndToEndTests.swift b/MSAL/test/integration/native_auth/end_to_end/reset_password/MSALNativeAuthResetPasswordEndToEndTests.swift index 21d3b165d6..35d1fcc87e 100644 --- a/MSAL/test/integration/native_auth/end_to_end/reset_password/MSALNativeAuthResetPasswordEndToEndTests.swift +++ b/MSAL/test/integration/native_auth/end_to_end/reset_password/MSALNativeAuthResetPasswordEndToEndTests.swift @@ -47,7 +47,7 @@ final class MSALNativeAuthResetPasswordEndToEndTests: MSALNativeAuthEndToEndBase return } - XCTAssertEqual(resetPasswordStartDelegate.channelTargetType, .email) + XCTAssertEqual(resetPasswordStartDelegate.channelTargetType?.isEmailType, true) XCTAssertFalse(resetPasswordStartDelegate.sentTo?.isEmpty ?? true) XCTAssertNotNil(resetPasswordStartDelegate.codeLength) @@ -103,7 +103,7 @@ final class MSALNativeAuthResetPasswordEndToEndTests: MSALNativeAuthEndToEndBase return } - XCTAssertEqual(resetPasswordStartDelegate.channelTargetType, .email) + XCTAssertEqual(resetPasswordStartDelegate.channelTargetType?.isEmailType, true) XCTAssertFalse(resetPasswordStartDelegate.sentTo?.isEmpty ?? true) XCTAssertNotNil(resetPasswordStartDelegate.codeLength) diff --git a/MSAL/test/integration/native_auth/end_to_end/sign_in/SignInDelegateSpies.swift b/MSAL/test/integration/native_auth/end_to_end/sign_in/SignInDelegateSpies.swift index cda4696f04..6c18ccbdad 100644 --- a/MSAL/test/integration/native_auth/end_to_end/sign_in/SignInDelegateSpies.swift +++ b/MSAL/test/integration/native_auth/end_to_end/sign_in/SignInDelegateSpies.swift @@ -30,8 +30,10 @@ class SignInPasswordStartDelegateSpy: SignInStartDelegate { private let expectation: XCTestExpectation private(set) var onSignInPasswordErrorCalled = false private(set) var onSignInCompletedCalled = false + private(set) var onSignInAwaitingMFACalled = false private(set) var error: MSAL.SignInStartError? private(set) var result: MSAL.MSALNativeAuthUserAccountResult? + private(set) var newStateAwaitingMFA: MSAL.AwaitingMFAState? init(expectation: XCTestExpectation) { self.expectation = expectation @@ -50,6 +52,13 @@ class SignInPasswordStartDelegateSpy: SignInStartDelegate { expectation.fulfill() } + + public func onSignInAwaitingMFA(newState: AwaitingMFAState) { + onSignInAwaitingMFACalled = true + + self.newStateAwaitingMFA = newState + expectation.fulfill() + } } class SignInStartDelegateSpy: SignInStartDelegate { diff --git a/MSAL/test/integration/native_auth/end_to_end/sign_up/MSALNativeAuthSignUpUsernameAndPasswordEndToEndTests.swift b/MSAL/test/integration/native_auth/end_to_end/sign_up/MSALNativeAuthSignUpUsernameAndPasswordEndToEndTests.swift index e71360a754..457be7c159 100644 --- a/MSAL/test/integration/native_auth/end_to_end/sign_up/MSALNativeAuthSignUpUsernameAndPasswordEndToEndTests.swift +++ b/MSAL/test/integration/native_auth/end_to_end/sign_up/MSALNativeAuthSignUpUsernameAndPasswordEndToEndTests.swift @@ -467,7 +467,7 @@ final class MSALNativeAuthSignUpUsernameAndPasswordEndToEndTests: MSALNativeAuth private func checkSignUpStartDelegate(_ delegate: SignUpPasswordStartDelegateSpy) { XCTAssertTrue(delegate.onSignUpCodeRequiredCalled) - XCTAssertEqual(delegate.channelTargetType, .email) + XCTAssertEqual(delegate.channelTargetType?.isEmailType, true) XCTAssertFalse(delegate.sentTo?.isEmpty ?? true) XCTAssertNotNil(delegate.codeLength) } diff --git a/MSAL/test/integration/native_auth/end_to_end/sign_up/MSALNativeAuthSignUpUsernameEndToEndTests.swift b/MSAL/test/integration/native_auth/end_to_end/sign_up/MSALNativeAuthSignUpUsernameEndToEndTests.swift index 43653084bb..9f08a6055e 100644 --- a/MSAL/test/integration/native_auth/end_to_end/sign_up/MSALNativeAuthSignUpUsernameEndToEndTests.swift +++ b/MSAL/test/integration/native_auth/end_to_end/sign_up/MSALNativeAuthSignUpUsernameEndToEndTests.swift @@ -272,7 +272,7 @@ final class MSALNativeAuthSignUpUsernameEndToEndTests: MSALNativeAuthEndToEndBas } private func checkSignUpStartDelegate(_ delegate: SignUpStartDelegateSpy) { - XCTAssertEqual(delegate.channelTargetType, .email) + XCTAssertEqual(delegate.channelTargetType?.isEmailType, true) XCTAssertFalse(delegate.sentTo?.isEmpty ?? true) XCTAssertNotNil(delegate.codeLength) } diff --git a/MSAL/test/integration/native_auth/requests/sign_in/MSALNativeAuthSignInChallengeIntegrationTests.swift b/MSAL/test/integration/native_auth/requests/sign_in/MSALNativeAuthSignInChallengeIntegrationTests.swift index 23bab460ce..a9bb8326f6 100644 --- a/MSAL/test/integration/native_auth/requests/sign_in/MSALNativeAuthSignInChallengeIntegrationTests.swift +++ b/MSAL/test/integration/native_auth/requests/sign_in/MSALNativeAuthSignInChallengeIntegrationTests.swift @@ -40,6 +40,7 @@ class MSALNativeAuthSignInChallengeIntegrationTests: MSALNativeAuthIntegrationBa sut = try provider.challenge( parameters: .init( context: context, + mfaAuthMethodId: nil, continuationToken: "Test Credential Token" ), context: context @@ -82,6 +83,15 @@ class MSALNativeAuthSignInChallengeIntegrationTests: MSALNativeAuthIntegrationBa expectedError: Error(error: .unauthorizedClient, errorDescription: nil, errorCodes: nil, errorURI: nil, innerErrors: nil) ) } + + func test_failRequest_introspectRequired() async throws { + let errorResponse = try await perform_testFail( + endpoint: .signInChallenge, + response: .introspectRequired, + expectedError: Error(error: .invalidRequest, errorDescription: nil, errorCodes: nil, errorURI: nil, innerErrors: nil, subError: .introspectRequired) + ) + XCTAssertEqual(errorResponse.subError, .introspectRequired) + } func test_failRequest_invalidContinuationToken() async throws { try await perform_testFail( diff --git a/MSAL/test/integration/native_auth/requests/sign_in/MSALNativeAuthSignInIntrospectIntegrationTests.swift b/MSAL/test/integration/native_auth/requests/sign_in/MSALNativeAuthSignInIntrospectIntegrationTests.swift new file mode 100644 index 0000000000..d2bb730bc2 --- /dev/null +++ b/MSAL/test/integration/native_auth/requests/sign_in/MSALNativeAuthSignInIntrospectIntegrationTests.swift @@ -0,0 +1,78 @@ +// +// 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 XCTest +@testable import MSAL +@_implementationOnly import MSAL_Private + +class MSALNativeAuthSignInIntrospectIntegrationTests: MSALNativeAuthIntegrationBaseTests { + private typealias Error = MSALNativeAuthSignInIntrospectResponseError + private var provider: MSALNativeAuthSignInRequestProvider! + + override func setUpWithError() throws { + try super.setUpWithError() + + provider = MSALNativeAuthSignInRequestProvider(requestConfigurator: MSALNativeAuthRequestConfigurator(config: config)) + + let context = MSALNativeAuthRequestContext(correlationId: correlationId) + + sut = try provider.introspect( + parameters: .init( + context: context, + continuationToken: "Test Credential Token" + ), + context: context + ) + } + + func test_succeedRequest_challengeTypeRedirect() async throws { + try await mockResponse(.challengeTypeRedirect, endpoint: .signInIntrospect) + let response: MSALNativeAuthSignInIntrospectResponse? = try await performTestSucceed() + + XCTAssertNil(response?.continuationToken) + XCTAssertEqual(response?.challengeType, .redirect) + } + + func test_succeedRequest_successfulResult() async throws { + try await mockResponse(.signInIntrospectSuccess, endpoint: .signInIntrospect) + let response: MSALNativeAuthSignInIntrospectResponse? = try await performTestSucceed() + + XCTAssertNotNil(response?.continuationToken) + guard let firstMethod = response?.methods?.first else { + return XCTFail("No authentication method returned") + } + XCTAssertEqual(firstMethod.id, "F37D8C55-BE83-449F-8F99-131F6553871D") + XCTAssertEqual(firstMethod.challengeChannel, "email") + XCTAssertEqual(firstMethod.challengeType, .oob) + XCTAssertEqual(firstMethod.loginHint, "**o*@c****so.com") + } + + func test_failRequest_invalidRequest() async throws { + try await perform_testFail( + endpoint: .signInIntrospect, + response: .invalidToken, + expectedError: Error(error: .invalidRequest, errorDescription: nil, errorCodes: nil, errorURI: nil, innerErrors: nil) + ) + } +} diff --git a/MSAL/test/integration/native_auth/requests/token/MSALNativeAuthTokenIntegrationTests.swift b/MSAL/test/integration/native_auth/requests/token/MSALNativeAuthTokenIntegrationTests.swift index 80b48bbdfd..96d2698efd 100644 --- a/MSAL/test/integration/native_auth/requests/token/MSALNativeAuthTokenIntegrationTests.swift +++ b/MSAL/test/integration/native_auth/requests/token/MSALNativeAuthTokenIntegrationTests.swift @@ -106,6 +106,15 @@ class MSALNativeAuthTokenIntegrationTests: MSALNativeAuthIntegrationBaseTests { expectedError: Error(error: .invalidRequest, errorDescription: nil, errorCodes: [55000], errorURI: nil, innerErrors: nil) ) } + + func test_failRequest_mfaRequired() async throws { + let errorResponse = try await perform_testFail( + endpoint: .signInToken, + response: .mfaRequired, + expectedError: Error(error: .invalidGrant, subError: .mfaRequired, errorDescription: nil, errorCodes: nil, errorURI: nil, innerErrors: nil) + ) + XCTAssertEqual(errorResponse.subError, .mfaRequired) + } func test_failRequest_invalidPassword() async throws { try await perform_testFail( diff --git a/MSAL/test/unit/native_auth/controllers/MSALNativeAuthMFAControllerTests.swift b/MSAL/test/unit/native_auth/controllers/MSALNativeAuthMFAControllerTests.swift new file mode 100644 index 0000000000..8dc0650f94 --- /dev/null +++ b/MSAL/test/unit/native_auth/controllers/MSALNativeAuthMFAControllerTests.swift @@ -0,0 +1,407 @@ +// +// 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 XCTest +@testable import MSAL +@_implementationOnly import MSAL_Private + +class MSALNativeAuthMFAControllerTests: MSALNativeAuthSignInControllerTests { + + func test_signInWithCodeSubmitCodeReceiveStrongAuthRequired_anErrorShouldBeReturned() { + let continuationToken = "continuationToken" + let expectedError = VerifyCodeError(type: .generalError, correlationId: defaultUUID) + + let expectation = expectation(description: "SignInController") + + tokenRequestProviderMock.mockRequestTokenFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) + + tokenResponseValidatorMock.tokenValidatedResponse = .strongAuthRequired(continuationToken: continuationToken) + + let state = SignInCodeRequiredState(scopes: [], controller: sut, inputValidator: MSALNativeAuthInputValidator(), continuationToken: continuationToken, correlationId: defaultUUID) + let delegate = SignInVerifyCodeDelegateSpy(expectation: expectation, expectedError: expectedError) + state.submitCode(code: "code", delegate: delegate) + + wait(for: [expectation], timeout: 1) + XCTAssertFalse(cacheAccessorMock.validateAndSaveTokensWasCalled) + checkTelemetryEventResult(id: .telemetryApiIdSignInSubmitCode, isSuccessful: false) + } + + func test_whenSignInWithCodeReceiveIntrospectRequired_errorShouldBeReturned() async { + let expectedUsername = "username" + let expectedCredentialToken = "continuationToken" + let expectedContext = MSALNativeAuthRequestContext(correlationId: defaultUUID) + + signInRequestProviderMock.mockInitiateRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) + signInRequestProviderMock.mockChallengeRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) + signInResponseValidatorMock.initiateValidatedResponse = .success(continuationToken: expectedCredentialToken) + signInResponseValidatorMock.challengeValidatedResponse = .introspectRequired + + let result = await sut.signIn(params: MSALNativeAuthSignInParameters(username: expectedUsername, password: nil, context: expectedContext, scopes: nil)) + + XCTAssertFalse(cacheAccessorMock.validateAndSaveTokensWasCalled) + checkTelemetryEventResult(id: .telemetryApiIdSignInWithCodeStart, isSuccessful: false) + if case .error(let error) = result.result { + XCTAssertEqual(error.type, .generalError) + } else { + XCTFail("Expected error result") + } + } + + func test_whenSignInWithPasswordReceiveIntrospectRequired_errorShouldBeReturned() async { + let expectedUsername = "username" + let expectedCredentialToken = "continuationToken" + let expectedContext = MSALNativeAuthRequestContext(correlationId: defaultUUID) + + signInRequestProviderMock.mockInitiateRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) + signInRequestProviderMock.mockChallengeRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) + signInResponseValidatorMock.initiateValidatedResponse = .success(continuationToken: expectedCredentialToken) + signInResponseValidatorMock.challengeValidatedResponse = .introspectRequired + + let result = await sut.signIn(params: MSALNativeAuthSignInParameters(username: expectedUsername, password: "pwd", context: expectedContext, scopes: nil)) + + XCTAssertFalse(cacheAccessorMock.validateAndSaveTokensWasCalled) + checkTelemetryEventResult(id: .telemetryApiIdSignInWithPasswordStart, isSuccessful: false) + if case .error(let error) = result.result { + XCTAssertEqual(error.type, .generalError) + } else { + XCTFail("Expected error result") + } + } + + func test_whenRequestChallengeDefaultStrongAuth_VerificationRequiredIsSentBackToUser() async { + let expectedContinuationToken = "continuationToken" + let expectedSentTo = "sentTo" + let expectedChannelType = MSALNativeAuthChannelType(value: "email") + let expectedCodeLength = 8 + let expectedContext = MSALNativeAuthRequestContext(correlationId: defaultUUID) + + signInRequestProviderMock.mockChallengeRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) + signInResponseValidatorMock.challengeValidatedResponse = .codeRequired( + continuationToken: expectedContinuationToken, + sentTo: expectedSentTo, + channelType: expectedChannelType, + codeLength: expectedCodeLength + ) + let result = await sut.requestChallenge(continuationToken: expectedContinuationToken, authMethod: nil, context: expectedContext, scopes: []) + result.telemetryUpdate?(.success(())) + + XCTAssertFalse(cacheAccessorMock.validateAndSaveTokensWasCalled) + checkTelemetryEventResult(id: .telemetryApiIdMFARequestChallenge, isSuccessful: true) + if case .verificationRequired(let sentTo, let channelTargetType, let codeLength, let newState) = result.result { + XCTAssertEqual(sentTo, expectedSentTo) + XCTAssertEqual(channelTargetType, expectedChannelType) + XCTAssertEqual(codeLength, expectedCodeLength) + XCTAssertEqual(newState.continuationToken, expectedContinuationToken) + } else { + XCTFail("Expected verificationRequired result") + } + } + + func test_whenRequestChallengeDefaultStrongAuth_SelectionRequiredIsSentBackToUser() async { + let expectedContinuationToken = "continuationToken" + let internalAuthMethod = MSALNativeAuthInternalAuthenticationMethod(id: "1", challengeType: .oob, challengeChannel: "email", loginHint: "hint") + let expectedContext = MSALNativeAuthRequestContext(correlationId: defaultUUID) + + signInRequestProviderMock.mockChallengeRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) + signInRequestProviderMock.mockIntrospectRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) + signInResponseValidatorMock.challengeValidatedResponse = .introspectRequired + signInResponseValidatorMock.introspectValidatedResponse = .authMethodsRetrieved(continuationToken: expectedContinuationToken, authMethods: [internalAuthMethod]) + let result = await sut.requestChallenge(continuationToken: expectedContinuationToken, authMethod: nil, context: expectedContext, scopes: []) + result.telemetryUpdate?(.success(())) + + XCTAssertFalse(cacheAccessorMock.validateAndSaveTokensWasCalled) + checkTelemetryEventResult(id: .telemetryApiIdMFARequestChallenge, isSuccessful: true) + if case .selectionRequired(let authMethods, let newState) = result.result { + XCTAssertEqual(authMethods.count, 1) + XCTAssertEqual(authMethods.first?.challengeType, internalAuthMethod.challengeType.rawValue) + XCTAssertEqual(authMethods.first?.id, internalAuthMethod.id) + XCTAssertEqual(authMethods.first?.channelTargetType.value, internalAuthMethod.challengeChannel) + XCTAssertEqual(authMethods.first?.loginHint, internalAuthMethod.loginHint) + XCTAssertEqual(newState.continuationToken, expectedContinuationToken) + } else { + XCTFail("Expected selectionRequired result") + } + } + + func test_whenRequestChallengeRequestFails_ErrorShouldBeReturned() async { + let expectedContext = MSALNativeAuthRequestContext(correlationId: defaultUUID) + + signInRequestProviderMock.expectedContext = expectedContext + signInRequestProviderMock.throwingChallengeError = MSALNativeAuthError(message: nil, correlationId: defaultUUID) + + let result = await sut.requestChallenge(continuationToken: "continuationToken", authMethod: nil, context: expectedContext, scopes: []) + + XCTAssertFalse(cacheAccessorMock.validateAndSaveTokensWasCalled) + checkTelemetryEventResult(id: .telemetryApiIdMFARequestChallenge, isSuccessful: false) + if case .error(let error, let newState) = result.result { + XCTAssertEqual(error.type, .generalError) + XCTAssertNotNil(newState) + } else { + XCTFail("Expected verificationRequired result") + } + } + + func test_whenRequestChallengeIntrospectRequestFails_ErrorShouldBeReturned() async { + let expectedContext = MSALNativeAuthRequestContext(correlationId: defaultUUID) + + signInRequestProviderMock.expectedContext = expectedContext + signInRequestProviderMock.mockChallengeRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) + signInRequestProviderMock.throwingIntrospectError = MSALNativeAuthError(message: nil, correlationId: defaultUUID) + signInResponseValidatorMock.challengeValidatedResponse = .introspectRequired + + let result = await sut.requestChallenge(continuationToken: "continuationToken", authMethod: nil, context: expectedContext, scopes: []) + + XCTAssertFalse(cacheAccessorMock.validateAndSaveTokensWasCalled) + checkTelemetryEventResult(id: .telemetryApiIdMFARequestChallenge, isSuccessful: false) + if case .error(let error, let newState) = result.result { + XCTAssertEqual(error.type, .generalError) + XCTAssertNotNil(newState) + } else { + XCTFail("Expected error result") + } + } + + func test_whenRequestChallengeCustomStrongAuth_VerificationRequiredIsSentBackToUser() async { + let expectedContinuationToken = "continuationToken" + let expectedSentTo = "sentTo" + let expectedChannelType = MSALNativeAuthChannelType(value: "email") + let expectedCodeLength = 8 + let expectedContext = MSALNativeAuthRequestContext(correlationId: defaultUUID) + let expectedAuthMethod = MSALAuthMethod(id: "id", challengeType: "oob", loginHint: "**", channelTargetType: MSALNativeAuthChannelType(value: "email")) + + signInRequestProviderMock.mockChallengeRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) + signInRequestProviderMock.expectedMFAAuthMethodId = expectedAuthMethod.id + signInResponseValidatorMock.challengeValidatedResponse = .codeRequired( + continuationToken: expectedContinuationToken, + sentTo: expectedSentTo, + channelType: expectedChannelType, + codeLength: expectedCodeLength + ) + let result = await sut.requestChallenge(continuationToken: expectedContinuationToken, authMethod: expectedAuthMethod, context: expectedContext, scopes: []) + result.telemetryUpdate?(.success(())) + + XCTAssertFalse(cacheAccessorMock.validateAndSaveTokensWasCalled) + checkTelemetryEventResult(id: .telemetryApiIdMFARequestChallenge, isSuccessful: true) + if case .verificationRequired(let sentTo, let channelTargetType, let codeLength, let newState) = result.result { + XCTAssertEqual(sentTo, expectedSentTo) + XCTAssertEqual(channelTargetType, expectedChannelType) + XCTAssertEqual(codeLength, expectedCodeLength) + XCTAssertEqual(newState.continuationToken, expectedContinuationToken) + } else { + XCTFail("Expected verificationRequired result") + } + } + + func test_whenRequestChallengePasswordRequiredResponse_anErrorShouldBeReturned() async { + let expectedContext = MSALNativeAuthRequestContext(correlationId: defaultUUID) + let expectedContinuationToken = "continuationToken" + + signInRequestProviderMock.expectedContext = expectedContext + signInRequestProviderMock.mockChallengeRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) + signInResponseValidatorMock.challengeValidatedResponse = .passwordRequired(continuationToken: expectedContinuationToken) + + let result = await sut.requestChallenge(continuationToken: expectedContinuationToken, authMethod: nil, context: expectedContext, scopes: []) + + XCTAssertFalse(cacheAccessorMock.validateAndSaveTokensWasCalled) + checkTelemetryEventResult(id: .telemetryApiIdMFARequestChallenge, isSuccessful: false) + if case .error(let error, let newState) = result.result { + XCTAssertEqual(error.type, .generalError) + XCTAssertNil(newState) + } else { + XCTFail("Expected error result") + } + } + + func test_whenGetAuthMethodsIntrospectRequestFail_anErrorShouldBeReturned() async { + let expectedContext = MSALNativeAuthRequestContext(correlationId: defaultUUID) + + signInRequestProviderMock.expectedContext = expectedContext + signInRequestProviderMock.throwingIntrospectError = MSALNativeAuthError(message: nil, correlationId: defaultUUID) + + let result = await sut.getAuthMethods(continuationToken: "CT", context: expectedContext, scopes: []) + + XCTAssertFalse(cacheAccessorMock.validateAndSaveTokensWasCalled) + checkTelemetryEventResult(id: .telemetryApiIdMFAGetAuthMethods, isSuccessful: false) + if case .error(let error, let newState) = result.result { + XCTAssertEqual(error.type, .generalError) + XCTAssertNotNil(newState) + } else { + XCTFail("Expected error result") + } + } + + func test_whenGetAuthMethodsIntrospectReturnsError_anErrorShouldBeReturned() async { + await checkGetAuthMethodsWithIntrospectValidatorError(validatedError: .redirect, expectedType: .browserRequired) + await checkGetAuthMethodsWithIntrospectValidatorError(validatedError: .invalidRequest(.init()), expectedType: .generalError) + await checkGetAuthMethodsWithIntrospectValidatorError(validatedError: .expiredToken(.init()), expectedType: .generalError) + await checkGetAuthMethodsWithIntrospectValidatorError(validatedError: .unexpectedError(.init()), expectedType: .generalError) + } + + func test_whenGetAuthMethods_correctResultShouldBeReturned() async { + let expectedContinuationToken = "continuationToken" + let internalAuthMethod = MSALNativeAuthInternalAuthenticationMethod(id: "1", challengeType: .oob, challengeChannel: "email", loginHint: "hint") + let expectedContext = MSALNativeAuthRequestContext(correlationId: defaultUUID) + + signInRequestProviderMock.mockIntrospectRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) + signInResponseValidatorMock.introspectValidatedResponse = .authMethodsRetrieved(continuationToken: expectedContinuationToken, authMethods: [internalAuthMethod]) + + let result = await sut.getAuthMethods(continuationToken: expectedContinuationToken, context: expectedContext, scopes: []) + result.telemetryUpdate?(.success(())) + + XCTAssertFalse(cacheAccessorMock.validateAndSaveTokensWasCalled) + checkTelemetryEventResult(id: .telemetryApiIdMFAGetAuthMethods, isSuccessful: true) + if case .selectionRequired(let authMethods, let newState) = result.result { + XCTAssertEqual(authMethods.count, 1) + XCTAssertEqual(authMethods.first?.challengeType, internalAuthMethod.challengeType.rawValue) + XCTAssertEqual(authMethods.first?.id, internalAuthMethod.id) + XCTAssertEqual(authMethods.first?.channelTargetType.value, internalAuthMethod.challengeChannel) + XCTAssertEqual(authMethods.first?.loginHint, internalAuthMethod.loginHint) + XCTAssertEqual(newState.continuationToken, expectedContinuationToken) + } else { + XCTFail("Expected selectionRequired result") + } + } + + func test_whenSubmitChallengeTokenRequestFails_correctErrorShouldBeReturned() async { + let expectedContext = MSALNativeAuthRequestContext(correlationId: defaultUUID) + + tokenRequestProviderMock.mockRequestTokenFunc(nil, throwError: MSALNativeAuthError(message: nil, correlationId: defaultUUID)) + + let result = await sut.submitChallenge(challenge: "1234", continuationToken: "CT", context: expectedContext, scopes: []) + + XCTAssertFalse(cacheAccessorMock.validateAndSaveTokensWasCalled) + checkTelemetryEventResult(id: .telemetryApiIdMFASubmitChallenge, isSuccessful: false) + if case .error(let error, let newState) = result.result { + XCTAssertEqual(error.type, .generalError) + XCTAssertNotNil(newState) + } else { + XCTFail("Expected error result") + } + } + + func test_whenSubmitChallengeTokenRequestReturnError_correctErrorShouldBeReturned() async { + await checkSubmitChallengeWithTokenValidatorError(validatedError: .authorizationPending(.init()), expectedErrorType: .generalError) + await checkSubmitChallengeWithTokenValidatorError(validatedError: .generalError(.init()), expectedErrorType: .generalError) + await checkSubmitChallengeWithTokenValidatorError(validatedError: .expiredToken(.init()), expectedErrorType: .generalError) + await checkSubmitChallengeWithTokenValidatorError(validatedError: .expiredRefreshToken(.init()), expectedErrorType: .generalError) + await checkSubmitChallengeWithTokenValidatorError(validatedError: .unauthorizedClient(.init()), expectedErrorType: .generalError) + await checkSubmitChallengeWithTokenValidatorError(validatedError: .invalidRequest(.init()), expectedErrorType: .generalError) + await checkSubmitChallengeWithTokenValidatorError(validatedError: .unexpectedError(.init()), expectedErrorType: .generalError) + await checkSubmitChallengeWithTokenValidatorError(validatedError: .userNotFound(.init()), expectedErrorType: .generalError) + await checkSubmitChallengeWithTokenValidatorError(validatedError: .invalidPassword(.init()), expectedErrorType: .generalError) + await checkSubmitChallengeWithTokenValidatorError(validatedError: .invalidOOBCode(.init()), expectedErrorType: .invalidChallenge) + await checkSubmitChallengeWithTokenValidatorError(validatedError: .unsupportedChallengeType(.init()), expectedErrorType: .generalError) + await checkSubmitChallengeWithTokenValidatorError(validatedError: .invalidScope(.init()), expectedErrorType: .generalError) + await checkSubmitChallengeWithTokenValidatorError(validatedError: .slowDown(.init()), expectedErrorType: .generalError) + } + + func test_whenSubmitChallengeThirdFactorRequired_correctErrorShouldBeReturned() async { + let expectedContinuationToken = "continuationToken" + let expectedChallenge = "1234" + let expectedContext = MSALNativeAuthRequestContext(correlationId: defaultUUID) + let expectedScope = "scope1" + + tokenRequestProviderMock.mockRequestTokenFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) + tokenResponseValidatorMock.tokenValidatedResponse = .strongAuthRequired(continuationToken: expectedContinuationToken) + + let result = await sut.submitChallenge(challenge: expectedChallenge, continuationToken: expectedContinuationToken, context: expectedContext, scopes: [expectedScope]) + + XCTAssertFalse(cacheAccessorMock.validateAndSaveTokensWasCalled) + checkTelemetryEventResult(id: .telemetryApiIdMFASubmitChallenge, isSuccessful: false) + if case .error(let error, let newState) = result.result { + XCTAssertEqual(error.type, .generalError) + } else { + XCTFail("Expected error result") + } + } + + func test_whenSubmitChallenge_signInShouldBeCompletedSuccessfully() async { + let expectedContinuationToken = "continuationToken" + let expectedChallenge = "1234" + let expectedContext = MSALNativeAuthRequestContext(correlationId: defaultUUID) + + tokenRequestProviderMock.mockRequestTokenFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) + + tokenResponseValidatorMock.tokenValidatedResponse = .success(tokenResponse) + tokenResponseValidatorMock.expectedTokenResponse = tokenResponse + cacheAccessorMock.mockUserAccounts = [MSALNativeAuthUserAccountResultStub.account] + cacheAccessorMock.expectedMSIDTokenResult = tokenResult + + let result = await sut.submitChallenge(challenge: expectedChallenge, continuationToken: expectedContinuationToken, context: expectedContext, scopes: []) + result.telemetryUpdate?(.success(())) + + XCTAssertTrue(cacheAccessorMock.clearCacheWasCalled) + XCTAssertTrue(cacheAccessorMock.validateAndSaveTokensWasCalled) + checkTelemetryEventResult(id: .telemetryApiIdMFASubmitChallenge, isSuccessful: true) + guard case let .completed(result) = result.result else { + return XCTFail("Result should be .completed") + } + } + + // MARK: Private methods + + private func checkGetAuthMethodsWithIntrospectValidatorError(validatedError: MSALNativeAuthSignInIntrospectValidatedErrorType, expectedType: MFAGetAuthMethodsError.ErrorType) async { + let expectedContext = MSALNativeAuthRequestContext(correlationId: defaultUUID) + + signInRequestProviderMock.expectedContext = expectedContext + signInRequestProviderMock.mockIntrospectRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) + signInResponseValidatorMock.introspectValidatedResponse = .error(validatedError) + let result = await sut.getAuthMethods(continuationToken: "CT", context: expectedContext, scopes: []) + + XCTAssertFalse(cacheAccessorMock.validateAndSaveTokensWasCalled) + checkTelemetryEventResult(id: .telemetryApiIdMFAGetAuthMethods, isSuccessful: false) + if case .error(let error, let newState) = result.result { + XCTAssertEqual(error.type, expectedType) + XCTAssertNotNil(newState) + } else { + XCTFail("Expected error result") + } + receivedEvents.removeAll() + } + + private func checkSubmitChallengeWithTokenValidatorError(validatedError: MSALNativeAuthTokenValidatedErrorType, expectedErrorType: MFASubmitChallengeError.ErrorType) async { + let expectedContinuationToken = "continuationToken" + let expectedChallenge = "1234" + let expectedContext = MSALNativeAuthRequestContext(correlationId: defaultUUID) + let expectedScope = "scope1" + + tokenRequestProviderMock.mockRequestTokenFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) + tokenRequestProviderMock.expectedContext = expectedContext + tokenRequestProviderMock.expectedTokenParams = MSALNativeAuthTokenRequestParameters(context: expectedContext, username: nil, continuationToken: expectedContinuationToken, grantType: MSALNativeAuthGrantType.oobCode, scope: expectedScope, password: nil, oobCode: expectedChallenge, includeChallengeType: true, refreshToken: nil) + + tokenResponseValidatorMock.tokenValidatedResponse = .error(validatedError) + + let result = await sut.submitChallenge(challenge: expectedChallenge, continuationToken: expectedContinuationToken, context: expectedContext, scopes: [expectedScope]) + + XCTAssertFalse(cacheAccessorMock.validateAndSaveTokensWasCalled) + checkTelemetryEventResult(id: .telemetryApiIdMFASubmitChallenge, isSuccessful: false) + if case .error(let error, let newState) = result.result { + XCTAssertEqual(error.type, expectedErrorType) + XCTAssertNotNil(newState) + } else { + XCTFail("Expected error result") + } + receivedEvents.removeAll() + } +} diff --git a/MSAL/test/unit/native_auth/controllers/MSALNativeAuthResetPasswordControllerTests.swift b/MSAL/test/unit/native_auth/controllers/MSALNativeAuthResetPasswordControllerTests.swift index 9b6995cc3f..2caa3130a0 100644 --- a/MSAL/test/unit/native_auth/controllers/MSALNativeAuthResetPasswordControllerTests.swift +++ b/MSAL/test/unit/native_auth/controllers/MSALNativeAuthResetPasswordControllerTests.swift @@ -201,7 +201,7 @@ final class MSALNativeAuthResetPasswordControllerTests: MSALNativeAuthTestCase { validatorMock.mockValidateResetPasswordStartFunc(.success(continuationToken: "continuationToken")) requestProviderMock.mockChallengeRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) requestProviderMock.expectedChallengeRequestParameters = expectedChallengeParams() - validatorMock.mockValidateResetPasswordChallengeFunc(.success("sentTo", .email, 4, "continuationToken")) + validatorMock.mockValidateResetPasswordChallengeFunc(.success("sentTo", MSALNativeAuthChannelType(value: "email"), 4, "continuationToken")) let exp = expectation(description: "ResetPasswordController expectation") let helper = prepareResetPasswordStartValidatorHelper(exp) @@ -214,7 +214,7 @@ final class MSALNativeAuthResetPasswordControllerTests: MSALNativeAuthTestCase { XCTAssertTrue(helper.onResetPasswordCodeRequiredCalled) XCTAssertEqual(helper.newState?.continuationToken, "continuationToken") XCTAssertEqual(helper.sentTo, "sentTo") - XCTAssertEqual(helper.channelTargetType, .email) + XCTAssertEqual(helper.channelTargetType?.isEmailType, true) XCTAssertEqual(helper.codeLength, 4) XCTAssertNil(helper.error) @@ -331,7 +331,7 @@ final class MSALNativeAuthResetPasswordControllerTests: MSALNativeAuthTestCase { requestProviderMock.mockChallengeRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) requestProviderMock.expectedChallengeRequestParameters = expectedChallengeParams() - validatorMock.mockValidateResetPasswordChallengeFunc(.success("sentTo", .email, 4, "continuationToken response")) + validatorMock.mockValidateResetPasswordChallengeFunc(.success("sentTo", MSALNativeAuthChannelType(value: "email"), 4, "continuationToken response")) let exp = expectation(description: "ResetPasswordController expectation") let helper = prepareResetPasswordResendCodeValidatorHelper(exp) @@ -344,7 +344,7 @@ final class MSALNativeAuthResetPasswordControllerTests: MSALNativeAuthTestCase { XCTAssertTrue(helper.onResetPasswordResendCodeRequiredCalled) XCTAssertEqual(helper.newState?.continuationToken, "continuationToken response") XCTAssertEqual(helper.sentTo, "sentTo") - XCTAssertEqual(helper.channelTargetType, .email) + XCTAssertEqual(helper.channelTargetType?.isEmailType, true) XCTAssertEqual(helper.codeLength, 4) XCTAssertNil(helper.error) @@ -889,6 +889,7 @@ final class MSALNativeAuthResetPasswordControllerTests: MSALNativeAuthTestCase { XCTAssertEqual(signInControllerMock.username, username) XCTAssertEqual(signInControllerMock.continuationToken, continuationToken) + XCTAssertEqual(signInControllerMock.telemetryId, .telemetryApiIdSignInAfterPasswordReset) } // MARK: - Common Methods diff --git a/MSAL/test/unit/native_auth/controllers/MSALNativeAuthSignInControllerTests.swift b/MSAL/test/unit/native_auth/controllers/MSALNativeAuthSignInControllerTests.swift index 67ec6beeb6..200ca1571b 100644 --- a/MSAL/test/unit/native_auth/controllers/MSALNativeAuthSignInControllerTests.swift +++ b/MSAL/test/unit/native_auth/controllers/MSALNativeAuthSignInControllerTests.swift @@ -27,19 +27,19 @@ import XCTest @_implementationOnly import MSAL_Private @_implementationOnly import MSAL_Unit_Test_Private -final class MSALNativeAuthSignInControllerTests: MSALNativeAuthTestCase { - - private var sut: MSALNativeAuthSignInController! - private var signInRequestProviderMock: MSALNativeAuthSignInRequestProviderMock! - private var tokenRequestProviderMock: MSALNativeAuthTokenRequestProviderMock! - private var cacheAccessorMock: MSALNativeAuthCacheAccessorMock! - private var signInResponseValidatorMock: MSALNativeAuthSignInResponseValidatorMock! - private var tokenResponseValidatorMock: MSALNativeAuthTokenResponseValidatorMock! - private var contextMock: MSALNativeAuthRequestContextMock! - private var tokenResult = MSIDTokenResult() - private var tokenResponse = MSIDCIAMTokenResponse() - private var defaultUUID = UUID(uuidString: DEFAULT_TEST_UID)! - private let defaultScopes = "openid profile offline_access" +class MSALNativeAuthSignInControllerTests: MSALNativeAuthTestCase { + + var sut: MSALNativeAuthSignInController! + var signInRequestProviderMock: MSALNativeAuthSignInRequestProviderMock! + var tokenRequestProviderMock: MSALNativeAuthTokenRequestProviderMock! + var cacheAccessorMock: MSALNativeAuthCacheAccessorMock! + var signInResponseValidatorMock: MSALNativeAuthSignInResponseValidatorMock! + var tokenResponseValidatorMock: MSALNativeAuthTokenResponseValidatorMock! + var contextMock: MSALNativeAuthRequestContextMock! + var tokenResult = MSIDTokenResult() + var tokenResponse = MSIDCIAMTokenResponse() + var defaultUUID = UUID(uuidString: DEFAULT_TEST_UID)! + let defaultScopes = "openid profile offline_access" private var signInInitiateApiErrorStub: MSALNativeAuthSignInInitiateResponseError { .init(error: .invalidRequest, errorDescription: nil, errorCodes: nil, errorURI: nil, innerErrors: nil) @@ -123,7 +123,7 @@ final class MSALNativeAuthSignInControllerTests: MSALNativeAuthTestCase { signInRequestProviderMock.mockInitiateRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) signInRequestProviderMock.mockChallengeRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) signInRequestProviderMock.expectedUsername = expectedUsername - signInRequestProviderMock.expectedCredentialToken = continuationToken + signInRequestProviderMock.expectedContinuationToken = continuationToken signInRequestProviderMock.expectedContext = expectedContext tokenRequestProviderMock.mockRequestTokenFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) @@ -152,7 +152,7 @@ final class MSALNativeAuthSignInControllerTests: MSALNativeAuthTestCase { signInRequestProviderMock.mockInitiateRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) signInRequestProviderMock.mockChallengeRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) signInRequestProviderMock.expectedUsername = expectedUsername - signInRequestProviderMock.expectedCredentialToken = continuationToken + signInRequestProviderMock.expectedContinuationToken = continuationToken signInRequestProviderMock.expectedContext = expectedContext tokenRequestProviderMock.expectedTokenParams = MSALNativeAuthTokenRequestParameters(context: expectedContext, username: expectedUsername, continuationToken: continuationToken, grantType: MSALNativeAuthGrantType.password, scope: expectedScopes, password: expectedPassword, oobCode: nil, includeChallengeType: true, refreshToken: nil) @@ -181,7 +181,7 @@ final class MSALNativeAuthSignInControllerTests: MSALNativeAuthTestCase { signInRequestProviderMock.mockInitiateRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) signInRequestProviderMock.mockChallengeRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) signInRequestProviderMock.expectedUsername = expectedUsername - signInRequestProviderMock.expectedCredentialToken = continuationToken + signInRequestProviderMock.expectedContinuationToken = continuationToken signInRequestProviderMock.expectedContext = expectedContext tokenRequestProviderMock.mockRequestTokenFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) @@ -201,7 +201,6 @@ final class MSALNativeAuthSignInControllerTests: MSALNativeAuthTestCase { await fulfillment(of: [expectation], timeout: 1) XCTAssertTrue(cacheAccessorMock.validateAndSaveTokensWasCalled) - checkTelemetryEventResult(id: .telemetryApiIdSignInWithPasswordStart, isSuccessful: true) } func test_successfulResponseAndUnsuccessfulValidation_shouldReturnError() async { @@ -218,7 +217,7 @@ final class MSALNativeAuthSignInControllerTests: MSALNativeAuthTestCase { signInRequestProviderMock.mockInitiateRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) signInRequestProviderMock.mockChallengeRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) signInRequestProviderMock.expectedUsername = expectedUsername - signInRequestProviderMock.expectedCredentialToken = continuationToken + signInRequestProviderMock.expectedContinuationToken = continuationToken signInRequestProviderMock.expectedContext = expectedContext tokenRequestProviderMock.mockRequestTokenFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) @@ -286,44 +285,12 @@ final class MSALNativeAuthSignInControllerTests: MSALNativeAuthTestCase { await checkDelegateErrorWithValidatorError(delegateError: SignInStartError(type: .invalidCredentials, correlationId: defaultUUID), validatorError: .invalidPassword(signInTokenApiErrorStub)) await checkDelegateErrorWithValidatorError(delegateError: SignInStartError(type: .generalError, message: "Error message", correlationId: defaultUUID), validatorError: .unexpectedError(.init(errorDescription: "Error message"))) } - - func test_whenCredentialsAreRequired_browserRequiredErrorIsReturned() async { - let expectedUsername = "username" - let expectedPassword = "password" - let expectedContext = MSALNativeAuthRequestContext(correlationId: defaultUUID) - let continuationToken = "continuationToken" - - signInResponseValidatorMock.initiateValidatedResponse = .success(continuationToken: continuationToken) - signInResponseValidatorMock.challengeValidatedResponse = .passwordRequired(continuationToken: continuationToken) - - signInRequestProviderMock.mockInitiateRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) - signInRequestProviderMock.mockChallengeRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) - signInRequestProviderMock.expectedUsername = expectedUsername - signInRequestProviderMock.expectedCredentialToken = continuationToken - signInRequestProviderMock.expectedContext = expectedContext - - tokenRequestProviderMock.mockRequestTokenFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) - tokenRequestProviderMock.expectedCredentialToken = continuationToken - - let expectation = expectation(description: "SignInController") - - let helper = SignInPasswordStartTestsValidatorHelper(expectation: expectation, expectedError: .init(type: .browserRequired, message: MSALNativeAuthErrorMessage.unsupportedMFA, correlationId: defaultUUID)) - - tokenResponseValidatorMock.tokenValidatedResponse = .error(.strongAuthRequired(.init(error: .unauthorizedClient, subError: nil, errorDescription: MSALNativeAuthErrorMessage.unsupportedMFA, errorCodes: nil, errorURI: nil, innerErrors: nil, continuationToken: nil))) - - let result = await sut.signIn(params: MSALNativeAuthSignInParameters(username: expectedUsername, password: expectedPassword, context: expectedContext, scopes: nil)) - - helper.onSignInPasswordError(result) - - await fulfillment(of: [expectation], timeout: 1) - checkTelemetryEventResult(id: .telemetryApiIdSignInWithPasswordStart, isSuccessful: false) - } func test_whenSignInUsingPassword_apiReturnsChallengeTypeOOB_codeRequiredShouldBeCalled() async { let expectedUsername = "username" let expectedPassword = "password" let expectedSentTo = "sentTo" - let expectedChannelTargetType = MSALNativeAuthChannelType.email + let expectedChannelTargetType = MSALNativeAuthChannelType(value: "email") let expectedCodeLength = 4 let expectedContext = MSALNativeAuthRequestContext(correlationId: defaultUUID) let continuationToken = "continuationToken" @@ -336,7 +303,7 @@ final class MSALNativeAuthSignInControllerTests: MSALNativeAuthTestCase { signInRequestProviderMock.mockInitiateRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) signInRequestProviderMock.mockChallengeRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) signInRequestProviderMock.expectedUsername = expectedUsername - signInRequestProviderMock.expectedCredentialToken = continuationToken + signInRequestProviderMock.expectedContinuationToken = continuationToken signInRequestProviderMock.expectedContext = expectedContext let helper = SignInPasswordStartTestsValidatorHelper(expectation: expectation) @@ -357,7 +324,7 @@ final class MSALNativeAuthSignInControllerTests: MSALNativeAuthTestCase { let expectedUsername = "username" let expectedPassword = "password" let expectedSentTo = "sentTo" - let expectedChannelTargetType = MSALNativeAuthChannelType.email + let expectedChannelTargetType = MSALNativeAuthChannelType(value: "email") let expectedCodeLength = 4 let expectedContext = MSALNativeAuthRequestContext(correlationId: defaultUUID) let continuationToken = "continuationToken" @@ -370,7 +337,7 @@ final class MSALNativeAuthSignInControllerTests: MSALNativeAuthTestCase { signInRequestProviderMock.mockInitiateRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) signInRequestProviderMock.mockChallengeRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) signInRequestProviderMock.expectedUsername = expectedUsername - signInRequestProviderMock.expectedCredentialToken = continuationToken + signInRequestProviderMock.expectedContinuationToken = continuationToken signInRequestProviderMock.expectedContext = expectedContext let helper = SignInPasswordStartTestsValidatorHelper(expectation: expectation) @@ -392,7 +359,7 @@ final class MSALNativeAuthSignInControllerTests: MSALNativeAuthTestCase { func test_whenSignInWithCodeStartWithValidInfo_codeRequiredShouldBeCalled() async { let expectedUsername = "username" let sentTo = "sentTo" - let channelTargetType = MSALNativeAuthChannelType.email + let channelTargetType = MSALNativeAuthChannelType(value: "email") let codeLength = 4 let continuationToken = "continuationToken" let expectedContext = MSALNativeAuthRequestContext(correlationId: defaultUUID) @@ -402,7 +369,7 @@ final class MSALNativeAuthSignInControllerTests: MSALNativeAuthTestCase { signInRequestProviderMock.mockInitiateRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) signInRequestProviderMock.mockChallengeRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) signInRequestProviderMock.expectedUsername = expectedUsername - signInRequestProviderMock.expectedCredentialToken = continuationToken + signInRequestProviderMock.expectedContinuationToken = continuationToken signInRequestProviderMock.expectedContext = expectedContext let helper = SignInCodeStartTestsValidatorHelper(expectation: expectation, expectedSentTo: sentTo, expectedChannelTargetType: channelTargetType, expectedCodeLength: codeLength) @@ -657,7 +624,6 @@ final class MSALNativeAuthSignInControllerTests: MSALNativeAuthTestCase { await checkSubmitPasswordPublicErrorWithTokenValidatorError(publicError: PasswordRequiredError(type: .generalError, message: "User does not exist", correlationId: defaultUUID), validatorError: .userNotFound(signInTokenApiErrorStub)) await checkSubmitPasswordPublicErrorWithTokenValidatorError(publicError: PasswordRequiredError(type: .generalError, correlationId: defaultUUID), validatorError: .invalidOOBCode(signInTokenApiErrorStub)) await checkSubmitPasswordPublicErrorWithTokenValidatorError(publicError: PasswordRequiredError(type: .generalError, correlationId: defaultUUID), validatorError: .unsupportedChallengeType(signInTokenApiErrorStub)) - await checkSubmitPasswordPublicErrorWithTokenValidatorError(publicError: PasswordRequiredError(type: .browserRequired, correlationId: defaultUUID), validatorError: .strongAuthRequired(signInTokenApiErrorStub)) await checkSubmitPasswordPublicErrorWithTokenValidatorError(publicError: PasswordRequiredError(type: .generalError, correlationId: defaultUUID), validatorError: .invalidScope(signInTokenApiErrorStub)) await checkSubmitPasswordPublicErrorWithTokenValidatorError(publicError: PasswordRequiredError(type: .generalError, correlationId: defaultUUID), validatorError: .authorizationPending(signInTokenApiErrorStub)) await checkSubmitPasswordPublicErrorWithTokenValidatorError(publicError: PasswordRequiredError(type: .generalError, correlationId: defaultUUID), validatorError: .slowDown(signInTokenApiErrorStub)) @@ -691,7 +657,6 @@ final class MSALNativeAuthSignInControllerTests: MSALNativeAuthTestCase { checkSubmitCodeDelegateErrorWithTokenValidatorError(delegateError: .generalError, validatorError: .userNotFound(signInTokenApiErrorStub)) checkSubmitCodeDelegateErrorWithTokenValidatorError(delegateError: .invalidCode, validatorError: .invalidOOBCode(signInTokenApiErrorStub)) checkSubmitCodeDelegateErrorWithTokenValidatorError(delegateError: .generalError, validatorError: .unsupportedChallengeType(signInTokenApiErrorStub)) - checkSubmitCodeDelegateErrorWithTokenValidatorError(delegateError: .browserRequired, validatorError: .strongAuthRequired(signInTokenApiErrorStub)) checkSubmitCodeDelegateErrorWithTokenValidatorError(delegateError: .generalError, validatorError: .invalidScope(signInTokenApiErrorStub)) checkSubmitCodeDelegateErrorWithTokenValidatorError(delegateError: .generalError, validatorError: .authorizationPending(signInTokenApiErrorStub)) checkSubmitCodeDelegateErrorWithTokenValidatorError(delegateError: .generalError, validatorError: .slowDown(signInTokenApiErrorStub)) @@ -701,7 +666,7 @@ final class MSALNativeAuthSignInControllerTests: MSALNativeAuthTestCase { func test_signInWithCodeResendCode_shouldSendNewCode() async { let expectedUsername = "username" let sentTo = "sentTo" - let channelTargetType = MSALNativeAuthChannelType.email + let channelTargetType = MSALNativeAuthChannelType(value: "email") let codeLength = 4 let continuationToken = "continuationToken" let expectedContext = MSALNativeAuthRequestContext(correlationId: defaultUUID) @@ -710,7 +675,7 @@ final class MSALNativeAuthSignInControllerTests: MSALNativeAuthTestCase { signInRequestProviderMock.mockChallengeRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) signInRequestProviderMock.expectedUsername = expectedUsername - signInRequestProviderMock.expectedCredentialToken = continuationToken + signInRequestProviderMock.expectedContinuationToken = continuationToken signInRequestProviderMock.expectedContext = expectedContext let helper = SignInResendCodeTestsValidatorHelper(expectation: expectation, expectedSentTo: sentTo, expectedChannelTargetType: channelTargetType, expectedCodeLength: codeLength) @@ -868,7 +833,24 @@ final class MSALNativeAuthSignInControllerTests: MSALNativeAuthTestCase { XCTAssertFalse(cacheAccessorMock.validateAndSaveTokensWasCalled) checkTelemetryEventResult(id: .telemetryApiIdSignInAfterSignUp, isSuccessful: false) } + + func checkTelemetryEventResult(id: MSALNativeAuthTelemetryApiId, isSuccessful: Bool) { + XCTAssertEqual(receivedEvents.count, 1) + + guard let telemetryEventDict = receivedEvents.first else { + return XCTFail("Telemetry test fail") + } + let expectedApiId = String(id.rawValue) + XCTAssertEqual(telemetryEventDict["api_id"] as? String, expectedApiId) + XCTAssertEqual(telemetryEventDict["event_name"] as? String, "api_event" ) + XCTAssertEqual(telemetryEventDict["correlation_id" ] as? String, DEFAULT_TEST_UID.uppercased()) + XCTAssertEqual(telemetryEventDict["is_successfull"] as? String, isSuccessful ? "yes" : "no") + XCTAssertEqual(telemetryEventDict["status"] as? String, isSuccessful ? "succeeded" : "failed") + XCTAssertNotNil(telemetryEventDict["start_time"]) + XCTAssertNotNil(telemetryEventDict["stop_time"]) + XCTAssertNotNil(telemetryEventDict["response_time"]) + } // MARK: private methods @@ -973,7 +955,7 @@ final class MSALNativeAuthSignInControllerTests: MSALNativeAuthTestCase { signInRequestProviderMock.mockInitiateRequestFunc((MSALNativeAuthHTTPRequestMock.prepareMockRequest())) signInRequestProviderMock.mockChallengeRequestFunc((MSALNativeAuthHTTPRequestMock.prepareMockRequest())) signInRequestProviderMock.expectedUsername = expectedUsername - signInRequestProviderMock.expectedCredentialToken = continuationToken + signInRequestProviderMock.expectedContinuationToken = continuationToken signInRequestProviderMock.expectedContext = expectedContext let expectation = expectation(description: "SignInController") @@ -991,25 +973,6 @@ final class MSALNativeAuthSignInControllerTests: MSALNativeAuthTestCase { receivedEvents.removeAll() await fulfillment(of: [expectation], timeout: 1) } - - private func checkTelemetryEventResult(id: MSALNativeAuthTelemetryApiId, isSuccessful: Bool) { - XCTAssertEqual(receivedEvents.count, 1) - - guard let telemetryEventDict = receivedEvents.first else { - return XCTFail("Telemetry test fail") - } - - let expectedApiId = String(id.rawValue) - XCTAssertEqual(telemetryEventDict["api_id"] as? String, expectedApiId) - XCTAssertEqual(telemetryEventDict["event_name"] as? String, "api_event" ) - XCTAssertEqual(telemetryEventDict["correlation_id" ] as? String, DEFAULT_TEST_UID.uppercased()) - XCTAssertEqual(telemetryEventDict["is_successfull"] as? String, isSuccessful ? "yes" : "no") - XCTAssertEqual(telemetryEventDict["status"] as? String, isSuccessful ? "succeeded" : "failed") - XCTAssertNotNil(telemetryEventDict["start_time"]) - XCTAssertNotNil(telemetryEventDict["stop_time"]) - XCTAssertNotNil(telemetryEventDict["response_time"]) - } - private func createSignInTokenApiError(message: String) -> MSALNativeAuthTokenResponseError { .init(error: .expiredToken, subError: nil, errorDescription: message, errorCodes: nil, errorURI: nil, innerErrors: nil, continuationToken: nil) diff --git a/MSAL/test/unit/native_auth/controllers/MSALNativeAuthSignUpControllerTests.swift b/MSAL/test/unit/native_auth/controllers/MSALNativeAuthSignUpControllerTests.swift index 45c113d4ef..6c88561350 100644 --- a/MSAL/test/unit/native_auth/controllers/MSALNativeAuthSignUpControllerTests.swift +++ b/MSAL/test/unit/native_auth/controllers/MSALNativeAuthSignUpControllerTests.swift @@ -336,7 +336,7 @@ final class MSALNativeAuthSignUpControllerTests: MSALNativeAuthTestCase { validatorMock.mockValidateSignUpStartFunc(.success(continuationToken: "continuationToken")) requestProviderMock.mockChallengeRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) requestProviderMock.expectedChallengeRequestParameters = expectedChallengeParams() - validatorMock.mockValidateSignUpChallengeFunc(.codeRequired("sentTo", .email, 4, "continuationToken 2")) + validatorMock.mockValidateSignUpChallengeFunc(.codeRequired("sentTo", MSALNativeAuthChannelType(value: "email"), 4, "continuationToken 2")) let exp = expectation(description: "SignUpController expectation") let helper = prepareSignUpPasswordStartValidatorHelper(exp) @@ -349,7 +349,7 @@ final class MSALNativeAuthSignUpControllerTests: MSALNativeAuthTestCase { XCTAssertTrue(helper.onSignUpCodeRequiredCalled) XCTAssertEqual(helper.newState?.continuationToken, "continuationToken 2") XCTAssertEqual(helper.sentTo, "sentTo") - XCTAssertEqual(helper.channelTargetType, .email) + XCTAssertEqual(helper.channelTargetType?.isEmailType, true) XCTAssertEqual(helper.codeLength, 4) XCTAssertNil(helper.error) @@ -726,7 +726,7 @@ final class MSALNativeAuthSignUpControllerTests: MSALNativeAuthTestCase { validatorMock.mockValidateSignUpStartFunc(.success(continuationToken: "continuationToken 1")) requestProviderMock.mockChallengeRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) requestProviderMock.expectedChallengeRequestParameters = expectedChallengeParams(token: "continuationToken 1") - validatorMock.mockValidateSignUpChallengeFunc(.codeRequired("sentTo", .email, 4, "continuationToken 2")) + validatorMock.mockValidateSignUpChallengeFunc(.codeRequired("sentTo", MSALNativeAuthChannelType(value: "email"), 4, "continuationToken 2")) let exp = expectation(description: "SignUpController expectation") let helper = prepareSignUpCodeStartValidatorHelper(exp) @@ -739,7 +739,7 @@ final class MSALNativeAuthSignUpControllerTests: MSALNativeAuthTestCase { XCTAssertTrue(helper.onSignUpCodeRequiredCalled) XCTAssertEqual(helper.newState?.continuationToken, "continuationToken 2") XCTAssertEqual(helper.sentTo, "sentTo") - XCTAssertEqual(helper.channelTargetType, .email) + XCTAssertEqual(helper.channelTargetType?.isEmailType, true) XCTAssertEqual(helper.codeLength, 4) XCTAssertNil(helper.error) @@ -879,7 +879,7 @@ final class MSALNativeAuthSignUpControllerTests: MSALNativeAuthTestCase { func test_whenSignUpResendCode_succeeds_it_continuesTheFlow() async { requestProviderMock.mockChallengeRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) requestProviderMock.expectedChallengeRequestParameters = expectedChallengeParams() - validatorMock.mockValidateSignUpChallengeFunc(.codeRequired("sentTo", .email, 4, "continuationToken")) + validatorMock.mockValidateSignUpChallengeFunc(.codeRequired("sentTo", MSALNativeAuthChannelType(value: "email"), 4, "continuationToken")) let exp = expectation(description: "SignUpController expectation") let helper = prepareSignUpResendCodeValidatorHelper(exp) @@ -892,7 +892,7 @@ final class MSALNativeAuthSignUpControllerTests: MSALNativeAuthTestCase { XCTAssertTrue(helper.onSignUpResendCodeCodeRequiredCalled) XCTAssertEqual(helper.newState?.continuationToken, "continuationToken") XCTAssertEqual(helper.sentTo, "sentTo") - XCTAssertEqual(helper.channelTargetType, .email) + XCTAssertEqual(helper.channelTargetType?.isEmailType, true) XCTAssertEqual(helper.codeLength, 4) XCTAssertNil(helper.error) @@ -1297,7 +1297,7 @@ final class MSALNativeAuthSignUpControllerTests: MSALNativeAuthTestCase { requestProviderMock.expectedContinueRequestParameters = expectedContinueParams() requestProviderMock.mockChallengeRequestFunc(MSALNativeAuthHTTPRequestMock.prepareMockRequest()) requestProviderMock.expectedChallengeRequestParameters = expectedChallengeParams(token: "continuationToken 2") - validatorMock.mockValidateSignUpChallengeFunc(.codeRequired("", .email, 4, "continuationToken 3")) + validatorMock.mockValidateSignUpChallengeFunc(.codeRequired("", MSALNativeAuthChannelType(value: "email"), 4, "continuationToken 3")) XCTAssertFalse(requestProviderMock.challengeCalled) @@ -1848,6 +1848,7 @@ final class MSALNativeAuthSignUpControllerTests: MSALNativeAuthTestCase { XCTAssertEqual(signInControllerMock.username, username) XCTAssertEqual(signInControllerMock.continuationToken, continuationToken) + XCTAssertEqual(signInControllerMock.telemetryId, .telemetryApiIdSignInAfterSignUp) } // MARK: - Common Methods diff --git a/MSAL/test/unit/native_auth/mock/MSALNativeAuthNetworkMocks.swift b/MSAL/test/unit/native_auth/mock/MSALNativeAuthNetworkMocks.swift index cf18358841..35f434570b 100644 --- a/MSAL/test/unit/native_auth/mock/MSALNativeAuthNetworkMocks.swift +++ b/MSAL/test/unit/native_auth/mock/MSALNativeAuthNetworkMocks.swift @@ -81,6 +81,7 @@ class MSALNativeAuthSignInResponseValidatorMock: MSALNativeAuthSignInResponseVal var expectedConfiguration: MSIDConfiguration? var expectedChallengeResponse: MSALNativeAuthSignInChallengeResponse? var expectedInitiateResponse: MSALNativeAuthSignInInitiateResponse? + var expectedIntrospectResponse: MSALNativeAuthSignInIntrospectResponse? var expectedResponseError: Error? var initiateValidatedResponse: MSALNativeAuthSignInInitiateValidatedResponse = .error(.userNotFound(.init( error: .userNotFound)) @@ -88,9 +89,12 @@ class MSALNativeAuthSignInResponseValidatorMock: MSALNativeAuthSignInResponseVal var challengeValidatedResponse: MSALNativeAuthSignInChallengeValidatedResponse = .error(.expiredToken(.init( error: .expiredToken) )) + var introspectValidatedResponse: MSALNativeAuthSignInIntrospectValidatedResponse = .error(.expiredToken(.init( + error: .expiredToken) + )) - func validate(context: MSIDRequestContext, result: Result) -> MSAL.MSALNativeAuthSignInChallengeValidatedResponse { + func validateChallenge(context: MSIDRequestContext, result: Result) -> MSAL.MSALNativeAuthSignInChallengeValidatedResponse { checkConfAndContext(context) if case .success(let successChallengeResponse) = result, let expectedChallengeResponse = expectedChallengeResponse { XCTAssertEqual(successChallengeResponse.challengeType, expectedChallengeResponse.challengeType) @@ -106,7 +110,7 @@ class MSALNativeAuthSignInResponseValidatorMock: MSALNativeAuthSignInResponseVal return challengeValidatedResponse } - func validate(context: MSIDRequestContext, result: Result) -> MSAL.MSALNativeAuthSignInInitiateValidatedResponse { + func validateInitiate(context: MSIDRequestContext, result: Result) -> MSAL.MSALNativeAuthSignInInitiateValidatedResponse { checkConfAndContext(context) if case .success(let successInitiateResponse) = result, let expectedInitiateResponse = expectedInitiateResponse { XCTAssertEqual(successInitiateResponse.challengeType, expectedInitiateResponse.challengeType) @@ -119,12 +123,25 @@ class MSALNativeAuthSignInResponseValidatorMock: MSALNativeAuthSignInResponseVal return initiateValidatedResponse } + func validateIntrospect(context: any MSIDRequestContext, result: Result) -> MSAL.MSALNativeAuthSignInIntrospectValidatedResponse { + checkConfAndContext(context) + if case .success(let successIntrospectResponse) = result, let expectedIntrospectResponse = expectedIntrospectResponse { + XCTAssertEqual(successIntrospectResponse.challengeType, expectedIntrospectResponse.challengeType) + XCTAssertEqual(successIntrospectResponse.continuationToken, expectedIntrospectResponse.continuationToken) + } + if case .failure(let introspectResponseError) = result, let expectedIntrospectResponseError = expectedResponseError { + XCTAssertTrue(type(of: introspectResponseError) == type(of: expectedIntrospectResponseError)) + XCTAssertEqual(introspectResponseError.localizedDescription, expectedIntrospectResponseError.localizedDescription) + } + return introspectValidatedResponse + } + private func checkConfAndContext(_ context: MSIDRequestContext, config: MSIDConfiguration? = nil) { - if let expectedRequestContext = expectedRequestContext { + if let expectedRequestContext { XCTAssertEqual(expectedRequestContext.correlationId(), context.correlationId()) XCTAssertEqual(expectedRequestContext.telemetryRequestId(), context.telemetryRequestId()) } - if let expectedConfiguration = expectedConfiguration { + if let expectedConfiguration { XCTAssertEqual(expectedConfiguration, config) } } @@ -158,11 +175,11 @@ class MSALNativeAuthTokenResponseValidatorMock: MSALNativeAuthTokenResponseValid } private func checkConfAndContext(_ context: MSIDRequestContext, config: MSIDConfiguration? = nil) { - if let expectedRequestContext = expectedRequestContext { + if let expectedRequestContext { XCTAssertEqual(expectedRequestContext.correlationId(), context.correlationId()) XCTAssertEqual(expectedRequestContext.telemetryRequestId(), context.telemetryRequestId()) } - if let expectedConfiguration = expectedConfiguration { + if let expectedConfiguration { XCTAssertEqual(expectedConfiguration, config) } } @@ -172,12 +189,15 @@ class MSALNativeAuthSignInRequestProviderMock: MSALNativeAuthSignInRequestProvid var throwingInitError: Error? var throwingChallengeError: Error? + var throwingIntrospectError: Error? private var requestInitiate: MSIDHttpRequest? private var requestChallenge: MSIDHttpRequest? + private var requestIntrospect: MSIDHttpRequest? var expectedContext: MSIDRequestContext? var expectedUsername: String? - var expectedCredentialToken: String? + var expectedMFAAuthMethodId: String? + var expectedContinuationToken: String? func mockInitiateRequestFunc(_ request: MSIDHttpRequest?, throwError: Error? = nil) { self.requestInitiate = request @@ -186,11 +206,11 @@ class MSALNativeAuthSignInRequestProviderMock: MSALNativeAuthSignInRequestProvid func inititate(parameters: MSAL.MSALNativeAuthSignInInitiateRequestParameters, context: MSIDRequestContext) throws -> MSIDHttpRequest { checkContext(context) - if let expectedUsername = expectedUsername { + if let expectedUsername { XCTAssertEqual(expectedUsername, parameters.username) } - if let request = requestInitiate { - return request + if let requestInitiate { + return requestInitiate } else if throwingInitError != nil { throw throwingInitError! } else { @@ -205,20 +225,40 @@ class MSALNativeAuthSignInRequestProviderMock: MSALNativeAuthSignInRequestProvid func challenge(parameters: MSAL.MSALNativeAuthSignInChallengeRequestParameters, context: MSIDRequestContext) throws -> MSIDHttpRequest { checkContext(context) - if let expectedCredentialToken = expectedCredentialToken { - XCTAssertEqual(expectedCredentialToken, parameters.continuationToken) + XCTAssertEqual(parameters.mfaAuthMethodId, expectedMFAAuthMethodId) + if let expectedContinuationToken { + XCTAssertEqual(expectedContinuationToken, parameters.continuationToken) } - if let request = requestChallenge { - return request + if let requestChallenge { + return requestChallenge } else if throwingChallengeError != nil { throw throwingChallengeError! } else { fatalError("Make sure to use mockChallengeRequestFunc()") } } + + func mockIntrospectRequestFunc(_ request: MSIDHttpRequest?, throwError: Error? = nil) { + self.requestIntrospect = request + self.throwingIntrospectError = throwError + } + + func introspect(parameters: MSAL.MSALNativeAuthSignInIntrospectRequestParameters, context: any MSIDRequestContext) throws -> MSIDHttpRequest { + checkContext(context) + if let expectedContinuationToken { + XCTAssertEqual(expectedContinuationToken, parameters.continuationToken) + } + if let requestIntrospect { + return requestIntrospect + } else if throwingIntrospectError != nil { + throw throwingIntrospectError! + } else { + fatalError("Make sure to use mockIntrospectRequestFunc()") + } + } fileprivate func checkContext(_ context: MSIDRequestContext) { - if let expectedContext = expectedContext { + if let expectedContext { XCTAssertEqual(expectedContext.correlationId(), context.correlationId()) } } @@ -242,7 +282,7 @@ class MSALNativeAuthTokenRequestProviderMock: MSALNativeAuthTokenRequestProvidin func signInWithPassword(parameters: MSAL.MSALNativeAuthTokenRequestParameters, context: MSIDRequestContext) throws -> MSIDHttpRequest { checkContext(context) - if let expectedTokenParams = expectedTokenParams { + if let expectedTokenParams { XCTAssertEqual(expectedTokenParams.username, parameters.username) XCTAssertEqual(expectedTokenParams.continuationToken, parameters.continuationToken) XCTAssertEqual(expectedTokenParams.continuationToken, parameters.continuationToken) @@ -252,8 +292,8 @@ class MSALNativeAuthTokenRequestProviderMock: MSALNativeAuthTokenRequestProvidin XCTAssertEqual(expectedTokenParams.oobCode, parameters.oobCode) XCTAssertEqual(expectedTokenParams.context.correlationId(), parameters.context.correlationId()) } - if let request = requestToken { - return request + if let requestToken { + return requestToken } else if throwingTokenError != nil { throw throwingTokenError! } else { @@ -268,7 +308,7 @@ class MSALNativeAuthTokenRequestProviderMock: MSALNativeAuthTokenRequestProvidin func refreshToken(parameters: MSAL.MSALNativeAuthTokenRequestParameters, context: MSIDRequestContext) throws -> MSIDHttpRequest { checkContext(context) - if let expectedTokenParams = expectedTokenParams { + if let expectedTokenParams { XCTAssertEqual(expectedTokenParams.username, parameters.username) XCTAssertEqual(expectedTokenParams.continuationToken, parameters.continuationToken) XCTAssertEqual(expectedTokenParams.continuationToken, parameters.continuationToken) @@ -278,8 +318,8 @@ class MSALNativeAuthTokenRequestProviderMock: MSALNativeAuthTokenRequestProvidin XCTAssertEqual(expectedTokenParams.oobCode, parameters.oobCode) XCTAssertEqual(expectedTokenParams.context.correlationId(), parameters.context.correlationId()) } - if let request = requestRefreshToken { - return request + if let requestRefreshToken { + return requestRefreshToken } else if throwingRefreshTokenError != nil { throw throwingRefreshTokenError! } else { @@ -288,7 +328,7 @@ class MSALNativeAuthTokenRequestProviderMock: MSALNativeAuthTokenRequestProvidin } fileprivate func checkContext(_ context: MSIDRequestContext) { - if let expectedContext = expectedContext { + if let expectedContext { XCTAssertEqual(expectedContext.correlationId(), context.correlationId()) } } diff --git a/MSAL/test/unit/native_auth/mock/MSALNativeAuthSignInControllerMock.swift b/MSAL/test/unit/native_auth/mock/MSALNativeAuthSignInControllerMock.swift index a70be9799a..78a895bb3d 100644 --- a/MSAL/test/unit/native_auth/mock/MSALNativeAuthSignInControllerMock.swift +++ b/MSAL/test/unit/native_auth/mock/MSALNativeAuthSignInControllerMock.swift @@ -25,10 +25,11 @@ @testable import MSAL import XCTest -class MSALNativeAuthSignInControllerMock: MSALNativeAuthSignInControlling { +class MSALNativeAuthSignInControllerMock: MSALNativeAuthSignInControlling, MSALNativeAuthMFAControlling { private(set) var username: String? private(set) var continuationToken: String? + private(set) var telemetryId: MSALNativeAuthTelemetryApiId? var expectation: XCTestExpectation? var signInStartResult: MSALNativeAuthSignInControlling.SignInControllerResponse! @@ -37,13 +38,18 @@ class MSALNativeAuthSignInControllerMock: MSALNativeAuthSignInControlling { var submitPasswordResult: SignInSubmitPasswordControllerResponse! var resendCodeResult: SignInResendCodeControllerResponse! + var requestChallengeResponse: MFARequestChallengeControllerResponse! + var getAuthMethodsResponse: MFAGetAuthMethodsControllerResponse! + var submitChallengeResponse: MFASubmitChallengeControllerResponse! + func signIn(params: MSAL.MSALNativeAuthSignInParameters) async -> MSALNativeAuthSignInControlling.SignInControllerResponse { return signInStartResult } - func signIn(username: String, continuationToken: String?, scopes: [String]?, context: MSAL.MSALNativeAuthRequestContext) async -> SignInAfterPreviousFlowControllerResponse { + func signIn(username: String, continuationToken: String?, scopes: [String]?, telemetryId: MSAL.MSALNativeAuthTelemetryApiId, context: MSAL.MSALNativeAuthRequestContext) async -> SignInAfterPreviousFlowControllerResponse { self.username = username self.continuationToken = continuationToken + self.telemetryId = telemetryId expectation?.fulfill() return continuationTokenResult @@ -60,4 +66,16 @@ class MSALNativeAuthSignInControllerMock: MSALNativeAuthSignInControlling { func resendCode(continuationToken: String, context: MSAL.MSALNativeAuthRequestContext, scopes: [String]) async -> SignInResendCodeControllerResponse { return resendCodeResult } + + func requestChallenge(continuationToken: String, authMethod: MSAL.MSALAuthMethod?, context: MSAL.MSALNativeAuthRequestContext, scopes: [String]) async -> MFARequestChallengeControllerResponse { + return requestChallengeResponse + } + + func getAuthMethods(continuationToken: String, context: MSAL.MSALNativeAuthRequestContext, scopes: [String]) async -> MFAGetAuthMethodsControllerResponse { + return getAuthMethodsResponse + } + + func submitChallenge(challenge: String, continuationToken: String, context: MSAL.MSALNativeAuthRequestContext, scopes: [String]) async -> MFASubmitChallengeControllerResponse { + return submitChallengeResponse + } } diff --git a/MSAL/test/unit/native_auth/mock/CredentialsDelegateSpies.swift b/MSAL/test/unit/native_auth/mock/delegate/CredentialsDelegateSpies.swift similarity index 100% rename from MSAL/test/unit/native_auth/mock/CredentialsDelegateSpies.swift rename to MSAL/test/unit/native_auth/mock/delegate/CredentialsDelegateSpies.swift diff --git a/MSAL/test/unit/native_auth/mock/delegate/MFADelegatesSpies.swift b/MSAL/test/unit/native_auth/mock/delegate/MFADelegatesSpies.swift new file mode 100644 index 0000000000..225234d889 --- /dev/null +++ b/MSAL/test/unit/native_auth/mock/delegate/MFADelegatesSpies.swift @@ -0,0 +1,201 @@ +// +// 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. + +@testable import MSAL +import XCTest + +open class MFARequestChallengeDelegateSpy: MFARequestChallengeDelegate { + + let expectation: XCTestExpectation + var expectedError: MFARequestChallengeError? + private(set) var newSentTo: String? + private(set) var newChannelTargetType: MSALNativeAuthChannelType? + private(set) var newCodeLength: Int? + private(set) var newMFARequiredState: MFARequiredState? + private(set) var newAuthMethods: [MSALAuthMethod]? + + init(expectation: XCTestExpectation, expectedError: MFARequestChallengeError? = nil) { + self.expectation = expectation + self.expectedError = expectedError + } + + public func onMFARequestChallengeError(error: MSAL.MFARequestChallengeError, newState: MSAL.MFARequiredState?) { + if let expectedError = expectedError { + XCTAssertTrue(Thread.isMainThread) + checkErrors(error: error, expectedError: expectedError) + expectation.fulfill() + return + } + XCTFail("This method should not be called") + expectation.fulfill() + } + + public func onMFARequestChallengeSelectionRequired(authMethods: [MSALAuthMethod], newState: MFARequiredState) { + XCTAssertTrue(Thread.isMainThread) + newMFARequiredState = newState + newAuthMethods = authMethods + + expectation.fulfill() + } + + public func onMFARequestChallengeVerificationRequired(newState: MFARequiredState, sentTo: String, channelTargetType: MSALNativeAuthChannelType, codeLength: Int) { + XCTAssertTrue(Thread.isMainThread) + newMFARequiredState = newState + newSentTo = sentTo + newChannelTargetType = channelTargetType + newCodeLength = codeLength + + expectation.fulfill() + } +} + +open class MFARequestChallengeNotImplementedDelegateSpy: MFARequestChallengeDelegate { + + let expectation: XCTestExpectation + let expectedError: MFARequestChallengeError + + init(expectation: XCTestExpectation, expectedError: MFARequestChallengeError) { + self.expectation = expectation + self.expectedError = expectedError + } + + public func onMFARequestChallengeError(error: MSAL.MFARequestChallengeError, newState: MSAL.MFARequiredState?) { + XCTAssertTrue(Thread.isMainThread) + XCTAssertNil(newState) + checkErrors(error: error, expectedError: expectedError) + expectation.fulfill() + } +} + +open class MFAGetAuthMethodsDelegateSpy: MFAGetAuthMethodsDelegate { + + let expectation: XCTestExpectation + var expectedError: MFAGetAuthMethodsError? + private(set) var newMFARequiredState: MFARequiredState? + private(set) var newAuthMethods: [MSALAuthMethod]? + + init(expectation: XCTestExpectation, expectedError: MFAGetAuthMethodsError? = nil) { + self.expectation = expectation + self.expectedError = expectedError + } + + public func onMFAGetAuthMethodsError(error: MSAL.MFAGetAuthMethodsError, newState: MSAL.MFARequiredState?) { + if let expectedError = expectedError { + XCTAssertTrue(Thread.isMainThread) + checkErrors(error: error, expectedError: expectedError) + self.newMFARequiredState = newState + expectation.fulfill() + return + } + XCTFail("This method should not be called") + expectation.fulfill() + } + + public func onMFAGetAuthMethodsSelectionRequired(authMethods: [MSALAuthMethod], newState: MFARequiredState) { + XCTAssertTrue(Thread.isMainThread) + newMFARequiredState = newState + newAuthMethods = authMethods + + expectation.fulfill() + } +} + +open class MFAGetAuthMethodsNotImplementedDelegateSpy: MFAGetAuthMethodsDelegate { + + let expectation: XCTestExpectation + let expectedError: MFAGetAuthMethodsError + + init(expectation: XCTestExpectation, expectedError: MFAGetAuthMethodsError) { + self.expectation = expectation + self.expectedError = expectedError + } + + public func onMFAGetAuthMethodsError(error: MSAL.MFAGetAuthMethodsError, newState: MSAL.MFARequiredState?) { + XCTAssertTrue(Thread.isMainThread) + XCTAssertNil(newState) + checkErrors(error: error, expectedError: expectedError) + expectation.fulfill() + } +} + +open class MFASubmitChallengeDelegateSpy: MFASubmitChallengeDelegate { + + let expectation: XCTestExpectation + var expectedError: MFASubmitChallengeError? + var expectedResult: MSALNativeAuthUserAccountResult? + private(set) var newMFARequiredState: MFARequiredState? + + init(expectation: XCTestExpectation, expectedResult: MSALNativeAuthUserAccountResult?, expectedError: MFASubmitChallengeError?) { + self.expectation = expectation + self.expectedError = expectedError + self.expectedResult = expectedResult + } + + public func onMFASubmitChallengeError(error: MSAL.MFASubmitChallengeError, newState: MSAL.MFARequiredState?) { + if let expectedError = expectedError { + XCTAssertTrue(Thread.isMainThread) + self.newMFARequiredState = newState + checkErrors(error: error, expectedError: expectedError) + expectation.fulfill() + return + } + XCTFail("This method should not be called") + expectation.fulfill() + } + + public func onSignInCompleted(result: MSALNativeAuthUserAccountResult) { + if let expectedResult = expectedResult { + XCTAssertTrue(Thread.isMainThread) + XCTAssertEqual(expectedResult.idToken, result.idToken) + } else { + XCTFail("This method should not be called") + } + expectation.fulfill() + } +} + +open class MFASubmitChallengeNotImplementedDelegateSpy: MFASubmitChallengeDelegate { + + let expectation: XCTestExpectation + let expectedError: MFASubmitChallengeError + + init(expectation: XCTestExpectation, expectedError: MFASubmitChallengeError) { + self.expectation = expectation + self.expectedError = expectedError + } + + public func onMFASubmitChallengeError(error: MSAL.MFASubmitChallengeError, newState: MSAL.MFARequiredState?) { + XCTAssertTrue(Thread.isMainThread) + checkErrors(error: error, expectedError: expectedError) + XCTAssertNil(newState) + expectation.fulfill() + } +} + +fileprivate func checkErrors(error: MSALNativeAuthError, expectedError: MSALNativeAuthError?) { + XCTAssertEqual(error.errorDescription, expectedError?.errorDescription) + XCTAssertEqual(error.errorCodes, expectedError?.errorCodes) + XCTAssertEqual(error.errorUri, expectedError?.errorUri) + XCTAssertEqual(error.correlationId, expectedError?.correlationId) +} diff --git a/MSAL/test/unit/native_auth/mock/ResetPasswordDelegateSpies.swift b/MSAL/test/unit/native_auth/mock/delegate/ResetPasswordDelegateSpies.swift similarity index 100% rename from MSAL/test/unit/native_auth/mock/ResetPasswordDelegateSpies.swift rename to MSAL/test/unit/native_auth/mock/delegate/ResetPasswordDelegateSpies.swift diff --git a/MSAL/test/unit/native_auth/mock/SignInDelegatesSpies.swift b/MSAL/test/unit/native_auth/mock/delegate/SignInDelegatesSpies.swift similarity index 99% rename from MSAL/test/unit/native_auth/mock/SignInDelegatesSpies.swift rename to MSAL/test/unit/native_auth/mock/delegate/SignInDelegatesSpies.swift index 72e0c9bf2c..93e61b01fe 100644 --- a/MSAL/test/unit/native_auth/mock/SignInDelegatesSpies.swift +++ b/MSAL/test/unit/native_auth/mock/delegate/SignInDelegatesSpies.swift @@ -176,7 +176,7 @@ open class SignInCodeStartDelegateSpy: SignInStartDelegate { public func onSignInCodeRequired(newState: SignInCodeRequiredState, sentTo: String, channelTargetType: MSALNativeAuthChannelType, codeLength: Int) { newSignInCodeRequiredState = newState XCTAssertEqual(sentTo, expectedSentTo) - XCTAssertEqual(channelTargetType, expectedChannelTargetType) + XCTAssertEqual(channelTargetType.value, expectedChannelTargetType?.value) XCTAssertEqual(codeLength, expectedCodeLength) XCTAssertTrue(Thread.isMainThread) if let verifyCodeDelegate = verifyCodeDelegate { @@ -211,7 +211,7 @@ class SignInResendCodeDelegateSpy: SignInResendCodeDelegate { func onSignInResendCodeCodeRequired(newState: SignInCodeRequiredState, sentTo: String, channelTargetType: MSALNativeAuthChannelType, codeLength: Int) { XCTAssertEqual(sentTo, expectedSentTo) - XCTAssertEqual(channelTargetType, expectedChannelTargetType) + XCTAssertEqual(channelTargetType.value, expectedChannelTargetType?.value) XCTAssertEqual(codeLength, expectedCodeLength) XCTAssertTrue(Thread.isMainThread) newSignInCodeRequiredState = newState diff --git a/MSAL/test/unit/native_auth/mock/SignUpDelegateSpies.swift b/MSAL/test/unit/native_auth/mock/delegate/SignUpDelegateSpies.swift similarity index 100% rename from MSAL/test/unit/native_auth/mock/SignUpDelegateSpies.swift rename to MSAL/test/unit/native_auth/mock/delegate/SignUpDelegateSpies.swift diff --git a/MSAL/test/unit/native_auth/network/MSALNativeAuthEndpointTests.swift b/MSAL/test/unit/native_auth/network/MSALNativeAuthEndpointTests.swift index 9c79d0094f..199bc8312e 100644 --- a/MSAL/test/unit/native_auth/network/MSALNativeAuthEndpointTests.swift +++ b/MSAL/test/unit/native_auth/network/MSALNativeAuthEndpointTests.swift @@ -30,7 +30,7 @@ final class MSALNativeAuthEndpointTests: XCTestCase { private typealias sut = MSALNativeAuthEndpoint func test_allEndpoints_are_tested() { - XCTAssertEqual(sut.allCases.count, 12) + XCTAssertEqual(sut.allCases.count, 13) } func test_signUp_start() { @@ -52,6 +52,10 @@ final class MSALNativeAuthEndpointTests: XCTestCase { func test_signInChallenge_endpoint() { XCTAssertEqual(sut.signInChallenge.rawValue, "/oauth2/v2.0/challenge") } + + func test_signInIntrospect_endpoint() { + XCTAssertEqual(sut.signInIntrospect.rawValue, "/oauth2/v2.0/introspect") + } func test_token_endpoint() { XCTAssertEqual(sut.token.rawValue, "/oauth2/v2.0/token") diff --git a/MSAL/test/unit/native_auth/network/MSALNativeAuthRequestConfiguratorTests.swift b/MSAL/test/unit/native_auth/network/MSALNativeAuthRequestConfiguratorTests.swift index d42c4b1516..1bc91c1db5 100644 --- a/MSAL/test/unit/native_auth/network/MSALNativeAuthRequestConfiguratorTests.swift +++ b/MSAL/test/unit/native_auth/network/MSALNativeAuthRequestConfiguratorTests.swift @@ -70,7 +70,8 @@ final class MSALNativeAuthRequestConfiguratorTests: XCTestCase { ) let request = MSIDHttpRequest() - let params = MSALNativeAuthSignInChallengeRequestParameters(context: context, + let params = MSALNativeAuthSignInChallengeRequestParameters(context: context, + mfaAuthMethodId: "1", continuationToken: "Test Credential Token") let sut = MSALNativeAuthRequestConfigurator(config: config) try sut.configure(configuratorType: .signIn(.challenge(params)), @@ -80,6 +81,7 @@ final class MSALNativeAuthRequestConfiguratorTests: XCTestCase { let expectedBodyParams = [ "client_id": DEFAULT_TEST_CLIENT_ID, "continuation_token": "Test Credential Token", + "id": "1", "challenge_type": "otp" ] @@ -88,6 +90,32 @@ final class MSALNativeAuthRequestConfiguratorTests: XCTestCase { checkHeaders(request: request) checkTelemetry(request.serverTelemetry, telemetry) } + + func test_signInIntrospect_getsConfiguredSuccessfully() throws { + XCTAssertNoThrow(config = try .init(clientId: DEFAULT_TEST_CLIENT_ID, authority: MSALCIAMAuthority(url: baseUrl), challengeTypes: [.otp], redirectUri: nil)) + let telemetry = MSALNativeAuthServerTelemetry( + currentRequestTelemetry: telemetryProvider.telemetryForSignIn(type: .signInIntrospect), + context: context + ) + + let request = MSIDHttpRequest() + let params = MSALNativeAuthSignInIntrospectRequestParameters(context: context, + continuationToken: "Test Credential Token") + let sut = MSALNativeAuthRequestConfigurator(config: config) + try sut.configure(configuratorType: .signIn(.introspect(params)), + request: request, + telemetryProvider: telemetryProvider) + + let expectedBodyParams = [ + "client_id": DEFAULT_TEST_CLIENT_ID, + "continuation_token": "Test Credential Token" + ] + + XCTAssertEqual(request.parameters, expectedBodyParams) + checkUrlRequest(request.urlRequest, endpoint: .signInIntrospect) + checkHeaders(request: request) + checkTelemetry(request.serverTelemetry, telemetry) + } func test_signInToken_getsConfiguredSuccessfully() throws { XCTAssertNoThrow(config = try .init(clientId: DEFAULT_TEST_CLIENT_ID, authority: MSALCIAMAuthority(url: baseUrl), challengeTypes: [.password], redirectUri: nil)) diff --git a/MSAL/test/unit/native_auth/network/errors/MSALNativeAuthSubErrorCodeTests.swift b/MSAL/test/unit/native_auth/network/errors/MSALNativeAuthSubErrorCodeTests.swift index 58b09289ed..40e2b3b75e 100644 --- a/MSAL/test/unit/native_auth/network/errors/MSALNativeAuthSubErrorCodeTests.swift +++ b/MSAL/test/unit/native_auth/network/errors/MSALNativeAuthSubErrorCodeTests.swift @@ -29,7 +29,7 @@ final class MSALNativeAuthSubErrorCodeTests: XCTestCase { private typealias sut = MSALNativeAuthSubErrorCode func test_allCases() { - XCTAssertEqual(sut.allCases.count, 9) + XCTAssertEqual(sut.allCases.count, 11) } func test_passwordTooWeak() { @@ -63,4 +63,12 @@ final class MSALNativeAuthSubErrorCodeTests: XCTestCase { func test_invalidOOBValue() { XCTAssertEqual(sut.invalidOOBValue.rawValue, "invalid_oob_value") } + + func test_introspectRequiredValue() { + XCTAssertEqual(sut.introspectRequired.rawValue, "introspect_required") + } + + func test_mfaRequiredValue() { + XCTAssertEqual(sut.mfaRequired.rawValue, "mfa_required") + } } diff --git a/MSAL/test/unit/native_auth/network/parameters/sign_in/MSALNativeAuthSignInChallengeRequestParametersTest.swift b/MSAL/test/unit/native_auth/network/parameters/sign_in/MSALNativeAuthSignInChallengeRequestParametersTest.swift index c7e4ad1fd6..bdfe19887c 100644 --- a/MSAL/test/unit/native_auth/network/parameters/sign_in/MSALNativeAuthSignInChallengeRequestParametersTest.swift +++ b/MSAL/test/unit/native_auth/network/parameters/sign_in/MSALNativeAuthSignInChallengeRequestParametersTest.swift @@ -39,6 +39,7 @@ final class MSALNativeAuthSignInChallengeRequestParametersTest: XCTestCase { func testMakeEndpointUrl_whenRightUrlStringIsUsed_noExceptionThrown() { XCTAssertNoThrow(config = try .init(clientId: DEFAULT_TEST_CLIENT_ID, authority: MSALCIAMAuthority(url: baseUrl), challengeTypes: [.password], redirectUri: nil)) let parameters = MSALNativeAuthSignInChallengeRequestParameters(context: MSALNativeAuthRequestContextMock(), + mfaAuthMethodId: nil, continuationToken: "Test Credential Token") var resultUrl: URL? = nil XCTAssertNoThrow(resultUrl = try parameters.makeEndpointUrl(config: config)) @@ -48,7 +49,8 @@ final class MSALNativeAuthSignInChallengeRequestParametersTest: XCTestCase { func test_otpParameters_shouldCreateCorrectBodyRequest() throws { XCTAssertNoThrow(config = try .init(clientId: DEFAULT_TEST_CLIENT_ID, authority: MSALCIAMAuthority(url: baseUrl), challengeTypes: [.otp], redirectUri: nil)) let params = MSALNativeAuthSignInChallengeRequestParameters( - context: context, + context: context, + mfaAuthMethodId: nil, continuationToken: "Test Credential Token" ) @@ -67,6 +69,7 @@ final class MSALNativeAuthSignInChallengeRequestParametersTest: XCTestCase { XCTAssertNoThrow(config = try .init(clientId: DEFAULT_TEST_CLIENT_ID, authority: MSALCIAMAuthority(url: baseUrl), challengeTypes: [.password, .redirect], redirectUri: nil)) let params = MSALNativeAuthSignInChallengeRequestParameters( context: context, + mfaAuthMethodId: nil, continuationToken: "Test Credential Token" ) diff --git a/MSAL/test/unit/native_auth/network/responses/validator/MSALNativeAuthResetPasswordResponseValidatorTests.swift b/MSAL/test/unit/native_auth/network/responses/validator/MSALNativeAuthResetPasswordResponseValidatorTests.swift index 8a3ff3b238..abdbff16f9 100644 --- a/MSAL/test/unit/native_auth/network/responses/validator/MSALNativeAuthResetPasswordResponseValidatorTests.swift +++ b/MSAL/test/unit/native_auth/network/responses/validator/MSALNativeAuthResetPasswordResponseValidatorTests.swift @@ -151,7 +151,7 @@ final class MSALNativeAuthResetPasswordResponseValidatorTests: XCTestCase { challengeType: .redirect, bindingMethod: nil, challengeTargetLabel: "challenge-type-label", - challengeChannel: .email, + challengeChannel: "email", continuationToken: "token", codeLength: nil) ) @@ -165,7 +165,7 @@ final class MSALNativeAuthResetPasswordResponseValidatorTests: XCTestCase { challengeType: .oob, bindingMethod: nil, challengeTargetLabel: "challenge-type-label", - challengeChannel: .email, + challengeChannel: "email", continuationToken: "token", codeLength: 6) ) @@ -177,7 +177,7 @@ final class MSALNativeAuthResetPasswordResponseValidatorTests: XCTestCase { } XCTAssertEqual(sentTo, "challenge-type-label") - XCTAssertEqual(channelTargetType, .email) + XCTAssertTrue(channelTargetType.isEmailType) XCTAssertEqual(codeLength, 6) XCTAssertEqual(continuationToken, "token") } @@ -187,7 +187,7 @@ final class MSALNativeAuthResetPasswordResponseValidatorTests: XCTestCase { challengeType: .oob, bindingMethod: nil, challengeTargetLabel: "challenge-type-label", - challengeChannel: .email, + challengeChannel: "email", continuationToken: nil, codeLength: 6) ) diff --git a/MSAL/test/unit/native_auth/network/responses/validator/MSALNativeAuthSignInResponseValidatorTest.swift b/MSAL/test/unit/native_auth/network/responses/validator/MSALNativeAuthSignInResponseValidatorTest.swift index 876698de0b..b3341ff70f 100644 --- a/MSAL/test/unit/native_auth/network/responses/validator/MSALNativeAuthSignInResponseValidatorTest.swift +++ b/MSAL/test/unit/native_auth/network/responses/validator/MSALNativeAuthSignInResponseValidatorTest.swift @@ -46,7 +46,7 @@ final class MSALNativeAuthSignInResponseValidatorTest: MSALNativeAuthTestCase { func test_whenChallengeTypeRedirect_validationShouldReturnRedirectError() { let context = MSALNativeAuthRequestContext(correlationId: defaultUUID) let challengeResponse = MSALNativeAuthSignInChallengeResponse(continuationToken: nil, challengeType: .redirect, bindingMethod: nil, challengeTargetLabel: nil, challengeChannel: nil, codeLength: nil, interval: nil) - let result = sut.validate(context: context, result: .success(challengeResponse)) + let result = sut.validateChallenge(context: context, result: .success(challengeResponse)) if case .error(.redirect) = result {} else { XCTFail("Unexpected result: \(result)") } @@ -56,7 +56,7 @@ final class MSALNativeAuthSignInResponseValidatorTest: MSALNativeAuthTestCase { let context = MSALNativeAuthRequestContext(correlationId: defaultUUID) let continuationToken = "continuationToken" let challengeResponse = MSALNativeAuthSignInChallengeResponse(continuationToken: continuationToken, challengeType: .password, bindingMethod: nil, challengeTargetLabel: nil, challengeChannel: nil, codeLength: nil, interval: nil) - let result = sut.validate(context: context, result: .success(challengeResponse)) + let result = sut.validateChallenge(context: context, result: .success(challengeResponse)) if case .passwordRequired(continuationToken: continuationToken) = result {} else { XCTFail("Unexpected result: \(result)") } @@ -65,7 +65,7 @@ final class MSALNativeAuthSignInResponseValidatorTest: MSALNativeAuthTestCase { func test_whenChallengeTypePasswordAndNoCredentialToken_validationShouldFail() { let context = MSALNativeAuthRequestContext(correlationId: defaultUUID) let challengeResponse = MSALNativeAuthSignInChallengeResponse(continuationToken: nil, challengeType: .password, bindingMethod: nil, challengeTargetLabel: nil, challengeChannel: nil, codeLength: nil, interval: nil) - let result = sut.validate(context: context, result: .success(challengeResponse)) + let result = sut.validateChallenge(context: context, result: .success(challengeResponse)) if case .error(.unexpectedError(.init(errorDescription: "Unexpected response body received"))) = result {} else { XCTFail("Unexpected result: \(result)") } @@ -76,10 +76,15 @@ final class MSALNativeAuthSignInResponseValidatorTest: MSALNativeAuthTestCase { let continuationToken = "continuationToken" let targetLabel = "targetLabel" let codeLength = 4 - let channelType = MSALNativeAuthInternalChannelType.email - let challengeResponse = MSALNativeAuthSignInChallengeResponse(continuationToken: continuationToken, challengeType: .oob, bindingMethod: nil, challengeTargetLabel: targetLabel, challengeChannel: channelType, codeLength: codeLength, interval: nil) - let result = sut.validate(context: context, result: .success(challengeResponse)) - if case .codeRequired(continuationToken: continuationToken, sentTo: targetLabel, channelType: .email, codeLength: codeLength) = result {} else { + let channelTypeRawValue = "email" + let challengeResponse = MSALNativeAuthSignInChallengeResponse(continuationToken: continuationToken, challengeType: .oob, bindingMethod: nil, challengeTargetLabel: targetLabel, challengeChannel: channelTypeRawValue, codeLength: codeLength, interval: nil) + let result = sut.validateChallenge(context: context, result: .success(challengeResponse)) + if case .codeRequired(let validatedCT, let sentTo, let channelType, let validatedCodeLength) = result { + XCTAssertEqual(validatedCT, continuationToken) + XCTAssertEqual(sentTo, targetLabel) + XCTAssertTrue(channelType.isEmailType) + XCTAssertEqual(validatedCodeLength, codeLength) + } else { XCTFail("Unexpected result: \(result)") } } @@ -89,24 +94,24 @@ final class MSALNativeAuthSignInResponseValidatorTest: MSALNativeAuthTestCase { let continuationToken = "continuationToken" let targetLabel = "targetLabel" let codeLength = 4 - let channelType = MSALNativeAuthInternalChannelType.email + let channelType = "email" let missingCredentialToken = MSALNativeAuthSignInChallengeResponse(continuationToken: nil, challengeType: .oob, bindingMethod: nil, challengeTargetLabel: targetLabel, challengeChannel: channelType, codeLength: codeLength, interval: nil) - var result = sut.validate(context: context, result: .success(missingCredentialToken)) + var result = sut.validateChallenge(context: context, result: .success(missingCredentialToken)) if case .error(.unexpectedError(.init(errorDescription: "Unexpected response body received"))) = result {} else { XCTFail("Unexpected result: \(result)") } let missingTargetLabel = MSALNativeAuthSignInChallengeResponse(continuationToken: continuationToken, challengeType: .oob, bindingMethod: nil, challengeTargetLabel: nil, challengeChannel: channelType, codeLength: codeLength, interval: nil) - result = sut.validate(context: context, result: .success(missingTargetLabel)) + result = sut.validateChallenge(context: context, result: .success(missingTargetLabel)) if case .error(.unexpectedError(.init(errorDescription: "Unexpected response body received"))) = result {} else { XCTFail("Unexpected result: \(result)") } let missingChannelType = MSALNativeAuthSignInChallengeResponse(continuationToken: continuationToken, challengeType: .oob, bindingMethod: nil, challengeTargetLabel: targetLabel, challengeChannel: nil, codeLength: codeLength, interval: nil) - result = sut.validate(context: context, result: .success(missingChannelType)) + result = sut.validateChallenge(context: context, result: .success(missingChannelType)) if case .error(.unexpectedError(.init(errorDescription: "Unexpected response body received"))) = result {} else { XCTFail("Unexpected result: \(result)") } let missingCodeLength = MSALNativeAuthSignInChallengeResponse(continuationToken: continuationToken, challengeType: .oob, bindingMethod: nil, challengeTargetLabel: targetLabel, challengeChannel: channelType, codeLength: nil, interval: nil) - result = sut.validate(context: context, result: .success(missingCodeLength)) + result = sut.validateChallenge(context: context, result: .success(missingCodeLength)) if case .error(.unexpectedError(.init(errorDescription: "Unexpected response body received"))) = result {} else { XCTFail("Unexpected result: \(result)") } @@ -114,20 +119,29 @@ final class MSALNativeAuthSignInResponseValidatorTest: MSALNativeAuthTestCase { func test_whenChallengeTypeOTP_validationShouldFail() { let context = MSALNativeAuthRequestContext(correlationId: defaultUUID) - let challengeResponse = MSALNativeAuthSignInChallengeResponse(continuationToken: "something", challengeType: .otp, bindingMethod: nil, challengeTargetLabel: "some", challengeChannel: .email, codeLength: 2, interval: nil) - let result = sut.validate(context: context, result: .success(challengeResponse)) + let challengeResponse = MSALNativeAuthSignInChallengeResponse(continuationToken: "something", challengeType: .otp, bindingMethod: nil, challengeTargetLabel: "some", challengeChannel: "email", codeLength: 2, interval: nil) + let result = sut.validateChallenge(context: context, result: .success(challengeResponse)) if case .error(.unexpectedError(.init(errorDescription: "Unexpected challenge type"))) = result {} else { XCTFail("Unexpected result: \(result)") } } + func test_whenIntrospectRequiredError_validationNotFail() { + let context = MSALNativeAuthRequestContext(correlationId: defaultUUID) + let challengeErrorResponse = MSALNativeAuthSignInChallengeResponseError(error: .invalidRequest, subError: .introspectRequired, correlationId: defaultUUID) + let result = sut.validateChallenge(context: context, result: .failure(challengeErrorResponse)) + if case .introspectRequired = result {} else { + XCTFail("Unexpected result: \(result)") + } + } + // MARK: initiate API tests func test_whenInitiateResponseIsValid_validationShouldBeSuccessful() { let context = MSALNativeAuthRequestContext(correlationId: defaultUUID) let continuationToken = "continuationToken" let initiateResponse = MSALNativeAuthSignInInitiateResponse(continuationToken: continuationToken, challengeType: nil) - let result = sut.validate(context: context, result: .success(initiateResponse)) + let result = sut.validateInitiate(context: context, result: .success(initiateResponse)) if case .success(continuationToken: continuationToken) = result {} else { XCTFail("Unexpected result: \(result)") } @@ -136,7 +150,7 @@ final class MSALNativeAuthSignInResponseValidatorTest: MSALNativeAuthTestCase { func test_whenInitiateResponseIsInvalid_validationShouldFail() { let context = MSALNativeAuthRequestContext(correlationId: defaultUUID) let initiateResponse = MSALNativeAuthSignInInitiateResponse(continuationToken: nil, challengeType: nil) - let result = sut.validate(context: context, result: .success(initiateResponse)) + let result = sut.validateInitiate(context: context, result: .success(initiateResponse)) if case .error(.unexpectedError(.init(errorDescription: "Unexpected response body received"))) = result {} else { XCTFail("Unexpected result: \(result)") } @@ -145,7 +159,7 @@ final class MSALNativeAuthSignInResponseValidatorTest: MSALNativeAuthTestCase { func test_whenInitiateChallengeTypeIsRedirect_validationShouldReturnRedirectError() { let context = MSALNativeAuthRequestContext(correlationId: defaultUUID) let initiateResponse = MSALNativeAuthSignInInitiateResponse(continuationToken: nil, challengeType: .redirect) - let result = sut.validate(context: context, result: .success(initiateResponse)) + let result = sut.validateInitiate(context: context, result: .success(initiateResponse)) if case .error(.redirect) = result {} else { XCTFail("Unexpected result: \(result)") } @@ -154,19 +168,79 @@ final class MSALNativeAuthSignInResponseValidatorTest: MSALNativeAuthTestCase { func test_whenInitiateChallengeTypeIsInvalid_validationShouldFail() { let context = MSALNativeAuthRequestContext(correlationId: defaultUUID) var initiateResponse = MSALNativeAuthSignInInitiateResponse(continuationToken: nil, challengeType: .oob) - var result = sut.validate(context: context, result: .success(initiateResponse)) + var result = sut.validateInitiate(context: context, result: .success(initiateResponse)) if case .error(.unexpectedError(.init(errorDescription: "Unexpected response body received"))) = result {} else { XCTFail("Unexpected result: \(result)") } initiateResponse = MSALNativeAuthSignInInitiateResponse(continuationToken: nil, challengeType: .otp) - result = sut.validate(context: context, result: .success(initiateResponse)) + result = sut.validateInitiate(context: context, result: .success(initiateResponse)) if case .error(.unexpectedError(.init(errorDescription: "Unexpected response body received"))) = result {} else { XCTFail("Unexpected result: \(result)") } initiateResponse = MSALNativeAuthSignInInitiateResponse(continuationToken: nil, challengeType: .password) - result = sut.validate(context: context, result: .success(initiateResponse)) + result = sut.validateInitiate(context: context, result: .success(initiateResponse)) if case .error(.unexpectedError(.init(errorDescription: "Unexpected response body received"))) = result {} else { XCTFail("Unexpected result: \(result)") } } + + // MARK: introspect API tests + + func test_whenIntrospectReturnsRedirect_validationShouldReturnRedirectError() { + let context = MSALNativeAuthRequestContext(correlationId: defaultUUID) + let challengeResponse = MSALNativeAuthSignInIntrospectResponse(continuationToken: nil, methods: nil, challengeType: .redirect) + let result = sut.validateIntrospect(context: context, result: .success(challengeResponse)) + if case .error(.redirect) = result {} else { + XCTFail("Unexpected result: \(result)") + } + } + + func test_whenIntrospectReturnsInvalidRequest_validationShouldReturnRightError() { + let context = MSALNativeAuthRequestContext(correlationId: defaultUUID) + let introspectErrorResponse = MSALNativeAuthSignInIntrospectResponseError(error: .invalidRequest) + let result = sut.validateIntrospect(context: context, result: .failure(introspectErrorResponse)) + if case .error(.invalidRequest(introspectErrorResponse)) = result {} else { + XCTFail("Unexpected result: \(result)") + } + } + + func test_whenIntrospectReturnsExpiredToken_validationShouldReturnRightError() { + let context = MSALNativeAuthRequestContext(correlationId: defaultUUID) + let introspectErrorResponse = MSALNativeAuthSignInIntrospectResponseError(error: .expiredToken) + let result = sut.validateIntrospect(context: context, result: .failure(introspectErrorResponse)) + if case .error(.expiredToken(introspectErrorResponse)) = result {} else { + XCTFail("Unexpected result: \(result)") + } + } + + func test_whenIntrospectWithNilAuthMethod_validationShouldFail() { + let context = MSALNativeAuthRequestContext(correlationId: defaultUUID) + let continuationToken = "continuationToken" + let challengeResponse = MSALNativeAuthSignInIntrospectResponse(continuationToken: continuationToken, methods: nil, challengeType: nil) + let result = sut.validateIntrospect(context: context, result: .success(challengeResponse)) + if case .error(.unexpectedError(MSALNativeAuthSignInIntrospectResponseError(error: .unknown, errorDescription: MSALNativeAuthErrorMessage.unexpectedResponseBody, errorCodes: nil, errorURI: nil, innerErrors: nil, correlationId: nil))) = result {} else { + XCTFail("Unexpected result: \(result)") + } + } + + func test_whenIntrospectWithEmptyAuthMethodList_validationShouldFail() { + let context = MSALNativeAuthRequestContext(correlationId: defaultUUID) + let continuationToken = "continuationToken" + let challengeResponse = MSALNativeAuthSignInIntrospectResponse(continuationToken: continuationToken, methods: [], challengeType: nil) + let result = sut.validateIntrospect(context: context, result: .success(challengeResponse)) + if case .error(.unexpectedError(MSALNativeAuthSignInIntrospectResponseError(error: .unknown, errorDescription: MSALNativeAuthErrorMessage.unexpectedResponseBody, errorCodes: nil, errorURI: nil, innerErrors: nil, correlationId: nil))) = result {} else { + XCTFail("Unexpected result: \(result)") + } + } + + func test_whenIntrospectReturnsValidResult_validationNotFail() { + let context = MSALNativeAuthRequestContext(correlationId: defaultUUID) + let continuationToken = "continuationToken" + let methods = [MSALNativeAuthInternalAuthenticationMethod(id: "1", challengeType: .oob, challengeChannel: "email", loginHint: "us******so.com")] + let challengeResponse = MSALNativeAuthSignInIntrospectResponse(continuationToken: continuationToken, methods: methods, challengeType: nil) + let result = sut.validateIntrospect(context: context, result: .success(challengeResponse)) + if case .authMethodsRetrieved(continuationToken: continuationToken, authMethods: methods) = result {} else { + XCTFail("Unexpected result: \(result)") + } + } } diff --git a/MSAL/test/unit/native_auth/network/responses/validator/MSALNativeAuthSignUpResponseValidatorTests.swift b/MSAL/test/unit/native_auth/network/responses/validator/MSALNativeAuthSignUpResponseValidatorTests.swift index 01b6e596b8..260fa8b505 100644 --- a/MSAL/test/unit/native_auth/network/responses/validator/MSALNativeAuthSignUpResponseValidatorTests.swift +++ b/MSAL/test/unit/native_auth/network/responses/validator/MSALNativeAuthSignUpResponseValidatorTests.swift @@ -246,7 +246,7 @@ final class MSALNativeAuthSignUpResponseValidatorTests: XCTestCase { bindingMethod: nil, interval: nil, challengeTargetLabel: "challenge-type-label", - challengeChannel: .email, + challengeChannel: "email", continuationToken: "token", codeLength: 6) ) @@ -258,7 +258,7 @@ final class MSALNativeAuthSignUpResponseValidatorTests: XCTestCase { } XCTAssertEqual(displayName, "challenge-type-label") - XCTAssertEqual(displayType, .email) + XCTAssertTrue(displayType.isEmailType) XCTAssertEqual(codeLength, 6) XCTAssertEqual(continuationToken, "token") } @@ -269,7 +269,7 @@ final class MSALNativeAuthSignUpResponseValidatorTests: XCTestCase { bindingMethod: nil, interval: nil, challengeTargetLabel: "challenge-type-label", - challengeChannel: .email, + challengeChannel: "email", continuationToken: "token", codeLength: nil) ) @@ -289,7 +289,7 @@ final class MSALNativeAuthSignUpResponseValidatorTests: XCTestCase { bindingMethod: nil, interval: nil, challengeTargetLabel: "challenge-type-label", - challengeChannel: .email, + challengeChannel: "email", continuationToken: nil, codeLength: nil) ) diff --git a/MSAL/test/unit/native_auth/network/responses/validator/MSALNativeAuthTokenResponseValidatorTests.swift b/MSAL/test/unit/native_auth/network/responses/validator/MSALNativeAuthTokenResponseValidatorTests.swift index 559716d8f3..5feb03325f 100644 --- a/MSAL/test/unit/native_auth/network/responses/validator/MSALNativeAuthTokenResponseValidatorTests.swift +++ b/MSAL/test/unit/native_auth/network/responses/validator/MSALNativeAuthTokenResponseValidatorTests.swift @@ -142,12 +142,8 @@ final class MSALNativeAuthTokenResponseValidatorTest: MSALNativeAuthTestCase { guard case .userNotFound(createError(errorCodes)) = checkErrorCodes() else { return XCTFail("Unexpected Error") } - errorCodes = [MSALNativeAuthESTSApiErrorCodes.strongAuthRequired.rawValue, unknownErrorCode1, unknownErrorCode2] - guard case .strongAuthRequired(createError(errorCodes)) = checkErrorCodes() else { - return XCTFail("Unexpected Error") - } - errorCodes = [MSALNativeAuthESTSApiErrorCodes.strongAuthRequired.rawValue, unknownErrorCode1, unknownErrorCode2] - guard case .strongAuthRequired(createError(errorCodes)) = checkErrorCodes() else { + errorCodes = [MSALNativeAuthESTSApiErrorCodes.userNotFound.rawValue, unknownErrorCode1, unknownErrorCode2] + guard case .userNotFound(createError(errorCodes)) = checkErrorCodes() else { return XCTFail("Unexpected Error") } errorCodes = [MSALNativeAuthESTSApiErrorCodes.invalidCredentials.rawValue, unknownErrorCode1, unknownErrorCode2] @@ -171,6 +167,34 @@ final class MSALNativeAuthTokenResponseValidatorTest: MSALNativeAuthTestCase { MSALNativeAuthTokenResponseError(error: .invalidGrant, subError: nil, errorDescription: nil, errorCodes: errorCodes, errorURI: nil, innerErrors: nil, continuationToken: nil) } } + + func test_invalidGrantMFARequired_triggerStrongAuthRequiredResponse() { + let continuationTokenResponse = "ct" + let error = MSALNativeAuthTokenResponseError(error: .invalidGrant, subError: .mfaRequired, errorDescription: nil, errorCodes: nil, errorURI: nil, innerErrors: nil, continuationToken: continuationTokenResponse) + + let context = MSALNativeAuthRequestContext(correlationId: defaultUUID) + let result = sut.validate(context: context, msidConfiguration: MSALNativeAuthConfigStubs.msidConfiguration, result: .failure(error)) + + guard case .strongAuthRequired(let continuationToken) = result else { + return XCTFail("Unexpected response") + } + XCTAssertEqual(continuationTokenResponse, continuationToken) + } + + func test_invalidGrantMFARequiredNoContinuationToken_validationFails() { + let error = MSALNativeAuthTokenResponseError(error: .invalidGrant, subError: .mfaRequired, errorDescription: nil, errorCodes: nil, errorURI: nil, innerErrors: nil, continuationToken: nil) + + let context = MSALNativeAuthRequestContext(correlationId: defaultUUID) + let result = sut.validate(context: context, msidConfiguration: MSALNativeAuthConfigStubs.msidConfiguration, result: .failure(error)) + + guard case .error(let errorType) = result else { + return XCTFail("Unexpected response") + } + guard case .generalError(let validatedError) = errorType else { + return XCTFail("Unexpected Error") + } + XCTAssertEqual(validatedError?.error, .unknown) + } func test_invalidGrantTokenResponse_withUnknownErrorCode_andKnownErrorCodes_isProperlyHandled() { let knownErrorCode = MSALNativeAuthESTSApiErrorCodes.userNotFound.rawValue @@ -199,7 +223,7 @@ final class MSALNativeAuthTokenResponseValidatorTest: MSALNativeAuthTestCase { let unknownErrorCode1 = Int.max var errorCodes: [Int] = [unknownErrorCode1] checkErrorCodes() - errorCodes = [MSALNativeAuthESTSApiErrorCodes.strongAuthRequired.rawValue] + errorCodes = [MSALNativeAuthESTSApiErrorCodes.invalidRequestParameter.rawValue] checkErrorCodes() errorCodes = [MSALNativeAuthESTSApiErrorCodes.userNotFound.rawValue] checkErrorCodes() diff --git a/MSAL/test/unit/native_auth/network/responses/validator/MSALNativeAuthTokenValidatedErrorTypeTests.swift b/MSAL/test/unit/native_auth/network/responses/validator/MSALNativeAuthTokenValidatedErrorTypeTests.swift index d46bf6076a..fd51d2713e 100644 --- a/MSAL/test/unit/native_auth/network/responses/validator/MSALNativeAuthTokenValidatedErrorTypeTests.swift +++ b/MSAL/test/unit/native_auth/network/responses/validator/MSALNativeAuthTokenValidatedErrorTypeTests.swift @@ -147,16 +147,6 @@ final class MSALNativeAuthTokenValidatedErrorTypeTests: XCTestCase { XCTAssertEqual(error.errorUri, testErrorUri) } - func test_convertToSignInPasswordStartError_strongAuthRequired() { - let error = sut.strongAuthRequired(apiErrorStub).convertToSignInPasswordStartError(correlationId: testCorrelationId) - - XCTAssertEqual(error.type, .browserRequired) - XCTAssertEqual(error.errorDescription, testDescription) - XCTAssertEqual(error.errorCodes, testErrorCodes) - XCTAssertEqual(error.correlationId, testCorrelationId) - XCTAssertEqual(error.errorUri, testErrorUri) - } - func test_convertToSignInPasswordStartError_invalidScope() { let error = sut.invalidScope(apiErrorStub).convertToSignInPasswordStartError(correlationId: testCorrelationId) diff --git a/MSAL/test/unit/native_auth/public/MSALNativeAuthPublicClientApplicationTest.swift b/MSAL/test/unit/native_auth/public/MSALNativeAuthPublicClientApplicationTest.swift index a7068e6531..226b0fc7e9 100644 --- a/MSAL/test/unit/native_auth/public/MSALNativeAuthPublicClientApplicationTest.swift +++ b/MSAL/test/unit/native_auth/public/MSALNativeAuthPublicClientApplicationTest.swift @@ -107,7 +107,7 @@ final class MSALNativeAuthPublicClientApplicationTest: XCTestCase { let expectedResult: SignUpStartResult = .codeRequired( newState: .init(controller: controllerFactoryMock.signUpController, username: "", continuationToken: "continuationToken", correlationId: correlationId), sentTo: "sentTo", - channelTargetType: .email, + channelTargetType: MSALNativeAuthChannelType(value: "email"), codeLength: 1 ) controllerFactoryMock.signUpController.startResult = .init(expectedResult, correlationId: correlationId, telemetryUpdate: { _ in @@ -130,7 +130,7 @@ final class MSALNativeAuthPublicClientApplicationTest: XCTestCase { let expectedResult: SignUpStartResult = .codeRequired( newState: .init(controller: controllerFactoryMock.signUpController, username: "", continuationToken: "continuationToken", correlationId: correlationId), sentTo: "sentTo", - channelTargetType: .email, + channelTargetType: MSALNativeAuthChannelType(value: "email"), codeLength: 1 ) controllerFactoryMock.signUpController.startResult = .init(expectedResult, correlationId: correlationId, telemetryUpdate: { _ in @@ -208,7 +208,7 @@ final class MSALNativeAuthPublicClientApplicationTest: XCTestCase { let expectedResult: SignUpStartResult = .codeRequired( newState: .init(controller: controllerFactoryMock.signUpController, username: "", continuationToken: "continuationToken", correlationId: correlationId), sentTo: "sentTo", - channelTargetType: .email, + channelTargetType: MSALNativeAuthChannelType(value: "email"), codeLength: 1 ) controllerFactoryMock.signUpController.startResult = .init(expectedResult, correlationId: correlationId, telemetryUpdate: { _ in @@ -231,7 +231,7 @@ final class MSALNativeAuthPublicClientApplicationTest: XCTestCase { let expectedResult: SignUpStartResult = .codeRequired( newState: .init(controller: controllerFactoryMock.signUpController, username: "", continuationToken: "continuationToken", correlationId: correlationId), sentTo: "sentTo", - channelTargetType: .email, + channelTargetType: MSALNativeAuthChannelType(value: "email"), codeLength: 1 ) controllerFactoryMock.signUpController.startResult = .init(expectedResult, correlationId: correlationId, telemetryUpdate: { _ in @@ -345,12 +345,12 @@ final class MSALNativeAuthPublicClientApplicationTest: XCTestCase { let delegate = SignInPasswordStartDelegateSpy(expectation: exp1) delegate.expectedSentTo = "sentTo" delegate.expectedCodeLength = 1 - delegate.expectedChannelTargetType = .email + delegate.expectedChannelTargetType = MSALNativeAuthChannelType(value: "email") let expectedResult: SignInStartResult = .codeRequired( newState: SignInCodeRequiredState(scopes: [], controller: controllerFactoryMock.signInController, continuationToken: "", correlationId: correlationId), sentTo: "sentTo", - channelTargetType: .email, + channelTargetType: MSALNativeAuthChannelType(value: "email"), codeLength: 1 ) @@ -373,7 +373,7 @@ final class MSALNativeAuthPublicClientApplicationTest: XCTestCase { let expectedResult: SignInStartResult = .codeRequired( newState: SignInCodeRequiredState(scopes: [], controller: controllerFactoryMock.signInController, continuationToken: "", correlationId: correlationId), sentTo: "sentTo", - channelTargetType: .email, + channelTargetType: MSALNativeAuthChannelType(value: "email"), codeLength: 1 ) @@ -401,12 +401,12 @@ final class MSALNativeAuthPublicClientApplicationTest: XCTestCase { let delegate = SignInCodeStartDelegateSpy(expectation: exp1) delegate.expectedSentTo = "sentTo" delegate.expectedCodeLength = 1 - delegate.expectedChannelTargetType = .email + delegate.expectedChannelTargetType = MSALNativeAuthChannelType(value: "email") let expectedResult: SignInStartResult = .codeRequired( newState: SignInCodeRequiredState(scopes: [], controller: controllerFactoryMock.signInController, continuationToken: "", correlationId: correlationId), sentTo: "sentTo", - channelTargetType: .email, + channelTargetType: MSALNativeAuthChannelType(value: "email"), codeLength: 1 ) @@ -428,7 +428,7 @@ final class MSALNativeAuthPublicClientApplicationTest: XCTestCase { let expectedResult: SignInStartResult = .codeRequired( newState: SignInCodeRequiredState(scopes: [], controller: controllerFactoryMock.signInController, continuationToken: "", correlationId: correlationId), sentTo: "sentTo", - channelTargetType: .email, + channelTargetType: MSALNativeAuthChannelType(value: "email"), codeLength: 1 ) @@ -499,7 +499,7 @@ final class MSALNativeAuthPublicClientApplicationTest: XCTestCase { let expectedResult: ResetPasswordStartResult = .codeRequired( newState: .init(controller: controllerFactoryMock.resetPasswordController, username: "username", continuationToken: "continuationToken", correlationId: correlationId), sentTo: "sentTo", - channelTargetType: .email, + channelTargetType: MSALNativeAuthChannelType(value: "email"), codeLength: 1 ) @@ -513,7 +513,7 @@ final class MSALNativeAuthPublicClientApplicationTest: XCTestCase { XCTAssertEqual(delegate.newState?.continuationToken, "continuationToken") XCTAssertEqual(delegate.newState?.username, "username") XCTAssertEqual(delegate.sentTo, "sentTo") - XCTAssertEqual(delegate.channelTargetType, .email) + XCTAssertEqual(delegate.channelTargetType?.isEmailType, true) XCTAssertEqual(delegate.codeLength, 1) } @@ -525,7 +525,7 @@ final class MSALNativeAuthPublicClientApplicationTest: XCTestCase { let expectedResult: ResetPasswordStartResult = .codeRequired( newState: .init(controller: controllerFactoryMock.resetPasswordController, username: "username", continuationToken: "continuationToken", correlationId: correlationId), sentTo: "sentTo", - channelTargetType: .email, + channelTargetType: MSALNativeAuthChannelType(value: "email"), codeLength: 1 ) controllerFactoryMock.resetPasswordController.resetPasswordResponse = .init(expectedResult, correlationId: correlationId, telemetryUpdate: { _ in @@ -566,7 +566,7 @@ final class MSALNativeAuthPublicClientApplicationTest: XCTestCase { let signUpResponseValidatorMock = MSALNativeAuthSignUpResponseValidatorMock() signUpResponseValidatorMock.mockValidateSignUpStartFunc(.success(continuationToken: "continuationToken")) - signUpResponseValidatorMock.mockValidateSignUpChallengeFunc(.codeRequired("sentTo", .email, 4, "continuationToken 2")) + signUpResponseValidatorMock.mockValidateSignUpChallengeFunc(.codeRequired("sentTo", MSALNativeAuthChannelType(value: "email"), 4, "continuationToken 2")) signUpResponseValidatorMock.mockValidateSignUpContinueFunc(.success(continuationToken: "continuationToken")) let signInRequestProviderMock = MSALNativeAuthSignInRequestProviderMock() @@ -671,7 +671,7 @@ final class MSALNativeAuthPublicClientApplicationTest: XCTestCase { let signUpResponseValidatorMock = MSALNativeAuthSignUpResponseValidatorMock() signUpResponseValidatorMock.mockValidateSignUpStartFunc(.success(continuationToken: "continuationToken")) - signUpResponseValidatorMock.mockValidateSignUpChallengeFunc(.codeRequired("sentTo", .email, 4, "continuationToken 2")) + signUpResponseValidatorMock.mockValidateSignUpChallengeFunc(.codeRequired("sentTo", MSALNativeAuthChannelType(value: "email"), 4, "continuationToken 2")) signUpResponseValidatorMock.mockValidateSignUpContinueFunc(.success(continuationToken: "continuationToken")) let signInRequestProviderMock = MSALNativeAuthSignInRequestProviderMock() @@ -771,7 +771,7 @@ final class MSALNativeAuthPublicClientApplicationTest: XCTestCase { signInRequestProviderMock.expectedContext = contextMock let continuationToken = "" let expectedSentTo = "sentTo" - let expectedChannelTargetType = MSALNativeAuthChannelType.email + let expectedChannelTargetType = MSALNativeAuthChannelType(value: "email") let expectedCodeLength = 4 let signInResponseValidatorMock = MSALNativeAuthSignInResponseValidatorMock() signInResponseValidatorMock.initiateValidatedResponse = .success(continuationToken: continuationToken) @@ -860,7 +860,7 @@ final class MSALNativeAuthPublicClientApplicationTest: XCTestCase { let continuationToken = "" let expectedSentTo = "sentTo" - let expectedChannelTargetType = MSALNativeAuthChannelType.email + let expectedChannelTargetType = MSALNativeAuthChannelType(value: "email") let expectedCodeLength = 4 delegateCodeStart.expectedSentTo = expectedSentTo delegateCodeStart.expectedChannelTargetType = expectedChannelTargetType @@ -962,7 +962,7 @@ final class MSALNativeAuthPublicClientApplicationTest: XCTestCase { let resetPasswordResponseValidator = MSALNativeAuthResetPasswordResponseValidatorMock() resetPasswordResponseValidator.mockValidateResetPasswordStartFunc(.success(continuationToken: "continuationToken")) - resetPasswordResponseValidator.mockValidateResetPasswordChallengeFunc(.success("sentTo", .email, 4, "continuationToken 2")) + resetPasswordResponseValidator.mockValidateResetPasswordChallengeFunc(.success("sentTo", MSALNativeAuthChannelType(value: "email"), 4, "continuationToken 2")) resetPasswordResponseValidator.mockValidateResetPasswordContinueFunc(.success(continuationToken: "continuationToken")) resetPasswordResponseValidator.mockValidateResetPasswordSubmitFunc(.success(continuationToken: "continuationToken 3", pollInterval: 0)) resetPasswordResponseValidator.mockValidateResetPasswordPollCompletionFunc(.success(status: .succeeded, continuationToken: "continuationToken")) diff --git a/MSAL/test/unit/native_auth/public/MSALNativeAuthUserAccountResultTests.swift b/MSAL/test/unit/native_auth/public/MSALNativeAuthUserAccountResultTests.swift index 8aec6f1e44..3fc304d355 100644 --- a/MSAL/test/unit/native_auth/public/MSALNativeAuthUserAccountResultTests.swift +++ b/MSAL/test/unit/native_auth/public/MSALNativeAuthUserAccountResultTests.swift @@ -158,4 +158,79 @@ class MSALNativeAuthUserAccountResultTests: XCTestCase { XCTAssertEqual(result.errorCodes, []) XCTAssertEqual(result.correlationId, contextCorrelationId) } + + func test_errorWithValidExternalErrorCodes_ParseShouldWorks() { + let contextCorrelationId = UUID() + let context = MSALNativeAuthRequestContext(correlationId: contextCorrelationId) + let errorCodes = [1, 2, 3] + let userInfo: [String : Any] = [ + MSALSTSErrorCodesKey: errorCodes + ] + let error = NSError(domain: "", code: 1, userInfo: userInfo) + + let result = sut.createRetrieveAccessTokenError(error: error, context: context) + + XCTAssertEqual(result.errorCodes, errorCodes) + } + + func test_errorWithInvalidExternalErrorCodes_ParseShouldWorks() { + let contextCorrelationId = UUID() + let context = MSALNativeAuthRequestContext(correlationId: contextCorrelationId) + let errorCodes = ["123"] + let userInfo: [String : Any] = [ + MSALSTSErrorCodesKey: errorCodes + ] + let error = NSError(domain: "", code: 1, userInfo: userInfo) + + let result = sut.createRetrieveAccessTokenError(error: error, context: context) + + XCTAssertEqual(result.errorCodes, []) + } + + func test_errorWithValidInnerErrorWithErrorCodes_ParseShouldWorks() { + let contextCorrelationId = UUID() + let context = MSALNativeAuthRequestContext(correlationId: contextCorrelationId) + let errorCodes = [1, 2, 3] + let userInfo: [String : Any] = [ + MSALSTSErrorCodesKey: errorCodes + ] + let innerError = NSError(domain: "", code: 1, userInfo: userInfo) + let error = NSError(domain: "", code: 1, userInfo: [NSUnderlyingErrorKey: innerError]) + + let result = sut.createRetrieveAccessTokenError(error: error, context: context) + + XCTAssertEqual(result.errorCodes, errorCodes) + } + + func test_errorWithInvalidInnerErrorWithErrorCodes_ParseShouldWorks() { + let contextCorrelationId = UUID() + let context = MSALNativeAuthRequestContext(correlationId: contextCorrelationId) + let errorCodes = ["123"] + let userInfo: [String : Any] = [ + MSALSTSErrorCodesKey: errorCodes + ] + let innerError = NSError(domain: "", code: 1, userInfo: userInfo) + let error = NSError(domain: "", code: 1, userInfo: [NSUnderlyingErrorKey: innerError]) + + let result = sut.createRetrieveAccessTokenError(error: error, context: context) + + XCTAssertEqual(result.errorCodes, []) + } + + func test_errorWithMFARequiredErrorCode_ErrorMessageShouldContainsCorrectMessage() { + let contextCorrelationId = UUID() + let context = MSALNativeAuthRequestContext(correlationId: contextCorrelationId) + let errorCodes = [50076] + let message = "message" + let userInfo: [String : Any] = [ + MSALErrorDescriptionKey: message, + MSALSTSErrorCodesKey: errorCodes + ] + let error = NSError(domain: "", code: 1, userInfo: userInfo) + + let result = sut.createRetrieveAccessTokenError(error: error, context: context) + + XCTAssertEqual(result.errorCodes, errorCodes) + XCTAssertEqual(result.errorDescription, MSALNativeAuthErrorMessage.refreshTokenMFARequiredError + message) + } } diff --git a/MSAL/test/unit/native_auth/public/delegate/mfa/MFAGetAuthMethodsDelegateDispatcherTests.swift b/MSAL/test/unit/native_auth/public/delegate/mfa/MFAGetAuthMethodsDelegateDispatcherTests.swift new file mode 100644 index 0000000000..8c70a157ab --- /dev/null +++ b/MSAL/test/unit/native_auth/public/delegate/mfa/MFAGetAuthMethodsDelegateDispatcherTests.swift @@ -0,0 +1,92 @@ +// +// 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. + +@testable import MSAL +import XCTest + +final class MFAGetAuthMethodsDelegateDispatcherTests: XCTestCase { + private var telemetryExp: XCTestExpectation! + private var delegateExp: XCTestExpectation! + private var sut: MFAGetAuthMethodsDelegateDispatcher! + private let controllerFactoryMock = MSALNativeAuthControllerFactoryMock() + private let correlationId = UUID() + + override func setUp() { + super.setUp() + telemetryExp = expectation(description: "delegateDispatcher telemetry exp") + delegateExp = expectation(description: "delegateDispatcher delegate exp") + } + + func test_dispatchSelection_whenDelegateMethodIsImplemented() async { + let delegate = MFAGetAuthMethodsDelegateSpy(expectation: delegateExp, expectedError: nil) + + sut = .init(delegate: delegate, telemetryUpdate: { result in + guard case .success = result else { + return XCTFail("wrong result") + } + self.telemetryExp.fulfill() + }) + + let expectedState = MFARequiredState(controller: controllerFactoryMock.signInController, scopes: [], continuationToken: "continuationToken", correlationId: correlationId) + let expectedAuthMethods = [MSALAuthMethod(id: "1", challengeType: "oob", loginHint: "us**@**oso.com", channelTargetType: MSALNativeAuthChannelType(value: "email"))] + + await sut.dispatchSelectionRequired(authMethods: expectedAuthMethods, newState: expectedState, correlationId: correlationId) + + await fulfillment(of: [telemetryExp, delegateExp], timeout: 1) + + XCTAssertEqual(delegate.newMFARequiredState, expectedState) + XCTAssertEqual(delegate.newAuthMethods, expectedAuthMethods) + } + + func test_dispatchSelection_whenDelegateOptionalMethodNotImplemented() async { + let expectedError = MFAGetAuthMethodsError( + type: .generalError, + message: String(format: MSALNativeAuthErrorMessage.delegateNotImplemented, "onMFAGetAuthMethodsSelectionRequired"), + correlationId: correlationId + ) + let delegate = MFAGetAuthMethodsNotImplementedDelegateSpy(expectation: delegateExp, expectedError: expectedError) + + sut = .init(delegate: delegate, telemetryUpdate: { result in + guard case let .failure(error) = result, let customError = error as? MFAGetAuthMethodsError else { + return XCTFail("wrong result") + } + + checkError(customError) + self.telemetryExp.fulfill() + }) + + let expectedState = MFARequiredState(controller: controllerFactoryMock.signInController, scopes: [], continuationToken: "continuationToken", correlationId: correlationId) + + await sut.dispatchSelectionRequired(authMethods: [], newState: expectedState, correlationId: correlationId) + + await fulfillment(of: [telemetryExp, delegateExp], timeout: 1) + checkError(delegate.expectedError) + + func checkError(_ error: MFAGetAuthMethodsError?) { + XCTAssertEqual(error?.type, expectedError.type) + XCTAssertEqual(error?.errorDescription, expectedError.errorDescription) + XCTAssertEqual(error?.correlationId, expectedError.correlationId) + } + } +} diff --git a/MSAL/test/unit/native_auth/public/delegate/mfa/MFASendChallengeDelegateDispatcherTests.swift b/MSAL/test/unit/native_auth/public/delegate/mfa/MFASendChallengeDelegateDispatcherTests.swift new file mode 100644 index 0000000000..5eb4eea6f4 --- /dev/null +++ b/MSAL/test/unit/native_auth/public/delegate/mfa/MFASendChallengeDelegateDispatcherTests.swift @@ -0,0 +1,159 @@ +// +// 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. + +@testable import MSAL +import XCTest + +final class MFARequestChallengeDelegateDispatcherTests: XCTestCase { + private var telemetryExp: XCTestExpectation! + private var delegateExp: XCTestExpectation! + private var sut: MFARequestChallengeDelegateDispatcher! + private let controllerFactoryMock = MSALNativeAuthControllerFactoryMock() + private let correlationId = UUID() + + override func setUp() { + super.setUp() + telemetryExp = expectation(description: "delegateDispatcher telemetry exp") + delegateExp = expectation(description: "delegateDispatcher delegate exp") + } + + func test_dispatchVerificationRequired_whenDelegateMethodIsImplemented() async { + let expectedState = MFARequiredState(controller: controllerFactoryMock.signInController, scopes: [], continuationToken: "continuationToken", correlationId: correlationId) + let expectedSentTo = "user@contoso.com" + let expectedChannelTargetType = MSALNativeAuthChannelType(value: "email") + let expectedCodeLength = 4 + + let delegate = MFARequestChallengeDelegateSpy(expectation: delegateExp, expectedError: nil) + + sut = .init(delegate: delegate, telemetryUpdate: { result in + guard case .success = result else { + return XCTFail("wrong result") + } + self.telemetryExp.fulfill() + }) + + await sut.dispatchVerificationRequired( + newState: expectedState, + sentTo: expectedSentTo, + channelTargetType: expectedChannelTargetType, + codeLength: expectedCodeLength, + correlationId: correlationId + ) + + await fulfillment(of: [telemetryExp, delegateExp], timeout: 1) + + XCTAssertEqual(delegate.newMFARequiredState, expectedState) + XCTAssertEqual(delegate.newSentTo, expectedSentTo) + XCTAssertEqual(delegate.newChannelTargetType, expectedChannelTargetType) + XCTAssertEqual(delegate.newCodeLength, expectedCodeLength) + } + + func test_dispatchVerificationRequired_whenDelegateOptionalMethodNotImplemented() async { + let expectedError = MFARequestChallengeError( + type: .generalError, + message: String(format: MSALNativeAuthErrorMessage.delegateNotImplemented, "onMFARequestChallengeVerificationRequired"), + correlationId: correlationId + ) + let delegate = MFARequestChallengeNotImplementedDelegateSpy(expectation: delegateExp, expectedError: expectedError) + + sut = .init(delegate: delegate, telemetryUpdate: { result in + guard case let .failure(error) = result, let customError = error as? MFARequestChallengeError else { + return XCTFail("wrong result") + } + + checkError(customError) + self.telemetryExp.fulfill() + }) + + await sut.dispatchVerificationRequired( + newState: MFARequiredState(controller: controllerFactoryMock.signInController, scopes: [], continuationToken: "continuationToken", correlationId: correlationId), + sentTo: "user@contoso.com", + channelTargetType: MSALNativeAuthChannelType(value: "email"), + codeLength: 4, + correlationId: correlationId + ) + + await fulfillment(of: [telemetryExp, delegateExp], timeout: 1) + checkError(delegate.expectedError) + + func checkError(_ error: MFARequestChallengeError?) { + XCTAssertEqual(error?.type, expectedError.type) + XCTAssertEqual(error?.errorDescription, expectedError.errorDescription) + XCTAssertEqual(error?.correlationId, expectedError.correlationId) + } + } + + func test_dispatchSelection_whenDelegateMethodIsImplemented() async { + let delegate = MFARequestChallengeDelegateSpy(expectation: delegateExp, expectedError: nil) + + sut = .init(delegate: delegate, telemetryUpdate: { result in + guard case .success = result else { + return XCTFail("wrong result") + } + self.telemetryExp.fulfill() + }) + + let expectedState = MFARequiredState(controller: controllerFactoryMock.signInController, scopes: [], continuationToken: "continuationToken", correlationId: correlationId) + let expectedAuthMethods = [MSALAuthMethod(id: "1", challengeType: "oob", loginHint: "us**@**oso.com", channelTargetType: MSALNativeAuthChannelType(value: "email"))] + + await sut.dispatchSelectionRequired(authMethods: expectedAuthMethods, newState: expectedState, correlationId: correlationId) + + await fulfillment(of: [telemetryExp, delegateExp], timeout: 1) + + XCTAssertEqual(delegate.newMFARequiredState, expectedState) + XCTAssertEqual(delegate.newAuthMethods, expectedAuthMethods) + } + + func test_dispatchSelection_whenDelegateOptionalMethodNotImplemented() async { + let expectedError = MFARequestChallengeError( + type: .generalError, + message: String(format: MSALNativeAuthErrorMessage.delegateNotImplemented, "onMFARequestChallengeSelectionRequired"), + correlationId: correlationId + ) + let delegate = MFARequestChallengeNotImplementedDelegateSpy(expectation: delegateExp, expectedError: expectedError) + + + sut = .init(delegate: delegate, telemetryUpdate: { result in + guard case let .failure(error) = result, let customError = error as? MFARequestChallengeError else { + return XCTFail("wrong result") + } + + checkError(customError) + self.telemetryExp.fulfill() + }) + + let expectedState = MFARequiredState(controller: controllerFactoryMock.signInController, scopes: [], continuationToken: "continuationToken", correlationId: correlationId) + + await sut.dispatchSelectionRequired(authMethods: [], newState: expectedState, correlationId: correlationId) + + await fulfillment(of: [telemetryExp, delegateExp], timeout: 1) + checkError(delegate.expectedError) + + func checkError(_ error: MFARequestChallengeError?) { + XCTAssertEqual(error?.type, expectedError.type) + XCTAssertEqual(error?.errorDescription, expectedError.errorDescription) + XCTAssertEqual(error?.correlationId, expectedError.correlationId) + } + } +} diff --git a/MSAL/test/unit/native_auth/public/delegate/mfa/MFASubmitChallengeDelegateDispatcherTests.swift b/MSAL/test/unit/native_auth/public/delegate/mfa/MFASubmitChallengeDelegateDispatcherTests.swift new file mode 100644 index 0000000000..455b187284 --- /dev/null +++ b/MSAL/test/unit/native_auth/public/delegate/mfa/MFASubmitChallengeDelegateDispatcherTests.swift @@ -0,0 +1,88 @@ +// +// 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. + +@testable import MSAL +import XCTest + +final class MFASubmitChallengeDelegateDispatcherTests: XCTestCase { + private var telemetryExp: XCTestExpectation! + private var delegateExp: XCTestExpectation! + private var sut: MFASubmitChallengeDelegateDispatcher! + private let controllerFactoryMock = MSALNativeAuthControllerFactoryMock() + private let correlationId = UUID() + + override func setUp() { + super.setUp() + telemetryExp = expectation(description: "delegateDispatcher telemetry exp") + delegateExp = expectation(description: "delegateDispatcher delegate exp") + } + + func test_dispatchSignInCompleted_whenDelegateMethodIsImplemented() async { + let expectedResult = MSALNativeAuthUserAccountResultStub.result + + let delegate = MFASubmitChallengeDelegateSpy(expectation: delegateExp, expectedResult: expectedResult, expectedError: nil) + + sut = .init(delegate: delegate, telemetryUpdate: { result in + guard case .success = result else { + return XCTFail("wrong result") + } + self.telemetryExp.fulfill() + }) + + await sut.dispatchSignInCompleted(result: expectedResult, correlationId: correlationId) + + await fulfillment(of: [telemetryExp, delegateExp], timeout: 1) + + XCTAssertEqual(delegate.expectedResult, expectedResult) + } + + func test_dispatchSignInCompleted_whenDelegateMethodIsNotImplemented() async { + let expectedError = MFASubmitChallengeError( + type: .generalError, + message: String(format: MSALNativeAuthErrorMessage.delegateNotImplemented, "onSignInCompleted"), + correlationId: correlationId + ) + let delegate = MFASubmitChallengeNotImplementedDelegateSpy(expectation: delegateExp, expectedError: expectedError) + + sut = .init(delegate: delegate, telemetryUpdate: { result in + guard case let .failure(error) = result, let customError = error as? MFASubmitChallengeError else { + return XCTFail("wrong result") + } + + checkError(customError) + self.telemetryExp.fulfill() + }) + + await sut.dispatchSignInCompleted(result: MSALNativeAuthUserAccountResultStub.result, correlationId: correlationId) + + await fulfillment(of: [telemetryExp, delegateExp], timeout: 1) + checkError(delegate.expectedError) + + func checkError(_ error: MFASubmitChallengeError?) { + XCTAssertEqual(error?.type, expectedError.type) + XCTAssertEqual(error?.errorDescription, expectedError.errorDescription) + XCTAssertEqual(error?.correlationId, expectedError.correlationId) + } + } +} diff --git a/MSAL/test/unit/native_auth/public/delegate/reset_password/ResetPasswordResendCodeDelegateDispatcherTests.swift b/MSAL/test/unit/native_auth/public/delegate/reset_password/ResetPasswordResendCodeDelegateDispatcherTests.swift index d3ccaeb6e4..f26ce40f75 100644 --- a/MSAL/test/unit/native_auth/public/delegate/reset_password/ResetPasswordResendCodeDelegateDispatcherTests.swift +++ b/MSAL/test/unit/native_auth/public/delegate/reset_password/ResetPasswordResendCodeDelegateDispatcherTests.swift @@ -51,7 +51,7 @@ final class ResetPasswordResendCodeDelegateDispatcherTests: XCTestCase { let expectedState = ResetPasswordCodeRequiredState(controller: controllerFactoryMock.resetPasswordController, username: "", continuationToken: "continuationToken", correlationId: correlationId) let expectedSentTo = "user@contoso.com" - let expectedChannelTargetType = MSALNativeAuthChannelType.email + let expectedChannelTargetType = MSALNativeAuthChannelType(value: "email") let expectedCodeLength = 4 await sut.dispatchResetPasswordResendCodeRequired( @@ -85,7 +85,7 @@ final class ResetPasswordResendCodeDelegateDispatcherTests: XCTestCase { let expectedState = ResetPasswordCodeRequiredState(controller: controllerFactoryMock.resetPasswordController, username: "", continuationToken: "continuationToken", correlationId: correlationId) let expectedSentTo = "user@contoso.com" - let expectedChannelTargetType = MSALNativeAuthChannelType.email + let expectedChannelTargetType = MSALNativeAuthChannelType(value: "email") let expectedCodeLength = 4 await sut.dispatchResetPasswordResendCodeRequired( diff --git a/MSAL/test/unit/native_auth/public/delegate/reset_password/ResetPasswordStartDelegateDispatcherTests.swift b/MSAL/test/unit/native_auth/public/delegate/reset_password/ResetPasswordStartDelegateDispatcherTests.swift index 6632ecd19b..33ffc4c0f3 100644 --- a/MSAL/test/unit/native_auth/public/delegate/reset_password/ResetPasswordStartDelegateDispatcherTests.swift +++ b/MSAL/test/unit/native_auth/public/delegate/reset_password/ResetPasswordStartDelegateDispatcherTests.swift @@ -51,7 +51,7 @@ final class ResetPasswordStartDelegateDispatcherTests: XCTestCase { let expectedState = ResetPasswordCodeRequiredState(controller: controllerFactoryMock.resetPasswordController, username: "username", continuationToken: "continuationToken", correlationId: correlationId) let expectedSentTo = "user@contoso.com" - let expectedChannelTargetType = MSALNativeAuthChannelType.email + let expectedChannelTargetType = MSALNativeAuthChannelType(value: "email") let expectedCodeLength = 4 await sut.dispatchResetPasswordCodeRequired( @@ -85,7 +85,7 @@ final class ResetPasswordStartDelegateDispatcherTests: XCTestCase { let expectedState = ResetPasswordCodeRequiredState(controller: controllerFactoryMock.resetPasswordController, username: "username", continuationToken: "continuationToken", correlationId: correlationId) let expectedSentTo = "user@contoso.com" - let expectedChannelTargetType = MSALNativeAuthChannelType.email + let expectedChannelTargetType = MSALNativeAuthChannelType(value: "email") let expectedCodeLength = 4 await sut.dispatchResetPasswordCodeRequired( diff --git a/MSAL/test/unit/native_auth/public/delegate/sign_in/SignInPasswordRequiredDelegateDispatcherTests.swift b/MSAL/test/unit/native_auth/public/delegate/sign_in/SignInPasswordRequiredDelegateDispatcherTests.swift index d6bc0ce04a..9a9514c9ee 100644 --- a/MSAL/test/unit/native_auth/public/delegate/sign_in/SignInPasswordRequiredDelegateDispatcherTests.swift +++ b/MSAL/test/unit/native_auth/public/delegate/sign_in/SignInPasswordRequiredDelegateDispatcherTests.swift @@ -51,7 +51,7 @@ final class SignInPasswordRequiredDelegateDispatcherTests: XCTestCase { await sut.dispatchSignInCompleted(result: expectedResult, correlationId: correlationId) - await fulfillment(of: [telemetryExp, delegateExp]) + await fulfillment(of: [telemetryExp, delegateExp], timeout: 1) XCTAssertEqual(delegate.expectedUserAccountResult, expectedResult) } @@ -74,7 +74,7 @@ final class SignInPasswordRequiredDelegateDispatcherTests: XCTestCase { await sut.dispatchSignInCompleted(result: expectedResult, correlationId: correlationId) - await fulfillment(of: [telemetryExp, delegateExp]) + await fulfillment(of: [telemetryExp, delegateExp], timeout: 1) checkError(delegate.delegateError) func checkError(_ error: PasswordRequiredError?) { diff --git a/MSAL/test/unit/native_auth/public/delegate/sign_in/SignInPasswordStartDelegateDispatcherTests.swift b/MSAL/test/unit/native_auth/public/delegate/sign_in/SignInPasswordStartDelegateDispatcherTests.swift index c1ff275498..e5ecf34d83 100644 --- a/MSAL/test/unit/native_auth/public/delegate/sign_in/SignInPasswordStartDelegateDispatcherTests.swift +++ b/MSAL/test/unit/native_auth/public/delegate/sign_in/SignInPasswordStartDelegateDispatcherTests.swift @@ -51,7 +51,7 @@ final class SignInPasswordStartDelegateDispatcherTests: XCTestCase { let expectedState = SignInCodeRequiredState(scopes: [], controller: controllerFactoryMock.signInController, continuationToken: "continuationToken", correlationId: correlationId) let expectedSentTo = "user@contoso.com" - let expectedChannelTargetType = MSALNativeAuthChannelType.email + let expectedChannelTargetType = MSALNativeAuthChannelType(value: "email") let expectedCodeLength = 4 await sut.dispatchSignInCodeRequired( @@ -86,7 +86,7 @@ final class SignInPasswordStartDelegateDispatcherTests: XCTestCase { let expectedState = SignInCodeRequiredState(scopes: [], controller: controllerFactoryMock.signInController, continuationToken: "continuationToken", correlationId: correlationId) let expectedSentTo = "user@contoso.com" - let expectedChannelTargetType = MSALNativeAuthChannelType.email + let expectedChannelTargetType = MSALNativeAuthChannelType(value: "email") let expectedCodeLength = 4 await sut.dispatchSignInCodeRequired( diff --git a/MSAL/test/unit/native_auth/public/delegate/sign_in/SignInResendCodeDelegateDispatcherTests.swift b/MSAL/test/unit/native_auth/public/delegate/sign_in/SignInResendCodeDelegateDispatcherTests.swift index 7acea4d591..c43a9d1e26 100644 --- a/MSAL/test/unit/native_auth/public/delegate/sign_in/SignInResendCodeDelegateDispatcherTests.swift +++ b/MSAL/test/unit/native_auth/public/delegate/sign_in/SignInResendCodeDelegateDispatcherTests.swift @@ -42,7 +42,7 @@ final class SignInResendCodeDelegateDispatcherTests: XCTestCase { func test_dispatchSignInResendCodeCodeRequired_whenDelegateMethodsAreImplemented() async { let expectedState = SignInCodeRequiredState(scopes: [], controller: controllerFactoryMock.signInController, continuationToken: "continuationToken", correlationId: correlationId) let expectedSentTo = "user@contoso.com" - let expectedChannelTargetType = MSALNativeAuthChannelType.email + let expectedChannelTargetType = MSALNativeAuthChannelType(value: "email") let expectedCodeLength = 4 let delegate = SignInResendCodeDelegateSpy(expectation: delegateExp, expectedSentTo: expectedSentTo, expectedChannelTargetType: expectedChannelTargetType, expectedCodeLength: expectedCodeLength) @@ -82,7 +82,7 @@ final class SignInResendCodeDelegateDispatcherTests: XCTestCase { let expectedState = SignInCodeRequiredState(scopes: [], controller: controllerFactoryMock.signInController, continuationToken: "continuationToken", correlationId: correlationId) let expectedSentTo = "user@contoso.com" - let expectedChannelTargetType = MSALNativeAuthChannelType.email + let expectedChannelTargetType = MSALNativeAuthChannelType(value: "email") let expectedCodeLength = 4 await sut.dispatchSignInResendCodeCodeRequired( diff --git a/MSAL/test/unit/native_auth/public/delegate/sign_in/SignInStartDelegateDispatcherTests.swift b/MSAL/test/unit/native_auth/public/delegate/sign_in/SignInStartDelegateDispatcherTests.swift index 12bd00da63..c3e6a04be4 100644 --- a/MSAL/test/unit/native_auth/public/delegate/sign_in/SignInStartDelegateDispatcherTests.swift +++ b/MSAL/test/unit/native_auth/public/delegate/sign_in/SignInStartDelegateDispatcherTests.swift @@ -41,7 +41,7 @@ final class SignInStartDelegateDispatcherTests: XCTestCase { func test_dispatchSignInCodeRequired_whenDelegateMethodsAreImplemented() async { let expectedState = SignInCodeRequiredState(scopes: [], controller: controllerFactoryMock.signInController, continuationToken: "continuationToken", correlationId: correlationId) let expectedSentTo = "user@contoso.com" - let expectedChannelTargetType = MSALNativeAuthChannelType.email + let expectedChannelTargetType = MSALNativeAuthChannelType(value: "email") let expectedCodeLength = 4 let delegate = SignInCodeStartDelegateSpy(expectation: delegateExp, expectedSentTo: expectedSentTo, expectedChannelTargetType: expectedChannelTargetType, expectedCodeLength: expectedCodeLength) @@ -85,7 +85,7 @@ final class SignInStartDelegateDispatcherTests: XCTestCase { let expectedState = SignInCodeRequiredState(scopes: [], controller: controllerFactoryMock.signInController, continuationToken: "continuationToken", correlationId: correlationId) let expectedSentTo = "user@contoso.com" - let expectedChannelTargetType = MSALNativeAuthChannelType.email + let expectedChannelTargetType = MSALNativeAuthChannelType(value: "email") let expectedCodeLength = 4 await sut.dispatchSignInCodeRequired( diff --git a/MSAL/test/unit/native_auth/public/delegate/sign_up/SignUpPasswordStartDelegateDispatcherTests.swift b/MSAL/test/unit/native_auth/public/delegate/sign_up/SignUpPasswordStartDelegateDispatcherTests.swift index 71c3e9046a..788368e9d4 100644 --- a/MSAL/test/unit/native_auth/public/delegate/sign_up/SignUpPasswordStartDelegateDispatcherTests.swift +++ b/MSAL/test/unit/native_auth/public/delegate/sign_up/SignUpPasswordStartDelegateDispatcherTests.swift @@ -51,7 +51,7 @@ final class SignUpPasswordStartDelegateDispatcherTests: XCTestCase { let expectedState = SignUpCodeRequiredState(controller: controllerFactoryMock.signUpController, username: "", continuationToken: "continuationToken", correlationId: correlationId) let expectedSentTo = "user@contoso.com" - let expectedChannelTargetType = MSALNativeAuthChannelType.email + let expectedChannelTargetType = MSALNativeAuthChannelType(value: "email") let expectedCodeLength = 4 await sut.dispatchSignUpCodeRequired( @@ -85,7 +85,7 @@ final class SignUpPasswordStartDelegateDispatcherTests: XCTestCase { let expectedState = SignUpCodeRequiredState(controller: controllerFactoryMock.signUpController, username: "", continuationToken: "continuationToken", correlationId: correlationId) let expectedSentTo = "user@contoso.com" - let expectedChannelTargetType = MSALNativeAuthChannelType.email + let expectedChannelTargetType = MSALNativeAuthChannelType(value: "email") let expectedCodeLength = 4 await sut.dispatchSignUpCodeRequired( diff --git a/MSAL/test/unit/native_auth/public/delegate/sign_up/SignUpResendCodeDelegateDispatcherTests.swift b/MSAL/test/unit/native_auth/public/delegate/sign_up/SignUpResendCodeDelegateDispatcherTests.swift index 94229ba5ec..1dd7634086 100644 --- a/MSAL/test/unit/native_auth/public/delegate/sign_up/SignUpResendCodeDelegateDispatcherTests.swift +++ b/MSAL/test/unit/native_auth/public/delegate/sign_up/SignUpResendCodeDelegateDispatcherTests.swift @@ -51,7 +51,7 @@ final class SignUpResendCodeDelegateDispatcherTests: XCTestCase { let expectedState = SignUpCodeRequiredState(controller: controllerFactoryMock.signUpController, username: "", continuationToken: "continuationToken", correlationId: correlationId) let expectedSentTo = "user@contoso.com" - let expectedChannelTargetType = MSALNativeAuthChannelType.email + let expectedChannelTargetType = MSALNativeAuthChannelType(value: "email") let expectedCodeLength = 4 await sut.dispatchSignUpResendCodeCodeRequired( @@ -85,7 +85,7 @@ final class SignUpResendCodeDelegateDispatcherTests: XCTestCase { let expectedState = SignUpCodeRequiredState(controller: controllerFactoryMock.signUpController, username: "", continuationToken: "continuationToken", correlationId: correlationId) let expectedSentTo = "user@contoso.com" - let expectedChannelTargetType = MSALNativeAuthChannelType.email + let expectedChannelTargetType = MSALNativeAuthChannelType(value: "email") let expectedCodeLength = 4 await sut.dispatchSignUpResendCodeCodeRequired( diff --git a/MSAL/test/unit/native_auth/public/delegate/sign_up/SignUpStartDelegateDispatcherTests.swift b/MSAL/test/unit/native_auth/public/delegate/sign_up/SignUpStartDelegateDispatcherTests.swift index 5357f1f311..939810b048 100644 --- a/MSAL/test/unit/native_auth/public/delegate/sign_up/SignUpStartDelegateDispatcherTests.swift +++ b/MSAL/test/unit/native_auth/public/delegate/sign_up/SignUpStartDelegateDispatcherTests.swift @@ -51,7 +51,7 @@ final class SignUpStartDelegateDispatcherTests: XCTestCase { let expectedState = SignUpCodeRequiredState(controller: controllerFactoryMock.signUpController, username: "", continuationToken: "continuationToken", correlationId: correlationId) let expectedSentTo = "user@contoso.com" - let expectedChannelTargetType = MSALNativeAuthChannelType.email + let expectedChannelTargetType = MSALNativeAuthChannelType(value: "email") let expectedCodeLength = 4 await sut.dispatchSignUpCodeRequired( @@ -85,7 +85,7 @@ final class SignUpStartDelegateDispatcherTests: XCTestCase { let expectedState = SignUpCodeRequiredState(controller: controllerFactoryMock.signUpController, username: "", continuationToken: "continuationToken", correlationId: correlationId) let expectedSentTo = "user@contoso.com" - let expectedChannelTargetType = MSALNativeAuthChannelType.email + let expectedChannelTargetType = MSALNativeAuthChannelType(value: "email") let expectedCodeLength = 4 await sut.dispatchSignUpCodeRequired( diff --git a/MSAL/test/unit/native_auth/public/error/MFAGetAuthMethodsErrorTests.swift b/MSAL/test/unit/native_auth/public/error/MFAGetAuthMethodsErrorTests.swift new file mode 100644 index 0000000000..f35b93d366 --- /dev/null +++ b/MSAL/test/unit/native_auth/public/error/MFAGetAuthMethodsErrorTests.swift @@ -0,0 +1,64 @@ +// +// 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 XCTest +@testable import MSAL + +final class MFAGetAuthMethodsErrorTests: XCTestCase { + + private var sut: MFAGetAuthMethodsError! + + func test_totalCases() { + XCTAssertEqual(MFAGetAuthMethodsError.ErrorType.allCases.count, 2) + } + + func test_customErrorDescription() { + let expectedMessage = "Custom error message" + sut = .init(type: .generalError, message: expectedMessage, correlationId: .init()) + XCTAssertEqual(sut.errorDescription, expectedMessage) + } + + func test_defaultErrorDescription() { + let sut: [MFAGetAuthMethodsError] = [ + .init(type: .browserRequired, correlationId: .init()), + .init(type: .generalError, correlationId: .init()) + ] + + let expectedDescriptions = [ + MSALNativeAuthErrorMessage.browserRequired, + MSALNativeAuthErrorMessage.generalError + ] + + let errorDescriptions = sut.map { $0.errorDescription } + + zip(errorDescriptions, expectedDescriptions).forEach { + XCTAssertEqual($0, $1) + } + } + + func test_isBrowserRequired() { + sut = .init(type: .browserRequired, correlationId: .init()) + XCTAssertTrue(sut.isBrowserRequired) + } +} diff --git a/MSAL/test/unit/native_auth/public/error/MFARequestChallengeErrorTests.swift b/MSAL/test/unit/native_auth/public/error/MFARequestChallengeErrorTests.swift new file mode 100644 index 0000000000..76a4591c22 --- /dev/null +++ b/MSAL/test/unit/native_auth/public/error/MFARequestChallengeErrorTests.swift @@ -0,0 +1,64 @@ +// +// 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 XCTest +@testable import MSAL + +final class MFARequestChallengeErrorTests: XCTestCase { + + private var sut: MFARequestChallengeError! + + func test_totalCases() { + XCTAssertEqual(MFARequestChallengeError.ErrorType.allCases.count, 2) + } + + func test_customErrorDescription() { + let expectedMessage = "Custom error message" + sut = .init(type: .generalError, message: expectedMessage, correlationId: .init()) + XCTAssertEqual(sut.errorDescription, expectedMessage) + } + + func test_defaultErrorDescription() { + let sut: [MFAGetAuthMethodsError] = [ + .init(type: .browserRequired, correlationId: .init()), + .init(type: .generalError, correlationId: .init()) + ] + + let expectedDescriptions = [ + MSALNativeAuthErrorMessage.browserRequired, + MSALNativeAuthErrorMessage.generalError + ] + + let errorDescriptions = sut.map { $0.errorDescription } + + zip(errorDescriptions, expectedDescriptions).forEach { + XCTAssertEqual($0, $1) + } + } + + func test_isBrowserRequired() { + sut = .init(type: .browserRequired, correlationId: .init()) + XCTAssertTrue(sut.isBrowserRequired) + } +} diff --git a/MSAL/test/unit/native_auth/public/error/MFASubmitChallengeErrorTests.swift b/MSAL/test/unit/native_auth/public/error/MFASubmitChallengeErrorTests.swift new file mode 100644 index 0000000000..6dbb6223d0 --- /dev/null +++ b/MSAL/test/unit/native_auth/public/error/MFASubmitChallengeErrorTests.swift @@ -0,0 +1,64 @@ +// +// 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 XCTest +@testable import MSAL + +final class MFASubmitChallengeErrorTests: XCTestCase { + + private var sut: MFASubmitChallengeError! + + func test_totalCases() { + XCTAssertEqual(MFASubmitChallengeError.ErrorType.allCases.count, 2) + } + + func test_customErrorDescription() { + let expectedMessage = "Custom error message" + sut = .init(type: .generalError, message: expectedMessage, correlationId: .init()) + XCTAssertEqual(sut.errorDescription, expectedMessage) + } + + func test_defaultErrorDescription() { + let sut: [MFASubmitChallengeError] = [ + .init(type: .invalidChallenge, correlationId: .init()), + .init(type: .generalError, correlationId: .init()) + ] + + let expectedDescriptions = [ + MSALNativeAuthErrorMessage.invalidChallenge, + MSALNativeAuthErrorMessage.generalError + ] + + let errorDescriptions = sut.map { $0.errorDescription } + + zip(errorDescriptions, expectedDescriptions).forEach { + XCTAssertEqual($0, $1) + } + } + + func test_isInvalidChallenge() { + sut = .init(type: .invalidChallenge, correlationId: .init()) + XCTAssertTrue(sut.isInvalidChallenge) + } +} diff --git a/MSAL/test/unit/native_auth/public/state_machine/mfa/AwaitingMFAStateTests.swift b/MSAL/test/unit/native_auth/public/state_machine/mfa/AwaitingMFAStateTests.swift new file mode 100644 index 0000000000..ee7cfd700a --- /dev/null +++ b/MSAL/test/unit/native_auth/public/state_machine/mfa/AwaitingMFAStateTests.swift @@ -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 XCTest +@testable import MSAL + +final class AwaitingMFAStateTests: XCTestCase { + + private var sut: AwaitingMFAState! + private var controller: MSALNativeAuthSignInControllerMock! + private var correlationId: UUID = UUID() + + override func setUp() { + super.setUp() + + controller = .init() + sut = .init(controller: controller, scopes: [], continuationToken: "continuationToken", correlationId: correlationId) + } + + // MARK: - Delegates + + // Request challenge + + func test_requestChallenge_delegate_withError_shouldReturnCorrectError() { + let exp = expectation(description: "mfa state") + + let expectedError = MFARequestChallengeError(type: .generalError, message: "test error", correlationId: correlationId) + let expectedState = MFARequiredState(controller: controller, scopes: [], continuationToken: "continuationToken", correlationId: correlationId) + + let expectedResult: MFARequestChallengeResult = .error( + error: expectedError, + newState: expectedState + ) + controller.requestChallengeResponse = .init(expectedResult, correlationId: correlationId) + + let delegate = MFARequestChallengeDelegateSpy(expectation: exp, expectedError: expectedError) + + sut.requestChallenge(delegate: delegate) + wait(for: [exp], timeout: 1) + + XCTAssertNil(delegate.newMFARequiredState) + } + + func test_requestChallenge_delegateVerificationRequired_shouldReturnCorrectResult() { + let exp = expectation(description: "mfa states") + let exp2 = expectation(description: "expectation Telemetry") + let expectedState = MFARequiredState(controller: controller, scopes: [], continuationToken: "continuationToken 2", correlationId: correlationId) + let expectedCodeLength = 1 + let expectedChannel = MSALNativeAuthChannelType(value: "email") + let expectedSentTo = "sentTo" + + let expectedResult: MFARequestChallengeResult = .verificationRequired( + sentTo: expectedSentTo, + channelTargetType: expectedChannel, + codeLength: expectedCodeLength, + newState: expectedState + ) + controller.requestChallengeResponse = .init(expectedResult, correlationId: correlationId, telemetryUpdate: { _ in + exp2.fulfill() + }) + + let delegate = MFARequestChallengeDelegateSpy(expectation: exp) + + sut.requestChallenge(delegate: delegate) + wait(for: [exp, exp2], timeout: 1.0) + + XCTAssertEqual(delegate.newSentTo, expectedSentTo) + XCTAssertEqual(delegate.newChannelTargetType, expectedChannel) + XCTAssertEqual(delegate.newCodeLength, expectedCodeLength) + XCTAssertEqual(delegate.newMFARequiredState, expectedState) + } + + func test_requestChallenge_delegateSelectionRequired_shouldReturnCorrectResult() { + let exp = expectation(description: "mfa states") + let exp2 = expectation(description: "expectation Telemetry") + let expectedState = MFARequiredState(controller: controller, scopes: [], continuationToken: "continuationToken 2", correlationId: correlationId) + let expectedAuthMethods = [MSALAuthMethod(id: "1", challengeType: "oob", loginHint: "hint", channelTargetType: MSALNativeAuthChannelType(value: "email"))] + + let expectedResult: MFARequestChallengeResult = .selectionRequired(authMethods: expectedAuthMethods, newState: expectedState) + controller.requestChallengeResponse = .init(expectedResult, correlationId: correlationId, telemetryUpdate: { _ in + exp2.fulfill() + }) + + let delegate = MFARequestChallengeDelegateSpy(expectation: exp) + + sut.requestChallenge(delegate: delegate) + wait(for: [exp, exp2], timeout: 1.0) + + XCTAssertEqual(delegate.newAuthMethods, expectedAuthMethods) + XCTAssertEqual(delegate.newMFARequiredState, expectedState) + } +} diff --git a/MSAL/test/unit/native_auth/public/state_machine/mfa/MFARequiredStateTests.swift b/MSAL/test/unit/native_auth/public/state_machine/mfa/MFARequiredStateTests.swift new file mode 100644 index 0000000000..2ddcb1a62a --- /dev/null +++ b/MSAL/test/unit/native_auth/public/state_machine/mfa/MFARequiredStateTests.swift @@ -0,0 +1,186 @@ +// +// 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 XCTest +@testable import MSAL + +final class MFARequiredStateTests: XCTestCase { + + private var sut: MFARequiredState! + private var controller: MSALNativeAuthSignInControllerMock! + private var correlationId: UUID = UUID() + + override func setUp() { + super.setUp() + + controller = .init() + sut = .init(controller: controller, scopes: [], continuationToken: "continuationToken", correlationId: correlationId) + } + + // MARK: - Delegates + + // Request challenge + + func test_requestChallengeWithAuthMethod_delegateVerificationRequired_shouldReturnCorrectResult() { + let exp = expectation(description: "mfa states") + let exp2 = expectation(description: "expectation Telemetry") + let expectedState = MFARequiredState(controller: controller, scopes: [], continuationToken: "continuationToken 2", correlationId: correlationId) + let expectedCodeLength = 1 + let expectedChannel = MSALNativeAuthChannelType(value: "email") + let expectedSentTo = "sentTo" + let expectedAuthMethod = MSALAuthMethod(id: "1", challengeType: "oob", loginHint: "hint", channelTargetType: MSALNativeAuthChannelType(value: "email")) + + let expectedResult: MFARequestChallengeResult = .verificationRequired( + sentTo: expectedSentTo, + channelTargetType: expectedChannel, + codeLength: expectedCodeLength, + newState: expectedState + ) + controller.requestChallengeResponse = .init(expectedResult, correlationId: correlationId, telemetryUpdate: { _ in + exp2.fulfill() + }) + + let delegate = MFARequestChallengeDelegateSpy(expectation: exp) + + sut.requestChallenge(authMethod: expectedAuthMethod, delegate: delegate) + wait(for: [exp, exp2], timeout: 1.0) + + XCTAssertEqual(delegate.newSentTo, expectedSentTo) + XCTAssertEqual(delegate.newChannelTargetType, expectedChannel) + XCTAssertEqual(delegate.newCodeLength, expectedCodeLength) + XCTAssertEqual(delegate.newMFARequiredState, expectedState) + } + + func test_requestChallengeWithAuthMethod_delegateSelectionRequired_shouldReturnCorrectResult() { + let exp = expectation(description: "mfa states") + let exp2 = expectation(description: "expectation Telemetry") + let expectedState = MFARequiredState(controller: controller, scopes: [], continuationToken: "continuationToken 2", correlationId: correlationId) + let expectedAuthMethod = MSALAuthMethod(id: "1", challengeType: "oob", loginHint: "hint", channelTargetType: MSALNativeAuthChannelType(value: "email")) + let expectedAuthMethods = [expectedAuthMethod] + + let expectedResult: MFARequestChallengeResult = .selectionRequired(authMethods: expectedAuthMethods, newState: expectedState) + controller.requestChallengeResponse = .init(expectedResult, correlationId: correlationId, telemetryUpdate: { _ in + exp2.fulfill() + }) + + let delegate = MFARequestChallengeDelegateSpy(expectation: exp) + + sut.requestChallenge(authMethod: expectedAuthMethod,delegate: delegate) + wait(for: [exp, exp2], timeout: 1.0) + + XCTAssertEqual(delegate.newAuthMethods, expectedAuthMethods) + XCTAssertEqual(delegate.newMFARequiredState, expectedState) + } + + // Get auth methods + + func test_getAuthMethods_delegate_withError_shouldReturnCorrectError() { + let exp = expectation(description: "mfa state") + + let expectedError = MFAGetAuthMethodsError(type: .generalError, message: "test error", correlationId: correlationId) + let expectedState = MFARequiredState(controller: controller, scopes: [], continuationToken: "continuationToken", correlationId: correlationId) + + let expectedResult: MFAGetAuthMethodsResult = .error( + error: expectedError, + newState: expectedState + ) + controller.getAuthMethodsResponse = .init(expectedResult, correlationId: correlationId) + + let delegate = MFAGetAuthMethodsDelegateSpy(expectation: exp, expectedError: expectedError) + + sut.getAuthMethods(delegate: delegate) + wait(for: [exp], timeout: 1) + + XCTAssertEqual(delegate.newMFARequiredState, expectedState) + } + + func test_getAuthMethods_delegateSelectionRequired_shouldReturnCorrectResponse() { + let exp = expectation(description: "mfa states") + let exp2 = expectation(description: "expectation Telemetry") + let expectedState = MFARequiredState(controller: controller, scopes: [], continuationToken: "continuationToken 2", correlationId: correlationId) + let expectedAuthMethod = MSALAuthMethod(id: "1", challengeType: "oob", loginHint: "hint", channelTargetType: MSALNativeAuthChannelType(value: "email")) + let expectedAuthMethods = [expectedAuthMethod] + + let expectedResult: MFAGetAuthMethodsResult = .selectionRequired(authMethods: expectedAuthMethods, newState: expectedState) + controller.getAuthMethodsResponse = .init(expectedResult, correlationId: correlationId, telemetryUpdate: { _ in + exp2.fulfill() + }) + + let delegate = MFAGetAuthMethodsDelegateSpy(expectation: exp) + + sut.getAuthMethods(delegate: delegate) + wait(for: [exp, exp2], timeout: 1.0) + + XCTAssertEqual(delegate.newAuthMethods, expectedAuthMethods) + XCTAssertEqual(delegate.newMFARequiredState, expectedState) + } + + // submit challenge + + func test_submitChallenge_delegate_withError_shouldReturnCorrectError() { + let exp = expectation(description: "mfa state") + + let expectedError = MFASubmitChallengeError(type: .invalidChallenge, message: "test error", correlationId: correlationId) + let expectedState = MFARequiredState(controller: controller, scopes: [], continuationToken: "continuationToken", correlationId: correlationId) + + let expectedResult: MFASubmitChallengeResult = .error( + error: expectedError, + newState: expectedState + ) + controller.submitChallengeResponse = .init(expectedResult, correlationId: correlationId) + + let delegate = MFASubmitChallengeDelegateSpy(expectation: exp, expectedResult: nil, expectedError: expectedError) + + sut.submitChallenge(challenge: "challenge", delegate: delegate) + wait(for: [exp], timeout: 1) + + XCTAssertEqual(delegate.newMFARequiredState, expectedState) + } + + func test_submitChallenge_delegateComplete_shouldReturnCorrectResponse() { + let exp = expectation(description: "mfa states") + let exp2 = expectation(description: "expectation Telemetry") + let expectedUserResult = MSALNativeAuthUserAccountResultStub.result + + let expectedResult: MFASubmitChallengeResult = .completed(expectedUserResult) + controller.submitChallengeResponse = .init(expectedResult, correlationId: correlationId, telemetryUpdate: { _ in + exp2.fulfill() + }) + + let delegate = MFASubmitChallengeDelegateSpy(expectation: exp, expectedResult: expectedUserResult, expectedError: nil) + + sut.submitChallenge(challenge: "1234", delegate: delegate) + wait(for: [exp, exp2], timeout: 1.0) + } + + func test_submitInvalidChallenge_shouldReturnCorrectResponse() { + let exp = expectation(description: "mfa states") + let expectedError = MFASubmitChallengeError(type: .invalidChallenge, correlationId: correlationId) + + let delegate = MFASubmitChallengeDelegateSpy(expectation: exp, expectedResult: nil, expectedError: expectedError) + + sut.submitChallenge(challenge: "", delegate: delegate) + wait(for: [exp], timeout: 1.0) + } +} diff --git a/MSAL/test/unit/native_auth/public/state_machine/reset_password/ResetPasswordCodeSentStateTests.swift b/MSAL/test/unit/native_auth/public/state_machine/reset_password/ResetPasswordCodeSentStateTests.swift index 106a1079ab..89c6a4ba38 100644 --- a/MSAL/test/unit/native_auth/public/state_machine/reset_password/ResetPasswordCodeSentStateTests.swift +++ b/MSAL/test/unit/native_auth/public/state_machine/reset_password/ResetPasswordCodeSentStateTests.swift @@ -69,7 +69,7 @@ final class ResetPasswordCodeRequiredStateTests: XCTestCase { let expectedResult: ResetPasswordResendCodeResult = .codeRequired( newState: expectedState, sentTo: "sentTo", - channelTargetType: .email, + channelTargetType: MSALNativeAuthChannelType(value: "email"), codeLength: 1 ) controller.resendCodeResponse = .init(expectedResult, correlationId: correlationId, telemetryUpdate: { _ in @@ -83,7 +83,7 @@ final class ResetPasswordCodeRequiredStateTests: XCTestCase { XCTAssertEqual(delegate.newState?.continuationToken, expectedState.continuationToken) XCTAssertEqual(delegate.sentTo, "sentTo") - XCTAssertEqual(delegate.channelTargetType, .email) + XCTAssertEqual(delegate.channelTargetType?.isEmailType, true) XCTAssertEqual(delegate.codeLength, 1) } @@ -95,7 +95,7 @@ final class ResetPasswordCodeRequiredStateTests: XCTestCase { let expectedResult: ResetPasswordResendCodeResult = .codeRequired( newState: expectedState, sentTo: "sentTo", - channelTargetType: .email, + channelTargetType: MSALNativeAuthChannelType(value: "email"), codeLength: 1 ) controller.resendCodeResponse = .init(expectedResult, correlationId: correlationId, telemetryUpdate: { _ in diff --git a/MSAL/test/unit/native_auth/public/state_machine/sign_in/SignInCodeRequiredStateTests.swift b/MSAL/test/unit/native_auth/public/state_machine/sign_in/SignInCodeRequiredStateTests.swift index a29aa0228d..6cbc70655c 100644 --- a/MSAL/test/unit/native_auth/public/state_machine/sign_in/SignInCodeRequiredStateTests.swift +++ b/MSAL/test/unit/native_auth/public/state_machine/sign_in/SignInCodeRequiredStateTests.swift @@ -72,14 +72,14 @@ final class SignInCodeRequiredStateTests: XCTestCase { let expectedResult: SignInResendCodeResult = .codeRequired( newState: expectedState, sentTo: "sentTo", - channelTargetType: .email, + channelTargetType: MSALNativeAuthChannelType(value: "email"), codeLength: 1 ) controller.resendCodeResult = .init(expectedResult, correlationId: correlationId, telemetryUpdate: { _ in exp2.fulfill() }) - let delegate = SignInResendCodeDelegateSpy(expectation: exp, expectedSentTo: "sentTo", expectedChannelTargetType: .email, expectedCodeLength: 1) + let delegate = SignInResendCodeDelegateSpy(expectation: exp, expectedSentTo: "sentTo", expectedChannelTargetType: MSALNativeAuthChannelType(value: "email"), expectedCodeLength: 1) sut.resendCode(delegate: delegate) wait(for: [exp, exp2]) @@ -94,7 +94,7 @@ final class SignInCodeRequiredStateTests: XCTestCase { let expectedResult: SignInResendCodeResult = .codeRequired( newState: expectedState, sentTo: "sentTo", - channelTargetType: .email, + channelTargetType: MSALNativeAuthChannelType(value: "email"), codeLength: 1 ) controller.resendCodeResult = .init(expectedResult, correlationId: correlationId, telemetryUpdate: { _ in diff --git a/MSAL/test/unit/native_auth/public/state_machine/sign_up/SignUpCodeSentStateTests.swift b/MSAL/test/unit/native_auth/public/state_machine/sign_up/SignUpCodeSentStateTests.swift index ded37afd55..8add408dd9 100644 --- a/MSAL/test/unit/native_auth/public/state_machine/sign_up/SignUpCodeSentStateTests.swift +++ b/MSAL/test/unit/native_auth/public/state_machine/sign_up/SignUpCodeSentStateTests.swift @@ -67,7 +67,7 @@ final class SignUpCodeRequiredStateTests: XCTestCase { let expectedResult: SignUpResendCodeResult = .codeRequired( newState: expectedState, sentTo: "sentTo", - channelTargetType: .email, + channelTargetType: MSALNativeAuthChannelType(value: "email"), codeLength: 1 ) controller.resendCodeResult = .init(expectedResult, correlationId: correlationId, telemetryUpdate: { _ in @@ -81,7 +81,7 @@ final class SignUpCodeRequiredStateTests: XCTestCase { XCTAssertEqual(delegate.newState?.continuationToken, expectedState.continuationToken) XCTAssertEqual(delegate.sentTo, "sentTo") - XCTAssertEqual(delegate.channelTargetType, .email) + XCTAssertEqual(delegate.channelTargetType?.isEmailType, true) XCTAssertEqual(delegate.codeLength, 1) } @@ -93,7 +93,7 @@ final class SignUpCodeRequiredStateTests: XCTestCase { let expectedResult: SignUpResendCodeResult = .codeRequired( newState: expectedState, sentTo: "sentTo", - channelTargetType: .email, + channelTargetType: MSALNativeAuthChannelType(value: "email"), codeLength: 1 ) controller.resendCodeResult = .init(expectedResult, correlationId: correlationId, telemetryUpdate: { _ in From e87372491d87f278190f4ea15c2ff8b9db32366d Mon Sep 17 00:00:00 2001 From: Silviu Petrescu <111577419+spetrescu84@users.noreply.github.com> Date: Thu, 3 Oct 2024 17:42:18 +0100 Subject: [PATCH 28/39] [iOS SDK] Update current user account result with latest account and id token after refresh (#2346) * Update the current user account result data * Made user account get access token testable Added unit and e2e tests * PR comments * Fixed test around scopes after adding bundle id for IOS to client * PR comments * Added ConfigTested to validate config after being created Made createRetrieveAccessTokenError and adjusted tests * Reverted issue around UUID --- MSAL/MSAL.xcodeproj/project.pbxproj | 86 ++++ .../MSALNativeAuthSilentTokenProvider.swift | 47 +++ .../MSALNativeAuthSilentTokenProviding.swift | 57 +++ ...tiveAuthSilentTokenProviderBuildable.swift | 28 ++ ...NativeAuthSilentTokenProviderFactory.swift | 30 ++ ...NativeAuthUserAccountResult+Internal.swift | 15 +- .../MSALNativeAuthUserAccountResult.swift | 9 +- .../CredentialsDelegateSpies.swift | 53 +++ ...ALNativeAuthUserAccountEndToEndTests.swift | 128 ++++++ ...lentTokenProviderFactoryConfigTester.swift | 38 ++ ...veAuthSilentTokenProviderFactoryMock.swift | 34 ++ ...SALNativeAuthSilentTokenProviderMock.swift | 41 ++ ...MSALNativeAuthUserAccountResultTests.swift | 366 +++++++++++++----- 13 files changed, 819 insertions(+), 113 deletions(-) create mode 100644 MSAL/src/native_auth/client_application_wrapper/silent_token/MSALNativeAuthSilentTokenProvider.swift create mode 100644 MSAL/src/native_auth/client_application_wrapper/silent_token/MSALNativeAuthSilentTokenProviding.swift create mode 100644 MSAL/src/native_auth/client_application_wrapper/silent_token/factory/MSALNativeAuthSilentTokenProviderBuildable.swift create mode 100644 MSAL/src/native_auth/client_application_wrapper/silent_token/factory/MSALNativeAuthSilentTokenProviderFactory.swift create mode 100644 MSAL/test/integration/native_auth/end_to_end/credentials/CredentialsDelegateSpies.swift create mode 100644 MSAL/test/integration/native_auth/end_to_end/credentials/MSALNativeAuthUserAccountEndToEndTests.swift create mode 100644 MSAL/test/unit/native_auth/mock/MSALNativeAuthSilentTokenProviderFactoryConfigTester.swift create mode 100644 MSAL/test/unit/native_auth/mock/MSALNativeAuthSilentTokenProviderFactoryMock.swift create mode 100644 MSAL/test/unit/native_auth/mock/MSALNativeAuthSilentTokenProviderMock.swift diff --git a/MSAL/MSAL.xcodeproj/project.pbxproj b/MSAL/MSAL.xcodeproj/project.pbxproj index b2dd6a3437..b11dcb919a 100644 --- a/MSAL/MSAL.xcodeproj/project.pbxproj +++ b/MSAL/MSAL.xcodeproj/project.pbxproj @@ -1006,6 +1006,8 @@ DE14D75D29897D8000F37BEF /* MSALNativeAuthTelemetryApiId.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEF9D995296EC35A006CB384 /* MSALNativeAuthTelemetryApiId.swift */; }; DE14D75E29897D9500F37BEF /* MSALNativeAuthUrlRequestSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2ACA49B2953576C00E98964 /* MSALNativeAuthUrlRequestSerializer.swift */; }; DE14D76129898CF900F37BEF /* MSALNativeAuthTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE14D76029898CF900F37BEF /* MSALNativeAuthTestCase.swift */; }; + DE1560E32CAE8F3F00C85E51 /* MSALNativeAuthSilentTokenProviderFactoryConfigTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE1560E22CAE8F3E00C85E51 /* MSALNativeAuthSilentTokenProviderFactoryConfigTester.swift */; }; + DE1560E42CAE8F3F00C85E51 /* MSALNativeAuthSilentTokenProviderFactoryConfigTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE1560E22CAE8F3E00C85E51 /* MSALNativeAuthSilentTokenProviderFactoryConfigTester.swift */; }; DE1BD0D32C3C275200B0888E /* MSAL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D65A6F501E3FD32D00C69FBA /* MSAL.framework */; }; DE1BD0E02C3C27C100B0888E /* MSALNativeAuthSignUpStartIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E243F6AD29D4427E00DAC60F /* MSALNativeAuthSignUpStartIntegrationTests.swift */; }; DE1BD0E12C3C27C300B0888E /* MSALNativeAuthSignUpChallengeIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E23E955E29D4B9F7001DC59C /* MSALNativeAuthSignUpChallengeIntegrationTests.swift */; }; @@ -1421,6 +1423,22 @@ DEF9D989296EC26A006CB384 /* MSALNativeAuthCurrentRequestTelemetry.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEF9D988296EC26A006CB384 /* MSALNativeAuthCurrentRequestTelemetry.swift */; }; DEF9D999296EC848006CB384 /* MSALNativeAuthOperationTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEF9D998296EC848006CB384 /* MSALNativeAuthOperationTypes.swift */; }; DEF9D99F296F08CE006CB384 /* MSALNativeAuthTelemetryProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEF9D99E296F08CE006CB384 /* MSALNativeAuthTelemetryProvider.swift */; }; + DEFE87602CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProviderFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFE875A2CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProviderFactory.swift */; }; + DEFE87612CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProviderBuildable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFE87592CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProviderBuildable.swift */; }; + DEFE87622CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFE875C2CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProvider.swift */; }; + DEFE87632CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFE875D2CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProviding.swift */; }; + DEFE87642CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProviderFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFE875A2CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProviderFactory.swift */; }; + DEFE87652CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProviderBuildable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFE87592CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProviderBuildable.swift */; }; + DEFE87662CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFE875C2CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProvider.swift */; }; + DEFE87672CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFE875D2CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProviding.swift */; }; + DEFE876A2CA6BC3A009D11DC /* MSALNativeAuthSilentTokenProviderMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFE87692CA6BC3A009D11DC /* MSALNativeAuthSilentTokenProviderMock.swift */; }; + DEFE876B2CA6BC3A009D11DC /* MSALNativeAuthSilentTokenProviderFactoryMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFE87682CA6BC3A009D11DC /* MSALNativeAuthSilentTokenProviderFactoryMock.swift */; }; + DEFE876C2CA6BC3A009D11DC /* MSALNativeAuthSilentTokenProviderMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFE87692CA6BC3A009D11DC /* MSALNativeAuthSilentTokenProviderMock.swift */; }; + DEFE876D2CA6BC3A009D11DC /* MSALNativeAuthSilentTokenProviderFactoryMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFE87682CA6BC3A009D11DC /* MSALNativeAuthSilentTokenProviderFactoryMock.swift */; }; + DEFE87712CA6BC91009D11DC /* CredentialsDelegateSpies.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFE876E2CA6BC91009D11DC /* CredentialsDelegateSpies.swift */; }; + DEFE87722CA6BC91009D11DC /* MSALNativeAuthUserAccountEndToEndTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFE876F2CA6BC91009D11DC /* MSALNativeAuthUserAccountEndToEndTests.swift */; }; + DEFE87732CA6BC91009D11DC /* CredentialsDelegateSpies.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFE876E2CA6BC91009D11DC /* CredentialsDelegateSpies.swift */; }; + DEFE87742CA6BC91009D11DC /* MSALNativeAuthUserAccountEndToEndTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEFE876F2CA6BC91009D11DC /* MSALNativeAuthUserAccountEndToEndTests.swift */; }; E2025CC92B2A182200E32871 /* MSALNativeAuthSubErrorCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2025CC82B2A182200E32871 /* MSALNativeAuthSubErrorCode.swift */; }; E2025D202B2B8EEA00E32871 /* MSALNativeAuthSubErrorCodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2025D1F2B2B8EEA00E32871 /* MSALNativeAuthSubErrorCodeTests.swift */; }; E205D62E29B783FF003887BC /* MSALNativeAuthConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = E205D62D29B783FF003887BC /* MSALNativeAuthConfiguration.swift */; }; @@ -2443,6 +2461,7 @@ DE14096A2A38DE0E008E6F1E /* CredentialsDelegateSpies.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CredentialsDelegateSpies.swift; sourceTree = ""; }; DE14096C2A38DF40008E6F1E /* MSALNativeAuthCredentialsControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthCredentialsControllerTests.swift; sourceTree = ""; }; DE14D76029898CF900F37BEF /* MSALNativeAuthTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthTestCase.swift; sourceTree = ""; }; + DE1560E22CAE8F3E00C85E51 /* MSALNativeAuthSilentTokenProviderFactoryConfigTester.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthSilentTokenProviderFactoryConfigTester.swift; sourceTree = ""; }; DE1BD0CF2C3C275200B0888E /* MSAL Mac Native Auth Integration Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "MSAL Mac Native Auth Integration Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; DE1BD0F62C3C282900B0888E /* MSAL Mac Native Auth E2E Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "MSAL Mac Native Auth E2E Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; DE1BD1322C3C3E9A00B0888E /* MSAL iOS Native Auth E2E Tests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = "MSAL iOS Native Auth E2E Tests.xctestplan"; sourceTree = ""; }; @@ -2532,6 +2551,14 @@ DEFB46EC2A52BA3700DBC006 /* MSALNativeAuthSignOutEndToEndTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthSignOutEndToEndTests.swift; sourceTree = ""; }; DEFB46F12A52C11400DBC006 /* MSALNativeAuthResetPasswordEndToEndTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthResetPasswordEndToEndTests.swift; sourceTree = ""; }; DEFB46F32A52C28100DBC006 /* ResetPasswordDelegateSpies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResetPasswordDelegateSpies.swift; sourceTree = ""; }; + DEFE87592CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProviderBuildable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthSilentTokenProviderBuildable.swift; sourceTree = ""; }; + DEFE875A2CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProviderFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthSilentTokenProviderFactory.swift; sourceTree = ""; }; + DEFE875C2CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthSilentTokenProvider.swift; sourceTree = ""; }; + DEFE875D2CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProviding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthSilentTokenProviding.swift; sourceTree = ""; }; + DEFE87682CA6BC3A009D11DC /* MSALNativeAuthSilentTokenProviderFactoryMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthSilentTokenProviderFactoryMock.swift; sourceTree = ""; }; + DEFE87692CA6BC3A009D11DC /* MSALNativeAuthSilentTokenProviderMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthSilentTokenProviderMock.swift; sourceTree = ""; }; + DEFE876E2CA6BC91009D11DC /* CredentialsDelegateSpies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialsDelegateSpies.swift; sourceTree = ""; }; + DEFE876F2CA6BC91009D11DC /* MSALNativeAuthUserAccountEndToEndTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthUserAccountEndToEndTests.swift; sourceTree = ""; }; E02396F31E79E2F4004D6278 /* MSALTelemetryApiId.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSALTelemetryApiId.h; sourceTree = ""; }; E2025CC82B2A182200E32871 /* MSALNativeAuthSubErrorCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthSubErrorCode.swift; sourceTree = ""; }; E2025D1F2B2B8EEA00E32871 /* MSALNativeAuthSubErrorCodeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSALNativeAuthSubErrorCodeTests.swift; sourceTree = ""; }; @@ -2935,6 +2962,7 @@ 287F64F42981A7B800ED90BD /* mock */ = { isa = PBXGroup; children = ( + DE1560E22CAE8F3E00C85E51 /* MSALNativeAuthSilentTokenProviderFactoryConfigTester.swift */, 28A600992C7898AE00455666 /* delegate */, E25BC0822995429D00588549 /* MSALNativeAuthCacheMocks.swift */, E2F5BE9429894FCA00C67EC7 /* MSALNativeAuthConfigStubs.swift */, @@ -2952,6 +2980,8 @@ E2EBD6292A1BB7700049467A /* MSALNativeAuthSignUpResponseValidatorMock.swift */, 28FDC4AB2A38D7D200E38BE1 /* MSALNativeAuthSignInControllerMock.swift */, DE946FE42B0F713A00978493 /* MSALNativeAuthHTTPRequestMock.swift */, + DEFE87682CA6BC3A009D11DC /* MSALNativeAuthSilentTokenProviderFactoryMock.swift */, + DEFE87692CA6BC3A009D11DC /* MSALNativeAuthSilentTokenProviderMock.swift */, 9B61C91D2A27E5E200CE9E3A /* reset_password */, ); path = mock; @@ -3390,6 +3420,7 @@ 9B235DA22A3D15E400657331 /* sign_in */, DEFB46E12A52B9B800DBC006 /* reset_password */, DEFB46EB2A52BA1300DBC006 /* sign_out */, + DEFE87702CA6BC91009D11DC /* credentials */, E24CE9D82C592BA70069E2E4 /* mock */, 9B235D9E2A3CFB4300657331 /* MSALNativeAuthEndToEndBaseTestCase.swift */, 2809E8342C3C37B7009F14D7 /* MSALNativeAuthEndToEndPasswordTestCase.swift */, @@ -4446,6 +4477,42 @@ path = sign_out; sourceTree = ""; }; + DEFE875B2CA6BBE5009D11DC /* factory */ = { + isa = PBXGroup; + children = ( + DEFE87592CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProviderBuildable.swift */, + DEFE875A2CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProviderFactory.swift */, + ); + path = factory; + sourceTree = ""; + }; + DEFE875E2CA6BBE5009D11DC /* silent_token */ = { + isa = PBXGroup; + children = ( + DEFE875B2CA6BBE5009D11DC /* factory */, + DEFE875C2CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProvider.swift */, + DEFE875D2CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProviding.swift */, + ); + path = silent_token; + sourceTree = ""; + }; + DEFE875F2CA6BBE5009D11DC /* client_application_wrapper */ = { + isa = PBXGroup; + children = ( + DEFE875E2CA6BBE5009D11DC /* silent_token */, + ); + path = client_application_wrapper; + sourceTree = ""; + }; + DEFE87702CA6BC91009D11DC /* credentials */ = { + isa = PBXGroup; + children = ( + DEFE876F2CA6BC91009D11DC /* MSALNativeAuthUserAccountEndToEndTests.swift */, + DEFE876E2CA6BC91009D11DC /* CredentialsDelegateSpies.swift */, + ); + path = credentials; + sourceTree = ""; + }; E02396F51E7AFFF7004D6278 /* telemetry */ = { isa = PBXGroup; children = ( @@ -4682,6 +4749,7 @@ E206FC5E296D65DE00AF4400 /* MSALNativeAuthInternalError.swift */, 28BBB7CD2C80C5740055AF64 /* MSALNativeAuthLogMessage.swift */, E26097942948FB660060DD7C /* cache */, + DEFE875F2CA6BBE5009D11DC /* client_application_wrapper */, E205D62C29B783D6003887BC /* configuration */, 289747AE29799A6600838C80 /* input_validator */, 2811CDCF296F16DE007BA21B /* controllers */, @@ -6307,6 +6375,8 @@ 28188F622C8F48BD00CFDD05 /* MSALNativeAuthSignInWithMFAEndToEndTests.swift in Sources */, 281A0E172C21E1FB00CB30CB /* MSALNativeAuthSignInUsernameEndToEndTests.swift in Sources */, 281A0E1C2C21E3A400CB30CB /* MSALNativeAuthSignOutEndToEndTests.swift in Sources */, + DEFE87732CA6BC91009D11DC /* CredentialsDelegateSpies.swift in Sources */, + DEFE87742CA6BC91009D11DC /* MSALNativeAuthUserAccountEndToEndTests.swift in Sources */, 281A0E162C21E1F800CB30CB /* MSALNativeAuthSignInUsernameAndPasswordEndToEndTests.swift in Sources */, 281A0E142C21E1F200CB30CB /* MSALNativeAuthSignUpUsernameAndPasswordEndToEndTests.swift in Sources */, 281A0E1A2C21E20300CB30CB /* ResetPasswordDelegateSpies.swift in Sources */, @@ -6504,6 +6574,10 @@ 287708252A178DC500E371ED /* MSALNativeAuthSignInInitiateValidatedResponse.swift in Sources */, 289E44AC2C9D7A9E00F6B9D7 /* MFARequestChallengeError.swift in Sources */, DEE34F89D170B71C00BC302A /* MSALNativeAuthResetPasswordPollCompletionStatus.swift in Sources */, + DEFE87602CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProviderFactory.swift in Sources */, + DEFE87612CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProviderBuildable.swift in Sources */, + DEFE87622CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProvider.swift in Sources */, + DEFE87632CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProviding.swift in Sources */, 9BE7E3D52A1CF51500CC3A62 /* MSALNativeAuthResetPasswordValidatedResponses.swift in Sources */, 287F65182983F77D00ED90BD /* MSALNativeAuthRequestParametersKey.swift in Sources */, 04D32CAE1FD615B3000B123E /* MSALErrorConverter.m in Sources */, @@ -6703,6 +6777,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DEFE87642CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProviderFactory.swift in Sources */, + DEFE87652CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProviderBuildable.swift in Sources */, + DEFE87662CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProvider.swift in Sources */, + DEFE87672CA6BBE5009D11DC /* MSALNativeAuthSilentTokenProviding.swift in Sources */, DE8DC4802C6621A100534E8F /* RetrieveAccessTokenError.swift in Sources */, 232D68CD223DB00500594BBD /* MSALTokenParameters.m in Sources */, DE8DC47E2C6621A100534E8F /* SignInStartError.swift in Sources */, @@ -7006,6 +7084,8 @@ E2C190772B20DF4300095534 /* SignInAfterResetPasswordErrorTests.swift in Sources */, 9BD2765F2A0E81CE00FBD033 /* ResetPasswordRequiredStateTests.swift in Sources */, D69ADB371E516F9B00952049 /* MSALTestCase.m in Sources */, + DEFE876A2CA6BC3A009D11DC /* MSALNativeAuthSilentTokenProviderMock.swift in Sources */, + DEFE876B2CA6BC3A009D11DC /* MSALNativeAuthSilentTokenProviderFactoryMock.swift in Sources */, DE94C9E829F19E6B00C1EC1F /* MSALNativeAuthResetPasswordPollCompletionRequestParametersTest.swift in Sources */, E22427FD2B0671A10006C55E /* ResetPasswordStartDelegateDispatcherTests.swift in Sources */, DE0D659529C1DCC9005798B1 /* MSALNativeAuthSignInInitiateRequestParametersTest.swift in Sources */, @@ -7020,6 +7100,7 @@ 233E970B226571AB007FCE2A /* MSALTelemetryAggregatedTests.m in Sources */, B2725ECA22C04661009B454A /* MSALLegacySharedAccountTests.m in Sources */, 28B6494D2A0959EB00EF3DB7 /* MSALNativeAuthSignInResponseValidatorTest.swift in Sources */, + DE1560E42CAE8F3F00C85E51 /* MSALNativeAuthSilentTokenProviderFactoryConfigTester.swift in Sources */, B253153B23DD717900432133 /* MSALDeviceInfoProviderTests.m in Sources */, 9B61C91C2A27E57C00CE9E3A /* MSALNativeAuthResetPasswordResponseValidatorMock.swift in Sources */, DE40A4D32A8F80C100928CEE /* MSALNativeAuthSignUpContinueResponseErrorTests.swift in Sources */, @@ -7146,6 +7227,8 @@ B2725EB622BF2774009B454A /* MSALPublicClientApplicationAccountUpdateTests.m in Sources */, DE8DC5082C6621EA00534E8F /* MSALNativeAuthHTTPRequestMock.swift in Sources */, DE8DC5462C66220D00534E8F /* MSALNativeAuthResetPasswordSubmitOauth2ErrorCodeTests.swift in Sources */, + DEFE876C2CA6BC3A009D11DC /* MSALNativeAuthSilentTokenProviderMock.swift in Sources */, + DEFE876D2CA6BC3A009D11DC /* MSALNativeAuthSilentTokenProviderFactoryMock.swift in Sources */, DE8DC4FB2C6621E700534E8F /* MSALNativeAuthCredentialsControllerTests.swift in Sources */, DE8DC5582C66221300534E8F /* MSALNativeAuthSignUpStartRequestParametersTest.swift in Sources */, DE8DC5372C66220000534E8F /* ResetPasswordRequiredStateTests.swift in Sources */, @@ -7238,6 +7321,7 @@ 28EE65222C8B109300015F90 /* MFASubmitChallengeErrorTests.swift in Sources */, 04D32CD11FD8AFF3000B123E /* MSALErrorConverterTests.m in Sources */, DE8DC5382C66220000534E8F /* MSALNativeAuthUserAccountResultTests.swift in Sources */, + DE1560E32CAE8F3F00C85E51 /* MSALNativeAuthSilentTokenProviderFactoryConfigTester.swift in Sources */, DE8DC4F62C6621E200534E8F /* MSALNativeAuthTelemetryTestDispatcher.swift in Sources */, DE8DC5662C66221A00534E8F /* MSALNativeAuthResponseSerializerTests.swift in Sources */, DE8DC5252C6621F500534E8F /* DispatchAccessTokenRetrieveCompletedTests.swift in Sources */, @@ -7342,6 +7426,8 @@ 28188F632C8F48BD00CFDD05 /* MSALNativeAuthSignInWithMFAEndToEndTests.swift in Sources */, DE1BD1002C3C283900B0888E /* MSALNativeAuthEmailCodeRetriever.swift in Sources */, DE1BD1042C3C284400B0888E /* MSALNativeAuthSignInUsernameAndPasswordEndToEndTests.swift in Sources */, + DEFE87712CA6BC91009D11DC /* CredentialsDelegateSpies.swift in Sources */, + DEFE87722CA6BC91009D11DC /* MSALNativeAuthUserAccountEndToEndTests.swift in Sources */, DE1BD1022C3C283F00B0888E /* MSALNativeAuthSignUpUsernameAndPasswordEndToEndTests.swift in Sources */, DE1BD10A2C3C285F00B0888E /* MSALNativeAuthEndToEndBaseTestCase.swift in Sources */, DE1BD1082C3C284D00B0888E /* ResetPasswordDelegateSpies.swift in Sources */, diff --git a/MSAL/src/native_auth/client_application_wrapper/silent_token/MSALNativeAuthSilentTokenProvider.swift b/MSAL/src/native_auth/client_application_wrapper/silent_token/MSALNativeAuthSilentTokenProvider.swift new file mode 100644 index 0000000000..9708a1cd7b --- /dev/null +++ b/MSAL/src/native_auth/client_application_wrapper/silent_token/MSALNativeAuthSilentTokenProvider.swift @@ -0,0 +1,47 @@ +// +// 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. + +/// Wrapper class around PublicClientApplication which helps with testability +class MSALNativeAuthSilentTokenProvider: MSALNativeAuthSilentTokenProviding { + + private let application: MSALNativeAuthPublicClientApplication? + + init( + configuration config: MSALPublicClientApplicationConfig, + challengeTypes: MSALNativeAuthChallengeTypes) throws { + self.application = try? MSALNativeAuthPublicClientApplication(configuration: config, challengeTypes: challengeTypes) + } + + func acquireTokenSilent(parameters: MSALSilentTokenParameters, + completionBlock: @escaping MSALNativeAuthSilentTokenResponse) { + application?.acquireTokenSilent(with: parameters) { result, error in + if let result { + let silentTokenResult = MSALNativeAuthSilentTokenResult(result: result) + completionBlock(silentTokenResult, error) + } else { + completionBlock(nil, error) + } + } + } +} diff --git a/MSAL/src/native_auth/client_application_wrapper/silent_token/MSALNativeAuthSilentTokenProviding.swift b/MSAL/src/native_auth/client_application_wrapper/silent_token/MSALNativeAuthSilentTokenProviding.swift new file mode 100644 index 0000000000..0af4e55ed1 --- /dev/null +++ b/MSAL/src/native_auth/client_application_wrapper/silent_token/MSALNativeAuthSilentTokenProviding.swift @@ -0,0 +1,57 @@ +// +// 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. + +/// Class used to extract infromation from MSALResult required by the UserAccountResult to be updated and returned +/// MSALResult cannot be directly created from Swift +class MSALNativeAuthSilentTokenResult { + let accessTokenResult: MSALNativeAuthTokenResult + let rawIdToken: String? + let account: MSALAccount + let correlationId: UUID + + init(result: MSALResult) { + self.rawIdToken = result.idToken + self.account = result.account + self.accessTokenResult = MSALNativeAuthTokenResult(accessToken: result.accessToken, + scopes: result.scopes, + expiresOn: result.expiresOn) + self.correlationId = result.correlationId + } + + init (accessTokenResult: MSALNativeAuthTokenResult, + rawIdToken: String?, + account: MSALAccount, + correlationId: UUID) { + self.rawIdToken = rawIdToken + self.account = account + self.accessTokenResult = accessTokenResult + self.correlationId = correlationId + } +} + +typealias MSALNativeAuthSilentTokenResponse = (MSALNativeAuthSilentTokenResult?, (any Error)?) -> Void + +protocol MSALNativeAuthSilentTokenProviding { + func acquireTokenSilent(parameters: MSALSilentTokenParameters, completionBlock: @escaping MSALNativeAuthSilentTokenResponse) +} diff --git a/MSAL/src/native_auth/client_application_wrapper/silent_token/factory/MSALNativeAuthSilentTokenProviderBuildable.swift b/MSAL/src/native_auth/client_application_wrapper/silent_token/factory/MSALNativeAuthSilentTokenProviderBuildable.swift new file mode 100644 index 0000000000..abc8de1245 --- /dev/null +++ b/MSAL/src/native_auth/client_application_wrapper/silent_token/factory/MSALNativeAuthSilentTokenProviderBuildable.swift @@ -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. + +protocol MSALNativeAuthSilentTokenProviderBuildable { + func makeSilentTokenProvider(configuration: MSALPublicClientApplicationConfig, + challengeTypes: MSALNativeAuthChallengeTypes) throws -> MSALNativeAuthSilentTokenProviding? +} diff --git a/MSAL/src/native_auth/client_application_wrapper/silent_token/factory/MSALNativeAuthSilentTokenProviderFactory.swift b/MSAL/src/native_auth/client_application_wrapper/silent_token/factory/MSALNativeAuthSilentTokenProviderFactory.swift new file mode 100644 index 0000000000..b8ffcb1112 --- /dev/null +++ b/MSAL/src/native_auth/client_application_wrapper/silent_token/factory/MSALNativeAuthSilentTokenProviderFactory.swift @@ -0,0 +1,30 @@ +// +// 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. + +class MSALNativeAuthSilentTokenProviderFactory: MSALNativeAuthSilentTokenProviderBuildable { + func makeSilentTokenProvider(configuration: MSALPublicClientApplicationConfig, + challengeTypes: MSALNativeAuthChallengeTypes) throws -> MSALNativeAuthSilentTokenProviding? { + return try? MSALNativeAuthSilentTokenProvider(configuration: configuration, challengeTypes: challengeTypes) + } +} diff --git a/MSAL/src/native_auth/public/MSALNativeAuthUserAccountResult+Internal.swift b/MSAL/src/native_auth/public/MSALNativeAuthUserAccountResult+Internal.swift index 97aa3d85c4..b09b425c50 100644 --- a/MSAL/src/native_auth/public/MSALNativeAuthUserAccountResult+Internal.swift +++ b/MSAL/src/native_auth/public/MSALNativeAuthUserAccountResult+Internal.swift @@ -43,7 +43,7 @@ extension MSALNativeAuthUserAccountResult { authority: authority) config.bypassRedirectURIValidation = configuration.redirectUri == nil - guard let client = try? MSALNativeAuthPublicClientApplication(configuration: config, challengeTypes: challengeTypes) + guard let silentTokenProvider = try? silentTokenProviderFactory.makeSilentTokenProvider(configuration: config, challengeTypes: challengeTypes) else { MSALLogger.log( level: .error, @@ -55,8 +55,7 @@ extension MSALNativeAuthUserAccountResult { correlationId: correlationId ?? context.correlationId())) } return } - - client.acquireTokenSilent(with: params) { [weak self] result, error in + silentTokenProvider.acquireTokenSilent(parameters: params) { [weak self] result, error in guard let self = self else { return } if let error = error as? NSError { @@ -67,10 +66,10 @@ extension MSALNativeAuthUserAccountResult { if let result = result { let delegateDispatcher = CredentialsDelegateDispatcher(delegate: delegate, telemetryUpdate: nil) - let accessTokenResult = MSALNativeAuthTokenResult(accessToken: result.accessToken, - scopes: result.scopes, - expiresOn: result.expiresOn) - Task { await delegateDispatcher.dispatchAccessTokenRetrieveCompleted(result: accessTokenResult, correlationId: result.correlationId) } + self.rawIdToken = result.rawIdToken + self.account = result.account + Task { await delegateDispatcher.dispatchAccessTokenRetrieveCompleted(result: result.accessTokenResult, + correlationId: result.correlationId) } return } @@ -83,7 +82,7 @@ extension MSALNativeAuthUserAccountResult { } } - func createRetrieveAccessTokenError(error: NSError, context: MSALNativeAuthRequestContext) -> RetrieveAccessTokenError { + private func createRetrieveAccessTokenError(error: NSError, context: MSALNativeAuthRequestContext) -> RetrieveAccessTokenError { if let innerError = error.userInfo[NSUnderlyingErrorKey] as? NSError { return createRetrieveAccessTokenError(error: innerError, context: context) } diff --git a/MSAL/src/native_auth/public/MSALNativeAuthUserAccountResult.swift b/MSAL/src/native_auth/public/MSALNativeAuthUserAccountResult.swift index 3f668f0e95..0f66543296 100644 --- a/MSAL/src/native_auth/public/MSALNativeAuthUserAccountResult.swift +++ b/MSAL/src/native_auth/public/MSALNativeAuthUserAccountResult.swift @@ -29,10 +29,11 @@ import Foundation /// The account object that holds account information. @objc public var account: MSALAccount - let configuration: MSALNativeAuthConfiguration - private var rawIdToken: String? + internal let configuration: MSALNativeAuthConfiguration + internal var rawIdToken: String? private let cacheAccessor: MSALNativeAuthCacheInterface private let inputValidator: MSALNativeAuthInputValidating + internal let silentTokenProviderFactory: MSALNativeAuthSilentTokenProviderBuildable /// Get the latest ID token for the account. @objc public var idToken: String? { @@ -44,13 +45,15 @@ import Foundation rawIdToken: String?, configuration: MSALNativeAuthConfiguration, cacheAccessor: MSALNativeAuthCacheInterface, - inputValidator: MSALNativeAuthInputValidating = MSALNativeAuthInputValidator() + inputValidator: MSALNativeAuthInputValidating = MSALNativeAuthInputValidator(), + silentTokenProviderFactory: MSALNativeAuthSilentTokenProviderBuildable = MSALNativeAuthSilentTokenProviderFactory() ) { self.account = account self.rawIdToken = rawIdToken self.configuration = configuration self.cacheAccessor = cacheAccessor self.inputValidator = inputValidator + self.silentTokenProviderFactory = silentTokenProviderFactory } /// Removes all the data from the cache. diff --git a/MSAL/test/integration/native_auth/end_to_end/credentials/CredentialsDelegateSpies.swift b/MSAL/test/integration/native_auth/end_to_end/credentials/CredentialsDelegateSpies.swift new file mode 100644 index 0000000000..590e7dce56 --- /dev/null +++ b/MSAL/test/integration/native_auth/end_to_end/credentials/CredentialsDelegateSpies.swift @@ -0,0 +1,53 @@ +// +// 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. + +@testable import MSAL +import XCTest + +open class CredentialsDelegateSpy: CredentialsDelegate { + + private let expectation: XCTestExpectation + private(set) var error: MSAL.RetrieveAccessTokenError? + private(set) var result: MSAL.MSALNativeAuthTokenResult? + private(set) var onAccessTokenRetrieveErrorCalled = false + private(set) var onAccessTokenRetrieveCompletedCalled = false + + init(expectation: XCTestExpectation) { + self.expectation = expectation + } + + public func onAccessTokenRetrieveCompleted(result: MSALNativeAuthTokenResult) { + onAccessTokenRetrieveCompletedCalled = true + self.result = result + + expectation.fulfill() + } + + public func onAccessTokenRetrieveError(error: MSAL.RetrieveAccessTokenError) { + onAccessTokenRetrieveErrorCalled = true + self.error = error + + expectation.fulfill() + } +} diff --git a/MSAL/test/integration/native_auth/end_to_end/credentials/MSALNativeAuthUserAccountEndToEndTests.swift b/MSAL/test/integration/native_auth/end_to_end/credentials/MSALNativeAuthUserAccountEndToEndTests.swift new file mode 100644 index 0000000000..4de154e638 --- /dev/null +++ b/MSAL/test/integration/native_auth/end_to_end/credentials/MSALNativeAuthUserAccountEndToEndTests.swift @@ -0,0 +1,128 @@ +// +// 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 Foundation +import XCTest + +final class MSALNativeAuthUserAccountEndToEndTests: MSALNativeAuthEndToEndPasswordTestCase { + + // Sign in with username and password to get access token and force refresh + func test_signInAndForceRefreshSucceeds() async throws { +#if os(macOS) + throw XCTSkip("Bundle id for macOS is not added to the client id, test is not needed on both iOS and macOS") +#endif + guard let sut = initialisePublicClientApplication(), let username = retrieveUsernameForSignInUsernameAndPassword(), let password = await retrievePasswordForSignInUsername() else { + XCTFail("Missing information") + return + } + + let signInExpectation = expectation(description: "signing in") + let signInDelegateSpy = SignInPasswordStartDelegateSpy(expectation: signInExpectation) + + sut.signIn(username: username, password: password, correlationId: correlationId, delegate: signInDelegateSpy) + + await fulfillment(of: [signInExpectation]) + + XCTAssertTrue(signInDelegateSpy.onSignInCompletedCalled) + XCTAssertNotNil(signInDelegateSpy.result?.idToken) + XCTAssertEqual(signInDelegateSpy.result?.account.username, username) + + let previousIdToken = signInDelegateSpy.result?.idToken + let refreshAccessTokenExpectation = expectation(description: "refreshing access token") + let credentialsDelegateSpy = CredentialsDelegateSpy(expectation: refreshAccessTokenExpectation) + + signInDelegateSpy.result?.getAccessToken(forceRefresh: true, delegate: credentialsDelegateSpy) + + await fulfillment(of: [refreshAccessTokenExpectation]) + + XCTAssertTrue(credentialsDelegateSpy.onAccessTokenRetrieveCompletedCalled) + XCTAssertNotNil(credentialsDelegateSpy.result?.accessToken) + XCTAssertNotEqual(previousIdToken, signInDelegateSpy.result?.idToken) + XCTAssertEqual(signInDelegateSpy.result?.account.username, username) + } + + // Sign in with username and password to get access token and force refresh with existing scopes + func test_signInAndForceRefreshWithExistingScopesSucceeds() async throws { +#if os(macOS) + throw XCTSkip("Bundle id for macOS is not added to the client id, test is not needed on both iOS and macOS") +#endif + guard let sut = initialisePublicClientApplication(), let username = retrieveUsernameForSignInUsernameAndPassword(), let password = await retrievePasswordForSignInUsername() else { + XCTFail("Missing information") + return + } + + let signInExpectation = expectation(description: "signing in") + let signInDelegateSpy = SignInPasswordStartDelegateSpy(expectation: signInExpectation) + + sut.signIn(username: username, password: password, correlationId: correlationId, delegate: signInDelegateSpy) + + await fulfillment(of: [signInExpectation]) + + XCTAssertTrue(signInDelegateSpy.onSignInCompletedCalled) + XCTAssertNotNil(signInDelegateSpy.result?.idToken) + XCTAssertEqual(signInDelegateSpy.result?.account.username, username) + + let previousIdToken = signInDelegateSpy.result?.idToken + let refreshAccessTokenExpectation = expectation(description: "refreshing access token") + let credentialsDelegateSpy = CredentialsDelegateSpy(expectation: refreshAccessTokenExpectation) + + signInDelegateSpy.result?.getAccessToken(scopes: ["User.Read"], forceRefresh: true, delegate: credentialsDelegateSpy) + + await fulfillment(of: [refreshAccessTokenExpectation]) + + XCTAssertTrue(credentialsDelegateSpy.onAccessTokenRetrieveCompletedCalled) + XCTAssertNotNil(credentialsDelegateSpy.result?.accessToken) + XCTAssertNotEqual(previousIdToken, signInDelegateSpy.result?.idToken) + XCTAssertEqual(signInDelegateSpy.result?.account.username, username) + } + + // Sign in with username and password to get access token and force refresh with different scopes + func test_signInAndForceRefreshWithDifferentScopesFails() async throws { + guard let sut = initialisePublicClientApplication(), let username = retrieveUsernameForSignInUsernameAndPassword(), let password = await retrievePasswordForSignInUsername() else { + XCTFail("Missing information") + return + } + + let signInExpectation = expectation(description: "signing in") + let signInDelegateSpy = SignInPasswordStartDelegateSpy(expectation: signInExpectation) + + sut.signIn(username: username, password: password, correlationId: correlationId, delegate: signInDelegateSpy) + + await fulfillment(of: [signInExpectation]) + + XCTAssertTrue(signInDelegateSpy.onSignInCompletedCalled) + XCTAssertNotNil(signInDelegateSpy.result?.idToken) + XCTAssertEqual(signInDelegateSpy.result?.account.username, username) + + let refreshAccessTokenExpectation = expectation(description: "refreshing access token") + let credentialsDelegateSpy = CredentialsDelegateSpy(expectation: refreshAccessTokenExpectation) + + signInDelegateSpy.result?.getAccessToken(scopes: ["Calendar.Read"], forceRefresh: true, delegate: credentialsDelegateSpy) + + await fulfillment(of: [refreshAccessTokenExpectation]) + + XCTAssertTrue(credentialsDelegateSpy.onAccessTokenRetrieveErrorCalled) + XCTAssertTrue(credentialsDelegateSpy.error!.errorDescription!.contains("Send an interactive authorization request for this user and resource.")) + } +} diff --git a/MSAL/test/unit/native_auth/mock/MSALNativeAuthSilentTokenProviderFactoryConfigTester.swift b/MSAL/test/unit/native_auth/mock/MSALNativeAuthSilentTokenProviderFactoryConfigTester.swift new file mode 100644 index 0000000000..3a5c1a9acb --- /dev/null +++ b/MSAL/test/unit/native_auth/mock/MSALNativeAuthSilentTokenProviderFactoryConfigTester.swift @@ -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 XCTest +@testable import MSAL +@_implementationOnly import MSAL_Private + +class MSALNativeAuthSilentTokenProviderFactoryConfigTester: MSALNativeAuthSilentTokenProviderBuildable { + var silentTokenProvider = MSALNativeAuthSilentTokenProviderMock() + var expectedBypassRedirectURIValidation = true + + func makeSilentTokenProvider(configuration: MSALPublicClientApplicationConfig, + challengeTypes: MSALNativeAuthChallengeTypes) throws -> (any MSAL.MSALNativeAuthSilentTokenProviding)? { + XCTAssertEqual(configuration.bypassRedirectURIValidation, expectedBypassRedirectURIValidation) + return silentTokenProvider + } +} diff --git a/MSAL/test/unit/native_auth/mock/MSALNativeAuthSilentTokenProviderFactoryMock.swift b/MSAL/test/unit/native_auth/mock/MSALNativeAuthSilentTokenProviderFactoryMock.swift new file mode 100644 index 0000000000..74798eacd5 --- /dev/null +++ b/MSAL/test/unit/native_auth/mock/MSALNativeAuthSilentTokenProviderFactoryMock.swift @@ -0,0 +1,34 @@ +// +// 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 XCTest +@testable import MSAL + +class MSALNativeAuthSilentTokenProviderFactoryMock: MSALNativeAuthSilentTokenProviderBuildable { + var silentTokenProvider = MSALNativeAuthSilentTokenProviderMock() + func makeSilentTokenProvider(configuration: MSALPublicClientApplicationConfig, + challengeTypes: MSALNativeAuthChallengeTypes) throws -> (any MSAL.MSALNativeAuthSilentTokenProviding)? { + return silentTokenProvider + } +} diff --git a/MSAL/test/unit/native_auth/mock/MSALNativeAuthSilentTokenProviderMock.swift b/MSAL/test/unit/native_auth/mock/MSALNativeAuthSilentTokenProviderMock.swift new file mode 100644 index 0000000000..d26592f382 --- /dev/null +++ b/MSAL/test/unit/native_auth/mock/MSALNativeAuthSilentTokenProviderMock.swift @@ -0,0 +1,41 @@ +// +// 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 XCTest +@testable import MSAL + +class MSALNativeAuthSilentTokenProviderMock : MSALNativeAuthSilentTokenProviding { + + var result: MSALNativeAuthSilentTokenResult? + var error: (any Error)? + var expectedParameters: MSALSilentTokenParameters? + + func acquireTokenSilent(parameters: MSALSilentTokenParameters, completionBlock: @escaping MSAL.MSALNativeAuthSilentTokenResponse) { + if let expectedParameters = expectedParameters { + XCTAssertEqual(expectedParameters.forceRefresh, parameters.forceRefresh) + XCTAssertEqual(expectedParameters.correlationId, parameters.correlationId) + } + completionBlock(result, error) + } +} diff --git a/MSAL/test/unit/native_auth/public/MSALNativeAuthUserAccountResultTests.swift b/MSAL/test/unit/native_auth/public/MSALNativeAuthUserAccountResultTests.swift index 3fc304d355..fcb0f76e63 100644 --- a/MSAL/test/unit/native_auth/public/MSALNativeAuthUserAccountResultTests.swift +++ b/MSAL/test/unit/native_auth/public/MSALNativeAuthUserAccountResultTests.swift @@ -27,45 +27,47 @@ import Foundation import XCTest @testable import MSAL @_implementationOnly import MSAL_Private +@_implementationOnly import MSAL_Unit_Test_Private class MSALNativeAuthUserAccountResultTests: XCTestCase { var sut: MSALNativeAuthUserAccountResult! private var cacheAccessorMock: MSALNativeAuthCacheAccessorMock! + private var silentTokenProviderFactoryMock: MSALNativeAuthSilentTokenProviderFactoryMock! private var account: MSALAccount! - private let innerCorrelationId = UUID().uuidString - private let withInnerCorrelationId = UUID().uuidString - private let withoutInnerCorrelationId = UUID().uuidString - + private let innerCorrelationId = UUID() + private let withInnerCorrelationId = UUID() + private let withoutInnerCorrelationId = UUID() + private var innerErrorMock: NSError { let innerUserInfo: [String : Any] = [ MSALInternalErrorCodeKey : -42002, MSALErrorDescriptionKey: "inner_user_info_error_description", MSALOAuthErrorKey: "inner_invalid_request", - MSALCorrelationIDKey: innerCorrelationId + MSALCorrelationIDKey: innerCorrelationId.uuidString ] return NSError(domain: "HttpResponseErrorDomain", code: 401, userInfo: innerUserInfo) } - + private var errorWithInnerErrorMock: NSError { let userInfo: [String : Any] = [ NSUnderlyingErrorKey : innerErrorMock , MSALErrorDescriptionKey: "user_info_error_description", MSALOAuthErrorKey: "invalid_request", - MSALCorrelationIDKey: withInnerCorrelationId + MSALCorrelationIDKey: withInnerCorrelationId.uuidString ] return NSError(domain: "HttpResponseErrorDomain", code: 501, userInfo: userInfo) } - + private var errorWithoutInnerErrorMock: NSError { let userInfo: [String : Any] = [ MSALInternalErrorCodeKey : -3003, MSALErrorDescriptionKey: "user_info_error_description", MSALOAuthErrorKey: "invalid_request", - MSALCorrelationIDKey: withoutInnerCorrelationId + MSALCorrelationIDKey: withoutInnerCorrelationId.uuidString ] return NSError(domain: "HttpResponseErrorDomain", code: 601, userInfo: userInfo) } - + private var errorWithoutInnerErrorWithoutDescriptionMock: NSError { let userInfo: [String : Any] = [ MSALOAuthErrorKey: "invalid_request", @@ -73,7 +75,7 @@ class MSALNativeAuthUserAccountResultTests: XCTestCase { ] return NSError(domain: "HttpResponseErrorDomain", code: 701, userInfo: userInfo) } - + private var errorWithoutInnerErrorWithoutCorrelationIdMock: NSError { let userInfo: [String : Any] = [ MSALOAuthErrorKey: "invalid_request", @@ -92,134 +94,291 @@ class MSALNativeAuthUserAccountResultTests: XCTestCase { let rawIdToken = "rawIdToken" cacheAccessorMock = MSALNativeAuthCacheAccessorMock() + silentTokenProviderFactoryMock = MSALNativeAuthSilentTokenProviderFactoryMock() sut = MSALNativeAuthUserAccountResult( account: account!, rawIdToken: rawIdToken, configuration: MSALNativeAuthConfigStubs.configuration, - cacheAccessor: cacheAccessorMock + cacheAccessor: cacheAccessorMock, + silentTokenProviderFactory: silentTokenProviderFactoryMock ) try super.setUpWithError() } + // MARK: - get access token tests + + func test_getAccessToken_successfullyReturnsAccessToken() async { + let accessToken = MSIDAccessToken() + accessToken.accessToken = "accessToken" + accessToken.scopes = ["scope1", "scope2"] + let contextCorrelationId = UUID() + let homeAccountId = MSALAccountId(accountIdentifier: "fedcba98-7654-3210-0000-000000000000.00000000-0000-1234-5678-90abcdefffff", objectId: "", tenantId: "https://contoso.com/tfp/tenantName") + let idToken = "newIdToken" + let account = MSALAccount(username: "1234567890", homeAccountId: homeAccountId, environment: "contoso.com", tenantProfiles: [])! + let silentTokenResult = MSALNativeAuthSilentTokenResult(accessTokenResult: MSALNativeAuthTokenResult(accessToken: accessToken.accessToken, + scopes: accessToken.scopes?.array as? [String] ?? [], + expiresOn: nil), + rawIdToken: idToken, + account: account, + correlationId: contextCorrelationId) + let params = MSALSilentTokenParameters(scopes: accessToken.scopes?.array as? [String] ?? [], account: account) + params.forceRefresh = false + params.correlationId = contextCorrelationId + + silentTokenProviderFactoryMock.silentTokenProvider.result = silentTokenResult + silentTokenProviderFactoryMock.silentTokenProvider.expectedParameters = params + + let delegateExp = expectation(description: "delegateDispatcher delegate exp") + let expectedResult = MSALNativeAuthTokenResult(accessToken: accessToken.accessToken, + scopes: accessToken.scopes?.array as? [String] ?? [], + expiresOn: accessToken.expiresOn) + + let delegate = CredentialsDelegateSpy(expectation: delegateExp, expectedResult: expectedResult) + delegate.expectedAccessToken = accessToken.accessToken + delegate.expectedScopes = accessToken.scopes?.array as? [String] ?? [] + sut.getAccessToken(correlationId: contextCorrelationId, delegate: delegate) + + await fulfillment(of: [delegateExp]) + + XCTAssertEqual(delegate.expectedResult, expectedResult) + XCTAssertEqual(sut.idToken, idToken) + XCTAssertEqual(sut.account, account) + } + + func test_getAccessTokenScopesAndForceRefresh_successfullyReturnsNewAccessToken() async { + let accessToken = MSIDAccessToken() + accessToken.accessToken = "newAccessToken" + accessToken.scopes = ["scope1", "scope2"] + let contextCorrelationId = UUID() + let homeAccountId = MSALAccountId(accountIdentifier: "fedcba98-7654-3210-0000-000000000000.00000000-0000-1234-5678-90abcdefffff", objectId: "", tenantId: "https://contoso.com/tfp/tenantName") + let idToken = "newIdToken" + let account = MSALAccount(username: "1234567890", homeAccountId: homeAccountId, environment: "contoso.com", tenantProfiles: [])! + let silentTokenResult = MSALNativeAuthSilentTokenResult(accessTokenResult: MSALNativeAuthTokenResult(accessToken: accessToken.accessToken, + scopes: accessToken.scopes?.array as? [String] ?? [], + expiresOn: nil), + rawIdToken: idToken, + account: account, + correlationId: contextCorrelationId) + + let params = MSALSilentTokenParameters(scopes: accessToken.scopes?.array as? [String] ?? [], account: account) + params.forceRefresh = true + params.correlationId = contextCorrelationId + + silentTokenProviderFactoryMock.silentTokenProvider.result = silentTokenResult + silentTokenProviderFactoryMock.silentTokenProvider.expectedParameters = params + + let delegateExp = expectation(description: "delegateDispatcher delegate exp") + let expectedResult = MSALNativeAuthTokenResult(accessToken: accessToken.accessToken, + scopes: accessToken.scopes?.array as? [String] ?? [], + expiresOn: accessToken.expiresOn) + + let delegate = CredentialsDelegateSpy(expectation: delegateExp, expectedResult: expectedResult) + delegate.expectedAccessToken = accessToken.accessToken + delegate.expectedScopes = accessToken.scopes?.array as? [String] ?? [] + sut.getAccessToken(scopes: accessToken.scopes?.array as? [String] ?? [], + forceRefresh: true, + correlationId: contextCorrelationId, + delegate: delegate) + + await fulfillment(of: [delegateExp]) + + XCTAssertEqual(delegate.expectedResult, expectedResult) + XCTAssertEqual(sut.idToken, idToken) + XCTAssertEqual(sut.account, account) + } + + func test_getAccessTokenWithRedirectURI_thenInternalConfigIsCreatedCorrectly() async { + let factory = MSALNativeAuthSilentTokenProviderFactoryConfigTester() + factory.expectedBypassRedirectURIValidation = false + let correlationId = UUID() + let configuration = try! MSALNativeAuthConfiguration ( + clientId: DEFAULT_TEST_CLIENT_ID, + authority: try! .init( + url: URL(string: DEFAULT_TEST_AUTHORITY)! + ), + challengeTypes: [.redirect], redirectUri: "contoso.com" + ) + sut = MSALNativeAuthUserAccountResult( + account: account!, + rawIdToken: "rawIdToken", + configuration: configuration, + cacheAccessor: cacheAccessorMock, + silentTokenProviderFactory: factory + ) + + let expectedError = RetrieveAccessTokenError(type: .generalError, message: nil, correlationId: correlationId, errorCodes: [], errorUri: nil) + factory.silentTokenProvider.error = expectedError + let delegateExp = expectation(description: "delegateDispatcher delegate exp") + let delegate = CredentialsDelegateSpy(expectation: delegateExp, expectedError: expectedError) + sut.getAccessToken(scopes: ["scope"], + forceRefresh: true, + correlationId: correlationId, + delegate: delegate) + + await fulfillment(of: [delegateExp]) + } + + func test_getAccessTokenWithoutRedirectURI_thenInternalConfigIsCreatedCorrectly() async { + let factory = MSALNativeAuthSilentTokenProviderFactoryConfigTester() + factory.expectedBypassRedirectURIValidation = true + let correlationId = UUID() + let configuration = try! MSALNativeAuthConfiguration ( + clientId: DEFAULT_TEST_CLIENT_ID, + authority: try! .init( + url: URL(string: DEFAULT_TEST_AUTHORITY)! + ), + challengeTypes: [.redirect], + redirectUri: nil + ) + sut = MSALNativeAuthUserAccountResult( + account: account!, + rawIdToken: "rawIdToken", + configuration: configuration, + cacheAccessor: cacheAccessorMock, + silentTokenProviderFactory: factory + ) + + let expectedError = RetrieveAccessTokenError(type: .generalError, message: nil, correlationId: correlationId, errorCodes: [], errorUri: nil) + factory.silentTokenProvider.error = expectedError + let delegateExp = expectation(description: "delegateDispatcher delegate exp") + let delegate = CredentialsDelegateSpy(expectation: delegateExp, expectedError: expectedError) + sut.getAccessToken(scopes: ["scope"], + forceRefresh: true, + correlationId: correlationId, + delegate: delegate) + + await fulfillment(of: [delegateExp]) + } + // MARK: - sign-out tests func test_signOut_successfullyCallsCacheAccessor() { sut.signOut() XCTAssertTrue(cacheAccessorMock.clearCacheWasCalled) } - + // MARK: - error tests - - func test_errorWithInnerError() { - let contextCorrelationId = UUID() - let context = MSALNativeAuthRequestContext(correlationId: contextCorrelationId) - - let result = sut.createRetrieveAccessTokenError(error: errorWithInnerErrorMock, - context: context) - - XCTAssertEqual(result.errorDescription, "inner_user_info_error_description") - XCTAssertEqual(result.errorCodes, []) - XCTAssertEqual(result.correlationId.uuidString, innerCorrelationId) - } - - func test_errorWithoutInnerError() { - let contextCorrelationId = UUID() - let context = MSALNativeAuthRequestContext(correlationId: contextCorrelationId) - - let result = sut.createRetrieveAccessTokenError(error: errorWithoutInnerErrorMock, - context: context) - - XCTAssertEqual(result.errorDescription, "user_info_error_description") - XCTAssertEqual(result.errorCodes, []) - XCTAssertEqual(result.correlationId.uuidString, withoutInnerCorrelationId) - } - - func test_errorWithoutInnerErrorWithoutDescription() { - let contextCorrelationId = UUID() - let context = MSALNativeAuthRequestContext(correlationId: contextCorrelationId) - - let result = sut.createRetrieveAccessTokenError(error: errorWithoutInnerErrorWithoutDescriptionMock, - context: context) - - XCTAssertEqual(result.errorDescription, errorWithoutInnerErrorWithoutDescriptionMock.localizedDescription) - XCTAssertEqual(result.errorCodes, []) - XCTAssertEqual(result.correlationId.uuidString, withoutInnerCorrelationId) - } - - func test_errorWithoutInnerErrorWithoutCorrelationId() { - let contextCorrelationId = UUID() - let context = MSALNativeAuthRequestContext(correlationId: contextCorrelationId) - - let result = sut.createRetrieveAccessTokenError(error: errorWithoutInnerErrorWithoutCorrelationIdMock, - context: context) - - XCTAssertEqual(result.errorDescription, "user_info_error_description") - XCTAssertEqual(result.errorCodes, []) - XCTAssertEqual(result.correlationId, contextCorrelationId) - } - - func test_errorWithValidExternalErrorCodes_ParseShouldWorks() { - let contextCorrelationId = UUID() - let context = MSALNativeAuthRequestContext(correlationId: contextCorrelationId) + + func test_errorWithInnerError() async { + silentTokenProviderFactoryMock.silentTokenProvider.error = errorWithInnerErrorMock + let delegateExp = expectation(description: "delegateDispatcher delegate exp") + let expectedError = RetrieveAccessTokenError(type: .generalError, message: "inner_user_info_error_description", correlationId: innerCorrelationId, errorCodes: [], errorUri: nil) + let delegate = CredentialsDelegateSpy(expectation: delegateExp, expectedError: expectedError) + sut.getAccessToken(delegate: delegate) + + await fulfillment(of: [delegateExp]) + } + + func test_errorWithoutInnerError() async { + silentTokenProviderFactoryMock.silentTokenProvider.error = errorWithoutInnerErrorMock + let delegateExp = expectation(description: "delegateDispatcher delegate exp") + let expectedError = RetrieveAccessTokenError(type: .generalError, message: "user_info_error_description", correlationId: withoutInnerCorrelationId, errorCodes: [], errorUri: nil) + let delegate = CredentialsDelegateSpy(expectation: delegateExp, expectedError: expectedError) + sut.getAccessToken(delegate: delegate) + + await fulfillment(of: [delegateExp]) + } + + func test_errorWithoutInnerErrorWithoutDescription() async { + let correlationId = UUID() + silentTokenProviderFactoryMock.silentTokenProvider.error = errorWithoutInnerErrorWithoutDescriptionMock + let delegateExp = expectation(description: "delegateDispatcher delegate exp") + let expectedError = RetrieveAccessTokenError(type: .generalError, message: errorWithoutInnerErrorWithoutDescriptionMock.localizedDescription, correlationId: correlationId, errorCodes: [], errorUri: nil) + let delegate = CredentialsDelegateSpy(expectation: delegateExp, expectedError: expectedError) + sut.getAccessToken(correlationId: correlationId, delegate: delegate) + + await fulfillment(of: [delegateExp]) + } + + func test_errorWithoutInnerErrorWithoutCorrelationId() async { + let correlationId = UUID() + silentTokenProviderFactoryMock.silentTokenProvider.error = errorWithoutInnerErrorWithoutCorrelationIdMock + let delegateExp = expectation(description: "delegateDispatcher delegate exp") + let expectedError = RetrieveAccessTokenError(type: .generalError, message: "user_info_error_description", correlationId: correlationId, errorCodes: [], errorUri: nil) + let delegate = CredentialsDelegateSpy(expectation: delegateExp, expectedError: expectedError) + sut.getAccessToken(correlationId: correlationId, + delegate: delegate) + + await fulfillment(of: [delegateExp]) + } + + func test_errorWithValidExternalErrorCodes_ParseShouldWorks() async { + let correlationId = UUID() let errorCodes = [1, 2, 3] let userInfo: [String : Any] = [ MSALSTSErrorCodesKey: errorCodes ] let error = NSError(domain: "", code: 1, userInfo: userInfo) - - let result = sut.createRetrieveAccessTokenError(error: error, context: context) - - XCTAssertEqual(result.errorCodes, errorCodes) + + silentTokenProviderFactoryMock.silentTokenProvider.error = error + let delegateExp = expectation(description: "delegateDispatcher delegate exp") + let expectedError = RetrieveAccessTokenError(type: .generalError, message: "The operation couldn’t be completed. ( error 1.)", correlationId: correlationId, errorCodes: errorCodes, errorUri: nil) + let delegate = CredentialsDelegateSpy(expectation: delegateExp, expectedError: expectedError) + sut.getAccessToken(correlationId: correlationId, + delegate: delegate) + + await fulfillment(of: [delegateExp]) } - - func test_errorWithInvalidExternalErrorCodes_ParseShouldWorks() { - let contextCorrelationId = UUID() - let context = MSALNativeAuthRequestContext(correlationId: contextCorrelationId) + + func test_errorWithInvalidExternalErrorCodes_ParseShouldWorks() async { + let correlationId = UUID() let errorCodes = ["123"] let userInfo: [String : Any] = [ MSALSTSErrorCodesKey: errorCodes ] let error = NSError(domain: "", code: 1, userInfo: userInfo) - - let result = sut.createRetrieveAccessTokenError(error: error, context: context) - - XCTAssertEqual(result.errorCodes, []) + + silentTokenProviderFactoryMock.silentTokenProvider.error = error + let delegateExp = expectation(description: "delegateDispatcher delegate exp") + let expectedError = RetrieveAccessTokenError(type: .generalError, message: "The operation couldn’t be completed. ( error 1.)", correlationId: correlationId, errorCodes: [], errorUri: nil) + let delegate = CredentialsDelegateSpy(expectation: delegateExp, expectedError: expectedError) + sut.getAccessToken(correlationId: correlationId, + delegate: delegate) + + await fulfillment(of: [delegateExp]) } - - func test_errorWithValidInnerErrorWithErrorCodes_ParseShouldWorks() { - let contextCorrelationId = UUID() - let context = MSALNativeAuthRequestContext(correlationId: contextCorrelationId) + + func test_errorWithValidInnerErrorWithErrorCodes_ParseShouldWorks() async { + let correlationId = UUID() let errorCodes = [1, 2, 3] let userInfo: [String : Any] = [ MSALSTSErrorCodesKey: errorCodes ] let innerError = NSError(domain: "", code: 1, userInfo: userInfo) let error = NSError(domain: "", code: 1, userInfo: [NSUnderlyingErrorKey: innerError]) - - let result = sut.createRetrieveAccessTokenError(error: error, context: context) - - XCTAssertEqual(result.errorCodes, errorCodes) + + silentTokenProviderFactoryMock.silentTokenProvider.error = error + let delegateExp = expectation(description: "delegateDispatcher delegate exp") + let expectedError = RetrieveAccessTokenError(type: .generalError, message: "The operation couldn’t be completed. ( error 1.)", correlationId: correlationId, errorCodes: [1, 2, 3], errorUri: nil) + let delegate = CredentialsDelegateSpy(expectation: delegateExp, expectedError: expectedError) + sut.getAccessToken(correlationId: correlationId, + delegate: delegate) + + await fulfillment(of: [delegateExp]) } - - func test_errorWithInvalidInnerErrorWithErrorCodes_ParseShouldWorks() { - let contextCorrelationId = UUID() - let context = MSALNativeAuthRequestContext(correlationId: contextCorrelationId) + + func test_errorWithInvalidInnerErrorWithErrorCodes_ParseShouldWorks() async { + let correlationId = UUID() let errorCodes = ["123"] let userInfo: [String : Any] = [ MSALSTSErrorCodesKey: errorCodes ] let innerError = NSError(domain: "", code: 1, userInfo: userInfo) let error = NSError(domain: "", code: 1, userInfo: [NSUnderlyingErrorKey: innerError]) - - let result = sut.createRetrieveAccessTokenError(error: error, context: context) - - XCTAssertEqual(result.errorCodes, []) + + silentTokenProviderFactoryMock.silentTokenProvider.error = error + let delegateExp = expectation(description: "delegateDispatcher delegate exp") + let expectedError = RetrieveAccessTokenError(type: .generalError, message: "The operation couldn’t be completed. ( error 1.)", correlationId: correlationId, errorCodes: [], errorUri: nil) + let delegate = CredentialsDelegateSpy(expectation: delegateExp, expectedError: expectedError) + sut.getAccessToken(correlationId: correlationId, + delegate: delegate) + + await fulfillment(of: [delegateExp]) } - - func test_errorWithMFARequiredErrorCode_ErrorMessageShouldContainsCorrectMessage() { - let contextCorrelationId = UUID() - let context = MSALNativeAuthRequestContext(correlationId: contextCorrelationId) + + func test_errorWithMFARequiredErrorCode_ErrorMessageShouldContainsCorrectMessage() async { + let correlationId = UUID() let errorCodes = [50076] let message = "message" let userInfo: [String : Any] = [ @@ -227,10 +386,13 @@ class MSALNativeAuthUserAccountResultTests: XCTestCase { MSALSTSErrorCodesKey: errorCodes ] let error = NSError(domain: "", code: 1, userInfo: userInfo) - - let result = sut.createRetrieveAccessTokenError(error: error, context: context) - - XCTAssertEqual(result.errorCodes, errorCodes) - XCTAssertEqual(result.errorDescription, MSALNativeAuthErrorMessage.refreshTokenMFARequiredError + message) + silentTokenProviderFactoryMock.silentTokenProvider.error = error + let delegateExp = expectation(description: "delegateDispatcher delegate exp") + let expectedError = RetrieveAccessTokenError(type: .generalError, message: MSALNativeAuthErrorMessage.refreshTokenMFARequiredError + message, correlationId: correlationId, errorCodes: [50076], errorUri: nil) + let delegate = CredentialsDelegateSpy(expectation: delegateExp, expectedError: expectedError) + sut.getAccessToken(correlationId: correlationId, + delegate: delegate) + + await fulfillment(of: [delegateExp]) } } From 5c6c291c707301eebb67fb74fe56bc063a203ca3 Mon Sep 17 00:00:00 2001 From: Sergei Demchenko Date: Fri, 4 Oct 2024 14:17:57 -0700 Subject: [PATCH 29/39] Support caller app info for browser SSO flows (#2347) * modified: MSAL/IdentityCore * modified: MSAL/IdentityCore * modified: MSAL/IdentityCore * Update core. --- MSAL/IdentityCore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MSAL/IdentityCore b/MSAL/IdentityCore index 5a86414eb7..f0c6ee3cf4 160000 --- a/MSAL/IdentityCore +++ b/MSAL/IdentityCore @@ -1 +1 @@ -Subproject commit 5a86414eb7c0e6345f9bee3a1e50eb5eb942daf9 +Subproject commit f0c6ee3cf419f6afcd41ffcbda78303b7ffcc4f5 From 8f11fa7b17230bd8af667dad2dc830129f67d0b3 Mon Sep 17 00:00:00 2001 From: Juan Arias Roldan <1686668+juan-arias@users.noreply.github.com> Date: Fri, 4 Oct 2024 16:41:33 -0700 Subject: [PATCH 30/39] Add specific minimum version to match downloaded visionOS simulator's version --- MSAL/MSAL.xcodeproj/project.pbxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MSAL/MSAL.xcodeproj/project.pbxproj b/MSAL/MSAL.xcodeproj/project.pbxproj index b11dcb919a..4e2aae04f6 100644 --- a/MSAL/MSAL.xcodeproj/project.pbxproj +++ b/MSAL/MSAL.xcodeproj/project.pbxproj @@ -9009,6 +9009,7 @@ PROVISIONING_PROFILE_SPECIFIER = ""; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; TARGETED_DEVICE_FAMILY = "1,2,7"; + XROS_DEPLOYMENT_TARGET = 1.2; }; name = Debug; }; @@ -9024,6 +9025,7 @@ PROVISIONING_PROFILE_SPECIFIER = ""; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; TARGETED_DEVICE_FAMILY = "1,2,7"; + XROS_DEPLOYMENT_TARGET = 1.2; }; name = Release; }; From 797f9350c1aa292d221d974961eb4deb85f9de03 Mon Sep 17 00:00:00 2001 From: Juan Arias Roldan <1686668+juan-arias@users.noreply.github.com> Date: Fri, 4 Oct 2024 22:32:46 -0700 Subject: [PATCH 31/39] Trigger Build From 09d8b55bce835c22a7f7061eb2418b20ad063878 Mon Sep 17 00:00:00 2001 From: Jason Zeng Date: Mon, 7 Oct 2024 06:41:11 -0700 Subject: [PATCH 32/39] Update common submodule --- MSAL/IdentityCore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MSAL/IdentityCore b/MSAL/IdentityCore index f0c6ee3cf4..1ab4dcf699 160000 --- a/MSAL/IdentityCore +++ b/MSAL/IdentityCore @@ -1 +1 @@ -Subproject commit f0c6ee3cf419f6afcd41ffcbda78303b7ffcc4f5 +Subproject commit 1ab4dcf699a50c11f7b37a185ccb451412336b1f From 358f25a6d3915e70419c86787b2dada6b2fcb71f Mon Sep 17 00:00:00 2001 From: Jason Zeng Date: Mon, 7 Oct 2024 06:44:36 -0700 Subject: [PATCH 33/39] Update common submodule --- MSAL/IdentityCore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MSAL/IdentityCore b/MSAL/IdentityCore index 1ab4dcf699..e33aa214fb 160000 --- a/MSAL/IdentityCore +++ b/MSAL/IdentityCore @@ -1 +1 @@ -Subproject commit 1ab4dcf699a50c11f7b37a185ccb451412336b1f +Subproject commit e33aa214fb81e8ec4f1fd3f2d0331c9708013fcf From 6a21cf57993dd53a893ca09e7d34aef2424362f0 Mon Sep 17 00:00:00 2001 From: Danilo Raspa <105228698+nilo-ms@users.noreply.github.com> Date: Mon, 7 Oct 2024 17:46:50 +0100 Subject: [PATCH 34/39] Remove duplicated test, use proper name for test and skip test for macOS (#2362) --- ...ALNativeAuthUserAccountEndToEndTests.swift | 36 ++----------------- 1 file changed, 2 insertions(+), 34 deletions(-) diff --git a/MSAL/test/integration/native_auth/end_to_end/credentials/MSALNativeAuthUserAccountEndToEndTests.swift b/MSAL/test/integration/native_auth/end_to_end/credentials/MSALNativeAuthUserAccountEndToEndTests.swift index 4de154e638..a8df6da563 100644 --- a/MSAL/test/integration/native_auth/end_to_end/credentials/MSALNativeAuthUserAccountEndToEndTests.swift +++ b/MSAL/test/integration/native_auth/end_to_end/credentials/MSALNativeAuthUserAccountEndToEndTests.swift @@ -62,8 +62,8 @@ final class MSALNativeAuthUserAccountEndToEndTests: MSALNativeAuthEndToEndPasswo XCTAssertEqual(signInDelegateSpy.result?.account.username, username) } - // Sign in with username and password to get access token and force refresh with existing scopes - func test_signInAndForceRefreshWithExistingScopesSucceeds() async throws { + // Sign in with username and password to get access token and force refresh with access token not linked to client Id + func test_signInAndForceRefreshWithNotConfiguredScopes() async throws { #if os(macOS) throw XCTSkip("Bundle id for macOS is not added to the client id, test is not needed on both iOS and macOS") #endif @@ -83,38 +83,6 @@ final class MSALNativeAuthUserAccountEndToEndTests: MSALNativeAuthEndToEndPasswo XCTAssertNotNil(signInDelegateSpy.result?.idToken) XCTAssertEqual(signInDelegateSpy.result?.account.username, username) - let previousIdToken = signInDelegateSpy.result?.idToken - let refreshAccessTokenExpectation = expectation(description: "refreshing access token") - let credentialsDelegateSpy = CredentialsDelegateSpy(expectation: refreshAccessTokenExpectation) - - signInDelegateSpy.result?.getAccessToken(scopes: ["User.Read"], forceRefresh: true, delegate: credentialsDelegateSpy) - - await fulfillment(of: [refreshAccessTokenExpectation]) - - XCTAssertTrue(credentialsDelegateSpy.onAccessTokenRetrieveCompletedCalled) - XCTAssertNotNil(credentialsDelegateSpy.result?.accessToken) - XCTAssertNotEqual(previousIdToken, signInDelegateSpy.result?.idToken) - XCTAssertEqual(signInDelegateSpy.result?.account.username, username) - } - - // Sign in with username and password to get access token and force refresh with different scopes - func test_signInAndForceRefreshWithDifferentScopesFails() async throws { - guard let sut = initialisePublicClientApplication(), let username = retrieveUsernameForSignInUsernameAndPassword(), let password = await retrievePasswordForSignInUsername() else { - XCTFail("Missing information") - return - } - - let signInExpectation = expectation(description: "signing in") - let signInDelegateSpy = SignInPasswordStartDelegateSpy(expectation: signInExpectation) - - sut.signIn(username: username, password: password, correlationId: correlationId, delegate: signInDelegateSpy) - - await fulfillment(of: [signInExpectation]) - - XCTAssertTrue(signInDelegateSpy.onSignInCompletedCalled) - XCTAssertNotNil(signInDelegateSpy.result?.idToken) - XCTAssertEqual(signInDelegateSpy.result?.account.username, username) - let refreshAccessTokenExpectation = expectation(description: "refreshing access token") let credentialsDelegateSpy = CredentialsDelegateSpy(expectation: refreshAccessTokenExpectation) From bd5e6b3228b94a8284a2bb2856c4ee2d2a3e8286 Mon Sep 17 00:00:00 2001 From: Jason Zeng Date: Mon, 7 Oct 2024 23:22:40 -0700 Subject: [PATCH 35/39] Update submodule --- CHANGELOG.md | 1 + MSAL/IdentityCore | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d156186869..3ea1368d14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## [TBD]: * Support extra query parameters on logout endpoint (#2339) +* Add support functions to help broker improve cross cloud experience (#2361) ## [1.5.1]: * Parse and add STS error codes in token error result (#2319) diff --git a/MSAL/IdentityCore b/MSAL/IdentityCore index e33aa214fb..49acd06ed6 160000 --- a/MSAL/IdentityCore +++ b/MSAL/IdentityCore @@ -1 +1 @@ -Subproject commit e33aa214fb81e8ec4f1fd3f2d0331c9708013fcf +Subproject commit 49acd06ed6778296f78940475ca902073274c416 From 62fe5610d1e61e35c5a15b2e60bca7b73cc6abe1 Mon Sep 17 00:00:00 2001 From: Jason Zeng Date: Tue, 8 Oct 2024 21:16:15 -0700 Subject: [PATCH 36/39] Update common core submodule --- MSAL/IdentityCore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MSAL/IdentityCore b/MSAL/IdentityCore index 49acd06ed6..4d1f8690d6 160000 --- a/MSAL/IdentityCore +++ b/MSAL/IdentityCore @@ -1 +1 @@ -Subproject commit 49acd06ed6778296f78940475ca902073274c416 +Subproject commit 4d1f8690d66c799a8e5f7b8cc1fe53dc079b8adc From 1687c06b3e61d964816da5e6680c5b2f4b286c1a Mon Sep 17 00:00:00 2001 From: Ameya <> Date: Wed, 9 Oct 2024 17:25:52 -0700 Subject: [PATCH 37/39] Updating common-core submodule to release/1.7.42 --- MSAL/IdentityCore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MSAL/IdentityCore b/MSAL/IdentityCore index 4d1f8690d6..184eaa08c3 160000 --- a/MSAL/IdentityCore +++ b/MSAL/IdentityCore @@ -1 +1 @@ -Subproject commit 4d1f8690d66c799a8e5f7b8cc1fe53dc079b8adc +Subproject commit 184eaa08c3de734e55a99a5b6eb5cca82cb15000 From db5ec3a166000fbfa2646be67336a905edb37c2c Mon Sep 17 00:00:00 2001 From: Ameya Date: Tue, 15 Oct 2024 10:25:45 -0700 Subject: [PATCH 38/39] Updating submodule to common-core main --- MSAL/IdentityCore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MSAL/IdentityCore b/MSAL/IdentityCore index 184eaa08c3..b4fd324d20 160000 --- a/MSAL/IdentityCore +++ b/MSAL/IdentityCore @@ -1 +1 @@ -Subproject commit 184eaa08c3de734e55a99a5b6eb5cca82cb15000 +Subproject commit b4fd324d200caef614635cecb4c0ac66c413e23f From 9be505b29c1fce64d1f8cb4f37daaabe87202f95 Mon Sep 17 00:00:00 2001 From: Ameya Date: Tue, 15 Oct 2024 10:28:03 -0700 Subject: [PATCH 39/39] Updating changelog version --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ea1368d14..5ad82e5551 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## [TBD]: +## [1.6.1]: * Support extra query parameters on logout endpoint (#2339) * Add support functions to help broker improve cross cloud experience (#2361)