diff --git a/common/src/main/java/com/microsoft/identity/common/nativeauth/internal/controllers/NativeAuthMsalController.kt b/common/src/main/java/com/microsoft/identity/common/nativeauth/internal/controllers/NativeAuthMsalController.kt index 8893e5bed6..a0a92641fb 100644 --- a/common/src/main/java/com/microsoft/identity/common/nativeauth/internal/controllers/NativeAuthMsalController.kt +++ b/common/src/main/java/com/microsoft/identity/common/nativeauth/internal/controllers/NativeAuthMsalController.kt @@ -223,10 +223,11 @@ class NativeAuthMsalController : BaseNativeAuthController() { tokenApiResult = tokenApiResult ) } + // TODO: this will need to change in JIT business logic PR is SignInTokenApiResult.InvalidAuthenticationType, is SignInTokenApiResult.MFARequired, is SignInTokenApiResult.CodeIncorrect, is SignInTokenApiResult.UserNotFound, is SignInTokenApiResult.InvalidCredentials, - is SignInTokenApiResult.UnknownError -> { + is SignInTokenApiResult.UnknownError, is SignInTokenApiResult.JITRequired -> { Logger.warnWithObject( TAG, tokenApiResult.correlationId, @@ -298,10 +299,10 @@ class NativeAuthMsalController : BaseNativeAuthController() { correlationId = tokenApiResult.correlationId ) } - + // TODO: this will need to change in JIT business logic PR is SignInTokenApiResult.UnknownError, is SignInTokenApiResult.InvalidAuthenticationType, is SignInTokenApiResult.MFARequired, is SignInTokenApiResult.InvalidCredentials, - is SignInTokenApiResult.UserNotFound -> { + is SignInTokenApiResult.UserNotFound, is SignInTokenApiResult.JITRequired -> { Logger.warnWithObject( TAG, tokenApiResult.correlationId, @@ -351,6 +352,7 @@ class NativeAuthMsalController : BaseNativeAuthController() { oAuth2Strategy = oAuth2Strategy, parameters = parametersWithScopes ) + // TODO: this will need to change in JIT business logic PR return when (tokenApiResult) { is SignInTokenApiResult.Success -> { saveAndReturnTokens( @@ -370,7 +372,7 @@ class NativeAuthMsalController : BaseNativeAuthController() { } is SignInTokenApiResult.UnknownError, is SignInTokenApiResult.InvalidAuthenticationType, is SignInTokenApiResult.InvalidCredentials, is SignInTokenApiResult.UserNotFound, - is SignInTokenApiResult.MFARequired -> { + is SignInTokenApiResult.MFARequired, is SignInTokenApiResult.JITRequired -> { Logger.warnWithObject( TAG, tokenApiResult.correlationId, @@ -2117,6 +2119,7 @@ class NativeAuthMsalController : BaseNativeAuthController() { oAuth2Strategy: NativeAuthOAuth2Strategy, parametersWithScopes: SignInStartCommandParameters, ): SignInStartCommandResult { + // TODO: this will need to change in JIT business logic PR return when (this) { is SignInTokenApiResult.InvalidCredentials -> { SignInCommandResult.InvalidCredentials( @@ -2145,7 +2148,7 @@ class NativeAuthMsalController : BaseNativeAuthController() { } is SignInTokenApiResult.CodeIncorrect, is SignInTokenApiResult.InvalidAuthenticationType, is SignInTokenApiResult.UserNotFound, - is SignInTokenApiResult.UnknownError -> { + is SignInTokenApiResult.UnknownError, is SignInTokenApiResult.JITRequired -> { Logger.warnWithObject( TAG, this.correlationId, @@ -2167,6 +2170,7 @@ class NativeAuthMsalController : BaseNativeAuthController() { oAuth2Strategy: NativeAuthOAuth2Strategy, parametersWithScopes: SignInSubmitPasswordCommandParameters, ): SignInSubmitPasswordCommandResult { + // TODO: this will need to change in JIT business logic PR return when (this) { is SignInTokenApiResult.InvalidCredentials -> { SignInCommandResult.InvalidCredentials( @@ -2194,7 +2198,8 @@ class NativeAuthMsalController : BaseNativeAuthController() { ) } is SignInTokenApiResult.UserNotFound, is SignInTokenApiResult.CodeIncorrect, - is SignInTokenApiResult.InvalidAuthenticationType, is SignInTokenApiResult.UnknownError -> { + is SignInTokenApiResult.InvalidAuthenticationType, is SignInTokenApiResult.UnknownError, + is SignInTokenApiResult.JITRequired -> { Logger.warnWithObject( TAG, this.correlationId, diff --git a/common/src/test/java/com/microsoft/identity/common/internal/providers/microsoft/nativeauth/integration/ResetPasswordOAuth2StrategyTest.kt b/common/src/test/java/com/microsoft/identity/common/internal/providers/microsoft/nativeauth/integration/ResetPasswordOAuth2StrategyTest.kt index 3984f04b9a..dfffe53962 100644 --- a/common/src/test/java/com/microsoft/identity/common/internal/providers/microsoft/nativeauth/integration/ResetPasswordOAuth2StrategyTest.kt +++ b/common/src/test/java/com/microsoft/identity/common/internal/providers/microsoft/nativeauth/integration/ResetPasswordOAuth2StrategyTest.kt @@ -114,6 +114,9 @@ class ResetPasswordOAuth2StrategyTest { whenever(mockConfig.getResetPasswordContinueEndpoint()).thenReturn(ApiConstants.MockApi.ssprContinueRequestUrl) whenever(mockConfig.getResetPasswordSubmitEndpoint()).thenReturn(ApiConstants.MockApi.ssprSubmitRequestUrl) whenever(mockConfig.getResetPasswordPollCompletionEndpoint()).thenReturn(ApiConstants.MockApi.ssprPollCompletionRequestUrl) + whenever(mockConfig.getJITIntrospectEndpoint()).thenReturn(ApiConstants.MockApi.jitIntrospectRequestUrl) + whenever(mockConfig.getJITChallengeEndpoint()).thenReturn(ApiConstants.MockApi.jitChallengeRequestUrl) + whenever(mockConfig.getJITContinueEndpoint()).thenReturn(ApiConstants.MockApi.jitContinueRequestUrl) whenever(mockConfig.challengeType).thenReturn(CHALLENGE_TYPE) nativeAuthOAuth2Strategy = NativeAuthOAuth2Strategy( diff --git a/common/src/test/java/com/microsoft/identity/common/internal/providers/microsoft/nativeauth/integration/SignInOAuthStrategyTest.kt b/common/src/test/java/com/microsoft/identity/common/internal/providers/microsoft/nativeauth/integration/SignInOAuthStrategyTest.kt index 5fa103061e..cbbba842a8 100644 --- a/common/src/test/java/com/microsoft/identity/common/internal/providers/microsoft/nativeauth/integration/SignInOAuthStrategyTest.kt +++ b/common/src/test/java/com/microsoft/identity/common/internal/providers/microsoft/nativeauth/integration/SignInOAuthStrategyTest.kt @@ -102,6 +102,9 @@ class SignInOAuthStrategyTest { whenever(mockConfig.getResetPasswordContinueEndpoint()).thenReturn(ApiConstants.MockApi.ssprContinueRequestUrl) whenever(mockConfig.getResetPasswordSubmitEndpoint()).thenReturn(ApiConstants.MockApi.ssprSubmitRequestUrl) whenever(mockConfig.getResetPasswordPollCompletionEndpoint()).thenReturn(ApiConstants.MockApi.ssprPollCompletionRequestUrl) + whenever(mockConfig.getJITIntrospectEndpoint()).thenReturn(ApiConstants.MockApi.jitIntrospectRequestUrl) + whenever(mockConfig.getJITChallengeEndpoint()).thenReturn(ApiConstants.MockApi.jitChallengeRequestUrl) + whenever(mockConfig.getJITContinueEndpoint()).thenReturn(ApiConstants.MockApi.jitContinueRequestUrl) whenever(mockConfig.challengeType).thenReturn(CHALLENGE_TYPE) nativeAuthOAuth2Strategy = NativeAuthOAuth2Strategy( diff --git a/common/src/test/java/com/microsoft/identity/common/internal/providers/microsoft/nativeauth/integration/SignUpOAuth2StrategyTest.kt b/common/src/test/java/com/microsoft/identity/common/internal/providers/microsoft/nativeauth/integration/SignUpOAuth2StrategyTest.kt index c4afc313b2..b3a659e38a 100644 --- a/common/src/test/java/com/microsoft/identity/common/internal/providers/microsoft/nativeauth/integration/SignUpOAuth2StrategyTest.kt +++ b/common/src/test/java/com/microsoft/identity/common/internal/providers/microsoft/nativeauth/integration/SignUpOAuth2StrategyTest.kt @@ -101,6 +101,9 @@ class SignUpOAuth2StrategyTest { whenever(mockConfig.getResetPasswordContinueEndpoint()).thenReturn(ApiConstants.MockApi.ssprContinueRequestUrl) whenever(mockConfig.getResetPasswordSubmitEndpoint()).thenReturn(ApiConstants.MockApi.ssprSubmitRequestUrl) whenever(mockConfig.getResetPasswordPollCompletionEndpoint()).thenReturn(ApiConstants.MockApi.ssprPollCompletionRequestUrl) + whenever(mockConfig.getJITIntrospectEndpoint()).thenReturn(ApiConstants.MockApi.jitIntrospectRequestUrl) + whenever(mockConfig.getJITChallengeEndpoint()).thenReturn(ApiConstants.MockApi.jitChallengeRequestUrl) + whenever(mockConfig.getJITContinueEndpoint()).thenReturn(ApiConstants.MockApi.jitContinueRequestUrl) whenever(mockConfig.challengeType).thenReturn(CHALLENGE_TYPE) nativeAuthOAuth2Strategy = NativeAuthOAuth2Strategy( diff --git a/common/src/test/java/com/microsoft/identity/common/internal/providers/microsoft/nativeauth/integration/scenario/ResetPasswordScenarioTest.kt b/common/src/test/java/com/microsoft/identity/common/internal/providers/microsoft/nativeauth/integration/scenario/ResetPasswordScenarioTest.kt index cff3fb6a39..d76646b99e 100644 --- a/common/src/test/java/com/microsoft/identity/common/internal/providers/microsoft/nativeauth/integration/scenario/ResetPasswordScenarioTest.kt +++ b/common/src/test/java/com/microsoft/identity/common/internal/providers/microsoft/nativeauth/integration/scenario/ResetPasswordScenarioTest.kt @@ -84,6 +84,9 @@ class ResetPasswordScenarioTest { whenever(mockConfig.getResetPasswordContinueEndpoint()).thenReturn(ApiConstants.MockApi.ssprContinueRequestUrl) whenever(mockConfig.getResetPasswordSubmitEndpoint()).thenReturn(ApiConstants.MockApi.ssprSubmitRequestUrl) whenever(mockConfig.getResetPasswordPollCompletionEndpoint()).thenReturn(ApiConstants.MockApi.ssprPollCompletionRequestUrl) + whenever(mockConfig.getJITIntrospectEndpoint()).thenReturn(ApiConstants.MockApi.jitIntrospectRequestUrl) + whenever(mockConfig.getJITChallengeEndpoint()).thenReturn(ApiConstants.MockApi.jitChallengeRequestUrl) + whenever(mockConfig.getJITContinueEndpoint()).thenReturn(ApiConstants.MockApi.jitContinueRequestUrl) whenever(mockConfig.challengeType).thenReturn(challengeType) nativeAuthOAuth2Strategy = NativeAuthOAuth2Strategy( diff --git a/common/src/test/java/com/microsoft/identity/common/internal/providers/microsoft/nativeauth/integration/scenario/SignUpScenarioTest.kt b/common/src/test/java/com/microsoft/identity/common/internal/providers/microsoft/nativeauth/integration/scenario/SignUpScenarioTest.kt index 62edfbf2dc..3038135b96 100644 --- a/common/src/test/java/com/microsoft/identity/common/internal/providers/microsoft/nativeauth/integration/scenario/SignUpScenarioTest.kt +++ b/common/src/test/java/com/microsoft/identity/common/internal/providers/microsoft/nativeauth/integration/scenario/SignUpScenarioTest.kt @@ -78,6 +78,9 @@ class SignUpScenarioTest { whenever(mockConfig.getResetPasswordContinueEndpoint()).thenReturn(ApiConstants.MockApi.ssprContinueRequestUrl) whenever(mockConfig.getResetPasswordSubmitEndpoint()).thenReturn(ApiConstants.MockApi.ssprSubmitRequestUrl) whenever(mockConfig.getResetPasswordPollCompletionEndpoint()).thenReturn(ApiConstants.MockApi.ssprPollCompletionRequestUrl) + whenever(mockConfig.getJITIntrospectEndpoint()).thenReturn(ApiConstants.MockApi.jitIntrospectRequestUrl) + whenever(mockConfig.getJITChallengeEndpoint()).thenReturn(ApiConstants.MockApi.jitChallengeRequestUrl) + whenever(mockConfig.getJITContinueEndpoint()).thenReturn(ApiConstants.MockApi.jitContinueRequestUrl) whenever(mockConfig.challengeType).thenReturn(CHALLENGE_TYPE) nativeAuthOAuth2Strategy = NativeAuthOAuth2Strategy( diff --git a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/NativeAuthOAuth2Configuration.kt b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/NativeAuthOAuth2Configuration.kt index eec5806899..04ba6dd53a 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/NativeAuthOAuth2Configuration.kt +++ b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/NativeAuthOAuth2Configuration.kt @@ -63,6 +63,9 @@ class NativeAuthOAuth2Configuration( private const val SIGN_IN_INTROSPECT_ENDPOINT_SUFFIX = "/oauth2/v2.0/introspect" private const val SIGN_IN_CHALLENGE_ENDPOINT_SUFFIX = "/oauth2/v2.0/challenge" private const val SIGN_IN_TOKEN_ENDPOINT_SUFFIX = "/oauth2/v2.0/token" + private const val JIT_INTROSPECT_ENDPOINT_SUFFIX = "/register/v1.0/introspect" + private const val JIT_CHALLENGE_ENDPOINT_SUFFIX = "/register/v1.0/challenge" + private const val JIT_CONTINUE_ENDPOINT_SUFFIX = "/register/v1.0/continue" } override fun getAuthorityUrl(): URL { @@ -217,6 +220,42 @@ class NativeAuthOAuth2Configuration( ) } + /** + * Get the endpoint to be used for making a register/v1.0/introspect request. + * + * @return URL the endpoint + */ + fun getJITIntrospectEndpoint(): URL { + return getEndpointUrlFromRootAndTenantAndSuffix( + root = getAuthorityUrl(), + endpointSuffix = JIT_INTROSPECT_ENDPOINT_SUFFIX + ) + } + + /** + * Get the endpoint to be used for making a register/v1.0/challenge request. + * + * @return URL the endpoint + */ + fun getJITChallengeEndpoint(): URL { + return getEndpointUrlFromRootAndTenantAndSuffix( + root = getAuthorityUrl(), + endpointSuffix = JIT_CHALLENGE_ENDPOINT_SUFFIX + ) + } + + /** + * Get the endpoint to be used for making a register/v1.0/continue request. + * + * @return URL the endpoint + */ + fun getJITContinueEndpoint(): URL { + return getEndpointUrlFromRootAndTenantAndSuffix( + root = getAuthorityUrl(), + endpointSuffix = JIT_CONTINUE_ENDPOINT_SUFFIX + ) + } + private fun getEndpointUrlFromRootAndTenantAndSuffix(root: URL, endpointSuffix: String): URL { return try { if (BuildValues.getDC().isNotEmpty()) { diff --git a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/NativeAuthRequestProvider.kt b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/NativeAuthRequestProvider.kt index a0e87dc229..d7051bc2c8 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/NativeAuthRequestProvider.kt +++ b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/NativeAuthRequestProvider.kt @@ -36,6 +36,9 @@ import com.microsoft.identity.common.java.nativeauth.commands.parameters.SignUpS import com.microsoft.identity.common.java.nativeauth.commands.parameters.SignUpSubmitPasswordCommandParameters import com.microsoft.identity.common.java.nativeauth.commands.parameters.SignUpSubmitUserAttributesCommandParameters import com.microsoft.identity.common.java.nativeauth.commands.parameters.SignInStartCommandParameters +import com.microsoft.identity.common.java.nativeauth.providers.requests.jit.JITChallengeRequest +import com.microsoft.identity.common.java.nativeauth.providers.requests.jit.JITContinueRequest +import com.microsoft.identity.common.java.nativeauth.providers.requests.jit.JITIntrospectRequest import com.microsoft.identity.common.java.net.HttpConstants import com.microsoft.identity.common.java.nativeauth.providers.requests.resetpassword.ResetPasswordChallengeRequest import com.microsoft.identity.common.java.nativeauth.providers.requests.resetpassword.ResetPasswordContinueRequest @@ -71,6 +74,9 @@ class NativeAuthRequestProvider(private val config: NativeAuthOAuth2Configuratio private val resetPasswordContinueEndpoint = config.getResetPasswordContinueEndpoint().toString() private val resetPasswordSubmitEndpoint = config.getResetPasswordSubmitEndpoint().toString() private val resetPasswordPollCompletionEndpoint = config.getResetPasswordPollCompletionEndpoint().toString() + private val jitIntrospectEndpoint = config.getJITIntrospectEndpoint().toString() + private val jitChallengeEndpoint = config.getJITChallengeEndpoint().toString() + private val jitContinueEndpoint = config.getJITContinueEndpoint().toString() //region /oauth/v2.0/initiate /** @@ -348,6 +354,58 @@ class NativeAuthRequestProvider(private val config: NativeAuthOAuth2Configuratio } //endregion + //region /register/introspect + internal fun createJITIntrospectRequest( + continuationToken: String, + correlationId: String + ): JITIntrospectRequest { + return JITIntrospectRequest.create( + continuationToken = continuationToken, + clientId = config.clientId, + requestUrl = jitIntrospectEndpoint, + headers = getRequestHeaders(correlationId) + ) + } + //endregion + + //region /register/challenge + internal fun createJITChallengeRequest( + continuationToken: String, + challengeType: String, + challengeTarget: String, + challengeChannel: String, + correlationId: String + ): JITChallengeRequest { + return JITChallengeRequest.create( + continuationToken = continuationToken, + challengeType = challengeType, + challengeTarget = challengeTarget, + challengeChannel = challengeChannel, + clientId = config.clientId, + requestUrl = jitChallengeEndpoint, + headers = getRequestHeaders(correlationId) + ) + } + //endregion + + //region /register/continue + internal fun createJITContinueRequest( + continuationToken: String, + grantType: String, + code: String, + correlationId: String + ): JITContinueRequest { + return JITContinueRequest.create( + continuationToken = continuationToken, + grantType = grantType, + oob = code, + clientId = config.clientId, + requestUrl = jitContinueEndpoint, + headers = getRequestHeaders(correlationId) + ) + } + //endregion + //region helpers private fun getRequestHeaders(correlationId: String): Map { val headers: MutableMap = TreeMap() diff --git a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/NativeAuthResponseHandler.kt b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/NativeAuthResponseHandler.kt index a936b4e27e..12d17a50e9 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/NativeAuthResponseHandler.kt +++ b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/NativeAuthResponseHandler.kt @@ -25,6 +25,9 @@ package com.microsoft.identity.common.java.nativeauth.providers import com.microsoft.identity.common.java.AuthenticationConstants import com.microsoft.identity.common.java.exception.ClientException import com.microsoft.identity.common.java.logging.LogSession +import com.microsoft.identity.common.java.nativeauth.providers.responses.jit.JITChallengeApiResponse +import com.microsoft.identity.common.java.nativeauth.providers.responses.jit.JITContinueApiResponse +import com.microsoft.identity.common.java.nativeauth.providers.responses.jit.JITIntrospectApiResponse import com.microsoft.identity.common.java.net.HttpResponse import com.microsoft.identity.common.java.providers.microsoft.microsoftsts.MicrosoftStsTokenResponse import com.microsoft.identity.common.java.nativeauth.providers.responses.resetpassword.ResetPasswordChallengeApiResponse @@ -35,7 +38,6 @@ import com.microsoft.identity.common.java.nativeauth.providers.responses.resetpa import com.microsoft.identity.common.java.nativeauth.providers.responses.signin.SignInChallengeApiResponse import com.microsoft.identity.common.java.nativeauth.providers.responses.signin.SignInInitiateApiResponse import com.microsoft.identity.common.java.nativeauth.providers.responses.signin.SignInIntrospectApiResponse -import com.microsoft.identity.common.java.nativeauth.providers.responses.signin.SignInIntrospectApiResult import com.microsoft.identity.common.java.nativeauth.providers.responses.signin.SignInTokenApiResponse import com.microsoft.identity.common.java.nativeauth.providers.responses.signin.SignInTokenApiResult import com.microsoft.identity.common.java.nativeauth.providers.responses.signup.SignUpChallengeApiResponse @@ -75,14 +77,7 @@ class NativeAuthResponseHandler { methodName = "${TAG}.getSignUpStartResultFromHttpResponse" ) - // If the API doesn't return a correlation ID header value, use the correlation ID of the original API request - val correlationId: String = response.getHeaderValue(AuthenticationConstants.AAD.CLIENT_REQUEST_ID, 0).let {responseCorrelationId -> - if (responseCorrelationId.isNullOrBlank()) { - requestCorrelationId - } else { - responseCorrelationId - } - } + val correlationId = retrieveCorrelationId(response, requestCorrelationId) val result = if (response.body.isNullOrBlank()) { SignUpStartApiResponse( @@ -130,14 +125,7 @@ class NativeAuthResponseHandler { methodName ="${TAG}.getSignUpChallengeResultFromHttpResponse" ) - // If the API doesn't return a correlation ID header value, use the correlation ID of the original API request - val correlationId: String = response.getHeaderValue(AuthenticationConstants.AAD.CLIENT_REQUEST_ID, 0).let {responseCorrelationId -> - if (responseCorrelationId.isNullOrBlank()) { - requestCorrelationId - } else { - responseCorrelationId - } - } + val correlationId = retrieveCorrelationId(response, requestCorrelationId) val result = if (response.body.isNullOrBlank()) { SignUpChallengeApiResponse( @@ -185,14 +173,7 @@ class NativeAuthResponseHandler { methodName ="${TAG}.getSignUpContinueResultFromHttpResponse" ) - // If the API doesn't return a correlation ID header value, use the correlation ID of the original API request - val correlationId: String = response.getHeaderValue(AuthenticationConstants.AAD.CLIENT_REQUEST_ID, 0).let {responseCorrelationId -> - if (responseCorrelationId.isNullOrBlank()) { - requestCorrelationId - } else { - responseCorrelationId - } - } + val correlationId = retrieveCorrelationId(response, requestCorrelationId) val result = if (response.body.isNullOrBlank()) { SignUpContinueApiResponse( @@ -238,14 +219,7 @@ class NativeAuthResponseHandler { methodName = "${TAG}.getSignInInitiateResultFromHttpResponse" ) - // If the API doesn't return a correlation ID header value, use the correlation ID of the original API request - val correlationId: String = response.getHeaderValue(AuthenticationConstants.AAD.CLIENT_REQUEST_ID, 0).let {responseCorrelationId -> - if (responseCorrelationId.isNullOrBlank()) { - requestCorrelationId - } else { - responseCorrelationId - } - } + val correlationId = retrieveCorrelationId(response, requestCorrelationId) val result = if (response.body.isNullOrBlank()) { SignInInitiateApiResponse( @@ -289,14 +263,7 @@ class NativeAuthResponseHandler { methodName = "${TAG}.getSignInChallengeResultFromHttpResponse" ) - // If the API doesn't return a correlation ID header value, use the correlation ID of the original API request - val correlationId: String = response.getHeaderValue(AuthenticationConstants.AAD.CLIENT_REQUEST_ID, 0).let {responseCorrelationId -> - if (responseCorrelationId.isNullOrBlank()) { - requestCorrelationId - } else { - responseCorrelationId - } - } + val correlationId = retrieveCorrelationId(response, requestCorrelationId) val result = if (response.body.isNullOrBlank()) { SignInChallengeApiResponse( @@ -347,14 +314,7 @@ class NativeAuthResponseHandler { methodName = "${TAG}.getSignInIntrospectResultFromHttpResponse" ) - // If the API doesn't return a correlation ID header value, use the correlation ID of the original API request - val correlationId: String = response.getHeaderValue(AuthenticationConstants.AAD.CLIENT_REQUEST_ID, 0).let {responseCorrelationId -> - if (responseCorrelationId.isNullOrBlank()) { - requestCorrelationId - } else { - responseCorrelationId - } - } + val correlationId = retrieveCorrelationId(response, requestCorrelationId) val result = if (response.body.isNullOrBlank()) { SignInIntrospectApiResponse( @@ -398,14 +358,7 @@ class NativeAuthResponseHandler { methodName = "${TAG}.getSignInTokenApiResultFromHttpResponse" ) - // If the API doesn't return a correlation ID header value, use the correlation ID of the original API request - val correlationId: String = response.getHeaderValue(AuthenticationConstants.AAD.CLIENT_REQUEST_ID, 0).let {responseCorrelationId -> - if (responseCorrelationId.isNullOrBlank()) { - requestCorrelationId - } else { - responseCorrelationId - } - } + val correlationId = retrieveCorrelationId(response, requestCorrelationId) // Use native-auth specific class in case of API error response, // or standard MicrosoftStsTokenResponse in case of success response @@ -462,14 +415,7 @@ class NativeAuthResponseHandler { methodName = "${TAG}.getResetPasswordStartApiResponseFromHttpResponse" ) - // If the API doesn't return a correlation ID header value, use the correlation ID of the original API request - val correlationId: String = response.getHeaderValue(AuthenticationConstants.AAD.CLIENT_REQUEST_ID, 0).let {responseCorrelationId -> - if (responseCorrelationId.isNullOrBlank()) { - requestCorrelationId - } else { - responseCorrelationId - } - } + val correlationId = retrieveCorrelationId(response, requestCorrelationId) val result = if (response.body.isNullOrBlank()) { ResetPasswordStartApiResponse( @@ -494,6 +440,7 @@ class NativeAuthResponseHandler { return result } + //endregion //region /resetpassword/challenge /** @@ -512,14 +459,7 @@ class NativeAuthResponseHandler { methodName = "${TAG}.getResetPasswordChallengeApiResponseFromHttpResponse" ) - // If the API doesn't return a correlation ID header value, use the correlation ID of the original API request - val correlationId: String = response.getHeaderValue(AuthenticationConstants.AAD.CLIENT_REQUEST_ID, 0).let {responseCorrelationId -> - if (responseCorrelationId.isNullOrBlank()) { - requestCorrelationId - } else { - responseCorrelationId - } - } + val correlationId = retrieveCorrelationId(response, requestCorrelationId) val result = if (response.body.isNullOrBlank()) { ResetPasswordChallengeApiResponse( @@ -549,6 +489,7 @@ class NativeAuthResponseHandler { return result } + //endregion //region /resetpassword/continue /** @@ -567,14 +508,7 @@ class NativeAuthResponseHandler { methodName = "${TAG}.getResetPasswordContinueApiResponseFromHttpResponse" ) - // If the API doesn't return a correlation ID header value, use the correlation ID of the original API request - val correlationId: String = response.getHeaderValue(AuthenticationConstants.AAD.CLIENT_REQUEST_ID, 0).let {responseCorrelationId -> - if (responseCorrelationId.isNullOrBlank()) { - requestCorrelationId - } else { - responseCorrelationId - } - } + val correlationId = retrieveCorrelationId(response, requestCorrelationId) val result = if (response.body.isNullOrBlank()) { ResetPasswordContinueApiResponse( @@ -600,6 +534,7 @@ class NativeAuthResponseHandler { ApiResultUtil.logResponse(TAG, result) return result } + //endregion //region /resetpassword/submit /** @@ -618,14 +553,7 @@ class NativeAuthResponseHandler { methodName = "${TAG}.getResetPasswordSubmitApiResponseFromHttpResponse" ) - // If the API doesn't return a correlation ID header value, use the correlation ID of the original API request - val correlationId: String = response.getHeaderValue(AuthenticationConstants.AAD.CLIENT_REQUEST_ID, 0).let {responseCorrelationId -> - if (responseCorrelationId.isNullOrBlank()) { - requestCorrelationId - } else { - responseCorrelationId - } - } + val correlationId = retrieveCorrelationId(response, requestCorrelationId) val result = if (response.body.isNullOrBlank()) { ResetPasswordSubmitApiResponse( @@ -651,6 +579,7 @@ class NativeAuthResponseHandler { return result } + //endregion //region /resetpassword/poll_completion /** @@ -669,14 +598,7 @@ class NativeAuthResponseHandler { methodName = "${TAG}.getResetPasswordPollCompletionApiResponseFromHttpResponse" ) - // If the API doesn't return a correlation ID header value, use the correlation ID of the original API request - val correlationId: String = response.getHeaderValue(AuthenticationConstants.AAD.CLIENT_REQUEST_ID, 0).let {responseCorrelationId -> - if (responseCorrelationId.isNullOrBlank()) { - requestCorrelationId - } else { - responseCorrelationId - } - } + val correlationId = retrieveCorrelationId(response, requestCorrelationId) val result = if (response.body.isNullOrBlank()) { ResetPasswordPollCompletionApiResponse( @@ -703,4 +625,161 @@ class NativeAuthResponseHandler { return result } + //endregion + + //region /register/introspect + /** + * Converts the HTTP response for /register/introspect API to [JITIntrospectResponse] object + * @param response : HTTP response received from the API + * @return JITIntrospectApiResponse object + */ + @Throws(ClientException::class) + fun getJITIntrospectApiResponseFromHttpResponse( + requestCorrelationId: String, + response: HttpResponse + ): JITIntrospectApiResponse { + LogSession.logMethodCall( + tag = TAG, + correlationId = null, + methodName = "${TAG}.getJITIntrospectApiResponseFromHttpResponse" + ) + + val correlationId = retrieveCorrelationId(response, requestCorrelationId) + + val result = if (response.body.isNullOrBlank()) { + JITIntrospectApiResponse( + statusCode = response.statusCode, + error = EMPTY_RESPONSE_ERROR, + errorDescription = EMPTY_RESPONSE_ERROR_ERROR_DESCRIPTION, + errorUri = null, + continuationToken = null, + correlationId = correlationId, + challengeType = null, + methods = null, + errorCodes = null + ) + } else { + ObjectMapper.deserializeJsonStringToObject( + response.body, + JITIntrospectApiResponse::class.java + ) + } + result.statusCode = response.statusCode + result.correlationId = correlationId + + ApiResultUtil.logResponse(TAG, result) + + return result + } + //endregion + + //region /register/challenge + /** + * Converts the HTTP response for /register/challenge API to [JITChallengeResponse] object + * @param response : HTTP response received from the API + * @return JITChallengeApiResponse object + */ + @Throws(ClientException::class) + fun getJITChallengeResponseFromHttpResponse( + requestCorrelationId: String, + response: HttpResponse + ): JITChallengeApiResponse { + LogSession.logMethodCall( + tag = TAG, + correlationId = null, + methodName = "${TAG}.getJITChallengeApiResponseFromHttpResponse" + ) + + val correlationId = retrieveCorrelationId(response, requestCorrelationId) + + val result = if (response.body.isNullOrBlank()) { + JITChallengeApiResponse( + statusCode = response.statusCode, + error = EMPTY_RESPONSE_ERROR, + errorDescription = EMPTY_RESPONSE_ERROR_ERROR_DESCRIPTION, + errorUri = null, + continuationToken = null, + challengeType = null, + bindingMethod = null, + challengeTarget = null, + challengeChannel = null, + codeLength = null, + interval = null, + errorCodes = null, + correlationId = correlationId + ) + } else { + ObjectMapper.deserializeJsonStringToObject( + response.body, + JITChallengeApiResponse::class.java + ) + } + result.statusCode = response.statusCode + result.correlationId = correlationId + + ApiResultUtil.logResponse(TAG, result) + + return result + } + //endregion + + //region /register/continue + /** + * Converts the HTTP response for /register/continue API to [JITContinueApiResponse] object + * @param response : HTTP response received from the API + * @return JITContinueApiResponse object + */ + @Throws(ClientException::class) + fun getJITContinueApiResponseFromHttpResponse( + requestCorrelationId: String, + response: HttpResponse + ): JITContinueApiResponse { + LogSession.logMethodCall( + tag = TAG, + correlationId = null, + methodName = "${TAG}.getJITContinueApiResponseFromHttpResponse" + ) + + val correlationId = retrieveCorrelationId(response, requestCorrelationId) + + val result = if (response.body.isNullOrBlank()) { + JITContinueApiResponse( + statusCode = response.statusCode, + error = EMPTY_RESPONSE_ERROR, + errorDescription = EMPTY_RESPONSE_ERROR_ERROR_DESCRIPTION, + continuationToken = null, + subError = null, + errorCodes = null, + correlationId = correlationId + ) + } else { + ObjectMapper.deserializeJsonStringToObject( + response.body, + JITContinueApiResponse::class.java + ) + } + result.statusCode = response.statusCode + result.correlationId = correlationId + + ApiResultUtil.logResponse(TAG, result) + + return result + } + //endregion + + /** + * If the API doesn't return a correlation ID header value, use the correlation ID of the original API request + */ + private fun retrieveCorrelationId( + response: HttpResponse, + requestCorrelationId: String + ): String { + return response.getHeaderValue(AuthenticationConstants.AAD.CLIENT_REQUEST_ID, 0).let {responseCorrelationId -> + if (responseCorrelationId.isNullOrBlank()) { + requestCorrelationId + } else { + responseCorrelationId + } + } + } } diff --git a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/requests/jit/JITChallengeRequest.kt b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/requests/jit/JITChallengeRequest.kt new file mode 100644 index 0000000000..ef6cfcc669 --- /dev/null +++ b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/requests/jit/JITChallengeRequest.kt @@ -0,0 +1,101 @@ +// 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.common.java.nativeauth.providers.requests.jit + +import com.google.gson.annotations.SerializedName +import com.microsoft.identity.common.java.nativeauth.providers.requests.NativeAuthRequest +import com.microsoft.identity.common.java.util.ArgUtils +import java.net.URL + +/** + * Represents a request to the register/challenge endpoint, and provides a create() function to instantiate the request using the provided parameters. + */ +data class JITChallengeRequest private constructor( + override var requestUrl: URL, + override var headers: Map, + override val parameters: NativeAuthJITChallengeRequestParameters +) : NativeAuthRequest() { + + companion object { + /** + * Returns a request object using the provided parameters. + * The request URL and headers passed will be set directly. + * The clientId, continuation token, and challengeType will be mapped to the NativeAuthJITChallengeRequestParameters object. + * + * Parameters that are null or empty will throw a ClientException. + * @see com.microsoft.identity.common.java.exception.ClientException + */ + fun create( + clientId: String, + continuationToken: String, + challengeType: String, + challengeTarget: String, + challengeChannel: String, + requestUrl: String, + headers: Map + ): JITChallengeRequest { + // Check for empty Strings and empty Maps + ArgUtils.validateNonNullArg(clientId, "clientId") + ArgUtils.validateNonNullArg(continuationToken, "continuationToken") + ArgUtils.validateNonNullArg(challengeType, "challengeType") + ArgUtils.validateNonNullArg(challengeTarget, "challengeTarget") + ArgUtils.validateNonNullArg(challengeChannel, "challengeChannel") + + return JITChallengeRequest( + requestUrl = URL(requestUrl), + headers = headers, + parameters = NativeAuthJITChallengeRequestParameters( + clientId = clientId, + continuationToken = continuationToken, + challengeType = challengeType, + challengeTarget = challengeTarget, + challengeChannel = challengeChannel + ) + ) + } + } + + override fun toUnsanitizedString(): String = "JITChallengeRequest(requestUrl=$requestUrl, headers=$headers, parameters=$parameters)" + + override fun toString(): String = "JITChallengeRequest()" + + /** + * NativeAuthJITChallengeRequestParameters represents the request parameters sent as part of + * /register/challenge API call + */ + data class NativeAuthJITChallengeRequestParameters( + @SerializedName("client_id") override val clientId: String, + @SerializedName("continuation_token") val continuationToken: String, + @SerializedName("challenge_type") val challengeType: String, + @SerializedName("challenge_target") val challengeTarget: String, + @SerializedName("challenge_channel") val challengeChannel: String + ) : NativeAuthRequestParameters() { + override fun toUnsanitizedString(): String = "NativeAuthJITChallengeRequestParameters(clientId=$clientId, " + + "challengeType=$challengeType, " + + "challengeTarget=$challengeTarget, " + + "challengeChannel=$challengeChannel)" + + override fun toString(): String = "NativeAuthJITChallengeRequestParameters(clientId=$clientId, " + + "challengeType=$challengeType)" + } +} \ No newline at end of file diff --git a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/requests/jit/JITContinueRequest.kt b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/requests/jit/JITContinueRequest.kt new file mode 100644 index 0000000000..73296693b2 --- /dev/null +++ b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/requests/jit/JITContinueRequest.kt @@ -0,0 +1,93 @@ +// 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.common.java.nativeauth.providers.requests.jit + +import com.google.gson.annotations.SerializedName +import com.microsoft.identity.common.java.nativeauth.providers.requests.NativeAuthRequest +import com.microsoft.identity.common.java.util.ArgUtils +import java.net.URL + +/** + * Represents a request to the register/continue endpoint, and provides a create() function to instantiate the request using the provided parameters. + */ +data class JITContinueRequest private constructor( + override var requestUrl: URL, + override var headers: Map, + override val parameters: NativeAuthJITContinueRequestParameters +) : NativeAuthRequest() { + + companion object { + /** + * Returns a request object using the provided parameters. + * The request URL and headers passed will be set directly. + * The clientId, continuation token will be mapped to the NativeAuthJITContinueRequestParameters object. + * + * Parameters that are null or empty will throw a ClientException. + * @see com.microsoft.identity.common.java.exception.ClientException + */ + fun create( + clientId: String, + continuationToken: String, + grantType: String, + oob: String, + requestUrl: String, + headers: Map + ): JITContinueRequest { + // Check for empty Strings and empty Maps + ArgUtils.validateNonNullArg(clientId, "clientId") + ArgUtils.validateNonNullArg(continuationToken, "continuationToken") + ArgUtils.validateNonNullArg(grantType, "grantType") + ArgUtils.validateNonNullArg(oob, "oob") + + return JITContinueRequest( + requestUrl = URL(requestUrl), + headers = headers, + parameters = NativeAuthJITContinueRequestParameters( + clientId = clientId, + continuationToken = continuationToken, + grantType = grantType, + oob = oob + ) + ) + } + } + + override fun toUnsanitizedString(): String = "JITContinueRequest(requestUrl=$requestUrl, headers=$headers, parameters=$parameters)" + + override fun toString(): String = "JITContinueRequest()" + + /** + * NativeAuthJITContinueRequestParameters represents the request parameters sent as part of + * /register/continue API call + */ + data class NativeAuthJITContinueRequestParameters( + @SerializedName("client_id") override val clientId: String, + @SerializedName("continuation_token") val continuationToken: String, + @SerializedName("grant_type") val grantType: String, + @SerializedName("oob") val oob: String + ) : NativeAuthRequestParameters() { + override fun toUnsanitizedString(): String = "NativeAuthJITContinueRequestParameters(clientId=$clientId, grantType=$grantType, oob=$oob)" + + override fun toString(): String = "NativeAuthJITContinueRequestParameters(clientId=$clientId, grantType=$grantType)" + } +} \ No newline at end of file diff --git a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/requests/jit/JITIntrospectRequest.kt b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/requests/jit/JITIntrospectRequest.kt new file mode 100644 index 0000000000..0b7160e2d1 --- /dev/null +++ b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/requests/jit/JITIntrospectRequest.kt @@ -0,0 +1,85 @@ +// 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.common.java.nativeauth.providers.requests.jit + +import com.google.gson.annotations.SerializedName +import com.microsoft.identity.common.java.nativeauth.providers.requests.NativeAuthRequest +import com.microsoft.identity.common.java.util.ArgUtils +import java.net.URL + +/** + * Represents a request to the register/introspect endpoint, and provides a create() function to instantiate the request using the provided parameters. + */ +data class JITIntrospectRequest private constructor( + override var requestUrl: URL, + override var headers: Map, + override val parameters: NativeAuthJITIntrospectRequestParameters +) : NativeAuthRequest() { + + companion object { + /** + * Returns a request object using the provided parameters. + * The request URL and headers passed will be set directly. + * The clientId, continuation token, and challengeType will be mapped to the NativeAuthJITIntrospectRequestParameters object. + * + * Parameters that are null or empty will throw a ClientException. + * @see com.microsoft.identity.common.java.exception.ClientException + */ + fun create( + clientId: String, + continuationToken: String, + requestUrl: String, + headers: Map + ): JITIntrospectRequest { + // Check for empty Strings and empty Maps + ArgUtils.validateNonNullArg(clientId, "clientId") + ArgUtils.validateNonNullArg(continuationToken, "continuationToken") + + return JITIntrospectRequest( + requestUrl = URL(requestUrl), + headers = headers, + parameters = NativeAuthJITIntrospectRequestParameters( + clientId = clientId, + continuationToken = continuationToken + ) + ) + } + } + + override fun toUnsanitizedString(): String = "JITIntrospectRequest(requestUrl=$requestUrl, headers=$headers, parameters=$parameters)" + + override fun toString(): String = "JITIntrospectRequest()" + + /** + * NativeAuthJITIntrospectRequestParameters represents the request parameters sent as part of + * /register/introspect API call + */ + data class NativeAuthJITIntrospectRequestParameters( + @SerializedName("client_id") override val clientId: String, + @SerializedName("continuation_token") val continuationToken: String + ) : NativeAuthRequestParameters() { + override fun toUnsanitizedString(): String = "NativeAuthJITIntrospectRequestParameters(clientId=$clientId)" + + override fun toString(): String = toUnsanitizedString() + } +} \ No newline at end of file diff --git a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/jit/JITChallengeApiResponse.kt b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/jit/JITChallengeApiResponse.kt new file mode 100644 index 0000000000..b915cf321e --- /dev/null +++ b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/jit/JITChallengeApiResponse.kt @@ -0,0 +1,129 @@ +// 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.common.java.nativeauth.providers.responses.jit + +import com.google.gson.annotations.Expose +import com.google.gson.annotations.SerializedName +import com.microsoft.identity.common.java.nativeauth.providers.IApiResponse +import com.microsoft.identity.common.java.nativeauth.providers.responses.ApiErrorResult +import com.microsoft.identity.common.java.nativeauth.util.isInvalidChallengeTarget +import com.microsoft.identity.common.java.nativeauth.util.isInvalidRequest +import com.microsoft.identity.common.java.nativeauth.util.isRedirect +import java.net.HttpURLConnection + +/** + * Represents the raw response from the register/challenge endpoint. + * Can be converted to JITChallengeApiResult using the provided toResult() method. + */ +class JITChallengeApiResponse( + @Expose override var statusCode: Int, + correlationId: String, + @SerializedName("continuation_token") val continuationToken: String?, + @Expose @SerializedName("challenge_type") val challengeType: String?, + @Expose @SerializedName("binding_method") val bindingMethod: String?, + @Expose @SerializedName("challenge_target") val challengeTarget: String?, + @Expose @SerializedName("challenge_channel") val challengeChannel: String?, + @Expose @SerializedName("code_length") val codeLength: Int?, + @Expose @SerializedName("interval") val interval: Int?, + @SerializedName("error") val error: String?, + @SerializedName("error_codes") val errorCodes: List?, + @SerializedName("error_description") val errorDescription: String?, + @SerializedName("error_uri") val errorUri: String?, +) : IApiResponse(statusCode, correlationId) { + override fun toUnsanitizedString(): String { + return "JITChallengeApiResponse(statusCode=$statusCode, " + + "correlationId=$correlationId " + + "error=$error, errorCodes=$errorCodes, errorDescription=$errorDescription, " + + "challengeType=$challengeType, challengeTarget=$challengeTarget, bindingMethod=$bindingMethod, " + + "challengeChannel=$challengeChannel, codeLength=$codeLength)" + } + + override fun toString(): String = "JITChallengeApiResponse(statusCode=$statusCode, " + + "correlationId=$correlationId" + + fun toResult(): JITChallengeApiResult { + return when (statusCode) { + // Handle 400 errors + HttpURLConnection.HTTP_BAD_REQUEST -> { + return when { + error.isInvalidRequest() && errorCodes?.first().isInvalidChallengeTarget() -> { + JITChallengeApiResult.InvalidVerificationContact( + error = error.orEmpty(), + errorDescription = errorDescription.orEmpty(), + errorCodes = errorCodes.orEmpty(), + correlationId = correlationId + ) + } + + else -> { + JITChallengeApiResult.UnknownError( + error = error.orEmpty(), + errorDescription = errorDescription.orEmpty(), + errorCodes = errorCodes.orEmpty(), + correlationId = correlationId + ) + } + } + } + + // Handle success and redirect + HttpURLConnection.HTTP_OK -> { + return when { + continuationToken.isNullOrBlank() || + challengeType.isNullOrBlank() || + challengeTarget.isNullOrBlank() || + challengeChannel.isNullOrBlank() || + codeLength == null -> { + JITChallengeApiResult.UnknownError( + error = ApiErrorResult.INVALID_STATE, + errorDescription = "Register authentication method /challenge did not return all mandatory fields", + errorCodes = errorCodes.orEmpty(), + correlationId = correlationId + ) + } + + else -> { + JITChallengeApiResult.Success( + correlationId = correlationId, + continuationToken = continuationToken, + challengeType = challengeType, + bindingMethod = bindingMethod, + challengeTargetLabel = challengeTarget, + challengeChannel = challengeChannel, + codeLength = codeLength + ) + } + } + } + + else -> { + JITChallengeApiResult.UnknownError( + error = error.orEmpty(), + errorDescription = errorDescription.orEmpty(), + errorCodes = errorCodes.orEmpty(), + correlationId = correlationId + ) + } + } + } +} \ No newline at end of file diff --git a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/jit/JITChallengeApiResult.kt b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/jit/JITChallengeApiResult.kt new file mode 100644 index 0000000000..33c74638db --- /dev/null +++ b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/jit/JITChallengeApiResult.kt @@ -0,0 +1,94 @@ +// 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.common.java.nativeauth.providers.responses.jit + +import com.microsoft.identity.common.java.nativeauth.providers.responses.ApiErrorResult +import com.microsoft.identity.common.java.nativeauth.providers.responses.ApiResult +import com.microsoft.identity.common.java.nativeauth.providers.responses.resetpassword.ResetPasswordContinueApiResult + +/** + * Represents the potential result types returned from the register/challenge endpoint, + * including a case for unexpected errors received from the server. + */ +sealed interface JITChallengeApiResult: ApiResult { + + data class Success( + override val correlationId: String, + val continuationToken: String, + val challengeType: String, + val bindingMethod: String?, + val challengeTargetLabel: String, + val challengeChannel: String, + val codeLength: Int + ) : JITChallengeApiResult { + override fun toUnsanitizedString(): String { + return "Success(correlationId=$correlationId " + + "challengeType=$challengeType, " + + "bindingMethod=$bindingMethod, " + + "challengeTargetLabel=$challengeTargetLabel, " + + "challengeChannel=$challengeChannel, " + + "codeLength=$codeLength)" + } + + override fun toString(): String { + return "Success(correlationId=$correlationId, " + + "challengeType=$challengeType, " + + "bindingMethod=$bindingMethod, " + + "codeLength=$codeLength)" + } + } + + data class InvalidVerificationContact( + override val correlationId: String, + override val error: String, + override val errorDescription: String, + override val errorCodes: List + ) : ApiErrorResult( + error = error, + errorDescription = errorDescription, + errorCodes = errorCodes, + correlationId = correlationId + ), JITChallengeApiResult { + override fun toUnsanitizedString() = "InvalidVerificationContact(correlationId=$correlationId, " + + "error=$error, errorDescription=$errorDescription, subError=$subError)" + + override fun toString(): String = "InvalidVerificationContact(correlationId=$correlationId)" + } + + data class UnknownError( + override val correlationId: String, + override val error: String, + override val errorDescription: String, + override val errorCodes: List + ) : ApiErrorResult( + error = error, + errorDescription = errorDescription, + errorCodes = errorCodes, + correlationId = correlationId + ), JITChallengeApiResult { + override fun toUnsanitizedString() = "UnknownError(correlationId=$correlationId, " + + "error=$error, errorDescription=$errorDescription, errorCodes=$errorCodes)" + + override fun toString(): String = "UnknownError(correlationId=$correlationId)" + } +} \ No newline at end of file diff --git a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/jit/JITContinueApiResponse.kt b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/jit/JITContinueApiResponse.kt new file mode 100644 index 0000000000..011a190cf9 --- /dev/null +++ b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/jit/JITContinueApiResponse.kt @@ -0,0 +1,109 @@ +// 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.common.java.nativeauth.providers.responses.jit + +import com.google.gson.annotations.Expose +import com.google.gson.annotations.SerializedName +import com.microsoft.identity.common.java.nativeauth.providers.IApiResponse +import com.microsoft.identity.common.java.nativeauth.providers.responses.ApiErrorResult +import com.microsoft.identity.common.java.nativeauth.util.isInvalidGrant +import com.microsoft.identity.common.java.nativeauth.util.isOOBValueInvalid +import java.net.HttpURLConnection + +/** + * Represents the raw response from the register/continue endpoint. + * Can be converted to JITContinueAPIResult using the provided toResult() method. + */ +class JITContinueApiResponse( + @Expose override var statusCode: Int, + correlationId: String, + @SerializedName("continuation_token") val continuationToken: String?, + @SerializedName("error") val error: String?, + @SerializedName("error_codes") val errorCodes: List?, + @SerializedName("error_description") val errorDescription: String?, + @SerializedName("suberror") val subError: String? +) : IApiResponse(statusCode, correlationId) { + override fun toUnsanitizedString(): String { + return "JITContinueAPIResponse(statusCode=$statusCode, " + + "correlationId=$correlationId " + + "error=$error, errorCodes=$errorCodes, errorDescription=$errorDescription)" + } + + override fun toString(): String = "JITContinueAPIResponse(statusCode=$statusCode, " + + "correlationId=$correlationId)" + + fun toResult(): JITContinueApiResult { + return when (statusCode) { + // Handle 400 errors + HttpURLConnection.HTTP_BAD_REQUEST -> { + return when { + error.isInvalidGrant() && subError.isOOBValueInvalid() -> { + JITContinueApiResult.CodeIncorrect( + correlationId = correlationId, + error = error.orEmpty(), + errorDescription = errorDescription.orEmpty(), + errorCodes = errorCodes.orEmpty(), + subError = subError.orEmpty() + ) + } + else -> { + JITContinueApiResult.UnknownError( + error = error.orEmpty(), + errorDescription = errorDescription.orEmpty(), + errorCodes = errorCodes.orEmpty(), + correlationId = correlationId + ) + } + } + } + + // Handle success and redirect + HttpURLConnection.HTTP_OK -> { + return when { + continuationToken.isNullOrBlank() -> { + JITContinueApiResult.UnknownError( + error = ApiErrorResult.INVALID_STATE, + errorDescription = "Register authentication method /continue did not return continuationToken field", + errorCodes = errorCodes.orEmpty(), + correlationId = correlationId + ) + } + else -> { + JITContinueApiResult.Success( + correlationId = correlationId, + continuationToken = continuationToken + ) + } + } + } + else -> { + JITContinueApiResult.UnknownError( + error = error.orEmpty(), + errorDescription = errorDescription.orEmpty(), + errorCodes = errorCodes.orEmpty(), + correlationId = correlationId + ) + } + } + } +} \ No newline at end of file diff --git a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/jit/JITContinueApiResult.kt b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/jit/JITContinueApiResult.kt new file mode 100644 index 0000000000..b12ff4bd46 --- /dev/null +++ b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/jit/JITContinueApiResult.kt @@ -0,0 +1,79 @@ +// 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.common.java.nativeauth.providers.responses.jit + +import com.microsoft.identity.common.java.nativeauth.providers.responses.ApiErrorResult +import com.microsoft.identity.common.java.nativeauth.providers.responses.ApiResult + +/** + * Represents the potential result types returned from the register/continue endpoint, + * including a case for unexpected errors received from the server. + */ +sealed interface JITContinueApiResult: ApiResult { + + data class Success( + override val correlationId: String, + val continuationToken: String + ) : JITContinueApiResult { + override fun toUnsanitizedString(): String { + return "Success(correlationId=$correlationId)" + } + + override fun toString(): String = toUnsanitizedString() + } + + data class CodeIncorrect( + override val correlationId: String, + override val error: String, + override val errorDescription: String, + override val errorCodes: List, + override val subError: String + ) : ApiErrorResult( + error = error, + errorDescription = errorDescription, + correlationId = correlationId, + errorCodes = errorCodes + ), JITContinueApiResult { + override fun toUnsanitizedString() = "CodeIncorrect(correlationId=$correlationId, " + + "error=$error, errorDescription=$errorDescription, subError=$subError)" + + override fun toString(): String = "CodeIncorrect(correlationId=$correlationId)" + } + + data class UnknownError( + override val correlationId: String, + override val error: String, + override val errorDescription: String, + override val errorCodes: List + ) : ApiErrorResult( + error = error, + errorDescription = errorDescription, + errorCodes = errorCodes, + correlationId = correlationId + ), JITContinueApiResult { + override fun toUnsanitizedString() = "UnknownError(correlationId=$correlationId, " + + "error=$error, errorDescription=$errorDescription, errorCodes=$errorCodes)" + + override fun toString(): String = "UnknownError(correlationId=$correlationId)" + } +} \ No newline at end of file diff --git a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/jit/JITIntrospectApiResponse.kt b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/jit/JITIntrospectApiResponse.kt new file mode 100644 index 0000000000..635d35d8aa --- /dev/null +++ b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/jit/JITIntrospectApiResponse.kt @@ -0,0 +1,118 @@ +// 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.common.java.nativeauth.providers.responses.jit + +import com.google.gson.annotations.Expose +import com.google.gson.annotations.SerializedName +import com.microsoft.identity.common.java.nativeauth.providers.IApiResponse +import com.microsoft.identity.common.java.nativeauth.providers.responses.ApiErrorResult +import com.microsoft.identity.common.java.nativeauth.providers.responses.signin.AuthenticationMethodApiResponse +import com.microsoft.identity.common.java.nativeauth.providers.responses.signin.toListOfAuthenticationMethodApiResult +import com.microsoft.identity.common.java.nativeauth.util.isRedirect +import java.lang.IllegalStateException +import java.net.HttpURLConnection + +/** + * Represents the raw response from the register/introspect endpoint. + * Can be converted to JITIntrospectApiResult using the provided toResult() method. + */ +class JITIntrospectApiResponse( + @Expose override var statusCode: Int, + correlationId: String, + @SerializedName("continuation_token") val continuationToken: String?, + @Expose @SerializedName("methods") val methods: List?, + @Expose @SerializedName("challenge_type") val challengeType: String?, + @SerializedName("error") val error: String?, + @SerializedName("error_codes") val errorCodes: List?, + @SerializedName("error_description") val errorDescription: String?, + @SerializedName("error_uri") val errorUri: String?, +) : IApiResponse(statusCode, correlationId) { + + override fun toUnsanitizedString(): String { + return "JITIntrospectApiResponse(statusCode=$statusCode, " + + "correlationId=$correlationId " + + "error=$error, errorCodes=$errorCodes, errorDescription=$errorDescription, " + + "methods=$methods, challengeType=$challengeType)" + } + + override fun toString(): String = "JITIntrospectApiResponse(statusCode=$statusCode, " + + "correlationId=$correlationId" + + fun toResult(): JITIntrospectApiResult { + return when (statusCode) { + // Handle 400 errors + HttpURLConnection.HTTP_BAD_REQUEST -> { + JITIntrospectApiResult.UnknownError( + error = error.orEmpty(), + errorDescription = errorDescription.orEmpty(), + errorCodes = errorCodes.orEmpty(), + correlationId = correlationId + ) + } + + // Handle success and redirect + HttpURLConnection.HTTP_OK -> { + return when { + methods.isNullOrEmpty() -> { + JITIntrospectApiResult.UnknownError( + error = ApiErrorResult.INVALID_STATE, + errorDescription = "register/introspect did not return methods", + errorCodes = errorCodes.orEmpty(), + correlationId = correlationId + ) + } + else -> { + try { + JITIntrospectApiResult.Success( + correlationId = correlationId, + continuationToken = continuationToken + ?: return JITIntrospectApiResult.UnknownError( + error = ApiErrorResult.INVALID_STATE, + errorDescription = "register/introspect did not return a continuation token", + errorCodes = errorCodes.orEmpty(), + correlationId = correlationId + ), + methods = methods.toListOfAuthenticationMethodApiResult() + ) + } catch (e: IllegalStateException) { + JITIntrospectApiResult.UnknownError( + error = ApiErrorResult.INVALID_STATE, + errorDescription = "register/introspect did not return valid methods: ${e.message}", + errorCodes = errorCodes.orEmpty(), + correlationId = correlationId + ) + } + } + } + } + else -> { + JITIntrospectApiResult.UnknownError( + error = error.orEmpty(), + errorDescription = errorDescription.orEmpty(), + errorCodes = errorCodes.orEmpty(), + correlationId = correlationId + ) + } + } + } +} \ No newline at end of file diff --git a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/jit/JITIntrospectApiResult.kt b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/jit/JITIntrospectApiResult.kt new file mode 100644 index 0000000000..c180a3ba00 --- /dev/null +++ b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/jit/JITIntrospectApiResult.kt @@ -0,0 +1,66 @@ +// 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.common.java.nativeauth.providers.responses.jit + +import com.microsoft.identity.common.java.nativeauth.providers.responses.ApiErrorResult +import com.microsoft.identity.common.java.nativeauth.providers.responses.ApiResult +import com.microsoft.identity.common.java.nativeauth.providers.responses.signin.AuthenticationMethodApiResult + +/** + * Represents the potential result types returned from the register/introspect endpoint, + * including a case for unexpected errors received from the server. + */ +sealed interface JITIntrospectApiResult: ApiResult { + + data class Success( + override val correlationId: String, + val continuationToken: String, + val methods: List + ) : JITIntrospectApiResult { + override fun toUnsanitizedString(): String { + return "Success(correlationId=$correlationId, " + + "methods=$methods)" + } + + override fun toString(): String { + return "Success(correlationId=$correlationId)" + } + } + + data class UnknownError( + override val correlationId: String, + override val error: String, + override val errorDescription: String, + override val errorCodes: List, + ) : ApiErrorResult( + error = error, + errorDescription = errorDescription, + errorCodes = errorCodes, + correlationId = correlationId + ), JITIntrospectApiResult { + override fun toUnsanitizedString() = "UnknownError(correlationId=$correlationId, " + + "error=$error, errorDescription=$errorDescription, errorCodes=$errorCodes)" + + override fun toString(): String = "UnknownError(correlationId=$correlationId)" + } +} \ No newline at end of file diff --git a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/signin/SignInTokenApiResponse.kt b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/signin/SignInTokenApiResponse.kt index 29df88a04e..e08b1d7bcc 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/signin/SignInTokenApiResponse.kt +++ b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/signin/SignInTokenApiResponse.kt @@ -31,6 +31,7 @@ import com.microsoft.identity.common.java.nativeauth.util.isInvalidCredentials import com.microsoft.identity.common.java.nativeauth.util.isInvalidGrant import com.microsoft.identity.common.java.nativeauth.util.isInvalidOOBValue import com.microsoft.identity.common.java.nativeauth.util.isInvalidRequest +import com.microsoft.identity.common.java.nativeauth.util.isJITRequired import com.microsoft.identity.common.java.nativeauth.util.isMFARequired import com.microsoft.identity.common.java.nativeauth.util.isPasswordChangeRequired import com.microsoft.identity.common.java.nativeauth.util.isUserNotFound @@ -128,6 +129,22 @@ class SignInTokenApiResponse( correlationId = correlationId ) } + subError.isJITRequired() -> { + SignInTokenApiResult.JITRequired( + error = error.orEmpty(), + errorDescription = errorDescription.orEmpty(), + continuationToken = continuationToken ?: + return SignInTokenApiResult.UnknownError( + error = ApiErrorResult.INVALID_STATE, + errorDescription = "oauth/v2.0/token did not return a continuation token", + errorCodes = errorCodes.orEmpty(), + correlationId = correlationId + ), + subError = subError.orEmpty(), + errorCodes = errorCodes.orEmpty(), + correlationId = correlationId + ) + } subError.isInvalidOOBValue() -> { SignInTokenApiResult.CodeIncorrect( error = error.orEmpty(), diff --git a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/signin/SignInTokenApiResult.kt b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/signin/SignInTokenApiResult.kt index d3115bd6c3..eb934c4c92 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/signin/SignInTokenApiResult.kt +++ b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/responses/signin/SignInTokenApiResult.kt @@ -63,6 +63,26 @@ sealed interface SignInTokenApiResult: ApiResult, ILoggable { override fun toString(): String = "MFARequired(correlationId=$correlationId)" } + data class JITRequired( + override val correlationId: String, + val continuationToken: String, + override val error: String, + override val subError: String, + override val errorDescription: String, + override val errorCodes: List + ) : ApiErrorResult( + error = error, + errorDescription = errorDescription, + subError = subError, + errorCodes = errorCodes, + correlationId = correlationId + ), SignInTokenApiResult { + override fun toUnsanitizedString() = "JITRequired(correlationId=$correlationId, " + + "error=$error, errorDescription=$errorDescription, subError=$subError, errorCodes=$errorCodes)" + + override fun toString(): String = "JITRequired(correlationId=$correlationId)" + } + data class UserNotFound( override val correlationId: String, override val error: String, diff --git a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/util/ApiErrorResponseUtil.kt b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/util/ApiErrorResponseUtil.kt index 44ede64ee6..4e959a55d5 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/util/ApiErrorResponseUtil.kt +++ b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/util/ApiErrorResponseUtil.kt @@ -93,6 +93,10 @@ internal fun String?.isExpiredToken(): Boolean { return this.contentEquals(other = "expired_token", ignoreCase = true) } +internal fun String?.isOOBValueInvalid(): Boolean { + return this.contentEquals(other = "invalid_oob_value", ignoreCase = true) +} + internal fun Int?.isInvalidParameter() : Boolean { return this == 90100 } @@ -109,10 +113,18 @@ internal fun Int?.isInvalidAuthenticationType(): Boolean { return this == 400002 } +internal fun Int?.isInvalidChallengeTarget(): Boolean { + return this == 901001 +} + fun String?.isMFARequired(): Boolean { return this.contentEquals(other = "mfa_required", ignoreCase = true) } +fun String?.isJITRequired(): Boolean { + return this.contentEquals(other = "registration_required", ignoreCase = true) +} + internal fun String?.isVerificationRequired(): Boolean { return this.contentEquals(other = "verification_required", ignoreCase = true) } diff --git a/common4j/src/test/com/microsoft/identity/common/java/nativeauth/providers/NativeAuthRequestProviderTest.kt b/common4j/src/test/com/microsoft/identity/common/java/nativeauth/providers/NativeAuthRequestProviderTest.kt index 9f24ae8b82..4af0a2a7bf 100644 --- a/common4j/src/test/com/microsoft/identity/common/java/nativeauth/providers/NativeAuthRequestProviderTest.kt +++ b/common4j/src/test/com/microsoft/identity/common/java/nativeauth/providers/NativeAuthRequestProviderTest.kt @@ -65,6 +65,8 @@ class NativeAuthRequestProviderTest { private val continuationToken = "uY29tL2F1dGhlbnRpY" private val challengeId = "902rnwfn3" private val correlationId = "jsdfo4nslkjsrg" + private val challengeTarget = "mail@contoso.com" + private val challengeChannel = "email" private val grantType = NativeAuthConstants.GrantType.OOB private val mockConfig = mockk { @@ -80,6 +82,9 @@ class NativeAuthRequestProviderTest { every { getResetPasswordContinueEndpoint() } returns ApiConstants.MockApi.ssprContinueRequestUrl every { getResetPasswordSubmitEndpoint() } returns ApiConstants.MockApi.ssprSubmitRequestUrl every { getResetPasswordPollCompletionEndpoint() } returns ApiConstants.MockApi.ssprPollCompletionRequestUrl + every { getJITIntrospectEndpoint() } returns ApiConstants.MockApi.jitIntrospectRequestUrl + every { getJITChallengeEndpoint() } returns ApiConstants.MockApi.jitChallengeRequestUrl + every { getJITContinueEndpoint() } returns ApiConstants.MockApi.jitContinueRequestUrl every { challengeType } returns this@NativeAuthRequestProviderTest.challengeType every { clientId } returns this@NativeAuthRequestProviderTest.clientId every { useMockApiForNativeAuth } returns true @@ -1251,4 +1256,231 @@ class NativeAuthRequestProviderTest { assertEquals(ApiConstants.MockApi.ssprPollCompletionRequestUrl, result.requestUrl) assertEquals(result.headers[AuthenticationConstants.AAD.CLIENT_REQUEST_ID], correlationId) } + + // region JIT tests + + @Test(expected = ClientException::class) + fun testJITIntrospectWithEmptyContinuationTokenShouldThrowException() { + val continuationToken = "" + + nativeAuthRequestProvider.createJITIntrospectRequest( + continuationToken, + correlationId + ) + } + + @Test(expected = ClientException::class) + fun testJITIntrospectWithEmptyClientIdShouldThrowException() { + every { mockConfig.clientId } returns emptyString + + nativeAuthRequestProvider.createJITIntrospectRequest( + continuationToken, + correlationId + ) + } + + @Test + fun testJITIntrospectWithUnsetCorrelationIdShouldNotHaveHeader() { + val correlationId = "UNSET" + + val result = nativeAuthRequestProvider.createJITIntrospectRequest( + continuationToken, + correlationId + ) + + assertNull(result.headers[AuthenticationConstants.AAD.CLIENT_REQUEST_ID]) + } + + @Test + fun testJITIntrospectRequestSuccess() { + val result = nativeAuthRequestProvider.createJITIntrospectRequest( + continuationToken, + correlationId + ) + + assertEquals(clientId, result.parameters.clientId) + assertEquals(continuationToken, result.parameters.continuationToken) + assertEquals(ApiConstants.MockApi.jitIntrospectRequestUrl, result.requestUrl) + assertEquals(result.headers[AuthenticationConstants.AAD.CLIENT_REQUEST_ID], correlationId) + } + + @Test(expected = ClientException::class) + fun testJITChallengeWithEmptyContinuationTokenShouldThrowException() { + val continuationToken = "" + + nativeAuthRequestProvider.createJITChallengeRequest( + continuationToken = continuationToken, + challengeType = challengeType, + challengeTarget = challengeTarget, + challengeChannel = challengeChannel, + correlationId = correlationId + ) + } + + @Test(expected = ClientException::class) + fun testJITChallengeWithEmptyChallengeTypeShouldThrowException() { + val challengeType = "" + + nativeAuthRequestProvider.createJITChallengeRequest( + continuationToken = continuationToken, + challengeType = challengeType, + challengeTarget = challengeTarget, + challengeChannel = challengeChannel, + correlationId = correlationId + ) + } + + @Test(expected = ClientException::class) + fun testJITChallengeWithEmptyChallengeTargetShouldThrowException() { + val challengeTarget = "" + + nativeAuthRequestProvider.createJITChallengeRequest( + continuationToken = continuationToken, + challengeType = challengeType, + challengeTarget = challengeTarget, + challengeChannel = challengeChannel, + correlationId = correlationId + ) + } + + @Test(expected = ClientException::class) + fun testJITChallengeWithEmptyChallengeChannelShouldThrowException() { + val challengeChannel = "" + + nativeAuthRequestProvider.createJITChallengeRequest( + continuationToken = continuationToken, + challengeType = challengeType, + challengeTarget = challengeTarget, + challengeChannel = challengeChannel, + correlationId = correlationId + ) + } + + @Test + fun testJITChallengeWithUnsetCorrelationIdShouldNotHaveHeader() { + val correlationId = "UNSET" + + val result = nativeAuthRequestProvider.createJITChallengeRequest( + continuationToken = continuationToken, + challengeType = challengeType, + challengeTarget = challengeTarget, + challengeChannel = challengeChannel, + correlationId = correlationId + ) + + assertNull(result.headers[AuthenticationConstants.AAD.CLIENT_REQUEST_ID]) + } + + @Test(expected = ClientException::class) + fun testJITChallengeWithEmptyClientIdShouldThrowException() { + every { mockConfig.clientId } returns emptyString + + nativeAuthRequestProvider.createJITChallengeRequest( + continuationToken = continuationToken, + challengeType = challengeType, + challengeTarget = challengeTarget, + challengeChannel = challengeChannel, + correlationId = correlationId + ) + } + + @Test + fun testJITChallengeRequestSuccess() { + val result = nativeAuthRequestProvider.createJITChallengeRequest( + continuationToken = continuationToken, + challengeType = challengeType, + challengeTarget = challengeTarget, + challengeChannel = challengeChannel, + correlationId = correlationId + ) + + assertEquals(clientId, result.parameters.clientId) + assertEquals(continuationToken, result.parameters.continuationToken) + assertEquals(challengeType, result.parameters.challengeType) + assertEquals(challengeTarget, result.parameters.challengeTarget) + assertEquals(challengeChannel, result.parameters.challengeChannel) + assertEquals(ApiConstants.MockApi.jitChallengeRequestUrl, result.requestUrl) + assertEquals(result.headers[AuthenticationConstants.AAD.CLIENT_REQUEST_ID], correlationId) + } + + @Test(expected = ClientException::class) + fun testJITContinueWithEmptyContinuationTokenShouldThrowException() { + val continuationToken = "" + + nativeAuthRequestProvider.createJITContinueRequest( + continuationToken = continuationToken, + grantType = grantType, + code = oobCode, + correlationId = correlationId + ) + } + + @Test(expected = ClientException::class) + fun testJITContinueWithEmptyGrantTypeShouldThrowException() { + val grantType = "" + + nativeAuthRequestProvider.createJITContinueRequest( + continuationToken = continuationToken, + grantType = grantType, + code = oobCode, + correlationId = correlationId + ) + } + + @Test(expected = ClientException::class) + fun testJITContinueWithEmptyOOBCodeShouldThrowException() { + val oobCode = "" + + nativeAuthRequestProvider.createJITContinueRequest( + continuationToken = continuationToken, + grantType = grantType, + code = oobCode, + correlationId = correlationId + ) + } + + @Test + fun testJITContinueWithUnsetCorrelationIdShouldNotHaveHeader() { + val correlationId = "UNSET" + + val result = nativeAuthRequestProvider.createJITContinueRequest( + continuationToken = continuationToken, + grantType = grantType, + code = oobCode, + correlationId = correlationId + ) + + assertNull(result.headers[AuthenticationConstants.AAD.CLIENT_REQUEST_ID]) + } + + @Test(expected = ClientException::class) + fun testJITContinueWithEmptyClientIdShouldThrowException() { + every { mockConfig.clientId } returns emptyString + + nativeAuthRequestProvider.createJITContinueRequest( + continuationToken = continuationToken, + grantType = grantType, + code = oobCode, + correlationId = correlationId + ) + } + + @Test + fun testJITContinueRequestSuccess() { + val result = nativeAuthRequestProvider.createJITContinueRequest( + continuationToken = continuationToken, + grantType = grantType, + code = oobCode, + correlationId = correlationId + ) + + assertEquals(clientId, result.parameters.clientId) + assertEquals(continuationToken, result.parameters.continuationToken) + assertEquals(grantType, result.parameters.grantType) + assertEquals(oobCode, result.parameters.oob) + assertEquals(ApiConstants.MockApi.jitContinueRequestUrl, result.requestUrl) + assertEquals(result.headers[AuthenticationConstants.AAD.CLIENT_REQUEST_ID], correlationId) + } + + //endregion } diff --git a/common4j/src/test/com/microsoft/identity/common/java/nativeauth/providers/NativeAuthResponseHandlerTest.kt b/common4j/src/test/com/microsoft/identity/common/java/nativeauth/providers/NativeAuthResponseHandlerTest.kt index 5df2058ad9..2812234634 100644 --- a/common4j/src/test/com/microsoft/identity/common/java/nativeauth/providers/NativeAuthResponseHandlerTest.kt +++ b/common4j/src/test/com/microsoft/identity/common/java/nativeauth/providers/NativeAuthResponseHandlerTest.kt @@ -28,6 +28,12 @@ import com.microsoft.identity.common.java.nativeauth.providers.responses.ApiErro import com.microsoft.identity.common.java.net.HttpResponse import com.microsoft.identity.common.java.nativeauth.providers.responses.UserAttributeApiResult import com.microsoft.identity.common.java.nativeauth.providers.responses.UserAttributeOptionsApiResult +import com.microsoft.identity.common.java.nativeauth.providers.responses.jit.JITChallengeApiResponse +import com.microsoft.identity.common.java.nativeauth.providers.responses.jit.JITChallengeApiResult +import com.microsoft.identity.common.java.nativeauth.providers.responses.jit.JITContinueApiResponse +import com.microsoft.identity.common.java.nativeauth.providers.responses.jit.JITContinueApiResult +import com.microsoft.identity.common.java.nativeauth.providers.responses.jit.JITIntrospectApiResponse +import com.microsoft.identity.common.java.nativeauth.providers.responses.jit.JITIntrospectApiResult import com.microsoft.identity.common.java.nativeauth.providers.responses.resetpassword.ResetPasswordChallengeApiResponse import com.microsoft.identity.common.java.nativeauth.providers.responses.resetpassword.ResetPasswordChallengeApiResult import com.microsoft.identity.common.java.nativeauth.providers.responses.resetpassword.ResetPasswordContinueApiResponse @@ -60,6 +66,7 @@ import org.junit.Assert import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue +import org.junit.Assert.fail import org.junit.Test import org.mockito.kotlin.mock import org.mockito.kotlin.whenever @@ -3399,4 +3406,339 @@ class NativeAuthResponseHandlerTest { Assert.assertEquals(NativeAuthResponseHandler.EMPTY_RESPONSE_ERROR_ERROR_DESCRIPTION, response.errorDescription) } // endregion + + //region JIT introspect + + @Test + fun testJITIntrospectApiSuccessButEmptyMethodList() { + val methods = emptyList() + val jitIntrospectApiResponse = JITIntrospectApiResponse( + statusCode = successStatusCode, + challengeType = emailChallengeChannel, + continuationToken = continuationToken, + error = null, + errorDescription = null, + methods = methods, + errorUri = null, + errorCodes = null, + correlationId = correlationId + ) + val apiResult = jitIntrospectApiResponse.toResult() + if (apiResult is JITIntrospectApiResult.UnknownError) { + assertEquals(ApiErrorResult.INVALID_STATE, apiResult.error) + } else { + fail("Expected JITIntrospectApiResult.UnknownError but got $apiResult") + } + } + + @Test + fun testJITIntrospectApiSuccess() { + val method = AuthenticationMethodApiResponse( + id = "email", + challengeType = emailChallengeChannel, + loginHint = "email@contoso.com", + challengeChannel = "" + ) + val methods = listOf(method) + val jitIntrospectApiResponse = JITIntrospectApiResponse( + statusCode = successStatusCode, + challengeType = emailChallengeChannel, + continuationToken = continuationToken, + error = null, + errorDescription = null, + methods = methods, + errorUri = null, + errorCodes = null, + correlationId = correlationId + ) + val apiResult = jitIntrospectApiResponse.toResult() + if (apiResult is JITIntrospectApiResult.Success) { + assertEquals(continuationToken, apiResult.continuationToken) + assertEquals(correlationId, apiResult.correlationId) + assertEquals(1, apiResult.methods.count()) + val resultMethod = apiResult.methods.first() + assertEquals(method.id, resultMethod.id) + assertEquals(method.challengeType, resultMethod.challengeType) + assertEquals(method.loginHint, resultMethod.loginHint) + assertEquals(method.challengeChannel, resultMethod.challengeChannel) + } else { + fail("Expected JITIntrospectApiResult.Success but got $apiResult") + } + } + + @Test + fun testJITIntrospectApiResponseBadRequest() { + val jitIntrospectApiResponse = JITIntrospectApiResponse( + statusCode = errorStatusCode, + continuationToken = null, + error = invalidGrantError, + errorCodes = emptyList(), + errorDescription = unknownErrorDescription, + errorUri = null, + methods = null, + challengeType = null, + correlationId = correlationId + ) + + val apiResult = jitIntrospectApiResponse.toResult() + if (apiResult is JITIntrospectApiResult.UnknownError) { + assertEquals(invalidGrantError, apiResult.error) + assertEquals(unknownErrorDescription, apiResult.errorDescription) + } else { + fail("Expected JITIntrospectApiResult.UnknownError but got $apiResult") + } + } + + //endregion + + //region JIT challenge + + @Test + fun testJITChallengeApiSuccessButMissingMandatoryField() { + val jitChallengeApiResponse = JITChallengeApiResponse( + statusCode = successStatusCode, + challengeType = emailChallengeChannel, + continuationToken = continuationToken, + error = null, + errorDescription = null, + errorUri = null, + errorCodes = null, + bindingMethod = null, + challengeChannel = null, + challengeTarget = null, + codeLength = null, + interval = null, + correlationId = correlationId + ) + val apiResult = jitChallengeApiResponse.toResult() + if (apiResult is JITChallengeApiResult.UnknownError) { + assertEquals(ApiErrorResult.INVALID_STATE, apiResult.error) + assertEquals(correlationId, apiResult.correlationId) + } else { + fail("Expected JITChallengeApiResult.UnknownError but got $apiResult") + } + } + + @Test + fun testJITChallengeApiUncommonStatusCodeShouldReturnError() { + val jitChallengeApiResponse = JITChallengeApiResponse( + statusCode = uncommonErrorStatusCode, + challengeType = redirect, + continuationToken = continuationToken, + error = null, + errorDescription = null, + errorUri = null, + errorCodes = null, + bindingMethod = null, + challengeChannel = null, + challengeTarget = null, + codeLength = null, + interval = null, + correlationId = correlationId + ) + val apiResult = jitChallengeApiResponse.toResult() + if (apiResult is JITChallengeApiResult.UnknownError) { + assertEquals(correlationId, apiResult.correlationId) + assertEquals(emptyString, apiResult.error) + } else { + fail("Expected JITChallengeApiResult.UnknownError but got $apiResult") + } + } + + @Test + fun testJITChallengeApiInvalidChallengeTargetIsHandledCorrectly() { + val errorCodes = listOf(901001) + val jitChallengeApiResponse = JITChallengeApiResponse( + statusCode = errorStatusCode, + challengeType = null, + continuationToken = null, + error = invalidRequestError, + errorDescription = errorDescription, + errorUri = null, + errorCodes = errorCodes, + bindingMethod = null, + challengeChannel = null, + challengeTarget = null, + codeLength = null, + interval = null, + correlationId = correlationId + ) + val apiResult = jitChallengeApiResponse.toResult() + if (apiResult is JITChallengeApiResult.InvalidVerificationContact) { + assertEquals(correlationId, apiResult.correlationId) + assertEquals(invalidRequestError, apiResult.error) + assertEquals(errorDescription, apiResult.errorDescription) + assertEquals(errorCodes, apiResult.errorCodes) + } else { + fail("Expected JITChallengeApiResult.InvalidVerificationContact but got $apiResult") + } + } + + @Test + fun testJITChallengeApiInvalidRequestWithUnknownErrorCodeIsHandledCorrectly() { + val errorCodes = listOf(100100) + val jitChallengeApiResponse = JITChallengeApiResponse( + statusCode = errorStatusCode, + challengeType = null, + continuationToken = null, + error = invalidRequestError, + errorDescription = errorDescription, + errorUri = null, + errorCodes = errorCodes, + bindingMethod = null, + challengeChannel = null, + challengeTarget = null, + codeLength = null, + interval = null, + correlationId = correlationId + ) + val apiResult = jitChallengeApiResponse.toResult() + if (apiResult is JITChallengeApiResult.UnknownError) { + assertEquals(correlationId, apiResult.correlationId) + assertEquals(invalidRequestError, apiResult.error) + assertEquals(errorDescription, apiResult.errorDescription) + assertEquals(errorCodes, apiResult.errorCodes) + } else { + fail("Expected JITChallengeApiResult.UnknownError but got $apiResult") + } + } + + @Test + fun testJITChallengeApiSuccessIsHandledCorrectly() { + val jitChallengeApiResponse = JITChallengeApiResponse( + statusCode = successStatusCode, + challengeType = challengeType, + continuationToken = continuationToken, + error = null, + errorDescription = null, + errorUri = null, + errorCodes = null, + bindingMethod = bindingMethod, + challengeChannel = emailChallengeChannel, + challengeTarget = challengeTargetLabel, + codeLength = codeLength, + interval = interval, + correlationId = correlationId + ) + val apiResult = jitChallengeApiResponse.toResult() + if (apiResult is JITChallengeApiResult.Success) { + assertEquals(correlationId, apiResult.correlationId) + assertEquals(emailChallengeChannel, apiResult.challengeChannel) + assertEquals(challengeType, apiResult.challengeType) + assertEquals(codeLength, apiResult.codeLength) + assertEquals(challengeTargetLabel, apiResult.challengeTargetLabel) + assertEquals(bindingMethod, apiResult.bindingMethod) + } else { + fail("Expected JITChallengeApiResult.Success but got $apiResult") + } + } + + //endregion + + //region JIT continue + + @Test + fun testJITContinueApiInvalidOOBIsHandledCorrectly() { + val jitContinueApiResponse = JITContinueApiResponse( + statusCode = errorStatusCode, + continuationToken = continuationToken, + error = invalidGrantError, + errorDescription = errorDescription, + errorCodes = null, + subError = invalidOOBValueError, + correlationId = correlationId + ) + val apiResult = jitContinueApiResponse.toResult() + if (apiResult is JITContinueApiResult.CodeIncorrect) { + assertEquals(correlationId, apiResult.correlationId) + assertEquals(invalidGrantError, apiResult.error) + assertEquals(errorDescription, apiResult.errorDescription) + } else { + fail("Expected JITContinueApiResult.CodeIncorrect but got $apiResult") + } + } + + @Test + fun testJITContinueApiInvalidGrantGenericSubErrorIsHandledAsUnknownError() { + val jitContinueApiResponse = JITContinueApiResponse( + statusCode = errorStatusCode, + continuationToken = continuationToken, + error = invalidGrantError, + errorDescription = errorDescription, + errorCodes = null, + subError = null, + correlationId = correlationId + ) + val apiResult = jitContinueApiResponse.toResult() + if (apiResult is JITContinueApiResult.UnknownError) { + assertEquals(correlationId, apiResult.correlationId) + assertEquals(invalidGrantError, apiResult.error) + assertEquals(errorDescription, apiResult.errorDescription) + } else { + fail("Expected JITContinueApiResult.UnknownError but got $apiResult") + } + } + + @Test + fun testJITContinueApiUncommonErrorCodeIsHandledAsUnknownError() { + val jitContinueApiResponse = JITContinueApiResponse( + statusCode = uncommonErrorStatusCode, + continuationToken = continuationToken, + error = invalidGrantError, + errorDescription = errorDescription, + errorCodes = null, + subError = null, + correlationId = correlationId + ) + val apiResult = jitContinueApiResponse.toResult() + if (apiResult is JITContinueApiResult.UnknownError) { + assertEquals(correlationId, apiResult.correlationId) + assertEquals(invalidGrantError, apiResult.error) + assertEquals(errorDescription, apiResult.errorDescription) + } else { + fail("Expected JITContinueApiResult.UnknownError but got $apiResult") + } + } + + @Test + fun testJITContinueApiSuccessButMissingContinuationTokenIsHandledAsUnknownError() { + val jitContinueApiResponse = JITContinueApiResponse( + statusCode = successStatusCode, + continuationToken = null, + error = invalidGrantError, + errorDescription = errorDescription, + errorCodes = null, + subError = null, + correlationId = correlationId + ) + val apiResult = jitContinueApiResponse.toResult() + if (apiResult is JITContinueApiResult.UnknownError) { + assertEquals(correlationId, apiResult.correlationId) + assertEquals(ApiErrorResult.INVALID_STATE, apiResult.error) + } else { + fail("Expected JITContinueApiResult.UnknownError but got $apiResult") + } + } + + @Test + fun testJITContinueApiSuccessIsHandledAsSuccess() { + val jitContinueApiResponse = JITContinueApiResponse( + statusCode = successStatusCode, + continuationToken = continuationToken, + error = null, + errorDescription = null, + errorCodes = null, + subError = null, + correlationId = correlationId + ) + val apiResult = jitContinueApiResponse.toResult() + if (apiResult is JITContinueApiResult.Success) { + assertEquals(correlationId, apiResult.correlationId) + assertEquals(continuationToken, apiResult.continuationToken) + } else { + fail("Expected JITContinueApiResult.Success but got $apiResult") + } + } + + //endregion } diff --git a/common4j/src/testFixtures/java/com/microsoft/identity/common/nativeauth/ApiConstants.kt b/common4j/src/testFixtures/java/com/microsoft/identity/common/nativeauth/ApiConstants.kt index 767a99300e..cf7892148d 100644 --- a/common4j/src/testFixtures/java/com/microsoft/identity/common/nativeauth/ApiConstants.kt +++ b/common4j/src/testFixtures/java/com/microsoft/identity/common/nativeauth/ApiConstants.kt @@ -45,6 +45,9 @@ interface ApiConstants { val ssprContinueRequestUrl = URL(BASE_REQUEST_PATH + "resetpassword/v1.0/continue") val ssprSubmitRequestUrl = URL(BASE_REQUEST_PATH + "resetpassword/v1.0/submit") val ssprPollCompletionRequestUrl = URL(BASE_REQUEST_PATH + "resetpassword/v1.0/poll_completion") + val jitIntrospectRequestUrl = URL(BASE_REQUEST_PATH + "register/v1.0/introspect") + val jitChallengeRequestUrl = URL(BASE_REQUEST_PATH + "register/v1.0/challenge") + val jitContinueRequestUrl = URL(BASE_REQUEST_PATH + "register/v1.0/continue") val tokenEndpoint = URL("https://contoso.com/1234/token") }