Skip to content

Commit b216b25

Browse files
authored
Merge pull request #2493 from AzureAD/yuki/e2e-batch-3
Native Auth E2E Test Batch 3
2 parents 1bfd768 + 0fb20f0 commit b216b25

File tree

6 files changed

+579
-39
lines changed

6 files changed

+579
-39
lines changed

MSAL/test/integration/native_auth/end_to_end/credentials/MSALNativeAuthUserAccountEndToEndTests.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,38 @@ final class MSALNativeAuthUserAccountEndToEndTests: MSALNativeAuthEndToEndPasswo
9393
XCTAssertTrue(credentialsDelegateSpy.onAccessTokenRetrieveErrorCalled)
9494
XCTAssertTrue(credentialsDelegateSpy.error!.errorDescription!.contains("Send an interactive authorization request for this user and resource."))
9595
}
96+
97+
// Sign in with username and password with extra scopes to get access token and validate the scopes
98+
func test_signInWithExtraScopes() async throws {
99+
#if os(macOS)
100+
throw XCTSkip("Bundle id for macOS is not added to the client id, test is not needed on both iOS and macOS")
101+
#endif
102+
guard let sut = initialisePublicClientApplication(), let username = retrieveUsernameForSignInUsernameAndPassword(), let password = await retrievePasswordForSignInUsername() else {
103+
XCTFail("Missing information")
104+
return
105+
}
106+
107+
let signInExpectation = expectation(description: "signing in")
108+
let signInDelegateSpy = SignInPasswordStartDelegateSpy(expectation: signInExpectation)
109+
110+
sut.signIn(username: username, password: password, scopes: ["User.Read"], correlationId: correlationId, delegate: signInDelegateSpy)
111+
112+
await fulfillment(of: [signInExpectation])
113+
114+
XCTAssertTrue(signInDelegateSpy.onSignInCompletedCalled)
115+
XCTAssertNotNil(signInDelegateSpy.result?.idToken)
116+
XCTAssertEqual(signInDelegateSpy.result?.account.username, username)
117+
118+
let getAccessTokenExpectation = expectation(description: "getting access token")
119+
let credentialsDelegateSpy = CredentialsDelegateSpy(expectation: getAccessTokenExpectation)
120+
121+
signInDelegateSpy.result?.getAccessToken(scopes: ["User.Read"], delegate: credentialsDelegateSpy)
122+
123+
await fulfillment(of: [getAccessTokenExpectation])
124+
125+
XCTAssertTrue(credentialsDelegateSpy.onAccessTokenRetrieveCompletedCalled)
126+
XCTAssertNotNil(credentialsDelegateSpy.result?.accessToken)
127+
XCTAssertNotNil(credentialsDelegateSpy.result?.scopes)
128+
XCTAssertTrue(credentialsDelegateSpy.result!.scopes.contains("User.Read"))
129+
}
96130
}

MSAL/test/integration/native_auth/end_to_end/reset_password/MSALNativeAuthResetPasswordEndToEndTests.swift

Lines changed: 199 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ final class MSALNativeAuthResetPasswordEndToEndTests: MSALNativeAuthEndToEndBase
3131
private let codeRetryCount = 3
3232

