diff --git a/common b/common index 4941a0f713..73483c83aa 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit 4941a0f7131161c653f11d7f9b75a255976ea74b +Subproject commit 73483c83aa0bc410f28f9d45556291de238d8cdf diff --git a/msal/src/main/java/com/microsoft/identity/client/PublicClientApplication.java b/msal/src/main/java/com/microsoft/identity/client/PublicClientApplication.java index 6bc5a50725..f0cf2b52d3 100644 --- a/msal/src/main/java/com/microsoft/identity/client/PublicClientApplication.java +++ b/msal/src/main/java/com/microsoft/identity/client/PublicClientApplication.java @@ -113,7 +113,6 @@ import com.microsoft.identity.common.java.commands.parameters.GenerateShrCommandParameters; import com.microsoft.identity.common.java.commands.parameters.InteractiveTokenCommandParameters; import com.microsoft.identity.common.java.commands.parameters.SilentTokenCommandParameters; -import com.microsoft.identity.common.java.controllers.BaseController; import com.microsoft.identity.common.java.controllers.CommandDispatcher; import com.microsoft.identity.common.java.controllers.CommandResult; import com.microsoft.identity.common.java.controllers.ExceptionAdapter; @@ -143,6 +142,7 @@ import com.microsoft.identity.nativeauth.INativeAuthPublicClientApplication; import com.microsoft.identity.nativeauth.NativeAuthPublicClientApplication; import com.microsoft.identity.nativeauth.NativeAuthPublicClientApplicationConfiguration; +import com.microsoft.identity.nativeauth.NativeAuthPublicClientApplicationParameters; import java.io.File; import java.util.ArrayList; @@ -237,6 +237,7 @@ static class NONNULL_CONSTANTS { static final String AUTHORITY = "authority"; static final String REDIRECT_URI = "redirect_uri"; static final String CONFIG_FILE = "config_file"; + static final String CLIENT_PARAMETER = "client_parameter"; static final String ACTIVITY = "activity"; static final String SCOPES = "scopes"; static final String ACCOUNT = "account"; @@ -846,6 +847,7 @@ public static INativeAuthPublicClientApplication createNativeAuthPublicClientApp null, null, null, + null, null ); } catch (MsalException e) { @@ -897,6 +899,7 @@ public static INativeAuthPublicClientApplication createNativeAuthPublicClientApp null, null, null, + null, null ); } catch (BaseException e) { @@ -928,8 +931,12 @@ public static INativeAuthPublicClientApplication createNativeAuthPublicClientApp * @param clientId The application client id. Cannot be null. * @param authority The default authority to be used for the authority. If this is null, the default authority will be used. * @param redirectUri The redirect URI of the application. + * @param challengeTypes The challengeTypes supported for authentication declared by client. * @return An instance of INativeAuthPublicClientApplication. + * + * @deprecated This method is deprecated. Use createNativeAuthPublicClientApplication(Context, NativeAuthPublicClientApplicationConfiguration) instead. */ + @Deprecated public static INativeAuthPublicClientApplication createNativeAuthPublicClientApplication( @NonNull final Context context, @NonNull final String clientId, @@ -947,7 +954,59 @@ public static INativeAuthPublicClientApplication createNativeAuthPublicClientApp clientId, authority, redirectUri, - challengeTypes + challengeTypes, + null + ); + } catch (BaseException e) { + throw new MsalClientException( + UNKNOWN_ERROR, + NATIVE_AUTH_APPLICATION_CREATION_UNKNOWN_ERROR_MESSAGE, + e + ); + } + } + + /** + * Creates an instance of INativeAuthPublicClientApplication using the provided context and configuration. + * + *

{@link PublicClientApplication#createNativeAuthPublicClientApplication(Context, NativeAuthPublicClientApplicationParameters)} + * will read the client id and other configuration settings from the provided configuration object.

+ * + *

This function will pass back an {@link MsalClientException} object if it is unable + * to return {@link INativeAuthPublicClientApplication}. For example, AccountMode + * in configuration is not set to single.

+ * + * @param context Application's {@link Context}. The SDK requires the application context + * to be passed in {@link PublicClientApplication}. Cannot be null. + *

+ * Note: The {@link Context} should be the application context instead of + * the running activity's context, which could potentially make the SDK hold a + * strong reference to the activity, thus preventing correct garbage + * collection and causing bugs. + *

+ * @param parameters The NativeAuthPublicClientApplication parameter class containing mandatory client ID, authorityUri, challenge types and optional capabilities, redirectUri. + * Cannot be null. + *

+ * For more information on the schema of the MSAL configuration object, + * please see Android app resource overview + * and MSAL Github Wiki. + *

+ * @return An instance of INativeAuthPublicClientApplication. + */ + public static INativeAuthPublicClientApplication createNativeAuthPublicClientApplication( + @NonNull final Context context, + @NonNull final NativeAuthPublicClientApplicationParameters parameters) throws MsalException { + validateNonNullArgument(context, NONNULL_CONSTANTS.CONTEXT); + validateNonNullArgument(parameters, NONNULL_CONSTANTS.CLIENT_PARAMETER); + + try { + return createNativeAuthApplication( + Companion.initializeNativeAuthConfiguration(context), + parameters.getClientId(), + parameters.getAuthorityUrl(), + parameters.getRedirectUri(), + parameters.getChallengeTypes(), + parameters.getCapabilities() ); } catch (BaseException e) { throw new MsalClientException( @@ -1133,7 +1192,8 @@ private static NativeAuthPublicClientApplication createNativeAuthApplication(@No @Nullable final String clientId, @Nullable final String authority, @Nullable final String redirectUri, - @Nullable final List challengeTypes) throws BaseException { + @Nullable final List challengeTypes, + @Nullable final List capabilities) throws BaseException { if (clientId != null) { config.setClientId(clientId); } @@ -1154,6 +1214,10 @@ private static NativeAuthPublicClientApplication createNativeAuthApplication(@No config.setChallengeTypes(challengeTypes); } + if (capabilities != null) { + config.setCapabilities(capabilities); + } + // Check whether account mode is set to SINGLE validateAccountModeConfiguration(config); diff --git a/msal/src/main/java/com/microsoft/identity/client/exception/MsalClientException.java b/msal/src/main/java/com/microsoft/identity/client/exception/MsalClientException.java index 99cca81f0a..dda91eabbf 100644 --- a/msal/src/main/java/com/microsoft/identity/client/exception/MsalClientException.java +++ b/msal/src/main/java/com/microsoft/identity/client/exception/MsalClientException.java @@ -278,7 +278,9 @@ public final class MsalClientException extends MsalException { * Configuration error. Native auth app passed with an invalid challenge type. */ public static final String NATIVE_AUTH_INVALID_CHALLENGE_TYPE_ERROR_CODE = "native_auth_invalid_challenge_type"; + public static final String NATIVE_AUTH_INVALID_CAPABILITY_ERROR_CODE = "native_auth_invalid_capability"; public static final String NATIVE_AUTH_INVALID_CHALLENGE_TYPE_ERROR_MESSAGE = "NativeAuthPublicClientApplication detected invalid challenge type."; + public static final String NATIVE_AUTH_INVALID_CAPABILITY_ERROR_MESSAGE = "NativeAuthPublicClientApplication detected invalid capability."; public MsalClientException(final String errorCode) { super(errorCode); 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 bcd31dd485..28d41e459f 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 @@ -362,6 +362,7 @@ public static SignUpStartCommandParameters createSignUpStartCommandParameters( .username(username) .password(password) .challengeType(configuration.getChallengeTypes()) + .capabilities(configuration.getCapabilities()) .userAttributes(userAttributes) // Start of the flow, so there is no correlation ID to use from a previous API response. // Set it to a default value. @@ -569,6 +570,7 @@ public static SignInStartCommandParameters createSignInStartCommandParameters( .authenticationScheme(authenticationScheme) .clientId(configuration.getClientId()) .challengeType(configuration.getChallengeTypes()) + .capabilities(configuration.getCapabilities()) .claimsRequestJson(claimsRequestJson) .scopes(scopes) // Start of the flow, so there is no correlation ID to use from a previous API response. @@ -993,6 +995,7 @@ public static ResetPasswordStartCommandParameters createResetPasswordStartComman .authority(authority) .username(username) .challengeType(configuration.getChallengeTypes()) + .capabilities(configuration.getCapabilities()) .clientId(configuration.getClientId()) // Start of the flow, so there is no correlation ID to use from a previous API response. // Set it to a default value. diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/NativeAuthPublicClientApplicationConfiguration.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/NativeAuthPublicClientApplicationConfiguration.kt index e2520bc399..35fd651d7e 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/NativeAuthPublicClientApplicationConfiguration.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/NativeAuthPublicClientApplicationConfiguration.kt @@ -48,10 +48,13 @@ public class NativeAuthPublicClientApplicationConfiguration : private val TAG = NativeAuthPublicClientApplicationConfiguration::class.java.simpleName private val VALID_CHALLENGE_TYPES = listOf(NativeAuthConstants.ChallengeType.PASSWORD, NativeAuthConstants.ChallengeType.OOB, NativeAuthConstants.ChallengeType.REDIRECT) + private val VALID_CAPABILITIES = listOf(NativeAuthConstants.Capabilities.MFA_REQUIRED, + NativeAuthConstants.Capabilities.REGISTRATION_REQUIRED) } private object NativeAuthSerializedNames { const val CHALLENGE_TYPES = "challenge_types" + const val CAPABILITIES = "capabilities" const val USE_MOCK_API = "use_mock_api_for_native_auth" const val DC = "dc" } @@ -61,6 +64,11 @@ public class NativeAuthPublicClientApplicationConfiguration : @SerializedName(NativeAuthSerializedNames.CHALLENGE_TYPES) private var challengeTypes: List? = null + //List of capabilities supported by the client. + //For a complete list of capabilities see [NativeAuthConstants.Capabilities] + @SerializedName(NativeAuthSerializedNames.CAPABILITIES) + private var capabilities: List? = null + //The mock API authority used for testing will be rejected by validation logic run on // instantiation. This flag is used to bypass those checks in various points in the application @SerializedName(NativeAuthSerializedNames.USE_MOCK_API) @@ -79,6 +87,14 @@ public class NativeAuthPublicClientApplicationConfiguration : this.challengeTypes = challengeTypes } + fun getCapabilities(): List? { + return capabilities + } + + fun setCapabilities(capabilities: List?) { + this.capabilities = capabilities + } + fun mergeConfiguration(config: NativeAuthPublicClientApplicationConfiguration) { // Call super.mergeConfiguration to handle base configuration fields super.mergeConfiguration(config) @@ -91,6 +107,8 @@ public class NativeAuthPublicClientApplicationConfiguration : // Handle Native Auth specific fields challengeTypes = if (config.challengeTypes == null) challengeTypes else config.challengeTypes + capabilities = if (config.capabilities == null) capabilities else config.capabilities + useMockAuthority = if (config.useMockAuthority == null) useMockAuthority else config.useMockAuthority dc = if (config.dc == null) dc else config.dc @@ -174,15 +192,21 @@ public class NativeAuthPublicClientApplicationConfiguration : // Check that challenge types are all valid validateChallengeTypes() + // Check that capabilities are all valid + validateCapabilities() } /** * Validates that the challenge types passed are valid */ + @Throws(MsalClientException::class) private fun validateChallengeTypes() { // Make all challenge types lowercase for simplicity challengeTypes = challengeTypes?.map { it.lowercase() } + // Remove duplicate capabilities + challengeTypes = challengeTypes?.distinct() + challengeTypes?.forEach { challengeType -> // Make sure challenge types passed were valid if (challengeType !in VALID_CHALLENGE_TYPES) { @@ -194,6 +218,28 @@ public class NativeAuthPublicClientApplicationConfiguration : } } + /** + * Validates that the capabilities passed are valid + */ + @Throws(MsalClientException::class) + private fun validateCapabilities() { + // Make all capabilities lowercase for simplicity + capabilities = capabilities?.map { it.lowercase() } + + // Remove duplicate capabilities + capabilities = capabilities?.distinct() + + capabilities?.forEach { capability -> + // Make sure capabilities passed were valid + if (capability !in VALID_CAPABILITIES) { + throw MsalClientException( + MsalClientException.NATIVE_AUTH_INVALID_CAPABILITY_ERROR_CODE, + MsalClientException.NATIVE_AUTH_INVALID_CAPABILITY_ERROR_MESSAGE + " \"" + capability + "\"" + ) + } + } + } + /** * Overriding this method to add a check for redirect uri. If no uri was passed, we don't need to check this. */ diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/NativeAuthPublicClientApplicationParameters.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/NativeAuthPublicClientApplicationParameters.kt new file mode 100644 index 0000000000..b6a36e1d69 --- /dev/null +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/NativeAuthPublicClientApplicationParameters.kt @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package com.microsoft.identity.nativeauth + + +public class NativeAuthPublicClientApplicationParameters ( + /** + * The application client id. Cannot be null. + */ + val clientId: String, + /** + * The authorityUrl to be used for the authority. + */ + val authorityUrl: String, + /** + * The challenge types supported for authentication declared by client. Cannot be null. + */ + val challengeTypes: List, +) { + + /** + * The capabilities supported for authentication declared by client. + */ + var capabilities: List? = null + + /** + * The redirect URI of the application. Required for using browser. + */ + var redirectUri: String? = null +} diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/parameters/NativeAuthChallengeAuthMethodParameters.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/parameters/NativeAuthChallengeAuthMethodParameters.kt index c8818674b2..9c1418d2be 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/parameters/NativeAuthChallengeAuthMethodParameters.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/parameters/NativeAuthChallengeAuthMethodParameters.kt @@ -1,3 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + package com.microsoft.identity.nativeauth.parameters import com.microsoft.identity.nativeauth.AuthMethod diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/parameters/NativeAuthRegisterStrongAuthVerificationRequiredResultParameters.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/parameters/NativeAuthRegisterStrongAuthVerificationRequiredResultParameters.kt index ad46912259..96dad98307 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/parameters/NativeAuthRegisterStrongAuthVerificationRequiredResultParameters.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/parameters/NativeAuthRegisterStrongAuthVerificationRequiredResultParameters.kt @@ -1,3 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + package com.microsoft.identity.nativeauth.parameters import com.microsoft.identity.nativeauth.statemachine.states.RegisterStrongAuthVerificationRequiredState diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/errors/Error.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/errors/Error.kt index 0a89c40957..c93dc23d19 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/errors/Error.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/errors/Error.kt @@ -175,7 +175,7 @@ class ResendCodeError( override val correlationId: String, override val errorCodes: List? = null, override var exception: Exception? = null -): SignInResendCodeResult, SignUpResendCodeResult, ResetPasswordResendCodeResult, Error(errorType = errorType, error = error, errorMessage= errorMessage, correlationId = correlationId, errorCodes = errorCodes, exception = exception) +): BrowserRequiredError, SignInResendCodeResult, SignUpResendCodeResult, ResetPasswordResendCodeResult, Error(errorType = errorType, error = error, errorMessage= errorMessage, correlationId = correlationId, errorCodes = errorCodes, exception = exception) class GetAccountError( override val errorType: String? = null, @@ -193,4 +193,4 @@ class SignOutError( override val correlationId: String, override val errorCodes: List? = null, override var exception: Exception? = null -): SignOutResult, Error(errorType = errorType, error = error, errorMessage= errorMessage, correlationId = correlationId, errorCodes = errorCodes, exception = exception) \ No newline at end of file +): SignOutResult, Error(errorType = errorType, error = error, errorMessage= errorMessage, correlationId = correlationId, errorCodes = errorCodes, exception = exception) diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/errors/GetAccessTokenError.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/errors/GetAccessTokenError.kt index aabb96ba4a..91785debe4 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/errors/GetAccessTokenError.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/errors/GetAccessTokenError.kt @@ -48,7 +48,7 @@ class GetAccessTokenError( override val correlationId: String, override val errorCodes: List? = null, override var exception: Exception? = null -): GetAccessTokenResult, Error(errorType = errorType, error = error, errorMessage= errorMessage, correlationId = correlationId, errorCodes = errorCodes, exception = exception) { +): BrowserRequiredError, GetAccessTokenResult, Error(errorType = errorType, error = error, errorMessage= errorMessage, correlationId = correlationId, errorCodes = errorCodes, exception = exception) { fun isNoAccountFound() : Boolean = this.errorType == GetAccessTokenErrorTypes.NO_ACCOUNT_FOUND fun isInvalidScopes(): Boolean = this.errorType == GetAccessTokenErrorTypes.INVALID_SCOPES diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/errors/JITErrors.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/errors/JITErrors.kt index aaf7f545f4..a3c31348f7 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/errors/JITErrors.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/errors/JITErrors.kt @@ -10,7 +10,7 @@ class RegisterStrongAuthChallengeError( override val correlationId: String, override val errorCodes: List? = null, override var exception: Exception? = null -): RegisterStrongAuthChallengeResult, Error(errorType = errorType, error = error, errorMessage= errorMessage, correlationId = correlationId, errorCodes = errorCodes, exception = exception) +): BrowserRequiredError, RegisterStrongAuthChallengeResult, Error(errorType = errorType, error = error, errorMessage= errorMessage, correlationId = correlationId, errorCodes = errorCodes, exception = exception) { fun isInvalidInput(): Boolean = this.errorType == ErrorTypes.INVALID_INPUT } @@ -22,7 +22,7 @@ class RegisterStrongAuthSubmitChallengeError( override val correlationId: String, override val errorCodes: List? = null, override var exception: Exception? = null -): RegisterStrongAuthSubmitChallengeResult, Error(errorType = errorType, error = error, errorMessage= errorMessage, correlationId = correlationId, errorCodes = errorCodes, exception = exception) +): BrowserRequiredError, RegisterStrongAuthSubmitChallengeResult, Error(errorType = errorType, error = error, errorMessage= errorMessage, correlationId = correlationId, errorCodes = errorCodes, exception = exception) { fun isInvalidChallenge(): Boolean = this.errorType == ErrorTypes.INVALID_CHALLENGE -} \ No newline at end of file +} 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 f56795672f..b6c6a583f6 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 @@ -66,7 +66,7 @@ class MFASubmitChallengeError( override val errorCodes: List? = null, val subError: String? = null, override var exception: Exception? = null -): MFASubmitChallengeResult, Error(errorType = errorType, error = error, errorMessage= errorMessage, correlationId = correlationId, errorCodes = errorCodes, exception = exception) +): BrowserRequiredError, MFASubmitChallengeResult, Error(errorType = errorType, error = error, errorMessage= errorMessage, correlationId = correlationId, errorCodes = errorCodes, exception = exception) { fun isInvalidChallenge(): Boolean = this.errorType == ErrorTypes.INVALID_CHALLENGE -} \ No newline at end of file +} diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/errors/ResetPasswordErrors.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/errors/ResetPasswordErrors.kt index 3bbcf97381..417dd3558b 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/errors/ResetPasswordErrors.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/errors/ResetPasswordErrors.kt @@ -84,7 +84,7 @@ class ResetPasswordSubmitPasswordError( override val errorCodes: List? = null, val subError: String? = null, override var exception: Exception? = null -): ResetPasswordSubmitPasswordResult, Error(errorType = errorType, error = error, errorMessage= errorMessage, correlationId = correlationId, errorCodes = errorCodes, exception = exception) { +): BrowserRequiredError, ResetPasswordSubmitPasswordResult, Error(errorType = errorType, error = error, errorMessage= errorMessage, correlationId = correlationId, errorCodes = errorCodes, exception = exception) { fun isInvalidPassword() : Boolean = this.errorType == ErrorTypes.INVALID_PASSWORD diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/errors/SignInErrors.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/errors/SignInErrors.kt index 792a4ce574..64e6af5036 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/errors/SignInErrors.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/errors/SignInErrors.kt @@ -61,7 +61,7 @@ class SignInSubmitPasswordError( override val correlationId: String, override val errorCodes: List? = null, override var exception: Exception? = null -): SignInSubmitPasswordResult, Error(errorType = errorType, error = error, errorMessage= errorMessage, correlationId = correlationId, errorCodes = errorCodes, exception = exception) { +): BrowserRequiredError, SignInSubmitPasswordResult, Error(errorType = errorType, error = error, errorMessage= errorMessage, correlationId = correlationId, errorCodes = errorCodes, exception = exception) { fun isInvalidCredentials(): Boolean = this.errorType == SignInErrorTypes.INVALID_CREDENTIALS } @@ -77,9 +77,10 @@ class SignInSubmitPasswordError( * @param exception an internal unexpected exception that happened. */ open class SignInContinuationError( + override val errorType: String? = null, override val error: String? = null, override val errorMessage: String?, override val correlationId: String, override val errorCodes: List? = null, override var exception: Exception? = null -): SignInResult, Error(errorType = null, error = error, errorMessage= errorMessage, correlationId = correlationId, errorCodes = errorCodes, exception = exception) +): BrowserRequiredError, SignInResult, Error(errorType = errorType, error = error, errorMessage= errorMessage, correlationId = correlationId, errorCodes = errorCodes, exception = exception) diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/JITStates.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/JITStates.kt index 43256ca091..8434b15082 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/JITStates.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/JITStates.kt @@ -122,6 +122,15 @@ abstract class BaseJITSubmitChallengeState( ) ) } + is INativeAuthCommandResult.Redirect -> { + RegisterStrongAuthChallengeError( + errorType = ErrorTypes.BROWSER_REQUIRED, + error = result.error, + errorMessage = result.redirectReason, + correlationId = result.correlationId, + errorCodes = result.errorCodes + ) + } } } } @@ -314,6 +323,15 @@ class RegisterStrongAuthVerificationRequiredState( errorCodes = result.errorCodes ) } + is INativeAuthCommandResult.Redirect -> { + RegisterStrongAuthSubmitChallengeError( + errorType = ErrorTypes.BROWSER_REQUIRED, + error = result.error, + errorMessage = result.redirectReason, + correlationId = result.correlationId, + errorCodes = result.errorCodes + ) + } is INativeAuthCommandResult.APIError -> { Logger.warnWithObject( TAG, 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 ed1c4069fb..2dbcaf149a 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 @@ -176,7 +176,7 @@ class AwaitingMFAState( MFARequestChallengeError( errorType = ErrorTypes.BROWSER_REQUIRED, error = result.error, - errorMessage = result.errorDescription, + errorMessage = result.redirectReason, correlationId = result.correlationId ) } @@ -323,7 +323,7 @@ class MFARequiredState( MFAGetAuthMethodsError( errorType = ErrorTypes.BROWSER_REQUIRED, error = result.error, - errorMessage = result.errorDescription, + errorMessage = result.redirectReason, correlationId = result.correlationId ) } @@ -464,7 +464,7 @@ class MFARequiredState( MFARequestChallengeError( errorType = ErrorTypes.BROWSER_REQUIRED, error = result.error, - errorMessage = result.errorDescription, + errorMessage = result.redirectReason, correlationId = result.correlationId ) } @@ -568,7 +568,15 @@ class MFARequiredState( errorCodes = result.errorCodes, subError = result.subError ) - + } + is INativeAuthCommandResult.Redirect -> { + MFASubmitChallengeError( + errorType = ErrorTypes.BROWSER_REQUIRED, + error = result.error, + errorMessage = result.redirectReason, + correlationId = result.correlationId, + errorCodes = result.errorCodes + ) } is INativeAuthCommandResult.APIError -> { Logger.warnWithObject( diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/ResetPasswordStates.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/ResetPasswordStates.kt index 103ab149f9..af0f1547c8 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/ResetPasswordStates.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/ResetPasswordStates.kt @@ -167,7 +167,7 @@ class ResetPasswordCodeRequiredState internal constructor( SubmitCodeError( errorType = ErrorTypes.BROWSER_REQUIRED, error = result.error, - errorMessage = result.errorDescription, + errorMessage = result.redirectReason, correlationId = result.correlationId ) } @@ -271,7 +271,16 @@ class ResetPasswordCodeRequiredState internal constructor( ) } - is INativeAuthCommandResult.Redirect, + is INativeAuthCommandResult.Redirect -> { + ResendCodeError( + errorType = ErrorTypes.BROWSER_REQUIRED, + error = result.error, + errorMessage = result.redirectReason, + correlationId = result.correlationId, + errorCodes = result.errorCodes + ) + } + is INativeAuthCommandResult.APIError -> { Logger.warnWithObject( TAG, @@ -449,6 +458,16 @@ class ResetPasswordPasswordRequiredState internal constructor( ) } + is INativeAuthCommandResult.Redirect -> { + ResetPasswordSubmitPasswordError( + errorType = ErrorTypes.BROWSER_REQUIRED, + error = result.error, + errorMessage = result.redirectReason, + correlationId = result.correlationId, + errorCodes = result.errorCodes + ) + } + 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 bb7c5f5bba..8b74cc60b2 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 @@ -181,7 +181,7 @@ class SignInCodeRequiredState internal constructor( SubmitCodeError( errorType = ErrorTypes.BROWSER_REQUIRED, error = result.error, - errorMessage = result.errorDescription, + errorMessage = result.redirectReason, correlationId = result.correlationId ) } @@ -287,7 +287,17 @@ class SignInCodeRequiredState internal constructor( ) } - is INativeAuthCommandResult.Redirect, is INativeAuthCommandResult.APIError -> { + is INativeAuthCommandResult.Redirect -> { + ResendCodeError( + errorType = ErrorTypes.BROWSER_REQUIRED, + error = result.error, + errorMessage = result.redirectReason, + correlationId = result.correlationId, + errorCodes = result.errorCodes + ) + } + + is INativeAuthCommandResult.APIError -> { Logger.warnWithObject( TAG, result.correlationId, @@ -466,7 +476,18 @@ class SignInPasswordRequiredState( ) ) } - is INativeAuthCommandResult.Redirect, is INativeAuthCommandResult.APIError -> { + + is INativeAuthCommandResult.Redirect -> { + SignInSubmitPasswordError( + errorType = ErrorTypes.BROWSER_REQUIRED, + error = result.error, + errorMessage = result.redirectReason, + correlationId = result.correlationId, + errorCodes = result.errorCodes + ) + } + + is INativeAuthCommandResult.APIError -> { Logger.warnWithObject( TAG, result.correlationId, @@ -698,7 +719,15 @@ class SignInContinuationState( authMethods = result.authMethods.toListOfAuthMethods() ) } - is INativeAuthCommandResult.Redirect, + is INativeAuthCommandResult.Redirect -> { + SignInContinuationError( + errorType = ErrorTypes.BROWSER_REQUIRED, + error = result.error, + errorMessage = result.redirectReason, + correlationId = result.correlationId, + errorCodes = result.errorCodes + ) + } is INativeAuthCommandResult.APIError -> { Logger.warnWithObject( TAG, diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/SignUpStates.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/SignUpStates.kt index 3270b49e51..0e97b55cd3 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/SignUpStates.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/SignUpStates.kt @@ -198,7 +198,7 @@ class SignUpCodeRequiredState internal constructor( SubmitCodeError( errorType = ErrorTypes.BROWSER_REQUIRED, error = result.error, - errorMessage = result.errorDescription, + errorMessage = result.redirectReason, correlationId = result.correlationId ) } @@ -313,8 +313,16 @@ class SignUpCodeRequiredState internal constructor( channel = result.challengeChannel ) } - - is INativeAuthCommandResult.Redirect, is INativeAuthCommandResult.APIError -> { + is INativeAuthCommandResult.Redirect -> { + ResendCodeError( + errorType = ErrorTypes.BROWSER_REQUIRED, + error = result.error, + errorMessage = result.redirectReason, + correlationId = result.correlationId, + errorCodes = result.errorCodes + ) + } + is INativeAuthCommandResult.APIError -> { Logger.warnWithObject( TAG, result.correlationId, @@ -487,7 +495,7 @@ class SignUpPasswordRequiredState internal constructor( SignUpSubmitPasswordError( errorType = ErrorTypes.BROWSER_REQUIRED, error = result.error, - errorMessage = result.errorDescription, + errorMessage = result.redirectReason, correlationId = result.correlationId ) } @@ -695,7 +703,7 @@ class SignUpAttributesRequiredState internal constructor( SignUpSubmitAttributesError( errorType = ErrorTypes.BROWSER_REQUIRED, error = result.error, - errorMessage = result.errorDescription, + errorMessage = result.redirectReason, correlationId = result.correlationId ) } 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 a088841e09..07bf0c05f5 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 @@ -50,11 +50,12 @@ class GetTokenTests : NativeAuthPublicClientApplicationAbstractTest() { private val defaultConfigType = ConfigType.SIGN_IN_PASSWORD private val defaultChallengeTypes = listOf("password", "oob") + private val defaultCapabilities = listOf("mfa_required", "registration_required") override fun setup() { super.setup() config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) resources = config.resources } 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 229e0487d1..63ae2ad800 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 @@ -40,6 +40,7 @@ import com.microsoft.identity.internal.testutils.labutils.LabUserQuery 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.NativeAuthPublicClientApplicationParameters import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.setMain @@ -114,15 +115,19 @@ abstract class NativeAuthPublicClientApplicationAbstractTest : IPublicClientAppl ?: throw IllegalStateException("Config not $secretValue") } - fun setupPCA(config: NativeAuthTestConfig.Config, challengeTypes: List): INativeAuthPublicClientApplication { + fun setupPCA(config: NativeAuthTestConfig.Config, challengeTypes: List, capabilities: List): INativeAuthPublicClientApplication { return try { - PublicClientApplication.createNativeAuthPublicClientApplication( - context, + val parameters = NativeAuthPublicClientApplicationParameters( config.clientId, config.authorityUrl, - null, challengeTypes ) + parameters.capabilities = capabilities + + PublicClientApplication.createNativeAuthPublicClientApplication( + context, + parameters + ) } 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 5389e39855..4d26af3e85 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 @@ -49,6 +49,7 @@ class SSPRTest : NativeAuthPublicClientApplicationAbstractTest() { private val defaultConfigType = ConfigType.SSPR private val defaultChallengeTypes = listOf("password", "oob") + private val defaultCapabilities = listOf("mfa_required", "registration_required") /** * Verify email with email OTP first and then reset password. @@ -58,7 +59,7 @@ class SSPRTest : NativeAuthPublicClientApplicationAbstractTest() { @Test fun testSSPRSuccess() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) var result: ResetPasswordStartResult @@ -88,7 +89,7 @@ class SSPRTest : NativeAuthPublicClientApplicationAbstractTest() { @Test fun testErrorInvalidPasswordFormat() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) var result: ResetPasswordStartResult @@ -119,7 +120,7 @@ class SSPRTest : NativeAuthPublicClientApplicationAbstractTest() { @Test fun testResendCode() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) var result: ResetPasswordStartResult @@ -155,7 +156,7 @@ class SSPRTest : NativeAuthPublicClientApplicationAbstractTest() { @Test fun testErrorUserNotExist() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) runBlocking { val username = tempEmailApi.generateRandomEmailAddressLocally() @@ -173,7 +174,7 @@ class SSPRTest : NativeAuthPublicClientApplicationAbstractTest() { @Test fun testErrorInsufficientChallengesBrowserRequired() { config = getConfig(defaultConfigType) - application = setupPCA(config, listOf("password")) + application = setupPCA(config, listOf("password"), defaultCapabilities) runBlocking { val username = config.email @@ -191,7 +192,7 @@ class SSPRTest : NativeAuthPublicClientApplicationAbstractTest() { @Test fun testErrorNoPasswordLinked() { config = getConfig(ConfigType.SIGN_IN_OTP) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes,defaultCapabilities) runBlocking { val username = config.email @@ -210,7 +211,7 @@ class SSPRTest : NativeAuthPublicClientApplicationAbstractTest() { @Test fun testErrorUserExistAsSocial() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) runBlocking { val username = INVALID_EMAIL // TODO: Use social accounts instead when ready 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 4a88444bce..5db7671f5c 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 @@ -47,12 +47,7 @@ class SignInEmailOTPTest : NativeAuthPublicClientApplicationAbstractTest() { 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) - } + private val defaultCapabilities = listOf("mfa_required", "registration_required") /** * Use valid email and OTP to get token and sign in. @@ -62,7 +57,7 @@ class SignInEmailOTPTest : NativeAuthPublicClientApplicationAbstractTest() { @Test fun testSuccess() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) retryOperation { runBlocking { @@ -84,7 +79,7 @@ class SignInEmailOTPTest : NativeAuthPublicClientApplicationAbstractTest() { @Test fun testErrorIsUserNotFound() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) retryOperation { runBlocking { @@ -104,7 +99,7 @@ class SignInEmailOTPTest : NativeAuthPublicClientApplicationAbstractTest() { @Test fun testErrorPasswordConfigBrowserRequired() { config = getConfig(ConfigType.SIGN_IN_PASSWORD) - application = setupPCA(config, listOf("oob")) + application = setupPCA(config, listOf("oob"), defaultCapabilities) runBlocking { val user = config.email @@ -122,7 +117,7 @@ class SignInEmailOTPTest : NativeAuthPublicClientApplicationAbstractTest() { @Test fun testSuccessConfigPasswordRequired() { config = getConfig(ConfigType.SIGN_IN_PASSWORD) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) runBlocking { val user = config.email @@ -145,7 +140,7 @@ class SignInEmailOTPTest : NativeAuthPublicClientApplicationAbstractTest() { @Test fun testResendCode() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) retryOperation { runBlocking { @@ -190,7 +185,7 @@ class SignInEmailOTPTest : NativeAuthPublicClientApplicationAbstractTest() { @Test fun testErrorIsInvalidCode() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) runBlocking { val user = config.email 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 ec75a1996d..17f5191312 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 @@ -47,6 +47,7 @@ class SignInEmailPasswordTest : NativeAuthPublicClientApplicationAbstractTest() private val defaultConfigType = ConfigType.SIGN_IN_PASSWORD private val defaultChallengeTypes = listOf("password", "oob") + private val defaultCapabilities = listOf("mfa_required", "registration_required") /** * Use valid email and password to get token. @@ -55,7 +56,7 @@ class SignInEmailPasswordTest : NativeAuthPublicClientApplicationAbstractTest() @Test fun testSuccess() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) runBlocking { val username = config.email @@ -73,7 +74,7 @@ class SignInEmailPasswordTest : NativeAuthPublicClientApplicationAbstractTest() @Test fun testErrorIsUserNotFound() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) runBlocking { val username = INVALID_EMAIL @@ -92,7 +93,7 @@ class SignInEmailPasswordTest : NativeAuthPublicClientApplicationAbstractTest() @Test fun testErrorIsInvalidCredentials() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) runBlocking { val username = config.email @@ -111,7 +112,7 @@ class SignInEmailPasswordTest : NativeAuthPublicClientApplicationAbstractTest() @Test fun testErrorOutOfPersistence() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) runBlocking { val username = config.email @@ -137,7 +138,7 @@ class SignInEmailPasswordTest : NativeAuthPublicClientApplicationAbstractTest() @Test fun testErrorOutOfPersistenceDifferentAccount() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) runBlocking { val param = NativeAuthSignInParameters(username = config.email) @@ -177,7 +178,7 @@ class SignInEmailPasswordTest : NativeAuthPublicClientApplicationAbstractTest() @Test fun testSuccessOTPConfigCodeRequired() { config = getConfig(ConfigType.SIGN_IN_OTP) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) runBlocking { val param = NativeAuthSignInParameters(username = config.email) @@ -203,7 +204,7 @@ class SignInEmailPasswordTest : NativeAuthPublicClientApplicationAbstractTest() @Test fun testErrorOTPConfigBrowserRequired() { config = getConfig(ConfigType.SIGN_IN_OTP) - application = setupPCA(config, listOf("password")) + application = setupPCA(config, listOf("password"), defaultCapabilities) runBlocking { val param = NativeAuthSignInParameters(username = config.email) @@ -221,7 +222,7 @@ class SignInEmailPasswordTest : NativeAuthPublicClientApplicationAbstractTest() @Test fun testSignOut() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) runBlocking { val param = NativeAuthSignInParameters(username = config.email) diff --git a/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignInJITTest.kt b/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignInJITTest.kt index 8a5ed0e747..5a4233e8bd 100644 --- a/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignInJITTest.kt +++ b/msal/src/test/java/com/microsoft/identity/client/e2e/tests/network/nativeauth/SignInJITTest.kt @@ -35,6 +35,7 @@ import com.microsoft.identity.nativeauth.parameters.NativeAuthSignInContinuation import com.microsoft.identity.nativeauth.parameters.NativeAuthSignInParameters import com.microsoft.identity.nativeauth.parameters.NativeAuthSignUpParameters import com.microsoft.identity.nativeauth.statemachine.errors.MFASubmitChallengeError +import com.microsoft.identity.nativeauth.statemachine.errors.SignInError import com.microsoft.identity.nativeauth.statemachine.results.GetAccessTokenResult import com.microsoft.identity.nativeauth.statemachine.results.MFARequiredResult import com.microsoft.identity.nativeauth.statemachine.results.RegisterStrongAuthChallengeResult @@ -66,6 +67,7 @@ class SignInJITTest : NativeAuthPublicClientApplicationAbstractTest() { private val defaultConfigType = ConfigType.SIGN_IN_MFA_SINGLE_AUTH private val defaultChallengeTypes = listOf("password", "oob") + private val defaultCapabilities = listOf("mfa_required", "registration_required") /** * Full flow: Ensure JIT is triggered on first signIn @@ -81,7 +83,7 @@ class SignInJITTest : NativeAuthPublicClientApplicationAbstractTest() { @Test fun `test sign in specifying custom verification contact`() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) resources = config.resources val authenticationContextId = "c4" val authenticationContextRequestClaimJson = "{\"access_token\":{\"acrs\":{\"essential\":true,\"value\":\"$authenticationContextId\"}}}" @@ -142,7 +144,7 @@ class SignInJITTest : NativeAuthPublicClientApplicationAbstractTest() { @Test fun `test sign after sign up without specify verification contact`() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) resources = config.resources val authenticationContextId = "c4" val authenticationContextRequestClaimJson = "{\"access_token\":{\"acrs\":{\"essential\":true,\"value\":\"$authenticationContextId\"}}}" @@ -199,7 +201,7 @@ class SignInJITTest : NativeAuthPublicClientApplicationAbstractTest() { @Test fun `test sign after sign up with specify verification contact`() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) resources = config.resources val authenticationContextId = "c4" val authenticationContextRequestClaimJson = "{\"access_token\":{\"acrs\":{\"essential\":true,\"value\":\"$authenticationContextId\"}}}" @@ -245,4 +247,49 @@ class SignInJITTest : NativeAuthPublicClientApplicationAbstractTest() { } } } + + /** + * Full flow: Ensure that correct error is returned when an user doesn’t supply the “mfa_required registration_required” capabilities + * - Initialise client with insufficient capabilities config + * - SignUp a new user with username and password + * - SignIn specifying authentication context as claim + * - Check that JIT flow is triggered + * - Do not specify a verification contact + * - Return redirect with reason registration required was not supplied + * + */ + @Ignore("Backward compatibility feature not available in eSTS production") + @Test + fun `test Redirect is triggered when Capabilities insufficient`() { + config = getConfig(defaultConfigType) + // Initialise client with insufficient capabilities config + application = setupPCA(config, defaultChallengeTypes, listOf("mfa_required")) + resources = config.resources + val authenticationContextId = "c4" + val authenticationContextRequestClaimJson = "{\"access_token\":{\"acrs\":{\"essential\":true,\"value\":\"$authenticationContextId\"}}}" + + retryOperation { + runBlocking { + // SignUp a new user with username and password + val username = tempEmailApi.generateRandomEmailAddressLocally() + val signUpParams = NativeAuthSignUpParameters(username) + signUpParams.password = getSafePassword().toCharArray() + val signUpResult = application.signUp(signUpParams) + assertResult(signUpResult) + val otp1 = tempEmailApi.retrieveCodeFromInbox(username) + val submitCodeResult = (signUpResult as SignUpResult.CodeRequired).nextState.submitCode(otp1) + assertResult(submitCodeResult) + + // SignIn after signUp with authentication context as claims to trigger MFA + val continuationParameters = NativeAuthSignInContinuationParameters() + continuationParameters.claimsRequest = ClaimsRequest.getClaimsRequestFromJsonString(authenticationContextRequestClaimJson) + val signWithContinuationResult = (submitCodeResult as SignUpResult.Complete).nextState.signIn(continuationParameters) + + // Return redirect with reason registration required was not supplied + assertTrue(signWithContinuationResult is SignInError) + assertTrue((signWithContinuationResult as SignInError).isBrowserRequired()) + assertTrue(signWithContinuationResult.errorMessage!!.contains("registration required was not supplied")) + } + } + } } 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 cf49063e6c..8847577d2a 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,10 +32,14 @@ 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 @@ -55,6 +59,7 @@ class SignInMFATest : NativeAuthPublicClientApplicationAbstractTest() { private val defaultConfigType = ConfigType.SIGN_IN_MFA_SINGLE_AUTH private val defaultChallengeTypes = listOf("password", "oob") + private val defaultCapabilities = listOf("mfa_required", "registration_required") /** * Full flow: @@ -72,7 +77,7 @@ class SignInMFATest : NativeAuthPublicClientApplicationAbstractTest() { @Test fun `test submit invalid challenge, request new challenge, submit correct challenge and complete MFA flow`() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) resources = config.resources retryOperation { @@ -142,7 +147,7 @@ class SignInMFATest : NativeAuthPublicClientApplicationAbstractTest() { @Test fun `test get other auth methods, request challenge on specific auth method and complete MFA flow`() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) resources = config.resources retryOperation { @@ -214,7 +219,7 @@ class SignInMFATest : NativeAuthPublicClientApplicationAbstractTest() { @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) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) resources = config.resources retryOperation { @@ -277,7 +282,7 @@ class SignInMFATest : NativeAuthPublicClientApplicationAbstractTest() { @Test fun `test MFA flow is triggered when authentication context is used as claim`() { config = getConfig(ConfigType.SIGN_IN_MFA_SINGLE_AUTH) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) resources = config.resources val authenticationContextId = "c4" val authenticationContextRequestClaimJson = "{\"access_token\":{\"acrs\":{\"essential\":true,\"value\":\"$authenticationContextId\"}}}" @@ -331,4 +336,43 @@ class SignInMFATest : NativeAuthPublicClientApplicationAbstractTest() { } } } + + /** + * Full flow: Ensure that correct error is returned when an user doesn’t supply the “mfa_required” capability. + * - Initialise client with empty capabilities config + * - SignIn specifying authentication context as claim + * - Receive MFA required error from API + * - Request default challenge and submit correct challenge + * - Return redirect with reason mfa required was not supplied + * + */ + @Ignore("Backward compatibility feature not available in eSTS production") + @Test + fun `test Redirect is triggered when Capabilities incapable`() { + config = getConfig(ConfigType.SIGN_IN_MFA_MULTI_AUTH) + //Initialise client with empty capabilities config + application = setupPCA(config, defaultChallengeTypes, listOf()) + resources = config.resources + val authenticationContextId = "c4" + val authenticationContextRequestClaimJson = "{\"access_token\":{\"acrs\":{\"essential\":true,\"value\":\"$authenticationContextId\"}}}" + + retryOperation { + runBlocking { + val username = config.email + val password = getSafePassword() + val params = NativeAuthSignInParameters(username) + params.password = password.toCharArray() + params.claimsRequest = ClaimsRequest.getClaimsRequestFromJsonString(authenticationContextRequestClaimJson) + + // SignIn specifying authentication context as claim + val result = application.signIn(params) + assertResult(result) + + // Return redirect with reason mfa required was not supplied + assertTrue(result is SignInError) + assertTrue((result as SignInError).isBrowserRequired()) + assertTrue(result.errorMessage!!.contains("mfa required was not supplied")) + } + } + } } 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 0f37859966..bd471cf9b2 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 @@ -46,11 +46,13 @@ class SignUpEmailOTPAttributesTest : NativeAuthPublicClientApplicationAbstractTe private val defaultConfigType = ConfigType.SIGN_UP_OTP_ATTRIBUTES private val defaultChallengeTypes = listOf("password", "oob") + private val defaultCapabilities = listOf("mfa_required", "registration_required") + override fun setup() { super.setup() config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) } /** 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 34df602588..1c52142755 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 @@ -47,6 +47,7 @@ class SignUpEmailOTPTest : NativeAuthPublicClientApplicationAbstractTest() { private val defaultConfigType = ConfigType.SIGN_UP_OTP private val defaultChallengeTypes = listOf("password", "oob") + private val defaultCapabilities = listOf("mfa_required", "registration_required") /** * Sign up with email + OTP. Verify email address using email OTP and sign up. @@ -56,7 +57,7 @@ class SignUpEmailOTPTest : NativeAuthPublicClientApplicationAbstractTest() { @Test fun testSuccess() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) retryOperation { runBlocking { // Running with runBlocking to avoid default 10 second execution timeout. @@ -80,7 +81,7 @@ class SignUpEmailOTPTest : NativeAuthPublicClientApplicationAbstractTest() { @Test fun testResendCode() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) retryOperation { runBlocking { // Running with runBlocking to avoid default 10 second execution timeout. @@ -105,7 +106,7 @@ class SignUpEmailOTPTest : NativeAuthPublicClientApplicationAbstractTest() { @Test fun testErrorUserExistAsOTP() { config = getConfig(ConfigType.SIGN_IN_OTP) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) runBlocking { // Running with runBlocking to avoid default 10 second execution timeout. val user = config.email @@ -122,7 +123,7 @@ class SignUpEmailOTPTest : NativeAuthPublicClientApplicationAbstractTest() { @Test fun testErrorUserExistAsPassword() { config = getConfig(ConfigType.SIGN_IN_PASSWORD) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) runBlocking { // Running with runBlocking to avoid default 10 second execution timeout. val user = config.email @@ -141,7 +142,7 @@ class SignUpEmailOTPTest : NativeAuthPublicClientApplicationAbstractTest() { @Test fun testErrorUserExistAsSocial() { config = getConfig(ConfigType.SIGN_IN_OTP) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) runBlocking { // Running with runBlocking to avoid default 10 second execution timeout. val user = config.email @@ -159,7 +160,7 @@ class SignUpEmailOTPTest : NativeAuthPublicClientApplicationAbstractTest() { @Test fun testErrorInvalidEmailFormat() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) runBlocking { // Running with runBlocking to avoid default 10 second execution timeout. val user = INVALID_EMAIL @@ -178,7 +179,7 @@ class SignUpEmailOTPTest : NativeAuthPublicClientApplicationAbstractTest() { @Test fun testSignInAfterSignUp() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) retryOperation { runBlocking { // Running with runBlocking to avoid default 10 second execution timeout. @@ -203,7 +204,7 @@ class SignUpEmailOTPTest : NativeAuthPublicClientApplicationAbstractTest() { @Test fun testErrorRedirect() { config = getConfig(ConfigType.SIGN_UP_PASSWORD) - application = setupPCA(config, listOf("oob")) + application = setupPCA(config, listOf("oob"), defaultCapabilities) runBlocking { val user = tempEmailApi.generateRandomEmailAddressLocally() @@ -222,7 +223,7 @@ class SignUpEmailOTPTest : NativeAuthPublicClientApplicationAbstractTest() { @Test fun testPasswordRequired() { config = getConfig(ConfigType.SIGN_UP_PASSWORD) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultChallengeTypes) retryOperation { runBlocking { // Running with runBlocking to avoid default 10 second execution timeout. 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 6ee55c30f6..5a6b3c7cd9 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 @@ -47,11 +47,12 @@ class SignUpEmailPasswordAttributesTest : NativeAuthPublicClientApplicationAbstr private val defaultConfigType = ConfigType.SIGN_UP_PASSWORD_ATTRIBUTES private val defaultChallengeTypes = listOf("password", "oob") + private val defaultCapabilities = listOf("mfa_required", "registration_required") override fun setup() { super.setup() config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) } /** 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 da60971540..5115b8c765 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 @@ -47,13 +47,14 @@ class SignUpEmailPasswordTest : NativeAuthPublicClientApplicationAbstractTest() private val defaultConfigType = ConfigType.SIGN_UP_PASSWORD private val defaultChallengeTypes = listOf("password", "oob") + private val defaultCapabilities = listOf("mfa_required", "registration_required") @Ignore("Retrieving OTP code failure.") @Test fun testSignUpErrorSimple() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) retryOperation { runBlocking { @@ -75,7 +76,7 @@ class SignUpEmailPasswordTest : NativeAuthPublicClientApplicationAbstractTest() @Test fun testSuccessOTPLast() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) retryOperation { runBlocking { @@ -100,7 +101,7 @@ class SignUpEmailPasswordTest : NativeAuthPublicClientApplicationAbstractTest() @Test fun testResendEmailOOB() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) retryOperation { runBlocking { @@ -127,7 +128,7 @@ class SignUpEmailPasswordTest : NativeAuthPublicClientApplicationAbstractTest() @Test fun testSuccessOTPFirst() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) retryOperation { runBlocking { // Running with runBlocking to avoid default 10 second execution timeout. @@ -154,7 +155,7 @@ class SignUpEmailPasswordTest : NativeAuthPublicClientApplicationAbstractTest() @Test fun testSuccessOTPResend() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) retryOperation { runBlocking { @@ -183,7 +184,7 @@ class SignUpEmailPasswordTest : NativeAuthPublicClientApplicationAbstractTest() @Test fun testErrorUserExistAsPassword() { config = getConfig(ConfigType.SIGN_IN_PASSWORD) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) runBlocking { // Running with runBlocking to avoid default 10 second execution timeout. val user = config.email @@ -203,7 +204,7 @@ class SignUpEmailPasswordTest : NativeAuthPublicClientApplicationAbstractTest() @Test fun testErrorUserExistAsSocial() { config = getConfig(ConfigType.SIGN_IN_PASSWORD) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) runBlocking { val user = config.email @@ -222,7 +223,7 @@ class SignUpEmailPasswordTest : NativeAuthPublicClientApplicationAbstractTest() @Test fun testErrorInvalidEmailFormat() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) runBlocking { val user = INVALID_EMAIL @@ -242,7 +243,7 @@ class SignUpEmailPasswordTest : NativeAuthPublicClientApplicationAbstractTest() @Test fun testErrorInvalidPasswordFormat() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) runBlocking { // Running with runBlocking to avoid default 10 second execution timeout. val user = tempEmailApi.generateRandomEmailAddressLocally() @@ -262,7 +263,7 @@ class SignUpEmailPasswordTest : NativeAuthPublicClientApplicationAbstractTest() @Test fun testSignInAfterSignUp() { config = getConfig(defaultConfigType) - application = setupPCA(config, defaultChallengeTypes) + application = setupPCA(config, defaultChallengeTypes, defaultCapabilities) retryOperation { runBlocking { // Running with runBlocking to avoid default 10 second execution timeout. diff --git a/msal/src/test/java/com/microsoft/identity/nativeauth/NativeAuthOAuth2ConfigurationTest.kt b/msal/src/test/java/com/microsoft/identity/nativeauth/NativeAuthOAuth2ConfigurationTest.kt index 3380ad93fb..920fef4ac0 100644 --- a/msal/src/test/java/com/microsoft/identity/nativeauth/NativeAuthOAuth2ConfigurationTest.kt +++ b/msal/src/test/java/com/microsoft/identity/nativeauth/NativeAuthOAuth2ConfigurationTest.kt @@ -42,7 +42,8 @@ class NativeAuthOAuth2ConfigurationTest { authorityUrl = authorityUrl, clientId = "1234", challengeType = "oob password redirect", - useMockApiForNativeAuth = false + useMockApiForNativeAuth = false, + capabilities = null ) val signUpEndpoint = configuration.getSignUpStartEndpoint() @@ -59,7 +60,8 @@ class NativeAuthOAuth2ConfigurationTest { authorityUrl = authorityUrl, clientId = "1234", challengeType = "oob password redirect", - useMockApiForNativeAuth = false + useMockApiForNativeAuth = false, + capabilities = null ) val signUpEndpoint = configuration.getSignUpStartEndpoint() diff --git a/msal/src/test/java/com/microsoft/identity/nativeauth/NativeAuthPublicClientApplicationConfigurationTest.kt b/msal/src/test/java/com/microsoft/identity/nativeauth/NativeAuthPublicClientApplicationConfigurationTest.kt index 45f0ef197e..22c4b20694 100644 --- a/msal/src/test/java/com/microsoft/identity/nativeauth/NativeAuthPublicClientApplicationConfigurationTest.kt +++ b/msal/src/test/java/com/microsoft/identity/nativeauth/NativeAuthPublicClientApplicationConfigurationTest.kt @@ -29,6 +29,7 @@ import com.microsoft.identity.common.java.authorities.AzureActiveDirectoryB2CAut import com.microsoft.identity.common.java.nativeauth.authorities.NativeAuthCIAMAuthority import junit.framework.Assert.assertEquals import junit.framework.Assert.fail +import org.junit.Assert import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.spy @@ -289,5 +290,100 @@ class NativeAuthPublicClientApplicationConfigurationTest { whenever(spyConfig.useBroker).thenReturn(false) spyConfig.setChallengeTypes(listOf("oob", "oob", "password", "redirect", "redirect", "redirect")) spyConfig.validateConfiguration() + Assert.assertEquals(spyConfig.getChallengeTypes()?.size,3) // redirect added by default + } + + // Capabilities are optional, so no exception should be thrown + @Test + fun testMissingCapabilities() { + val config = NativeAuthPublicClientApplicationConfiguration() + config.clientId = clientId + config.accountMode = AccountMode.SINGLE + val spyConfig = spy(config) + whenever(spyConfig.authorities).thenReturn(listOf(NativeAuthCIAMAuthority(ciamAuthority, clientId))) + whenever(spyConfig.defaultAuthority).thenReturn(NativeAuthCIAMAuthority(ciamAuthority, clientId)) + whenever(spyConfig.isSharedDevice).thenReturn(false) + whenever(spyConfig.useBroker).thenReturn(false) + whenever(spyConfig.getCapabilities()).thenReturn(emptyList()) + spyConfig.validateConfiguration() + } + + @Test + fun testInvalidCapabilities() { + val config = NativeAuthPublicClientApplicationConfiguration() + config.clientId = clientId + config.accountMode = AccountMode.SINGLE + val spyConfig = spy(config) + whenever(spyConfig.authorities).thenReturn(listOf(NativeAuthCIAMAuthority(ciamAuthority, clientId))) + whenever(spyConfig.defaultAuthority).thenReturn(NativeAuthCIAMAuthority(ciamAuthority, clientId)) + whenever(spyConfig.isSharedDevice).thenReturn(false) + whenever(spyConfig.useBroker).thenReturn(false) + spyConfig.setCapabilities(listOf("lorem")) + + try { + spyConfig.validateConfiguration() + } catch (e: MsalClientException) { + assertEquals(MsalClientException.NATIVE_AUTH_INVALID_CAPABILITY_ERROR_CODE, e.errorCode) + return + } + // An exception should be thrown + fail() + } + + @Test + fun testCaseInsensitiveCapabilities() { + val config = NativeAuthPublicClientApplicationConfiguration() + config.clientId = clientId + config.accountMode = AccountMode.SINGLE + val spyConfig = spy(config) + whenever(spyConfig.authorities).thenReturn(listOf(NativeAuthCIAMAuthority(ciamAuthority, clientId))) + whenever(spyConfig.defaultAuthority).thenReturn(NativeAuthCIAMAuthority(ciamAuthority, clientId)) + whenever(spyConfig.isSharedDevice).thenReturn(false) + whenever(spyConfig.useBroker).thenReturn(false) + spyConfig.setCapabilities(listOf("Mfa_RequIRED")) + spyConfig.validateConfiguration() + } + + @Test + fun testMultipleCorrectCapabilities() { + val config = NativeAuthPublicClientApplicationConfiguration() + config.clientId = clientId + config.accountMode = AccountMode.SINGLE + val spyConfig = spy(config) + whenever(spyConfig.authorities).thenReturn(listOf(NativeAuthCIAMAuthority(ciamAuthority, clientId))) + whenever(spyConfig.defaultAuthority).thenReturn(NativeAuthCIAMAuthority(ciamAuthority, clientId)) + whenever(spyConfig.isSharedDevice).thenReturn(false) + whenever(spyConfig.useBroker).thenReturn(false) + spyConfig.setCapabilities(listOf("mfa_required", "registration_required")) + spyConfig.validateConfiguration() + } + + @Test + fun testSingleCorrectCapability() { + val config = NativeAuthPublicClientApplicationConfiguration() + config.clientId = clientId + config.accountMode = AccountMode.SINGLE + val spyConfig = spy(config) + whenever(spyConfig.authorities).thenReturn(listOf(NativeAuthCIAMAuthority(ciamAuthority, clientId))) + whenever(spyConfig.defaultAuthority).thenReturn(NativeAuthCIAMAuthority(ciamAuthority, clientId)) + whenever(spyConfig.isSharedDevice).thenReturn(false) + whenever(spyConfig.useBroker).thenReturn(false) + spyConfig.setCapabilities(listOf("mfa_required")) + spyConfig.validateConfiguration() + } + + @Test + fun testRepeatedCorrectCapabilities() { + val config = NativeAuthPublicClientApplicationConfiguration() + config.clientId = clientId + config.accountMode = AccountMode.SINGLE + val spyConfig = spy(config) + whenever(spyConfig.authorities).thenReturn(listOf(NativeAuthCIAMAuthority(ciamAuthority, clientId))) + whenever(spyConfig.defaultAuthority).thenReturn(NativeAuthCIAMAuthority(ciamAuthority, clientId)) + whenever(spyConfig.isSharedDevice).thenReturn(false) + whenever(spyConfig.useBroker).thenReturn(false) + spyConfig.setCapabilities(listOf("mfa_required", "mfa_required", "registration_required", "registration_required")) + spyConfig.validateConfiguration() + Assert.assertEquals(spyConfig.getCapabilities()?.size,2) } }