From 223ac0cbb7d740876e51a21a9b8856e2349c3a7b Mon Sep 17 00:00:00 2001 From: Danilo Raspa <105228698+nilo-ms@users.noreply.github.com> Date: Thu, 23 Jan 2025 16:33:10 +0000 Subject: [PATCH] Native auth: Add support for claims request, AB#3117218, Fixes AB#3117218 (#2572) Adding support on native authentication for claims request. During sign-in, native authentication users can now specify claims requests, which will be sent to the /token endpoint. This enhancement specifically supports authentication context. MSAL PR: https://github.com/AzureAD/microsoft-authentication-library-for-android/pull/2246 [AB#3117218](https://identitydivision.visualstudio.com/Engineering/_workitems/edit/3117218) --- changelog.txt | 1 + .../controllers/NativeAuthMsalController.kt | 2 +- .../nativeauth/internal/util/CommandUtil.java | 2 + .../internal/util/CommandUtilTest.kt | 80 +++++++++++++++++++ .../BaseSignInTokenCommandParameters.java | 2 +- .../MFADefaultChallengeCommandParameters.java | 8 ++ .../MFASubmitChallengeCommandParameters.java | 8 ++ .../SignInStartCommandParameters.java | 6 ++ .../SignInSubmitCodeCommandParameters.java | 8 ++ ...SignInSubmitPasswordCommandParameters.java | 8 ++ .../providers/NativeAuthRequestProvider.kt | 6 +- .../requests/signin/SignInTokenRequest.kt | 19 +++-- .../NativeAuthRequestProviderTest.kt | 64 +++++++++++++++ 13 files changed, 203 insertions(+), 11 deletions(-) create mode 100644 common/src/test/java/com/microsoft/identity/common/nativeauth/internal/util/CommandUtilTest.kt diff --git a/changelog.txt b/changelog.txt index 2b73579f16..1d41cae8ab 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,6 +1,7 @@ vNext ---------- - [MINOR] Add support for OneBox Environment (#2559) +- [MINOR] Add support for claims requests for native authentication (#2572) Version 19.0.0 ---------- 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 c2e6671437..8893e5bed6 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 @@ -153,7 +153,7 @@ class NativeAuthMsalController : BaseNativeAuthController() { "Parameters has password" ) val mergedScopes = addDefaultScopes(parameters.scopes) - var parametersWithScopes = CommandUtil.createSignInStartCommandParametersWithScopes( + val parametersWithScopes = CommandUtil.createSignInStartCommandParametersWithScopes( parameters, mergedScopes ) diff --git a/common/src/main/java/com/microsoft/identity/common/nativeauth/internal/util/CommandUtil.java b/common/src/main/java/com/microsoft/identity/common/nativeauth/internal/util/CommandUtil.java index 15ba7f9955..7454b245f8 100644 --- a/common/src/main/java/com/microsoft/identity/common/nativeauth/internal/util/CommandUtil.java +++ b/common/src/main/java/com/microsoft/identity/common/nativeauth/internal/util/CommandUtil.java @@ -138,6 +138,7 @@ public static SignInSubmitPasswordCommandParameters createSignInSubmitPasswordCo .password(parameters.getPassword()) .scopes(parameters.getScopes()) .correlationId(correlationId) + .claimsRequestJson(parameters.getClaimsRequestJson()) .challengeType(parameters.getChallengeType()) .build(); @@ -189,6 +190,7 @@ public static SignInSubmitCodeCommandParameters createSignInSubmitCodeCommandPar .scopes(parameters.getScopes()) .correlationId(parameters.getCorrelationId()) .challengeType(parameters.getChallengeType()) + .claimsRequestJson(parameters.claimsRequestJson) .build(); return commandParameters; diff --git a/common/src/test/java/com/microsoft/identity/common/nativeauth/internal/util/CommandUtilTest.kt b/common/src/test/java/com/microsoft/identity/common/nativeauth/internal/util/CommandUtilTest.kt new file mode 100644 index 0000000000..9106ae07bf --- /dev/null +++ b/common/src/test/java/com/microsoft/identity/common/nativeauth/internal/util/CommandUtilTest.kt @@ -0,0 +1,80 @@ +package com.microsoft.identity.common.nativeauth.internal.util + +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import com.microsoft.identity.common.components.AndroidPlatformComponentsFactory +import com.microsoft.identity.common.java.interfaces.IPlatformComponents +import com.microsoft.identity.common.java.nativeauth.commands.parameters.MFASubmitChallengeCommandParameters +import com.microsoft.identity.common.java.nativeauth.commands.parameters.SignInStartCommandParameters +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.MockitoAnnotations +import org.robolectric.RobolectricTestRunner +import java.util.UUID + +/** + * Tests for [CommandUtil]. + */ +@RunWith(RobolectricTestRunner::class) +class CommandUtilTest { + + private lateinit var platformComponents: IPlatformComponents + private lateinit var context: Context + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + context = ApplicationProvider.getApplicationContext() + platformComponents = AndroidPlatformComponentsFactory.createFromContext( + context + ) + } + + @Test + fun testCreateSignInSubmitPasswordCommandParameters_containsCorrectInfo() { + val correlationId = UUID.randomUUID().toString() + val continuationToken = "continuation" + val signInStartParams = SignInStartCommandParameters.builder() + .password("test".toCharArray()) + .claimsRequestJson("claimsRequestJson") + .clientId("clientId") + .challengeType(arrayListOf("OOB")) + .redirectUri("redirectUri") + .username("username") + .platformComponents(platformComponents) + .build() + val submitPasswordParams = CommandUtil.createSignInSubmitPasswordCommandParameters(signInStartParams, correlationId, continuationToken) + + assert(submitPasswordParams.getPassword().contentEquals(signInStartParams.getPassword())) + assert(submitPasswordParams.getContinuationToken().contentEquals(continuationToken)) + assert(submitPasswordParams.correlationId?.contentEquals(correlationId) == true) + assert(submitPasswordParams.getClaimsRequestJson().contentEquals(signInStartParams.getClaimsRequestJson())) + assert(submitPasswordParams.clientId?.contentEquals(signInStartParams.clientId) == true) + assert(submitPasswordParams.getChallengeType()?.equals(signInStartParams.getChallengeType()) == true) + assert(submitPasswordParams.redirectUri?.equals(signInStartParams.redirectUri) == true) + } + + @Test + fun testCreateSignInSubmitCodeCommandParameters_containsCorrectInfo() { + val mfaSubmitChallengeParams = MFASubmitChallengeCommandParameters.builder() + .claimsRequestJson("claimsRequestJson") + .clientId("clientId") + .challengeType(arrayListOf("OOB")) + .redirectUri("redirectUri") + .platformComponents(platformComponents) + .challenge("123456") + .continuationToken("continuationToken") + .correlationId(UUID.randomUUID().toString()) + .build() + val submitCodeParams = CommandUtil.createSignInSubmitCodeCommandParameters(mfaSubmitChallengeParams) + + assert(submitCodeParams.getContinuationToken().contentEquals(mfaSubmitChallengeParams.getContinuationToken())) + assert(submitCodeParams.correlationId?.contentEquals(mfaSubmitChallengeParams.correlationId) == true) + assert(submitCodeParams.getClaimsRequestJson().contentEquals(mfaSubmitChallengeParams.getClaimsRequestJson())) + assert(submitCodeParams.clientId?.contentEquals(mfaSubmitChallengeParams.clientId) == true) + assert(submitCodeParams.getChallengeType()?.equals(mfaSubmitChallengeParams.getChallengeType()) == true) + assert(submitCodeParams.getCode() == mfaSubmitChallengeParams.getChallenge()) + assert(submitCodeParams.redirectUri?.equals(mfaSubmitChallengeParams.redirectUri) == true) + } +} \ No newline at end of file diff --git a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/commands/parameters/BaseSignInTokenCommandParameters.java b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/commands/parameters/BaseSignInTokenCommandParameters.java index 8f2cc9c80f..91bacba78e 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/commands/parameters/BaseSignInTokenCommandParameters.java +++ b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/commands/parameters/BaseSignInTokenCommandParameters.java @@ -31,7 +31,7 @@ import lombok.experimental.SuperBuilder; /** - * BaseSignInTokenCommandParameters is the base class for parameters for all all Native Auth sign in related commands. + * BaseSignInTokenCommandParameters is the base class for parameters for all Native Auth sign in related commands. */ @Getter @EqualsAndHashCode(callSuper = true) diff --git a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/commands/parameters/MFADefaultChallengeCommandParameters.java b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/commands/parameters/MFADefaultChallengeCommandParameters.java index 0ed55984d0..38f1288364 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/commands/parameters/MFADefaultChallengeCommandParameters.java +++ b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/commands/parameters/MFADefaultChallengeCommandParameters.java @@ -22,6 +22,8 @@ //THE SOFTWARE. package com.microsoft.identity.common.java.nativeauth.commands.parameters; +import javax.annotation.Nullable; + import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -45,6 +47,12 @@ public class MFADefaultChallengeCommandParameters extends BaseSignInTokenCommand @NonNull public final String continuationToken; + /** + * Claims to send to the token endpoint. + */ + @Nullable + public final String claimsRequestJson; + @NonNull @Override public String toUnsanitizedString() { diff --git a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/commands/parameters/MFASubmitChallengeCommandParameters.java b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/commands/parameters/MFASubmitChallengeCommandParameters.java index 9e8f579a8f..a24233b45a 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/commands/parameters/MFASubmitChallengeCommandParameters.java +++ b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/commands/parameters/MFASubmitChallengeCommandParameters.java @@ -22,6 +22,8 @@ // THE SOFTWARE. package com.microsoft.identity.common.java.nativeauth.commands.parameters; +import javax.annotation.Nullable; + import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NonNull; @@ -49,6 +51,12 @@ public class MFASubmitChallengeCommandParameters extends BaseSignInTokenCommandP @NonNull public final String continuationToken; + /** + * Claims to send to the token endpoint. + */ + @Nullable + public final String claimsRequestJson; + @NonNull @Override public String toUnsanitizedString() { diff --git a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/commands/parameters/SignInStartCommandParameters.java b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/commands/parameters/SignInStartCommandParameters.java index 0188b2dbdd..dacbf5e440 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/commands/parameters/SignInStartCommandParameters.java +++ b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/commands/parameters/SignInStartCommandParameters.java @@ -54,6 +54,12 @@ public class SignInStartCommandParameters extends BaseSignInTokenCommandParamete @Nullable public final char[] password; + /** + * Claims to send to the token endpoint. + */ + @Nullable + public final String claimsRequestJson; + @NonNull @Override public String toUnsanitizedString() { diff --git a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/commands/parameters/SignInSubmitCodeCommandParameters.java b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/commands/parameters/SignInSubmitCodeCommandParameters.java index c22f7d679c..3d36a50f40 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/commands/parameters/SignInSubmitCodeCommandParameters.java +++ b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/commands/parameters/SignInSubmitCodeCommandParameters.java @@ -22,6 +22,8 @@ // THE SOFTWARE. package com.microsoft.identity.common.java.nativeauth.commands.parameters; +import javax.annotation.Nullable; + import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NonNull; @@ -49,6 +51,12 @@ public class SignInSubmitCodeCommandParameters extends BaseSignInTokenCommandPar @NonNull public final String continuationToken; + /** + * Claims to send to the token endpoint. + */ + @Nullable + public final String claimsRequestJson; + @NonNull @Override public String toUnsanitizedString() { diff --git a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/commands/parameters/SignInSubmitPasswordCommandParameters.java b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/commands/parameters/SignInSubmitPasswordCommandParameters.java index 2077995540..4751f93120 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/commands/parameters/SignInSubmitPasswordCommandParameters.java +++ b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/commands/parameters/SignInSubmitPasswordCommandParameters.java @@ -22,6 +22,8 @@ // THE SOFTWARE. package com.microsoft.identity.common.java.nativeauth.commands.parameters; +import javax.annotation.Nullable; + import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -51,6 +53,12 @@ public class SignInSubmitPasswordCommandParameters extends BaseSignInTokenComman @NonNull public final String continuationToken; + /** + * Claims to send to the token endpoint. + */ + @Nullable + public final String claimsRequestJson; + @NonNull @Override public String toUnsanitizedString() { 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 f8d81614b4..a0e87dc229 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 @@ -162,7 +162,8 @@ class NativeAuthRequestProvider(private val config: NativeAuthOAuth2Configuratio clientId = config.clientId, challengeType = config.challengeType, requestUrl = signInTokenEndpoint, - headers = getRequestHeaders(commandParameters.getCorrelationId()) + headers = getRequestHeaders(commandParameters.getCorrelationId()), + claimsRequestJson = commandParameters.claimsRequestJson ) } @@ -198,7 +199,8 @@ class NativeAuthRequestProvider(private val config: NativeAuthOAuth2Configuratio clientId = config.clientId, challengeType = config.challengeType, requestUrl = signInTokenEndpoint, - headers = getRequestHeaders(commandParameters.getCorrelationId()) + headers = getRequestHeaders(commandParameters.getCorrelationId()), + claimsRequestJson = commandParameters.claimsRequestJson ) } //endregion diff --git a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/requests/signin/SignInTokenRequest.kt b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/requests/signin/SignInTokenRequest.kt index 6e20334d95..39e49cae76 100644 --- a/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/requests/signin/SignInTokenRequest.kt +++ b/common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/requests/signin/SignInTokenRequest.kt @@ -56,7 +56,8 @@ data class SignInTokenRequest private constructor( scopes: List? = null, challengeType: String? = null, requestUrl: String, - headers: Map + headers: Map, + claimsRequestJson: String? ): SignInTokenRequest { // Check for empty Strings and empty Maps ArgUtils.validateNonNullArg(oob, "oob") @@ -74,7 +75,8 @@ data class SignInTokenRequest private constructor( clientId = clientId, grantType = NativeAuthConstants.GrantType.OOB, challengeType = challengeType, - scope = scopes?.joinToString(" ") + scope = scopes?.joinToString(" "), + claimsRequestJson = claimsRequestJson ), requestUrl = URL(requestUrl), headers = headers, @@ -96,7 +98,8 @@ data class SignInTokenRequest private constructor( scopes: List? = null, challengeType: String? = null, requestUrl: String, - headers: Map + headers: Map, + claimsRequestJson: String? ): SignInTokenRequest { // Check for empty Strings and empty Maps ArgUtils.validateNonNullArg(password, "password") @@ -106,7 +109,6 @@ data class SignInTokenRequest private constructor( ArgUtils.validateNonNullArg(requestUrl, "requestUrl") ArgUtils.validateNonNullArg(headers, "headers") - return SignInTokenRequest( parameters = NativeAuthRequestSignInTokenRequestParameters( password = password, @@ -114,7 +116,8 @@ data class SignInTokenRequest private constructor( clientId = clientId, grantType = NativeAuthConstants.GrantType.PASSWORD, challengeType = challengeType, - scope = scopes?.joinToString(" ") + scope = scopes?.joinToString(" "), + claimsRequestJson = claimsRequestJson ), requestUrl = URL(requestUrl), headers = headers, @@ -153,7 +156,8 @@ data class SignInTokenRequest private constructor( username = username, grantType = NativeAuthConstants.GrantType.CONTINUATION_TOKEN, challengeType = challengeType, - scope = scopes?.joinToString(" ") + scope = scopes?.joinToString(" "), + claimsRequestJson = null ), requestUrl = URL(requestUrl), headers = headers @@ -175,7 +179,8 @@ data class SignInTokenRequest private constructor( @SerializedName("grant_type") val grantType: String, @SerializedName("continuation_token") val continuationToken: String? = null, @SerializedName("scope") val scope: String?, - @SerializedName("challenge_type") val challengeType: String? + @SerializedName("challenge_type") val challengeType: String?, + @SerializedName("claims") val claimsRequestJson: String? ) : NativeAuthRequestParameters() { override fun toUnsanitizedString(): String = "NativeAuthRequestSignInTokenRequestParameters(nca=$nca, clientInfo=$clientInfo, clientId=$clientId, grantType=$grantType, scope=$scope, challengeType=$challengeType)" 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 ac591d8709..9f24ae8b82 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 @@ -37,6 +37,7 @@ import com.microsoft.identity.common.java.nativeauth.commands.parameters.SignUpS import com.microsoft.identity.common.java.exception.ClientException import com.microsoft.identity.common.java.interfaces.PlatformComponents import com.microsoft.identity.common.java.nativeauth.providers.requests.NativeAuthRequest.Companion.toJsonString +import com.microsoft.identity.common.java.nativeauth.providers.requests.signin.SignInTokenRequest import com.microsoft.identity.common.nativeauth.ApiConstants import io.mockk.every import io.mockk.mockk @@ -778,6 +779,69 @@ class NativeAuthRequestProviderTest { ) } + @Test + fun testSignInTokenOOBShouldContainsCorrectParams() { + val commandParameters = SignInSubmitCodeCommandParameters.builder() + .platformComponents(mock()) + .continuationToken(continuationToken) + .correlationId(correlationId) + .code("code") + .claimsRequestJson("claims") + .build() + + val request = nativeAuthRequestProvider.createOOBTokenRequest( + commandParameters = commandParameters + ) + assertEquals(request.parameters.oob, commandParameters.code) + assertEquals(request.parameters.claimsRequestJson, commandParameters.claimsRequestJson) + assertEquals(request.parameters.continuationToken, commandParameters.continuationToken) + assertEquals(request.parameters.challengeType, mockConfig.challengeType) + assertNull(request.parameters.scope) + } + + @Test + fun testSignInTokenPasswordShouldContainsCorrectParams() { + val commandParameters = SignInSubmitPasswordCommandParameters.builder() + .platformComponents(mock()) + .continuationToken(continuationToken) + .correlationId(correlationId) + .password("pwd".toCharArray()) + .claimsRequestJson("claims") + .build() + + val request = nativeAuthRequestProvider.createPasswordTokenRequest( + commandParameters + ) + assertEquals(request.parameters.password, commandParameters.password) + assertEquals(request.parameters.claimsRequestJson, commandParameters.claimsRequestJson) + assertEquals(request.parameters.continuationToken, commandParameters.continuationToken) + assertEquals(request.parameters.challengeType, mockConfig.challengeType) + assertNull(request.parameters.scope) + } + + @Test + fun testSignInTokenContinuationShouldContainsCorrectParams() { + val scopes = arrayListOf("OOB", "PASSWORD") + val headers = mapOf("key" to "value") + val request = SignInTokenRequest.createContinuationTokenRequest( + continuationToken, + clientId, + username, + scopes, + challengeType, + ApiConstants.MockApi.signInTokenRequestUrl.toString(), + headers + ) + assertNull(request.parameters.password) + assertNull(request.parameters.claimsRequestJson) + assertEquals(request.parameters.scope, "OOB PASSWORD") + assertEquals(request.parameters.continuationToken, continuationToken) + assertEquals(request.parameters.challengeType, challengeType) + assertEquals(request.parameters.username, username) + assertEquals(request.parameters.clientId, clientId) + assertEquals(request.headers, headers) + } + @Test(expected = ClientException::class) fun testPasswordTokenRequestWithEmptyPasswordShouldThrowException() { val commandParameters = SignInSubmitPasswordCommandParameters.builder()