diff --git a/common b/common index 0a5a5e7e6..a346e00d4 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit 0a5a5e7e67f52ace577602ff7645bcc1918b55bf +Subproject commit a346e00d43a2ab5572bbb5a11e362686c905199b diff --git a/msal/src/main/java/com/microsoft/identity/client/internal/CommandParametersAdapter.java b/msal/src/main/java/com/microsoft/identity/client/internal/CommandParametersAdapter.java index 28d41e459..8059b216d 100644 --- a/msal/src/main/java/com/microsoft/identity/client/internal/CommandParametersAdapter.java +++ b/msal/src/main/java/com/microsoft/identity/client/internal/CommandParametersAdapter.java @@ -61,9 +61,7 @@ import com.microsoft.identity.common.java.dto.AccountRecord; import com.microsoft.identity.common.java.exception.ClientException; import com.microsoft.identity.common.java.nativeauth.authorities.NativeAuthCIAMAuthority; -import com.microsoft.identity.common.java.nativeauth.commands.parameters.GetAuthMethodsCommandParameters; -import com.microsoft.identity.common.java.nativeauth.commands.parameters.MFADefaultChallengeCommandParameters; -import com.microsoft.identity.common.java.nativeauth.commands.parameters.MFASelectedDefaultChallengeCommandParameters; +import com.microsoft.identity.common.java.nativeauth.commands.parameters.MFAChallengeAuthMethodCommandParameters; import com.microsoft.identity.common.java.nativeauth.commands.parameters.MFASubmitChallengeCommandParameters; import com.microsoft.identity.common.java.nativeauth.commands.parameters.ResetPasswordResendCodeCommandParameters; import com.microsoft.identity.common.java.nativeauth.commands.parameters.ResetPasswordStartCommandParameters; @@ -677,6 +675,7 @@ public static SignInSubmitCodeCommandParameters createSignInSubmitCodeCommandPar .continuationToken(continuationToken) .authenticationScheme(authenticationScheme) .challengeType(configuration.getChallengeTypes()) + .isMFAGrantType(false) .code(code) .scopes(scopes) .correlationId(correlationId) @@ -778,54 +777,6 @@ public static SignInSubmitPasswordCommandParameters createSignInSubmitPasswordCo return commandParameters; } - /** - * Creates command parameter for [{@link com.microsoft.identity.common.nativeauth.internal.commands.MFAChallengeCommand}] of Native Auth - * @param configuration PCA configuration - * @param tokenCache token cache for storing results - * @param correlationId correlation ID to use in the API request, taken from the previous request in the flow - * @param continuationToken continuation token - * @param scopes scopes requested during sign in flow - * @return Command parameter object - * @throws ClientException - */ - public static MFADefaultChallengeCommandParameters createMFADefaultChallengeCommandParameters( - @NonNull final NativeAuthPublicClientApplicationConfiguration configuration, - @NonNull final OAuth2TokenCache tokenCache, - @NonNull final String continuationToken, - @NonNull final String correlationId, - final List scopes) throws ClientException { - - final NativeAuthCIAMAuthority authority = ((NativeAuthCIAMAuthority) configuration.getDefaultAuthority()); - - final AbstractAuthenticationScheme authenticationScheme = AuthenticationSchemeFactory.createScheme( - AndroidPlatformComponentsFactory.createFromContext(configuration.getAppContext()), - null - ); - - final MFADefaultChallengeCommandParameters commandParameters = - MFADefaultChallengeCommandParameters.builder() - .platformComponents(AndroidPlatformComponentsFactory.createFromContext(configuration.getAppContext())) - .applicationName(configuration.getAppContext().getPackageName()) - .applicationVersion(getPackageVersion(configuration.getAppContext())) - .clientId(configuration.getClientId()) - .isSharedDevice(configuration.getIsSharedDevice()) - .redirectUri(configuration.getRedirectUri()) - .oAuth2TokenCache(tokenCache) - .requiredBrokerProtocolVersion(configuration.getRequiredBrokerProtocolVersion()) - .sdkType(SdkType.MSAL) - .sdkVersion(PublicClientApplication.getSdkVersion()) - .powerOptCheckEnabled(configuration.isPowerOptCheckForEnabled()) - .authority(authority) - .authenticationScheme(authenticationScheme) - .continuationToken(continuationToken) - .scopes(scopes) - .challengeType(configuration.getChallengeTypes()) - .correlationId(correlationId) - .build(); - - return commandParameters; - } - /** * Creates command parameter for [{@link com.microsoft.identity.common.nativeauth.internal.commands.MFAChallengeCommand}] of Native Auth * @param configuration PCA configuration @@ -836,7 +787,7 @@ public static MFADefaultChallengeCommandParameters createMFADefaultChallengeComm * @return Command parameter object * @throws ClientException */ - public static MFASelectedDefaultChallengeCommandParameters createMFASelectedChallengeCommandParameters( + public static MFAChallengeAuthMethodCommandParameters createMFAChallengeAuthMethodCommandParameters( @NonNull final NativeAuthPublicClientApplicationConfiguration configuration, @NonNull final OAuth2TokenCache tokenCache, @NonNull final String continuationToken, @@ -853,8 +804,8 @@ public static MFASelectedDefaultChallengeCommandParameters createMFASelectedChal final String authMethodId = authMethod.getId(); - final MFASelectedDefaultChallengeCommandParameters commandParameters = - MFASelectedDefaultChallengeCommandParameters.builder() + final MFAChallengeAuthMethodCommandParameters commandParameters = + MFAChallengeAuthMethodCommandParameters.builder() .platformComponents(AndroidPlatformComponentsFactory.createFromContext(configuration.getAppContext())) .applicationName(configuration.getAppContext().getPackageName()) .applicationVersion(getPackageVersion(configuration.getAppContext())) @@ -927,44 +878,6 @@ public static MFASubmitChallengeCommandParameters createMFASubmitChallengeComman return commandParameters; } - /** - * Creates command parameter for [{@link com.microsoft.identity.common.nativeauth.internal.commands.GetAuthMethodsCommand}] of Native Auth - * @param configuration PCA configuration - * @param tokenCache token cache for storing results - * @param correlationId correlation ID to use in the API request, taken from the previous request in the flow - * @param continuationToken Continuation token - * @return Command parameter object - */ - public static GetAuthMethodsCommandParameters createGetAuthMethodsCommandParameters( - @NonNull final NativeAuthPublicClientApplicationConfiguration configuration, - @NonNull final OAuth2TokenCache tokenCache, - @NonNull final String continuationToken, - @NonNull final String correlationId) { - - final NativeAuthCIAMAuthority authority = ((NativeAuthCIAMAuthority) configuration.getDefaultAuthority()); - - final GetAuthMethodsCommandParameters commandParameters = - GetAuthMethodsCommandParameters.builder() - .platformComponents(AndroidPlatformComponentsFactory.createFromContext(configuration.getAppContext())) - .applicationName(configuration.getAppContext().getPackageName()) - .applicationVersion(getPackageVersion(configuration.getAppContext())) - .clientId(configuration.getClientId()) - .isSharedDevice(configuration.getIsSharedDevice()) - .redirectUri(configuration.getRedirectUri()) - .oAuth2TokenCache(tokenCache) - .requiredBrokerProtocolVersion(configuration.getRequiredBrokerProtocolVersion()) - .sdkType(SdkType.MSAL) - .sdkVersion(PublicClientApplication.getSdkVersion()) - .powerOptCheckEnabled(configuration.isPowerOptCheckForEnabled()) - .authority(authority) - .continuationToken(continuationToken) - .challengeType(configuration.getChallengeTypes()) - .correlationId(correlationId) - .build(); - - return commandParameters; - } - /** * Creates command parameter for [ResetPasswordStartCommand] of Native Auth. * @param configuration PCA configuration diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/NativeAuthPublicClientApplication.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/NativeAuthPublicClientApplication.kt index 45cfe9866..497a72039 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/NativeAuthPublicClientApplication.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/NativeAuthPublicClientApplication.kt @@ -768,7 +768,8 @@ class NativeAuthPublicClientApplication( correlationId = result.correlationId, scopes = scopes, config = nativeAuthConfig - ) + ), + authMethods = result.authMethods.toListOfAuthMethods() ) } diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/errors/MFAErrors.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/errors/MFAErrors.kt index b6c6a583f..27ffe9b2b 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/errors/MFAErrors.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/errors/MFAErrors.kt @@ -1,6 +1,5 @@ package com.microsoft.identity.nativeauth.statemachine.errors -import com.microsoft.identity.nativeauth.statemachine.results.MFAGetAuthMethodsResult import com.microsoft.identity.nativeauth.statemachine.results.MFARequiredResult import com.microsoft.identity.nativeauth.statemachine.results.MFASubmitChallengeResult @@ -26,27 +25,6 @@ class MFARequestChallengeError( override var exception: Exception? = null ): MFARequiredResult, BrowserRequiredError, Error(errorType = errorType, error = error, errorMessage= errorMessage, correlationId = correlationId, errorCodes = errorCodes, exception = exception) -/** - * MFA get authentication methods error. Use the utility methods of this class - * to identify and handle the error. This error is produced by - * [com.microsoft.identity.nativeauth.statemachine.states.MFARequiredState.getAuthMethods] - * @param errorType the error type value of the error that occurred. - * @param error the error returned by the authentication server. - * @param errorMessage the error message returned by the authentication server. - * @param correlationId a unique identifier for the request that can help in diagnostics. - * @param errorCodes a list of specific error codes returned by the authentication server. - * @param exception an internal unexpected exception that happened. - */ -class MFAGetAuthMethodsError( - override val errorType: String? = null, - override val error: String? = null, - override val errorMessage: String?, - override val correlationId: String, - override val errorCodes: List? = null, - val subError: String? = null, - override var exception: Exception? = null -): MFAGetAuthMethodsResult, BrowserRequiredError, Error(errorType = errorType, error = error, errorMessage= errorMessage, correlationId = correlationId, errorCodes = errorCodes, exception = exception) - /** * MFA submit challenge error. The user should use the utility methods of this class * to identify and handle the error. This error is produced by diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/results/MFAResult.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/results/MFAResult.kt index 6c87895b6..f43a36c38 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/results/MFAResult.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/results/MFAResult.kt @@ -23,7 +23,6 @@ package com.microsoft.identity.nativeauth.statemachine.results -import com.microsoft.identity.nativeauth.AuthMethod import com.microsoft.identity.nativeauth.statemachine.states.MFARequiredState /** @@ -46,26 +45,8 @@ interface MFARequiredResult: Result { val sentTo: String, val channel: String, ) : MFARequiredResult, Result.SuccessResult(nextState = nextState) - - /** - * Selection required result, which indicates that a specific authentication method must be selected, which - * the server will send the challenge to (once sendChallenge() is called). - * - * @param nextState [com.microsoft.identity.nativeauth.statemachine.states.MFARequiredState] the current state of the flow with follow-on methods. - * @param authMethods the authentication methods that can be used to complete the challenge flow. - */ - class SelectionRequired( - override val nextState: MFARequiredState, - val authMethods: List - ) : MFARequiredResult, MFAGetAuthMethodsResult, Result.SuccessResult(nextState = nextState) } -/** - * Results related to get authentication methods operation, produced by - * [com.microsoft.identity.nativeauth.statemachine.states.MFARequiredState.getAuthMethods] - */ -interface MFAGetAuthMethodsResult : Result - /** * Results related to MFA submit challenge operation, produced by * [com.microsoft.identity.nativeauth.statemachine.states.MFARequiredState.submitChallenge] diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/results/SignInResult.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/results/SignInResult.kt index 9c32fd675..2e1e79f9a 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/results/SignInResult.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/results/SignInResult.kt @@ -82,8 +82,9 @@ interface SignInResult : Result { * @param nextState [com.microsoft.identity.nativeauth.statemachine.states.AwaitingMFAState] the current state of the flow with follow-on methods. */ class MFARequired( - override val nextState: AwaitingMFAState - ) : SignInResult, Result.SuccessResult(nextState = nextState), SignInSubmitPasswordResult + override val nextState: AwaitingMFAState, + val authMethods: List + ) : Result.SuccessResult(nextState = nextState), SignInResult, SignInSubmitPasswordResult /** * StrongAuthMethodRegistration Result, which indicates that a registration of a strong authentication method is required to continue. @@ -94,7 +95,7 @@ interface SignInResult : Result { class StrongAuthMethodRegistrationRequired( override val nextState: RegisterStrongAuthState, val authMethods: List - ) : SignInResult, SignInSubmitPasswordResult, Result.SuccessResult(nextState = nextState) + ) : Result.SuccessResult(nextState = nextState), SignInResult, SignInSubmitPasswordResult } /** diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/MFAStates.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/MFAStates.kt index 2dbcaf149..d4f840167 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/MFAStates.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/MFAStates.kt @@ -32,14 +32,12 @@ import com.microsoft.identity.common.java.controllers.CommandDispatcher import com.microsoft.identity.common.java.eststelemetry.PublicApiId import com.microsoft.identity.common.java.logging.LogSession import com.microsoft.identity.common.java.logging.Logger -import com.microsoft.identity.common.java.nativeauth.controllers.results.GetAuthMethodsCommandResult import com.microsoft.identity.common.java.nativeauth.controllers.results.INativeAuthCommandResult import com.microsoft.identity.common.java.nativeauth.controllers.results.MFAChallengeCommandResult import com.microsoft.identity.common.java.nativeauth.controllers.results.MFACommandResult import com.microsoft.identity.common.java.nativeauth.controllers.results.MFASubmitChallengeCommandResult import com.microsoft.identity.common.java.nativeauth.controllers.results.SignInCommandResult import com.microsoft.identity.common.java.nativeauth.util.checkAndWrapCommandResultType -import com.microsoft.identity.common.nativeauth.internal.commands.GetAuthMethodsCommand import com.microsoft.identity.common.nativeauth.internal.commands.MFAChallengeCommand import com.microsoft.identity.common.nativeauth.internal.commands.MFASubmitChallengeCommand import com.microsoft.identity.common.nativeauth.internal.controllers.NativeAuthMsalController @@ -47,14 +45,11 @@ import com.microsoft.identity.nativeauth.AuthMethod import com.microsoft.identity.nativeauth.NativeAuthPublicClientApplication import com.microsoft.identity.nativeauth.NativeAuthPublicClientApplicationConfiguration import com.microsoft.identity.nativeauth.statemachine.errors.ErrorTypes -import com.microsoft.identity.nativeauth.statemachine.errors.MFAGetAuthMethodsError import com.microsoft.identity.nativeauth.statemachine.errors.MFARequestChallengeError import com.microsoft.identity.nativeauth.statemachine.errors.MFASubmitChallengeError -import com.microsoft.identity.nativeauth.statemachine.results.MFAGetAuthMethodsResult import com.microsoft.identity.nativeauth.statemachine.results.MFARequiredResult import com.microsoft.identity.nativeauth.statemachine.results.MFASubmitChallengeResult import com.microsoft.identity.nativeauth.statemachine.results.SignInResult -import com.microsoft.identity.nativeauth.toListOfAuthMethods import com.microsoft.identity.nativeauth.utils.serializable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -77,18 +72,19 @@ class AwaitingMFAState( * Requests a challenge to be sent to the user's default authentication method; callback variant. * * Warning: this API is experimental. It may be changed in the future without notice. Do not use in production applications. + * @param authMethod [com.microsoft.identity.nativeauth.AuthMethod] the authentication method used for the challenge operation. * @param callback [com.microsoft.identity.nativeauth.statemachine.states.AwaitingMFAState.RequestChallengeCallback] to receive the result on. * @return The result of the request challenge action. */ - fun requestChallenge(callback: RequestChallengeCallback) { + fun requestChallenge(authMethod: AuthMethod, callback: RequestChallengeCallback) { LogSession.logMethodCall( tag = TAG, correlationId = correlationId, - methodName = "${TAG}.requestChallenge(callback: RequestChallengeCallback)" + methodName = "${TAG}.requestChallenge(authMethod: AuthMethod, callback: RequestChallengeCallback)" ) NativeAuthPublicClientApplication.pcaScope.launch { try { - val result = requestChallenge() + val result = requestChallenge(authMethod) callback.onResult(result) } catch (e: MsalException) { Logger.error(TAG, "Exception thrown in requestChallenge", e) @@ -103,28 +99,28 @@ class AwaitingMFAState( * Warning: this API is experimental. It may be changed in the future without notice. Do not use in production applications. * @return The result of the request challenge action. */ - suspend fun requestChallenge(): MFARequiredResult { + suspend fun requestChallenge(authMethod: AuthMethod): MFARequiredResult { LogSession.logMethodCall( tag = TAG, correlationId = correlationId, - methodName = "${TAG}.requestChallenge()" + methodName = "${TAG}.requestChallenge(authMethod: AuthMethod)" ) Logger.warn(TAG, "Warning: this API is experimental. It may be changed in the future without notice. Do not use in production applications.") return withContext(Dispatchers.IO) { try { - val params = CommandParametersAdapter.createMFADefaultChallengeCommandParameters( + val params = CommandParametersAdapter.createMFAChallengeAuthMethodCommandParameters( config, config.oAuth2TokenCache, continuationToken, correlationId, - scopes + authMethod ) val command = MFAChallengeCommand( parameters = params, controller = NativeAuthMsalController(), - publicApiId = PublicApiId.NATIVE_AUTH_MFA_DEFAULT_CHALLENGE + publicApiId = PublicApiId.NATIVE_AUTH_MFA_SELECTED_CHALLENGE ) val rawCommandResult = @@ -146,22 +142,11 @@ class AwaitingMFAState( channel = result.challengeChannel ) } - is MFACommandResult.SelectionRequired -> { - MFARequiredResult.SelectionRequired( - nextState = MFARequiredState( - continuationToken = result.continuationToken, - correlationId = result.correlationId, - scopes = scopes, - config = config - ), - authMethods = result.authMethods.toListOfAuthMethods() - ) - } is INativeAuthCommandResult.APIError -> { Logger.warnWithObject( TAG, result.correlationId, - "requestChallenge() received unexpected result: ", + "requestChallenge(authMethod: AuthMethod) received unexpected result: ", result ) MFARequestChallengeError( @@ -229,116 +214,6 @@ class MFARequiredState( ) : BaseState(continuationToken = continuationToken, correlationId = correlationId), State, Parcelable { private val TAG: String = MFARequiredState::class.java.simpleName - /** - * GetAuthMethodsCallback receives the result for getAuthMethods() in MFA flows in native authentication. - */ - interface GetAuthMethodsCallback : Callback - - /** - * Retrieves all authentication methods that can be used to complete the challenge flow; callback variant. - * - * Warning: this API is experimental. It may be changed in the future without notice. Do not use in production applications. - * @param callback [com.microsoft.identity.nativeauth.statemachine.states.MFARequiredState.GetAuthMethodsCallback] to receive the result on. - * @return The results of the get authentication methods action. - */ - fun getAuthMethods(callback: GetAuthMethodsCallback) { - LogSession.logMethodCall( - tag = TAG, - correlationId = correlationId, - methodName = "${TAG}.getAuthMethods(callback: GetAuthMethodsCallback)" - ) - NativeAuthPublicClientApplication.pcaScope.launch { - try { - val result = getAuthMethods() - callback.onResult(result) - } catch (e: MsalException) { - Logger.error(TAG, "Exception thrown in getAuthMethods", e) - callback.onError(e) - } - } - } - - /** - * Retrieves all authentication methods that can be used to complete the challenge flow; Kotlin coroutines variant. - * - * Warning: this API is experimental. It may be changed in the future without notice. Do not use in production applications. - * @return The results of the get authentication methods action. - */ - suspend fun getAuthMethods(): MFAGetAuthMethodsResult { - LogSession.logMethodCall( - tag = TAG, - correlationId = correlationId, - methodName = "${TAG}.getAuthMethods()" - ) - - Logger.warn(TAG, "Warning: this API is experimental. It may be changed in the future without notice. Do not use in production applications.") - - return withContext(Dispatchers.IO) { - try { - val params = CommandParametersAdapter.createGetAuthMethodsCommandParameters( - config, - config.oAuth2TokenCache, - continuationToken, - correlationId - ) - val command = GetAuthMethodsCommand( - parameters = params, - controller = NativeAuthMsalController(), - publicApiId = PublicApiId.NATIVE_AUTH_GET_AUTH_METHODS - ) - - val rawCommandResult = - CommandDispatcher.submitSilentReturningFuture(command) - .get() - - return@withContext when (val result = - rawCommandResult.checkAndWrapCommandResultType()) { - is MFACommandResult.SelectionRequired -> { - MFARequiredResult.SelectionRequired( - nextState = MFARequiredState( - continuationToken = result.continuationToken, - correlationId = result.correlationId, - scopes = scopes, - config = config - ), - authMethods = result.authMethods.toListOfAuthMethods() - ) - } - is INativeAuthCommandResult.APIError -> { - Logger.warnWithObject( - TAG, - result.correlationId, - "getAuthMethods() received unexpected result: ", - result - ) - MFAGetAuthMethodsError( - errorMessage = result.errorDescription, - error = result.error, - correlationId = result.correlationId, - errorCodes = result.errorCodes, - exception = result.exception - ) - } - is INativeAuthCommandResult.Redirect -> { - MFAGetAuthMethodsError( - errorType = ErrorTypes.BROWSER_REQUIRED, - error = result.error, - errorMessage = result.redirectReason, - correlationId = result.correlationId - ) - } - } - } catch (e: Exception) { - MFAGetAuthMethodsError( - errorType = ErrorTypes.CLIENT_EXCEPTION, - errorMessage = "MSAL client exception occurred in getAuthMethods().", - exception = e, - correlationId = correlationId - ) - } - } - } - /** * RequestChallengeCallback receives the result for requestChallenge() in MFA flows in native authentication. */ @@ -346,19 +221,17 @@ class MFARequiredState( /** * Requests a challenge to be sent to the user's default authentication method; callback variant. - * If an authentication method ID was supplied, the server will send a challenge to the specified method. If no ID is supplied, - * the server will attempt to send the challenge to the user's default auth method. * * Warning: this API is experimental. It may be changed in the future without notice. Do not use in production applications. * @param authMethod [com.microsoft.identity.nativeauth.AuthMethod] the authentication method used for the challenge operation. - * @param callback [com.microsoft.identity.nativeauth.statemachine.states.MFARequiredState.RequestChallengeCallback] to receive the result on. + * @param callback [com.microsoft.identity.nativeauth.statemachine.states.AwaitingMFAState.RequestChallengeCallback] to receive the result on. * @return The result of the request challenge action. */ - fun requestChallenge(authMethod: AuthMethod? = null, callback: RequestChallengeCallback) { + fun requestChallenge(authMethod: AuthMethod, callback: RequestChallengeCallback) { LogSession.logMethodCall( tag = TAG, correlationId = correlationId, - methodName = "${TAG}.requestChallenge(callback: RequestChallengeCallback)" + methodName = "${TAG}.requestChallenge(authMethod: AuthMethod, callback: RequestChallengeCallback)" ) NativeAuthPublicClientApplication.pcaScope.launch { try { @@ -373,14 +246,12 @@ class MFARequiredState( /** * Requests a challenge to be sent to the user's default authentication method; Kotlin coroutines variant. - * If an authentication method ID was supplied, the server will send a challenge to the specified method. If no ID is supplied, - * the server will attempt to send the challenge to the user's default auth method. * * Warning: this API is experimental. It may be changed in the future without notice. Do not use in production applications. * @param authMethod [com.microsoft.identity.nativeauth.AuthMethod] the authentication method used for the challenge operation. * @return The result of the request challenge action. */ - suspend fun requestChallenge(authMethod: AuthMethod? = null): MFARequiredResult { + suspend fun requestChallenge(authMethod: AuthMethod): MFARequiredResult { LogSession.logMethodCall( tag = TAG, correlationId = correlationId, @@ -391,23 +262,13 @@ class MFARequiredState( return withContext(Dispatchers.IO) { try { - val params = if (authMethod != null) { - CommandParametersAdapter.createMFASelectedChallengeCommandParameters( - config, - config.oAuth2TokenCache, - continuationToken, - correlationId, - authMethod - ) - } else { - CommandParametersAdapter.createMFADefaultChallengeCommandParameters( - config, - config.oAuth2TokenCache, - continuationToken, - correlationId, - scopes - ) - } + val params = CommandParametersAdapter.createMFAChallengeAuthMethodCommandParameters( + config, + config.oAuth2TokenCache, + continuationToken, + correlationId, + authMethod + ) val command = MFAChallengeCommand( parameters = params, @@ -434,17 +295,6 @@ class MFARequiredState( channel = result.challengeChannel ) } - is MFACommandResult.SelectionRequired -> { - MFARequiredResult.SelectionRequired( - nextState = MFARequiredState( - continuationToken = result.continuationToken, - correlationId = result.correlationId, - scopes = scopes, - config = config - ), - authMethods = result.authMethods.toListOfAuthMethods() - ) - } is INativeAuthCommandResult.APIError -> { Logger.warnWithObject( TAG, diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/SignInStates.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/SignInStates.kt index 8b74cc60b..ca267ce63 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/SignInStates.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/SignInStates.kt @@ -452,7 +452,8 @@ class SignInPasswordRequiredState( correlationId = result.correlationId, scopes = scopes, config = config - ) + ), + authMethods = result.authMethods.toListOfAuthMethods() ) } is SignInCommandResult.StrongAuthMethodRegistrationRequired -> { diff --git a/msal/src/test/java/com/microsoft/identity/client/CommandParametersTest.java b/msal/src/test/java/com/microsoft/identity/client/CommandParametersTest.java index 6e2d39559..f541b4d7d 100644 --- a/msal/src/test/java/com/microsoft/identity/client/CommandParametersTest.java +++ b/msal/src/test/java/com/microsoft/identity/client/CommandParametersTest.java @@ -422,6 +422,7 @@ public void createSignInSubmitCodeCommandParameters_CommandParamsContainsExpecte Assert.assertEquals(commandParameters.scopes, scopes); Assert.assertEquals(commandParameters.challengeType, challengeTypes); Assert.assertEquals(commandParameters.getCorrelationId(), correlationId); + Assert.assertFalse(commandParameters.isMFAGrantType); } @Test 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 8847577d2..fdb0d595b 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 @@ -32,15 +32,11 @@ import com.microsoft.identity.nativeauth.INativeAuthPublicClientApplication import com.microsoft.identity.nativeauth.parameters.NativeAuthGetAccessTokenParameters import com.microsoft.identity.nativeauth.parameters.NativeAuthSignInParameters import com.microsoft.identity.nativeauth.statemachine.errors.MFASubmitChallengeError -import com.microsoft.identity.nativeauth.statemachine.errors.ResetPasswordError import com.microsoft.identity.nativeauth.statemachine.errors.SignInError -import com.microsoft.identity.nativeauth.statemachine.errors.SignUpError import com.microsoft.identity.nativeauth.statemachine.results.GetAccessTokenResult import com.microsoft.identity.nativeauth.statemachine.results.MFARequiredResult import com.microsoft.identity.nativeauth.statemachine.results.SignInResult import kotlinx.coroutines.runBlocking -import org.junit.Assert -import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue import org.junit.Assert.fail @@ -95,7 +91,7 @@ class SignInMFATest : NativeAuthPublicClientApplicationAbstractTest() { // Initiate challenge, send code to email val sendChallengeResult = - (result as SignInResult.MFARequired).nextState.requestChallenge() + (result as SignInResult.MFARequired).nextState.requestChallenge(result.authMethods.first()) assertResult(sendChallengeResult) (sendChallengeResult as MFARequiredResult.VerificationRequired) assertNotNull(sendChallengeResult.sentTo) @@ -108,144 +104,7 @@ class SignInMFATest : NativeAuthPublicClientApplicationAbstractTest() { assertTrue((submitIncorrectChallengeResult as MFASubmitChallengeError).isInvalidChallenge()) // Request new challenge - val requestNewChallengeResult = sendChallengeResult.nextState.requestChallenge() - assertResult(requestNewChallengeResult) - (requestNewChallengeResult as MFARequiredResult.VerificationRequired) - assertNotNull(requestNewChallengeResult.sentTo) - assertNotNull(requestNewChallengeResult.codeLength) - assertNotNull(requestNewChallengeResult.channel) - - // Retrieve challenge from mailbox and submit - val otp = tempEmailApi.retrieveCodeFromInbox(username) - val submitCorrectChallengeResult = requestNewChallengeResult.nextState.submitChallenge(otp) - assertResult(submitCorrectChallengeResult) - - val accountState = (submitCorrectChallengeResult as SignInResult.Complete).resultValue - val accessTokenParam = NativeAuthGetAccessTokenParameters() - val getAccessTokenResult = accountState.getAccessToken(accessTokenParam) - assertResult(getAccessTokenResult) - val authResult = (getAccessTokenResult as GetAccessTokenResult.Complete).resultValue - assertTrue(authResult.scope.contains(scopeA)) - assertTrue(authResult.scope.contains(scopeB)) - } - } - } - - /** - * Full flow: - * - Receive MFA required error from API. - * - Request default challenge. - * - Challenge sent successfully, SelectionRequired is returned. - * - Call getAuthMethods to retrieve all auth methods available. - * - Request new challenge. - * - Submit correct challenge. - * - Complete MFA flow and complete sign in. - * - * 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("Retrieving OTP code failure.") - @Test - fun `test get other auth methods, request challenge on specific auth method and complete MFA flow`() { - config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) - resources = config.resources - - retryOperation { - runBlocking { - val username = config.email - - val scopeA = resources[0].scopes[0] - val scopeB = resources[0].scopes[1] - - val signInParam = NativeAuthSignInParameters(username = username) - signInParam.password = getSafePassword().toCharArray() - signInParam.scopes = listOf(scopeA, scopeB) - val result = application.signIn(signInParam) - 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 other auth methods - val getAuthMethodsResult = sendChallengeResult.nextState.getAuthMethods() - assertResult(getAuthMethodsResult) - (getAuthMethodsResult as MFARequiredResult.SelectionRequired) - assertTrue(getAuthMethodsResult.authMethods.size == 1) - assertEquals("email", getAuthMethodsResult.authMethods[0].challengeChannel) - - // Request challenge for specific auth method - val requestNewChallengeResult = - sendChallengeResult.nextState.requestChallenge(getAuthMethodsResult.authMethods[0]) - assertResult(requestNewChallengeResult) - (requestNewChallengeResult as MFARequiredResult.VerificationRequired) - assertNotNull(requestNewChallengeResult.sentTo) - assertNotNull(requestNewChallengeResult.codeLength) - assertNotNull(requestNewChallengeResult.channel) - - // Retrieve challenge from mailbox and submit - val otp = tempEmailApi.retrieveCodeFromInbox(username) - val submitCorrectChallengeResult = requestNewChallengeResult.nextState.submitChallenge(otp) - assertResult(submitCorrectChallengeResult) - - val accountState = (submitCorrectChallengeResult as SignInResult.Complete).resultValue - val accessTokenParam = NativeAuthGetAccessTokenParameters() - val getAccessTokenResult = accountState.getAccessToken(accessTokenParam) - assertResult(getAccessTokenResult) - val authResult = (getAccessTokenResult as GetAccessTokenResult.Complete).resultValue - assertTrue(authResult.scope.contains(scopeA)) - assertTrue(authResult.scope.contains(scopeB)) - } - } - } - - /** - * Full flow: - * - Receive MFA required error from API. - * - Request default challenge. - * - No default auth method available, so SelectionRequired is returned. - * - Request new challenge on specific auth method. - * - Submit correct challenge. - * - Complete MFA flow and complete sign in. - * - * 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("Retrieving OTP code failure.") - @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, defaultCapabilities) - resources = config.resources - - retryOperation { - runBlocking { - val username = config.email - - val scopeA = resources[0].scopes[0] - val scopeB = resources[0].scopes[1] - - val param = NativeAuthSignInParameters(username = username) - param.password = getSafePassword().toCharArray() - param.scopes = listOf(scopeA, scopeB) - val result = application.signIn(param) - assertResult(result) - - // Initiate challenge, send code to email - val sendChallengeResult = - (result as SignInResult.MFARequired).nextState.requestChallenge() - assertResult(sendChallengeResult) - (sendChallengeResult as MFARequiredResult.SelectionRequired) - assertTrue(sendChallengeResult.authMethods.size == 1) - assertEquals("email", sendChallengeResult.authMethods[0].challengeChannel) - - // Request challenge for specific auth method - val requestNewChallengeResult = - sendChallengeResult.nextState.requestChallenge(sendChallengeResult.authMethods[0]) + val requestNewChallengeResult = sendChallengeResult.nextState.requestChallenge(result.authMethods.first()) assertResult(requestNewChallengeResult) (requestNewChallengeResult as MFARequiredResult.VerificationRequired) assertNotNull(requestNewChallengeResult.sentTo) @@ -301,7 +160,7 @@ class SignInMFATest : NativeAuthPublicClientApplicationAbstractTest() { // Initiate challenge, send code to email val sendChallengeResult = - (result as SignInResult.MFARequired).nextState.requestChallenge() + (result as SignInResult.MFARequired).nextState.requestChallenge(result.authMethods.first()) assertResult(sendChallengeResult) (sendChallengeResult as MFARequiredResult.VerificationRequired) assertNotNull(sendChallengeResult.sentTo) diff --git a/msal/src/test/java/com/microsoft/identity/nativeauth/NativeAuthPublicClientApplicationJavaTest.java b/msal/src/test/java/com/microsoft/identity/nativeauth/NativeAuthPublicClientApplicationJavaTest.java index df4f4355d..2a6d4c1b5 100644 --- a/msal/src/test/java/com/microsoft/identity/nativeauth/NativeAuthPublicClientApplicationJavaTest.java +++ b/msal/src/test/java/com/microsoft/identity/nativeauth/NativeAuthPublicClientApplicationJavaTest.java @@ -49,7 +49,6 @@ import com.microsoft.identity.nativeauth.statemachine.errors.SubmitCodeError; import com.microsoft.identity.nativeauth.statemachine.results.GetAccessTokenResult; import com.microsoft.identity.nativeauth.statemachine.results.GetAccountResult; -import com.microsoft.identity.nativeauth.statemachine.results.MFAGetAuthMethodsResult; import com.microsoft.identity.nativeauth.statemachine.results.MFARequiredResult; import com.microsoft.identity.nativeauth.statemachine.results.MFASubmitChallengeResult; import com.microsoft.identity.nativeauth.statemachine.results.ResetPasswordResendCodeResult; @@ -273,15 +272,12 @@ public void onError(@NonNull BaseException exception) { SignInSubmitCodeResult result = submitCodeResult.get(30, TimeUnit.SECONDS); assertTrue(result instanceof SubmitCodeError); - SubmitCodeError error = spy((SubmitCodeError)result); + SubmitCodeError error = (SubmitCodeError)result; assertTrue(error.isInvalidCode()); - // correlation ID field in will be null, because the mock API doesn't return this. So, we mock - // it's value in order to make it consistent with the subsequent call to mock API. - mockCorrelationId(error, correlationId); - // 3. Submit (valid) code // 3a. Setup server response + correlationId = UUID.randomUUID().toString(); configureMockApi( MockApiEndpoint.SignInToken, correlationId, @@ -302,8 +298,10 @@ public void onError(@NonNull BaseException exception) { } }; nextState.submitCode(code, submitCodeCallback2); + + SignInSubmitCodeResult submitCodeSuccessResult = submitCodeResult2.get(30, TimeUnit.SECONDS); // 3a. Server accepts code, returns tokens - assertTrue(submitCodeResult2.get(30, TimeUnit.SECONDS) instanceof SignInResult.Complete); + assertTrue(submitCodeSuccessResult instanceof SignInResult.Complete); } /** @@ -383,12 +381,10 @@ public void onError(@NonNull BaseException exception) { SignInSubmitCodeResult result = submitCodeResult.get(30, TimeUnit.SECONDS); assertTrue(result instanceof SubmitCodeError); - SubmitCodeError error = spy((SubmitCodeError)result); + SubmitCodeError error = (SubmitCodeError)result; assertTrue(error.isInvalidCode()); - // correlation ID field in will be null, because the mock API doesn't return this. So, we mock - // it's value in order to make it consistent with the subsequent call to mock API. - mockCorrelationId(error, correlationId); + correlationId = UUID.randomUUID().toString(); // 3. Submit (valid) code // 3a. Setup server response @@ -412,8 +408,9 @@ public void onError(@NonNull BaseException exception) { } }; nextState.submitCode(code, submitCodeCallback2); + SignInSubmitCodeResult submitCodeSuccessResult = submitCodeResult2.get(30, TimeUnit.SECONDS); // 3a. Server accepts code, returns tokens - assertTrue(submitCodeResult2.get(30, TimeUnit.SECONDS) instanceof SignInResult.Complete); + assertTrue(submitCodeSuccessResult instanceof SignInResult.Complete); } /** @@ -511,7 +508,7 @@ public void onError(@NonNull BaseException exception) { } @Test - public void testSignInMFAScenario1() throws ExecutionException, InterruptedException, TimeoutException { + public void testSignInMFAScenario2() throws ExecutionException, InterruptedException, TimeoutException { String correlationId = UUID.randomUUID().toString(); configureMockApi( MockApiEndpoint.SignInInitiate, @@ -535,125 +532,12 @@ public void testSignInMFAScenario1() throws ExecutionException, InterruptedExcep MockApiResponseType.MFA_REQUIRED ); - SignInTestCallback signInCallback = new SignInTestCallback(); - - application.signIn( - username, - password, - null, - signInCallback - ); - - SignInResult result = signInCallback.get(); - assertTrue(result instanceof SignInResult.MFARequired); - - correlationId = UUID.randomUUID().toString(); - // 4a. Sign in challenge for default auth method - // 4b. Setup server response with oob required - configureMockApi( - MockApiEndpoint.SignInChallenge, - correlationId, - MockApiResponseType.CHALLENGE_TYPE_OOB - ); - - // correlation ID field in will be null, because the mock API doesn't return this. So, we mock - // it's value in order to make it consistent with the subsequent call to mock API. - AwaitingMFAState nextState = spy(((SignInResult.MFARequired) result).getNextState()); - mockCorrelationId(nextState, correlationId); - - AwaitingMFAStateRequestChallengeTestCallback sendChallengeCallback = new AwaitingMFAStateRequestChallengeTestCallback(); - nextState.requestChallenge(sendChallengeCallback); - - MFARequiredResult sendChallengeResult = sendChallengeCallback.get(); - assertTrue(sendChallengeResult instanceof MFARequiredResult.VerificationRequired); - - correlationId = UUID.randomUUID().toString(); - // 5a. Sign in challenge for default auth method - // 5b. Setup server response with introspect required configureMockApi( MockApiEndpoint.Introspect, correlationId, MockApiResponseType.INTROSPECT_SUCCESS ); - // correlation ID field in will be null, because the mock API doesn't return this. So, we mock - // it's value in order to make it consistent with the subsequent call to mock API. - MFARequiredState nextState2 = spy(((MFARequiredResult.VerificationRequired) sendChallengeResult).getNextState()); - mockCorrelationId(nextState2, correlationId); - - GetAuthMethodsTestCallback getAuthMethodsCallback = new GetAuthMethodsTestCallback(); - nextState2.getAuthMethods(getAuthMethodsCallback); - - MFAGetAuthMethodsResult getAuthMethodsResult = getAuthMethodsCallback.get(); - assertTrue(getAuthMethodsResult instanceof MFARequiredResult.SelectionRequired); - - correlationId = UUID.randomUUID().toString(); - // 6a. Sign in challenge for specified auth method - // 6b. Setup server response with oob required - configureMockApi( - MockApiEndpoint.SignInChallenge, - correlationId, - MockApiResponseType.CHALLENGE_TYPE_OOB - ); - - // correlation ID field in will be null, because the mock API doesn't return this. So, we mock - // it's value in order to make it consistent with the subsequent call to mock API. - MFARequiredState nextState3 = spy(((MFARequiredResult.SelectionRequired) getAuthMethodsResult).getNextState()); - mockCorrelationId(nextState3, correlationId); - - MFARequiredStateRequestChallengeTestCallback sendSelectedAuthMethodCallback = new MFARequiredStateRequestChallengeTestCallback(); - AuthMethod authMethod = ((MFARequiredResult.SelectionRequired) getAuthMethodsResult).getAuthMethods().get(0); - nextState3.requestChallenge(authMethod, sendSelectedAuthMethodCallback); - - MFARequiredResult sendSelectedAuthMethodResult = sendSelectedAuthMethodCallback.get(); - assertTrue(sendSelectedAuthMethodResult instanceof MFARequiredResult.VerificationRequired); - - correlationId = UUID.randomUUID().toString(); - // 7a. Send challenge value to the API - // 7b. Sign in completed, receive tokens - configureMockApi( - MockApiEndpoint.SignInToken, - correlationId, - MockApiResponseType.TOKEN_SUCCESS - ); - - // correlation ID field in will be null, because the mock API doesn't return this. So, we mock - // it's value in order to make it consistent with the subsequent call to mock API. - MFARequiredState nextState4 = spy(((MFARequiredResult.VerificationRequired) sendSelectedAuthMethodResult).getNextState()); - mockCorrelationId(nextState4, correlationId); - - SubmitChallengeTestCallback submitChallengeCallback = new SubmitChallengeTestCallback(); - nextState4.submitChallenge(code, submitChallengeCallback); - - MFASubmitChallengeResult submitChallengeResult = submitChallengeCallback.get(); - assertTrue(submitChallengeResult instanceof SignInResult.Complete); - } - - @Test - public void testSignInMFAScenario2() throws ExecutionException, InterruptedException, TimeoutException { - String correlationId = UUID.randomUUID().toString(); - configureMockApi( - MockApiEndpoint.SignInInitiate, - correlationId, - MockApiResponseType.INITIATE_SUCCESS - ); - - // 2a. Sign in challenge - // 2b. Setup server response with password required - configureMockApi( - MockApiEndpoint.SignInChallenge, - correlationId, - MockApiResponseType.CHALLENGE_TYPE_PASSWORD - ); - - // 3a. Token with password - // 3b. mfa_required - configureMockApi( - MockApiEndpoint.SignInToken, - correlationId, - MockApiResponseType.MFA_REQUIRED - ); - SignInTestCallback signInCallback = new SignInTestCallback(); application.signIn( @@ -669,22 +553,6 @@ public void testSignInMFAScenario2() throws ExecutionException, InterruptedExcep correlationId = UUID.randomUUID().toString(); // 3a. Sign in challenge for default auth method // 3b. Setup server response with oob required - configureMockApi( - MockApiEndpoint.SignInChallenge, - correlationId, - MockApiResponseType.INTROSPECT_REQUIRED - ); - - // correlation ID field in will be null, because the mock API doesn't return this. So, we mock - // it's value in order to make it consistent with the subsequent call to mock API. - AwaitingMFAState nextState = spy(((SignInResult.MFARequired) result).getNextState()); - mockCorrelationId(nextState, correlationId); - - AwaitingMFAStateRequestChallengeTestCallback sendChallengeCallback = new AwaitingMFAStateRequestChallengeTestCallback(); - nextState.requestChallenge(sendChallengeCallback); - - MFARequiredResult sendChallengeResult = sendChallengeCallback.get(); - assertTrue(sendChallengeResult instanceof MFARequiredResult.SelectionRequired); correlationId = UUID.randomUUID().toString(); // 6a. Sign in challenge for specified auth method @@ -697,15 +565,15 @@ public void testSignInMFAScenario2() throws ExecutionException, InterruptedExcep // correlation ID field in will be null, because the mock API doesn't return this. So, we mock // it's value in order to make it consistent with the subsequent call to mock API. - MFARequiredState nextState3 = spy(((MFARequiredResult.SelectionRequired) sendChallengeResult).getNextState()); - mockCorrelationId(nextState3, correlationId); + AwaitingMFAState nextState = spy(((SignInResult.MFARequired) result).getNextState()); + mockCorrelationId(nextState, correlationId); - MFARequiredStateRequestChallengeTestCallback sendSelectedAuthMethodCallback = new MFARequiredStateRequestChallengeTestCallback(); - AuthMethod authMethod = ((MFARequiredResult.SelectionRequired) sendChallengeResult).getAuthMethods().get(0); - nextState3.requestChallenge(authMethod, sendSelectedAuthMethodCallback); + AwaitingMFAStateRequestChallengeTestCallback sendChallengeCallback = new AwaitingMFAStateRequestChallengeTestCallback(); + AuthMethod authMethod = ((SignInResult.MFARequired) result).getAuthMethods().get(0); + nextState.requestChallenge(authMethod, sendChallengeCallback); - MFARequiredResult sendSelectedAuthMethodResult = sendSelectedAuthMethodCallback.get(); - assertTrue(sendSelectedAuthMethodResult instanceof MFARequiredResult.VerificationRequired); + MFARequiredResult sendChallengeResult = sendChallengeCallback.get(); + assertTrue(sendChallengeResult instanceof MFARequiredResult.VerificationRequired); correlationId = UUID.randomUUID().toString(); // 7a. Send challenge value to the API @@ -718,7 +586,7 @@ public void testSignInMFAScenario2() throws ExecutionException, InterruptedExcep // correlation ID field in will be null, because the mock API doesn't return this. So, we mock // it's value in order to make it consistent with the subsequent call to mock API. - MFARequiredState nextState4 = spy(((MFARequiredResult.VerificationRequired) sendSelectedAuthMethodResult).getNextState()); + MFARequiredState nextState4 = spy(((MFARequiredResult.VerificationRequired) sendChallengeResult).getNextState()); mockCorrelationId(nextState4, correlationId); SubmitChallengeTestCallback submitChallengeCallback = new SubmitChallengeTestCallback(); @@ -3447,19 +3315,6 @@ public void onError(@NonNull BaseException exception) { } } -class GetAuthMethodsTestCallback extends TestCallback implements MFARequiredState.GetAuthMethodsCallback { - - @Override - public void onResult(MFAGetAuthMethodsResult result) { - future.setResult(result); - } - - @Override - public void onError(@NonNull BaseException exception) { - future.setException(exception); - } -} - class MFARequiredStateRequestChallengeTestCallback extends TestCallback implements MFARequiredState.RequestChallengeCallback { @Override diff --git a/msal/src/test/java/com/microsoft/identity/nativeauth/NativeAuthPublicClientApplicationKotlinTest.kt b/msal/src/test/java/com/microsoft/identity/nativeauth/NativeAuthPublicClientApplicationKotlinTest.kt index d48ad0cdb..a78281b15 100644 --- a/msal/src/test/java/com/microsoft/identity/nativeauth/NativeAuthPublicClientApplicationKotlinTest.kt +++ b/msal/src/test/java/com/microsoft/identity/nativeauth/NativeAuthPublicClientApplicationKotlinTest.kt @@ -51,15 +51,13 @@ import com.microsoft.identity.nativeauth.parameters.NativeAuthSignUpParameters import com.microsoft.identity.nativeauth.statemachine.errors.ErrorTypes import com.microsoft.identity.nativeauth.statemachine.errors.GetAccessTokenError import com.microsoft.identity.nativeauth.statemachine.errors.GetAccessTokenErrorTypes -import com.microsoft.identity.nativeauth.statemachine.errors.MFAGetAuthMethodsError -import com.microsoft.identity.nativeauth.statemachine.errors.MFARequestChallengeError +import com.microsoft.identity.nativeauth.statemachine.errors.MFASubmitChallengeError import com.microsoft.identity.nativeauth.statemachine.errors.ResetPasswordError import com.microsoft.identity.nativeauth.statemachine.errors.ResetPasswordSubmitPasswordError import com.microsoft.identity.nativeauth.statemachine.errors.SignInContinuationError import com.microsoft.identity.nativeauth.statemachine.errors.SignInError import com.microsoft.identity.nativeauth.statemachine.errors.SignUpError import com.microsoft.identity.nativeauth.statemachine.errors.SignUpSubmitAttributesError -import com.microsoft.identity.nativeauth.statemachine.errors.MFASubmitChallengeError import com.microsoft.identity.nativeauth.statemachine.errors.SubmitCodeError import com.microsoft.identity.nativeauth.statemachine.results.GetAccessTokenResult import com.microsoft.identity.nativeauth.statemachine.results.GetAccountResult @@ -2637,212 +2635,6 @@ class NativeAuthPublicClientApplicationKotlinTest(private val allowPII: Boolean) assertTrue((submitCodeResult as SubmitCodeError).error.equals("unsuccessful_command")) // ClientException will be caught in CommandResultUtil.kt and converted to generic error in interface layer } - @Test - fun testSignInMFAVerificationRequiredGetAuthMethodsComplete() = runTest { - // 1. Sign in initiate with username - // 1a. Setup server response - var correlationId = UUID.randomUUID().toString() - configureMockApi( - MockApiEndpoint.SignInInitiate, - correlationId, - MockApiResponseType.INITIATE_SUCCESS - ) - - // 2a. Sign in challenge - // 2b. Setup server response with password required - configureMockApi( - MockApiEndpoint.SignInChallenge, - correlationId, - MockApiResponseType.CHALLENGE_TYPE_PASSWORD - ) - - // 3a. Token with password - // 3b. mfa_required - configureMockApi( - MockApiEndpoint.SignInToken, - correlationId, - MockApiResponseType.MFA_REQUIRED - ) - - val result = application.signIn(username, password) - assertResult(result) - - correlationId = UUID.randomUUID().toString() - // 3a. Sign in challenge for default auth method - // 3b. Setup server response with oob required - configureMockApi( - MockApiEndpoint.SignInChallenge, - correlationId, - MockApiResponseType.CHALLENGE_TYPE_OOB - ) - - // correlation ID field in will be null, because the mock API doesn't return this. So, we mock - // it's value in order to make it consistent with the subsequent call to mock API. - val nextState = spy((result as SignInResult.MFARequired).nextState) - nextState.mockCorrelationId(correlationId) - - // Initiate challenge, send code to email - val sendChallengeResult = nextState.requestChallenge() - assertResult(sendChallengeResult) - (sendChallengeResult as MFARequiredResult.VerificationRequired) - assertNotNull(sendChallengeResult.sentTo) - assertNotNull(sendChallengeResult.codeLength) - assertNotNull(sendChallengeResult.channel) - - correlationId = UUID.randomUUID().toString() - // 4a. Call /introspect to get additional methods - // 4b. Return list of auth methods - configureMockApi( - MockApiEndpoint.Introspect, - correlationId, - MockApiResponseType.INTROSPECT_SUCCESS - ) - // correlation ID field in will be null, because the mock API doesn't return this. So, we mock - // it's value in order to make it consistent with the subsequent call to mock API. - val nextState2 = spy(sendChallengeResult.nextState) - nextState2.mockCorrelationId(correlationId) - - // Call /introspect to get all auth methods - val getAuthMethodsResult = nextState2.getAuthMethods() - assertResult(getAuthMethodsResult) - (getAuthMethodsResult as MFARequiredResult.SelectionRequired) - assertNotNull(getAuthMethodsResult.authMethods) - - correlationId = UUID.randomUUID().toString() - // 5a. Sign in challenge for specified auth method - // 5b. Setup server response with oob required - configureMockApi( - MockApiEndpoint.SignInChallenge, - correlationId, - MockApiResponseType.CHALLENGE_TYPE_OOB - ) - - // correlation ID field in will be null, because the mock API doesn't return this. So, we mock - // it's value in order to make it consistent with the subsequent call to mock API. - val nextState3 = spy(getAuthMethodsResult.nextState) - nextState3.mockCorrelationId(correlationId) - - // Call /challenge with specified ID - val sendSpecifiedChallengeResult = nextState3.requestChallenge(getAuthMethodsResult.authMethods[0]) - assertResult(sendSpecifiedChallengeResult) - (sendSpecifiedChallengeResult as MFARequiredResult.VerificationRequired) - assertNotNull(sendSpecifiedChallengeResult.sentTo) - assertNotNull(sendSpecifiedChallengeResult.codeLength) - assertNotNull(sendSpecifiedChallengeResult.channel) - - correlationId = UUID.randomUUID().toString() - // 6a. Token with oob - // 6b. Success, with tokens - configureMockApi( - MockApiEndpoint.SignInToken, - correlationId, - MockApiResponseType.TOKEN_SUCCESS - ) - - // correlation ID field in will be null, because the mock API doesn't return this. So, we mock - // it's value in order to make it consistent with the subsequent call to mock API. - val nextState4 = spy(sendSpecifiedChallengeResult.nextState) - nextState4.mockCorrelationId(correlationId) - - val submitChallengeResult = nextState4.submitChallenge(code) - assertResult(submitChallengeResult) - } - - @Test - fun testSignInMFASelectionRequiredGetAuthMethodsComplete() = runTest { - // 1. Sign in initiate with username - // 1a. Setup server response - var correlationId = UUID.randomUUID().toString() - configureMockApi( - MockApiEndpoint.SignInInitiate, - correlationId, - MockApiResponseType.INITIATE_SUCCESS - ) - - // 2a. Sign in challenge - // 2b. Setup server response with password required - configureMockApi( - MockApiEndpoint.SignInChallenge, - correlationId, - MockApiResponseType.CHALLENGE_TYPE_PASSWORD - ) - - // 3a. Token with password - // 3b. mfa_required - configureMockApi( - MockApiEndpoint.SignInToken, - correlationId, - MockApiResponseType.MFA_REQUIRED - ) - - val result = application.signIn(username, password) - assertResult(result) - - // correlation ID field in will be null, because the mock API doesn't return this. So, we mock - // it's value in order to make it consistent with the subsequent call to mock API. - correlationId = UUID.randomUUID().toString() - // 4a. Sign in challenge for default auth method - // 3b. Setup server response with introspect_required - configureMockApi( - MockApiEndpoint.SignInChallenge, - correlationId, - MockApiResponseType.INTROSPECT_REQUIRED - ) - - configureMockApi( - MockApiEndpoint.Introspect, - correlationId, - MockApiResponseType.INTROSPECT_SUCCESS - ) - // Initiate challenge, send code to email - val nextState = spy((result as SignInResult.MFARequired).nextState) - - nextState.mockCorrelationId(correlationId) - val sendChallengeResult = nextState.requestChallenge() - assertResult(sendChallengeResult) - (sendChallengeResult as MFARequiredResult.SelectionRequired) - assertNotNull(sendChallengeResult.authMethods) - - correlationId = UUID.randomUUID().toString() - // 5a. Sign in challenge for specified auth method - // 5b. Setup server response with oob required - configureMockApi( - MockApiEndpoint.SignInChallenge, - correlationId, - MockApiResponseType.CHALLENGE_TYPE_OOB - ) - - // correlation ID field in will be null, because the mock API doesn't return this. So, we mock - // it's value in order to make it consistent with the subsequent call to mock API. - val nextState3 = spy(sendChallengeResult.nextState) - nextState3.mockCorrelationId(correlationId) - - // Call /challenge with specified ID - val sendSpecifiedChallengeResult = nextState3.requestChallenge(sendChallengeResult.authMethods[0]) - assertResult(sendSpecifiedChallengeResult) - (sendSpecifiedChallengeResult as MFARequiredResult.VerificationRequired) - assertNotNull(sendSpecifiedChallengeResult.sentTo) - assertNotNull(sendSpecifiedChallengeResult.codeLength) - assertNotNull(sendSpecifiedChallengeResult.channel) - - correlationId = UUID.randomUUID().toString() - // 6a. Token with oob - // 6b. Success, with tokens - configureMockApi( - MockApiEndpoint.SignInToken, - correlationId, - MockApiResponseType.TOKEN_SUCCESS - ) - - // correlation ID field in will be null, because the mock API doesn't return this. So, we mock - // it's value in order to make it consistent with the subsequent call to mock API. - val nextState4 = spy(sendSpecifiedChallengeResult.nextState) - nextState4.mockCorrelationId(correlationId) - - val submitChallengeResult = nextState4.submitChallenge(code) - assertResult(submitChallengeResult) - } - @Test fun testSignInMFAVerificationRequiredComplete() = runTest { // 1. Sign in initiate with username @@ -2888,7 +2680,7 @@ class NativeAuthPublicClientApplicationKotlinTest(private val allowPII: Boolean) val nextState = spy((result as SignInResult.MFARequired).nextState) nextState.mockCorrelationId(correlationId) - val sendChallengeResult = nextState.requestChallenge() + val sendChallengeResult = nextState.requestChallenge(result.authMethods.first()) assertResult(sendChallengeResult) (sendChallengeResult as MFARequiredResult.VerificationRequired) assertNotNull(sendChallengeResult.sentTo) @@ -2914,7 +2706,7 @@ class NativeAuthPublicClientApplicationKotlinTest(private val allowPII: Boolean) } @Test - fun testSignInMFASelectionRequiredGetAuthMethodsRedirect() = runTest { + fun testSignInMFARequiredReturnsEmailAuthMethod() = runTest { // 1. Sign in initiate with username // 1a. Setup server response var correlationId = UUID.randomUUID().toString() @@ -2943,68 +2735,19 @@ class NativeAuthPublicClientApplicationKotlinTest(private val allowPII: Boolean) val result = application.signIn(username, password) assertResult(result) - // correlation ID field in will be null, because the mock API doesn't return this. So, we mock - // it's value in order to make it consistent with the subsequent call to mock API. correlationId = UUID.randomUUID().toString() - // 4a. Sign in challenge for default auth method - // 3b. Setup server response with introspect_required + // 3a. Sign in challenge for default auth method + // 3b. Setup server response with oob required configureMockApi( MockApiEndpoint.SignInChallenge, correlationId, - MockApiResponseType.INTROSPECT_REQUIRED + MockApiResponseType.CHALLENGE_TYPE_OOB ) - + // 3c. Setup oauth/introspect to return email auth method configureMockApi( MockApiEndpoint.Introspect, correlationId, - MockApiResponseType.CHALLENGE_TYPE_REDIRECT - ) - // Initiate challenge, send code to email - val nextState = spy((result as SignInResult.MFARequired).nextState) - - nextState.mockCorrelationId(correlationId) - val sendChallengeResult = nextState.requestChallenge() - assertResult(sendChallengeResult) - assertTrue((sendChallengeResult as MFARequestChallengeError).isBrowserRequired()) - } - - @Test - fun testSignInMFAVerificationRequiredGetAuthMethodsRedirect() = runTest { - // 1. Sign in initiate with username - // 1a. Setup server response - var correlationId = UUID.randomUUID().toString() - configureMockApi( - MockApiEndpoint.SignInInitiate, - correlationId, - MockApiResponseType.INITIATE_SUCCESS - ) - - // 2a. Sign in challenge - // 2b. Setup server response with password required - configureMockApi( - MockApiEndpoint.SignInChallenge, - correlationId, - MockApiResponseType.CHALLENGE_TYPE_PASSWORD - ) - - // 3a. Token with password - // 3b. mfa_required - configureMockApi( - MockApiEndpoint.SignInToken, - correlationId, - MockApiResponseType.MFA_REQUIRED - ) - - val result = application.signIn(username, password) - assertResult(result) - - correlationId = UUID.randomUUID().toString() - // 3a. Sign in challenge for default auth method - // 3b. Setup server response with oob required - configureMockApi( - MockApiEndpoint.SignInChallenge, - correlationId, - MockApiResponseType.CHALLENGE_TYPE_OOB + MockApiResponseType.INTROSPECT_SUCCESS ) // correlation ID field in will be null, because the mock API doesn't return this. So, we mock @@ -3013,30 +2756,12 @@ class NativeAuthPublicClientApplicationKotlinTest(private val allowPII: Boolean) nextState.mockCorrelationId(correlationId) // Initiate challenge, send code to email - val sendChallengeResult = nextState.requestChallenge() + val sendChallengeResult = nextState.requestChallenge(result.authMethods.first()) assertResult(sendChallengeResult) (sendChallengeResult as MFARequiredResult.VerificationRequired) assertNotNull(sendChallengeResult.sentTo) assertNotNull(sendChallengeResult.codeLength) assertNotNull(sendChallengeResult.channel) - - correlationId = UUID.randomUUID().toString() - // 4a. Call /introspect to get additional methods - // 4b. Return list of auth methods - configureMockApi( - MockApiEndpoint.Introspect, - correlationId, - MockApiResponseType.CHALLENGE_TYPE_REDIRECT - ) - // correlation ID field in will be null, because the mock API doesn't return this. So, we mock - // it's value in order to make it consistent with the subsequent call to mock API. - val nextState2 = spy(sendChallengeResult.nextState) - nextState2.mockCorrelationId(correlationId) - - // Call /introspect to get all auth methods - val getAuthMethodsResult = nextState2.getAuthMethods() - assertResult(getAuthMethodsResult) - assertTrue((getAuthMethodsResult as MFAGetAuthMethodsError).isBrowserRequired()) } @Test @@ -3084,7 +2809,7 @@ class NativeAuthPublicClientApplicationKotlinTest(private val allowPII: Boolean) val nextState = spy((result as SignInResult.MFARequired).nextState) nextState.mockCorrelationId(correlationId) - val sendChallengeResult = nextState.requestChallenge() + val sendChallengeResult = nextState.requestChallenge(result.authMethods.first()) assertResult(sendChallengeResult) (sendChallengeResult as MFARequiredResult.VerificationRequired) assertNotNull(sendChallengeResult.sentTo) @@ -3156,51 +2881,22 @@ class NativeAuthPublicClientApplicationKotlinTest(private val allowPII: Boolean) MockApiResponseType.MFA_REQUIRED ) - val result = application.signIn(username, password) - assertResult(result) - - correlationId = UUID.randomUUID().toString() - // 3a. Sign in challenge for default auth method - // 3b. Setup server response with oob required - configureMockApi( - MockApiEndpoint.SignInChallenge, - correlationId, - MockApiResponseType.CHALLENGE_TYPE_OOB - ) - - // correlation ID field in will be null, because the mock API doesn't return this. So, we mock - // it's value in order to make it consistent with the subsequent call to mock API. - val nextState = spy((result as SignInResult.MFARequired).nextState) - nextState.mockCorrelationId(correlationId) - - // Initiate challenge, send code to email - val sendChallengeResult = nextState.requestChallenge() - assertResult(sendChallengeResult) - (sendChallengeResult as MFARequiredResult.VerificationRequired) - assertNotNull(sendChallengeResult.sentTo) - assertNotNull(sendChallengeResult.codeLength) - assertNotNull(sendChallengeResult.channel) - - correlationId = UUID.randomUUID().toString() - // 4a. Call /introspect to get additional methods + // 4a. Call /introspect to get methods // 4b. Return list of auth methods configureMockApi( MockApiEndpoint.Introspect, correlationId, MockApiResponseType.INTROSPECT_SUCCESS ) + + val result = application.signIn(username, password) + assertResult(result) + // correlation ID field in will be null, because the mock API doesn't return this. So, we mock // it's value in order to make it consistent with the subsequent call to mock API. - val nextState2 = spy(sendChallengeResult.nextState) - nextState2.mockCorrelationId(correlationId) - - // Call /introspect to get all auth methods - val getAuthMethodsResult = nextState2.getAuthMethods() - assertResult(getAuthMethodsResult) - (getAuthMethodsResult as MFARequiredResult.SelectionRequired) - assertNotNull(getAuthMethodsResult.authMethods) + val nextState = spy((result as SignInResult.MFARequired).nextState) + nextState.mockCorrelationId(correlationId) - correlationId = UUID.randomUUID().toString() // 5a. Sign in challenge for specified auth method // 5b. Setup server response with oob required configureMockApi( @@ -3209,18 +2905,13 @@ class NativeAuthPublicClientApplicationKotlinTest(private val allowPII: Boolean) MockApiResponseType.CHALLENGE_TYPE_OOB ) - // correlation ID field in will be null, because the mock API doesn't return this. So, we mock - // it's value in order to make it consistent with the subsequent call to mock API. - val nextState3 = spy(getAuthMethodsResult.nextState) - nextState3.mockCorrelationId(correlationId) - - // Call /challenge with specified ID - val sendSpecifiedChallengeResult = nextState3.requestChallenge(getAuthMethodsResult.authMethods[0]) - assertResult(sendSpecifiedChallengeResult) - (sendSpecifiedChallengeResult as MFARequiredResult.VerificationRequired) - assertNotNull(sendSpecifiedChallengeResult.sentTo) - assertNotNull(sendSpecifiedChallengeResult.codeLength) - assertNotNull(sendSpecifiedChallengeResult.channel) + // Initiate challenge, send code to email + val sendChallengeResult = nextState.requestChallenge(result.authMethods.first()) + assertResult(sendChallengeResult) + (sendChallengeResult as MFARequiredResult.VerificationRequired) + assertNotNull(sendChallengeResult.sentTo) + assertNotNull(sendChallengeResult.codeLength) + assertNotNull(sendChallengeResult.channel) correlationId = UUID.randomUUID().toString() // 6a. Token with oob @@ -3233,7 +2924,7 @@ class NativeAuthPublicClientApplicationKotlinTest(private val allowPII: Boolean) // correlation ID field in will be null, because the mock API doesn't return this. So, we mock // it's value in order to make it consistent with the subsequent call to mock API. - val nextState4 = spy(sendSpecifiedChallengeResult.nextState) + val nextState4 = spy(sendChallengeResult.nextState) nextState4.mockCorrelationId(correlationId) val submitChallengeResult = nextState4.submitChallenge(code) @@ -3251,7 +2942,7 @@ class NativeAuthPublicClientApplicationKotlinTest(private val allowPII: Boolean) // correlation ID field in will be null, because the mock API doesn't return this. So, we mock // it's value in order to make it consistent with the subsequent call to mock API. - val nextState5 = spy(sendSpecifiedChallengeResult.nextState) + val nextState5 = spy(sendChallengeResult.nextState) nextState4.mockCorrelationId(correlationId) // 8b. Call SDK interface @@ -3305,7 +2996,7 @@ class NativeAuthPublicClientApplicationKotlinTest(private val allowPII: Boolean) val nextState = spy((result as SignInResult.MFARequired).nextState) nextState.mockCorrelationId(correlationId) - val sendChallengeResult = nextState.requestChallenge() + val sendChallengeResult = nextState.requestChallenge(result.authMethods.first()) assertResult(sendChallengeResult) (sendChallengeResult as MFARequiredResult.VerificationRequired) assertNotNull(sendChallengeResult.sentTo) @@ -3327,7 +3018,7 @@ class NativeAuthPublicClientApplicationKotlinTest(private val allowPII: Boolean) nextState2.mockCorrelationId(correlationId) // Resend - val resendChallengeResult = nextState2.requestChallenge() + val resendChallengeResult = nextState2.requestChallenge(result.authMethods.first()) assertResult(resendChallengeResult) (resendChallengeResult as MFARequiredResult.VerificationRequired) assertNotNull(resendChallengeResult.sentTo)