Skip to content

Commit 72deaa0

Browse files
authored
Native Auth JIT E2E test, Fixes AB#3188154 (#2296)
2. Ensure JIT is triggered in signIn after signUp (preverified): test sign after sign up without specify verification contact 3. Ensure JIT is triggered in signIn after signUp and a second email is used as verification contact: test sign after sign up with specify verification contact 4. Ensure JIT is triggered on first signIn: test sign in without specify verification contact [AB#3188154](https://identitydivision.visualstudio.com/fac9d424-53d2-45c0-91b5-ef6ba7a6bf26/_workitems/edit/3188154)
1 parent 844c0bd commit 72deaa0

File tree

2 files changed

+251
-2
lines changed

2 files changed

+251
-2
lines changed
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// All rights reserved.
3+
//
4+
// This code is licensed under the MIT License.
5+
//
6+
// Permission is hereby granted, free of charge, to any person obtaining a copy
7+
// of this software and associated documentation files(the "Software"), to deal
8+
// in the Software without restriction, including without limitation the rights
9+
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
10+
// copies of the Software, and to permit persons to whom the Software is
11+
// furnished to do so, subject to the following conditions :
12+
//
13+
// The above copyright notice and this permission notice shall be included in
14+
// all copies or substantial portions of the Software.
15+
//
16+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
// THE SOFTWARE.
23+
24+
package com.microsoft.identity.client.e2e.tests.network.nativeauth
25+
26+
import com.microsoft.identity.client.claims.ClaimsRequest
27+
import com.microsoft.identity.client.e2e.utils.assertResult
28+
import com.microsoft.identity.internal.testutils.nativeauth.ConfigType
29+
import com.microsoft.identity.internal.testutils.nativeauth.api.TemporaryEmailService
30+
import com.microsoft.identity.internal.testutils.nativeauth.api.models.NativeAuthTestConfig
31+
import com.microsoft.identity.nativeauth.INativeAuthPublicClientApplication
32+
import com.microsoft.identity.nativeauth.parameters.NativeAuthChallengeAuthMethodParameters
33+
import com.microsoft.identity.nativeauth.parameters.NativeAuthGetAccessTokenParameters
34+
import com.microsoft.identity.nativeauth.parameters.NativeAuthSignInContinuationParameters
35+
import com.microsoft.identity.nativeauth.parameters.NativeAuthSignInParameters
36+
import com.microsoft.identity.nativeauth.parameters.NativeAuthSignUpParameters
37+
import com.microsoft.identity.nativeauth.statemachine.errors.MFASubmitChallengeError
38+
import com.microsoft.identity.nativeauth.statemachine.results.GetAccessTokenResult
39+
import com.microsoft.identity.nativeauth.statemachine.results.MFARequiredResult
40+
import com.microsoft.identity.nativeauth.statemachine.results.RegisterStrongAuthChallengeResult
41+
import com.microsoft.identity.nativeauth.statemachine.results.SignInResult
42+
import com.microsoft.identity.nativeauth.statemachine.results.SignUpResendCodeResult
43+
import com.microsoft.identity.nativeauth.statemachine.results.SignUpResult
44+
import com.microsoft.identity.nativeauth.statemachine.states.RegisterStrongAuthVerificationRequiredState
45+
import kotlinx.coroutines.runBlocking
46+
import org.junit.Assert
47+
import org.junit.Assert.assertEquals
48+
import org.junit.Assert.assertNotNull
49+
import org.junit.Assert.assertTrue
50+
import org.junit.Assert.fail
51+
import org.junit.Ignore
52+
import org.junit.Test
53+
import org.robolectric.RuntimeEnvironment.application
54+
import org.robolectric.shadows.ShadowPackageManager.resources
55+
import org.robolectric.versioning.AndroidVersions
56+
import java.util.Base64
57+
58+
class SignInJITTest : NativeAuthPublicClientApplicationAbstractTest() {
59+
60+
private val tempEmailApi = TemporaryEmailService()
61+
62+
private lateinit var resources: List<NativeAuthTestConfig.Resource>
63+
64+
lateinit var application: INativeAuthPublicClientApplication
65+
lateinit var config: NativeAuthTestConfig.Config
66+
67+
private val defaultConfigType = ConfigType.SIGN_IN_MFA_SINGLE_AUTH
68+
private val defaultChallengeTypes = listOf("password", "oob")
69+
70+
/**
71+
* Full flow: Ensure JIT is triggered on first signIn
72+
* - SignUp a new user with username and password.
73+
* - Do not SignIn after signUp, but start a new signIn flow.
74+
* - Check that JIT flow is triggered.
75+
* - Specify a different email as verification contact.
76+
* - Complete JIT. Verification email should be sent to the second email.
77+
* - Access token is received.
78+
*
79+
*/
80+
@Ignore("Retrieving OTP code failure.")
81+
@Test
82+
fun `test sign in specifying custom verification contact`() {
83+
config = getConfig(defaultConfigType)
84+
application = setupPCA(config, defaultChallengeTypes)
85+
resources = config.resources
86+
val authenticationContextId = "c4"
87+
val authenticationContextRequestClaimJson = "{\"access_token\":{\"acrs\":{\"essential\":true,\"value\":\"$authenticationContextId\"}}}"
88+
89+
retryOperation {
90+
runBlocking {
91+
// SignUp a new user with username and password
92+
val username = tempEmailApi.generateRandomEmailAddressLocally()
93+
val signUpParams = NativeAuthSignUpParameters(username)
94+
signUpParams.password = getSafePassword().toCharArray()
95+
val signUpResult = application.signUp(signUpParams)
96+
assertResult<SignUpResult.CodeRequired>(signUpResult)
97+
val otp1 = tempEmailApi.retrieveCodeFromInbox(username)
98+
val submitCodeResult = (signUpResult as SignUpResult.CodeRequired).nextState.submitCode(otp1)
99+
assertResult<SignUpResult.Complete>(submitCodeResult)
100+
101+
// Start a new signIn flow. Check that JIT flow is triggered.
102+
val signInParams = NativeAuthSignInParameters(username)
103+
signInParams.password = getSafePassword().toCharArray()
104+
signInParams.claimsRequest = ClaimsRequest.getClaimsRequestFromJsonString(authenticationContextRequestClaimJson)
105+
val signInResult = application.signIn(signInParams)
106+
assertResult<SignInResult.StrongAuthMethodRegistrationRequired>(signInResult)
107+
val authMethod = (signInResult as SignInResult.StrongAuthMethodRegistrationRequired).authMethods[0]
108+
109+
// Specify a different email as verification contact.
110+
val authMethodParams = NativeAuthChallengeAuthMethodParameters(authMethod)
111+
val contact = tempEmailApi.generateRandomEmailAddressLocally()
112+
authMethodParams.verificationContact = contact
113+
114+
// Complete JIT. Verification email should be sent to the second email.
115+
val challengeResult = signInResult.nextState.challengeAuthMethod(authMethodParams)
116+
val otp2 = tempEmailApi.retrieveCodeFromInbox(contact)
117+
val submitChallengeResult = (challengeResult as RegisterStrongAuthChallengeResult.VerificationRequired).result.getNextState().submitChallenge(otp2)
118+
assertResult<SignInResult.Complete>(submitChallengeResult)
119+
120+
// Access token is received.
121+
val accountState = (submitChallengeResult as SignInResult.Complete).resultValue
122+
val accountParam = NativeAuthGetAccessTokenParameters()
123+
val getAccessTokenResult = accountState.getAccessToken(accountParam)
124+
assertResult<GetAccessTokenResult.Complete>(getAccessTokenResult)
125+
val authResult = (getAccessTokenResult as GetAccessTokenResult.Complete).resultValue
126+
assertNotNull(authResult)
127+
}
128+
}
129+
}
130+
131+
/**
132+
* Full flow: Ensure JIT is triggered in signIn after signUp (preverified)
133+
* - SignUp a new user with username and password.
134+
* - SignIn after signUp with authentication context as claims to trigger MFA. // TODO: tenant setting
135+
* - Check that JIT flow is triggered.
136+
* - Do not specify a verification contact.
137+
* - SignIn should be completed without needs to send a code to the email.
138+
* - Access token is received.
139+
*
140+
*/
141+
@Ignore("Retrieving OTP code failure.")
142+
@Test
143+
fun `test sign after sign up without specify verification contact`() {
144+
config = getConfig(defaultConfigType)
145+
application = setupPCA(config, defaultChallengeTypes)
146+
resources = config.resources
147+
val authenticationContextId = "c4"
148+
val authenticationContextRequestClaimJson = "{\"access_token\":{\"acrs\":{\"essential\":true,\"value\":\"$authenticationContextId\"}}}"
149+
150+
retryOperation {
151+
runBlocking {
152+
// SignUp a new user with username and password.
153+
val username = tempEmailApi.generateRandomEmailAddressLocally()
154+
val signUpParams = NativeAuthSignUpParameters(username)
155+
signUpParams.password = getSafePassword().toCharArray()
156+
val signUpResult = application.signUp(signUpParams)
157+
assertResult<SignUpResult.CodeRequired>(signUpResult)
158+
val otp1 = tempEmailApi.retrieveCodeFromInbox(username)
159+
val submitCodeResult = (signUpResult as SignUpResult.CodeRequired).nextState.submitCode(otp1)
160+
assertResult<SignUpResult.Complete>(submitCodeResult)
161+
162+
// SignIn after signUp with authentication context as claims to trigger MFA.
163+
val continuationParameters = NativeAuthSignInContinuationParameters()
164+
continuationParameters.claimsRequest = ClaimsRequest.getClaimsRequestFromJsonString(authenticationContextRequestClaimJson)
165+
val signWithContinuationResult = (submitCodeResult as SignUpResult.Complete).nextState.signIn(continuationParameters)
166+
167+
// Check that JIT flow is triggered.
168+
assertResult<SignInResult.StrongAuthMethodRegistrationRequired>(signWithContinuationResult)
169+
val authMethod = (signWithContinuationResult as SignInResult.StrongAuthMethodRegistrationRequired).authMethods[0]
170+
// Do not specify a verification contact.
171+
val authMethodParams = NativeAuthChallengeAuthMethodParameters(authMethod)
172+
173+
// SignIn should be completed without needs to send a code to the email.
174+
val challengeResult = signWithContinuationResult.nextState.challengeAuthMethod(authMethodParams)
175+
assertResult<SignInResult.Complete>(challengeResult)
176+
177+
// Access token is received.
178+
val accountState = (challengeResult as SignInResult.Complete).resultValue
179+
val accountParam = NativeAuthGetAccessTokenParameters()
180+
val getAccessTokenResult = accountState.getAccessToken(accountParam)
181+
assertResult<GetAccessTokenResult.Complete>(getAccessTokenResult)
182+
val authResult = (getAccessTokenResult as GetAccessTokenResult.Complete).resultValue
183+
assertNotNull(authResult)
184+
}
185+
}
186+
}
187+
188+
/**
189+
* Full flow: Ensure JIT is triggered in signIn after signUp and a second email is used as verification contact
190+
* - SignUp a new user with username and password.
191+
* - SignIn after signUp with authentication context as claims to trigger MFA. // TODO: tenant setting
192+
* - Check that JIT flow is triggered.
193+
* - Specify a different email as verification contact.
194+
* - Complete JIT. Verification email should be sent to the second email.
195+
* - Access token is received.
196+
*
197+
*/
198+
@Ignore("Retrieving OTP code failure.")
199+
@Test
200+
fun `test sign after sign up with specify verification contact`() {
201+
config = getConfig(defaultConfigType)
202+
application = setupPCA(config, defaultChallengeTypes)
203+
resources = config.resources
204+
val authenticationContextId = "c4"
205+
val authenticationContextRequestClaimJson = "{\"access_token\":{\"acrs\":{\"essential\":true,\"value\":\"$authenticationContextId\"}}}"
206+
207+
retryOperation {
208+
runBlocking {
209+
// SignUp a new user with username and password.
210+
val username = tempEmailApi.generateRandomEmailAddressLocally()
211+
val signUpParams = NativeAuthSignUpParameters(username)
212+
signUpParams.password = getSafePassword().toCharArray()
213+
val signUpResult = application.signUp(signUpParams)
214+
assertResult<SignUpResult.CodeRequired>(signUpResult)
215+
val otp1 = tempEmailApi.retrieveCodeFromInbox(username)
216+
val submitCodeResult = (signUpResult as SignUpResult.CodeRequired).nextState.submitCode(otp1)
217+
assertResult<SignUpResult.Complete>(submitCodeResult)
218+
219+
// SignIn after signUp with authentication context as claims to trigger MFA.
220+
val continuationParameters = NativeAuthSignInContinuationParameters()
221+
continuationParameters.claimsRequest = ClaimsRequest.getClaimsRequestFromJsonString(authenticationContextRequestClaimJson)
222+
val signWithContinuationResult = (submitCodeResult as SignUpResult.Complete).nextState.signIn(continuationParameters)
223+
224+
// Check that JIT flow is triggered.
225+
assertResult<SignInResult.StrongAuthMethodRegistrationRequired>(signWithContinuationResult)
226+
val authMethod = (signWithContinuationResult as SignInResult.StrongAuthMethodRegistrationRequired).authMethods[0]
227+
// Specify a different email as verification contact.
228+
val authMethodParams = NativeAuthChallengeAuthMethodParameters(authMethod)
229+
val contact = tempEmailApi.generateRandomEmailAddressLocally()
230+
authMethodParams.verificationContact = contact
231+
232+
// Complete JIT. Verification email should be sent to the second email.
233+
val challengeResult = signWithContinuationResult.nextState.challengeAuthMethod(authMethodParams)
234+
val otp2 = tempEmailApi.retrieveCodeFromInbox(contact)
235+
val submitChallengeResult = (challengeResult as RegisterStrongAuthChallengeResult.VerificationRequired).result.getNextState().submitChallenge(otp2)
236+
assertResult<SignInResult.Complete>(submitChallengeResult)
237+
238+
// Access token is received.
239+
val accountState = (submitChallengeResult as SignInResult.Complete).resultValue
240+
val accountParam = NativeAuthGetAccessTokenParameters()
241+
val getAccessTokenResult = accountState.getAccessToken(accountParam)
242+
assertResult<GetAccessTokenResult.Complete>(getAccessTokenResult)
243+
val authResult = (getAccessTokenResult as GetAccessTokenResult.Complete).resultValue
244+
assertNotNull(authResult)
245+
}
246+
}
247+
}
248+
}

msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignInMFATest.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,8 @@ class SignInMFATest : NativeAuthPublicClientApplicationAbstractTest() {
310310

311311
// Retrieve access token
312312
val accountState = (submitCorrectChallengeResult as SignInResult.Complete).resultValue
313-
val getAccessTokenResult = accountState.getAccessToken()
313+
val accountParam = NativeAuthGetAccessTokenParameters()
314+
val getAccessTokenResult = accountState.getAccessToken(accountParam)
314315
assertResult<GetAccessTokenResult.Complete>(getAccessTokenResult)
315316
val authResult = (getAccessTokenResult as GetAccessTokenResult.Complete).resultValue
316317

@@ -321,7 +322,7 @@ class SignInMFATest : NativeAuthPublicClientApplicationAbstractTest() {
321322
return@runBlocking
322323
}
323324
val atBody = atParts[1]
324-
val charset = charset("UTF-8")
325+
val charset = Charsets.UTF_8
325326
val atDecoded = String(
326327
Base64.getUrlDecoder().decode(atBody.toByteArray(charset)),
327328
charset

0 commit comments

Comments
 (0)