Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Android E2E Sign Up, Fixes AB#3073857 #2213

Open
wants to merge 27 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
44afe84
Apply LoggerCheckHelper.kt (applied for those NativeAuthPublicClientA…
Yuki-YuXin Oct 23, 2024
17f6a07
Update the NativeAuthPublicClientApplicationAbstractTest structure to…
Yuki-YuXin Oct 23, 2024
3a1df81
Finish sign up otp
Yuki-YuXin Oct 29, 2024
b1150ff
Merge branch 'dev' into yuki/signup-e2e
Yuki-YuXin Oct 31, 2024
18326e3
Remove ignore tag
Yuki-YuXin Oct 31, 2024
839611a
Comment out the logger check
Yuki-YuXin Oct 31, 2024
45b81a6
Clean up the name
Yuki-YuXin Oct 31, 2024
145ff57
Remove logger
Yuki-YuXin Oct 31, 2024
122b73c
Add loggerCheckHelper
Yuki-YuXin Oct 31, 2024
43f9339
loggerCheck = setupLoggerCheckHelper()
Yuki-YuXin Oct 31, 2024
db78d7f
Double checking the work scopes have been all included
Yuki-YuXin Nov 1, 2024
a2da64d
Remove LoggerCheckHelper.kt experiment since it is out of scope
Yuki-YuXin Nov 19, 2024
7178cec
Fix SignUpEmailPasswordTest.kt testSignInAfterSignUp()
Yuki-YuXin Nov 20, 2024
e897b06
Remove LoggerCheckHelper.kt
Yuki-YuXin Nov 20, 2024
627ef91
Merge branch 'dev' into yuki/signup-e2e
Yuki-YuXin Nov 20, 2024
91bac62
Remove @Ignore MFA
Yuki-YuXin Nov 20, 2024
4b8c24c
Merge remote-tracking branch 'origin/yuki/signup-e2e' into yuki/signu…
Yuki-YuXin Nov 20, 2024
6a1710d
Re-add the ignore tag because of the failure
Yuki-YuXin Nov 21, 2024
242b66c
Address testResendCode comment and situation
Yuki-YuXin Nov 21, 2024
bd75314
ALl test cases apply new AbstractTest to extract configuration initia…
Yuki-YuXin Nov 26, 2024
6027036
Merge branch 'dev' into yuki/signup-e2e
Yuki-YuXin Nov 26, 2024
c2e96b6
Remove overlap MFA test case
Yuki-YuXin Nov 26, 2024
38f1f2d
Add config modification for the testSuccess, which causes the failure
Yuki-YuXin Nov 26, 2024
adc64bc
Merge branch 'dev' into yuki/signup-e2e
Yuki-YuXin Nov 27, 2024
fa13a6a
Address comments
Yuki-YuXin Nov 27, 2024
5abc55e
Merge remote-tracking branch 'origin/yuki/signup-e2e' into yuki/signu…
Yuki-YuXin Nov 27, 2024
4cfcce0
Use SIGN_IN_MFA_MULTI_AUTH config instead of SINGLE for the failed MF…
Yuki-YuXin Nov 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -41,13 +42,19 @@ class GetTokenTests : NativeAuthPublicClientApplicationAbstractTest() {

private lateinit var resources: List<NativeAuthTestConfig.Resource>

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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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()
Expand All @@ -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)
spetrescu84 marked this conversation as resolved.
Show resolved Hide resolved
Logger.getInstance().setEnablePII(true)
Logger.getInstance().setLogLevel(Logger.LogLevel.VERBOSE)
CommandDispatcherHelper.clear()
Dispatchers.setMain(testDispatcher)
setupPCA(defaultConfigType)
}

@After
Expand Down Expand Up @@ -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<String>): INativeAuthPublicClientApplication {
return try {
PublicClientApplication.createNativeAuthPublicClientApplication(
context,
config.clientId,
config.authorityUrl,
Expand All @@ -139,6 +124,7 @@ abstract class NativeAuthPublicClientApplicationAbstractTest : IPublicClientAppl
)
} catch (e: MsalException) {
Assert.fail(e.message)
throw e
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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() {
Expand All @@ -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 {
Expand All @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
spetrescu84 marked this conversation as resolved.
Show resolved Hide resolved
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<SignInResult.MFARequired>(result)

// Initiate challenge, send code to email
val sendChallengeResult = (result as SignInResult.MFARequired).nextState.requestChallenge()
assertResult<MFARequiredResult.VerificationRequired>(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<MFARequiredResult.SelectionRequired>(authMethodsResult)
(authMethodsResult as MFARequiredResult.SelectionRequired)
assertTrue(authMethodsResult.authMethods.isNotEmpty())

// call /challenge with specified ID
val sendChallengeResult2 = sendChallengeResult.nextState.requestChallenge(authMethodsResult.authMethods[0])
assertResult<MFARequiredResult.VerificationRequired>(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<SignInResult.Complete>(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())
}
}
}
Loading
Loading