diff --git a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/AcquireTokenSilentIT.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/AcquireTokenSilentIT.java index 070ee6b2..56d5f7d5 100644 --- a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/AcquireTokenSilentIT.java +++ b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/AcquireTokenSilentIT.java @@ -9,10 +9,7 @@ import org.testng.annotations.Test; import java.net.MalformedURLException; -import java.util.Collections; -import java.util.Date; -import java.util.HashSet; -import java.util.Set; +import java.util.*; import java.util.concurrent.ExecutionException; import static com.microsoft.aad.msal4j.TestConstants.KEYVAULT_DEFAULT_SCOPE; @@ -178,9 +175,12 @@ public void acquireTokenSilent_ConfidentialClient_acquireTokenSilent(String envi cfg = new Config(environment); IConfidentialClientApplication cca = getConfidentialClientApplications(); - + //test that adding extra query parameters does not break the flow + Map extraParameters = new HashMap<>(); + extraParameters.put("test","test"); IAuthenticationResult result = cca.acquireToken(ClientCredentialParameters .builder(Collections.singleton(cfg.graphDefaultScope())) + .extraQueryParameters(extraParameters) .build()) .get(); @@ -191,6 +191,7 @@ public void acquireTokenSilent_ConfidentialClient_acquireTokenSilent(String envi result = cca.acquireTokenSilently(SilentParameters .builder(Collections.singleton(cfg.graphDefaultScope())) + .extraQueryParameters(extraParameters) .build()) .get(); @@ -401,10 +402,13 @@ private IAuthenticationResult acquireTokenSilently(IPublicClientApplication pca, } private IAuthenticationResult acquireTokenUsernamePassword(User user, IPublicClientApplication pca, String scope) throws InterruptedException, ExecutionException { + Map map = new HashMap<>(); + map.put("test","test"); return pca.acquireToken(UserNamePasswordParameters. builder(Collections.singleton(scope), user.getUpn(), user.getPassword().toCharArray()) + .extraQueryParameters(map) .build()) .get(); } diff --git a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/AuthorizationCodeIT.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/AuthorizationCodeIT.java index 80058c8e..26bbe6d3 100644 --- a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/AuthorizationCodeIT.java +++ b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/AuthorizationCodeIT.java @@ -229,6 +229,7 @@ private IAuthenticationResult acquireTokenInteractiveB2C(ConfidentialClientAppli result = cca.acquireToken(AuthorizationCodeParameters .builder(authCode, new URI(TestConstants.LOCALHOST + httpListener.port())) .scopes(Collections.singleton(TestConstants.B2C_LAB_SCOPE)) + .extraQueryParameters(new HashMap<>()) .build()) .get(); } catch (Exception e) { diff --git a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/ConfidentialClientApplicationUnitT.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/ConfidentialClientApplicationUnitT.java index cef021f7..66d35c27 100644 --- a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/ConfidentialClientApplicationUnitT.java +++ b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/ConfidentialClientApplicationUnitT.java @@ -31,7 +31,6 @@ import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; -import java.util.function.Function; import static com.microsoft.aad.msal4j.TestConstants.KEYVAULT_DEFAULT_SCOPE; import static org.easymock.EasyMock.*; @@ -261,6 +260,8 @@ public void testClientAssertion_acquireToken() throws Exception{ Assert.assertTrue(body.contains("client_assertion_type=" + URLEncoder.encode(JWTAuthentication.CLIENT_ASSERTION_TYPE, "utf-8"))); Assert.assertTrue(body.contains("scope=" + URLEncoder.encode("openid profile offline_access " + scope, "utf-8"))); Assert.assertTrue(body.contains("client_id=" + TestConfiguration.AAD_CLIENT_ID)); + Assert.assertTrue(body.contains("test=test")); + Assert.assertTrue(body.contains("id_token_hint=token_hint_value")); } private ServiceBundle mockedServiceBundle(IHttpClient httpClientMock) { @@ -274,7 +275,15 @@ private ServiceBundle mockedServiceBundle(IHttpClient httpClientMock) { private ClientCredentialRequest getClientCredentialRequest(ConfidentialClientApplication app, String scope) { Set scopes = new HashSet<>(); scopes.add(scope); - ClientCredentialParameters clientCredentials = ClientCredentialParameters.builder(scopes).tenant(IdToken.TENANT_IDENTIFIER).build(); + + Map extraQueryParameters = new HashMap<>(); + extraQueryParameters.put("id_token_hint", "token_hint_value"); + extraQueryParameters.put("test", "test"); + + ClientCredentialParameters clientCredentials = ClientCredentialParameters.builder(scopes) + .tenant(IdToken.TENANT_IDENTIFIER) + .extraQueryParameters(extraQueryParameters) + .build(); RequestContext requestContext = new RequestContext( app, PublicApi.ACQUIRE_TOKEN_FOR_CLIENT, diff --git a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/TokenCacheIT.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/TokenCacheIT.java index 66bd6f90..70da5288 100644 --- a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/TokenCacheIT.java +++ b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/TokenCacheIT.java @@ -9,6 +9,8 @@ import org.testng.annotations.Test; import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.Set; public class TokenCacheIT { @@ -32,10 +34,14 @@ public void singleAccountInCache_RemoveAccountTest() throws Exception { // Check that cache is empty Assert.assertEquals(pca.getAccounts().join().size(), 0); + Map extraQueryParameters = new HashMap<>(); + extraQueryParameters.put("test", "test"); + pca.acquireToken(UserNamePasswordParameters. builder(Collections.singleton(TestConstants.GRAPH_DEFAULT_SCOPE), user.getUpn(), user.getPassword().toCharArray()) + .extraQueryParameters(extraQueryParameters) .build()) .get(); diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthorizationCodeParameters.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthorizationCodeParameters.java index 85d2fc3c..73a1b0c3 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthorizationCodeParameters.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthorizationCodeParameters.java @@ -57,6 +57,11 @@ public class AuthorizationCodeParameters implements IAcquireTokenParameters { */ private Map extraHttpHeaders; + /** + * Adds additional query parameters to the token request + */ + private Map extraQueryParameters; + /** * Overrides the tenant value in the authority URL for this request */ diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthorizationRequestUrlParameters.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthorizationRequestUrlParameters.java index c52c0eb6..da1feccc 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthorizationRequestUrlParameters.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthorizationRequestUrlParameters.java @@ -7,6 +7,8 @@ import lombok.Getter; import lombok.NonNull; import lombok.experimental.Accessors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.net.MalformedURLException; import java.net.URL; @@ -37,8 +39,12 @@ public class AuthorizationRequestUrlParameters { //Unlike other prompts (which are sent as query parameters), admin consent has its own endpoint format private static final String ADMIN_CONSENT_ENDPOINT = "https://login.microsoftonline.com/{tenant}/adminconsent"; + Map extraQueryParameters; + Map> requestParameters = new HashMap<>(); + Logger log = LoggerFactory.getLogger(AuthorizationRequestUrlParameters.class); + public static Builder builder(String redirectUri, Set scopes) { @@ -152,6 +158,18 @@ private AuthorizationRequestUrlParameters(Builder builder) { this.instanceAware = builder.instanceAware; requestParameters.put("instance_aware", Collections.singletonList(String.valueOf(instanceAware))); } + + if(null != builder.extraQueryParameters && !builder.extraQueryParameters.isEmpty()){ + this.extraQueryParameters = builder.extraQueryParameters; + for(Map.Entry entry: this.extraQueryParameters.entrySet()){ + String key = entry.getKey(); + String value = entry.getValue(); + if(requestParameters.containsKey(key)){ + log.warn("A query parameter {} has been provided with values multiple times.", key); + } + requestParameters.put(key, Collections.singletonList(value)); + } + } } URL createAuthorizationURL(Authority authority, @@ -194,6 +212,7 @@ public static class Builder { private Prompt prompt; private String correlationId; private boolean instanceAware; + private Map extraQueryParameters; public AuthorizationRequestUrlParameters build() { return new AuthorizationRequestUrlParameters(this); @@ -340,5 +359,14 @@ public Builder instanceAware(boolean val) { this.instanceAware = val; return self(); } + + /** + * Query parameters that you can add to the request, + * in addition to the list of parameters already provided. + */ + public Builder extraQueryParameters(Map val) { + this.extraQueryParameters = val; + return self(); + } } } diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/ClientCredentialParameters.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/ClientCredentialParameters.java index 367516c0..440c5e08 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/ClientCredentialParameters.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/ClientCredentialParameters.java @@ -44,6 +44,11 @@ public class ClientCredentialParameters implements IAcquireTokenParameters { */ private Map extraHttpHeaders; + /** + * Adds additional query parameters to the token request + */ + private Map extraQueryParameters; + /** * Overrides the tenant value in the authority URL for this request */ diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/DeviceCodeFlowParameters.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/DeviceCodeFlowParameters.java index daede3d7..63f9c8e3 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/DeviceCodeFlowParameters.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/DeviceCodeFlowParameters.java @@ -49,6 +49,11 @@ public class DeviceCodeFlowParameters implements IAcquireTokenParameters { */ private Map extraHttpHeaders; + /** + * Adds additional query parameters to the token request + */ + private Map extraQueryParameters; + /** * Overrides the tenant value in the authority URL for this request */ diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IAcquireTokenParameters.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IAcquireTokenParameters.java index d226ed3f..f79219f9 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IAcquireTokenParameters.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IAcquireTokenParameters.java @@ -16,4 +16,6 @@ interface IAcquireTokenParameters { Map extraHttpHeaders(); String tenant(); + + Map extraQueryParameters(); } diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IntegratedWindowsAuthenticationParameters.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IntegratedWindowsAuthenticationParameters.java index bd245fd3..cee5865d 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IntegratedWindowsAuthenticationParameters.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IntegratedWindowsAuthenticationParameters.java @@ -46,6 +46,11 @@ public class IntegratedWindowsAuthenticationParameters implements IAcquireTokenP */ private Map extraHttpHeaders; + /** + * Adds additional parameters to the token request + */ + private Map extraQueryParameters; + /** * Overrides the tenant value in the authority URL for this request */ diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/InteractiveRequest.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/InteractiveRequest.java index 2c024bb9..93a6b462 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/InteractiveRequest.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/InteractiveRequest.java @@ -87,11 +87,15 @@ private URL createAuthorizationUrl() { .loginHint(interactiveRequestParameters.loginHint()) .domainHint(interactiveRequestParameters.domainHint()) .correlationId(publicClientApplication.correlationId()) - .instanceAware(interactiveRequestParameters.instanceAware()); + .instanceAware(interactiveRequestParameters.instanceAware()) + .extraQueryParameters(interactiveRequestParameters.extraQueryParameters()); addPkceAndState(authorizationRequestUrlBuilder); + AuthorizationRequestUrlParameters authorizationRequestUrlParameters = + authorizationRequestUrlBuilder.build(); + return publicClientApplication.getAuthorizationRequestUrl( - authorizationRequestUrlBuilder.build()); + authorizationRequestUrlParameters); } private void addPkceAndState(AuthorizationRequestUrlParameters.Builder builder) { diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/InteractiveRequestParameters.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/InteractiveRequestParameters.java index acdb638a..33e89eab 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/InteractiveRequestParameters.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/InteractiveRequestParameters.java @@ -80,6 +80,11 @@ public class InteractiveRequestParameters implements IAcquireTokenParameters { */ private Map extraHttpHeaders; + /** + * Adds additional query parameters to the token request + */ + private Map extraQueryParameters; + /** * Overrides the tenant value in the authority URL for this request */ diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/OnBehalfOfParameters.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/OnBehalfOfParameters.java index 633b41dc..1c929bc4 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/OnBehalfOfParameters.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/OnBehalfOfParameters.java @@ -46,6 +46,11 @@ public class OnBehalfOfParameters implements IAcquireTokenParameters { */ private Map extraHttpHeaders; + /** + * Adds additional parameters to the token request + */ + private Map extraQueryParameters; + /** * Overrides the tenant value in the authority URL for this request */ diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/RefreshTokenParameters.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/RefreshTokenParameters.java index 5a9750b0..862462a4 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/RefreshTokenParameters.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/RefreshTokenParameters.java @@ -48,6 +48,11 @@ public class RefreshTokenParameters implements IAcquireTokenParameters { */ private Map extraHttpHeaders; + /** + * Adds additional parameters to the token request + */ + private Map extraQueryParameters; + /** * Overrides the tenant value in the authority URL for this request */ diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/SilentParameters.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/SilentParameters.java index 8778a07b..429c5dbb 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/SilentParameters.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/SilentParameters.java @@ -54,6 +54,11 @@ public class SilentParameters implements IAcquireTokenParameters { */ private Map extraHttpHeaders; + /** + * Adds additional query parameters to the token request + */ + private Map extraQueryParameters; + /** * Overrides the tenant value in the authority URL for this request */ diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/TokenRequestExecutor.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/TokenRequestExecutor.java index a8ab5194..50805df2 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/TokenRequestExecutor.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/TokenRequestExecutor.java @@ -34,7 +34,7 @@ class TokenRequestExecutor { AuthenticationResult executeTokenRequest() throws ParseException, IOException { - log.debug("Sending token request to: " + requestAuthority.canonicalAuthorityUrl()); + log.debug("Sending token request to: {}", requestAuthority.canonicalAuthorityUrl()); OAuthHttpRequest oAuthHttpRequest = createOauthHttpRequest(); HTTPResponse oauthHttpResponse = oAuthHttpRequest.send(); return createAuthenticationResultFromOauthHttpResponse(oauthHttpResponse); @@ -67,6 +67,15 @@ OAuthHttpRequest createOauthHttpRequest() throws SerializeException, MalformedUR params.put("claims", Collections.singletonList(claimsRequest)); } + if(msalRequest.requestContext().apiParameters().extraQueryParameters() != null ){ + for(String key: msalRequest.requestContext().apiParameters().extraQueryParameters().keySet()){ + if(params.containsKey(key)){ + log.warn("A query parameter {} has been provided with values multiple times.", key); + } + params.put(key, Collections.singletonList(msalRequest.requestContext().apiParameters().extraQueryParameters().get(key))); + } + } + oauthHttpRequest.setQuery(URLUtils.serializeParameters(params)); if (msalRequest.application().clientAuthentication() != null) { diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/UserNamePasswordParameters.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/UserNamePasswordParameters.java index b7f70f55..cc4dab0c 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/UserNamePasswordParameters.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/UserNamePasswordParameters.java @@ -53,6 +53,11 @@ public class UserNamePasswordParameters implements IAcquireTokenParameters { */ private Map extraHttpHeaders; + /** + * Adds additional query parameters to the token request + */ + private Map extraQueryParameters; + /** * Overrides the tenant value in the authority URL for this request */ diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AuthorizationRequestUrlParametersTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AuthorizationRequestUrlParametersTest.java index 589bb339..66dd4f3a 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AuthorizationRequestUrlParametersTest.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AuthorizationRequestUrlParametersTest.java @@ -20,14 +20,20 @@ public void testBuilder_onlyRequiredParameters() throws UnsupportedEncodingExcep String redirectUri = "http://localhost:8080"; Set scope = Collections.singleton("scope"); + Map extraParameters = new HashMap<>(); + extraParameters.put("id_token_hint", "test"); + extraParameters.put("another_param", "some_value"); + AuthorizationRequestUrlParameters parameters = AuthorizationRequestUrlParameters .builder(redirectUri, scope) + .extraQueryParameters(extraParameters) .build(); Assert.assertEquals(parameters.responseMode(), ResponseMode.FORM_POST); Assert.assertEquals(parameters.redirectUri(), redirectUri); Assert.assertEquals(parameters.scopes().size(), 4); + Assert.assertEquals(parameters.extraQueryParameters.size(), 2); Assert.assertNull(parameters.loginHint()); Assert.assertNull(parameters.codeChallenge()); @@ -58,6 +64,7 @@ public void testBuilder_onlyRequiredParameters() throws UnsupportedEncodingExcep Assert.assertEquals(queryParameters.get("redirect_uri"), "http://localhost:8080"); Assert.assertEquals(queryParameters.get("client_id"), "client_id"); Assert.assertEquals(queryParameters.get("response_mode"), "form_post"); + Assert.assertEquals(queryParameters.get("id_token_hint"),"test"); } @Test(expectedExceptions = IllegalArgumentException.class) @@ -71,6 +78,22 @@ public void testBuilder_invalidRequiredParameters() { .build(); } + @Test + public void testBuilder_conflictingParameters() { + PublicClientApplication app = PublicClientApplication.builder("client_id").build(); + + String redirectUri = "http://localhost:8080"; + Set scope = Collections.singleton("scope"); + + Map extraParameters = new HashMap<>(); + extraParameters.put("scope", "scope"); + + AuthorizationRequestUrlParameters + .builder(redirectUri, scope) + .extraQueryParameters(extraParameters) + .build(); + } + @Test public void testBuilder_optionalParameters() throws UnsupportedEncodingException { Set clientCapabilities = new HashSet<>();