3333
func test_resetPassword_withoutAutomaticSignIn_succeeds() async throws {
34-
throw XCTSkip("1secmail service is down. Ignoring test for now.")
34+
throw XCTSkip("Retrieving OTP failure")
3535

3636
guard let sut = initialisePublicClientApplication(),
3737
let username = retrieveUsernameForResetPassword()
@@ -71,10 +71,207 @@ final class MSALNativeAuthResetPasswordEndToEndTests: MSALNativeAuthEndToEndBase
7171
await fulfillment(of: [resetPasswordCompletedExp])
7272
XCTAssertTrue(resetPasswordRequiredDelegate.onResetPasswordCompletedCalled)
7373
}
74+
75+
// User Case 3.1.3. SSPR – New password being set doesn’t meet password complexity requirements set on portal
76+
func test_resetPassword_passwordComplexity_error() async throws {
77+
throw XCTSkip("Retrieving OTP failure")
78+
79+
guard let sut = initialisePublicClientApplication(),
80+
let username = retrieveUsernameForResetPassword()
81+
else {
82+
XCTFail("Missing information")
83+
return
84+
}
85+
let codeRequiredExp = expectation(description: "code required")
86+
let resetPasswordStartDelegate = ResetPasswordStartDelegateSpy(expectation: codeRequiredExp)
87+
88+
sut.resetPassword(username: username, delegate: resetPasswordStartDelegate)
89+
90+
await fulfillment(of: [codeRequiredExp])
91+
XCTAssertTrue(resetPasswordStartDelegate.onResetPasswordCodeRequiredCalled)
92+
93+
guard resetPasswordStartDelegate.onResetPasswordCodeRequiredCalled else {
94+
XCTFail("onResetPasswordCodeRequired not called")
95+
return
96+
}
97+
98+
XCTAssertEqual(resetPasswordStartDelegate.channelTargetType?.isEmailType, true)
99+
XCTAssertFalse(resetPasswordStartDelegate.sentTo?.isEmpty ?? true)
100+
XCTAssertNotNil(resetPasswordStartDelegate.codeLength)
101+
102+
// Now submit the code...
103+
let newPasswordRequiredState = await retrieveAndSubmitCode(resetPasswordStartDelegate: resetPasswordStartDelegate,
104+
username: username,
105+
retries: codeRetryCount)
106+
107+
// Now submit the password...
108+
let resetPasswordCompletedExp = expectation(description: "reset password completed")
109+
let resetPasswordRequiredDelegate = ResetPasswordRequiredDelegateSpy(expectation: resetPasswordCompletedExp)
110+
111+
let uniquePassword = "INVALID_PASSWORD"
112+
newPasswordRequiredState?.submitPassword(password: uniquePassword, delegate: resetPasswordRequiredDelegate)
113+
114+
await fulfillment(of: [resetPasswordCompletedExp])
115+
XCTAssertTrue(resetPasswordRequiredDelegate.onResetPasswordRequiredErrorCalled)
116+
XCTAssertEqual(resetPasswordRequiredDelegate.error?.isInvalidPassword, true)
117+
}
118+
119+
// User Case 3.1.4 SSPR - Resend email OTP
120+
func test_resetPassword_resendCode_succeeds() async throws {
121+
throw XCTSkip("Retrieving OTP failure")
122+
123+
guard let sut = initialisePublicClientApplication(),
124+
let username = retrieveUsernameForResetPassword()
125+
else {
126+
XCTFail("Missing information")
127+
return
128+
}
129+
let codeRequiredExp = expectation(description: "code required")
130+
let resetPasswordStartDelegate = ResetPasswordStartDelegateSpy(expectation: codeRequiredExp)
131+
132+
sut.resetPassword(username: username, delegate: resetPasswordStartDelegate)
133+
134+
await fulfillment(of: [codeRequiredExp])
135+
XCTAssertTrue(resetPasswordStartDelegate.onResetPasswordCodeRequiredCalled)
136+
137+
guard resetPasswordStartDelegate.onResetPasswordCodeRequiredCalled else {
138+
XCTFail("onResetPasswordCodeRequired not called")
139+
return
140+
}
141+
142+
// Now get code1...
143+
guard let code1 = await retrieveCodeFor(email: username) else {
144+
XCTFail("OTP code could not be retrieved")
145+
return
146+
}
147+
148+
// Resend code
149+
let resendCodeRequiredExp = expectation(description: "code required again")
150+
let resetPasswordResendCodeDelegate = ResetPasswordResendCodeDelegateSpy(expectation: resendCodeRequiredExp)
151+
152+
// Call resend code method
153+
resetPasswordStartDelegate.newState?.resendCode(delegate: resetPasswordResendCodeDelegate)
154+
155+
await fulfillment(of: [resendCodeRequiredExp])
156+
157+
// Verify that resend code method was called
158+
XCTAssertTrue(resetPasswordResendCodeDelegate.onResetPasswordResendCodeCodeRequiredCalled,
159+
"Resend code method should have been called")
160+
161+
// Now get code2...
162+
guard let code2 = await retrieveCodeFor(email: username) else {
163+
XCTFail("OTP code could not be retrieved")
164+
return
165+
}
166+
167+
// Verify that the codes are different
168+
XCTAssertNotEqual(code1, code2, "Resent code should be different from the original code")
169+
170+
// Now submit the code...
171+
let newPasswordRequiredState = await retrieveAndSubmitCode(resetPasswordStartDelegate: resetPasswordStartDelegate,
172+
username: username,
173+
retries: codeRetryCount)
174+
175+
// Now submit the password...
176+
let resetPasswordCompletedExp = expectation(description: "reset password completed")
177+
let resetPasswordRequiredDelegate = ResetPasswordRequiredDelegateSpy(expectation: resetPasswordCompletedExp)
178+
179+
let uniquePassword = generateRandomPassword()
180+
newPasswordRequiredState?.submitPassword(password: uniquePassword, delegate: resetPasswordRequiredDelegate)
181+
182+
await fulfillment(of: [resetPasswordCompletedExp])
183+
XCTAssertTrue(resetPasswordRequiredDelegate.onResetPasswordCompletedCalled)
184+
}
185+
186+
// User Case 3.1.5 SSPR - Email is not found in records
187+
func test_resetPassword_emailNotFound_error() async throws {
188+
guard let sut = initialisePublicClientApplication() else {
189+
XCTFail("Missing information")
190+
return
191+
}
192+
193+
let resetPasswordFailureExp = expectation(description: "reset password user not found")
194+
let resetPasswordStartDelegate = ResetPasswordStartDelegateSpy(expectation: resetPasswordFailureExp)
195+
196+
let unknownUsername = UUID().uuidString + "@contoso.com"
197+
198+
sut.resetPassword(username: unknownUsername, delegate: resetPasswordStartDelegate)
199+
200+
await fulfillment(of: [resetPasswordFailureExp])
201+
202+
// Verify error condition
203+
XCTAssertTrue(resetPasswordStartDelegate.onResetPasswordErrorCalled)
204+
XCTAssertEqual(resetPasswordStartDelegate.error?.isUserNotFound, true)
205+
}
206+
207+
// User Case 3.1.6 SSPR - When SSPR requires a challenge type not supported by the client, redirect to web-fallback
208+
func test_resetPassword_webfallback_error() async throws {
209+
guard let sut = initialisePublicClientApplication(challengeTypes: [.password]),
210+
let username = retrieveUsernameForResetPassword()
211+
else {
212+
XCTFail("Missing information")
213+
return
214+
}
215+
216+
let resetPasswordFailureExp = expectation(description: "reset password web-fallback")
217+
let resetPasswordStartDelegate = ResetPasswordStartDelegateSpy(expectation: resetPasswordFailureExp)
218+
219+
sut.resetPassword(username: username, delegate: resetPasswordStartDelegate)
220+
221+
await fulfillment(of: [resetPasswordFailureExp])
222+
223+
// Verify error condition
224+
XCTAssertTrue(resetPasswordStartDelegate.onResetPasswordErrorCalled)
225+
XCTAssertEqual(resetPasswordStartDelegate.error?.isBrowserRequired, true)
226+
}
227+
228+
// User Case 3.1.8 SSPR – Email exists but not linked to any password
229+
func test_resetPassword_accoutWithoutPassword_error() async throws {
230+
guard let sut = initialisePublicClientApplication(),
231+
let username = retrieveUsernameForSignInCode()
232+
else {
233+
XCTFail("Missing information")
234+
return
235+
}
236+
237+
let resetPasswordFailureExp = expectation(description: "does not support password")
238+
let resetPasswordStartDelegate = ResetPasswordStartDelegateSpy(expectation: resetPasswordFailureExp)
239+
240+
sut.resetPassword(username: username, delegate: resetPasswordStartDelegate)
241+
242+
await fulfillment(of: [resetPasswordFailureExp])
243+
244+
// Verify error condition
245+
XCTAssertTrue(resetPasswordStartDelegate.onResetPasswordErrorCalled)
246+
XCTAssertTrue(resetPasswordStartDelegate.error?.errorDescription?.contains("The tenant or user does not support native credential recovery.") ?? false)
247+
}
248+
249+
// User Case 3.1.9 - Email exists but signup method was OTP, social, etc.
250+
func test_resetPassword_socialAccount_error() async throws {
251+
throw XCTSkip("Skipping test as it requires a Social account, not present in MSIDLAB")
252+
253+
guard let sut = initialisePublicClientApplication() else {
254+
XCTFail("Missing information")
255+
return
256+
}
257+
258+
let username = "invalid" // TODO: use social account instead
259+
260+
let resetPasswordFailureExp = expectation(description: "reset password user not found")
261+
let resetPasswordStartDelegate = ResetPasswordStartDelegateSpy(expectation: resetPasswordFailureExp)
262+
263+
sut.resetPassword(username: username, delegate: resetPasswordStartDelegate)
264+
265+
await fulfillment(of: [resetPasswordFailureExp])
266+
267+
// Verify error condition
268+
XCTAssertTrue(resetPasswordStartDelegate.onResetPasswordErrorCalled)
269+
XCTAssertEqual(resetPasswordStartDelegate.error?.isInvalidUsername, true)
270+
}
74271

