Skip to content

Commit 16f94eb

Browse files
committed
Merge branch 'dev' of https://github.com/AzureAD/microsoft-authentication-library-for-objc into yuki/e2e-parameter
# Conflicts: # MSAL/test/integration/native_auth/end_to_end/sign_in/MSALNativeAuthSignInUsernameEndToEndTests.swift
2 parents 873af70 + b216b25 commit 16f94eb

File tree

6 files changed

+597
-49
lines changed

6 files changed

+597
-49
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
@@ -105,4 +105,38 @@ final class MSALNativeAuthUserAccountEndToEndTests: MSALNativeAuthEndToEndPasswo
105105
XCTAssertTrue(credentialsDelegateSpy.onAccessTokenRetrieveErrorCalled)
106106
XCTAssertTrue(credentialsDelegateSpy.error!.errorDescription!.contains("Send an interactive authorization request for this user and resource."))
107107
}
108+
109+
// Sign in with username and password with extra scopes to get access token and validate the scopes
110+
func test_signInWithExtraScopes() async throws {
111+
#if os(macOS)
112+
throw XCTSkip("Bundle id for macOS is not added to the client id, test is not needed on both iOS and macOS")
113+
#endif
114+
guard let sut = initialisePublicClientApplication(), let username = retrieveUsernameForSignInUsernameAndPassword(), let password = await retrievePasswordForSignInUsername() else {
115+
XCTFail("Missing information")
116+
return
117+
}
118+
119+
let signInExpectation = expectation(description: "signing in")
120+
let signInDelegateSpy = SignInPasswordStartDelegateSpy(expectation: signInExpectation)
121+
122+
sut.signIn(username: username, password: password, scopes: ["User.Read"], correlationId: correlationId, delegate: signInDelegateSpy)
123+
124+
await fulfillment(of: [signInExpectation])
125+
126+
XCTAssertTrue(signInDelegateSpy.onSignInCompletedCalled)
127+
XCTAssertNotNil(signInDelegateSpy.result?.idToken)
128+
XCTAssertEqual(signInDelegateSpy.result?.account.username, username)
129+
130+
let getAccessTokenExpectation = expectation(description: "getting access token")
131+
let credentialsDelegateSpy = CredentialsDelegateSpy(expectation: getAccessTokenExpectation)
132+
133+
signInDelegateSpy.result?.getAccessToken(scopes: ["User.Read"], delegate: credentialsDelegateSpy)
134+
135+
await fulfillment(of: [getAccessTokenExpectation])
136+
137+
XCTAssertTrue(credentialsDelegateSpy.onAccessTokenRetrieveCompletedCalled)
138+
XCTAssertNotNil(credentialsDelegateSpy.result?.accessToken)
139+
XCTAssertNotNil(credentialsDelegateSpy.result?.scopes)
140+
XCTAssertTrue(credentialsDelegateSpy.result!.scopes.contains("User.Read"))
141+
}
108142
}

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

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

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