diff --git a/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/GetTokenTests.kt b/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/GetTokenTests.kt index f933a2362..a73348796 100644 --- a/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/GetTokenTests.kt +++ b/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/GetTokenTests.kt @@ -27,6 +27,7 @@ import com.microsoft.identity.client.e2e.shadows.ShadowBaseController import com.microsoft.identity.client.e2e.utils.assertResult import com.microsoft.identity.internal.testutils.nativeauth.ConfigType import com.microsoft.identity.internal.testutils.nativeauth.api.models.NativeAuthTestConfig +import com.microsoft.identity.nativeauth.INativeAuthPublicClientApplication import com.microsoft.identity.nativeauth.statemachine.errors.GetAccessTokenError import com.microsoft.identity.nativeauth.statemachine.errors.SignInError import com.microsoft.identity.nativeauth.statemachine.results.GetAccessTokenResult @@ -41,13 +42,19 @@ class GetTokenTests : NativeAuthPublicClientApplicationAbstractTest() { private lateinit var resources: List + lateinit var application: INativeAuthPublicClientApplication + lateinit var config: NativeAuthTestConfig.Config + + private val defaultConfigType = ConfigType.SIGN_IN_PASSWORD + private val defaultChallengeTypes = listOf("password", "oob") + override fun setup() { super.setup() + config = getConfig(defaultConfigType) + application = setupPCA(config, defaultChallengeTypes) resources = config.resources } - override val defaultConfigType = ConfigType.SIGN_IN_PASSWORD - /** * Signing in with an invalid scope should make the API and the SDK return an error. */ diff --git a/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/NativeAuthPublicClientApplicationAbstractTest.kt b/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/NativeAuthPublicClientApplicationAbstractTest.kt index 138c99de2..2c75e8c71 100644 --- a/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/NativeAuthPublicClientApplicationAbstractTest.kt +++ b/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/NativeAuthPublicClientApplicationAbstractTest.kt @@ -27,7 +27,6 @@ import android.content.Context import androidx.test.core.app.ApplicationProvider import com.google.gson.Gson import com.google.gson.reflect.TypeToken -import com.microsoft.identity.client.Logger import com.microsoft.identity.client.PublicClientApplication import com.microsoft.identity.client.e2e.shadows.ShadowAndroidSdkStorageEncryptionManager import com.microsoft.identity.client.e2e.tests.IPublicClientApplicationTest @@ -39,18 +38,8 @@ import com.microsoft.identity.internal.testutils.labutils.LabConstants import com.microsoft.identity.internal.testutils.labutils.LabUserHelper import com.microsoft.identity.internal.testutils.labutils.LabUserQuery import com.microsoft.identity.internal.testutils.nativeauth.ConfigType -import com.microsoft.identity.internal.testutils.nativeauth.api.TemporaryEmailService import com.microsoft.identity.internal.testutils.nativeauth.api.models.NativeAuthTestConfig import com.microsoft.identity.nativeauth.INativeAuthPublicClientApplication -import com.microsoft.identity.nativeauth.statemachine.errors.SubmitCodeError -import com.microsoft.identity.nativeauth.statemachine.results.MFASubmitChallengeResult -import com.microsoft.identity.nativeauth.statemachine.results.ResetPasswordSubmitCodeResult -import com.microsoft.identity.nativeauth.statemachine.results.SignInSubmitCodeResult -import com.microsoft.identity.nativeauth.statemachine.results.SignUpSubmitCodeResult -import com.microsoft.identity.nativeauth.statemachine.states.MFARequiredState -import com.microsoft.identity.nativeauth.statemachine.states.ResetPasswordCodeRequiredState -import com.microsoft.identity.nativeauth.statemachine.states.SignInCodeRequiredState -import com.microsoft.identity.nativeauth.statemachine.states.SignUpCodeRequiredState import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.setMain @@ -70,12 +59,12 @@ import org.robolectric.annotation.LooperMode abstract class NativeAuthPublicClientApplicationAbstractTest : IPublicClientApplicationTest { companion object{ const val SHARED_PREFERENCES_NAME = "com.microsoft.identity.client.account_credential_cache" + const val INVALID_EMAIL = "email" + const val INVALID_PASSWORD = "password" } private lateinit var context: Context private lateinit var activity: Activity - lateinit var application: INativeAuthPublicClientApplication - lateinit var config: NativeAuthTestConfig.Config // Remove default Coroutine test timeout of 10 seconds. private val testDispatcher = StandardTestDispatcher() @@ -84,19 +73,13 @@ abstract class NativeAuthPublicClientApplicationAbstractTest : IPublicClientAppl return "" // Not needed for native auth flows } - abstract val defaultConfigType: ConfigType - @Before open fun setup() { context = ApplicationProvider.getApplicationContext() activity = Mockito.mock(Activity::class.java) Mockito.`when`(activity.applicationContext).thenReturn(context) - Logger.getInstance().setEnableLogcatLog(true) - Logger.getInstance().setEnablePII(true) - Logger.getInstance().setLogLevel(Logger.LogLevel.VERBOSE) CommandDispatcherHelper.clear() Dispatchers.setMain(testDispatcher) - setupPCA(defaultConfigType) } @After @@ -124,13 +107,15 @@ abstract class NativeAuthPublicClientApplicationAbstractTest : IPublicClientAppl return Gson().fromJson(secretValue, type) } - fun setupPCA(configType: ConfigType) { + fun getConfig(configType: ConfigType): NativeAuthTestConfig.Config { val secretValue = getConfigsThroughSecretValue() - config = secretValue?.get(configType.stringValue) ?: throw IllegalStateException("Config not $secretValue") - val challengeTypes = listOf("password", "oob") + return secretValue?.get(configType.stringValue) + ?: throw IllegalStateException("Config not $secretValue") + } - try { - application = PublicClientApplication.createNativeAuthPublicClientApplication( + fun setupPCA(config: NativeAuthTestConfig.Config, challengeTypes: List): INativeAuthPublicClientApplication { + return try { + PublicClientApplication.createNativeAuthPublicClientApplication( context, config.clientId, config.authorityUrl, @@ -139,6 +124,7 @@ abstract class NativeAuthPublicClientApplicationAbstractTest : IPublicClientAppl ) } catch (e: MsalException) { Assert.fail(e.message) + throw e } } diff --git a/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SSPRTest.kt b/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SSPRTest.kt index 5072904dd..6bb27acb4 100644 --- a/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SSPRTest.kt +++ b/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SSPRTest.kt @@ -26,22 +26,32 @@ package com.microsoft.identity.client.e2e.tests.network.nativeauth import com.microsoft.identity.client.e2e.utils.assertResult import com.microsoft.identity.internal.testutils.nativeauth.ConfigType import com.microsoft.identity.internal.testutils.nativeauth.api.TemporaryEmailService +import com.microsoft.identity.internal.testutils.nativeauth.api.models.NativeAuthTestConfig +import com.microsoft.identity.nativeauth.INativeAuthPublicClientApplication import com.microsoft.identity.nativeauth.statemachine.errors.ResetPasswordError import com.microsoft.identity.nativeauth.statemachine.results.ResetPasswordResult import com.microsoft.identity.nativeauth.statemachine.results.ResetPasswordStartResult import com.microsoft.identity.nativeauth.statemachine.results.ResetPasswordSubmitCodeResult -import com.microsoft.identity.nativeauth.statemachine.results.SignUpResult import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import org.junit.Assert -import org.junit.Ignore import org.junit.Test class SSPRTest : NativeAuthPublicClientApplicationAbstractTest() { private val tempEmailApi = TemporaryEmailService() - override val defaultConfigType = ConfigType.SSPR + lateinit var application: INativeAuthPublicClientApplication + lateinit var config: NativeAuthTestConfig.Config + + private val defaultConfigType = ConfigType.SSPR + private val defaultChallengeTypes = listOf("password", "oob") + + override fun setup() { + super.setup() + config = getConfig(defaultConfigType) + application = setupPCA(config, defaultChallengeTypes) + } @Test fun testSSPRErrorSimple() = runTest { @@ -55,7 +65,7 @@ class SSPRTest : NativeAuthPublicClientApplicationAbstractTest() { /** * Verify email with email OTP first and then reset password. - * (hero scenario 8 & 17, use case 3.1.1, Test case 46) + * (hero scenario 8 & 17, use case 3.1.1) */ @Test fun testSSPRSuccess() = runBlocking { diff --git a/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignInEmailOTPTest.kt b/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignInEmailOTPTest.kt index 3b931150e..559c723e0 100644 --- a/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignInEmailOTPTest.kt +++ b/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignInEmailOTPTest.kt @@ -26,6 +26,8 @@ package com.microsoft.identity.client.e2e.tests.network.nativeauth import com.microsoft.identity.client.e2e.utils.assertResult import com.microsoft.identity.internal.testutils.nativeauth.ConfigType import com.microsoft.identity.internal.testutils.nativeauth.api.TemporaryEmailService +import com.microsoft.identity.internal.testutils.nativeauth.api.models.NativeAuthTestConfig +import com.microsoft.identity.nativeauth.INativeAuthPublicClientApplication import com.microsoft.identity.nativeauth.statemachine.errors.SignInError import com.microsoft.identity.nativeauth.statemachine.errors.SubmitCodeError import com.microsoft.identity.nativeauth.statemachine.results.SignInResult @@ -38,11 +40,21 @@ class SignInEmailOTPTest : NativeAuthPublicClientApplicationAbstractTest() { private val tempEmailApi = TemporaryEmailService() - override val defaultConfigType = ConfigType.SIGN_IN_OTP + lateinit var application: INativeAuthPublicClientApplication + lateinit var config: NativeAuthTestConfig.Config + + private val defaultConfigType = ConfigType.SIGN_IN_OTP + private val defaultChallengeTypes = listOf("password", "oob") + + override fun setup() { + super.setup() + config = getConfig(defaultConfigType) + application = setupPCA(config, defaultChallengeTypes) + } /** * Use valid email and OTP to get token and sign in. - * (hero scenario 6, use case 2.2.1, Test case 30) + * (hero scenario 6, use case 2.2.1) */ @Test fun testSuccess() { @@ -60,7 +72,7 @@ class SignInEmailOTPTest : NativeAuthPublicClientApplicationAbstractTest() { /** * Use invalid email address to receive a "user not found" error. - * (use case 2.2.2, Test case 31) + * (use case 2.2.2) */ @Test fun testErrorIsUserNotFound() = runTest { @@ -74,7 +86,7 @@ class SignInEmailOTPTest : NativeAuthPublicClientApplicationAbstractTest() { /** * Use valid email address, but invalid OTP to receive "invalid code" error. - * (use case 2.2.7, Test case 35) + * (use case 2.2.7) */ @Test fun testErrorIsInvalidCode() { diff --git a/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignInEmailPasswordTest.kt b/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignInEmailPasswordTest.kt index e1bb3ec31..95ae8c6d4 100644 --- a/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignInEmailPasswordTest.kt +++ b/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignInEmailPasswordTest.kt @@ -25,11 +25,14 @@ package com.microsoft.identity.client.e2e.tests.network.nativeauth import com.microsoft.identity.client.e2e.utils.assertResult import com.microsoft.identity.internal.testutils.nativeauth.ConfigType +import com.microsoft.identity.internal.testutils.nativeauth.api.models.NativeAuthTestConfig +import com.microsoft.identity.nativeauth.INativeAuthPublicClientApplication import com.microsoft.identity.nativeauth.NativeAuthPublicClientApplicationConfiguration import com.microsoft.identity.nativeauth.statemachine.errors.SignInError import com.microsoft.identity.nativeauth.statemachine.results.MFARequiredResult import com.microsoft.identity.nativeauth.statemachine.results.SignInResult import com.microsoft.identity.nativeauth.statemachine.states.AwaitingMFAState +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import org.junit.Assert import org.junit.Assert.assertNotNull @@ -38,95 +41,66 @@ import org.junit.Ignore import org.junit.Test import org.mockito.Mockito import org.mockito.kotlin.spy +import org.robolectric.RuntimeEnvironment.application class SignInEmailPasswordTest : NativeAuthPublicClientApplicationAbstractTest() { - override val defaultConfigType = ConfigType.SIGN_IN_PASSWORD + lateinit var application: INativeAuthPublicClientApplication + lateinit var config: NativeAuthTestConfig.Config + + private val defaultConfigType = ConfigType.SIGN_IN_PASSWORD + private val defaultChallengeTypes = listOf("password", "oob") /** * Use valid email and password to get token. - * (hero scenario 15, use case 1.2.1, Test case 37) + * (hero scenario 15, use case 1.2.1) */ @Test - fun testSuccess() = runTest { - val username = config.email - val password = getSafePassword() - val result = application.signIn(username, password.toCharArray()) - Assert.assertTrue(result is SignInResult.Complete) + fun testSuccess() { + config = getConfig(defaultConfigType) + application = setupPCA(config, defaultChallengeTypes) + + runBlocking { + val username = config.email + val password = getSafePassword() + val result = application.signIn(username, password.toCharArray()) + assertTrue(result is SignInResult.Complete) + } } /** * Use invalid email address to receive a "user not found" error. - * (use case 1.2.2, Test case 38) + * (use case 1.2.2) */ @Test - fun testErrorIsUserNotFound() = runTest { - val username = config.email - val password = getSafePassword() - // Turn an existing username to a non-existing username - val alteredUsername = username.replace("@", "1234@") - val result = application.signIn(alteredUsername, password.toCharArray()) - Assert.assertTrue(result is SignInError) - Assert.assertTrue((result as SignInError).isUserNotFound()) + fun testErrorIsUserNotFound() { + config = getConfig(defaultConfigType) + application = setupPCA(config, defaultChallengeTypes) + + runBlocking { + val username = INVALID_EMAIL + val password = getSafePassword() + val result = application.signIn(username, password.toCharArray()) + assertTrue(result is SignInError) + assertTrue((result as SignInError).isUserNotFound()) + } } /** * Use valid email address and invalid password to receive a "invalid credentials" error. - * (use case 1.2.3, Test case 39) + * (use case 1.2.3) */ @Test - fun testErrorIsInvalidCredentials() = runTest { - val username = config.email - val password = getSafePassword() - // Turn correct password into an incorrect one - val alteredPassword = password + "1234" - val result = application.signIn(username, alteredPassword.toCharArray()) - Assert.assertTrue(result is SignInError) - Assert.assertTrue((result as SignInError).isInvalidCredentials()) - } - - @Test - @Ignore("Ignore until MFA is available on test slice") - fun testSignInMFASimple() = runTest { - val nativeAuthConfigField = application.javaClass.getDeclaredField("nativeAuthConfig") - nativeAuthConfigField.isAccessible = true - val config = nativeAuthConfigField.get(application) as NativeAuthPublicClientApplicationConfiguration - - val mfaRequiredResult = SignInResult.MFARequired( - nextState = AwaitingMFAState( - continuationToken = "1234", - correlationId = "abcd", - scopes = null, - config = config - ) - ) - val app = spy(application) - Mockito.doReturn(mfaRequiredResult) - .`when`(app).signIn("user", "password".toCharArray(), null) - - val result = app.signIn("user", "password".toCharArray(), null) - assertResult(result) - - // Initiate challenge, send code to email - val sendChallengeResult = (result as SignInResult.MFARequired).nextState.requestChallenge() - assertResult(sendChallengeResult) - (sendChallengeResult as MFARequiredResult.VerificationRequired) - assertNotNull(sendChallengeResult.sentTo) - assertNotNull(sendChallengeResult.codeLength) - assertNotNull(sendChallengeResult.channel) - - // Retrieve all methods to build additional "pick MFA method UI" - val authMethodsResult = sendChallengeResult.nextState.getAuthMethods() - assertResult(authMethodsResult) - (authMethodsResult as MFARequiredResult.SelectionRequired) - assertTrue(authMethodsResult.authMethods.isNotEmpty()) - - // call /challenge with specified ID - val sendChallengeResult2 = sendChallengeResult.nextState.requestChallenge(authMethodsResult.authMethods[0]) - assertResult(sendChallengeResult2) + fun testErrorIsInvalidCredentials() { + config = getConfig(defaultConfigType) + application = setupPCA(config, defaultChallengeTypes) - // Submit the user supplied code to the API - val submitCodeResult = (sendChallengeResult2 as MFARequiredResult.VerificationRequired).nextState.submitChallenge("1234") - assertResult(submitCodeResult) + runBlocking { + val username = config.email + val password = INVALID_PASSWORD + val result = application.signIn(username, password.toCharArray()) + assertTrue(result is SignInError) + assertTrue((result as SignInError).isInvalidCredentials()) + } } } diff --git a/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignInMFATest.kt b/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignInMFATest.kt index bb3b74a25..f3a21410c 100644 --- a/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignInMFATest.kt +++ b/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignInMFATest.kt @@ -27,6 +27,7 @@ import com.microsoft.identity.client.e2e.utils.assertResult import com.microsoft.identity.internal.testutils.nativeauth.ConfigType import com.microsoft.identity.internal.testutils.nativeauth.api.TemporaryEmailService import com.microsoft.identity.internal.testutils.nativeauth.api.models.NativeAuthTestConfig +import com.microsoft.identity.nativeauth.INativeAuthPublicClientApplication import com.microsoft.identity.nativeauth.statemachine.errors.MFASubmitChallengeError import com.microsoft.identity.nativeauth.statemachine.results.GetAccessTokenResult import com.microsoft.identity.nativeauth.statemachine.results.MFARequiredResult @@ -37,20 +38,18 @@ import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue import org.junit.Ignore import org.junit.Test -import java.lang.Thread.sleep class SignInMFATest : NativeAuthPublicClientApplicationAbstractTest() { private val tempEmailApi = TemporaryEmailService() - override var defaultConfigType = ConfigType.SIGN_IN_MFA_SINGLE_AUTH - private lateinit var resources: List - override fun setup() { - super.setup() - resources = config.resources - } + lateinit var application: INativeAuthPublicClientApplication + lateinit var config: NativeAuthTestConfig.Config + + private val defaultConfigType = ConfigType.SIGN_IN_MFA_SINGLE_AUTH + private val defaultChallengeTypes = listOf("password", "oob") /** * Full flow: @@ -64,9 +63,12 @@ class SignInMFATest : NativeAuthPublicClientApplicationAbstractTest() { * * Note: this test also asserts whether the scopes requested at sign in are present in the token that's received at the end of the flow */ - @Ignore("Fetching OTP code is unstable") @Test fun `test submit invalid challenge, request new challenge, submit correct challenge and complete MFA flow`() { + config = getConfig(defaultConfigType) + application = setupPCA(config, defaultChallengeTypes) + resources = config.resources + retryOperation { runBlocking { // Running with runBlocking to avoid default 10 second execution timeout. val username = config.email @@ -131,9 +133,12 @@ class SignInMFATest : NativeAuthPublicClientApplicationAbstractTest() { * * Note: this test also asserts whether the scopes requested at sign in are present in the token that's received at the end of the flow */ - @Ignore("Fetching OTP code is unstable") @Test fun `test get other auth methods, request challenge on specific auth method and complete MFA flow`() { + config = getConfig(defaultConfigType) + application = setupPCA(config, defaultChallengeTypes) + resources = config.resources + retryOperation { runBlocking { val scopeA = resources[0].scopes[0] @@ -199,14 +204,14 @@ class SignInMFATest : NativeAuthPublicClientApplicationAbstractTest() { * * Note: this test also asserts whether the scopes requested at sign in are present in the token that's received at the end of the flow */ - @Ignore("Fetching OTP code is unstable") @Test fun `test selection required, request challenge on specific auth method and complete MFA flow`() { + config = getConfig(ConfigType.SIGN_IN_MFA_MULTI_AUTH) + application = setupPCA(config, defaultChallengeTypes) + resources = config.resources + retryOperation { runBlocking { - val configType = ConfigType.SIGN_IN_MFA_MULTI_AUTH - setupPCA(configType) - val scopeA = resources[0].scopes[0] val scopeB = resources[0].scopes[1] diff --git a/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignUpEmailOTPAttributesTest.kt b/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignUpEmailOTPAttributesTest.kt index b29cea102..d2eede10f 100644 --- a/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignUpEmailOTPAttributesTest.kt +++ b/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignUpEmailOTPAttributesTest.kt @@ -26,23 +26,33 @@ package com.microsoft.identity.client.e2e.tests.network.nativeauth import com.microsoft.identity.client.e2e.utils.assertResult import com.microsoft.identity.internal.testutils.nativeauth.ConfigType import com.microsoft.identity.internal.testutils.nativeauth.api.TemporaryEmailService +import com.microsoft.identity.internal.testutils.nativeauth.api.models.NativeAuthTestConfig +import com.microsoft.identity.nativeauth.INativeAuthPublicClientApplication import com.microsoft.identity.nativeauth.UserAttributes -import com.microsoft.identity.nativeauth.statemachine.results.SignInResult import com.microsoft.identity.nativeauth.statemachine.results.SignUpResult import kotlinx.coroutines.runBlocking import org.junit.Assert -import org.junit.Ignore import org.junit.Test class SignUpEmailOTPAttributesTest : NativeAuthPublicClientApplicationAbstractTest() { private val tempEmailApi = TemporaryEmailService() - override val defaultConfigType = ConfigType.SIGN_UP_OTP_ATTRIBUTES + lateinit var application: INativeAuthPublicClientApplication + lateinit var config: NativeAuthTestConfig.Config + + private val defaultConfigType = ConfigType.SIGN_UP_OTP_ATTRIBUTES + private val defaultChallengeTypes = listOf("password", "oob") + + override fun setup() { + super.setup() + config = getConfig(defaultConfigType) + application = setupPCA(config, defaultChallengeTypes) + } /** * Signup user with custom attributes with verify OTP as last step. - * (hero scenario 2, use case 2.1.2, Test case 2) + * (hero scenario 2, use case 2.1.2) */ @Test fun testSuccessAttributesFirst() { @@ -63,7 +73,7 @@ class SignUpEmailOTPAttributesTest : NativeAuthPublicClientApplicationAbstractTe /** * Verify email OTP first and then collect custom attributes. - * (hero scenario 3, use case 2.1.3, Test case 3) + * (hero scenario 3, use case 2.1.3) */ @Test fun testSuccessAttributesLastSameScreen() { @@ -91,7 +101,7 @@ class SignUpEmailOTPAttributesTest : NativeAuthPublicClientApplicationAbstractTe /** * Verify email OTP first and then collect custom attributes in multiple steps (mimicking a multi-screen UX). - * (hero scenario 4, use case 2.1.4, Test case 4) + * (hero scenario 4, use case 2.1.4) */ @Test fun testSuccessAttributesLastMultipleScreens() { diff --git a/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignUpEmailOTPTest.kt b/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignUpEmailOTPTest.kt index 561d55dc1..1c191ba47 100644 --- a/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignUpEmailOTPTest.kt +++ b/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignUpEmailOTPTest.kt @@ -26,6 +26,11 @@ package com.microsoft.identity.client.e2e.tests.network.nativeauth import com.microsoft.identity.client.e2e.utils.assertResult import com.microsoft.identity.internal.testutils.nativeauth.ConfigType import com.microsoft.identity.internal.testutils.nativeauth.api.TemporaryEmailService +import com.microsoft.identity.internal.testutils.nativeauth.api.models.NativeAuthTestConfig +import com.microsoft.identity.nativeauth.INativeAuthPublicClientApplication +import com.microsoft.identity.nativeauth.statemachine.errors.SignUpError +import com.microsoft.identity.nativeauth.statemachine.results.SignInResult +import com.microsoft.identity.nativeauth.statemachine.results.SignUpResendCodeResult import com.microsoft.identity.nativeauth.statemachine.results.SignUpResult import kotlinx.coroutines.runBlocking import org.junit.Assert @@ -36,14 +41,21 @@ class SignUpEmailOTPTest : NativeAuthPublicClientApplicationAbstractTest() { private val tempEmailApi = TemporaryEmailService() - override val defaultConfigType = ConfigType.SIGN_UP_OTP + lateinit var application: INativeAuthPublicClientApplication + lateinit var config: NativeAuthTestConfig.Config + + private val defaultConfigType = ConfigType.SIGN_UP_OTP + private val defaultChallengeTypes = listOf("password", "oob") /** * Sign up with email + OTP. Verify email address using email OTP and sign up. - * (hero scenario 1, use case 2.1.1, Test case 1) + * (hero scenario 1, use case 2.1.1) */ @Test fun testSuccess() { + config = getConfig(defaultConfigType) + application = setupPCA(config, defaultChallengeTypes) + retryOperation { runBlocking { // Running with runBlocking to avoid default 10 second execution timeout. val user = tempEmailApi.generateRandomEmailAddress() @@ -56,4 +68,157 @@ class SignUpEmailOTPTest : NativeAuthPublicClientApplicationAbstractTest() { } } } + + /** + * Sign up with email + OTP. Resend email OTP. + * (hero scenario 1, use case 2.1.5) + */ + @Test + fun testResendCode() { + config = getConfig(defaultConfigType) + application = setupPCA(config, defaultChallengeTypes) + + retryOperation { + runBlocking { // Running with runBlocking to avoid default 10 second execution timeout. + val user = tempEmailApi.generateRandomEmailAddress() + val signUpResult = application.signUp(user) + assertResult(signUpResult) + val otp1 = tempEmailApi.retrieveCodeFromInbox(user) + val codeRequiredState = (signUpResult as SignUpResult.CodeRequired).nextState + val resendCodeResult = codeRequiredState.resendCode() + assertResult(resendCodeResult) + val otp2 = tempEmailApi.retrieveCodeFromInbox(user) + Assert.assertNotEquals(otp1, otp2) + } + } + } + + /** + * Sign up with email + OTP. User already exists with given email as email-otp account. + * (hero scenario 1, use case 2.1.6) + */ + @Test + fun testErrorUserExistAsOTP() { + config = getConfig(ConfigType.SIGN_IN_OTP) + application = setupPCA(config, defaultChallengeTypes) + + runBlocking { // Running with runBlocking to avoid default 10 second execution timeout. + val user = config.email + val signUpResult = application.signUp(user) + Assert.assertTrue(signUpResult is SignUpError) + Assert.assertTrue((signUpResult as SignUpError).isUserAlreadyExists()) + } + } + + /** + * Sign up with email + OTP. User already exists with given email as email-pw account. + */ + @Test + fun testErrorUserExistAsPassword() { + config = getConfig(ConfigType.SIGN_IN_PASSWORD) + application = setupPCA(config, defaultChallengeTypes) + + runBlocking { // Running with runBlocking to avoid default 10 second execution timeout. + val user = config.email + val signUpResult = application.signUp(user) + Assert.assertTrue(signUpResult is SignUpError) + Assert.assertTrue((signUpResult as SignUpError).isUserAlreadyExists()) + } + } + + /** + * Sign up with email + OTP. User already exists with given email as social account. + * (use case 2.1.7) + */ + @Ignore("TODO: Add social account in the tenant.") + @Test + fun testErrorUserExistAsSocial() { + config = getConfig(ConfigType.SIGN_IN_OTP) + application = setupPCA(config, defaultChallengeTypes) + + runBlocking { // Running with runBlocking to avoid default 10 second execution timeout. + val user = config.email + val signUpResult = application.signUp(user) + Assert.assertTrue(signUpResult is SignUpError) + Assert.assertTrue((signUpResult as SignUpError).isUserAlreadyExists()) + } + } + + /** + * Sign up with email + OTP. Developer makes a request with invalid format email address. + * (use case 2.1.8) + */ + @Test + fun testErrorInvalidEmailFormat() { + config = getConfig(defaultConfigType) + application = setupPCA(config, defaultChallengeTypes) + + runBlocking { // Running with runBlocking to avoid default 10 second execution timeout. + val user = INVALID_EMAIL + val signUpResult = application.signUp(user) + Assert.assertTrue(signUpResult is SignUpError) + Assert.assertTrue((signUpResult as SignUpError).isInvalidUsername()) + } + } + + /** + * Sign up with email + OTP. Developer can opt to get AT and/or ID token (aka sign in after signup). + * (use case 2.1.9) + */ + @Test + fun testSignInAfterSignUp() { + config = getConfig(defaultConfigType) + application = setupPCA(config, defaultChallengeTypes) + + retryOperation { + runBlocking { // Running with runBlocking to avoid default 10 second execution timeout. + val user = tempEmailApi.generateRandomEmailAddress() + val signUpResult = application.signUp(user) + assertResult(signUpResult) + val otp = tempEmailApi.retrieveCodeFromInbox(user) + val submitCodeResult = (signUpResult as SignUpResult.CodeRequired).nextState.submitCode(otp) + assertResult(submitCodeResult) + val signWithContinuationResult = (submitCodeResult as SignUpResult.Complete).nextState.signIn() + assertResult(signWithContinuationResult) + } + } + } + + /** + * Sign up with email + OTP. Server requires password authentication, which is not supported by the developer (aka redirect flow). + * (use case 2.1.10) + */ + @Test + fun testErrorRedirect() { + config = getConfig(ConfigType.SIGN_UP_PASSWORD) + application = setupPCA(config, listOf("oob")) + + runBlocking { // Running with runBlocking to avoid default 10 second execution timeout. + val user = tempEmailApi.generateRandomEmailAddress() + val signUpResult = application.signUp(user) + Assert.assertTrue(signUpResult is SignUpError) + Assert.assertTrue((signUpResult as SignUpError).isBrowserRequired()) + } + } + + /** + * Sign up with email + OTP. Server requires password authentication, which is supported by the developer. + * (hero scenario 11, use case 2.1.11) + */ + @Test + fun testPasswordRequired() { + config = getConfig(ConfigType.SIGN_UP_PASSWORD) + application = setupPCA(config, defaultChallengeTypes) + + retryOperation { + runBlocking { // Running with runBlocking to avoid default 10 second execution timeout. + val user = tempEmailApi.generateRandomEmailAddress() + val signUpResult = application.signUp(user) + assertResult(signUpResult) + val otp = tempEmailApi.retrieveCodeFromInbox(user) + val submitCodeResult = (signUpResult as SignUpResult.CodeRequired).nextState.submitCode(otp) + assertResult(submitCodeResult) + } + } + } } \ No newline at end of file diff --git a/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignUpEmailPasswordAttributesTest.kt b/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignUpEmailPasswordAttributesTest.kt index 251ade4af..9b330b9de 100644 --- a/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignUpEmailPasswordAttributesTest.kt +++ b/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignUpEmailPasswordAttributesTest.kt @@ -26,25 +26,36 @@ package com.microsoft.identity.client.e2e.tests.network.nativeauth import com.microsoft.identity.client.e2e.utils.assertResult import com.microsoft.identity.internal.testutils.nativeauth.ConfigType import com.microsoft.identity.internal.testutils.nativeauth.api.TemporaryEmailService +import com.microsoft.identity.internal.testutils.nativeauth.api.models.NativeAuthTestConfig +import com.microsoft.identity.nativeauth.INativeAuthPublicClientApplication import com.microsoft.identity.nativeauth.UserAttributes import com.microsoft.identity.nativeauth.statemachine.results.SignUpResult import kotlinx.coroutines.runBlocking import org.junit.Assert -import org.junit.Ignore import org.junit.Test class SignUpEmailPasswordAttributesTest : NativeAuthPublicClientApplicationAbstractTest() { private val tempEmailApi = TemporaryEmailService() - override val defaultConfigType = ConfigType.SIGN_UP_PASSWORD_ATTRIBUTES + lateinit var application: INativeAuthPublicClientApplication + lateinit var config: NativeAuthTestConfig.Config + + private val defaultConfigType = ConfigType.SIGN_UP_PASSWORD_ATTRIBUTES + private val defaultChallengeTypes = listOf("password", "oob") + + override fun setup() { + super.setup() + config = getConfig(defaultConfigType) + application = setupPCA(config, defaultChallengeTypes) + } /** * Sign up with password and attributes on start, then verify OTP as last step. * Mimic a 2-step UX: * 1. Capture email address, password and attributes * 2. Validate OTP. - * (hero scenario 10, use case 1.1.3, Test case 15) + * (hero scenario 10, use case 1.1.3) */ @Test fun testEmailPasswordAttributesOnSameScreen() { @@ -73,7 +84,7 @@ class SignUpEmailPasswordAttributesTest : NativeAuthPublicClientApplicationAbstr * 1. Capture email address & validate * 2. Set password * 3. Set custom attributes. - * (hero scenario 12, use case 1.1.6) - Test case 28 + * (hero scenario 12, use case 1.1.6) */ @Test fun testSeparateEmailPasswordAndAttributesOnSameScreen() { @@ -109,7 +120,7 @@ class SignUpEmailPasswordAttributesTest : NativeAuthPublicClientApplicationAbstr * 3. Set first attribute. * 4. Set second attribute. * 5. etc. - * ((hero scenario 13) - Test case 29 + * ((hero scenario 13) */ @Test fun testSeparateEmailPasswordAndAttributesOnMultipleScreens() { diff --git a/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignUpEmailPasswordTest.kt b/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignUpEmailPasswordTest.kt index bcf255a4c..9bd3365f6 100644 --- a/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignUpEmailPasswordTest.kt +++ b/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignUpEmailPasswordTest.kt @@ -26,10 +26,13 @@ package com.microsoft.identity.client.e2e.tests.network.nativeauth import com.microsoft.identity.client.e2e.utils.assertResult import com.microsoft.identity.internal.testutils.nativeauth.ConfigType import com.microsoft.identity.internal.testutils.nativeauth.api.TemporaryEmailService +import com.microsoft.identity.internal.testutils.nativeauth.api.models.NativeAuthTestConfig +import com.microsoft.identity.nativeauth.INativeAuthPublicClientApplication import com.microsoft.identity.nativeauth.statemachine.errors.SignUpError +import com.microsoft.identity.nativeauth.statemachine.results.SignInResult +import com.microsoft.identity.nativeauth.statemachine.results.SignUpResendCodeResult import com.microsoft.identity.nativeauth.statemachine.results.SignUpResult import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.runTest import org.junit.Assert import org.junit.Ignore import org.junit.Test @@ -38,10 +41,18 @@ class SignUpEmailPasswordTest : NativeAuthPublicClientApplicationAbstractTest() private val tempEmailApi = TemporaryEmailService() - override val defaultConfigType = ConfigType.SIGN_UP_PASSWORD + lateinit var application: INativeAuthPublicClientApplication + lateinit var config: NativeAuthTestConfig.Config + + private val defaultConfigType = ConfigType.SIGN_UP_PASSWORD + private val defaultChallengeTypes = listOf("password", "oob") + @Test fun testSignUpErrorSimple() { + config = getConfig(defaultConfigType) + application = setupPCA(config, defaultChallengeTypes) + retryOperation { runBlocking { val user = tempEmailApi.generateRandomEmailAddress() @@ -53,11 +64,14 @@ class SignUpEmailPasswordTest : NativeAuthPublicClientApplicationAbstractTest() } /** - * Set email and password (mimicking one combined screen for email & password collection), and then verify email OTP as last step - * (hero scenario 9, use case 1.1.1, Test case 13) + * Sign up with email + password. Set email and password (mimicking one combined screen for email & password collection), and then verify email OTP as last step + * (hero scenario 9, use case 1.1.1) */ @Test fun testSuccessOTPLast() { + config = getConfig(defaultConfigType) + application = setupPCA(config, defaultChallengeTypes) + retryOperation { runBlocking { // Running with runBlocking to avoid default 10 second execution timeout. val user = tempEmailApi.generateRandomEmailAddress() @@ -73,11 +87,39 @@ class SignUpEmailPasswordTest : NativeAuthPublicClientApplicationAbstractTest() } /** - * Verify email address using email OTP and then set password (mimicking email and password collection on separate screens). - * (use case 1.1.4, Test case 16) + * Sign up with email + password. Resend email OOB. + * (use case 1.1.2) + */ + @Test + fun testResendEmailOOB() { + config = getConfig(defaultConfigType) + application = setupPCA(config, defaultChallengeTypes) + + retryOperation { + runBlocking { // Running with runBlocking to avoid default 10 second execution timeout. + val user = tempEmailApi.generateRandomEmailAddress() + val password = getSafePassword() + val signUpResult = application.signUp(user, password.toCharArray()) + assertResult(signUpResult) + val otp1 = tempEmailApi.retrieveCodeFromInbox(user) + val codeRequiredState = (signUpResult as SignUpResult.CodeRequired).nextState + val resendCodeResult = codeRequiredState.resendCode() + assertResult(resendCodeResult) + val otp2 = tempEmailApi.retrieveCodeFromInbox(user) + Assert.assertNotEquals(otp1, otp2) + } + } + } + + /** + * Sign up with email + password. Verify email address using email OTP and then set password (mimicking email and password collection on separate screens). + * (use case 1.1.4) */ @Test fun testSuccessOTPFirst() { + config = getConfig(defaultConfigType) + application = setupPCA(config, defaultChallengeTypes) + retryOperation { runBlocking { // Running with runBlocking to avoid default 10 second execution timeout. val user = tempEmailApi.generateRandomEmailAddress() @@ -93,4 +135,129 @@ class SignUpEmailPasswordTest : NativeAuthPublicClientApplicationAbstractTest() } } } + + /** + * Sign up with email + password. Verify email address using email OTP, resend OTP and then set password. + * (use case 1.1.5) + */ + @Test + fun testSuccessOTPResend() { + config = getConfig(defaultConfigType) + application = setupPCA(config, defaultChallengeTypes) + + retryOperation { + runBlocking { // Running with runBlocking to avoid default 10 second execution timeout. + val user = tempEmailApi.generateRandomEmailAddress() + val signUpResult = application.signUp(user) + assertResult(signUpResult) + + val resendCodeResult = (signUpResult as SignUpResult.CodeRequired).nextState.resendCode() + assertResult(resendCodeResult) + + val otp = tempEmailApi.retrieveCodeFromInbox(user) + val submitCodeResult = (resendCodeResult as SignUpResendCodeResult.Success).nextState.submitCode(otp) + assertResult(submitCodeResult) + + val submitPasswordResult = (submitCodeResult as SignUpResult.PasswordRequired).nextState.submitPassword(getSafePassword().toCharArray()) + Assert.assertTrue(submitPasswordResult is SignUpResult.Complete) + } + } + } + + /** + * Sign up with email + password. User already exists with given email as email-pw account. + * (use case 1.1.10) + */ + @Test + fun testErrorUserExistAsPassword() { + config = getConfig(ConfigType.SIGN_IN_PASSWORD) + application = setupPCA(config, defaultChallengeTypes) + + runBlocking { // Running with runBlocking to avoid default 10 second execution timeout. + val user = config.email + val password = getSafePassword() + val signUpResult = application.signUp(user, password.toCharArray()) + Assert.assertTrue(signUpResult is SignUpError) + Assert.assertTrue((signUpResult as SignUpError).isUserAlreadyExists()) + } + } + + /** + * Sign up with email + password. User already exists with given email as social account. + * (use case 1.1.11) + */ + @Ignore("TODO: Add social account in the tenant.") + @Test + fun testErrorUserExistAsSocial() { + config = getConfig(ConfigType.SIGN_IN_PASSWORD) + application = setupPCA(config, defaultChallengeTypes) + + runBlocking { // Running with runBlocking to avoid default 10 second execution timeout. + val user = config.email + val password = getSafePassword() + val signUpResult = application.signUp(user, password.toCharArray()) + Assert.assertTrue(signUpResult is SignUpError) + Assert.assertTrue((signUpResult as SignUpError).isUserAlreadyExists()) + } + } + + /** + * Sign up with email + password. Developer makes a request with invalid format email address. + * (use case 1.1.12) + */ + @Test + fun testErrorInvalidEmailFormat() { + config = getConfig(defaultConfigType) + application = setupPCA(config, defaultChallengeTypes) + + runBlocking { // Running with runBlocking to avoid default 10 second execution timeout. + val user = INVALID_EMAIL + val password = getSafePassword() + val signUpResult = application.signUp(user, password.toCharArray()) + Assert.assertTrue(signUpResult is SignUpError) + Assert.assertTrue((signUpResult as SignUpError).isInvalidUsername()) + } + } + + /** + * Sign up with email + password. Developer makes a request with password that does not match password complexity requirements set on portal. + * (use case 1.1.13) + */ + @Test + fun testErrorInvalidPasswordFormat() { + config = getConfig(defaultConfigType) + application = setupPCA(config, defaultChallengeTypes) + + runBlocking { // Running with runBlocking to avoid default 10 second execution timeout. + val user = tempEmailApi.generateRandomEmailAddress() + val password = INVALID_PASSWORD + val signUpResult = application.signUp(user, password.toCharArray()) + Assert.assertTrue(signUpResult is SignUpError) + Assert.assertTrue((signUpResult as SignUpError).isInvalidPassword()) + } + } + + /** + * Sign up with email + password. Developer can opt to get AT and/or ID token (aka sign in after signup). + * (use case 1.1.14) + */ + @Test + fun testSignInAfterSignUp() { + config = getConfig(defaultConfigType) + application = setupPCA(config, defaultChallengeTypes) + + retryOperation { + runBlocking { // Running with runBlocking to avoid default 10 second execution timeout. + val user = tempEmailApi.generateRandomEmailAddress() + val password = getSafePassword() + val signUpResult = application.signUp(user, password.toCharArray()) + assertResult(signUpResult) + val otp = tempEmailApi.retrieveCodeFromInbox(user) + val submitCodeResult = (signUpResult as SignUpResult.CodeRequired).nextState.submitCode(otp) + assertResult(submitCodeResult) + val signWithContinuationResult = (submitCodeResult as SignUpResult.Complete).nextState.signIn() + assertResult(signWithContinuationResult) + } + } + } } \ No newline at end of file