75272
// SSPR - with automatic sign in
76273
func test_resetPassword_withAutomaticSignIn_succeeds() async throws {
77-
throw XCTSkip("1secmail service is down. Ignoring test for now.")
274+
throw XCTSkip("Retrieving OTP failure")
78275

79276
guard let sut = initialisePublicClientApplication(),
80277
let username = retrieveUsernameForResetPassword()

MSAL/test/integration/native_auth/end_to_end/reset_password/ResetPasswordDelegateSpies.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,36 @@ class ResetPasswordRequiredDelegateSpy: ResetPasswordRequiredDelegate {
121121
}
122122
}
123123

124+
class ResetPasswordResendCodeDelegateSpy: ResetPasswordResendCodeDelegate {
125+
private let expectation: XCTestExpectation
126+
private(set) var onResetPasswordResendCodeErrorCalled = false
127+
private(set) var error: ResendCodeError?
128+
private(set) var onResetPasswordResendCodeCodeRequiredCalled = false
129+
private(set) var resetPasswordCodeRequiredState: ResetPasswordCodeRequiredState?
130+
private(set) var sentTo: String?
131+
private(set) var channelTargetType: MSALNativeAuthChannelType?
132+
private(set) var codeLength: Int?
133+
134+
init(expectation: XCTestExpectation) {
135+
self.expectation = expectation
136+
}
137+
138+
func onResetPasswordResendCodeError(error: MSAL.ResendCodeError, newState: MSAL.ResetPasswordCodeRequiredState?) {
139+
onResetPasswordResendCodeErrorCalled = true
140+
self.error = error
141+
}
142+
143+
func onResetPasswordResendCodeCodeRequired(newState: ResetPasswordCodeRequiredState, sentTo: String, channelTargetType: MSALNativeAuthChannelType, codeLength: Int) {
144+
onResetPasswordResendCodeCodeRequiredCalled = true
145+
resetPasswordCodeRequiredState = newState
146+
self.sentTo = sentTo
147+
self.channelTargetType = channelTargetType
148+
self.codeLength = codeLength
149+
150+
expectation.fulfill()
151+
}
152+
}
153+
124154
class SignInAfterResetPasswordDelegateSpy: SignInAfterResetPasswordDelegate {
125155
private let expectation: XCTestExpectation
126156
private(set) var onSignInAfterResetPasswordErrorCalled = false

0 commit comments

Comments
 (0)