Skip to content

Commit 3305bfc

Browse files
committed
SSPR
1 parent 5aa77c9 commit 3305bfc

File tree

2 files changed

+195
-2
lines changed

2 files changed

+195
-2
lines changed

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

Lines changed: 165 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,173 @@ 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_Passwprd"
112+
newPasswordRequiredState?.submitPassword(password: uniquePassword, delegate: resetPasswordRequiredDelegate)
113+
114+
await fulfillment(of: [resetPasswordCompletedExp])
115+
XCTAssertTrue(resetPasswordRequiredDelegate.onResetPasswordCompletedCalled)
116+
}
117+
118+
// User Case 3.1.4 SSPR - Resend email OTP
119+
func test_resetPassword_resendCode_succeeds() async throws {
120+
throw XCTSkip("Retrieving OTP failure")
121+
122+
guard let sut = initialisePublicClientApplication(),
123+
let username = retrieveUsernameForResetPassword()
124+
else {
125+
XCTFail("Missing information")
126+
return
127+
}
128+
let codeRequiredExp = expectation(description: "code required")
129+
let resetPasswordStartDelegate = ResetPasswordStartDelegateSpy(expectation: codeRequiredExp)
130+
131+
sut.resetPassword(username: username, delegate: resetPasswordStartDelegate)
132+
133+
await fulfillment(of: [codeRequiredExp])
134+
XCTAssertTrue(resetPasswordStartDelegate.onResetPasswordCodeRequiredCalled)
135+
136+
guard resetPasswordStartDelegate.onResetPasswordCodeRequiredCalled else {
137+
XCTFail("onResetPasswordCodeRequired not called")
138+
return
139+
}
140+
141+
// Now get code1...
142+
guard let code1 = await retrieveCodeFor(email: username) else {
143+
XCTFail("OTP code could not be retrieved")
144+
return
145+
}
146+
147+
// Resend code
148+
let resendCodeRequiredExp = expectation(description: "code required again")
149+
let resetPasswordResendCodeDelegate = ResetPasswordResendCodeDelegateSpy(expectation: resendCodeRequiredExp)
150+
151+
// Call resend code method
152+
resetPasswordStartDelegate.newState?.resendCode(delegate: resetPasswordResendCodeDelegate)
153+
154+
await fulfillment(of: [resendCodeRequiredExp])
155+
156+
// Verify that resend code method was called
157+
XCTAssertTrue(resetPasswordResendCodeDelegate.onResetPasswordResendCodeCodeRequiredCalled,
158+
"Resend code method should have been called")
159+
160+
// Now get code2...
161+
guard let code2 = await retrieveCodeFor(email: username) else {
162+
XCTFail("OTP code could not be retrieved")
163+
return
164+
}
165+
166+
// Verify that the codes are different
167+
XCTAssertNotEqual(code1, code2, "Resent code should be different from the original code")
168+
}
169+
170+
// User Case 3.1.5 SSPR - Email is not found in records
171+
func test_resetPassword_emailNotFound_error() async throws {
172+
guard let sut = initialisePublicClientApplication(),
173+
let username = retrieveUsernameForResetPassword()
174+
else {
175+
XCTFail("Missing information")
176+
return
177+
}
178+
179+
let resetPasswordFailureExp = expectation(description: "reset password user not found")
180+
let resetPasswordStartDelegate = ResetPasswordStartDelegateSpy(expectation: resetPasswordFailureExp)
181+
182+
sut.resetPassword(username: username, delegate: resetPasswordStartDelegate)
183+
184+
await fulfillment(of: [resetPasswordFailureExp])
185+
186+
// Verify error condition
187+
XCTAssertTrue(resetPasswordStartDelegate.onResetPasswordErrorCalled)
188+
XCTAssertEqual(resetPasswordStartDelegate.error?.isUserNotFound, true)
189+
}
190+
191+
// User Case 3.1.6 SSPR - When SSPR requires a challenge type not supported by the client, redirect to web-fallback
192+
func test_resetPassword_webfallback_error() async throws {
193+
guard let sut = initialisePublicClientApplication(challengeTypes: [.password]),
194+
let username = retrieveUsernameForResetPassword()
195+
else {
196+
XCTFail("Missing information")
197+
return
198+
}
199+
200+
let resetPasswordFailureExp = expectation(description: "reset password web-fallback")
201+
let resetPasswordStartDelegate = ResetPasswordStartDelegateSpy(expectation: resetPasswordFailureExp)
202+
203+
sut.resetPassword(username: username, delegate: resetPasswordStartDelegate)
204+
205+
await fulfillment(of: [resetPasswordFailureExp])
206+
207+
// Verify error condition
208+
XCTAssertTrue(resetPasswordStartDelegate.onResetPasswordErrorCalled)
209+
XCTAssertEqual(resetPasswordStartDelegate.error?.isBrowserRequired, true)
210+
}
211+
212+
// User Case 3.1.8 SSPR – Email exists but not linked to any password
213+
// Not applicable
214+
215+
// User Case 3.1.9 - Email exists but signup method was OTP, social, etc.
216+
func test_resetPassword_socialAccount_error() async throws {
217+
throw XCTSkip("Skipping test as it requires a Social account, not present in MSIDLAB")
218+
219+
guard let sut = initialisePublicClientApplication() else {
220+
XCTFail("Missing information")
221+
return
222+
}
223+
224+
let username = "invalid"
225+
226+
let resetPasswordFailureExp = expectation(description: "reset password user not found")
227+
let resetPasswordStartDelegate = ResetPasswordStartDelegateSpy(expectation: resetPasswordFailureExp)
228+
229+
sut.resetPassword(username: username, delegate: resetPasswordStartDelegate)
230+
231+
await fulfillment(of: [resetPasswordFailureExp])
232+
233+
// Verify error condition
234+
XCTAssertTrue(resetPasswordStartDelegate.onResetPasswordErrorCalled)
235+
XCTAssertEqual(resetPasswordStartDelegate.error?.isInvalidUsername, true)
236+
}
74237

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

79242
guard let sut = initialisePublicClientApplication(),
80243
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)