diff --git a/changelog b/changelog index 3ccb30317..567071b91 100644 --- a/changelog +++ b/changelog @@ -3,6 +3,7 @@ MSAL Wiki : https://github.com/AzureAD/microsoft-authentication-library-for-andr vNext ---------- - [MINOR] Move native auth public methods to parameter class (#2245) +- [MINOR] Add support for claims requests for native auth sign in (#2246) Version 5.9.0 ---------- diff --git a/common b/common index bb5aaaa55..223ac0cbb 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit bb5aaaa5583124a8a217a015e7736cef7673226b +Subproject commit 223ac0cbb7d740876e51a21a9b8856e2349c3a7b diff --git a/msal/src/main/java/com/microsoft/identity/client/internal/CommandParametersAdapter.java b/msal/src/main/java/com/microsoft/identity/client/internal/CommandParametersAdapter.java index 0afebbb0f..7038cd60f 100644 --- a/msal/src/main/java/com/microsoft/identity/client/internal/CommandParametersAdapter.java +++ b/msal/src/main/java/com/microsoft/identity/client/internal/CommandParametersAdapter.java @@ -539,7 +539,8 @@ public static SignInStartCommandParameters createSignInStartCommandParameters( @NonNull final OAuth2TokenCache tokenCache, @NonNull final String username, @Nullable final char[] password, - final List scopes) throws ClientException { + final List scopes, + @Nullable final ClaimsRequest claimsRequest) throws ClientException { final AbstractAuthenticationScheme authenticationScheme = AuthenticationSchemeFactory.createScheme( AndroidPlatformComponentsFactory.createFromContext(configuration.getAppContext()), null @@ -547,6 +548,8 @@ public static SignInStartCommandParameters createSignInStartCommandParameters( final NativeAuthCIAMAuthority authority = ((NativeAuthCIAMAuthority) configuration.getDefaultAuthority()); + final String claimsRequestJson = ClaimsRequest.getJsonStringFromClaimsRequest(claimsRequest); + final SignInStartCommandParameters commandParameters = SignInStartCommandParameters.builder() .platformComponents(AndroidPlatformComponentsFactory.createFromContext(configuration.getAppContext())) .applicationName(configuration.getAppContext().getPackageName()) @@ -565,6 +568,7 @@ public static SignInStartCommandParameters createSignInStartCommandParameters( .authenticationScheme(authenticationScheme) .clientId(configuration.getClientId()) .challengeType(configuration.getChallengeTypes()) + .claimsRequestJson(claimsRequestJson) .scopes(scopes) // Start of the flow, so there is no correlation ID to use from a previous API response. // Set it to a default value. @@ -640,7 +644,8 @@ public static SignInSubmitCodeCommandParameters createSignInSubmitCodeCommandPar @NonNull final String code, @NonNull final String continuationToken, @NonNull final String correlationId, - final List scopes) throws ClientException { + final List scopes, + @Nullable final String claimsRequestJson) throws ClientException { final NativeAuthCIAMAuthority authority = ((NativeAuthCIAMAuthority) configuration.getDefaultAuthority()); @@ -668,6 +673,7 @@ public static SignInSubmitCodeCommandParameters createSignInSubmitCodeCommandPar .code(code) .scopes(scopes) .correlationId(correlationId) + .claimsRequestJson(claimsRequestJson) .build(); return commandParameters; @@ -729,7 +735,8 @@ public static SignInSubmitPasswordCommandParameters createSignInSubmitPasswordCo @NonNull final String continuationToken, @NonNull final char[] password, @NonNull final String correlationId, - final List scopes) throws ClientException { + final List scopes, + @Nullable final String claimsRequestJson) throws ClientException { final NativeAuthCIAMAuthority authority = ((NativeAuthCIAMAuthority) configuration.getDefaultAuthority()); @@ -758,6 +765,7 @@ public static SignInSubmitPasswordCommandParameters createSignInSubmitPasswordCo .scopes(scopes) .challengeType(configuration.getChallengeTypes()) .correlationId(correlationId) + .claimsRequestJson(claimsRequestJson) .build(); return commandParameters; diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/NativeAuthPublicClientApplication.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/NativeAuthPublicClientApplication.kt index fb176b500..d2514968f 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/NativeAuthPublicClientApplication.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/NativeAuthPublicClientApplication.kt @@ -28,6 +28,7 @@ import com.microsoft.identity.client.AccountAdapter import com.microsoft.identity.client.AuthenticationResultAdapter import com.microsoft.identity.client.IAccount import com.microsoft.identity.client.PublicClientApplication +import com.microsoft.identity.client.claims.ClaimsRequest import com.microsoft.identity.client.exception.MsalClientException import com.microsoft.identity.client.exception.MsalException import com.microsoft.identity.client.internal.CommandParametersAdapter @@ -309,7 +310,7 @@ class NativeAuthPublicClientApplication( ) pcaScope.launch { try { - val result = internalSignIn(username, password, scopes) + val result = internalSignIn(username, password, scopes, claimsRequest = null) callback.onResult(result) } catch (e: MsalException) { Logger.error(TAG, "Exception thrown in signIn", e) @@ -333,7 +334,7 @@ class NativeAuthPublicClientApplication( ) pcaScope.launch { try { - val result = internalSignIn(parameters.username, parameters.password, parameters.scopes) + val result = internalSignIn(parameters.username, parameters.password, parameters.scopes, parameters.claimsRequest) callback.onResult(result) } catch (e: MsalException) { Logger.error(TAG, "Exception thrown in signIn", e) @@ -362,7 +363,7 @@ class NativeAuthPublicClientApplication( correlationId = null, methodName = "${TAG}.signIn(username: String, password: CharArray?, scopes: List?)" ) - return internalSignIn(username, password, scopes) + return internalSignIn(username, password, scopes, claimsRequest = null) } /** @@ -378,7 +379,7 @@ class NativeAuthPublicClientApplication( correlationId = null, methodName = "${TAG}.signIn(parameters: NativeAuthSignInParameters)" ) - return internalSignIn(parameters.username, parameters.password, parameters.scopes) + return internalSignIn(parameters.username, parameters.password, parameters.scopes, parameters.claimsRequest) } @@ -605,7 +606,8 @@ class NativeAuthPublicClientApplication( private suspend fun internalSignIn( username: String, password: CharArray?, - scopes: List? + scopes: List?, + claimsRequest: ClaimsRequest? ): SignInResult { return withContext(Dispatchers.IO) { try { @@ -627,13 +629,14 @@ class NativeAuthPublicClientApplication( nativeAuthConfig.oAuth2TokenCache, username, password, - scopes + scopes, + claimsRequest ) val command = SignInStartCommand( params, NativeAuthMsalController(), - PublicApiId.NATIVE_AUTH_SIGN_IN_WITH_EMAIL + if (hasPassword) PublicApiId.NATIVE_AUTH_SIGN_IN_WITH_EMAIL_PASSWORD else PublicApiId.NATIVE_AUTH_SIGN_IN_WITH_EMAIL ) val rawCommandResult = CommandDispatcher.submitSilentReturningFuture(command).get() @@ -679,7 +682,8 @@ class NativeAuthPublicClientApplication( continuationToken = result.continuationToken, correlationId = result.correlationId, scopes = scopes, - config = nativeAuthConfig + config = nativeAuthConfig, + claimsRequestJson = params.claimsRequestJson ), codeLength = result.codeLength, sentTo = result.challengeTargetLabel, @@ -715,7 +719,8 @@ class NativeAuthPublicClientApplication( continuationToken = result.continuationToken, correlationId = result.correlationId, scopes = scopes, - config = nativeAuthConfig + config = nativeAuthConfig, + claimsRequestJson = params.claimsRequestJson ) ) } diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/parameters/NativeAuthSignInParameters.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/parameters/NativeAuthSignInParameters.kt index 9badc3eb8..fa314a443 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/parameters/NativeAuthSignInParameters.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/parameters/NativeAuthSignInParameters.kt @@ -23,12 +23,14 @@ package com.microsoft.identity.nativeauth.parameters +import com.microsoft.identity.client.claims.ClaimsRequest + /** * Encapsulates the parameters passed to the signIn methods of NativeAuthPublicClientApplication */ class NativeAuthSignInParameters( /** - * username of the account to sign in. + * username of the account to sign in */ val username: String ) { @@ -43,4 +45,9 @@ class NativeAuthSignInParameters( * Not all scopes are guaranteed to be included in the access token returned. */ var scopes: List? = null + + /** + * The claims parameter that needs to be sent to the service. + */ + var claimsRequest: ClaimsRequest? = null } \ No newline at end of file diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/SignInStates.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/SignInStates.kt index e914aa7e5..f4c16a1e6 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/SignInStates.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/SignInStates.kt @@ -76,6 +76,7 @@ class SignInCodeRequiredState internal constructor( override val continuationToken: String, override val correlationId: String, private val scopes: List?, + private val claimsRequestJson: String?, private val config: NativeAuthPublicClientApplicationConfiguration ) : BaseState(continuationToken = continuationToken, correlationId = correlationId), State, Parcelable { private val TAG: String = SignInCodeRequiredState::class.java.simpleName @@ -84,6 +85,7 @@ class SignInCodeRequiredState internal constructor( continuationToken = parcel.readString() ?: "", correlationId = parcel.readString() ?: "UNSET", scopes = parcel.createStringArrayList(), + claimsRequestJson = parcel.readString(), config = parcel.serializable() as NativeAuthPublicClientApplicationConfiguration ) @@ -136,7 +138,8 @@ class SignInCodeRequiredState internal constructor( code, continuationToken, correlationId, - scopes + scopes, + claimsRequestJson ) val signInSubmitCodeCommand = SignInSubmitCodeCommand( @@ -274,7 +277,8 @@ class SignInCodeRequiredState internal constructor( continuationToken = result.continuationToken, correlationId = result.correlationId, scopes = scopes, - config = config + config = config, + claimsRequestJson = claimsRequestJson ), codeLength = result.codeLength, sentTo = result.challengeTargetLabel, @@ -344,6 +348,7 @@ class SignInPasswordRequiredState( override val continuationToken: String, override val correlationId: String, private val scopes: List?, + private val claimsRequestJson: String?, private val config: NativeAuthPublicClientApplicationConfiguration ) : BaseState(continuationToken = continuationToken, correlationId = correlationId), State, Parcelable { private val TAG: String = SignInPasswordRequiredState::class.java.simpleName @@ -351,6 +356,7 @@ class SignInPasswordRequiredState( continuationToken = parcel.readString() ?: "", correlationId = parcel.readString() ?: "UNSET", scopes = parcel.createStringArrayList(), + claimsRequestJson = parcel.readString(), config = parcel.serializable() as NativeAuthPublicClientApplicationConfiguration ) @@ -403,7 +409,8 @@ class SignInPasswordRequiredState( continuationToken, password, correlationId, - scopes + scopes, + claimsRequestJson ) try { @@ -483,6 +490,7 @@ class SignInPasswordRequiredState( parcel.writeString(correlationId) parcel.writeStringList(scopes) parcel.writeSerializable(config) + parcel.writeString(claimsRequestJson) } override fun describeContents(): Int { diff --git a/msal/src/test/java/com/microsoft/identity/client/CommandParametersTest.java b/msal/src/test/java/com/microsoft/identity/client/CommandParametersTest.java index 499f006e1..590200187 100644 --- a/msal/src/test/java/com/microsoft/identity/client/CommandParametersTest.java +++ b/msal/src/test/java/com/microsoft/identity/client/CommandParametersTest.java @@ -41,9 +41,15 @@ import com.microsoft.identity.common.java.commands.parameters.InteractiveTokenCommandParameters; import com.microsoft.identity.common.java.commands.parameters.SilentTokenCommandParameters; import com.microsoft.identity.common.java.constants.FidoConstants; +import com.microsoft.identity.common.java.nativeauth.commands.parameters.SignInStartCommandParameters; +import com.microsoft.identity.common.java.nativeauth.commands.parameters.SignInSubmitCodeCommandParameters; +import com.microsoft.identity.common.java.nativeauth.commands.parameters.SignInSubmitPasswordCommandParameters; import com.microsoft.identity.common.java.providers.oauth2.OAuth2TokenCache; import com.microsoft.identity.common.java.exception.ClientException; import com.microsoft.identity.common.java.ui.PreferredAuthMethod; +import com.microsoft.identity.msal.R; +import com.microsoft.identity.nativeauth.NativeAuthPublicClientApplicationConfiguration; +import com.microsoft.identity.nativeauth.NativeAuthPublicClientApplicationConfigurationFactory; import org.junit.Assert; import org.junit.Before; @@ -358,6 +364,96 @@ public void testAppendToExtraQueryParametersIfWebAuthnCapable_WebAuthnCapableFal Assert.assertEquals(combinedQueryParameters.size(), 1); } + @Test + public void testCreateSignInStartCommandParameters_CommandParamsContainsExpectedParams() throws ClientException { + List challengeTypes = new ArrayList<>(Collections.singletonList("OOB")); + String username = "username"; + char[] pwd = "example".toCharArray(); + List scopes = new ArrayList<>(Collections.singletonList("User.Read")); + ClaimsRequest claimsRequest = ClaimsRequest.getClaimsRequestFromJsonString("{\"access_token\":{\"acrs\":{\"essential\":true,\"value\":\"c4\"}}}"); + NativeAuthPublicClientApplicationConfiguration configuration = new NativeAuthPublicClientApplicationConfiguration(); + configuration.setChallengeTypes(challengeTypes); + configuration.setClientId("clientId"); + configuration.setAppContext(mContext); + configuration.setPowerOptCheckEnabled(false); + + final SignInStartCommandParameters commandParameters = CommandParametersAdapter.createSignInStartCommandParameters( + configuration, + null, + username, + pwd, + scopes, + claimsRequest + ); + Assert.assertEquals(commandParameters.claimsRequestJson, ClaimsRequest.getJsonStringFromClaimsRequest(claimsRequest)); + Assert.assertEquals(commandParameters.password, pwd); + Assert.assertEquals(commandParameters.username, username); + Assert.assertEquals(commandParameters.scopes, scopes); + Assert.assertEquals(commandParameters.challengeType, challengeTypes); + } + + @Test + public void createSignInSubmitCodeCommandParameters_CommandParamsContainsExpectedParams() throws ClientException { + List challengeTypes = new ArrayList<>(Collections.singletonList("OOB")); + String code = "123456"; + String continuationToken = "continuationToken"; + String correlationId = UUID.randomUUID().toString(); + List scopes = new ArrayList<>(Collections.singletonList("User.Read")); + String claimsRequestJson = "{\"access_token\":{\"acrs\":{\"essential\":true,\"value\":\"c4\"}}}"; + NativeAuthPublicClientApplicationConfiguration configuration = new NativeAuthPublicClientApplicationConfiguration(); + configuration.setChallengeTypes(challengeTypes); + configuration.setClientId("clientId"); + configuration.setAppContext(mContext); + configuration.setPowerOptCheckEnabled(false); + + final SignInSubmitCodeCommandParameters commandParameters = CommandParametersAdapter.createSignInSubmitCodeCommandParameters( + configuration, + null, + code, + continuationToken, + correlationId, + scopes, + claimsRequestJson + ); + Assert.assertEquals(commandParameters.claimsRequestJson, claimsRequestJson); + Assert.assertEquals(commandParameters.code, code); + Assert.assertEquals(commandParameters.continuationToken, continuationToken); + Assert.assertEquals(commandParameters.scopes, scopes); + Assert.assertEquals(commandParameters.challengeType, challengeTypes); + Assert.assertEquals(commandParameters.getCorrelationId(), correlationId); + } + + @Test + public void createSignInSubmitPasswordCommandParameters_CommandParamsContainsExpectedParams() throws ClientException { + List challengeTypes = new ArrayList<>(Collections.singletonList("OOB")); + char[] pwd = "example".toCharArray(); + String continuationToken = "continuationToken"; + String correlationId = UUID.randomUUID().toString(); + List scopes = new ArrayList<>(Collections.singletonList("User.Read")); + String claimsRequestJson = "{\"access_token\":{\"acrs\":{\"essential\":true,\"value\":\"c4\"}}}"; + NativeAuthPublicClientApplicationConfiguration configuration = new NativeAuthPublicClientApplicationConfiguration(); + configuration.setChallengeTypes(challengeTypes); + configuration.setClientId("clientId"); + configuration.setAppContext(mContext); + configuration.setPowerOptCheckEnabled(false); + + final SignInSubmitPasswordCommandParameters commandParameters = CommandParametersAdapter.createSignInSubmitPasswordCommandParameters( + configuration, + null, + continuationToken, + pwd, + correlationId, + scopes, + claimsRequestJson + ); + Assert.assertEquals(commandParameters.claimsRequestJson, claimsRequestJson); + Assert.assertEquals(commandParameters.password, pwd); + Assert.assertEquals(commandParameters.continuationToken, continuationToken); + Assert.assertEquals(commandParameters.scopes, scopes); + Assert.assertEquals(commandParameters.challengeType, challengeTypes); + Assert.assertEquals(commandParameters.getCorrelationId(), correlationId); + } + private ClaimsRequest getAccessTokenClaimsRequest(@NonNull String claimName, @NonNull String claimValue) { ClaimsRequest cp1ClaimsRequest = new ClaimsRequest(); RequestedClaimAdditionalInformation info = new RequestedClaimAdditionalInformation();