From e9a44d5f92e1dc3a0d420cfd240a41f4ab69f0fa Mon Sep 17 00:00:00 2001 From: Avery-Dunn Date: Sun, 23 Oct 2022 16:36:46 -0700 Subject: [PATCH 01/27] Add IBroker implementation for MSALRuntime --- msal4j-brokers/pom.xml | 5 + .../aad/msal4jbrokers/MSALRuntimeBroker.java | 31 ----- .../aad/msal4jbrokers/MsalRuntimeBroker.java | 120 ++++++++++++++++++ .../com/microsoft/aad/msal4j/IBroker.java | 51 ++++++-- 4 files changed, 168 insertions(+), 39 deletions(-) delete mode 100644 msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MSALRuntimeBroker.java create mode 100644 msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java diff --git a/msal4j-brokers/pom.xml b/msal4j-brokers/pom.xml index 060d756e..27c3d373 100644 --- a/msal4j-brokers/pom.xml +++ b/msal4j-brokers/pom.xml @@ -32,6 +32,11 @@ msal4j 1.13.2 + + com.microsoft.azure + javamsalruntime + 1.0.0 + org.projectlombok lombok diff --git a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MSALRuntimeBroker.java b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MSALRuntimeBroker.java deleted file mode 100644 index 598b83ac..00000000 --- a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MSALRuntimeBroker.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.microsoft.aad.msal4jbrokers; - -import com.microsoft.aad.msal4j.*; -import lombok.extern.slf4j.Slf4j; - -import java.util.concurrent.CompletableFuture; - -@Slf4j -public class MSALRuntimeBroker implements IBroker { - - @Override - public IAuthenticationResult acquireToken(PublicClientApplication application, SilentParameters requestParameters) { - log.debug("Should not call this API if msal runtime init failed"); - throw new MsalClientException("Broker implementation missing", "missing_broker"); - } - - @Override - public IAuthenticationResult acquireToken(PublicClientApplication application, InteractiveRequestParameters requestParameters) { - throw new MsalClientException("Broker implementation missing", "missing_broker"); - } - - @Override - public IAuthenticationResult acquireToken(PublicClientApplication application, UserNamePasswordParameters requestParameters) { - throw new MsalClientException("Broker implementation missing", "missing_broker"); - } - - @Override - public CompletableFuture removeAccount(IAccount account) { - throw new MsalClientException("Broker implementation missing", "missing_broker"); - } -} diff --git a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java new file mode 100644 index 00000000..4f6f5bfa --- /dev/null +++ b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java @@ -0,0 +1,120 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.aad.msal4jbrokers; + +import com.microsoft.aad.msal4j.*; +import com.microsoft.azure.javamsalruntime.AuthParams; +import com.microsoft.azure.javamsalruntime.MsalRuntimeInterop; +import com.microsoft.azure.javamsalruntime.ResultHandler; + +public class MsalRuntimeBroker implements IBroker { + + @Override + public boolean isAvailable() { + //TODO: ensure it's a supported platform/architecture + //TODO: ensure an msalruntime.dll is available for the platform/architecture + //TODO: check one method from msalruntime.dll to make sure it works? + + return true; + } + + @Override + public IAuthenticationResult acquireToken(PublicClientApplication application, + SilentParameters parameters) throws Exception { + MsalRuntimeInterop interop = new MsalRuntimeInterop(); + interop.initializeBroker(); + + ResultHandler resultHandler = new ResultHandler(); + + // If request has an account ID, try to get account info from MSALRuntime + if (parameters.account() != null) { + interop.readAccountById(resultHandler, parameters.account().homeAccountId()); + } + + AuthParams authParams = new AuthParams( + interop.msalRuntimeLibrary, interop.errorHandler, application.clientId(), application.authority(), + parameters.scopes().toString(), + null, // No redirect url in a silent request + parameters.claims().toString()); + + // If request did not have an account ID or MSALRuntime did not return an account, attempt a silent sign in + if (resultHandler.getAuthResult().getAccount() == null) { + interop.signInSilently(resultHandler, authParams); + } + + // Account information populated, attempt to acquire access token + interop.acquireTokenSilently(resultHandler, authParams); + + // Parse the results of the MSALRuntime calls into an MSAL Java IAuthenticationResult + return parseBrokerAuthResult(parameters.authorityUrl(), resultHandler.getAuthResult().getIdToken(), + resultHandler.getAuthResult().getAccessToken(), resultHandler.getAuthResult().getAccount().getAccountId(), + resultHandler.getAuthResult().getAccount().getAccountClientInfo(), resultHandler.getAuthResult().getAccessTokenExpirationTime()); + } + + @Override + public IAuthenticationResult acquireToken(PublicClientApplication application, + InteractiveRequestParameters parameters) throws Exception { + MsalRuntimeInterop interop = new MsalRuntimeInterop(); + interop.initializeBroker(); + + ResultHandler resultHandler = new ResultHandler(); + + AuthParams authParams = new AuthParams( + interop.msalRuntimeLibrary, interop.errorHandler, application.clientId(), application.authority(), + parameters.scopes().toString(), + parameters.redirectUri().toString(), + parameters.claims().toString()); + + // Perform an interactive sign in to get the user information + interop.signInInteractively(resultHandler, authParams, parameters.loginHint()); + + // Account information populated, attempt to acquire access token + interop.acquireTokenInteractively(resultHandler, authParams); + + // Parse the results of the MSALRuntime calls into an MSAL Java IAuthenticationResult + return parseBrokerAuthResult(application.authority(), resultHandler.getAuthResult().getIdToken(), + resultHandler.getAuthResult().getAccessToken(), resultHandler.getAuthResult().getAccount().getAccountId(), + resultHandler.getAuthResult().getAccount().getAccountClientInfo(), resultHandler.getAuthResult().getAccessTokenExpirationTime()); + } + + @Override + public IAuthenticationResult acquireToken(PublicClientApplication application, + UserNamePasswordParameters parameters) throws Exception { + MsalRuntimeInterop interop = new MsalRuntimeInterop(); + interop.initializeBroker(); + + ResultHandler resultHandler = new ResultHandler(); + + AuthParams authParams = new AuthParams( + interop.msalRuntimeLibrary, interop.errorHandler, application.clientId(), application.authority(), + parameters.scopes().toString(), + null, // No redirect url in a silent request + parameters.claims().toString()); + + authParams.setUsernamePassword(interop.msalRuntimeLibrary, interop.errorHandler, + parameters.username(), String.valueOf(parameters.password())); + + //No interaction is needed for the username/password flow + interop.signInSilently(resultHandler, authParams); + + // Account information populated, attempt to acquire access token + interop.acquireTokenSilently(resultHandler, authParams); + + // Parse the results of the MSALRuntime calls into an MSAL Java IAuthenticationResult + return parseBrokerAuthResult(application.authority(), resultHandler.getAuthResult().getIdToken(), + resultHandler.getAuthResult().getAccessToken(), resultHandler.getAuthResult().getAccount().getAccountId(), + resultHandler.getAuthResult().getAccount().getAccountClientInfo(), resultHandler.getAuthResult().getAccessTokenExpirationTime()); + + } + + @Override + public void removeAccount(PublicClientApplication application, IAccount account) throws Exception { + MsalRuntimeInterop interop = new MsalRuntimeInterop(); + interop.initializeBroker(); + + ResultHandler resultHandler = new ResultHandler(); + + interop.signOutSilently(resultHandler, application.clientId(), account.homeAccountId()); + } +} diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java index 919a8092..7b901a8b 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java @@ -3,24 +3,27 @@ package com.microsoft.aad.msal4j; +import com.nimbusds.jwt.JWTParser; + +import java.net.URL; import java.util.Set; import java.util.concurrent.CompletableFuture; /** * Used to define the basic set of methods that all Brokers must implement * - * All methods are so they can be referenced by MSAL Java without an implementation, and by default simply throw an - * exception saying that a broker implementation is missing + * All methods are marked as default so they can be referenced by MSAL Java without an implementation, + * and most will simply throw an exception if not overridden by an IBroker implementation */ public interface IBroker { /** - * checks if a IBroker implementation exists + * Checks if an IBroker implementation is accessible by MSAL Java */ - default boolean isAvailable(){ return false; } + /** * Acquire a token silently, i.e. without direct user interaction * @@ -30,7 +33,7 @@ default boolean isAvailable(){ * @param requestParameters MsalRequest object which contains everything needed for the broker implementation to make a request * @return IBroker implementations will return an AuthenticationResult object */ - default IAuthenticationResult acquireToken(PublicClientApplication application, SilentParameters requestParameters) { + default IAuthenticationResult acquireToken(PublicClientApplication application, SilentParameters requestParameters) throws Exception { throw new MsalClientException("Broker implementation missing", AuthenticationErrorCode.MISSING_BROKER); } @@ -40,7 +43,7 @@ default IAuthenticationResult acquireToken(PublicClientApplication application, * @param requestParameters MsalRequest object which contains everything needed for the broker implementation to make a request * @return IBroker implementations will return an AuthenticationResult object */ - default IAuthenticationResult acquireToken(PublicClientApplication application, InteractiveRequestParameters requestParameters) { + default IAuthenticationResult acquireToken(PublicClientApplication application, InteractiveRequestParameters requestParameters) throws Exception { throw new MsalClientException("Broker implementation missing", AuthenticationErrorCode.MISSING_BROKER); } @@ -50,11 +53,43 @@ default IAuthenticationResult acquireToken(PublicClientApplication application, * @param requestParameters MsalRequest object which contains everything needed for the broker implementation to make a request * @return IBroker implementations will return an AuthenticationResult object */ - default IAuthenticationResult acquireToken(PublicClientApplication application, UserNamePasswordParameters requestParameters) { + default IAuthenticationResult acquireToken(PublicClientApplication application, UserNamePasswordParameters requestParameters) throws Exception { throw new MsalClientException("Broker implementation missing", AuthenticationErrorCode.MISSING_BROKER); } - default CompletableFuture removeAccount(IAccount account) { + default void removeAccount(PublicClientApplication application, IAccount account) throws Exception { throw new MsalClientException("Broker implementation missing", AuthenticationErrorCode.MISSING_BROKER); } + + //TODO: Any better place to put this helper method? This feels wrong + //AuthenticationResult requires many package-private classes that broker package can't access, so helper methods will be + // made for each broker to create the sort of AuthenticationResult that the rest of MSAL Java expects + default IAuthenticationResult parseBrokerAuthResult(String authority, String idToken, String accessToken, + String accountId, String clientInfo, + long accessTokenExpirationTime) { + + //TODO: need to either make AuthenticationResult public or implement IAuthenticationResult here in the interop layer + AuthenticationResult.AuthenticationResultBuilder builder = AuthenticationResult.builder(); + + try { + if (idToken != null) { + builder.idToken(idToken); + if (accountId!= null) { + String idTokenJson = + JWTParser.parse(idToken).getParsedParts()[1].decodeToString(); + //TODO: need to figure out if 'policy' field is relevant for brokers + builder.accountCacheEntity(AccountCacheEntity.create(clientInfo, + Authority.createAuthority(new URL(authority)), JsonHelper.convertJsonToObject(idTokenJson, + IdToken.class), null)); + } + } + if (accessToken != null) { + builder.accessToken(accessToken); + builder.expiresOn(accessTokenExpirationTime); + } + } catch (Exception e) { + //TODO: throw new msalexception. Could a valid broker result be an invalid MSAL Java result? + } + return builder.build(); + } } \ No newline at end of file From 8a8df73f989185db4216ee146e09c1ec86a97a96 Mon Sep 17 00:00:00 2001 From: Avery-Dunn Date: Mon, 19 Dec 2022 08:00:38 -0800 Subject: [PATCH 02/27] Remove dll used during testing --- msal4j-brokers/pom.xml | 2 +- .../aad/msal4jbrokers/MsalRuntimeBroker.java | 208 ++++++++++-------- .../com/microsoft/aad/msal4j/IBroker.java | 19 +- .../msal4j/InteractiveRequestParameters.java | 5 + 4 files changed, 132 insertions(+), 102 deletions(-) diff --git a/msal4j-brokers/pom.xml b/msal4j-brokers/pom.xml index 27c3d373..502b9072 100644 --- a/msal4j-brokers/pom.xml +++ b/msal4j-brokers/pom.xml @@ -30,7 +30,7 @@ com.microsoft.azure msal4j - 1.13.2 + 1.13.3 com.microsoft.azure diff --git a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java index 4f6f5bfa..f1e8bd54 100644 --- a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java +++ b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java @@ -3,118 +3,152 @@ package com.microsoft.aad.msal4jbrokers; -import com.microsoft.aad.msal4j.*; -import com.microsoft.azure.javamsalruntime.AuthParams; +import com.microsoft.aad.msal4j.IAuthenticationResult; +import com.microsoft.aad.msal4j.IBroker; +import com.microsoft.aad.msal4j.InteractiveRequestParameters; +import com.microsoft.aad.msal4j.PublicClientApplication; +import com.microsoft.aad.msal4j.SilentParameters; +import com.microsoft.aad.msal4j.UserNamePasswordParameters; +import com.microsoft.azure.javamsalruntime.Account; +import com.microsoft.azure.javamsalruntime.AuthParameters; +import com.microsoft.azure.javamsalruntime.AuthResult; +import com.microsoft.azure.javamsalruntime.MsalInteropException; import com.microsoft.azure.javamsalruntime.MsalRuntimeInterop; -import com.microsoft.azure.javamsalruntime.ResultHandler; +import com.microsoft.azure.javamsalruntime.ReadAccountResult; -public class MsalRuntimeBroker implements IBroker { - - @Override - public boolean isAvailable() { - //TODO: ensure it's a supported platform/architecture - //TODO: ensure an msalruntime.dll is available for the platform/architecture - //TODO: check one method from msalruntime.dll to make sure it works? +import java.net.MalformedURLException; +import java.util.Collections; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; - return true; +public class MsalRuntimeBroker implements IBroker { + private static MsalRuntimeInterop interop; + + static { + try { + //MsalRuntimeInterop performs various initialization steps in a similar static block, + // so when an MsalRuntimeBroker is created this will cause the interop layer to initialize + interop = new MsalRuntimeInterop(); + } catch (MsalInteropException e) { + //TODO: Meaningful error message explaining why MSALRuntime failed to initialize (unsupported platform, error from MSALRuntime, etc.) + } } @Override - public IAuthenticationResult acquireToken(PublicClientApplication application, - SilentParameters parameters) throws Exception { - MsalRuntimeInterop interop = new MsalRuntimeInterop(); - interop.initializeBroker(); + public void acquireToken(PublicClientApplication application, SilentParameters parameters, CompletableFuture future) throws ExecutionException, InterruptedException { + Account accountResult = null; - ResultHandler resultHandler = new ResultHandler(); - - // If request has an account ID, try to get account info from MSALRuntime + //If request has an account ID, MSALRuntime data cached for that account + // try to get account info from MSALRuntime if (parameters.account() != null) { - interop.readAccountById(resultHandler, parameters.account().homeAccountId()); + accountResult = ((ReadAccountResult) interop.readAccountById(parameters.account().homeAccountId(), application.correlationId()).get()).getAccount(); } - AuthParams authParams = new AuthParams( - interop.msalRuntimeLibrary, interop.errorHandler, application.clientId(), application.authority(), - parameters.scopes().toString(), - null, // No redirect url in a silent request - parameters.claims().toString()); - - // If request did not have an account ID or MSALRuntime did not return an account, attempt a silent sign in - if (resultHandler.getAuthResult().getAccount() == null) { - interop.signInSilently(resultHandler, authParams); + try (AuthParameters authParameters = + new AuthParameters + .AuthParametersBuilder(application.clientId(), + application.authority(), + parameters.scopes().toString()) + .build()) { + + //Account was not cached in MSALRuntime, must perform sign in first to populate account info + if (accountResult == null) { + accountResult = ((AuthResult) interop.signInSilently(authParameters, application.correlationId()).get()).getAccount(); + } + + //Either account was already cached or silent sign in was successful, so we can retrieve tokens from MSALRuntime, + // parse the result into an MSAL Java AuthenticationResult, and complete the future + interop.acquireTokenSilently(authParameters, application.correlationId(), accountResult) + .thenApply(authResult -> future.complete(parseBrokerAuthResult( + application.authority(), + ((AuthResult) authResult).getIdToken(), + ((AuthResult) authResult).getAccessToken(), + ((AuthResult) authResult).getAccount().getAccountId(), + ((AuthResult) authResult).getAccount().getClientInfo(), + ((AuthResult) authResult).getAccessTokenExpirationTime()))); } - - // Account information populated, attempt to acquire access token - interop.acquireTokenSilently(resultHandler, authParams); - - // Parse the results of the MSALRuntime calls into an MSAL Java IAuthenticationResult - return parseBrokerAuthResult(parameters.authorityUrl(), resultHandler.getAuthResult().getIdToken(), - resultHandler.getAuthResult().getAccessToken(), resultHandler.getAuthResult().getAccount().getAccountId(), - resultHandler.getAuthResult().getAccount().getAccountClientInfo(), resultHandler.getAuthResult().getAccessTokenExpirationTime()); } @Override - public IAuthenticationResult acquireToken(PublicClientApplication application, - InteractiveRequestParameters parameters) throws Exception { - MsalRuntimeInterop interop = new MsalRuntimeInterop(); - interop.initializeBroker(); - - ResultHandler resultHandler = new ResultHandler(); - - AuthParams authParams = new AuthParams( - interop.msalRuntimeLibrary, interop.errorHandler, application.clientId(), application.authority(), - parameters.scopes().toString(), - parameters.redirectUri().toString(), - parameters.claims().toString()); - - // Perform an interactive sign in to get the user information - interop.signInInteractively(resultHandler, authParams, parameters.loginHint()); - - // Account information populated, attempt to acquire access token - interop.acquireTokenInteractively(resultHandler, authParams); - - // Parse the results of the MSALRuntime calls into an MSAL Java IAuthenticationResult - return parseBrokerAuthResult(application.authority(), resultHandler.getAuthResult().getIdToken(), - resultHandler.getAuthResult().getAccessToken(), resultHandler.getAuthResult().getAccount().getAccountId(), - resultHandler.getAuthResult().getAccount().getAccountClientInfo(), resultHandler.getAuthResult().getAccessTokenExpirationTime()); + public void acquireToken(PublicClientApplication application, InteractiveRequestParameters parameters, CompletableFuture future) throws ExecutionException, InterruptedException { + try (AuthParameters authParameters = + new AuthParameters + .AuthParametersBuilder(application.clientId(), + application.authority(), + parameters.scopes().toString()) + .claims(parameters.claims().toString()) + .build()) { + + Account accountResult = ((AuthResult) interop.signInInteractively(parameters.windowHandle(), authParameters, application.correlationId(), parameters.loginHint()).get()).getAccount(); + + interop.acquireTokenInteractively(parameters.windowHandle(), authParameters, application.correlationId(), accountResult) + .thenApply(authResult -> future.complete(parseBrokerAuthResult( + application.authority(), + ((AuthResult) authResult).getIdToken(), + ((AuthResult) authResult).getAccessToken(), + ((AuthResult) authResult).getAccount().getAccountId(), + ((AuthResult) authResult).getAccount().getClientInfo(), + ((AuthResult) authResult).getAccessTokenExpirationTime()))); + } } + /** + * @deprecated + */ + @Deprecated @Override - public IAuthenticationResult acquireToken(PublicClientApplication application, - UserNamePasswordParameters parameters) throws Exception { - MsalRuntimeInterop interop = new MsalRuntimeInterop(); - interop.initializeBroker(); + public void acquireToken(PublicClientApplication application, UserNamePasswordParameters parameters, CompletableFuture future) throws ExecutionException, InterruptedException { + + try (AuthParameters authParameters = + new AuthParameters + .AuthParametersBuilder(application.clientId(), + application.authority(), + parameters.scopes().toString()) + .claims(parameters.claims().toString()) + .build()) { + + authParameters.setUsernamePassword(parameters.username(), new String(parameters.password())); + + Account accountResult = ((AuthResult) interop.signInSilently(authParameters, application.correlationId()).get()).getAccount(); + + //Either account was already cached or silent sign in was successful, so we can retrieve tokens from MSALRuntime, + // parse the result into an MSAL Java AuthenticationResult, and complete the future + interop.acquireTokenSilently(authParameters, application.correlationId(), accountResult) + .thenApply(authResult -> future.complete(parseBrokerAuthResult( + application.authority(), + ((AuthResult) authResult).getIdToken(), + ((AuthResult) authResult).getAccessToken(), + ((AuthResult) authResult).getAccount().getAccountId(), + ((AuthResult) authResult).getAccount().getClientInfo(), + ((AuthResult) authResult).getAccessTokenExpirationTime()))); + } + } - ResultHandler resultHandler = new ResultHandler(); + //Simple manual test for early development/testing + public static void main(String args[]) throws MalformedURLException, ExecutionException, InterruptedException { + String clientId = "903c8a8a-9e74-415e-9921-711a293d90cb"; + String authority = "https://login.microsoftonline.com/common"; + String scopes = "https://graph.microsoft.com/.default"; - AuthParams authParams = new AuthParams( - interop.msalRuntimeLibrary, interop.errorHandler, application.clientId(), application.authority(), - parameters.scopes().toString(), - null, // No redirect url in a silent request - parameters.claims().toString()); - authParams.setUsernamePassword(interop.msalRuntimeLibrary, interop.errorHandler, - parameters.username(), String.valueOf(parameters.password())); + MsalRuntimeBroker broker = new MsalRuntimeBroker(); - //No interaction is needed for the username/password flow - interop.signInSilently(resultHandler, authParams); + PublicClientApplication pca = PublicClientApplication.builder( + clientId). + authority(authority). + correlationId(UUID.randomUUID().toString()). + build(); - // Account information populated, attempt to acquire access token - interop.acquireTokenSilently(resultHandler, authParams); + SilentParameters parameters = SilentParameters.builder(Collections.singleton(scopes)).build(); - // Parse the results of the MSALRuntime calls into an MSAL Java IAuthenticationResult - return parseBrokerAuthResult(application.authority(), resultHandler.getAuthResult().getIdToken(), - resultHandler.getAuthResult().getAccessToken(), resultHandler.getAuthResult().getAccount().getAccountId(), - resultHandler.getAuthResult().getAccount().getAccountClientInfo(), resultHandler.getAuthResult().getAccessTokenExpirationTime()); + CompletableFuture future = new CompletableFuture<>(); - } - - @Override - public void removeAccount(PublicClientApplication application, IAccount account) throws Exception { - MsalRuntimeInterop interop = new MsalRuntimeInterop(); - interop.initializeBroker(); + broker.acquireToken(pca, parameters, future); - ResultHandler resultHandler = new ResultHandler(); + IAuthenticationResult result = future.get(); - interop.signOutSilently(resultHandler, application.clientId(), account.homeAccountId()); + System.out.println(result.idToken()); + System.out.println(result.accessToken()); } } diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java index 7b901a8b..ac33e006 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java @@ -6,8 +6,8 @@ import com.nimbusds.jwt.JWTParser; import java.net.URL; -import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; /** * Used to define the basic set of methods that all Brokers must implement @@ -29,35 +29,26 @@ default boolean isAvailable(){ * * This may be accomplished by returning tokens from a token cache, using cached refresh tokens to get new tokens, * or via any authentication flow where a user is not prompted to enter credentials - * - * @param requestParameters MsalRequest object which contains everything needed for the broker implementation to make a request - * @return IBroker implementations will return an AuthenticationResult object */ - default IAuthenticationResult acquireToken(PublicClientApplication application, SilentParameters requestParameters) throws Exception { + default void acquireToken(PublicClientApplication application, SilentParameters requestParameters, CompletableFuture future) throws MsalClientException, ExecutionException, InterruptedException { throw new MsalClientException("Broker implementation missing", AuthenticationErrorCode.MISSING_BROKER); } /** * Acquire a token interactively, by prompting users to enter their credentials in some way - * - * @param requestParameters MsalRequest object which contains everything needed for the broker implementation to make a request - * @return IBroker implementations will return an AuthenticationResult object */ - default IAuthenticationResult acquireToken(PublicClientApplication application, InteractiveRequestParameters requestParameters) throws Exception { + default void acquireToken(PublicClientApplication application, InteractiveRequestParameters parameters, CompletableFuture future) throws ExecutionException, InterruptedException { throw new MsalClientException("Broker implementation missing", AuthenticationErrorCode.MISSING_BROKER); } /** * Acquire a token silently, i.e. without direct user interaction, using username/password authentication - * - * @param requestParameters MsalRequest object which contains everything needed for the broker implementation to make a request - * @return IBroker implementations will return an AuthenticationResult object */ - default IAuthenticationResult acquireToken(PublicClientApplication application, UserNamePasswordParameters requestParameters) throws Exception { + default void acquireToken(PublicClientApplication application, UserNamePasswordParameters parameters, CompletableFuture future) throws ExecutionException, InterruptedException { throw new MsalClientException("Broker implementation missing", AuthenticationErrorCode.MISSING_BROKER); } - default void removeAccount(PublicClientApplication application, IAccount account) throws Exception { + default void removeAccount(PublicClientApplication application, IAccount account) throws MsalClientException { throw new MsalClientException("Broker implementation missing", AuthenticationErrorCode.MISSING_BROKER); } 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..f6e77b91 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 @@ -100,6 +100,11 @@ public class InteractiveRequestParameters implements IAcquireTokenParameters { */ private boolean instanceAware; + /** + * The parent window handle used to open UI elements in non-browser scenarios + */ + private long windowHandle; + private static InteractiveRequestParametersBuilder builder() { return new InteractiveRequestParametersBuilder(); } From 5b8e235adfbd83e52503b0d3f104cfd6250e78ab Mon Sep 17 00:00:00 2001 From: Avery-Dunn Date: Mon, 19 Dec 2022 16:00:18 -0800 Subject: [PATCH 03/27] Integrate broker steps to relevant flows in PublicClientApplication --- .../aad/msal4jbrokers/MsalRuntimeBroker.java | 46 +++++++++++-- .../aad/msal4j/AuthenticationErrorCode.java | 10 ++- .../com/microsoft/aad/msal4j/IBroker.java | 6 +- .../aad/msal4j/PublicClientApplication.java | 68 +++++++++++++++++-- 4 files changed, 115 insertions(+), 15 deletions(-) diff --git a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java index f1e8bd54..26f84c8f 100644 --- a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java +++ b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java @@ -9,6 +9,9 @@ import com.microsoft.aad.msal4j.PublicClientApplication; import com.microsoft.aad.msal4j.SilentParameters; import com.microsoft.aad.msal4j.UserNamePasswordParameters; +import com.microsoft.aad.msal4j.MsalException; +import com.microsoft.aad.msal4j.AuthenticationErrorCode; +import com.microsoft.aad.msal4j.IAccount; import com.microsoft.azure.javamsalruntime.Account; import com.microsoft.azure.javamsalruntime.AuthParameters; import com.microsoft.azure.javamsalruntime.AuthResult; @@ -36,13 +39,19 @@ public class MsalRuntimeBroker implements IBroker { } @Override - public void acquireToken(PublicClientApplication application, SilentParameters parameters, CompletableFuture future) throws ExecutionException, InterruptedException { + public void acquireToken(PublicClientApplication application, SilentParameters parameters, CompletableFuture future) { Account accountResult = null; //If request has an account ID, MSALRuntime data cached for that account // try to get account info from MSALRuntime if (parameters.account() != null) { - accountResult = ((ReadAccountResult) interop.readAccountById(parameters.account().homeAccountId(), application.correlationId()).get()).getAccount(); + try { + accountResult = ((ReadAccountResult) interop.readAccountById(parameters.account().homeAccountId(), application.correlationId()).get()).getAccount(); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } } try (AuthParameters authParameters = @@ -67,11 +76,16 @@ public void acquireToken(PublicClientApplication application, SilentParameters p ((AuthResult) authResult).getAccount().getAccountId(), ((AuthResult) authResult).getAccount().getClientInfo(), ((AuthResult) authResult).getAccessTokenExpirationTime()))); + } catch (MsalInteropException interopException) { + throw new MsalException(interopException.getErrorMessage(), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); + } catch (InterruptedException | ExecutionException ex) { + //TODO: these exceptions can occur when waiting on a result from MSALRuntime. Not possible to continue? Retrow exception or create MSAL exception? } } @Override - public void acquireToken(PublicClientApplication application, InteractiveRequestParameters parameters, CompletableFuture future) throws ExecutionException, InterruptedException { + public void acquireToken(PublicClientApplication application, InteractiveRequestParameters parameters, CompletableFuture future) { + try (AuthParameters authParameters = new AuthParameters .AuthParametersBuilder(application.clientId(), @@ -90,6 +104,10 @@ public void acquireToken(PublicClientApplication application, InteractiveRequest ((AuthResult) authResult).getAccount().getAccountId(), ((AuthResult) authResult).getAccount().getClientInfo(), ((AuthResult) authResult).getAccessTokenExpirationTime()))); + } catch (MsalInteropException interopException) { + throw new MsalException(interopException.getErrorMessage(), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); + } catch (InterruptedException | ExecutionException ex) { + //TODO: these exceptions can occur when waiting on a result from MSALRuntime. Not possible to continue? Retrow exception or create MSAL exception? } } @@ -98,7 +116,7 @@ public void acquireToken(PublicClientApplication application, InteractiveRequest */ @Deprecated @Override - public void acquireToken(PublicClientApplication application, UserNamePasswordParameters parameters, CompletableFuture future) throws ExecutionException, InterruptedException { + public void acquireToken(PublicClientApplication application, UserNamePasswordParameters parameters, CompletableFuture future) { try (AuthParameters authParameters = new AuthParameters @@ -122,6 +140,26 @@ public void acquireToken(PublicClientApplication application, UserNamePasswordPa ((AuthResult) authResult).getAccount().getAccountId(), ((AuthResult) authResult).getAccount().getClientInfo(), ((AuthResult) authResult).getAccessTokenExpirationTime()))); + } catch (MsalInteropException interopException) { + throw new MsalException(interopException.getErrorMessage(), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); + } catch (InterruptedException | ExecutionException ex) { + //TODO: these exceptions can occur when waiting on a result from MSALRuntime. Not possible to continue? Retrow exception or create MSAL exception? + } + } + + @Override + public void removeAccount(PublicClientApplication application, IAccount msalJavaAccount) { + try { + + Account msalRuntimeAccount = ((ReadAccountResult) interop.readAccountById(msalJavaAccount.homeAccountId(), application.correlationId()).get()).getAccount(); + + if (msalRuntimeAccount != null) { + interop.signOutSilently(application.clientId(), application.correlationId(), msalRuntimeAccount); + } + } catch (MsalInteropException interopException) { + throw new MsalException(interopException.getErrorMessage(), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); + } catch (InterruptedException | ExecutionException ex) { + //TODO: these exceptions can occur when waiting on a result from MSALRuntime. Not possible to continue? Retrow exception or create MSAL exception? } } diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthenticationErrorCode.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthenticationErrorCode.java index 78f5260c..6992b48c 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthenticationErrorCode.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthenticationErrorCode.java @@ -115,9 +115,17 @@ public class AuthenticationErrorCode { * A JWT parsing failure, indicating the JWT provided to MSAL is of invalid format. */ public final static String INVALID_JWT = "invalid_jwt"; + /** * Indicates that a Broker implementation is missing from the device, such as when an app developer * does not include one of our broker packages as a dependency in their project, or otherwise cannot - * be accessed by MSAL Java*/ + * be accessed by MSAL Java + */ public final static String MISSING_BROKER = "missing_broker"; + + /** + * Indicates an error from the Java/MSALRuntime interop layer used by the Java Brokers package, + * and will generally just be forwarding an error message from the interop layer or MSALRuntime itself + */ + public final static String MSALRUNTIME_INTEROP_ERROR = "missing_broker"; } diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java index ac33e006..04f41705 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java @@ -30,21 +30,21 @@ default boolean isAvailable(){ * This may be accomplished by returning tokens from a token cache, using cached refresh tokens to get new tokens, * or via any authentication flow where a user is not prompted to enter credentials */ - default void acquireToken(PublicClientApplication application, SilentParameters requestParameters, CompletableFuture future) throws MsalClientException, ExecutionException, InterruptedException { + default void acquireToken(PublicClientApplication application, SilentParameters requestParameters, CompletableFuture future) { throw new MsalClientException("Broker implementation missing", AuthenticationErrorCode.MISSING_BROKER); } /** * Acquire a token interactively, by prompting users to enter their credentials in some way */ - default void acquireToken(PublicClientApplication application, InteractiveRequestParameters parameters, CompletableFuture future) throws ExecutionException, InterruptedException { + default void acquireToken(PublicClientApplication application, InteractiveRequestParameters parameters, CompletableFuture future) { throw new MsalClientException("Broker implementation missing", AuthenticationErrorCode.MISSING_BROKER); } /** * Acquire a token silently, i.e. without direct user interaction, using username/password authentication */ - default void acquireToken(PublicClientApplication application, UserNamePasswordParameters parameters, CompletableFuture future) throws ExecutionException, InterruptedException { + default void acquireToken(PublicClientApplication application, UserNamePasswordParameters parameters, CompletableFuture future) { throw new MsalClientException("Broker implementation missing", AuthenticationErrorCode.MISSING_BROKER); } diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/PublicClientApplication.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/PublicClientApplication.java index a7f18dda..5155245c 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/PublicClientApplication.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/PublicClientApplication.java @@ -8,6 +8,7 @@ import com.nimbusds.oauth2.sdk.id.ClientID; import org.slf4j.LoggerFactory; +import java.net.MalformedURLException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicReference; @@ -23,6 +24,7 @@ public class PublicClientApplication extends AbstractClientApplicationBase implements IPublicClientApplication { private final ClientAuthenticationPost clientAuthentication; + private IBroker broker; @Override public CompletableFuture acquireToken(UserNamePasswordParameters parameters) { @@ -35,12 +37,20 @@ public CompletableFuture acquireToken(UserNamePasswordPar parameters, UserIdentifier.fromUpn(parameters.username())); - UserNamePasswordRequest userNamePasswordRequest = - new UserNamePasswordRequest(parameters, - this, - context); + CompletableFuture future = new CompletableFuture<>(); - return this.executeRequest(userNamePasswordRequest); + if (this.broker != null) { + broker.acquireToken(this, parameters, future); + } else { + UserNamePasswordRequest userNamePasswordRequest = + new UserNamePasswordRequest(parameters, + this, + context); + + future = this.executeRequest(userNamePasswordRequest); + } + + return future; } @Override @@ -112,17 +122,48 @@ public CompletableFuture acquireToken(InteractiveRequestP this, context); - CompletableFuture future = executeRequest(interactiveRequest); - futureReference.set(future); + CompletableFuture future = new CompletableFuture<>(); + + if (this.broker != null) { + broker.acquireToken(this, parameters, future); + futureReference.set(future); + } else { + future = executeRequest(interactiveRequest); + futureReference.set(future); + } + + return future; + } + + @Override + public CompletableFuture acquireTokenSilently(SilentParameters parameters) throws MalformedURLException { + CompletableFuture future = new CompletableFuture<>(); + + if (this.broker != null) { + broker.acquireToken(this, parameters, future); + } else { + future = super.acquireTokenSilently(parameters); + } + return future; } + @Override + public CompletableFuture removeAccount(IAccount account) { + if (this.broker != null) { + broker.removeAccount(this, account); + } + + return super.removeAccount(account); + } + private PublicClientApplication(Builder builder) { super(builder); validateNotBlank("clientId", clientId()); log = LoggerFactory.getLogger(PublicClientApplication.class); this.clientAuthentication = new ClientAuthenticationPost(ClientAuthenticationMethod.NONE, new ClientID(clientId())); + this.broker = builder.broker; } @Override @@ -146,6 +187,19 @@ private Builder(String clientId) { super(clientId); } + private IBroker broker = null; + + /** + * Implementation of IBroker that will be used to retrieve tokens + *

+ * Setting this will cause MSAL Java to use a broker (such as WAM/MSALRuntime) in flows that support that broker + */ + public PublicClientApplication.Builder broker(IBroker val) { + this.broker = val; + + return self(); + } + @Override public PublicClientApplication build() { From ade07177e01e804d4048fc473434f0e70bad58f6 Mon Sep 17 00:00:00 2001 From: Avery-Dunn Date: Wed, 4 Jan 2023 08:49:48 -0800 Subject: [PATCH 04/27] Add logic to cancel MsalRuntimeFutures --- .../aad/msal4jbrokers/MsalRuntimeBroker.java | 55 +++++++++++++------ .../com/microsoft/aad/msal4j/IBroker.java | 2 - 2 files changed, 39 insertions(+), 18 deletions(-) diff --git a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java index 26f84c8f..a96fdd33 100644 --- a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java +++ b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java @@ -16,12 +16,14 @@ import com.microsoft.azure.javamsalruntime.AuthParameters; import com.microsoft.azure.javamsalruntime.AuthResult; import com.microsoft.azure.javamsalruntime.MsalInteropException; +import com.microsoft.azure.javamsalruntime.MsalRuntimeFuture; import com.microsoft.azure.javamsalruntime.MsalRuntimeInterop; import com.microsoft.azure.javamsalruntime.ReadAccountResult; import java.net.MalformedURLException; import java.util.Collections; import java.util.UUID; +import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; @@ -41,16 +43,13 @@ public class MsalRuntimeBroker implements IBroker { @Override public void acquireToken(PublicClientApplication application, SilentParameters parameters, CompletableFuture future) { Account accountResult = null; - //If request has an account ID, MSALRuntime data cached for that account // try to get account info from MSALRuntime if (parameters.account() != null) { try { accountResult = ((ReadAccountResult) interop.readAccountById(parameters.account().homeAccountId(), application.correlationId()).get()).getAccount(); - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (ExecutionException e) { - e.printStackTrace(); + } catch (InterruptedException | ExecutionException e) { + //TODO: these exceptions can occur when waiting on a result from MSALRuntime. Not possible to continue? Rethrow exception or create MSAL exception? } } @@ -60,16 +59,19 @@ public void acquireToken(PublicClientApplication application, SilentParameters p application.authority(), parameters.scopes().toString()) .build()) { + MsalRuntimeFuture currentAsyncOperation; //Account was not cached in MSALRuntime, must perform sign in first to populate account info if (accountResult == null) { - accountResult = ((AuthResult) interop.signInSilently(authParameters, application.correlationId()).get()).getAccount(); + currentAsyncOperation = interop.signInSilently(authParameters, application.correlationId()); + setFutureToCancel(future, currentAsyncOperation); + accountResult = ((AuthResult) currentAsyncOperation.get()).getAccount(); } - //Either account was already cached or silent sign in was successful, so we can retrieve tokens from MSALRuntime, // parse the result into an MSAL Java AuthenticationResult, and complete the future - interop.acquireTokenSilently(authParameters, application.correlationId(), accountResult) - .thenApply(authResult -> future.complete(parseBrokerAuthResult( + currentAsyncOperation = interop.acquireTokenSilently(authParameters, application.correlationId(), accountResult); + setFutureToCancel(future, currentAsyncOperation); + currentAsyncOperation.thenApply(authResult -> future.complete(parseBrokerAuthResult( application.authority(), ((AuthResult) authResult).getIdToken(), ((AuthResult) authResult).getAccessToken(), @@ -79,7 +81,7 @@ public void acquireToken(PublicClientApplication application, SilentParameters p } catch (MsalInteropException interopException) { throw new MsalException(interopException.getErrorMessage(), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); } catch (InterruptedException | ExecutionException ex) { - //TODO: these exceptions can occur when waiting on a result from MSALRuntime. Not possible to continue? Retrow exception or create MSAL exception? + //TODO: these exceptions can occur when waiting on a result from MSALRuntime. Not possible to continue? Rethrow exception or create MSAL exception? } } @@ -94,10 +96,15 @@ public void acquireToken(PublicClientApplication application, InteractiveRequest .claims(parameters.claims().toString()) .build()) { - Account accountResult = ((AuthResult) interop.signInInteractively(parameters.windowHandle(), authParameters, application.correlationId(), parameters.loginHint()).get()).getAccount(); + MsalRuntimeFuture currentMsalRuntimeFuture; - interop.acquireTokenInteractively(parameters.windowHandle(), authParameters, application.correlationId(), accountResult) - .thenApply(authResult -> future.complete(parseBrokerAuthResult( + currentMsalRuntimeFuture = interop.signInInteractively(parameters.windowHandle(), authParameters, application.correlationId(), parameters.loginHint()); + setFutureToCancel(future, currentMsalRuntimeFuture); + Account accountResult = ((AuthResult) currentMsalRuntimeFuture.get()).getAccount(); + + currentMsalRuntimeFuture = interop.acquireTokenInteractively(parameters.windowHandle(), authParameters, application.correlationId(), accountResult); + setFutureToCancel(future, currentMsalRuntimeFuture); + currentMsalRuntimeFuture.thenApply(authResult -> future.complete(parseBrokerAuthResult( application.authority(), ((AuthResult) authResult).getIdToken(), ((AuthResult) authResult).getAccessToken(), @@ -126,14 +133,19 @@ public void acquireToken(PublicClientApplication application, UserNamePasswordPa .claims(parameters.claims().toString()) .build()) { + MsalRuntimeFuture currentMsalRuntimeFuture; + authParameters.setUsernamePassword(parameters.username(), new String(parameters.password())); - Account accountResult = ((AuthResult) interop.signInSilently(authParameters, application.correlationId()).get()).getAccount(); + currentMsalRuntimeFuture = interop.signInSilently(authParameters, application.correlationId()); + setFutureToCancel(future, currentMsalRuntimeFuture); + Account accountResult = ((AuthResult) currentMsalRuntimeFuture.get()).getAccount(); //Either account was already cached or silent sign in was successful, so we can retrieve tokens from MSALRuntime, // parse the result into an MSAL Java AuthenticationResult, and complete the future - interop.acquireTokenSilently(authParameters, application.correlationId(), accountResult) - .thenApply(authResult -> future.complete(parseBrokerAuthResult( + currentMsalRuntimeFuture = interop.acquireTokenSilently(authParameters, application.correlationId(), accountResult); + setFutureToCancel(future, currentMsalRuntimeFuture); + currentMsalRuntimeFuture.thenApply(authResult -> future.complete(parseBrokerAuthResult( application.authority(), ((AuthResult) authResult).getIdToken(), ((AuthResult) authResult).getAccessToken(), @@ -163,6 +175,17 @@ public void removeAccount(PublicClientApplication application, IAccount msalJava } } + /** + * If the future returned by MSAL Java is canceled before we can complete it using a result from MSALRuntime, we must cancel the async operations MSALRuntime is performing + * + * However, there are multiple sequential calls that need to be made to MSALRuntime, each of which returns an MsalRuntimeFuture which we'd need to cancel + * + * This utility method encapsulates the logic for swapping which MsalRuntimeFuture gets canceled if the main future is canceled + */ + public void setFutureToCancel(CompletableFuture future, MsalRuntimeFuture futureToCancel) { + future.whenComplete((result, ex) -> {if (ex instanceof CancellationException) futureToCancel.cancelAsyncOperation();}); + } + //Simple manual test for early development/testing public static void main(String args[]) throws MalformedURLException, ExecutionException, InterruptedException { String clientId = "903c8a8a-9e74-415e-9921-711a293d90cb"; diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java index 04f41705..5c85dfe4 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java @@ -7,7 +7,6 @@ import java.net.URL; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; /** * Used to define the basic set of methods that all Brokers must implement @@ -59,7 +58,6 @@ default IAuthenticationResult parseBrokerAuthResult(String authority, String idT String accountId, String clientInfo, long accessTokenExpirationTime) { - //TODO: need to either make AuthenticationResult public or implement IAuthenticationResult here in the interop layer AuthenticationResult.AuthenticationResultBuilder builder = AuthenticationResult.builder(); try { From 77e93045c68e56c969e645276a4352661ddc8d51 Mon Sep 17 00:00:00 2001 From: Avery-Dunn Date: Thu, 5 Jan 2023 08:23:49 -0800 Subject: [PATCH 05/27] Expand javadocs and exception handling --- .../aad/msal4jbrokers/MsalRuntimeBroker.java | 32 +++++++++---------- .../com/microsoft/aad/msal4j/IBroker.java | 12 +++---- .../msal4j/InteractiveRequestParameters.java | 9 +++++- .../aad/msal4j/PublicClientApplication.java | 2 +- 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java index a96fdd33..aaf944aa 100644 --- a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java +++ b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java @@ -9,7 +9,7 @@ import com.microsoft.aad.msal4j.PublicClientApplication; import com.microsoft.aad.msal4j.SilentParameters; import com.microsoft.aad.msal4j.UserNamePasswordParameters; -import com.microsoft.aad.msal4j.MsalException; +import com.microsoft.aad.msal4j.MsalClientException; import com.microsoft.aad.msal4j.AuthenticationErrorCode; import com.microsoft.aad.msal4j.IAccount; import com.microsoft.azure.javamsalruntime.Account; @@ -36,20 +36,20 @@ public class MsalRuntimeBroker implements IBroker { // so when an MsalRuntimeBroker is created this will cause the interop layer to initialize interop = new MsalRuntimeInterop(); } catch (MsalInteropException e) { - //TODO: Meaningful error message explaining why MSALRuntime failed to initialize (unsupported platform, error from MSALRuntime, etc.) + throw new MsalClientException(String.format("Could not initialize MSALRuntime: %s", e.getErrorMessage()), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); } } @Override public void acquireToken(PublicClientApplication application, SilentParameters parameters, CompletableFuture future) { Account accountResult = null; - //If request has an account ID, MSALRuntime data cached for that account - // try to get account info from MSALRuntime + + //If request has an account ID, MSALRuntime likely has data cached for that account that we can retrieve if (parameters.account() != null) { try { accountResult = ((ReadAccountResult) interop.readAccountById(parameters.account().homeAccountId(), application.correlationId()).get()).getAccount(); - } catch (InterruptedException | ExecutionException e) { - //TODO: these exceptions can occur when waiting on a result from MSALRuntime. Not possible to continue? Rethrow exception or create MSAL exception? + } catch (InterruptedException | ExecutionException ex) { + throw new MsalClientException(String.format("MSALRuntime async operation interrupted when waiting for result: %s", ex.getMessage()), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); } } @@ -79,9 +79,9 @@ public void acquireToken(PublicClientApplication application, SilentParameters p ((AuthResult) authResult).getAccount().getClientInfo(), ((AuthResult) authResult).getAccessTokenExpirationTime()))); } catch (MsalInteropException interopException) { - throw new MsalException(interopException.getErrorMessage(), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); + throw new MsalClientException(interopException.getErrorMessage(), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); } catch (InterruptedException | ExecutionException ex) { - //TODO: these exceptions can occur when waiting on a result from MSALRuntime. Not possible to continue? Rethrow exception or create MSAL exception? + throw new MsalClientException(String.format("MSALRuntime async operation interrupted when waiting for result: %s", ex.getMessage()), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); } } @@ -112,9 +112,9 @@ public void acquireToken(PublicClientApplication application, InteractiveRequest ((AuthResult) authResult).getAccount().getClientInfo(), ((AuthResult) authResult).getAccessTokenExpirationTime()))); } catch (MsalInteropException interopException) { - throw new MsalException(interopException.getErrorMessage(), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); + throw new MsalClientException(interopException.getErrorMessage(), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); } catch (InterruptedException | ExecutionException ex) { - //TODO: these exceptions can occur when waiting on a result from MSALRuntime. Not possible to continue? Retrow exception or create MSAL exception? + throw new MsalClientException(String.format("MSALRuntime async operation interrupted when waiting for result: %s", ex.getMessage()), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); } } @@ -141,8 +141,6 @@ public void acquireToken(PublicClientApplication application, UserNamePasswordPa setFutureToCancel(future, currentMsalRuntimeFuture); Account accountResult = ((AuthResult) currentMsalRuntimeFuture.get()).getAccount(); - //Either account was already cached or silent sign in was successful, so we can retrieve tokens from MSALRuntime, - // parse the result into an MSAL Java AuthenticationResult, and complete the future currentMsalRuntimeFuture = interop.acquireTokenSilently(authParameters, application.correlationId(), accountResult); setFutureToCancel(future, currentMsalRuntimeFuture); currentMsalRuntimeFuture.thenApply(authResult -> future.complete(parseBrokerAuthResult( @@ -153,9 +151,9 @@ public void acquireToken(PublicClientApplication application, UserNamePasswordPa ((AuthResult) authResult).getAccount().getClientInfo(), ((AuthResult) authResult).getAccessTokenExpirationTime()))); } catch (MsalInteropException interopException) { - throw new MsalException(interopException.getErrorMessage(), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); + throw new MsalClientException(interopException.getErrorMessage(), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); } catch (InterruptedException | ExecutionException ex) { - //TODO: these exceptions can occur when waiting on a result from MSALRuntime. Not possible to continue? Retrow exception or create MSAL exception? + throw new MsalClientException(String.format("MSALRuntime async operation interrupted when waiting for result: %s", ex.getMessage()), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); } } @@ -169,9 +167,9 @@ public void removeAccount(PublicClientApplication application, IAccount msalJava interop.signOutSilently(application.clientId(), application.correlationId(), msalRuntimeAccount); } } catch (MsalInteropException interopException) { - throw new MsalException(interopException.getErrorMessage(), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); + throw new MsalClientException(interopException.getErrorMessage(), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); } catch (InterruptedException | ExecutionException ex) { - //TODO: these exceptions can occur when waiting on a result from MSALRuntime. Not possible to continue? Retrow exception or create MSAL exception? + throw new MsalClientException(String.format("MSALRuntime async operation interrupted when waiting for result: %s", ex.getMessage()), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); } } @@ -186,7 +184,7 @@ public void setFutureToCancel(CompletableFuture future, M future.whenComplete((result, ex) -> {if (ex instanceof CancellationException) futureToCancel.cancelAsyncOperation();}); } - //Simple manual test for early development/testing + //Simple manual test for early development/testing, will be removed for final version public static void main(String args[]) throws MalformedURLException, ExecutionException, InterruptedException { String clientId = "903c8a8a-9e74-415e-9921-711a293d90cb"; String authority = "https://login.microsoftonline.com/common"; diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java index 5c85dfe4..ae4fb75e 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java @@ -16,13 +16,6 @@ */ public interface IBroker { - /** - * Checks if an IBroker implementation is accessible by MSAL Java - */ - default boolean isAvailable(){ - return false; - } - /** * Acquire a token silently, i.e. without direct user interaction * @@ -54,6 +47,11 @@ default void removeAccount(PublicClientApplication application, IAccount account //TODO: Any better place to put this helper method? This feels wrong //AuthenticationResult requires many package-private classes that broker package can't access, so helper methods will be // made for each broker to create the sort of AuthenticationResult that the rest of MSAL Java expects + + /** + * MSAL Java's AuthenticationResult requires several package-private classes that a broker implementation can't access, + * so this helper method can be used to create AuthenticationResults from within the MSAL Java package + */ default IAuthenticationResult parseBrokerAuthResult(String authority, String idToken, String accessToken, String accountId, String clientInfo, long accessTokenExpirationTime) { 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 f6e77b91..a41d1832 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 @@ -101,7 +101,14 @@ public class InteractiveRequestParameters implements IAcquireTokenParameters { private boolean instanceAware; /** - * The parent window handle used to open UI elements in non-browser scenarios + * The parent window handle used to open UI elements with the correct parent + * + * + * For browser scenarios and Windows console applications, this value should not need to be set + * + * For Windows console applications, MSAL Java will attempt to discover the console's window handle if this parameter is not set + * + * For scenarios where MSAL Java is responsible for opening UI elements (such as when using MSALRuntime), this parameter is required and an exception will be thrown if not set */ private long windowHandle; diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/PublicClientApplication.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/PublicClientApplication.java index 5155245c..d8a13356 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/PublicClientApplication.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/PublicClientApplication.java @@ -192,7 +192,7 @@ private Builder(String clientId) { /** * Implementation of IBroker that will be used to retrieve tokens *

- * Setting this will cause MSAL Java to use a broker (such as WAM/MSALRuntime) in flows that support that broker + * Setting this will cause MSAL Java to use the given broker implementation to retrieve tokens from a broker (such as WAM/MSALRuntime) in flows that support it */ public PublicClientApplication.Builder broker(IBroker val) { this.broker = val; From 45b4a406a2b89e50510bf2d84193fe9aeffe0abf Mon Sep 17 00:00:00 2001 From: Avery-Dunn Date: Thu, 5 Jan 2023 09:04:43 -0800 Subject: [PATCH 06/27] Address code review comments --- .../com/microsoft/aad/msal4j/AuthenticationErrorCode.java | 2 +- .../com/microsoft/aad/msal4j/PublicClientApplication.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthenticationErrorCode.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthenticationErrorCode.java index 6992b48c..9852f58f 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthenticationErrorCode.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthenticationErrorCode.java @@ -127,5 +127,5 @@ public class AuthenticationErrorCode { * Indicates an error from the Java/MSALRuntime interop layer used by the Java Brokers package, * and will generally just be forwarding an error message from the interop layer or MSALRuntime itself */ - public final static String MSALRUNTIME_INTEROP_ERROR = "missing_broker"; + public final static String MSALRUNTIME_INTEROP_ERROR = "interop_error"; } diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/PublicClientApplication.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/PublicClientApplication.java index d8a13356..20ec0ed0 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/PublicClientApplication.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/PublicClientApplication.java @@ -126,12 +126,12 @@ public CompletableFuture acquireToken(InteractiveRequestP if (this.broker != null) { broker.acquireToken(this, parameters, future); - futureReference.set(future); } else { future = executeRequest(interactiveRequest); - futureReference.set(future); } + futureReference.set(future); + return future; } From 3df9af2ac4981ec44db34430b3819e5dcf87d158 Mon Sep 17 00:00:00 2001 From: Avery-Dunn Date: Thu, 5 Jan 2023 10:30:54 -0800 Subject: [PATCH 07/27] Simplify future chaining, address code review comments --- .../aad/msal4jbrokers/MsalRuntimeBroker.java | 68 +++++++++++-------- .../aad/msal4j/AuthenticationErrorCode.java | 9 ++- .../com/microsoft/aad/msal4j/IBroker.java | 12 ++-- .../aad/msal4j/PublicClientApplication.java | 12 ++-- 4 files changed, 56 insertions(+), 45 deletions(-) diff --git a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java index aaf944aa..8e6dc38b 100644 --- a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java +++ b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java @@ -41,9 +41,10 @@ public class MsalRuntimeBroker implements IBroker { } @Override - public void acquireToken(PublicClientApplication application, SilentParameters parameters, CompletableFuture future) { + public CompletableFuture acquireToken(PublicClientApplication application, SilentParameters parameters) { Account accountResult = null; - + CompletableFuture future = new CompletableFuture<>(); + //If request has an account ID, MSALRuntime likely has data cached for that account that we can retrieve if (parameters.account() != null) { try { @@ -59,34 +60,40 @@ public void acquireToken(PublicClientApplication application, SilentParameters p application.authority(), parameters.scopes().toString()) .build()) { - MsalRuntimeFuture currentAsyncOperation; + + MsalRuntimeFuture msalRuntimeAsyncOperation = new MsalRuntimeFuture(); + setFutureToCancel(future, msalRuntimeAsyncOperation); //Account was not cached in MSALRuntime, must perform sign in first to populate account info if (accountResult == null) { - currentAsyncOperation = interop.signInSilently(authParameters, application.correlationId()); - setFutureToCancel(future, currentAsyncOperation); - accountResult = ((AuthResult) currentAsyncOperation.get()).getAccount(); + msalRuntimeAsyncOperation = interop.signInSilently(authParameters, application.correlationId()); + accountResult = ((AuthResult) msalRuntimeAsyncOperation.get()).getAccount(); } + //Either account was already cached or silent sign in was successful, so we can retrieve tokens from MSALRuntime, // parse the result into an MSAL Java AuthenticationResult, and complete the future - currentAsyncOperation = interop.acquireTokenSilently(authParameters, application.correlationId(), accountResult); - setFutureToCancel(future, currentAsyncOperation); - currentAsyncOperation.thenApply(authResult -> future.complete(parseBrokerAuthResult( + msalRuntimeAsyncOperation = interop.acquireTokenSilently(authParameters, application.correlationId(), accountResult); + setFutureToCancel(future, msalRuntimeAsyncOperation); + msalRuntimeAsyncOperation.thenApply(authResult -> future.complete(parseBrokerAuthResult( application.authority(), ((AuthResult) authResult).getIdToken(), ((AuthResult) authResult).getAccessToken(), ((AuthResult) authResult).getAccount().getAccountId(), ((AuthResult) authResult).getAccount().getClientInfo(), ((AuthResult) authResult).getAccessTokenExpirationTime()))); + } catch (MsalInteropException interopException) { throw new MsalClientException(interopException.getErrorMessage(), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); } catch (InterruptedException | ExecutionException ex) { throw new MsalClientException(String.format("MSALRuntime async operation interrupted when waiting for result: %s", ex.getMessage()), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); } + + return future; } @Override - public void acquireToken(PublicClientApplication application, InteractiveRequestParameters parameters, CompletableFuture future) { + public CompletableFuture acquireToken(PublicClientApplication application, InteractiveRequestParameters parameters) { + CompletableFuture future = new CompletableFuture<>(); try (AuthParameters authParameters = new AuthParameters @@ -96,15 +103,14 @@ public void acquireToken(PublicClientApplication application, InteractiveRequest .claims(parameters.claims().toString()) .build()) { - MsalRuntimeFuture currentMsalRuntimeFuture; + MsalRuntimeFuture msalRuntimeAsyncOperation = new MsalRuntimeFuture(); + setFutureToCancel(future, msalRuntimeAsyncOperation); - currentMsalRuntimeFuture = interop.signInInteractively(parameters.windowHandle(), authParameters, application.correlationId(), parameters.loginHint()); - setFutureToCancel(future, currentMsalRuntimeFuture); - Account accountResult = ((AuthResult) currentMsalRuntimeFuture.get()).getAccount(); + msalRuntimeAsyncOperation = interop.signInInteractively(parameters.windowHandle(), authParameters, application.correlationId(), parameters.loginHint()); + Account accountResult = ((AuthResult) msalRuntimeAsyncOperation.get()).getAccount(); - currentMsalRuntimeFuture = interop.acquireTokenInteractively(parameters.windowHandle(), authParameters, application.correlationId(), accountResult); - setFutureToCancel(future, currentMsalRuntimeFuture); - currentMsalRuntimeFuture.thenApply(authResult -> future.complete(parseBrokerAuthResult( + msalRuntimeAsyncOperation = interop.acquireTokenInteractively(parameters.windowHandle(), authParameters, application.correlationId(), accountResult); + msalRuntimeAsyncOperation.thenApply(authResult -> future.complete(parseBrokerAuthResult( application.authority(), ((AuthResult) authResult).getIdToken(), ((AuthResult) authResult).getAccessToken(), @@ -116,6 +122,8 @@ public void acquireToken(PublicClientApplication application, InteractiveRequest } catch (InterruptedException | ExecutionException ex) { throw new MsalClientException(String.format("MSALRuntime async operation interrupted when waiting for result: %s", ex.getMessage()), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); } + + return future; } /** @@ -123,7 +131,9 @@ public void acquireToken(PublicClientApplication application, InteractiveRequest */ @Deprecated @Override - public void acquireToken(PublicClientApplication application, UserNamePasswordParameters parameters, CompletableFuture future) { + public CompletableFuture acquireToken(PublicClientApplication application, UserNamePasswordParameters parameters) { + + CompletableFuture future = new CompletableFuture<>(); try (AuthParameters authParameters = new AuthParameters @@ -133,17 +143,16 @@ public void acquireToken(PublicClientApplication application, UserNamePasswordPa .claims(parameters.claims().toString()) .build()) { - MsalRuntimeFuture currentMsalRuntimeFuture; - authParameters.setUsernamePassword(parameters.username(), new String(parameters.password())); - currentMsalRuntimeFuture = interop.signInSilently(authParameters, application.correlationId()); - setFutureToCancel(future, currentMsalRuntimeFuture); - Account accountResult = ((AuthResult) currentMsalRuntimeFuture.get()).getAccount(); + MsalRuntimeFuture msalRuntimeAsyncOperation = new MsalRuntimeFuture(); + setFutureToCancel(future, msalRuntimeAsyncOperation); + + msalRuntimeAsyncOperation = interop.signInSilently(authParameters, application.correlationId()); + Account accountResult = ((AuthResult) msalRuntimeAsyncOperation.get()).getAccount(); - currentMsalRuntimeFuture = interop.acquireTokenSilently(authParameters, application.correlationId(), accountResult); - setFutureToCancel(future, currentMsalRuntimeFuture); - currentMsalRuntimeFuture.thenApply(authResult -> future.complete(parseBrokerAuthResult( + msalRuntimeAsyncOperation = interop.acquireTokenSilently(authParameters, application.correlationId(), accountResult); + msalRuntimeAsyncOperation.thenApply(authResult -> future.complete(parseBrokerAuthResult( application.authority(), ((AuthResult) authResult).getIdToken(), ((AuthResult) authResult).getAccessToken(), @@ -155,6 +164,8 @@ public void acquireToken(PublicClientApplication application, UserNamePasswordPa } catch (InterruptedException | ExecutionException ex) { throw new MsalClientException(String.format("MSALRuntime async operation interrupted when waiting for result: %s", ex.getMessage()), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); } + + return future; } @Override @@ -197,13 +208,12 @@ public static void main(String args[]) throws MalformedURLException, ExecutionEx clientId). authority(authority). correlationId(UUID.randomUUID().toString()). + broker(broker). build(); SilentParameters parameters = SilentParameters.builder(Collections.singleton(scopes)).build(); - CompletableFuture future = new CompletableFuture<>(); - - broker.acquireToken(pca, parameters, future); + CompletableFuture future = pca.acquireTokenSilently(parameters); IAuthenticationResult result = future.get(); diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthenticationErrorCode.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthenticationErrorCode.java index 9852f58f..1ea0232e 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthenticationErrorCode.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthenticationErrorCode.java @@ -124,8 +124,13 @@ public class AuthenticationErrorCode { public final static String MISSING_BROKER = "missing_broker"; /** - * Indicates an error from the Java/MSALRuntime interop layer used by the Java Brokers package, + * Indicates an error from the MSAL Java/MSALRuntime interop layer used by the Java Brokers package, * and will generally just be forwarding an error message from the interop layer or MSALRuntime itself */ - public final static String MSALRUNTIME_INTEROP_ERROR = "interop_error"; + public final static String MSALRUNTIME_INTEROP_ERROR = "interop_package_error"; + + /** + * Indicates an error in the MSAL Java Brokers package + */ + public final static String MSALJAVA_BROKERS_ERROR = "brokers_package_error"; } diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java index ae4fb75e..389d3e19 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java @@ -22,21 +22,21 @@ public interface IBroker { * This may be accomplished by returning tokens from a token cache, using cached refresh tokens to get new tokens, * or via any authentication flow where a user is not prompted to enter credentials */ - default void acquireToken(PublicClientApplication application, SilentParameters requestParameters, CompletableFuture future) { + default CompletableFuture acquireToken(PublicClientApplication application, SilentParameters requestParameters) { throw new MsalClientException("Broker implementation missing", AuthenticationErrorCode.MISSING_BROKER); } /** * Acquire a token interactively, by prompting users to enter their credentials in some way */ - default void acquireToken(PublicClientApplication application, InteractiveRequestParameters parameters, CompletableFuture future) { + default CompletableFuture acquireToken(PublicClientApplication application, InteractiveRequestParameters parameters) { throw new MsalClientException("Broker implementation missing", AuthenticationErrorCode.MISSING_BROKER); } /** * Acquire a token silently, i.e. without direct user interaction, using username/password authentication */ - default void acquireToken(PublicClientApplication application, UserNamePasswordParameters parameters, CompletableFuture future) { + default CompletableFuture acquireToken(PublicClientApplication application, UserNamePasswordParameters parameters) { throw new MsalClientException("Broker implementation missing", AuthenticationErrorCode.MISSING_BROKER); } @@ -44,10 +44,6 @@ default void removeAccount(PublicClientApplication application, IAccount account throw new MsalClientException("Broker implementation missing", AuthenticationErrorCode.MISSING_BROKER); } - //TODO: Any better place to put this helper method? This feels wrong - //AuthenticationResult requires many package-private classes that broker package can't access, so helper methods will be - // made for each broker to create the sort of AuthenticationResult that the rest of MSAL Java expects - /** * MSAL Java's AuthenticationResult requires several package-private classes that a broker implementation can't access, * so this helper method can be used to create AuthenticationResults from within the MSAL Java package @@ -75,7 +71,7 @@ default IAuthenticationResult parseBrokerAuthResult(String authority, String idT builder.expiresOn(accessTokenExpirationTime); } } catch (Exception e) { - //TODO: throw new msalexception. Could a valid broker result be an invalid MSAL Java result? + throw new MsalClientException(String.format("Exception when converting broker result to MSAL Java AuthenticationResult: %s", e.getMessage()), AuthenticationErrorCode.MSALJAVA_BROKERS_ERROR); } return builder.build(); } diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/PublicClientApplication.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/PublicClientApplication.java index 20ec0ed0..4f496b39 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/PublicClientApplication.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/PublicClientApplication.java @@ -37,10 +37,10 @@ public CompletableFuture acquireToken(UserNamePasswordPar parameters, UserIdentifier.fromUpn(parameters.username())); - CompletableFuture future = new CompletableFuture<>(); + CompletableFuture future; if (this.broker != null) { - broker.acquireToken(this, parameters, future); + future = broker.acquireToken(this, parameters); } else { UserNamePasswordRequest userNamePasswordRequest = new UserNamePasswordRequest(parameters, @@ -122,10 +122,10 @@ public CompletableFuture acquireToken(InteractiveRequestP this, context); - CompletableFuture future = new CompletableFuture<>(); + CompletableFuture future; if (this.broker != null) { - broker.acquireToken(this, parameters, future); + future = broker.acquireToken(this, parameters); } else { future = executeRequest(interactiveRequest); } @@ -137,10 +137,10 @@ public CompletableFuture acquireToken(InteractiveRequestP @Override public CompletableFuture acquireTokenSilently(SilentParameters parameters) throws MalformedURLException { - CompletableFuture future = new CompletableFuture<>(); + CompletableFuture future; if (this.broker != null) { - broker.acquireToken(this, parameters, future); + future = broker.acquireToken(this, parameters); } else { future = super.acquireTokenSilently(parameters); } From 2fd56ecae61887f9205717b553ebd67cb444e4c9 Mon Sep 17 00:00:00 2001 From: Avery-Dunn Date: Tue, 10 Jan 2023 18:33:25 -0800 Subject: [PATCH 08/27] Reorganize future chaining, fix testing issues --- .../aad/msal4jbrokers/MsalRuntimeBroker.java | 128 +++++++----------- 1 file changed, 50 insertions(+), 78 deletions(-) diff --git a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java index 8e6dc38b..9bd2a445 100644 --- a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java +++ b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java @@ -35,6 +35,7 @@ public class MsalRuntimeBroker implements IBroker { //MsalRuntimeInterop performs various initialization steps in a similar static block, // so when an MsalRuntimeBroker is created this will cause the interop layer to initialize interop = new MsalRuntimeInterop(); + interop.startupMsalRuntime(); } catch (MsalInteropException e) { throw new MsalClientException(String.format("Could not initialize MSALRuntime: %s", e.getErrorMessage()), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); } @@ -43,7 +44,6 @@ public class MsalRuntimeBroker implements IBroker { @Override public CompletableFuture acquireToken(PublicClientApplication application, SilentParameters parameters) { Account accountResult = null; - CompletableFuture future = new CompletableFuture<>(); //If request has an account ID, MSALRuntime likely has data cached for that account that we can retrieve if (parameters.account() != null) { @@ -54,76 +54,61 @@ public CompletableFuture acquireToken(PublicClientApplica } } - try (AuthParameters authParameters = - new AuthParameters - .AuthParametersBuilder(application.clientId(), - application.authority(), - parameters.scopes().toString()) - .build()) { - - MsalRuntimeFuture msalRuntimeAsyncOperation = new MsalRuntimeFuture(); - setFutureToCancel(future, msalRuntimeAsyncOperation); + try { + AuthParameters authParameters = new AuthParameters + .AuthParametersBuilder(application.clientId(), + application.authority(), + String.join(" ", parameters.scopes())) + .build(); - //Account was not cached in MSALRuntime, must perform sign in first to populate account info if (accountResult == null) { - msalRuntimeAsyncOperation = interop.signInSilently(authParameters, application.correlationId()); - accountResult = ((AuthResult) msalRuntimeAsyncOperation.get()).getAccount(); + return interop.signInSilently(authParameters, application.correlationId()) + .thenCompose(acctResult -> interop.acquireTokenSilently(authParameters, application.correlationId(), ((AuthResult) acctResult).getAccount())) + .thenApply(authResult -> parseBrokerAuthResult( + application.authority(), + ((AuthResult) authResult).getIdToken(), + ((AuthResult) authResult).getAccessToken(), + ((AuthResult) authResult).getAccount().getAccountId(), + ((AuthResult) authResult).getAccount().getClientInfo(), + ((AuthResult) authResult).getAccessTokenExpirationTime())); + } else { + return interop.acquireTokenSilently(authParameters, application.correlationId(), accountResult) + .thenApply(authResult -> parseBrokerAuthResult(application.authority(), + ((AuthResult) authResult).getIdToken(), + ((AuthResult) authResult).getAccessToken(), + ((AuthResult) authResult).getAccount().getAccountId(), + ((AuthResult) authResult).getAccount().getClientInfo(), + ((AuthResult) authResult).getAccessTokenExpirationTime()) + + ); } - - //Either account was already cached or silent sign in was successful, so we can retrieve tokens from MSALRuntime, - // parse the result into an MSAL Java AuthenticationResult, and complete the future - msalRuntimeAsyncOperation = interop.acquireTokenSilently(authParameters, application.correlationId(), accountResult); - setFutureToCancel(future, msalRuntimeAsyncOperation); - msalRuntimeAsyncOperation.thenApply(authResult -> future.complete(parseBrokerAuthResult( - application.authority(), - ((AuthResult) authResult).getIdToken(), - ((AuthResult) authResult).getAccessToken(), - ((AuthResult) authResult).getAccount().getAccountId(), - ((AuthResult) authResult).getAccount().getClientInfo(), - ((AuthResult) authResult).getAccessTokenExpirationTime()))); - } catch (MsalInteropException interopException) { throw new MsalClientException(interopException.getErrorMessage(), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); - } catch (InterruptedException | ExecutionException ex) { - throw new MsalClientException(String.format("MSALRuntime async operation interrupted when waiting for result: %s", ex.getMessage()), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); } - - return future; } @Override public CompletableFuture acquireToken(PublicClientApplication application, InteractiveRequestParameters parameters) { - CompletableFuture future = new CompletableFuture<>(); - - try (AuthParameters authParameters = - new AuthParameters - .AuthParametersBuilder(application.clientId(), - application.authority(), - parameters.scopes().toString()) - .claims(parameters.claims().toString()) - .build()) { - - MsalRuntimeFuture msalRuntimeAsyncOperation = new MsalRuntimeFuture(); - setFutureToCancel(future, msalRuntimeAsyncOperation); - - msalRuntimeAsyncOperation = interop.signInInteractively(parameters.windowHandle(), authParameters, application.correlationId(), parameters.loginHint()); - Account accountResult = ((AuthResult) msalRuntimeAsyncOperation.get()).getAccount(); - - msalRuntimeAsyncOperation = interop.acquireTokenInteractively(parameters.windowHandle(), authParameters, application.correlationId(), accountResult); - msalRuntimeAsyncOperation.thenApply(authResult -> future.complete(parseBrokerAuthResult( + try { + AuthParameters authParameters = new AuthParameters + .AuthParametersBuilder(application.clientId(), + application.authority(), + String.join(" ", parameters.scopes())) + .build(); + + return interop.signInInteractively(parameters.windowHandle(), authParameters, application.correlationId(), parameters.loginHint()) + .thenCompose(acctResult -> interop.acquireTokenInteractively(parameters.windowHandle(), authParameters, application.correlationId(), ((AuthResult) acctResult).getAccount())) + .thenApply(authResult -> parseBrokerAuthResult( application.authority(), ((AuthResult) authResult).getIdToken(), ((AuthResult) authResult).getAccessToken(), ((AuthResult) authResult).getAccount().getAccountId(), ((AuthResult) authResult).getAccount().getClientInfo(), - ((AuthResult) authResult).getAccessTokenExpirationTime()))); + ((AuthResult) authResult).getAccessTokenExpirationTime()) + ); } catch (MsalInteropException interopException) { throw new MsalClientException(interopException.getErrorMessage(), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); - } catch (InterruptedException | ExecutionException ex) { - throw new MsalClientException(String.format("MSALRuntime async operation interrupted when waiting for result: %s", ex.getMessage()), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); } - - return future; } /** @@ -132,46 +117,32 @@ public CompletableFuture acquireToken(PublicClientApplica @Deprecated @Override public CompletableFuture acquireToken(PublicClientApplication application, UserNamePasswordParameters parameters) { - - CompletableFuture future = new CompletableFuture<>(); - try (AuthParameters authParameters = new AuthParameters .AuthParametersBuilder(application.clientId(), application.authority(), - parameters.scopes().toString()) - .claims(parameters.claims().toString()) + String.join(" ", parameters.scopes())) .build()) { authParameters.setUsernamePassword(parameters.username(), new String(parameters.password())); - MsalRuntimeFuture msalRuntimeAsyncOperation = new MsalRuntimeFuture(); - setFutureToCancel(future, msalRuntimeAsyncOperation); - - msalRuntimeAsyncOperation = interop.signInSilently(authParameters, application.correlationId()); - Account accountResult = ((AuthResult) msalRuntimeAsyncOperation.get()).getAccount(); - - msalRuntimeAsyncOperation = interop.acquireTokenSilently(authParameters, application.correlationId(), accountResult); - msalRuntimeAsyncOperation.thenApply(authResult -> future.complete(parseBrokerAuthResult( + return interop.signInSilently(authParameters, application.correlationId()) + .thenCompose(acctResult -> interop.acquireTokenSilently(authParameters, application.correlationId(), ((AuthResult) acctResult).getAccount())) + .thenApply(authResult -> parseBrokerAuthResult( application.authority(), ((AuthResult) authResult).getIdToken(), ((AuthResult) authResult).getAccessToken(), ((AuthResult) authResult).getAccount().getAccountId(), ((AuthResult) authResult).getAccount().getClientInfo(), - ((AuthResult) authResult).getAccessTokenExpirationTime()))); + ((AuthResult) authResult).getAccessTokenExpirationTime())); } catch (MsalInteropException interopException) { throw new MsalClientException(interopException.getErrorMessage(), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); - } catch (InterruptedException | ExecutionException ex) { - throw new MsalClientException(String.format("MSALRuntime async operation interrupted when waiting for result: %s", ex.getMessage()), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); } - - return future; } @Override public void removeAccount(PublicClientApplication application, IAccount msalJavaAccount) { - try { - + try { Account msalRuntimeAccount = ((ReadAccountResult) interop.readAccountById(msalJavaAccount.homeAccountId(), application.correlationId()).get()).getAccount(); if (msalRuntimeAccount != null) { @@ -186,13 +157,15 @@ public void removeAccount(PublicClientApplication application, IAccount msalJava /** * If the future returned by MSAL Java is canceled before we can complete it using a result from MSALRuntime, we must cancel the async operations MSALRuntime is performing - * + *

* However, there are multiple sequential calls that need to be made to MSALRuntime, each of which returns an MsalRuntimeFuture which we'd need to cancel - * + *

* This utility method encapsulates the logic for swapping which MsalRuntimeFuture gets canceled if the main future is canceled */ public void setFutureToCancel(CompletableFuture future, MsalRuntimeFuture futureToCancel) { - future.whenComplete((result, ex) -> {if (ex instanceof CancellationException) futureToCancel.cancelAsyncOperation();}); + future.whenComplete((result, ex) -> { + if (ex instanceof CancellationException) futureToCancel.cancelAsyncOperation(); + }); } //Simple manual test for early development/testing, will be removed for final version @@ -200,8 +173,7 @@ public static void main(String args[]) throws MalformedURLException, ExecutionEx String clientId = "903c8a8a-9e74-415e-9921-711a293d90cb"; String authority = "https://login.microsoftonline.com/common"; String scopes = "https://graph.microsoft.com/.default"; - - + MsalRuntimeBroker broker = new MsalRuntimeBroker(); PublicClientApplication pca = PublicClientApplication.builder( From fdf9ef7e5bea941f29c0eae35e883310c5811f28 Mon Sep 17 00:00:00 2001 From: Avery-Dunn Date: Thu, 12 Jan 2023 08:41:22 -0800 Subject: [PATCH 09/27] Adjust how broker availability is checked --- .../main/java/com/microsoft/aad/msal4j/IBroker.java | 4 ++++ .../aad/msal4j/PublicClientApplication.java | 13 +++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java index 389d3e19..69906319 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java @@ -44,6 +44,10 @@ default void removeAccount(PublicClientApplication application, IAccount account throw new MsalClientException("Broker implementation missing", AuthenticationErrorCode.MISSING_BROKER); } + default boolean isBrokerAvailable() { + throw new MsalClientException("Broker implementation missing", AuthenticationErrorCode.MISSING_BROKER); + } + /** * MSAL Java's AuthenticationResult requires several package-private classes that a broker implementation can't access, * so this helper method can be used to create AuthenticationResults from within the MSAL Java package diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/PublicClientApplication.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/PublicClientApplication.java index 4f496b39..11b19604 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/PublicClientApplication.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/PublicClientApplication.java @@ -25,6 +25,7 @@ public class PublicClientApplication extends AbstractClientApplicationBase imple private final ClientAuthenticationPost clientAuthentication; private IBroker broker; + private boolean brokerEnabled; @Override public CompletableFuture acquireToken(UserNamePasswordParameters parameters) { @@ -39,7 +40,7 @@ public CompletableFuture acquireToken(UserNamePasswordPar CompletableFuture future; - if (this.broker != null) { + if (brokerEnabled) { future = broker.acquireToken(this, parameters); } else { UserNamePasswordRequest userNamePasswordRequest = @@ -124,7 +125,7 @@ public CompletableFuture acquireToken(InteractiveRequestP CompletableFuture future; - if (this.broker != null) { + if (brokerEnabled) { future = broker.acquireToken(this, parameters); } else { future = executeRequest(interactiveRequest); @@ -139,7 +140,7 @@ public CompletableFuture acquireToken(InteractiveRequestP public CompletableFuture acquireTokenSilently(SilentParameters parameters) throws MalformedURLException { CompletableFuture future; - if (this.broker != null) { + if (brokerEnabled) { future = broker.acquireToken(this, parameters); } else { future = super.acquireTokenSilently(parameters); @@ -150,7 +151,7 @@ public CompletableFuture acquireTokenSilently(SilentParam @Override public CompletableFuture removeAccount(IAccount account) { - if (this.broker != null) { + if (brokerEnabled) { broker.removeAccount(this, account); } @@ -164,6 +165,7 @@ private PublicClientApplication(Builder builder) { this.clientAuthentication = new ClientAuthenticationPost(ClientAuthenticationMethod.NONE, new ClientID(clientId())); this.broker = builder.broker; + this.brokerEnabled = builder.brokerEnabled; } @Override @@ -188,6 +190,7 @@ private Builder(String clientId) { } private IBroker broker = null; + private boolean brokerEnabled = false; /** * Implementation of IBroker that will be used to retrieve tokens @@ -197,6 +200,8 @@ private Builder(String clientId) { public PublicClientApplication.Builder broker(IBroker val) { this.broker = val; + this.brokerEnabled = this.broker.isBrokerAvailable(); + return self(); } From 9152677c41152e9aa8996676caece619de088684 Mon Sep 17 00:00:00 2001 From: Avery-Dunn Date: Thu, 12 Jan 2023 08:53:52 -0800 Subject: [PATCH 10/27] Create automated test --- msal4j-brokers/pom.xml | 6 +++ .../aad/msal4jbrokers/MsalRuntimeBroker.java | 43 +---------------- .../microsoft/aad/msal4jbrokers/BrokerIT.java | 48 +++++++++++++++++++ 3 files changed, 56 insertions(+), 41 deletions(-) create mode 100644 msal4j-brokers/src/test/java/com/microsoft/aad/msal4jbrokers/BrokerIT.java diff --git a/msal4j-brokers/pom.xml b/msal4j-brokers/pom.xml index 502b9072..bd1f0375 100644 --- a/msal4j-brokers/pom.xml +++ b/msal4j-brokers/pom.xml @@ -43,6 +43,12 @@ 1.18.6 provided + + org.testng + testng + 7.1.0 + test + diff --git a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java index 9bd2a445..1177f256 100644 --- a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java +++ b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java @@ -16,14 +16,9 @@ import com.microsoft.azure.javamsalruntime.AuthParameters; import com.microsoft.azure.javamsalruntime.AuthResult; import com.microsoft.azure.javamsalruntime.MsalInteropException; -import com.microsoft.azure.javamsalruntime.MsalRuntimeFuture; import com.microsoft.azure.javamsalruntime.MsalRuntimeInterop; import com.microsoft.azure.javamsalruntime.ReadAccountResult; -import java.net.MalformedURLException; -import java.util.Collections; -import java.util.UUID; -import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; @@ -155,41 +150,7 @@ public void removeAccount(PublicClientApplication application, IAccount msalJava } } - /** - * If the future returned by MSAL Java is canceled before we can complete it using a result from MSALRuntime, we must cancel the async operations MSALRuntime is performing - *

- * However, there are multiple sequential calls that need to be made to MSALRuntime, each of which returns an MsalRuntimeFuture which we'd need to cancel - *

- * This utility method encapsulates the logic for swapping which MsalRuntimeFuture gets canceled if the main future is canceled - */ - public void setFutureToCancel(CompletableFuture future, MsalRuntimeFuture futureToCancel) { - future.whenComplete((result, ex) -> { - if (ex instanceof CancellationException) futureToCancel.cancelAsyncOperation(); - }); - } - - //Simple manual test for early development/testing, will be removed for final version - public static void main(String args[]) throws MalformedURLException, ExecutionException, InterruptedException { - String clientId = "903c8a8a-9e74-415e-9921-711a293d90cb"; - String authority = "https://login.microsoftonline.com/common"; - String scopes = "https://graph.microsoft.com/.default"; - - MsalRuntimeBroker broker = new MsalRuntimeBroker(); - - PublicClientApplication pca = PublicClientApplication.builder( - clientId). - authority(authority). - correlationId(UUID.randomUUID().toString()). - broker(broker). - build(); - - SilentParameters parameters = SilentParameters.builder(Collections.singleton(scopes)).build(); - - CompletableFuture future = pca.acquireTokenSilently(parameters); - - IAuthenticationResult result = future.get(); - - System.out.println(result.idToken()); - System.out.println(result.accessToken()); + public boolean isBrokerAvailable() { + return MsalRuntimeInterop.isPlatformSupported(); } } diff --git a/msal4j-brokers/src/test/java/com/microsoft/aad/msal4jbrokers/BrokerIT.java b/msal4j-brokers/src/test/java/com/microsoft/aad/msal4jbrokers/BrokerIT.java new file mode 100644 index 00000000..49db89e7 --- /dev/null +++ b/msal4j-brokers/src/test/java/com/microsoft/aad/msal4jbrokers/BrokerIT.java @@ -0,0 +1,48 @@ +package com.microsoft.aad.msal4jbrokers; + +import com.microsoft.aad.msal4j.IAuthenticationResult; +import com.microsoft.aad.msal4j.PublicClientApplication; +import com.microsoft.aad.msal4j.SilentParameters; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.net.MalformedURLException; +import java.util.Collections; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class BrokerIT { + + @Test + public void acquireTokenSilent_usingBroker_DefaultOSAccount() throws MalformedURLException, ExecutionException, InterruptedException { + //TODO: Hardcoded params for now, will hopefully be able to copy the Python interop's ID Labs queries. If not, will only be able to do automated testing in the interop package + String clientId = "903c8a8a-9e74-415e-9921-711a293d90cb"; + String authority = "https://login.microsoftonline.com/common"; + String scopes = "https://graph.microsoft.com/.default"; + + MsalRuntimeBroker broker = new MsalRuntimeBroker(); + + PublicClientApplication pca = PublicClientApplication.builder( + clientId). + authority(authority). + correlationId(UUID.randomUUID().toString()). + broker(broker). + build(); + + SilentParameters parameters = SilentParameters.builder(Collections.singleton(scopes)).build(); + + CompletableFuture future = pca.acquireTokenSilently(parameters); + + IAuthenticationResult result = future.get(); + + Assert.assertNotNull(result); + Assert.assertNotNull(result.accessToken()); + Assert.assertNotNull(result.idToken()); + + System.out.println("Access token in result: " + result.accessToken()); + System.out.println("ID token in result: " + result.idToken()); + System.out.println("Account ID in result: " + result.account().homeAccountId()); + System.out.println("Username in result: " + result.account().username()); + } +} From dc79a19d0f937e73e1d60c8c2cf23e3f9cdbe4ff Mon Sep 17 00:00:00 2001 From: Avery-Dunn Date: Thu, 12 Jan 2023 10:01:00 -0800 Subject: [PATCH 11/27] Adjust startup logic --- .../aad/msal4jbrokers/MsalRuntimeBroker.java | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java index 1177f256..8efaab77 100644 --- a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java +++ b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java @@ -18,11 +18,15 @@ import com.microsoft.azure.javamsalruntime.MsalInteropException; import com.microsoft.azure.javamsalruntime.MsalRuntimeInterop; import com.microsoft.azure.javamsalruntime.ReadAccountResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; public class MsalRuntimeBroker implements IBroker { + private static final Logger LOG = LoggerFactory.getLogger(AuthResult.class); + private static MsalRuntimeInterop interop; static { @@ -30,7 +34,6 @@ public class MsalRuntimeBroker implements IBroker { //MsalRuntimeInterop performs various initialization steps in a similar static block, // so when an MsalRuntimeBroker is created this will cause the interop layer to initialize interop = new MsalRuntimeInterop(); - interop.startupMsalRuntime(); } catch (MsalInteropException e) { throw new MsalClientException(String.format("Could not initialize MSALRuntime: %s", e.getErrorMessage()), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); } @@ -150,7 +153,26 @@ public void removeAccount(PublicClientApplication application, IAccount msalJava } } + /** + * Calls MSALRuntime's startup API. If MSALRuntime started successfully, we can assume that the broker is available for use. + * + * If an exception is thrown when trying to start MSALRuntime, we assume that we cannot use the broker and will not make any more attempts to do so. + * + * @return boolean representing whether or not MSALRuntime started successfully + */ + @Override public boolean isBrokerAvailable() { - return MsalRuntimeInterop.isPlatformSupported(); + try { + interop.startupMsalRuntime(); + + LOG.info("MSALRuntime started successfully. MSAL Java will use MSALRuntime in all supported broker flows."); + + return true; + } catch (MsalInteropException e) { + LOG.warn("Exception thrown when trying to start MSALRuntime: {}", e.getErrorMessage()); + LOG.warn("MSALRuntime could not be started. MSAL Java will fall back to non-broker flows."); + + return false; + } } } From ab4f576925f460e8b06e4c2267c6652e5934348b Mon Sep 17 00:00:00 2001 From: Avery-Dunn Date: Wed, 18 Jan 2023 07:44:36 -0800 Subject: [PATCH 12/27] Correct version number for interop --- msal4j-brokers/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msal4j-brokers/pom.xml b/msal4j-brokers/pom.xml index bd1f0375..b5af18ef 100644 --- a/msal4j-brokers/pom.xml +++ b/msal4j-brokers/pom.xml @@ -35,7 +35,7 @@ com.microsoft.azure javamsalruntime - 1.0.0 + [0.13.3,) org.projectlombok From a518eda7cce6a8ad07ced51ff09cba6162e196c9 Mon Sep 17 00:00:00 2001 From: Avery-Dunn Date: Wed, 18 Jan 2023 07:51:46 -0800 Subject: [PATCH 13/27] Correct broker versioning --- msal4j-brokers/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msal4j-brokers/pom.xml b/msal4j-brokers/pom.xml index b5af18ef..e3a28733 100644 --- a/msal4j-brokers/pom.xml +++ b/msal4j-brokers/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.microsoft.azure msal4j-brokers - 0.0.1 + 1.0.0-SNAPSHOT jar msal4j-brokers From 9349e7e95abd6e04f910e44d6e2a1a4d6c9afb68 Mon Sep 17 00:00:00 2001 From: Avery-Dunn Date: Tue, 24 Jan 2023 10:47:57 -0800 Subject: [PATCH 14/27] Move broker tests to MSAL Java package --- msal4j-brokers/pom.xml | 2 +- msal4j-sdk/pom.xml | 6 ++++++ .../java/com.microsoft.aad.msal4j}/BrokerIT.java | 6 ++---- 3 files changed, 9 insertions(+), 5 deletions(-) rename {msal4j-brokers/src/test/java/com/microsoft/aad/msal4jbrokers => msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j}/BrokerIT.java (90%) diff --git a/msal4j-brokers/pom.xml b/msal4j-brokers/pom.xml index e3a28733..3b5cc754 100644 --- a/msal4j-brokers/pom.xml +++ b/msal4j-brokers/pom.xml @@ -35,7 +35,7 @@ com.microsoft.azure javamsalruntime - [0.13.3,) + 0.13.3 org.projectlombok diff --git a/msal4j-sdk/pom.xml b/msal4j-sdk/pom.xml index a7b4fde5..5af98c9f 100644 --- a/msal4j-sdk/pom.xml +++ b/msal4j-sdk/pom.xml @@ -133,6 +133,12 @@ 2.7 test + + com.microsoft.azure + msal4j-brokers + 1.0.0-SNAPSHOT + test + diff --git a/msal4j-brokers/src/test/java/com/microsoft/aad/msal4jbrokers/BrokerIT.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/BrokerIT.java similarity index 90% rename from msal4j-brokers/src/test/java/com/microsoft/aad/msal4jbrokers/BrokerIT.java rename to msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/BrokerIT.java index 49db89e7..68a38190 100644 --- a/msal4j-brokers/src/test/java/com/microsoft/aad/msal4jbrokers/BrokerIT.java +++ b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/BrokerIT.java @@ -1,8 +1,6 @@ -package com.microsoft.aad.msal4jbrokers; +package com.microsoft.aad.msal4j; -import com.microsoft.aad.msal4j.IAuthenticationResult; -import com.microsoft.aad.msal4j.PublicClientApplication; -import com.microsoft.aad.msal4j.SilentParameters; +import com.microsoft.aad.msal4jbrokers.MsalRuntimeBroker; import org.testng.Assert; import org.testng.annotations.Test; From b95362bac457aab5bda3b189202e4de25f2105bc Mon Sep 17 00:00:00 2001 From: Avery-Dunn Date: Thu, 26 Jan 2023 13:12:37 -0800 Subject: [PATCH 15/27] Remove usage of msal4j-brokers from msal4j --- msal4j-brokers/pom.xml | 4 +- msal4j-sdk/pom.xml | 6 --- .../com.microsoft.aad.msal4j/BrokerIT.java | 46 ------------------- 3 files changed, 1 insertion(+), 55 deletions(-) delete mode 100644 msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/BrokerIT.java diff --git a/msal4j-brokers/pom.xml b/msal4j-brokers/pom.xml index 3b5cc754..7fb0c061 100644 --- a/msal4j-brokers/pom.xml +++ b/msal4j-brokers/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.microsoft.azure msal4j-brokers - 1.0.0-SNAPSHOT + 1.0.0-beta jar msal4j-brokers @@ -26,7 +26,6 @@ UTF-8 - com.microsoft.azure msal4j @@ -71,7 +70,6 @@ - ${project.build.directory}/delombok org.projectlombok diff --git a/msal4j-sdk/pom.xml b/msal4j-sdk/pom.xml index 5af98c9f..a7b4fde5 100644 --- a/msal4j-sdk/pom.xml +++ b/msal4j-sdk/pom.xml @@ -133,12 +133,6 @@ 2.7 test - - com.microsoft.azure - msal4j-brokers - 1.0.0-SNAPSHOT - test - diff --git a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/BrokerIT.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/BrokerIT.java deleted file mode 100644 index 68a38190..00000000 --- a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/BrokerIT.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.microsoft.aad.msal4j; - -import com.microsoft.aad.msal4jbrokers.MsalRuntimeBroker; -import org.testng.Assert; -import org.testng.annotations.Test; - -import java.net.MalformedURLException; -import java.util.Collections; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - -public class BrokerIT { - - @Test - public void acquireTokenSilent_usingBroker_DefaultOSAccount() throws MalformedURLException, ExecutionException, InterruptedException { - //TODO: Hardcoded params for now, will hopefully be able to copy the Python interop's ID Labs queries. If not, will only be able to do automated testing in the interop package - String clientId = "903c8a8a-9e74-415e-9921-711a293d90cb"; - String authority = "https://login.microsoftonline.com/common"; - String scopes = "https://graph.microsoft.com/.default"; - - MsalRuntimeBroker broker = new MsalRuntimeBroker(); - - PublicClientApplication pca = PublicClientApplication.builder( - clientId). - authority(authority). - correlationId(UUID.randomUUID().toString()). - broker(broker). - build(); - - SilentParameters parameters = SilentParameters.builder(Collections.singleton(scopes)).build(); - - CompletableFuture future = pca.acquireTokenSilently(parameters); - - IAuthenticationResult result = future.get(); - - Assert.assertNotNull(result); - Assert.assertNotNull(result.accessToken()); - Assert.assertNotNull(result.idToken()); - - System.out.println("Access token in result: " + result.accessToken()); - System.out.println("ID token in result: " + result.idToken()); - System.out.println("Account ID in result: " + result.account().homeAccountId()); - System.out.println("Username in result: " + result.account().username()); - } -} From f6843881fe7427ea54daf771d58c872308f3b899 Mon Sep 17 00:00:00 2001 From: Avery-Dunn Date: Mon, 30 Jan 2023 07:51:00 -0800 Subject: [PATCH 16/27] Add missing SLFJ dependency --- msal4j-brokers/pom.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/msal4j-brokers/pom.xml b/msal4j-brokers/pom.xml index 7fb0c061..947f4ac1 100644 --- a/msal4j-brokers/pom.xml +++ b/msal4j-brokers/pom.xml @@ -48,6 +48,17 @@ 7.1.0 test + + org.slf4j + slf4j-api + 1.7.36 + + + ch.qos.logback + logback-classic + 1.2.3 + test + From 8e73565a8eb8fe2735d785388cccc09e212c8cee Mon Sep 17 00:00:00 2001 From: Avery-Dunn Date: Mon, 30 Jan 2023 07:59:36 -0800 Subject: [PATCH 17/27] Use newest msal4j --- msal4j-brokers/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msal4j-brokers/pom.xml b/msal4j-brokers/pom.xml index 947f4ac1..44fe4fbe 100644 --- a/msal4j-brokers/pom.xml +++ b/msal4j-brokers/pom.xml @@ -29,7 +29,7 @@ com.microsoft.azure msal4j - 1.13.3 + 1.13.4 com.microsoft.azure From de9c85b54f72682ee8d190b9ac40cea0db7c9cc9 Mon Sep 17 00:00:00 2001 From: Avery-Dunn Date: Tue, 31 Jan 2023 09:08:26 -0800 Subject: [PATCH 18/27] Bump javamsalruntime version number --- msal4j-brokers/pom.xml | 2 +- .../com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/msal4j-brokers/pom.xml b/msal4j-brokers/pom.xml index 44fe4fbe..4e2140ce 100644 --- a/msal4j-brokers/pom.xml +++ b/msal4j-brokers/pom.xml @@ -34,7 +34,7 @@ com.microsoft.azure javamsalruntime - 0.13.3 + 0.13.4 org.projectlombok diff --git a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java index 8efaab77..68000997 100644 --- a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java +++ b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java @@ -25,7 +25,7 @@ import java.util.concurrent.ExecutionException; public class MsalRuntimeBroker implements IBroker { - private static final Logger LOG = LoggerFactory.getLogger(AuthResult.class); + private static final Logger LOG = LoggerFactory.getLogger(MsalRuntimeBroker.class); private static MsalRuntimeInterop interop; @@ -115,12 +115,13 @@ public CompletableFuture acquireToken(PublicClientApplica @Deprecated @Override public CompletableFuture acquireToken(PublicClientApplication application, UserNamePasswordParameters parameters) { - try (AuthParameters authParameters = + try { + AuthParameters authParameters = new AuthParameters .AuthParametersBuilder(application.clientId(), application.authority(), String.join(" ", parameters.scopes())) - .build()) { + .build(); authParameters.setUsernamePassword(parameters.username(), new String(parameters.password())); From 1e336b10c3cb1f0c95b1dc60ee872c7923566d8d Mon Sep 17 00:00:00 2001 From: Avery-Dunn <62066438+Avery-Dunn@users.noreply.github.com> Date: Tue, 31 Jan 2023 14:32:30 -0800 Subject: [PATCH 19/27] Version changes for 1.14.0-beta release (#589) --- msal4j-brokers/changelog.txt | 4 ++++ msal4j-brokers/pom.xml | 2 +- msal4j-sdk/README.md | 6 +++--- msal4j-sdk/bnd.bnd | 2 +- msal4j-sdk/changelog.txt | 5 +++++ msal4j-sdk/pom.xml | 2 +- msal4j-sdk/src/samples/msal-b2c-web-sample/pom.xml | 2 +- msal4j-sdk/src/samples/msal-obo-sample/pom.xml | 2 +- msal4j-sdk/src/samples/msal-web-sample/pom.xml | 2 +- 9 files changed, 18 insertions(+), 9 deletions(-) create mode 100644 msal4j-brokers/changelog.txt diff --git a/msal4j-brokers/changelog.txt b/msal4j-brokers/changelog.txt new file mode 100644 index 00000000..fc496f34 --- /dev/null +++ b/msal4j-brokers/changelog.txt @@ -0,0 +1,4 @@ +Version 1.0.0-beta +============= +- Initial release +- Provides the API and dependencies needed to utilize auth brokers through MSALRuntime \ No newline at end of file diff --git a/msal4j-brokers/pom.xml b/msal4j-brokers/pom.xml index 4e2140ce..11f873bf 100644 --- a/msal4j-brokers/pom.xml +++ b/msal4j-brokers/pom.xml @@ -29,7 +29,7 @@ com.microsoft.azure msal4j - 1.13.4 + 1.14.0-beta com.microsoft.azure diff --git a/msal4j-sdk/README.md b/msal4j-sdk/README.md index 1e24a762..2d8bba8e 100644 --- a/msal4j-sdk/README.md +++ b/msal4j-sdk/README.md @@ -16,7 +16,7 @@ Quick links: The library supports the following Java environments: - Java 8 (or higher) -Current version - 1.13.4 +Current version - 1.14.0-beta You can find the changes for each version in the [change log](https://github.com/AzureAD/microsoft-authentication-library-for-java/blob/master/changelog.txt). @@ -28,13 +28,13 @@ Find [the latest package in the Maven repository](https://mvnrepository.com/arti com.microsoft.azure msal4j - 1.13.4 + 1.14.0-beta ``` ### Gradle ```gradle -compile group: 'com.microsoft.azure', name: 'msal4j', version: '1.13.4' +compile group: 'com.microsoft.azure', name: 'msal4j', version: '1.14.0-beta' ``` ## Usage diff --git a/msal4j-sdk/bnd.bnd b/msal4j-sdk/bnd.bnd index b3f81aae..5b3083e0 100644 --- a/msal4j-sdk/bnd.bnd +++ b/msal4j-sdk/bnd.bnd @@ -1,2 +1,2 @@ -Export-Package: com.microsoft.aad.msal4j;version="1.13.4" +Export-Package: com.microsoft.aad.msal4j;version="1.14.0-beta" Automatic-Module-Name: com.microsoft.aad.msal4j diff --git a/msal4j-sdk/changelog.txt b/msal4j-sdk/changelog.txt index 442411d4..8ce801a5 100644 --- a/msal4j-sdk/changelog.txt +++ b/msal4j-sdk/changelog.txt @@ -1,3 +1,8 @@ +Version 1.14.0-beta +============= +- Add IBroker interface +- Add app-level parameter for enabling the use of auth brokers + Version 1.13.4 ============= - regional endpoint updates diff --git a/msal4j-sdk/pom.xml b/msal4j-sdk/pom.xml index a7b4fde5..17595a33 100644 --- a/msal4j-sdk/pom.xml +++ b/msal4j-sdk/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.microsoft.azure msal4j - 1.13.4 + 1.14.0-beta jar msal4j diff --git a/msal4j-sdk/src/samples/msal-b2c-web-sample/pom.xml b/msal4j-sdk/src/samples/msal-b2c-web-sample/pom.xml index 3bc00e86..684a1baf 100644 --- a/msal4j-sdk/src/samples/msal-b2c-web-sample/pom.xml +++ b/msal4j-sdk/src/samples/msal-b2c-web-sample/pom.xml @@ -23,7 +23,7 @@ com.microsoft.azure msal4j - 1.13.4 + 1.14.0-beta com.nimbusds diff --git a/msal4j-sdk/src/samples/msal-obo-sample/pom.xml b/msal4j-sdk/src/samples/msal-obo-sample/pom.xml index 38374034..84ed61d3 100644 --- a/msal4j-sdk/src/samples/msal-obo-sample/pom.xml +++ b/msal4j-sdk/src/samples/msal-obo-sample/pom.xml @@ -23,7 +23,7 @@ com.microsoft.azure msal4j - 1.13.4 + 1.14.0-beta com.nimbusds diff --git a/msal4j-sdk/src/samples/msal-web-sample/pom.xml b/msal4j-sdk/src/samples/msal-web-sample/pom.xml index 29cb2b44..97498900 100644 --- a/msal4j-sdk/src/samples/msal-web-sample/pom.xml +++ b/msal4j-sdk/src/samples/msal-web-sample/pom.xml @@ -23,7 +23,7 @@ com.microsoft.azure msal4j - 1.13.4 + 1.14.0-beta com.nimbusds From 596f1346753ddfd2dd08f6f9470935a252d46e09 Mon Sep 17 00:00:00 2001 From: Avery-Dunn Date: Tue, 31 Jan 2023 15:19:20 -0800 Subject: [PATCH 20/27] Add missing pom info needed by sonatype --- msal4j-brokers/pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/msal4j-brokers/pom.xml b/msal4j-brokers/pom.xml index 4e2140ce..096a7a7d 100644 --- a/msal4j-brokers/pom.xml +++ b/msal4j-brokers/pom.xml @@ -22,6 +22,12 @@ https://github.com/AzureAD/microsoft-authentication-library-for-java + + + ms + Microsoft Corporation + + UTF-8 From 0a39518a83f586fda9e835c7a165d6e04d938f06 Mon Sep 17 00:00:00 2001 From: Avery-Dunn <62066438+Avery-Dunn@users.noreply.github.com> Date: Wed, 14 Jun 2023 13:47:54 -0700 Subject: [PATCH 21/27] APIs for toggling MSALRuntime's logging (#608) * Add APIs for toggling MSALRuntime's logging systems * Rename logging methods to be more clear --- .../aad/msal4jbrokers/MsalRuntimeBroker.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java index 68000997..82563ace 100644 --- a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java +++ b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java @@ -176,4 +176,37 @@ public boolean isBrokerAvailable() { return false; } } + + /** + * Toggles whether or not detailed MSALRuntime logs will appear in MSAL Java's normal logging framework. + * + * If enabled, you will see logs directly from MSALRuntime, containing verbose information relating to telemetry, API calls,successful/failed requests, and more. + * These logs will appear alongside MSAL Java's logs (with a message indicating they came from MSALRuntime), and will follow the same log level as MSAL Java's logs (info/debug/error/etc.). + * + * If disabled (default), MSAL Java will still produce some logs related to MSALRuntime, particularly in error messages, but will be much less verbose. + * + * @param enableLogging true to enable MSALRuntime logs, false to disable it + */ + public void enableBrokerLogging(boolean enableLogging) { + try { + MsalRuntimeInterop.toggleMsalRuntimeLogging(enableLogging); + } catch (Exception ex) { + throw new MsalClientException(String.format("Error occurred when calling MSALRuntime logging API: %s", ex.getMessage()), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); + } + } + + /** + * If enabled, Personal Identifiable Information (PII) can appear in logs and error messages produced by MSALRuntime. + * + * If disabled (default), PII will not be shown, and you will simply see "(PII)" or similar notes in places where PII data would have appeared. + * + * @param enablePII true to allow PII to appear in logs and error messages, false to disallow it + */ + public void enableBrokerPIILogging(boolean enablePII) { + try { + MsalRuntimeInterop.toggleMsalRuntimePIILogging(enablePII); + } catch (Exception ex) { + throw new MsalClientException(String.format("Error occurred when calling MSALRuntime PII logging API: %s", ex.getMessage()), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); + } + } } From b27c81b3040b1cc15004397eb57bc636df4398af Mon Sep 17 00:00:00 2001 From: Avery-Dunn <62066438+Avery-Dunn@users.noreply.github.com> Date: Mon, 19 Jun 2023 11:16:20 -0700 Subject: [PATCH 22/27] Add support for POP tokens to MSAL Java and MSAL Java Brokers (#639) * Version changes for 1.14.0-beta release * regional endpoint change to always use login.microsoft.com * Add support for both current and legacy B2C authority formats (#594) * Add support for both current and legacy B2C authority formats * Fix B2C format test * add 2 seconds timeout while calling IMDS * Fix failing tests * Fix failing tests * delete commented out code * Use the dedicated admin consent endpoint instead of a query parameter (#599) * updated versions for release * update condition to throw exception * added test for invalid authority * Add tests for a CIAM user and reduce test code duplication (#603) * Add tests for a CIAM user and reduce code duplication in several test files * Revert changed method name * Attempt to resolve credscan flag * Resolve credscan issues * Address code review comments * Use default scope * expose extraQueryParameters * expose extraQueryParameters * ExtraQueryParameters tests * retrigger the tests * Updated an existing test case to check added parameters * Replace exception with warning * version updates for release * update json-smart version * Updated json-smart version Updated json-smart version to a 'bug-free' version * version updates for release * Initial commit * add CIAM authority file * revert authority validation changes * Fix failing tests * Fix failing tests * remove commented out line * remove unnecessary code * update exception message for device code flow * add refresh_in logic * resolve build issues + address PR comments * update tests * updated org-json version to resolve Dependabot alert * Better redirect URI error handling and dependency upgrade (#633) * Better error handling for redirect URIs * Update oauth2-oidc-sdk dependency * Address review comments Co-authored-by: Bogdan Gavril --------- Co-authored-by: Bogdan Gavril * Version updates for 1.13.8 release (#634) * Version updates for 1.13.8 release * Update changelog.txt * Point to MSAL Java reference docs onboarded to Microsoft Learn * Add support for POP tokens to MSAL Java and MSAL Java Brokers * Send extraQueryParameters to interop's AuthParameters * Avoid exposing new PopParameters class, change API to match design doc * Update msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/InteractiveRequestParameters.java Co-authored-by: Bogdan Gavril * Update msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/InteractiveRequestParameters.java Co-authored-by: Bogdan Gavril * Update change log URl in README.md (#649) Replaced broken link in change log reference with msal4j-sdk/changelog.txt * Issue 447 * Feedback incorporation * enum for os type * Use enum for HTTP methods * Add broker tests, address PR review comments * Improve PoP tests * Address code review comments * Version updates * Re-add extraQueryParameters support --------- Co-authored-by: siddhijain Co-authored-by: Bogdan Gavril Co-authored-by: Dickson Mwendia <64727760+Dickson-Mwendia@users.noreply.github.com> Co-authored-by: Tamas Csizmadia --- README.md | 10 +- msal4j-brokers/pom.xml | 45 ++++- .../aad/msal4jbrokers/MsalRuntimeBroker.java | 102 ++++++---- .../infrastructure/SeleniumConstants.java | 21 ++ .../infrastructure/SeleniumExtensions.java | 115 +++++++++++ .../test/java/labapi/HttpClientHelper.java | 62 ++++++ .../java/labapi/KeyVaultSecretsProvider.java | 112 +++++++++++ .../src/test/java/labapi/LabConstants.java | 24 +++ .../src/test/java/labapi/LabService.java | 99 ++++++++++ .../src/test/java/labapi/LabUserProvider.java | 46 +++++ msal4j-brokers/src/test/java/labapi/User.java | 36 ++++ .../test/java/labapi/UserQueryParameters.java | 13 ++ .../src/test/java/labapi/UserSecret.java | 14 ++ .../test/java/test/ProofOfPossessionTest.java | 186 ++++++++++++++++++ msal4j-sdk/README.md | 6 +- msal4j-sdk/bnd.bnd | 2 +- msal4j-sdk/changelog.txt | 29 +++ msal4j-sdk/pom.xml | 6 +- .../AcquireTokenInteractiveIT.java | 125 +++++++----- .../AcquireTokenSilentIT.java | 14 +- .../AuthorizationCodeIT.java | 1 + .../ClientCredentialsIT.java | 66 +++++-- .../ConfidentialClientApplicationUnitT.java | 31 ++- .../DeviceCodeIT.java | 29 ++- .../InvalidAuthorityIT.java | 26 +++ .../SeleniumTest.java | 4 +- .../TestConstants.java | 18 +- .../TokenCacheIT.java | 6 + .../UsernamePasswordIT.java | 61 ++++-- .../infrastructure/SeleniumExtensions.java | 9 +- .../java/labapi/AppCredentialProvider.java | 7 +- .../java/labapi/AzureEnvironment.java | 1 + .../java/labapi/FederationProvider.java | 1 + .../java/labapi/LabConstants.java | 1 + .../java/labapi/LabUserProvider.java | 10 + .../java/labapi/UserQueryParameters.java | 1 + .../msal4j/AadInstanceDiscoveryProvider.java | 63 +++--- .../msal4j/AbstractClientApplicationBase.java | 19 +- .../AcquireTokenByAppProviderSupplier.java | 13 +- ...AcquireTokenByInteractiveFlowSupplier.java | 78 +++++++- .../aad/msal4j/AuthenticationErrorCode.java | 2 +- .../aad/msal4j/AuthenticationResult.java | 3 + .../com/microsoft/aad/msal4j/Authority.java | 40 ++-- .../microsoft/aad/msal4j/AuthorityType.java | 2 +- .../msal4j/AuthorizationCodeParameters.java | 5 + .../AuthorizationRequestUrlParameters.java | 40 +++- .../microsoft/aad/msal4j/B2CAuthority.java | 39 ++-- .../microsoft/aad/msal4j/CIAMAuthority.java | 51 +++++ .../msal4j/ClientCredentialParameters.java | 5 + .../aad/msal4j/DeviceCodeFlowParameters.java | 5 + .../com/microsoft/aad/msal4j/HttpHelper.java | 3 + .../com/microsoft/aad/msal4j/HttpMethod.java | 42 +++- .../aad/msal4j/IAcquireTokenParameters.java | 2 + .../com/microsoft/aad/msal4j/IBroker.java | 25 ++- ...gratedWindowsAuthenticationParameters.java | 5 + .../aad/msal4j/InteractiveRequest.java | 50 +++-- .../msal4j/InteractiveRequestParameters.java | 26 +++ .../com/microsoft/aad/msal4j/OSHelper.java | 39 ++++ .../aad/msal4j/OnBehalfOfParameters.java | 5 + .../microsoft/aad/msal4j/PopParameters.java | 44 +++++ .../java/com/microsoft/aad/msal4j/Prompt.java | 8 - .../aad/msal4j/PublicClientApplication.java | 68 ++++++- .../aad/msal4j/RefreshTokenParameters.java | 5 + .../aad/msal4j/SilentParameters.java | 27 +++ .../aad/msal4j/TokenRequestExecutor.java | 11 +- .../msal4j/UserNamePasswordParameters.java | 23 +++ .../src/samples/msal-b2c-web-sample/pom.xml | 2 +- .../src/samples/msal-obo-sample/pom.xml | 2 +- .../src/samples/msal-web-sample/pom.xml | 2 +- .../microsoft/aad/msal4j/AuthorityTest.java | 30 ++- ...AuthorizationRequestUrlParametersTest.java | 23 +++ .../aad/msal4j/DeviceCodeFlowTest.java | 2 +- 72 files changed, 1874 insertions(+), 274 deletions(-) create mode 100644 msal4j-brokers/src/test/java/infrastructure/SeleniumConstants.java create mode 100644 msal4j-brokers/src/test/java/infrastructure/SeleniumExtensions.java create mode 100644 msal4j-brokers/src/test/java/labapi/HttpClientHelper.java create mode 100644 msal4j-brokers/src/test/java/labapi/KeyVaultSecretsProvider.java create mode 100644 msal4j-brokers/src/test/java/labapi/LabConstants.java create mode 100644 msal4j-brokers/src/test/java/labapi/LabService.java create mode 100644 msal4j-brokers/src/test/java/labapi/LabUserProvider.java create mode 100644 msal4j-brokers/src/test/java/labapi/User.java create mode 100644 msal4j-brokers/src/test/java/labapi/UserQueryParameters.java create mode 100644 msal4j-brokers/src/test/java/labapi/UserSecret.java create mode 100644 msal4j-brokers/src/test/java/test/ProofOfPossessionTest.java create mode 100644 msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/InvalidAuthorityIT.java create mode 100644 msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/CIAMAuthority.java create mode 100644 msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/OSHelper.java create mode 100644 msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/PopParameters.java diff --git a/README.md b/README.md index fbdee417..00e86f3d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ `main` branch | `dev` branch | Reference Docs --------------------|-----------------|--------------- -[![Build status](https://identitydivision.visualstudio.com/IDDP/_apis/build/status/CI/Java/MSAL%20Java%20CI%20Build?branchName=main)](https://identitydivision.visualstudio.com/IDDP/_build/latest?definitionId=762) | [![Build status](https://identitydivision.visualstudio.com/IDDP/_apis/build/status/CI/Java/MSAL%20Java%20CI%20Build?branchName=dev)](https://identitydivision.visualstudio.com/IDDP/_build/latest?definitionId=762)| [![Javadocs](http://javadoc.io/badge/com.microsoft.azure/com.microsoft.aad.msal4j.svg)](http://javadoc.io/doc/com.microsoft.azure/com.microsoft.aad.msal4j) +[![Build status](https://identitydivision.visualstudio.com/IDDP/_apis/build/status/CI/Java/MSAL%20Java%20CI%20Build?branchName=main)](https://identitydivision.visualstudio.com/IDDP/_build/latest?definitionId=762) | [![Build status](https://identitydivision.visualstudio.com/IDDP/_apis/build/status/CI/Java/MSAL%20Java%20CI%20Build?branchName=dev)](https://identitydivision.visualstudio.com/IDDP/_build/latest?definitionId=762)| [MSAL Java reference](https://learn.microsoft.com/en-us/java/api/com.microsoft.aad.msal4j?view=msal-java-latest) The Microsoft Authentication Library for Java (MSAL4J) enables applications to integrate with the [Microsoft identity platform](https://docs.microsoft.com/en-us/azure/active-directory/develop/). It allows you to sign in users or apps with Microsoft identities (Azure AD, Microsoft accounts and Azure AD B2C accounts) and obtain tokens to call Microsoft APIs such as [Microsoft Graph](https://graph.microsoft.io/) or your own APIs registered with the Microsoft identity platform. It is built using industry standard OAuth2 and OpenID Connect protocols. @@ -16,9 +16,9 @@ Quick links: The library supports the following Java environments: - Java 8 (or higher) -Current version - 1.13.2 +Current version - 1.13.8 -You can find the changes for each version in the [change log](https://github.com/AzureAD/microsoft-authentication-library-for-java/blob/master/changelog.txt). +You can find the changes for each version in the [change log](https://github.com/AzureAD/microsoft-authentication-library-for-java/blob/main/msal4j-sdk/changelog.txt). You can get the com.microsoft.aad.msal4j package through Maven or Gradle. @@ -28,13 +28,13 @@ Find [the latest package in the Maven repository](https://mvnrepository.com/arti com.microsoft.azure msal4j - 1.13.2 + 1.13.8 ``` ### Gradle ```gradle -implementation group: 'com.microsoft.azure', name: 'com.microsoft.aad.msal4j', version: '1.13.2' +implementation group: 'com.microsoft.azure', name: 'com.microsoft.aad.msal4j', version: '1.13.8' ``` ## Usage diff --git a/msal4j-brokers/pom.xml b/msal4j-brokers/pom.xml index ba9c681c..5eb983d5 100644 --- a/msal4j-brokers/pom.xml +++ b/msal4j-brokers/pom.xml @@ -5,12 +5,11 @@ 4.0.0 com.microsoft.azure msal4j-brokers - 1.0.0-beta + 1.0.1-beta jar msal4j-brokers - Microsoft Authentication Library for Java - Brokers helps you integrate with the broker - on windows machine to secure Access tokens and refresh tokens. + Microsoft Authentication Library for Java - Brokers is a companion package for MSAL Java that allows easy integration with authentication brokers https://github.com/AzureAD/microsoft-authentication-library-for-java @@ -35,12 +34,12 @@ com.microsoft.azure msal4j - 1.14.0-beta + 1.14.1-beta com.microsoft.azure javamsalruntime - 0.13.4 + 0.13.8 org.projectlombok @@ -65,6 +64,42 @@ 1.2.3 test + + commons-io + commons-io + 2.11.0 + test + + + org.seleniumhq.selenium + selenium-api + 3.14.0 + test + + + org.seleniumhq.selenium + selenium-chrome-driver + 3.14.0 + test + + + org.seleniumhq.selenium + selenium-support + 3.14.0 + test + + + com.azure + azure-core + 1.22.0 + test + + + com.azure + azure-security-keyvault-secrets + 4.3.5 + test + diff --git a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java index 82563ace..9951ae78 100644 --- a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java +++ b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java @@ -3,15 +3,7 @@ package com.microsoft.aad.msal4jbrokers; -import com.microsoft.aad.msal4j.IAuthenticationResult; -import com.microsoft.aad.msal4j.IBroker; -import com.microsoft.aad.msal4j.InteractiveRequestParameters; -import com.microsoft.aad.msal4j.PublicClientApplication; -import com.microsoft.aad.msal4j.SilentParameters; -import com.microsoft.aad.msal4j.UserNamePasswordParameters; -import com.microsoft.aad.msal4j.MsalClientException; -import com.microsoft.aad.msal4j.AuthenticationErrorCode; -import com.microsoft.aad.msal4j.IAccount; +import com.microsoft.aad.msal4j.*; import com.microsoft.azure.javamsalruntime.Account; import com.microsoft.azure.javamsalruntime.AuthParameters; import com.microsoft.azure.javamsalruntime.AuthResult; @@ -28,6 +20,7 @@ public class MsalRuntimeBroker implements IBroker { private static final Logger LOG = LoggerFactory.getLogger(MsalRuntimeBroker.class); private static MsalRuntimeInterop interop; + private static Boolean brokerAvailable; static { try { @@ -53,11 +46,20 @@ public CompletableFuture acquireToken(PublicClientApplica } try { - AuthParameters authParameters = new AuthParameters - .AuthParametersBuilder(application.clientId(), + AuthParameters.AuthParametersBuilder authParamsBuilder = new AuthParameters. + AuthParametersBuilder(application.clientId(), application.authority(), String.join(" ", parameters.scopes())) - .build(); + .additionalParameters(parameters.extraQueryParameters()); + + //If POP auth scheme configured, set parameters to get MSALRuntime to return POP tokens + if (parameters.proofOfPossession() != null) { + authParamsBuilder.popParameters(parameters.proofOfPossession().getHttpMethod().methodName, + parameters.proofOfPossession().getUri(), + parameters.proofOfPossession().getNonce()); + } + + AuthParameters authParameters = authParamsBuilder.build(); if (accountResult == null) { return interop.signInSilently(authParameters, application.correlationId()) @@ -68,7 +70,8 @@ public CompletableFuture acquireToken(PublicClientApplica ((AuthResult) authResult).getAccessToken(), ((AuthResult) authResult).getAccount().getAccountId(), ((AuthResult) authResult).getAccount().getClientInfo(), - ((AuthResult) authResult).getAccessTokenExpirationTime())); + ((AuthResult) authResult).getAccessTokenExpirationTime(), + ((AuthResult) authResult).isPopAuthorization())); } else { return interop.acquireTokenSilently(authParameters, application.correlationId(), accountResult) .thenApply(authResult -> parseBrokerAuthResult(application.authority(), @@ -76,9 +79,8 @@ public CompletableFuture acquireToken(PublicClientApplica ((AuthResult) authResult).getAccessToken(), ((AuthResult) authResult).getAccount().getAccountId(), ((AuthResult) authResult).getAccount().getClientInfo(), - ((AuthResult) authResult).getAccessTokenExpirationTime()) - - ); + ((AuthResult) authResult).getAccessTokenExpirationTime(), + ((AuthResult) authResult).isPopAuthorization())); } } catch (MsalInteropException interopException) { throw new MsalClientException(interopException.getErrorMessage(), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); @@ -88,11 +90,21 @@ public CompletableFuture acquireToken(PublicClientApplica @Override public CompletableFuture acquireToken(PublicClientApplication application, InteractiveRequestParameters parameters) { try { - AuthParameters authParameters = new AuthParameters - .AuthParametersBuilder(application.clientId(), + AuthParameters.AuthParametersBuilder authParamsBuilder = new AuthParameters. + AuthParametersBuilder(application.clientId(), application.authority(), String.join(" ", parameters.scopes())) - .build(); + .redirectUri(parameters.redirectUri().toString()) + .additionalParameters(parameters.extraQueryParameters()); + + //If POP auth scheme configured, set parameters to get MSALRuntime to return POP tokens + if (parameters.proofOfPossession() != null) { + authParamsBuilder.popParameters(parameters.proofOfPossession().getHttpMethod().methodName, + parameters.proofOfPossession().getUri(), + parameters.proofOfPossession().getNonce()); + } + + AuthParameters authParameters = authParamsBuilder.build(); return interop.signInInteractively(parameters.windowHandle(), authParameters, application.correlationId(), parameters.loginHint()) .thenCompose(acctResult -> interop.acquireTokenInteractively(parameters.windowHandle(), authParameters, application.correlationId(), ((AuthResult) acctResult).getAccount())) @@ -102,8 +114,8 @@ public CompletableFuture acquireToken(PublicClientApplica ((AuthResult) authResult).getAccessToken(), ((AuthResult) authResult).getAccount().getAccountId(), ((AuthResult) authResult).getAccount().getClientInfo(), - ((AuthResult) authResult).getAccessTokenExpirationTime()) - ); + ((AuthResult) authResult).getAccessTokenExpirationTime(), + ((AuthResult) authResult).isPopAuthorization())); } catch (MsalInteropException interopException) { throw new MsalClientException(interopException.getErrorMessage(), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); } @@ -116,14 +128,20 @@ public CompletableFuture acquireToken(PublicClientApplica @Override public CompletableFuture acquireToken(PublicClientApplication application, UserNamePasswordParameters parameters) { try { - AuthParameters authParameters = - new AuthParameters - .AuthParametersBuilder(application.clientId(), - application.authority(), - String.join(" ", parameters.scopes())) - .build(); + AuthParameters.AuthParametersBuilder authParamsBuilder = new AuthParameters. + AuthParametersBuilder(application.clientId(), + application.authority(), + String.join(" ", parameters.scopes())) + .additionalParameters(parameters.extraQueryParameters()); - authParameters.setUsernamePassword(parameters.username(), new String(parameters.password())); + //If POP auth scheme configured, set parameters to get MSALRuntime to return POP tokens + if (parameters.proofOfPossession() != null) { + authParamsBuilder.popParameters(parameters.proofOfPossession().getHttpMethod().methodName, + parameters.proofOfPossession().getUri(), + parameters.proofOfPossession().getNonce()); + } + + AuthParameters authParameters = authParamsBuilder.build(); return interop.signInSilently(authParameters, application.correlationId()) .thenCompose(acctResult -> interop.acquireTokenSilently(authParameters, application.correlationId(), ((AuthResult) acctResult).getAccount())) @@ -133,7 +151,8 @@ public CompletableFuture acquireToken(PublicClientApplica ((AuthResult) authResult).getAccessToken(), ((AuthResult) authResult).getAccount().getAccountId(), ((AuthResult) authResult).getAccount().getClientInfo(), - ((AuthResult) authResult).getAccessTokenExpirationTime())); + ((AuthResult) authResult).getAccessTokenExpirationTime(), + ((AuthResult) authResult).isPopAuthorization())); } catch (MsalInteropException interopException) { throw new MsalClientException(interopException.getErrorMessage(), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); } @@ -163,18 +182,23 @@ public void removeAccount(PublicClientApplication application, IAccount msalJava */ @Override public boolean isBrokerAvailable() { - try { - interop.startupMsalRuntime(); + //brokerAvailable is only set after the first attempt to call MSALRuntime's startup API + if (brokerAvailable == null) { + try { + interop.startupMsalRuntime(); - LOG.info("MSALRuntime started successfully. MSAL Java will use MSALRuntime in all supported broker flows."); + LOG.info("MSALRuntime started successfully. MSAL Java will use MSALRuntime in all supported broker flows."); - return true; - } catch (MsalInteropException e) { - LOG.warn("Exception thrown when trying to start MSALRuntime: {}", e.getErrorMessage()); - LOG.warn("MSALRuntime could not be started. MSAL Java will fall back to non-broker flows."); + brokerAvailable = true; + } catch (MsalInteropException e) { + LOG.warn("Exception thrown when trying to start MSALRuntime: {}", e.getErrorMessage()); + LOG.warn("MSALRuntime could not be started. MSAL Java will fall back to non-broker flows."); - return false; + brokerAvailable = false; + } } + + return brokerAvailable; } /** @@ -189,7 +213,7 @@ public boolean isBrokerAvailable() { */ public void enableBrokerLogging(boolean enableLogging) { try { - MsalRuntimeInterop.toggleMsalRuntimeLogging(enableLogging); + MsalRuntimeInterop.enableLogging(enableLogging); } catch (Exception ex) { throw new MsalClientException(String.format("Error occurred when calling MSALRuntime logging API: %s", ex.getMessage()), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); } @@ -204,7 +228,7 @@ public void enableBrokerLogging(boolean enableLogging) { */ public void enableBrokerPIILogging(boolean enablePII) { try { - MsalRuntimeInterop.toggleMsalRuntimePIILogging(enablePII); + MsalRuntimeInterop.enableLoggingPii(enablePII); } catch (Exception ex) { throw new MsalClientException(String.format("Error occurred when calling MSALRuntime PII logging API: %s", ex.getMessage()), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); } diff --git a/msal4j-brokers/src/test/java/infrastructure/SeleniumConstants.java b/msal4j-brokers/src/test/java/infrastructure/SeleniumConstants.java new file mode 100644 index 00000000..859a9bd8 --- /dev/null +++ b/msal4j-brokers/src/test/java/infrastructure/SeleniumConstants.java @@ -0,0 +1,21 @@ +//---------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ + +package infrastructure; + +public class SeleniumConstants { + final static String WEB_UPN_INPUT_ID = "i0116"; + final static String WEB_PASSWORD_ID = "i0118"; + final static String WEB_SUBMIT_ID = "idSIButton9"; + + // Stay signed in? + final static String STAY_SIGN_IN_NO_BUTTON_ID = "idBtn_Back"; + + // Are you trying to sign in to ... + //Only continue if you downloaded the app from a store or website that you trust. + final static String ARE_YOU_TRYING_TO_SIGN_IN_TO = "idSIButton9"; +} diff --git a/msal4j-brokers/src/test/java/infrastructure/SeleniumExtensions.java b/msal4j-brokers/src/test/java/infrastructure/SeleniumExtensions.java new file mode 100644 index 00000000..bf46d23e --- /dev/null +++ b/msal4j-brokers/src/test/java/infrastructure/SeleniumExtensions.java @@ -0,0 +1,115 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package infrastructure; + +import labapi.User; +import org.openqa.selenium.By; +import org.openqa.selenium.StaleElementReferenceException; +import org.openqa.selenium.TimeoutException; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.chrome.ChromeDriver; +import org.openqa.selenium.chrome.ChromeOptions; +import org.openqa.selenium.support.ui.ExpectedCondition; +import org.openqa.selenium.support.ui.WebDriverWait; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.TimeUnit; + +public class SeleniumExtensions { + + private final static Logger LOG = LoggerFactory.getLogger(SeleniumExtensions.class); + + private SeleniumExtensions() { + } + + public static WebDriver createDefaultWebDriver() { + ChromeOptions options = new ChromeOptions(); + //no visual rendering, remove when debugging + options.addArguments("--headless"); + + System.setProperty("webdriver.chrome.driver", "C:/Windows/chromedriver.exe"); + ChromeDriver driver = new ChromeDriver(options); + driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS); + + return driver; + } + + public static WebElement waitForElementToBeVisibleAndEnable(WebDriver driver, By by, int timeOutInSeconds) { + WebDriverWait webDriverWait = new WebDriverWait(driver, timeOutInSeconds); + return webDriverWait.until((dr) -> + { + try { + WebElement elementToBeDisplayed = driver.findElement(by); + if (elementToBeDisplayed.isDisplayed() && elementToBeDisplayed.isEnabled()) { + return elementToBeDisplayed; + } + return null; + } catch (StaleElementReferenceException e) { + return null; + } + }); + } + + public static WebElement waitForElementToBeVisibleAndEnable(WebDriver driver, By by) { + int DEFAULT_TIMEOUT_IN_SEC = 15; + + return waitForElementToBeVisibleAndEnable(driver, by, DEFAULT_TIMEOUT_IN_SEC); + } + + public static void performADLogin(WebDriver driver, User user) { + LOG.info("PerformADLogin"); + + LOG.info("Loggin in ... Entering username"); + driver.findElement(new By.ById(SeleniumConstants.WEB_UPN_INPUT_ID)).sendKeys(user.getUpn()); + + LOG.info("Loggin in ... Clicking after username"); + driver.findElement(new By.ById(SeleniumConstants.WEB_SUBMIT_ID)).click(); + + LOG.info("Loggin in ... Entering password"); + By by = new By.ById(SeleniumConstants.WEB_PASSWORD_ID); + waitForElementToBeVisibleAndEnable(driver, by).sendKeys(user.getPassword()); + + LOG.info("Loggin in ... click submit"); + waitForElementToBeVisibleAndEnable(driver, new By.ById(SeleniumConstants.WEB_SUBMIT_ID)). + click(); + + try { + checkAuthenticationCompletePage(driver); + return; + } catch (TimeoutException ex) { + } + + LOG.info("Checking optional questions"); + + try { + LOG.info("Are you trying to sign in to ... ? checking"); + waitForElementToBeVisibleAndEnable(driver, new By.ById(SeleniumConstants.ARE_YOU_TRYING_TO_SIGN_IN_TO), 3). + click(); + LOG.info("Are you trying to sign in to ... ? click Continue"); + + } catch (TimeoutException ex) { + } + + try { + LOG.info("Stay signed in? checking"); + waitForElementToBeVisibleAndEnable(driver, new By.ById(SeleniumConstants.STAY_SIGN_IN_NO_BUTTON_ID), 3). + click(); + LOG.info("Stay signed in? click NO"); + } catch (TimeoutException ex) { + } + } + + private static void checkAuthenticationCompletePage(WebDriver driver) { + (new WebDriverWait(driver, 5)).until((ExpectedCondition) d -> { + boolean condition = false; + WebElement we = d.findElement(new By.ByTagName("body")); + if (we != null && we.getText().contains("Authentication complete")) { + condition = true; + } + return condition; + }); + } +} diff --git a/msal4j-brokers/src/test/java/labapi/HttpClientHelper.java b/msal4j-brokers/src/test/java/labapi/HttpClientHelper.java new file mode 100644 index 00000000..e4d2eb1e --- /dev/null +++ b/msal4j-brokers/src/test/java/labapi/HttpClientHelper.java @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package labapi; + +import javax.net.ssl.HttpsURLConnection; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; +import java.util.Map; + +class HttpClientHelper { + static String sendRequestToLab(String url, Map queryMap, String accessToken) + throws IOException { + return sendRequestToLab(buildUrl(url, queryMap), accessToken); + } + + static String sendRequestToLab(URL labUrl, String accessToken) throws IOException { + HttpsURLConnection conn = (HttpsURLConnection)labUrl.openConnection(); + + conn.setRequestProperty("Authorization", "Bearer " + accessToken); + + conn.setReadTimeout(20000); + conn.setConnectTimeout(20000); + + StringBuilder content; + try (BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()))) { + String inputLine; + content = new StringBuilder(); + while ((inputLine = in.readLine()) != null) { + content.append(inputLine); + } + } + conn.disconnect(); + return content.toString(); + } + + private static URL buildUrl(String url, Map queryMap) + throws MalformedURLException, UnsupportedOperationException { + String queryParameters; + queryParameters = queryMap.entrySet() + .stream() + .map(p -> encodeUTF8(p.getKey()) + "=" + encodeUTF8(p.getValue())) + .reduce((p1, p2) -> p1 + "&" + p2) + .orElse(""); + + String urlString = url + "?" + queryParameters; + return new URL(urlString); + } + + private static String encodeUTF8(String s) { + try { + return URLEncoder.encode(s, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException("Error: cannot encode query parameter " + s); + } + } +} diff --git a/msal4j-brokers/src/test/java/labapi/KeyVaultSecretsProvider.java b/msal4j-brokers/src/test/java/labapi/KeyVaultSecretsProvider.java new file mode 100644 index 00000000..673b550a --- /dev/null +++ b/msal4j-brokers/src/test/java/labapi/KeyVaultSecretsProvider.java @@ -0,0 +1,112 @@ +package labapi; + +import com.azure.core.credential.AccessToken; +import com.azure.core.credential.TokenCredential; +import com.azure.security.keyvault.secrets.SecretClient; +import com.azure.security.keyvault.secrets.SecretClientBuilder; +import com.azure.security.keyvault.secrets.models.KeyVaultSecretIdentifier; +import com.microsoft.aad.msal4j.ClientCredentialFactory; +import com.microsoft.aad.msal4j.ClientCredentialParameters; +import com.microsoft.aad.msal4j.ConfidentialClientApplication; +import com.microsoft.aad.msal4j.IAuthenticationResult; +import com.microsoft.aad.msal4j.IClientCredential; +import reactor.core.publisher.Mono; + +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class KeyVaultSecretsProvider { + private final SecretClient secretClient; + + private static final String CLIENT_ID = "2afb0add-2f32-4946-ac90-81a02aa4550e"; + public static String CERTIFICATE_ALIAS = "MsalJavaAutomationRunner"; + + private static final String WIN_KEYSTORE = "Windows-MY"; + private static final String KEYSTORE_PROVIDER = "SunMSCAPI"; + + private static final String MAC_KEYSTORE = "KeychainStore"; + + static Map cache = new ConcurrentHashMap<>(); + + KeyVaultSecretsProvider() { + secretClient = getAuthenticatedSecretClient(); + } + + String getSecret(String secretUrl) { + // extract keyName from secretUrl + KeyVaultSecretIdentifier keyVaultSecretIdentifier = new KeyVaultSecretIdentifier(secretUrl); + String key = keyVaultSecretIdentifier.getName(); + + if (cache.containsKey(key)) { + return cache.get(key); + } + + String secret = secretClient.getSecret(key).getValue(); + cache.put(key, secret); + + return secret; + } + + private SecretClient getAuthenticatedSecretClient() { + return new SecretClientBuilder() + .credential(getTokenCredential()) + .vaultUrl(LabConstants.MSIDLAB_VAULT_URL) + .buildClient(); + } + + private AccessToken requestAccessTokenForAutomation() { + IAuthenticationResult result; + try { + ConfidentialClientApplication cca = + ConfidentialClientApplication + .builder(CLIENT_ID, getClientCredentialFromKeyStore()) + .authority(LabConstants.MICROSOFT_AUTHORITY) + .build(); + result = cca.acquireToken(ClientCredentialParameters + .builder(Collections.singleton( + LabConstants.KEYVAULT_DEFAULT_SCOPE)) + .build()) + .get(); + } catch (Exception e) { + throw new RuntimeException("Error acquiring token from Azure AD: " + e.getMessage()); + } + if (result != null) { + return new AccessToken( + result.accessToken(), + OffsetDateTime.ofInstant(result.expiresOnDate().toInstant(), ZoneOffset.UTC)); + } else { + throw new NullPointerException("Authentication result is null"); + } + } + + private IClientCredential getClientCredentialFromKeyStore() { + PrivateKey key; + X509Certificate publicCertificate; + try { + String os = System.getProperty("os.name"); + KeyStore keystore; + if (os.toLowerCase().contains("windows")) { + keystore = KeyStore.getInstance(WIN_KEYSTORE, KEYSTORE_PROVIDER); + } else { + keystore = KeyStore.getInstance(MAC_KEYSTORE); + } + + keystore.load(null, null); + key = (PrivateKey)keystore.getKey(CERTIFICATE_ALIAS, null); + publicCertificate = (X509Certificate)keystore.getCertificate(CERTIFICATE_ALIAS); + } catch (Exception e) { + throw new RuntimeException("Error getting certificate from keystore: " + e.getMessage()); + } + return ClientCredentialFactory.createFromCertificate(key, publicCertificate); + } + + private TokenCredential getTokenCredential() { + return tokenRequestContext -> Mono.defer(() -> Mono.just(requestAccessTokenForAutomation())); + } +} diff --git a/msal4j-brokers/src/test/java/labapi/LabConstants.java b/msal4j-brokers/src/test/java/labapi/LabConstants.java new file mode 100644 index 00000000..37e4f921 --- /dev/null +++ b/msal4j-brokers/src/test/java/labapi/LabConstants.java @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package labapi; + +public class LabConstants { + public final static String KEYVAULT_DEFAULT_SCOPE = "https://vault.azure.net/.default"; + public final static String MSIDLAB_DEFAULT_SCOPE = "https://msidlab.com/.default"; + public final static String MSIDLAB_VAULT_URL = "https://msidlabs.vault.azure.net/"; + + public final static String MICROSOFT_AUTHORITY = + "https://login.microsoftonline.com/microsoft.onmicrosoft.com"; + + public final static String LAB_USER_ENDPOINT = "https://msidlab.com/api/user"; + public final static String LAB_USER_SECRET_ENDPOINT = "https://msidlab.com/api/LabSecret"; + + public final static String APP_ID_KEY_VAULT_SECRET = + "https://msidlabs.vault.azure.net/secrets/LabVaultAppID"; + public final static String APP_PASSWORD_KEY_VAULT_SECRET = + "https://msidlabs.vault.azure.net/secrets/LabVaultAppSecret"; + + public final static String AZURE_ENVIRONMENT = "azurecloud"; + public final static String FEDERATION_PROVIDER_NONE = "none"; +} diff --git a/msal4j-brokers/src/test/java/labapi/LabService.java b/msal4j-brokers/src/test/java/labapi/LabService.java new file mode 100644 index 00000000..f15690bc --- /dev/null +++ b/msal4j-brokers/src/test/java/labapi/LabService.java @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package labapi; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.microsoft.aad.msal4j.ClientCredentialFactory; +import com.microsoft.aad.msal4j.ClientCredentialParameters; +import com.microsoft.aad.msal4j.ConfidentialClientApplication; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutionException; + +public class LabService { + static ConfidentialClientApplication labApp; + + static ObjectMapper mapper = + new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + static T convertJsonToObject(final String json, final Class clazz) { + try { + return mapper.readValue(json, clazz); + } catch (IOException e) { + throw new RuntimeException("JSON processing error: " + e.getMessage(), e); + } + } + + static void initLabApp() throws MalformedURLException { + KeyVaultSecretsProvider keyVaultSecretsProvider = new KeyVaultSecretsProvider(); + + String appID = keyVaultSecretsProvider.getSecret(LabConstants.APP_ID_KEY_VAULT_SECRET); + String appSecret = + keyVaultSecretsProvider.getSecret(LabConstants.APP_PASSWORD_KEY_VAULT_SECRET); + + labApp = ConfidentialClientApplication + .builder(appID, ClientCredentialFactory.createFromSecret(appSecret)) + .authority(LabConstants.MICROSOFT_AUTHORITY) + .build(); + } + + static String getLabAccessToken() + throws MalformedURLException, ExecutionException, InterruptedException { + if (labApp == null) { + initLabApp(); + } + return labApp + .acquireToken( + ClientCredentialParameters + .builder(Collections.singleton(LabConstants.MSIDLAB_DEFAULT_SCOPE)) + .build()) + .get() + .accessToken(); + } + + User getUser(UserQueryParameters query) { + try { + Map queryMap = query.parameters; + String result = HttpClientHelper.sendRequestToLab( + LabConstants.LAB_USER_ENDPOINT, queryMap, getLabAccessToken()); + + User[] users = convertJsonToObject(result, User[].class); + User user = users[0]; + if (user.getUserType().equals("Guest")) { + String secretId = user.getHomeDomain().split("\\.")[0]; + user.setPassword(getSecret(secretId)); + } else { + user.setPassword(getSecret(user.getLabName())); + } + if (query.parameters.containsKey(UserQueryParameters.FEDERATION_PROVIDER)) { + user.setFederationProvider( + query.parameters.get(UserQueryParameters.FEDERATION_PROVIDER)); + } else { + user.setFederationProvider(LabConstants.FEDERATION_PROVIDER_NONE); + } + return user; + } catch (Exception ex) { + throw new RuntimeException("Error getting user from lab: " + ex.getMessage()); + } + } + + public static String getSecret(String labName) { + String result; + try { + Map queryMap = new HashMap<>(); + queryMap.put("secret", labName); + result = HttpClientHelper.sendRequestToLab( + LabConstants.LAB_USER_SECRET_ENDPOINT, queryMap, getLabAccessToken()); + + return convertJsonToObject(result, UserSecret.class).value; + } catch (Exception ex) { + throw new RuntimeException("Error getting user secret from lab: " + ex.getMessage()); + } + } +} diff --git a/msal4j-brokers/src/test/java/labapi/LabUserProvider.java b/msal4j-brokers/src/test/java/labapi/LabUserProvider.java new file mode 100644 index 00000000..cc37543e --- /dev/null +++ b/msal4j-brokers/src/test/java/labapi/LabUserProvider.java @@ -0,0 +1,46 @@ +//---------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +//------------------------------------------------------------------------------ + +package labapi; + +import java.util.HashMap; +import java.util.Map; + +public class LabUserProvider { + private static LabUserProvider instance; + + private final LabService labService; + private Map userCache; + + private LabUserProvider() { + labService = new LabService(); + userCache = new HashMap<>(); + } + + public static synchronized LabUserProvider getInstance() { + if (instance == null) { + instance = new LabUserProvider(); + } + return instance; + } + + public User getDefaultUser() { + UserQueryParameters query = new UserQueryParameters(); + query.parameters.put(UserQueryParameters.AZURE_ENVIRONMENT, LabConstants.AZURE_ENVIRONMENT); + + return getLabUser(query); + } + + public User getLabUser(UserQueryParameters userQuery) { + if (userCache.containsKey(userQuery)) { + return userCache.get(userQuery); + } + User response = labService.getUser(userQuery); + userCache.put(userQuery, response); + return response; + } +} diff --git a/msal4j-brokers/src/test/java/labapi/User.java b/msal4j-brokers/src/test/java/labapi/User.java new file mode 100644 index 00000000..64584380 --- /dev/null +++ b/msal4j-brokers/src/test/java/labapi/User.java @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package labapi; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; + +@Getter +public class User { + @JsonProperty("appId") + private String appId; + + @JsonProperty("userType") + private String userType; + + @JsonProperty("upn") + @Setter + private String upn; + + @JsonProperty("homeDomain") + private String homeDomain; + + @JsonProperty("homeUPN") + private String homeUPN; + + @JsonProperty("labName") + private String labName; + + @Setter + private String password; + + @Setter + private String federationProvider; +} diff --git a/msal4j-brokers/src/test/java/labapi/UserQueryParameters.java b/msal4j-brokers/src/test/java/labapi/UserQueryParameters.java new file mode 100644 index 00000000..a5bafe8d --- /dev/null +++ b/msal4j-brokers/src/test/java/labapi/UserQueryParameters.java @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package labapi; + +import java.util.HashMap; +import java.util.Map; + +public class UserQueryParameters { + public static final String FEDERATION_PROVIDER = "federationprovider"; + public static final String AZURE_ENVIRONMENT = "azureenvironment"; + public Map parameters = new HashMap<>(); +} diff --git a/msal4j-brokers/src/test/java/labapi/UserSecret.java b/msal4j-brokers/src/test/java/labapi/UserSecret.java new file mode 100644 index 00000000..ff4b619a --- /dev/null +++ b/msal4j-brokers/src/test/java/labapi/UserSecret.java @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package labapi; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class UserSecret { + @JsonProperty("secret") + String secret; + + @JsonProperty("value") + String value; +} diff --git a/msal4j-brokers/src/test/java/test/ProofOfPossessionTest.java b/msal4j-brokers/src/test/java/test/ProofOfPossessionTest.java new file mode 100644 index 00000000..07fd5224 --- /dev/null +++ b/msal4j-brokers/src/test/java/test/ProofOfPossessionTest.java @@ -0,0 +1,186 @@ +package test; + +import com.microsoft.aad.msal4j.*; +import com.microsoft.aad.msal4jbrokers.MsalRuntimeBroker; +import infrastructure.SeleniumExtensions; +import labapi.LabUserProvider; +import labapi.User; +import org.openqa.selenium.WebDriver; +import org.testng.Assert; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Collections; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +public class ProofOfPossessionTest { + private final static String MICROSOFT_AUTHORITY_ORGANIZATIONS = + "https://login.microsoftonline.com/organizations/"; + private final static String GRAPH_DEFAULT_SCOPE = "user.read"; + + private LabUserProvider labUserProvider; + + WebDriver seleniumDriver; + + public void setUp() { + labUserProvider = LabUserProvider.getInstance(); + } + + public void acquirePopToken_WithBroker() throws Exception { + User user = labUserProvider.getDefaultUser(); + + MsalRuntimeBroker broker = new MsalRuntimeBroker(); + + PublicClientApplication pca = createPublicClientApp(user, broker); + + IAuthenticationResult result = acquirePoPTokenUsernamePassword(pca, user, Collections.singleton(GRAPH_DEFAULT_SCOPE)); + + //A valid PoP access token should be returned if a broker was set + assertTokenResultNotNull(result); + } + + public void acquirePopToken_WithoutBroker() throws Exception { + User user = labUserProvider.getDefaultUser(); + + PublicClientApplication pca = createPublicClientApp(user); + + //Setting UserNamePasswordParameters.proofOfPossession without enabling the broker should result in an exception when trying to get a token + IAuthenticationResult result = acquirePoPTokenUsernamePassword(pca, user, Collections.singleton(GRAPH_DEFAULT_SCOPE)); + } + + public void acquirePopToken_BrowserAndBroker() throws Exception { + User user = labUserProvider.getDefaultUser(); + + seleniumDriver = SeleniumExtensions.createDefaultWebDriver(); + + //First, get a non-PoP (bearer) token through a browser + PublicClientApplication pcaWithoutBroker = createPublicClientApp(user); + + SystemBrowserOptions browserOptions = + SystemBrowserOptions + .builder() + .openBrowserAction(new SeleniumOpenBrowserAction(user, pcaWithoutBroker)) + .build(); + + IAuthenticationResult browserResult = acquireTokenInteractive(pcaWithoutBroker, browserOptions); + + assertTokenResultNotNull(browserResult); + + seleniumDriver.quit(); + + //Then, get a PoP token silently, using the cache that contains the non-PoP token + MsalRuntimeBroker broker = new MsalRuntimeBroker(); + + PublicClientApplication pcaWithBroker = createPublicClientApp(user, broker, pcaWithoutBroker.tokenCache().serialize()); + + IAuthenticationResult acquireSilentResult = acquireTokenSilent(pcaWithBroker, browserResult.account()); + + //Ensure that the silent request retrieved a new PoP token, rather than the cached non-Pop token + Assert.assertNotNull(acquireSilentResult); + Assert.assertNotEquals(acquireSilentResult.accessToken(), browserResult.accessToken()); + } + + private PublicClientApplication createPublicClientApp(User user) throws MalformedURLException { + return PublicClientApplication.builder(user.getAppId()) + .authority(MICROSOFT_AUTHORITY_ORGANIZATIONS) + .correlationId(UUID.randomUUID().toString()) + .build(); + } + + private PublicClientApplication createPublicClientApp(User user, MsalRuntimeBroker broker) throws MalformedURLException { + return PublicClientApplication.builder(user.getAppId()) + .authority(MICROSOFT_AUTHORITY_ORGANIZATIONS) + .correlationId(UUID.randomUUID().toString()) + .broker(broker) + .build(); + } + + private PublicClientApplication createPublicClientApp(User user, MsalRuntimeBroker broker, String cache) throws MalformedURLException { + return PublicClientApplication.builder(user.getAppId()) + .authority(MICROSOFT_AUTHORITY_ORGANIZATIONS) + .correlationId(UUID.randomUUID().toString()) + .setTokenCacheAccessAspect(new TokenPersistence(cache)) + .broker(broker) + .build(); + } + + private IAuthenticationResult acquirePoPTokenUsernamePassword(PublicClientApplication pca, User user, Set scopes) + throws URISyntaxException, ExecutionException, InterruptedException { + UserNamePasswordParameters parameters = UserNamePasswordParameters.builder( + scopes, user.getUpn(), + user.getPassword().toCharArray()) + .proofOfPossession(HttpMethod.GET, new URI("http://localhost"), null) + .build(); + + return pca.acquireToken(parameters).get(); + } + + private IAuthenticationResult acquireTokenInteractive(PublicClientApplication pca, SystemBrowserOptions browserOptions) + throws URISyntaxException { + InteractiveRequestParameters interactiveParams = InteractiveRequestParameters + .builder(new URI("http://localhost:8080")) + .scopes(Collections.singleton(GRAPH_DEFAULT_SCOPE)) + .systemBrowserOptions(browserOptions) + .build(); + + return pca.acquireToken(interactiveParams).join(); + } + + private IAuthenticationResult acquireTokenSilent(PublicClientApplication pca, IAccount account) throws URISyntaxException, MalformedURLException, ExecutionException, InterruptedException { + SilentParameters silentParams = SilentParameters.builder(Collections.singleton(GRAPH_DEFAULT_SCOPE), account) + .proofOfPossession(HttpMethod.GET, new URI("http://localhost"), null) + .build(); + + return pca.acquireTokenSilently(silentParams).get(); + } + + private void assertTokenResultNotNull(IAuthenticationResult result) { + Assert.assertNotNull(result); + Assert.assertNotNull(result.accessToken()); + Assert.assertNotNull(result.idToken()); + } + + class SeleniumOpenBrowserAction implements OpenBrowserAction { + + private User user; + private PublicClientApplication pca; + + SeleniumOpenBrowserAction(User user, PublicClientApplication pca) { + this.user = user; + this.pca = pca; + } + + public void openBrowser(URL url) { + seleniumDriver.navigate().to(url); + runSeleniumAutomatedLogin(user, pca); + } + } + + void runSeleniumAutomatedLogin(User user, AbstractClientApplicationBase app) { + SeleniumExtensions.performADLogin(seleniumDriver, user); + } + + static class TokenPersistence implements ITokenCacheAccessAspect { + String data; + + TokenPersistence(String data) { + this.data = data; + } + + @Override + public void beforeCacheAccess(ITokenCacheAccessContext iTokenCacheAccessContext) { + iTokenCacheAccessContext.tokenCache().deserialize(data); + } + + @Override + public void afterCacheAccess(ITokenCacheAccessContext iTokenCacheAccessContext) { + data = iTokenCacheAccessContext.tokenCache().serialize(); + } + } +} diff --git a/msal4j-sdk/README.md b/msal4j-sdk/README.md index 2d8bba8e..9e4bc23d 100644 --- a/msal4j-sdk/README.md +++ b/msal4j-sdk/README.md @@ -16,7 +16,7 @@ Quick links: The library supports the following Java environments: - Java 8 (or higher) -Current version - 1.14.0-beta +Current version - 1.14.1-beta You can find the changes for each version in the [change log](https://github.com/AzureAD/microsoft-authentication-library-for-java/blob/master/changelog.txt). @@ -28,13 +28,13 @@ Find [the latest package in the Maven repository](https://mvnrepository.com/arti com.microsoft.azure msal4j - 1.14.0-beta + 1.14.1-beta ``` ### Gradle ```gradle -compile group: 'com.microsoft.azure', name: 'msal4j', version: '1.14.0-beta' +compile group: 'com.microsoft.azure', name: 'msal4j', version: '1.14.1-beta' ``` ## Usage diff --git a/msal4j-sdk/bnd.bnd b/msal4j-sdk/bnd.bnd index 5b3083e0..309b785b 100644 --- a/msal4j-sdk/bnd.bnd +++ b/msal4j-sdk/bnd.bnd @@ -1,2 +1,2 @@ -Export-Package: com.microsoft.aad.msal4j;version="1.14.0-beta" +Export-Package: com.microsoft.aad.msal4j;version="1.14.1-beta" Automatic-Module-Name: com.microsoft.aad.msal4j diff --git a/msal4j-sdk/changelog.txt b/msal4j-sdk/changelog.txt index 8ce801a5..09b53357 100644 --- a/msal4j-sdk/changelog.txt +++ b/msal4j-sdk/changelog.txt @@ -1,8 +1,37 @@ +Version 1.14.1-beta +============= +- Add proof-of-possession token support +- Add MSALRuntime logging support + Version 1.14.0-beta ============= - Add IBroker interface - Add app-level parameter for enabling the use of auth brokers +Version 1.13.8 +============= +- Added support for CIAM authority +- Added refresh_in logic for managed identity flow +- Better exception handling in interactive flow +- Updated vulnerable dependency versions + +Version 1.13.7 +============= +- Update json-smart library version to a secured one. + +Version 1.13.6 +============= +- Added ExtraQueryParameters API. +- added tests for a CIAM user. +- updated condition to throw exception only for an invalid authority while performing instance discovery. + +Version 1.13.5 +============= +- fixed url for admin consent. +- added 2s timeout to IMDS endpoint call. +- fixed url for regional endpoint calls. +- added support for current and legacy B2c authority formats. + Version 1.13.4 ============= - regional endpoint updates diff --git a/msal4j-sdk/pom.xml b/msal4j-sdk/pom.xml index 17595a33..d96c4540 100644 --- a/msal4j-sdk/pom.xml +++ b/msal4j-sdk/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.microsoft.azure msal4j - 1.14.0-beta + 1.14.1-beta jar msal4j @@ -36,12 +36,12 @@ com.nimbusds oauth2-oidc-sdk - 9.35 + 10.7.1 net.minidev json-smart - 2.4.8 + 2.4.10 org.slf4j diff --git a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/AcquireTokenInteractiveIT.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/AcquireTokenInteractiveIT.java index b50e2fdb..edce1e88 100644 --- a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/AcquireTokenInteractiveIT.java +++ b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/AcquireTokenInteractiveIT.java @@ -3,10 +3,7 @@ package com.microsoft.aad.msal4j; -import labapi.AzureEnvironment; -import labapi.B2CProvider; -import labapi.FederationProvider; -import labapi.User; +import labapi.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.Assert; @@ -16,6 +13,8 @@ import java.net.URI; import java.net.URL; import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.ExecutionException; public class AcquireTokenInteractiveIT extends SeleniumTest { @@ -28,13 +27,13 @@ public void acquireTokenInteractive_ManagedUser(String environment) { cfg = new Config(environment); User user = labUserProvider.getDefaultUser(cfg.azureEnvironment); - assertAcquireTokenAAD(user); + assertAcquireTokenCommon(user, cfg.organizationsAuthority(), cfg.graphDefaultScope()); } @Test() public void acquireTokenInteractive_ADFSv2019_OnPrem() { User user = labUserProvider.getOnPremAdfsUser(FederationProvider.ADFS_2019); - assertAcquireTokenADFS2019(user); + assertAcquireTokenCommon(user, TestConstants.ADFS_AUTHORITY, TestConstants.ADFS_SCOPE); } @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) @@ -42,7 +41,7 @@ public void acquireTokenInteractive_ADFSv2019_Federated(String environment) { cfg = new Config(environment); User user = labUserProvider.getFederatedAdfsUser(cfg.azureEnvironment, FederationProvider.ADFS_2019); - assertAcquireTokenAAD(user); + assertAcquireTokenCommon(user, cfg.organizationsAuthority(), cfg.graphDefaultScope()); } @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) @@ -50,7 +49,7 @@ public void acquireTokenInteractive_ADFSv4_Federated(String environment) { cfg = new Config(environment); User user = labUserProvider.getFederatedAdfsUser(cfg.azureEnvironment, FederationProvider.ADFS_4); - assertAcquireTokenAAD(user); + assertAcquireTokenCommon(user, cfg.organizationsAuthority(), cfg.graphDefaultScope()); } @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) @@ -58,7 +57,7 @@ public void acquireTokenInteractive_ADFSv3_Federated(String environment) { cfg = new Config(environment); User user = labUserProvider.getFederatedAdfsUser(cfg.azureEnvironment, FederationProvider.ADFS_3); - assertAcquireTokenAAD(user); + assertAcquireTokenCommon(user, cfg.organizationsAuthority(), cfg.graphDefaultScope()); } @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) @@ -66,7 +65,52 @@ public void acquireTokenInteractive_ADFSv2_Federated(String environment) { cfg = new Config(environment); User user = labUserProvider.getFederatedAdfsUser(cfg.azureEnvironment, FederationProvider.ADFS_2); - assertAcquireTokenAAD(user); + assertAcquireTokenCommon(user, cfg.organizationsAuthority(), cfg.graphDefaultScope()); + } + + @Test + public void acquireTokenInteractive_Ciam() { + User user = labUserProvider.getCiamUser(); + + Map extraQueryParameters = new HashMap<>(); + extraQueryParameters.put("dc","ESTS-PUB-EUS-AZ1-FD000-TEST1"); + + PublicClientApplication pca; + try { + pca = PublicClientApplication.builder( + user.getAppId()). + authority("https://" + user.getLabName() + ".ciamlogin.com/") + .build(); + } catch (MalformedURLException ex) { + throw new RuntimeException(ex.getMessage()); + } + + IAuthenticationResult result; + try { + URI url = new URI("http://localhost:8080"); + + SystemBrowserOptions browserOptions = + SystemBrowserOptions + .builder() + .openBrowserAction(new SeleniumOpenBrowserAction(user, pca)) + .build(); + + InteractiveRequestParameters parameters = InteractiveRequestParameters + .builder(url) + .scopes(Collections.singleton(TestConstants.GRAPH_DEFAULT_SCOPE)) + .extraQueryParameters(extraQueryParameters) + .systemBrowserOptions(browserOptions) + .build(); + + result = pca.acquireToken(parameters).get(); + + } catch (Exception e) { + LOG.error("Error acquiring token with authCode: " + e.getMessage()); + throw new RuntimeException("Error acquiring token with authCode: " + e.getMessage()); + } + + assertTokenResultNotNull(result); + Assert.assertEquals(user.getUpn(), result.account().username()); } @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) @@ -74,7 +118,15 @@ public void acquireTokenWithAuthorizationCode_B2C_Local(String environment) { cfg = new Config(environment); User user = labUserProvider.getB2cUser(cfg.azureEnvironment, B2CProvider.LOCAL); - assertAcquireTokenB2C(user); + assertAcquireTokenB2C(user, TestConstants.B2C_AUTHORITY); + } + + @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) + public void acquireTokenWithAuthorizationCode_B2C_LegacyFormat(String environment) { + cfg = new Config(environment); + + User user = labUserProvider.getB2cUser(cfg.azureEnvironment, B2CProvider.LOCAL); + assertAcquireTokenB2C(user, TestConstants.B2C_AUTHORITY_LEGACY_FORMAT); } @Test @@ -85,12 +137,12 @@ public void acquireTokenInteractive_ManagedUser_InstanceAware() { assertAcquireTokenInstanceAware(user); } - private void assertAcquireTokenAAD(User user) { + private void assertAcquireTokenCommon(User user, String authority, String scope) { PublicClientApplication pca; try { pca = PublicClientApplication.builder( user.getAppId()). - authority(cfg.organizationsAuthority()). + authority(authority). build(); } catch (MalformedURLException ex) { throw new RuntimeException(ex.getMessage()); @@ -99,49 +151,26 @@ private void assertAcquireTokenAAD(User user) { IAuthenticationResult result = acquireTokenInteractive( user, pca, - cfg.graphDefaultScope()); + scope); - Assert.assertNotNull(result); - Assert.assertNotNull(result.accessToken()); - Assert.assertNotNull(result.idToken()); + assertTokenResultNotNull(result); Assert.assertEquals(user.getUpn(), result.account().username()); } - private void assertAcquireTokenADFS2019(User user) { - PublicClientApplication pca; - try { - pca = PublicClientApplication.builder( - TestConstants.ADFS_APP_ID). - authority(TestConstants.ADFS_AUTHORITY). - build(); - } catch (MalformedURLException ex) { - throw new RuntimeException(ex.getMessage()); - } - - IAuthenticationResult result = acquireTokenInteractive(user, pca, TestConstants.ADFS_SCOPE); - - Assert.assertNotNull(result); - Assert.assertNotNull(result.accessToken()); - Assert.assertNotNull(result.idToken()); - Assert.assertEquals(user.getUpn(), result.account().username()); - } - - private void assertAcquireTokenB2C(User user) { + private void assertAcquireTokenB2C(User user, String authority) { PublicClientApplication pca; try { pca = PublicClientApplication.builder( user.getAppId()). - b2cAuthority(TestConstants.B2C_AUTHORITY_SIGN_IN). + b2cAuthority(authority + TestConstants.B2C_SIGN_IN_POLICY). build(); } catch (MalformedURLException ex) { throw new RuntimeException(ex.getMessage()); } IAuthenticationResult result = acquireTokenInteractive(user, pca, user.getAppId()); - Assert.assertNotNull(result); - Assert.assertNotNull(result.accessToken()); - Assert.assertNotNull(result.idToken()); + assertTokenResultNotNull(result); } private void assertAcquireTokenInstanceAware(User user) { @@ -157,9 +186,7 @@ private void assertAcquireTokenInstanceAware(User user) { IAuthenticationResult result = acquireTokenInteractive_instanceAware(user, pca, cfg.graphDefaultScope()); - Assert.assertNotNull(result); - Assert.assertNotNull(result.accessToken()); - Assert.assertNotNull(result.idToken()); + assertTokenResultNotNull(result); Assert.assertEquals(user.getUpn(), result.account().username()); //This test is using a client app with the login.microsoftonline.com config to get tokens for a login.microsoftonline.us user, @@ -223,9 +250,7 @@ public void afterCacheAccess(ITokenCacheAccessContext iTokenCacheAccessContext) build(); IAuthenticationResult result = acquireTokenInteractive(user, publicCloudPca, TestConstants.USER_READ_SCOPE); - Assert.assertNotNull(result); - Assert.assertNotNull(result.accessToken()); - Assert.assertNotNull(result.idToken()); + assertTokenResultNotNull(result); Assert.assertEquals(user.getHomeUPN(), result.account().username()); publicCloudPca.removeAccount(publicCloudPca.getAccounts().join().iterator().next()).join(); @@ -263,6 +288,12 @@ private IAuthenticationResult acquireTokenInteractive( return result; } + private void assertTokenResultNotNull(IAuthenticationResult result) { + Assert.assertNotNull(result); + Assert.assertNotNull(result.accessToken()); + Assert.assertNotNull(result.idToken()); + } + private IAuthenticationResult acquireTokenInteractive_instanceAware( User user, PublicClientApplication pca, 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/ClientCredentialsIT.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/ClientCredentialsIT.java index 8c1f5256..e5c5d157 100644 --- a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/ClientCredentialsIT.java +++ b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/ClientCredentialsIT.java @@ -5,8 +5,11 @@ import labapi.AppCredentialProvider; import labapi.AzureEnvironment; +import labapi.LabUserProvider; +import labapi.User; import org.testng.Assert; import org.testng.annotations.BeforeClass; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.io.IOException; @@ -16,6 +19,8 @@ import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.Callable; import static com.microsoft.aad.msal4j.TestConstants.KEYVAULT_DEFAULT_SCOPE; @@ -23,16 +28,18 @@ @Test public class ClientCredentialsIT { private IClientCertificate certificate; + private LabUserProvider labUserProvider; @BeforeClass void init() throws CertificateException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, NoSuchProviderException, IOException { certificate = CertificateHelper.getClientCertificate(); + labUserProvider = LabUserProvider.getInstance(); } @Test public void acquireTokenClientCredentials_ClientCertificate() throws Exception { String clientId = "2afb0add-2f32-4946-ac90-81a02aa4550e"; - assertAcquireTokenCommon(clientId, certificate); + assertAcquireTokenCommon(clientId, certificate, TestConstants.MICROSOFT_AUTHORITY); } @Test @@ -42,7 +49,7 @@ public void acquireTokenClientCredentials_ClientSecret() throws Exception { final String password = appProvider.getLabVaultPassword(); IClientCredential credential = ClientCredentialFactory.createFromSecret(password); - assertAcquireTokenCommon(clientId, credential); + assertAcquireTokenCommon(clientId, credential, TestConstants.MICROSOFT_AUTHORITY); } @Test @@ -53,7 +60,35 @@ public void acquireTokenClientCredentials_ClientAssertion() throws Exception { IClientCredential credential = ClientCredentialFactory.createFromClientAssertion(clientAssertion.assertion()); - assertAcquireTokenCommon(clientId, credential); + assertAcquireTokenCommon(clientId, credential, TestConstants.MICROSOFT_AUTHORITY); + } + + @Test + public void acquireTokenClientCredentials_ClientSecret_Ciam() throws Exception { + + User user = labUserProvider.getCiamUser(); + String clientId = user.getAppId(); + + Map extraQueryParameters = new HashMap<>(); + extraQueryParameters.put("dc","ESTS-PUB-EUS-AZ1-FD000-TEST1"); + + AppCredentialProvider appProvider = new AppCredentialProvider(AzureEnvironment.CIAM); + IClientCredential credential = ClientCredentialFactory.createFromSecret(appProvider.getOboAppPassword()); + + ConfidentialClientApplication cca = ConfidentialClientApplication.builder( + clientId, credential). + authority("https://" + user.getLabName() + ".ciamlogin.com/"). + build(); + + IAuthenticationResult result = cca.acquireToken(ClientCredentialParameters + .builder(Collections.singleton(TestConstants.GRAPH_DEFAULT_SCOPE)) + .extraQueryParameters(extraQueryParameters) + .build()) + .get(); + + Assert.assertNotNull(result); + Assert.assertNotNull(result.accessToken()); + assertAcquireTokenCommon(clientId, credential, TestConstants.CIAM_AUTHORITY); } @Test @@ -69,7 +104,7 @@ public void acquireTokenClientCredentials_Callback() throws Exception { IClientCredential credential = ClientCredentialFactory.createFromCallback(callable); - assertAcquireTokenCommon(clientId, credential); + assertAcquireTokenCommon(clientId, credential, TestConstants.MICROSOFT_AUTHORITY); // Creates an invalid client assertion to build the application, but overrides it with a valid client assertion // in the request parameters in order to make a successful token request @@ -118,13 +153,18 @@ public void acquireTokenClientCredentials_DefaultCacheLookup() throws Exception Assert.assertNotEquals(result2.accessToken(), result3.accessToken()); } - @Test - public void acquireTokenClientCredentials_Regional() throws Exception { + @DataProvider(name = "regionWithAuthority") + public static Object[][] createData() { + return new Object[][]{{"westus", TestConstants.REGIONAL_MICROSOFT_AUTHORITY_BASIC_HOST_WESTUS}, + {"eastus", TestConstants.REGIONAL_MICROSOFT_AUTHORITY_BASIC_HOST_EASTUS}}; + } + + @Test(dataProvider = "regionWithAuthority") + public void acquireTokenClientCredentials_Regional(String[] regionWithAuthority) throws Exception { String clientId = "2afb0add-2f32-4946-ac90-81a02aa4550e"; - assertAcquireTokenCommon_withRegion(clientId, certificate); + assertAcquireTokenCommon_withRegion(clientId, certificate, regionWithAuthority[0], regionWithAuthority[1]); } - private ClientAssertion getClientAssertion(String clientId) { return JwtHelper.buildJwt( clientId, @@ -133,10 +173,10 @@ private ClientAssertion getClientAssertion(String clientId) { true); } - private void assertAcquireTokenCommon(String clientId, IClientCredential credential) throws Exception { + private void assertAcquireTokenCommon(String clientId, IClientCredential credential, String authority) throws Exception { ConfidentialClientApplication cca = ConfidentialClientApplication.builder( clientId, credential). - authority(TestConstants.MICROSOFT_AUTHORITY). + authority(authority). build(); IAuthenticationResult result = cca.acquireToken(ClientCredentialParameters @@ -164,7 +204,7 @@ private void assertAcquireTokenCommon_withParameters(String clientId, IClientCre Assert.assertNotNull(result.accessToken()); } - private void assertAcquireTokenCommon_withRegion(String clientId, IClientCredential credential) throws Exception { + private void assertAcquireTokenCommon_withRegion(String clientId, IClientCredential credential, String region, String regionalAuthority) throws Exception { ConfidentialClientApplication ccaNoRegion = ConfidentialClientApplication.builder( clientId, credential). authority(TestConstants.MICROSOFT_AUTHORITY). @@ -172,7 +212,7 @@ private void assertAcquireTokenCommon_withRegion(String clientId, IClientCredent ConfidentialClientApplication ccaRegion = ConfidentialClientApplication.builder( clientId, credential). - authority("https://login.microsoft.com/microsoft.onmicrosoft.com").azureRegion("westus"). + authority("https://login.microsoft.com/microsoft.onmicrosoft.com").azureRegion(region). build(); //Ensure behavior when region not specified @@ -193,7 +233,7 @@ private void assertAcquireTokenCommon_withRegion(String clientId, IClientCredent Assert.assertNotNull(resultRegion); Assert.assertNotNull(resultRegion.accessToken()); - Assert.assertEquals(resultRegion.environment(), TestConstants.REGIONAL_MICROSOFT_AUTHORITY_BASIC_HOST_WESTUS); + Assert.assertEquals(resultRegion.environment(), regionalAuthority); IAuthenticationResult resultRegionCached = ccaRegion.acquireToken(ClientCredentialParameters .builder(Collections.singleton(KEYVAULT_DEFAULT_SCOPE)) 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..5624c60c 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, @@ -309,6 +318,7 @@ public void validateAppTokenProviderAsync() throws Exception{ IClientCredential iClientCredential = ClientCredentialFactory.createFromClientAssertion( clientAssertion.assertion()); + Long refreshInSeconds = new Date().getTime() / 1000 + + 800000; //builds client with AppTokenProvider ConfidentialClientApplication cca = ConfidentialClientApplication. builder(TestConfiguration.AAD_CLIENT_ID, iClientCredential) @@ -316,7 +326,7 @@ public void validateAppTokenProviderAsync() throws Exception{ Assert.assertNotNull(parameters.scopes); Assert.assertNotNull(parameters.correlationId); Assert.assertNotNull(parameters.tenantId); - return getAppTokenProviderResult("/default"); + return getAppTokenProviderResult("/default", refreshInSeconds); }) .build(); @@ -329,6 +339,10 @@ public void validateAppTokenProviderAsync() throws Exception{ Assert.assertNotNull(result1.accessToken()); Assert.assertEquals(cca.tokenCache.accessTokens.size(), 1); + //check that refreshOn is set correctly when provided by an app developer + Assert.assertNotNull(cca.tokenCache.accessTokens.values().iterator().next().refreshOn()); + Assert.assertEquals(cca.tokenCache.accessTokens.values().iterator().next().refreshOn(), refreshInSeconds.toString()); + System.out.println(cca.tokenCache.accessTokens.values().iterator().next().refreshOn()); //Acquire token from cache @@ -347,7 +361,7 @@ public void validateAppTokenProviderAsync() throws Exception{ Assert.assertNotNull(parameters.scopes); Assert.assertNotNull(parameters.correlationId); Assert.assertNotNull(parameters.tenantId); - return getAppTokenProviderResult("/newScope"); + return getAppTokenProviderResult("/newScope", 0L); }) .build(); @@ -360,17 +374,20 @@ public void validateAppTokenProviderAsync() throws Exception{ Assert.assertNotEquals(result2.accessToken(), result3.accessToken()); Assert.assertEquals(cca.tokenCache.accessTokens.size(), 1); - + //check that refreshOn is set correctly when a value is not provided by an app developer + Assert.assertNotNull(cca.tokenCache.accessTokens.values().iterator().next().refreshOn()); + System.out.println(cca.tokenCache.accessTokens.values().iterator().next().refreshOn()); } - private CompletableFuture getAppTokenProviderResult(String differentScopesForAt) + private CompletableFuture getAppTokenProviderResult(String differentScopesForAt, + long refreshInSeconds) { long currTimestampSec = new Date().getTime() / 1000; TokenProviderResult token = new TokenProviderResult(); token.setAccessToken(TestConstants.DEFAULT_ACCESS_TOKEN + differentScopesForAt); //Used to indicate that there is a new access token for a different set of scopes token.setTenantId("tenantId"); token.setExpiresInSeconds(currTimestampSec + 1000000); - token.setRefreshInSeconds(currTimestampSec + 800000); + token.setRefreshInSeconds(refreshInSeconds); return CompletableFuture.completedFuture(token); } diff --git a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/DeviceCodeIT.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/DeviceCodeIT.java index aa23ffd3..4b1d10d1 100644 --- a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/DeviceCodeIT.java +++ b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/DeviceCodeIT.java @@ -43,9 +43,7 @@ public void DeviceCodeFlowADTest(String environment) throws Exception { authority(cfg.tenantSpecificAuthority()). build(); - Consumer deviceCodeConsumer = (DeviceCode deviceCode) -> { - runAutomatedDeviceCodeFlow(deviceCode, user); - }; + Consumer deviceCodeConsumer = (DeviceCode deviceCode) -> runAutomatedDeviceCodeFlow(deviceCode, user); IAuthenticationResult result = pca.acquireToken(DeviceCodeFlowParameters .builder(Collections.singleton(cfg.graphDefaultScope()), @@ -113,6 +111,29 @@ public void DeviceCodeFlowMSATest() throws Exception { Assert.assertFalse(Strings.isNullOrEmpty(result.accessToken())); } + @Test + public void DeviceCodeFlowCiamTest() throws Exception { + User user = labUserProvider.getCiamUser(); + + PublicClientApplication pca = PublicClientApplication.builder( + user.getAppId()). + authority("https://" + user.getLabName() + ".ciamlogin.com/"). + build(); + + Consumer deviceCodeConsumer = (DeviceCode deviceCode) -> { + runAutomatedDeviceCodeFlow(deviceCode, user); + }; + + IAuthenticationResult result = pca.acquireToken(DeviceCodeFlowParameters + .builder(Collections.singleton(""), + deviceCodeConsumer) + .build()) + .get(); + + Assert.assertNotNull(result); + Assert.assertFalse(Strings.isNullOrEmpty(result.accessToken())); + } + private void runAutomatedDeviceCodeFlow(DeviceCode deviceCode, User user) { boolean isRunningLocally = true;//!Strings.isNullOrEmpty( //System.getenv(TestConstants.LOCAL_FLAG_ENV_VAR)); @@ -151,7 +172,7 @@ private void runAutomatedDeviceCodeFlow(DeviceCode deviceCode, User user) { if (isADFS2019) { SeleniumExtensions.performADFS2019Login(seleniumDriver, user); } else { - SeleniumExtensions.performADLogin(seleniumDriver, user); + SeleniumExtensions.performADOrCiamLogin(seleniumDriver, user); } } catch (Exception e) { if (!isRunningLocally) { diff --git a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/InvalidAuthorityIT.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/InvalidAuthorityIT.java new file mode 100644 index 00000000..07be1538 --- /dev/null +++ b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/InvalidAuthorityIT.java @@ -0,0 +1,26 @@ +package com.microsoft.aad.msal4j; + +import org.testng.annotations.Test; + +import java.net.URI; +import java.util.Collections; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class InvalidAuthorityIT extends SeleniumTest{ + + @Test(expectedExceptions = ExecutionException.class, expectedExceptionsMessageRegExp = ".*?invalid instance.*?") + public void acquireTokenWithAuthorizationCode_InvalidAuthority() throws Exception{ + PublicClientApplication app; + app = PublicClientApplication.builder( + TestConfiguration.AAD_CLIENT_ID) + .authority("https://dummy.microsoft.com/common") //invalid authority, request fails at instance discovery + .build(); + + CompletableFuture future = app.acquireToken( + AuthorizationCodeParameters.builder("auth_code", new URI(TestConfiguration.AAD_DEFAULT_REDIRECT_URI)) + .scopes(Collections.singleton("default-scope")) + .authorizationCode("auth_code").redirectUri(new URI(TestConfiguration.AAD_DEFAULT_REDIRECT_URI)).build()); + future.get(); + } +} diff --git a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/SeleniumTest.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/SeleniumTest.java index 62306e12..ccb218b1 100644 --- a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/SeleniumTest.java +++ b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/SeleniumTest.java @@ -51,9 +51,11 @@ void runSeleniumAutomatedLogin(User user, AbstractClientApplicationBase app) { break; } } else if (authorityType == AuthorityType.AAD) { - SeleniumExtensions.performADLogin(seleniumDriver, user); + SeleniumExtensions.performADOrCiamLogin(seleniumDriver, user); } else if (authorityType == AuthorityType.ADFS) { SeleniumExtensions.performADFS2019Login(seleniumDriver, user); + } else if (authorityType == AuthorityType.CIAM) { + SeleniumExtensions.performADOrCiamLogin(seleniumDriver, user); } } } diff --git a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/TestConstants.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/TestConstants.java index 97e65f16..d11fcdf8 100644 --- a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/TestConstants.java +++ b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/TestConstants.java @@ -32,14 +32,20 @@ public class TestConstants { public final static String TENANT_SPECIFIC_AUTHORITY = MICROSOFT_AUTHORITY_HOST + MICROSOFT_AUTHORITY_TENANT; public final static String REGIONAL_MICROSOFT_AUTHORITY_BASIC_HOST_WESTUS = "westus.login.microsoft.com"; + public final static String REGIONAL_MICROSOFT_AUTHORITY_BASIC_HOST_EASTUS = "eastus.login.microsoft.com"; + +// public final static String CIAM_AUTHORITY = MICROSOFT_AUTHORITY_HOST + "msidlabciam1.onmicrosoft.com"; + public final static String CIAM_AUTHORITY = "https://msidlabciam1.ciamlogin.com/" + "msidlabciam1.onmicrosoft.com"; + + public final static String CIAM_TEST_AUTHORITY = "https://contoso0781.ciamlogin.com/6babcaad-604b-40ac-a9d7-9fd97c0b779f/v2.0/.well-known/openid-configuration?dc=ESTS-PUB-EUS-AZ1-FD000-TEST1&ciamhost=true"; + public final static String ARLINGTON_ORGANIZATIONS_AUTHORITY = ARLINGTON_MICROSOFT_AUTHORITY_HOST + "organizations/"; - public final static String ARLINGTON_COMMON_AUTHORITY = ARLINGTON_MICROSOFT_AUTHORITY_HOST + "common/"; public final static String ARLINGTON_TENANT_SPECIFIC_AUTHORITY = ARLINGTON_MICROSOFT_AUTHORITY_HOST + ARLINGTON_AUTHORITY_TENANT; public final static String ARLINGTON_GRAPH_DEFAULT_SCOPE = "https://graph.microsoft.us/.default"; + public final static String B2C_AUTHORITY = "https://msidlabb2c.b2clogin.com/msidlabb2c.onmicrosoft.com/"; + public final static String B2C_AUTHORITY_LEGACY_FORMAT = "https://msidlabb2c.b2clogin.com/tfp/msidlabb2c.onmicrosoft.com/"; - public final static String B2C_AUTHORITY = "https://msidlabb2c.b2clogin.com/tfp/msidlabb2c.onmicrosoft.com/"; - public final static String B2C_AUTHORITY_URL = "https://msidlabb2c.b2clogin.com/msidlabb2c.onmicrosoft.com/"; public final static String B2C_ROPC_POLICY = "B2C_1_ROPC_Auth"; public final static String B2C_SIGN_IN_POLICY = "B2C_1_SignInPolicy"; public final static String B2C_AUTHORITY_SIGN_IN = B2C_AUTHORITY + B2C_SIGN_IN_POLICY; @@ -49,7 +55,6 @@ public class TestConstants { public final static String B2C_MICROSOFTLOGIN_ROPC = B2C_MICROSOFTLOGIN_AUTHORITY + B2C_ROPC_POLICY; public final static String LOCALHOST = "http://localhost:"; - public final static String LOCAL_FLAG_ENV_VAR = "MSAL_JAVA_RUN_LOCAL"; public final static String ADFS_AUTHORITY = "https://fs.msidlab8.com/adfs/"; public final static String ADFS_SCOPE = USER_READ_SCOPE; @@ -57,11 +62,6 @@ public class TestConstants { public final static String CLAIMS = "{\"id_token\":{\"auth_time\":{\"essential\":true}}}"; public final static Set CLIENT_CAPABILITIES_EMPTY = new HashSet<>(Collections.emptySet()); - public final static Set CLIENT_CAPABILITIES_LLT = new HashSet<>(Collections.singletonList("llt")); - - // cross cloud b2b settings - public final static String AUTHORITY_ARLINGTON = "https://login.microsoftonline.us/" + ARLINGTON_AUTHORITY_TENANT; - public final static String AUTHORITY_MOONCAKE = "https://login.chinacloudapi.cn/mncmsidlab1.partner.onmschina.cn"; public final static String AUTHORITY_PUBLIC_TENANT_SPECIFIC = "https://login.microsoftonline.com/" + MICROSOFT_AUTHORITY_TENANT; public final static String DEFAULT_ACCESS_TOKEN = "defaultAccessToken"; 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/integrationtest/java/com.microsoft.aad.msal4j/UsernamePasswordIT.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/UsernamePasswordIT.java index 498166aa..8b9c9fe0 100644 --- a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/UsernamePasswordIT.java +++ b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/UsernamePasswordIT.java @@ -9,6 +9,8 @@ import org.testng.annotations.Test; import java.util.Collections; +import java.util.HashMap; +import java.util.Map; @Test() public class UsernamePasswordIT { @@ -27,7 +29,7 @@ public void acquireTokenWithUsernamePassword_Managed(String environment) throws User user = labUserProvider.getDefaultUser(cfg.azureEnvironment); - assertAcquireTokenCommonAAD(user); + assertAcquireTokenCommon(user, cfg.organizationsAuthority(), cfg.graphDefaultScope(), user.getAppId()); } @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) @@ -41,7 +43,7 @@ public void acquireTokenWithUsernamePassword_ADFSv2019_Federated(String environm User user = labUserProvider.getLabUser(query); - assertAcquireTokenCommonAAD(user); + assertAcquireTokenCommon(user, cfg.organizationsAuthority(), cfg.graphDefaultScope(), user.getAppId()); } @Test @@ -52,7 +54,7 @@ public void acquireTokenWithUsernamePassword_ADFSv2019_OnPrem() throws Exception User user = labUserProvider.getLabUser(query); - assertAcquireTokenCommonADFS(user); + assertAcquireTokenCommon(user, TestConstants.ADFS_AUTHORITY, TestConstants.ADFS_SCOPE, TestConstants.ADFS_APP_ID); } @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) @@ -66,7 +68,7 @@ public void acquireTokenWithUsernamePassword_ADFSv4(String environment) throws E User user = labUserProvider.getLabUser(query); - assertAcquireTokenCommonAAD(user); + assertAcquireTokenCommon(user, cfg.organizationsAuthority(), cfg.graphDefaultScope(), user.getAppId()); } @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) @@ -80,7 +82,7 @@ public void acquireTokenWithUsernamePassword_ADFSv3(String environment) throws E User user = labUserProvider.getLabUser(query); - assertAcquireTokenCommonAAD(user); + assertAcquireTokenCommon(user, cfg.organizationsAuthority(), cfg.graphDefaultScope(), user.getAppId()); } @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) @@ -97,6 +99,29 @@ public void acquireTokenWithUsernamePassword_ADFSv2(String environment) throws E assertAcquireTokenCommonAAD(user); } + @Test + public void acquireTokenWithUsernamePassword_Ciam() throws Exception { + + Map extraQueryParameters = new HashMap<>(); + extraQueryParameters.put("dc","ESTS-PUB-EUS-AZ1-FD000-TEST1"); + + User user = labUserProvider.getCiamUser(); + PublicClientApplication pca = PublicClientApplication.builder(user.getAppId()) + .authority("https://" + user.getLabName() + ".ciamlogin.com/") + .build(); + + + IAuthenticationResult result = pca.acquireToken(UserNamePasswordParameters. + builder(Collections.singleton(TestConstants.GRAPH_DEFAULT_SCOPE), + user.getUpn(), + user.getPassword().toCharArray()) + .extraQueryParameters(extraQueryParameters) + .build()) + .get(); + + Assert.assertNotNull(result.accessToken()); + } + @Test public void acquireTokenWithUsernamePassword_AuthorityWithPort() throws Exception { User user = labUserProvider.getDefaultUser(); @@ -108,10 +133,6 @@ public void acquireTokenWithUsernamePassword_AuthorityWithPort() throws Exceptio user.getAppId()); } - private void assertAcquireTokenCommonADFS(User user) throws Exception { - assertAcquireTokenCommon(user, TestConstants.ADFS_AUTHORITY, TestConstants.ADFS_SCOPE, - TestConstants.ADFS_APP_ID); - } private void assertAcquireTokenCommonAAD(User user) throws Exception { assertAcquireTokenCommon(user, cfg.organizationsAuthority(), cfg.graphDefaultScope(), @@ -120,6 +141,7 @@ private void assertAcquireTokenCommonAAD(User user) throws Exception { private void assertAcquireTokenCommon(User user, String authority, String scope, String appId) throws Exception { + PublicClientApplication pca = PublicClientApplication.builder( appId). authority(authority). @@ -130,11 +152,10 @@ private void assertAcquireTokenCommon(User user, String authority, String scope, user.getUpn(), user.getPassword().toCharArray()) .build()) + .get(); - Assert.assertNotNull(result); - Assert.assertNotNull(result.accessToken()); - Assert.assertNotNull(result.idToken()); + assertTokenResultNotNull(result); Assert.assertEquals(user.getUpn(), result.account().username()); } @@ -157,9 +178,7 @@ public void acquireTokenWithUsernamePassword_B2C_CustomAuthority() throws Except .build()) .get(); - Assert.assertNotNull(result); - Assert.assertNotNull(result.accessToken()); - Assert.assertNotNull(result.idToken()); + assertTokenResultNotNull(result); IAccount account = pca.getAccounts().join().iterator().next(); SilentParameters.builder(Collections.singleton(TestConstants.B2C_READ_SCOPE), account); @@ -169,9 +188,7 @@ public void acquireTokenWithUsernamePassword_B2C_CustomAuthority() throws Except .build()) .get(); - Assert.assertNotNull(result); - Assert.assertNotNull(result.accessToken()); - Assert.assertNotNull(result.idToken()); + assertTokenResultNotNull(result); } @Test @@ -193,9 +210,7 @@ public void acquireTokenWithUsernamePassword_B2C_LoginMicrosoftOnline() throws E .build()) .get(); - Assert.assertNotNull(result); - Assert.assertNotNull(result.accessToken()); - Assert.assertNotNull(result.idToken()); + assertTokenResultNotNull(result); IAccount account = pca.getAccounts().join().iterator().next(); SilentParameters.builder(Collections.singleton(TestConstants.B2C_READ_SCOPE), account); @@ -205,6 +220,10 @@ public void acquireTokenWithUsernamePassword_B2C_LoginMicrosoftOnline() throws E .build()) .get(); + assertTokenResultNotNull(result); + } + + private void assertTokenResultNotNull(IAuthenticationResult result) { Assert.assertNotNull(result); Assert.assertNotNull(result.accessToken()); Assert.assertNotNull(result.idToken()); diff --git a/msal4j-sdk/src/integrationtest/java/infrastructure/SeleniumExtensions.java b/msal4j-sdk/src/integrationtest/java/infrastructure/SeleniumExtensions.java index f7084c01..c40784b5 100644 --- a/msal4j-sdk/src/integrationtest/java/infrastructure/SeleniumExtensions.java +++ b/msal4j-sdk/src/integrationtest/java/infrastructure/SeleniumExtensions.java @@ -65,8 +65,8 @@ public static WebElement waitForElementToBeVisibleAndEnable(WebDriver driver, By return waitForElementToBeVisibleAndEnable(driver, by, DEFAULT_TIMEOUT_IN_SEC); } - public static void performADLogin(WebDriver driver, User user) { - LOG.info("PerformADLogin"); + public static void performADOrCiamLogin(WebDriver driver, User user) { + LOG.info("performADOrCiamLogin"); UserInformationFields fields = new UserInformationFields(user); @@ -76,7 +76,7 @@ public static void performADLogin(WebDriver driver, User user) { LOG.info("Loggin in ... Clicking after username"); driver.findElement(new By.ById(fields.getAadSignInButtonId())).click(); - if (user.getFederationProvider() == FederationProvider.ADFS_2 && + if (user.getFederationProvider().equals(FederationProvider.ADFS_2) && !user.getLabName().equals(LabConstants.ARLINGTON_LAB_NAME)) { LOG.info("Loggin in ... ADFS-V2 - Entering the username in ADFSv2 form"); @@ -96,6 +96,7 @@ public static void performADLogin(WebDriver driver, User user) { checkAuthenticationCompletePage(driver); return; } catch (TimeoutException ex) { + LOG.error(ex.getMessage()); } LOG.info("Checking optional questions"); @@ -107,6 +108,7 @@ public static void performADLogin(WebDriver driver, User user) { LOG.info("Are you trying to sign in to ... ? click Continue"); } catch (TimeoutException ex) { + LOG.error(ex.getMessage()); } try { @@ -115,6 +117,7 @@ public static void performADLogin(WebDriver driver, User user) { click(); LOG.info("Stay signed in? click NO"); } catch (TimeoutException ex) { + LOG.error(ex.getMessage()); } } diff --git a/msal4j-sdk/src/integrationtest/java/labapi/AppCredentialProvider.java b/msal4j-sdk/src/integrationtest/java/labapi/AppCredentialProvider.java index af949eec..2a458449 100644 --- a/msal4j-sdk/src/integrationtest/java/labapi/AppCredentialProvider.java +++ b/msal4j-sdk/src/integrationtest/java/labapi/AppCredentialProvider.java @@ -35,8 +35,11 @@ public AppCredentialProvider(String azureEnvironment) { oboClientId = LabConstants.ARLINGTON_OBO_APP_ID; oboAppIdURI = "https://arlmsidlab1.us/IDLABS_APP_Confidential_Client"; - oboPassword = keyVaultSecretsProvider. - getSecret(LabService.getApp(oboClientId).clientSecret); + oboPassword = keyVaultSecretsProvider.getSecret(LabService.getApp(oboClientId).clientSecret); + break; + case AzureEnvironment.CIAM: + oboPassword = keyVaultSecretsProvider.getSecret(LabConstants.CIAM_KEY_VAULT_SECRET_KEY); + break; default: throw new UnsupportedOperationException("Azure Environment - " + azureEnvironment + " unsupported"); diff --git a/msal4j-sdk/src/integrationtest/java/labapi/AzureEnvironment.java b/msal4j-sdk/src/integrationtest/java/labapi/AzureEnvironment.java index 3b1dbb7f..6faa0e54 100644 --- a/msal4j-sdk/src/integrationtest/java/labapi/AzureEnvironment.java +++ b/msal4j-sdk/src/integrationtest/java/labapi/AzureEnvironment.java @@ -10,4 +10,5 @@ public class AzureEnvironment { public static final String AZURE = "azurecloud"; public static final String AZURE_PPE = "azureppe"; public static final String AZURE_US_GOVERNMENT = "azureusgovernment"; + public static final String CIAM = "ciam"; } diff --git a/msal4j-sdk/src/integrationtest/java/labapi/FederationProvider.java b/msal4j-sdk/src/integrationtest/java/labapi/FederationProvider.java index e586fe5b..39291622 100644 --- a/msal4j-sdk/src/integrationtest/java/labapi/FederationProvider.java +++ b/msal4j-sdk/src/integrationtest/java/labapi/FederationProvider.java @@ -12,6 +12,7 @@ public class FederationProvider { public static final String ADFS_2019 = "adfsv2019"; public static final String PING = "ping"; public static final String SHIBBOLETH = "shibboleth"; + public static final String CIAM = "ciam"; } diff --git a/msal4j-sdk/src/integrationtest/java/labapi/LabConstants.java b/msal4j-sdk/src/integrationtest/java/labapi/LabConstants.java index ba3ddf81..569acafd 100644 --- a/msal4j-sdk/src/integrationtest/java/labapi/LabConstants.java +++ b/msal4j-sdk/src/integrationtest/java/labapi/LabConstants.java @@ -14,6 +14,7 @@ public class LabConstants { public final static String USER_MSA_USERNAME_URL = "https://msidlabs.vault.azure.net/secrets/MSA-MSIDLAB4-UserName"; public final static String USER_MSA_PASSWORD_URL = "https://msidlabs.vault.azure.net/secrets/MSA-MSIDLAB4-Password"; public final static String OBO_APP_PASSWORD_URL = "https://msidlabs.vault.azure.net/secrets/TodoListServiceV2-OBO"; + public final static String CIAM_KEY_VAULT_SECRET_KEY = "https://msidlabs.vault.azure.net/secrets/MSIDLABCIAM1-cc"; public final static String ARLINGTON_APP_ID = "cb7faed4-b8c0-49ee-b421-f5ed16894c83"; public final static String ARLINGTON_OBO_APP_ID = "c0555d2d-02f2-4838-802e-3463422e571d"; diff --git a/msal4j-sdk/src/integrationtest/java/labapi/LabUserProvider.java b/msal4j-sdk/src/integrationtest/java/labapi/LabUserProvider.java index c169d8a7..68eca889 100644 --- a/msal4j-sdk/src/integrationtest/java/labapi/LabUserProvider.java +++ b/msal4j-sdk/src/integrationtest/java/labapi/LabUserProvider.java @@ -105,6 +105,16 @@ public User getUserByGuestHomeAzureEnvironments(String guestEnvironment, String return getLabUser(query); } + public User getCiamUser() { + + UserQueryParameters query = new UserQueryParameters(); + query.parameters.put(UserQueryParameters.FEDERATION_PROVIDER, FederationProvider.CIAM); + query.parameters.put(UserQueryParameters.SIGN_IN_AUDIENCE, "azureadmyorg"); + query.parameters.put(UserQueryParameters.PUBLIC_CLIENT, "no"); + + return getLabUser(query); + } + public User getLabUser(UserQueryParameters userQuery) { if (userCache.containsKey(userQuery)) { return userCache.get(userQuery); diff --git a/msal4j-sdk/src/integrationtest/java/labapi/UserQueryParameters.java b/msal4j-sdk/src/integrationtest/java/labapi/UserQueryParameters.java index 56243152..b25c46cb 100644 --- a/msal4j-sdk/src/integrationtest/java/labapi/UserQueryParameters.java +++ b/msal4j-sdk/src/integrationtest/java/labapi/UserQueryParameters.java @@ -22,6 +22,7 @@ public class UserQueryParameters { public static final String HOME_AZURE_ENVIRONMENT = "guesthomeazureenvironment"; public static final String GUEST_HOME_DIN = "guesthomedin"; public static final String SIGN_IN_AUDIENCE = "signInAudience"; + public static final String PUBLIC_CLIENT = "publicClient"; public Map parameters = new HashMap<>(); } diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AadInstanceDiscoveryProvider.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AadInstanceDiscoveryProvider.java index 72c61451..a66094e9 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AadInstanceDiscoveryProvider.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AadInstanceDiscoveryProvider.java @@ -14,7 +14,7 @@ import java.util.TreeSet; import java.util.Map; import java.util.HashMap; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.*; class AadInstanceDiscoveryProvider { @@ -22,7 +22,7 @@ class AadInstanceDiscoveryProvider { private final static String AUTHORIZE_ENDPOINT_TEMPLATE = "https://{host}/{tenant}/oauth2/v2.0/authorize"; private final static String INSTANCE_DISCOVERY_ENDPOINT_TEMPLATE = "https://{host}:{port}/common/discovery/instance"; private final static String INSTANCE_DISCOVERY_REQUEST_PARAMETERS_TEMPLATE = "?api-version=1.1&authorization_endpoint={authorizeEndpoint}"; - private final static String HOST_TEMPLATE_WITH_REGION = "{region}.{host}"; + private final static String HOST_TEMPLATE_WITH_REGION = "{region}.login.microsoft.com"; private final static String SOVEREIGN_HOST_TEMPLATE_WITH_REGION = "{region}.{host}"; private final static String REGION_NAME = "REGION_NAME"; private final static int PORT_NOT_SET = -1; @@ -31,11 +31,15 @@ class AadInstanceDiscoveryProvider { private static final String DEFAULT_API_VERSION = "2020-06-01"; private static final String IMDS_ENDPOINT = "https://169.254.169.254/metadata/instance/compute/location?" + DEFAULT_API_VERSION + "&format=text"; + private static final int IMDS_TIMEOUT = 2; + private static final TimeUnit IMDS_TIMEOUT_UNIT = TimeUnit.SECONDS; static final TreeSet TRUSTED_HOSTS_SET = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); static final TreeSet TRUSTED_SOVEREIGN_HOSTS_SET = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); private static final Logger log = LoggerFactory.getLogger(AadInstanceDiscoveryProvider.class); + //flag to check if instance discovery has failed + private static boolean instanceDiscoveryFailed = false; static ConcurrentHashMap cache = new ConcurrentHashMap<>(); static { @@ -47,7 +51,9 @@ class AadInstanceDiscoveryProvider { TRUSTED_HOSTS_SET.addAll(Arrays.asList( "login.windows.net", - "login.microsoftonline.com")); + "login.microsoftonline.com", + "login.microsoft.com", + "sts.windows.net")); TRUSTED_HOSTS_SET.addAll(TRUSTED_SOVEREIGN_HOSTS_SET); } @@ -69,8 +75,8 @@ static InstanceDiscoveryMetadataEntry getMetadataEntry(URL authorityUrl, //If region autodetection is enabled and a specific region not already set, // set the application's region to the discovered region so that future requests can skip the IMDS endpoint call if (null == msalRequest.application().azureRegion() && msalRequest.application().autoDetectRegion() - && null != detectedRegion) { - msalRequest.application().azureRegion = detectedRegion; + && null != detectedRegion) { + msalRequest.application().azureRegion = detectedRegion; } cacheRegionInstanceMetadata(authorityUrl.getHost(), msalRequest.application().azureRegion()); serviceBundle.getServerSideTelemetry().getCurrentRequest().regionOutcome( @@ -80,7 +86,7 @@ static InstanceDiscoveryMetadataEntry getMetadataEntry(URL authorityUrl, InstanceDiscoveryMetadataEntry result = cache.get(host); if (result == null) { - if(msalRequest.application().instanceDiscovery()){ + if(msalRequest.application().instanceDiscovery() && !instanceDiscoveryFailed){ doInstanceDiscoveryAndCache(authorityUrl, validateAuthority, msalRequest, serviceBundle); } else { // instanceDiscovery flag is set to False. Do not perform instanceDiscovery. @@ -187,8 +193,7 @@ private static String getRegionalizedHost(String host, String region) { // whereas sovereign cloud endpoints and any non-Microsoft authorities are assumed to follow another template if (TRUSTED_HOSTS_SET.contains(host) && !TRUSTED_SOVEREIGN_HOSTS_SET.contains(host)){ regionalizedHost = HOST_TEMPLATE_WITH_REGION. - replace("{region}", region). - replace("{host}", host); + replace("{region}", region); } else { regionalizedHost = SOVEREIGN_HOST_TEMPLATE_WITH_REGION. @@ -231,12 +236,18 @@ private static AadInstanceDiscoveryResponse sendInstanceDiscoveryRequest(URL aut httpResponse = executeRequest(instanceDiscoveryRequestUrl, msalRequest.headers().getReadonlyHeaderMap(), msalRequest, serviceBundle); + AadInstanceDiscoveryResponse response = JsonHelper.convertJsonToObject(httpResponse.body(), AadInstanceDiscoveryResponse.class); + if (httpResponse.statusCode() != HttpHelper.HTTP_STATUS_200) { - throw MsalServiceExceptionFactory.fromHttpResponse(httpResponse); + if(httpResponse.statusCode() == HttpHelper.HTTP_STATUS_400 && response.error().equals("invalid_instance")){ + // instance discovery failed due to an invalid authority, throw an exception. + throw MsalServiceExceptionFactory.fromHttpResponse(httpResponse); + } + // instance discovery failed due to reasons other than an invalid authority, do not perform instance discovery again in this environment. + instanceDiscoveryFailed = true; } - - return JsonHelper.convertJsonToObject(httpResponse.body(), AadInstanceDiscoveryResponse.class); + return response; } private static int determineRegionOutcome(String detectedRegion, String providedRegion, boolean autoDetect) { @@ -290,33 +301,39 @@ private static String discoverRegion(MsalRequest msalRequest, ServiceBundle serv return System.getenv(REGION_NAME); } - try { - //Check the IMDS endpoint to retrieve current region (will only work if application is running in an Azure VM) - Map headers = new HashMap<>(); - headers.put("Metadata", "true"); - IHttpResponse httpResponse = executeRequest(IMDS_ENDPOINT, headers, msalRequest, serviceBundle); + //Check the IMDS endpoint to retrieve current region (will only work if application is running in an Azure VM) + Map headers = new HashMap<>(); + headers.put("Metadata", "true"); + ExecutorService executor = Executors.newSingleThreadExecutor(); + Future future = executor.submit(() -> executeRequest(IMDS_ENDPOINT, headers, msalRequest, serviceBundle)); + + try { + log.info("Starting call to IMDS endpoint."); + IHttpResponse httpResponse = future.get(IMDS_TIMEOUT, IMDS_TIMEOUT_UNIT); //If call to IMDS endpoint was successful, return region from response body if (httpResponse.statusCode() == HttpHelper.HTTP_STATUS_200 && !httpResponse.body().isEmpty()) { - log.info("Region retrieved from IMDS endpoint: " + httpResponse.body()); + log.info(String.format("Region retrieved from IMDS endpoint: %s", httpResponse.body())); currentRequest.regionSource(RegionTelemetry.REGION_SOURCE_IMDS.telemetryValue); return httpResponse.body(); } - log.warn(String.format("Call to local IMDS failed with status code: %s, or response was empty", httpResponse.statusCode())); currentRequest.regionSource(RegionTelemetry.REGION_SOURCE_FAILED_AUTODETECT.telemetryValue); - - return null; - } catch (Exception e) { + } catch (Exception ex) { + // handle other exceptions //IMDS call failed, cannot find region //The IMDS endpoint is only available from within an Azure environment, so the most common cause of this // exception will likely be java.net.SocketException: Network is unreachable: connect - log.warn(String.format("Exception during call to local IMDS endpoint: %s", e.getMessage())); + log.warn(String.format("Exception during call to local IMDS endpoint: %s", ex.getMessage())); currentRequest.regionSource(RegionTelemetry.REGION_SOURCE_FAILED_AUTODETECT.telemetryValue); + future.cancel(true); - return null; + } finally { + executor.shutdownNow(); } + + return null; } private static void doInstanceDiscoveryAndCache(URL authorityUrl, diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AbstractClientApplicationBase.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AbstractClientApplicationBase.java index b1b7ba6b..37ee24d5 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AbstractClientApplicationBase.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AbstractClientApplicationBase.java @@ -357,7 +357,7 @@ public T authority(String val) throws MalformedURLException { authority = Authority.enforceTrailingSlash(val); URL authorityURL = new URL(authority); - Authority.validateAuthority(authorityURL); + switch (Authority.detectAuthorityType(authorityURL)) { case AAD: @@ -366,13 +366,30 @@ public T authority(String val) throws MalformedURLException { case ADFS: authenticationAuthority = new ADFSAuthority(authorityURL); break; + case CIAM: + authenticationAuthority = new CIAMAuthority(authorityURL); + break; default: throw new IllegalArgumentException("Unsupported authority type."); } + Authority.validateAuthority(authenticationAuthority.canonicalAuthorityUrl()); + return self(); } + /** + * Set URL of the authenticating B2C authority from which MSAL will acquire tokens + * + * Valid B2C authorities should look like: https://<something.b2clogin.com/<tenant>/<policy> + * + * MSAL Java also supports a legacy B2C authority format, which looks like: https://<host>/tfp/<tenant>/<policy> + * + * However, MSAL Java will eventually stop supporting the legacy format. See here for information on how to migrate to the new format: https://aka.ms/msal4j-b2c + * + * @param val a boolean value for validateAuthority + * @return instance of the Builder on which method was called + */ public T b2cAuthority(String val) throws MalformedURLException { authority = Authority.enforceTrailingSlash(val); diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AcquireTokenByAppProviderSupplier.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AcquireTokenByAppProviderSupplier.java index 1afa2d7d..4966f0de 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AcquireTokenByAppProviderSupplier.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AcquireTokenByAppProviderSupplier.java @@ -11,6 +11,8 @@ */ class AcquireTokenByAppProviderSupplier extends AuthenticationResultSupplier { + private static final int TWO_HOURS = 2*3600; + private AppTokenProviderParameters appTokenProviderParameters; private ClientCredentialRequest clientCredentialRequest; @@ -23,7 +25,7 @@ class AcquireTokenByAppProviderSupplier extends AuthenticationResultSupplier { this.appTokenProviderParameters = appTokenProviderParameters; } - private static void validateTokenProviderResult(TokenProviderResult tokenProviderResult) { + private static void validateAndUpdateTokenProviderResult(TokenProviderResult tokenProviderResult) { if (null == tokenProviderResult.getAccessToken() || tokenProviderResult.getAccessToken().isEmpty()) { handleInvalidExternalValueError(tokenProviderResult.getAccessToken()); } @@ -35,6 +37,13 @@ private static void validateTokenProviderResult(TokenProviderResult tokenProvide if (null == tokenProviderResult.getTenantId() || tokenProviderResult.getTenantId().isEmpty()) { handleInvalidExternalValueError(tokenProviderResult.getTenantId()); } + + if (0 == tokenProviderResult.getRefreshInSeconds()){ + long expireInSeconds = tokenProviderResult.getExpiresInSeconds(); + if(expireInSeconds >= TWO_HOURS){ + tokenProviderResult.setRefreshInSeconds(expireInSeconds/2); + } + } } private static void handleInvalidExternalValueError(String nameOfValue) { @@ -70,7 +79,7 @@ public AuthenticationResult fetchTokenUsingAppTokenProvider(AppTokenProviderPara throw new MsalAzureSDKException(ex); } - validateTokenProviderResult(tokenProviderResult); + validateAndUpdateTokenProviderResult(tokenProviderResult); return AuthenticationResult.builder() .accessToken(tokenProviderResult.getAccessToken()) diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AcquireTokenByInteractiveFlowSupplier.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AcquireTokenByInteractiveFlowSupplier.java index f9dadf71..8029c5d8 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AcquireTokenByInteractiveFlowSupplier.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AcquireTokenByInteractiveFlowSupplier.java @@ -7,17 +7,20 @@ import org.slf4j.LoggerFactory; import java.awt.*; +import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.util.Arrays; +import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; class AcquireTokenByInteractiveFlowSupplier extends AuthenticationResultSupplier { - private final static Logger LOG = LoggerFactory.getLogger(AcquireTokenByAuthorizationGrantSupplier.class); + private static final Logger LOG = LoggerFactory.getLogger(AcquireTokenByInteractiveFlowSupplier.class); private PublicClientApplication clientApplication; private InteractiveRequest interactiveRequest; @@ -25,6 +28,12 @@ class AcquireTokenByInteractiveFlowSupplier extends AuthenticationResultSupplier private BlockingQueue authorizationResultQueue; private HttpListener httpListener; + /**MSAL tried to open the browser on Linux using the xdg-open, gnome-open, or kfmclient tools, but failed. + Make sure you can open a page using xdg-open tool. See ... for details. */ + public static final String LINUX_XDG_OPEN = "linux_xdg_open_failed"; + + public static final String LINUX_OPEN_AS_SUDO_NOT_SUPPORTED = "Unable to open a web page using xdg-open, gnome-open, kfmclient or wslview tools in sudo mode. Please run the process as non-sudo user."; + AcquireTokenByInteractiveFlowSupplier(PublicClientApplication clientApplication, InteractiveRequest request) { super(clientApplication, request); @@ -106,8 +115,38 @@ private void updateRedirectUrl() { AuthenticationErrorCode.INVALID_REDIRECT_URI); } } + private static List getOpenToolsLinux() { + return Arrays.asList("xdg-open", "gnome-open", "kfmclient", "microsoft-edge", "wslview"); + } + + private static String getExecutablePath(String executable) { + String pathEnvVar = System.getenv("PATH"); + if (pathEnvVar != null) { + String[] paths = pathEnvVar.split(File + .pathSeparator); + for (String basePath : paths) { + String path = basePath + File.separator + executable; + if (new File(path).exists()) { + return path; + } + } + } + return null; + } + + private void openDefaultSystemBrowser(URL url){ + if (OSHelper.isWindows()) { //windows + openDefaultSystemBrowserInWindows(url); + } else if (OSHelper.isMac()) { // mac os + openDefaultSystemBrowserInMac(url); + } else if (OSHelper.isLinux()) { //linux or unix os + openDefaultSystemBrowserInLinux(url); + } else { + throw new UnsupportedOperationException(OSHelper.getOs() + "Operating system not supported exception."); + } + } - private void openDefaultSystemBrowser(URL url) { + private static void openDefaultSystemBrowserInWindows(URL url){ try { if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { Desktop.getDesktop().browse(url.toURI()); @@ -121,6 +160,41 @@ private void openDefaultSystemBrowser(URL url) { } } + private static void openDefaultSystemBrowserInMac(URL url){ + Runtime runtime = Runtime.getRuntime(); + try { + runtime.exec("open " + url); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static void openDefaultSystemBrowserInLinux(URL url){ + String sudoUser = System.getenv("SUDO_USER"); + if (sudoUser != null && !sudoUser.isEmpty()) { + throw new MsalClientException(LINUX_XDG_OPEN, LINUX_OPEN_AS_SUDO_NOT_SUPPORTED); + } + + boolean opened = false; + List openTools = getOpenToolsLinux(); + for (String openTool : openTools) { + String openToolPath = getExecutablePath(openTool); + if (openToolPath != null) { + Runtime runtime = Runtime.getRuntime(); + try { + runtime.exec(openTool + url); + } catch (IOException e) { + throw new RuntimeException(e); + } + opened = true; + break; + } + } + if (!opened) { + throw new MsalClientException(LINUX_XDG_OPEN, LINUX_OPEN_AS_SUDO_NOT_SUPPORTED); + } + } + private AuthorizationResult getAuthorizationResultFromHttpListener() { AuthorizationResult result = null; try { diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthenticationErrorCode.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthenticationErrorCode.java index 1ea0232e..87d4615a 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthenticationErrorCode.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthenticationErrorCode.java @@ -130,7 +130,7 @@ public class AuthenticationErrorCode { public final static String MSALRUNTIME_INTEROP_ERROR = "interop_package_error"; /** - * Indicates an error in the MSAL Java Brokers package + * Indicates an error related to the MSAL Java Brokers package */ public final static String MSALJAVA_BROKERS_ERROR = "brokers_package_error"; } diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthenticationResult.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthenticationResult.java index dcef23f5..1338467b 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthenticationResult.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthenticationResult.java @@ -90,4 +90,7 @@ private ITenantProfile getTenantProfile() { private final Date expiresOnDate = new Date(expiresOn * 1000); private final String scopes; + + @Getter(value = AccessLevel.PACKAGE) + private final Boolean isPopAuthorization; } diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/Authority.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/Authority.java index 0a15a355..5644db45 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/Authority.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/Authority.java @@ -20,6 +20,7 @@ abstract class Authority { private static final String ADFS_PATH_SEGMENT = "adfs"; private static final String B2C_PATH_SEGMENT = "tfp"; + private static final String B2C_HOST_SEGMENT = "b2clogin.com"; private final static String USER_REALM_ENDPOINT = "common/userrealm"; private final static String userRealmEndpointFormat = "https://%s/" + USER_REALM_ENDPOINT + "/%s?api-version=1.0"; @@ -53,19 +54,22 @@ private void setCommonAuthorityProperties() { this.host = canonicalAuthorityUrl.getAuthority().toLowerCase(); } - static Authority createAuthority(URL authorityUrl) { - validateAuthority(authorityUrl); - + static Authority createAuthority(URL authorityUrl) throws MalformedURLException{ + Authority createdAuthority; AuthorityType authorityType = detectAuthorityType(authorityUrl); if (authorityType == AuthorityType.AAD) { - return new AADAuthority(authorityUrl); + createdAuthority = new AADAuthority(authorityUrl); } else if (authorityType == AuthorityType.B2C) { - return new B2CAuthority(authorityUrl); + createdAuthority = new B2CAuthority(authorityUrl); } else if (authorityType == AuthorityType.ADFS) { - return new ADFSAuthority(authorityUrl); + createdAuthority = new ADFSAuthority(authorityUrl); + } else if(authorityType == AuthorityType.CIAM){ + createdAuthority = new CIAMAuthority(authorityUrl); } else { throw new IllegalArgumentException("Unsupported Authority Type"); } + validateAuthority(createdAuthority.canonicalAuthorityUrl()); + return createdAuthority; } static AuthorityType detectAuthorityType(URL authorityUrl) { @@ -75,17 +79,23 @@ static AuthorityType detectAuthorityType(URL authorityUrl) { final String path = authorityUrl.getPath().substring(1); if (StringHelper.isBlank(path)) { + if(isCiamAuthority(authorityUrl.getHost())){ + return AuthorityType.CIAM; + } throw new IllegalArgumentException( "authority Uri should have at least one segment in the path (i.e. https:////...)"); } + final String host = authorityUrl.getHost(); final String firstPath = path.substring(0, path.indexOf("/")); - if (isB2CAuthority(firstPath)) { + if (isB2CAuthority(host, firstPath)) { return AuthorityType.B2C; } else if (isAdfsAuthority(firstPath)) { return AuthorityType.ADFS; - } else { + } else if(isCiamAuthority(host)){ + return AuthorityType.CIAM; + } else{ return AuthorityType.AAD; } } @@ -131,7 +141,11 @@ static void validateAuthority(URL authorityUrl) { static String getTenant(URL authorityUrl, AuthorityType authorityType) { String[] segments = authorityUrl.getPath().substring(1).split("/"); if (authorityType == AuthorityType.B2C) { - return segments[1]; + if (segments.length < 3){ + return segments[0]; + } else { + return segments[1]; + } } return segments[0]; } @@ -144,8 +158,12 @@ private static boolean isAdfsAuthority(final String firstPath) { return firstPath.compareToIgnoreCase(ADFS_PATH_SEGMENT) == 0; } - private static boolean isB2CAuthority(final String firstPath) { - return firstPath.compareToIgnoreCase(B2C_PATH_SEGMENT) == 0; + private static boolean isB2CAuthority(final String host, final String firstPath) { + return host.contains(B2C_HOST_SEGMENT) || firstPath.compareToIgnoreCase(B2C_PATH_SEGMENT) == 0; + } + + private static boolean isCiamAuthority(final String host){ + return host.endsWith(CIAMAuthority.CIAM_HOST_SEGMENT); } String deviceCodeEndpoint() { diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthorityType.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthorityType.java index aa442d74..f686f2f2 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthorityType.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthorityType.java @@ -4,5 +4,5 @@ package com.microsoft.aad.msal4j; enum AuthorityType { - AAD, ADFS, B2C + AAD, ADFS, B2C, CIAM } 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 48045b1e..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; @@ -34,8 +36,15 @@ public class AuthorizationRequestUrlParameters { private String correlationId; private boolean instanceAware; + //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) { @@ -149,13 +158,32 @@ 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, Map> requestParameters) { URL authorizationRequestUrl; try { - String authorizationCodeEndpoint = authority.authorizationEndpoint(); + String authorizationCodeEndpoint; + if (prompt == Prompt.ADMIN_CONSENT) { + authorizationCodeEndpoint = ADMIN_CONSENT_ENDPOINT + .replace("{tenant}", authority.tenant); + } else { + authorizationCodeEndpoint = authority.authorizationEndpoint(); + } + String uriString = authorizationCodeEndpoint + "?" + URLUtils.serializeParameters(requestParameters); @@ -184,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); @@ -330,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/B2CAuthority.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/B2CAuthority.java index bc94f7bf..3d15c846 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/B2CAuthority.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/B2CAuthority.java @@ -26,27 +26,42 @@ class B2CAuthority extends Authority { } private void validatePathSegments(String[] segments) { - if (segments.length < 3) { + if (segments.length < 2) { throw new IllegalArgumentException( - "B2C 'authority' Uri should have at least 3 segments in the path " + - "(i.e. https:///tfp///...)"); + "Valid B2C 'authority' URLs should follow either of these formats: https://///... or https:///something///..."); } } private void setAuthorityProperties() { String[] segments = canonicalAuthorityUrl.getPath().substring(1).split("/"); + // In the early days of MSAL, the only way for the library to identify a B2C authority was whether or not the authority + // had three segments in the path, and the first segment was 'tfp'. Valid B2C authorities looked like: https:///tfp///... + // + // More recent changes to B2C should ensure that any new B2C authorities have 'b2clogin.com' in the host of the URL, + // so app developers shouldn't need to add 'tfp' and the first path segment should just be the tenant: https://.b2clogin.com///... + // + // However, legacy URLs using the old format must still be supported by these sorts of checks here and elsewhere, so for the near + // future at least we must consider both formats as valid until we're either sure all customers are swapped, + // or until we're comfortable with a potentially breaking change validatePathSegments(segments); - policy = segments[2]; - - final String b2cAuthorityFormat = "https://%s/%s/%s/%s/"; - this.authority = String.format( - b2cAuthorityFormat, - canonicalAuthorityUrl.getAuthority(), - segments[0], - segments[1], - segments[2]); + try { + policy = segments[2]; + this.authority = String.format( + "https://%s/%s/%s/%s/", + canonicalAuthorityUrl.getAuthority(), + segments[0], + segments[1], + segments[2]); + } catch (IndexOutOfBoundsException e){ + policy = segments[1]; + this.authority = String.format( + "https://%s/%s/%s/", + canonicalAuthorityUrl.getAuthority(), + segments[0], + segments[1]); + } this.authorizationEndpoint = String.format(B2C_AUTHORIZATION_ENDPOINT_FORMAT, host, tenant, policy); this.tokenEndpoint = String.format(B2C_TOKEN_ENDPOINT_FORMAT, host, tenant, policy); diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/CIAMAuthority.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/CIAMAuthority.java new file mode 100644 index 00000000..5d4795a6 --- /dev/null +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/CIAMAuthority.java @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.aad.msal4j; + +import java.net.MalformedURLException; +import java.net.URL; + +public class CIAMAuthority extends Authority{ + + public static final String CIAM_HOST_SEGMENT = ".ciamlogin.com"; + + static final String AUTHORIZATION_ENDPOINT = "oauth2/v2.0/authorize"; + static final String TOKEN_ENDPOINT = "oauth2/v2.0/token"; + static final String DEVICE_CODE_ENDPOINT = "oauth2/v2.0/devicecode"; + + private static final String CIAM_AUTHORITY_FORMAT = "https://%s/%s/"; + private static final String DEVICE_CODE_ENDPOINT_FORMAT = CIAM_AUTHORITY_FORMAT + DEVICE_CODE_ENDPOINT; + + private static final String CIAM_AUTHORIZATION_ENDPOINT_FORMAT = CIAM_AUTHORITY_FORMAT + AUTHORIZATION_ENDPOINT; + private static final String CIAM_TOKEN_ENDPOINT_FORMAT = CIAM_AUTHORITY_FORMAT + TOKEN_ENDPOINT; + + CIAMAuthority(URL authorityUrl) throws MalformedURLException { + super(transformAuthority(authorityUrl), AuthorityType.CIAM); + setAuthorityProperties(); + this.authority = String.format(CIAM_AUTHORITY_FORMAT,host,tenant); + } + + /** This method takes a CIAM authority string of format "tenant.ciamlogin.com" or "https://tenant.ciamlogin.com" + and converts it into a full authority url with a path segment of format "/tenant.onmicrosoft.com" + * @param originalAuthority authority to be transformed + * @return full CIAM authority with path + */ + protected static URL transformAuthority(URL originalAuthority) throws MalformedURLException { + String host = originalAuthority.getHost() + originalAuthority.getPath(); + String transformedAuthority = originalAuthority.toString(); + if(originalAuthority.getPath().equals("/")){ + int ciamHostIndex = host.indexOf(CIAMAuthority.CIAM_HOST_SEGMENT); + String tenant = host.substring(0 , ciamHostIndex); + transformedAuthority = originalAuthority + tenant + ".onmicrosoft.com/"; + } + return new URL(transformedAuthority); + } + + private void setAuthorityProperties() { + this.authorizationEndpoint = String.format(CIAM_AUTHORIZATION_ENDPOINT_FORMAT, host, tenant); + this.tokenEndpoint = String.format(CIAM_TOKEN_ENDPOINT_FORMAT, host, tenant); + this.deviceCodeEndpoint = String.format(DEVICE_CODE_ENDPOINT_FORMAT, host, tenant); + this.selfSignedJwtAudience = this.tokenEndpoint; + } +} 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/HttpHelper.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/HttpHelper.java index 2c088fd5..cc6b4e7d 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/HttpHelper.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/HttpHelper.java @@ -22,6 +22,9 @@ class HttpHelper { public static final int RETRY_DELAY_MS = 1000; public static final int HTTP_STATUS_200 = 200; + + public static final int HTTP_STATUS_400 = 400; + public static final int HTTP_STATUS_429 = 429; public static final int HTTP_STATUS_500 = 500; diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/HttpMethod.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/HttpMethod.java index 64af4605..9497ec3c 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/HttpMethod.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/HttpMethod.java @@ -4,17 +4,53 @@ package com.microsoft.aad.msal4j; /** - * Http request method. + * An enumerator representing common HTTP request methods. */ public enum HttpMethod { + /** + * The HTTP CONNECT method. + */ + CONNECT("CONNECT"), + + /** + * The HTTP DELETE method. + */ + DELETE("DELETE"), + /** * The HTTP GET method. */ - GET, + GET("GET"), + + /** + * The HTTP HEAD method. + */ + HEAD("HEAD"), + + /** + * The HTTP OPTIONS method. + */ + OPTIONS("OPTIONS"), /** * The HTTP POST method. */ - POST + POST("POST"), + + /** + * The HTTP PUT method. + */ + PUT("PUT"), + + /** + * The HTTP TRACE method. + */ + TRACE("TRACE"); + + public final String methodName; + + HttpMethod(String methodName) { + this.methodName = methodName; + } } 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/IBroker.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java index 69906319..ab3f0ce0 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java @@ -10,15 +10,15 @@ /** * Used to define the basic set of methods that all Brokers must implement - * + *

* All methods are marked as default so they can be referenced by MSAL Java without an implementation, - * and most will simply throw an exception if not overridden by an IBroker implementation + * and most will simply throw an exception if not overridden by an IBroker implementation */ public interface IBroker { /** * Acquire a token silently, i.e. without direct user interaction - * + *

* This may be accomplished by returning tokens from a token cache, using cached refresh tokens to get new tokens, * or via any authentication flow where a user is not prompted to enter credentials */ @@ -44,27 +44,31 @@ default void removeAccount(PublicClientApplication application, IAccount account throw new MsalClientException("Broker implementation missing", AuthenticationErrorCode.MISSING_BROKER); } + /** + * Returns whether a broker is available and ready to use on this machine, allowing the use of the methods + * in this interface and other broker-only features in MSAL Java + */ default boolean isBrokerAvailable() { throw new MsalClientException("Broker implementation missing", AuthenticationErrorCode.MISSING_BROKER); } /** * MSAL Java's AuthenticationResult requires several package-private classes that a broker implementation can't access, - * so this helper method can be used to create AuthenticationResults from within the MSAL Java package + * so this helper method can be used to create AuthenticationResults from within the MSAL Java package */ default IAuthenticationResult parseBrokerAuthResult(String authority, String idToken, String accessToken, - String accountId, String clientInfo, - long accessTokenExpirationTime) { + String accountId, String clientInfo, + long accessTokenExpirationTime, + boolean isPopAuthorization) { - AuthenticationResult.AuthenticationResultBuilder builder = AuthenticationResult.builder(); + AuthenticationResult.AuthenticationResultBuilder builder = AuthenticationResult.builder(); try { if (idToken != null) { builder.idToken(idToken); - if (accountId!= null) { + if (accountId != null) { String idTokenJson = JWTParser.parse(idToken).getParsedParts()[1].decodeToString(); - //TODO: need to figure out if 'policy' field is relevant for brokers builder.accountCacheEntity(AccountCacheEntity.create(clientInfo, Authority.createAuthority(new URL(authority)), JsonHelper.convertJsonToObject(idTokenJson, IdToken.class), null)); @@ -74,6 +78,9 @@ default IAuthenticationResult parseBrokerAuthResult(String authority, String idT builder.accessToken(accessToken); builder.expiresOn(accessTokenExpirationTime); } + + builder.isPopAuthorization(isPopAuthorization); + } catch (Exception e) { throw new MsalClientException(String.format("Exception when converting broker result to MSAL Java AuthenticationResult: %s", e.getMessage()), AuthenticationErrorCode.MSALJAVA_BROKERS_ERROR); } 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..36149e2e 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 @@ -10,6 +10,7 @@ import java.net.InetAddress; import java.net.URI; import java.net.URL; +import java.net.UnknownHostException; import java.security.SecureRandom; import java.util.Base64; import java.util.UUID; @@ -55,24 +56,31 @@ URL authorizationUrl() { } private void validateRedirectUrl(URI redirectUri) { + String host = redirectUri.getHost(); + String scheme = redirectUri.getScheme(); + InetAddress address; + + //Validate URI scheme. Only http is valid, as determined by the HttpListener created in AcquireTokenByInteractiveFlowSupplier.startHttpListener() + if (scheme == null || !scheme.equals("http")) { + throw new MsalClientException(String.format( + "Only http://localhost or http://localhost:port is supported for the redirect URI of an interactive request using a browser, but \"%s\" was found. For more information about redirect URI formats, see https://aka.ms/msal4j-interactive-request", scheme), + AuthenticationErrorCode.LOOPBACK_REDIRECT_URI); + } + + //Ensure that the given redirect URI has a known address try { - if (!InetAddress.getByName(redirectUri.getHost()).isLoopbackAddress()) { - throw new MsalClientException(String.format( - "Only loopback redirect uri is supported, but %s was found " + - "Configure http://localhost or http://localhost:port both during app registration" + - "and when you create the create the InteractiveRequestParameters object", redirectUri.getHost()), - AuthenticationErrorCode.LOOPBACK_REDIRECT_URI); - } - - if (!redirectUri.getScheme().equals("http")) { - throw new MsalClientException(String.format( - "Only http uri scheme is supported but %s was found. Configure http://localhost" + - "or http://localhost:port both during app registration and when you create" + - " the create the InteractiveRequestParameters object", redirectUri.toString()), - AuthenticationErrorCode.LOOPBACK_REDIRECT_URI); - } - } catch (Exception exception) { - throw new MsalClientException(exception); + address = InetAddress.getByName(host); + } catch (UnknownHostException e) { + throw new MsalClientException(String.format( + "Unknown host exception for host \"%s\". For more information about redirect URI formats, see https://aka.ms/msal4j-interactive-request", host), + AuthenticationErrorCode.LOOPBACK_REDIRECT_URI); + } + + //Ensure that the redirect URI is considered a loopback address + if (address == null || !address.isLoopbackAddress()) { + throw new MsalClientException( + "Only loopback redirect URI is supported for interactive requests. For more information about redirect URI formats, see https://aka.ms/msal4j-interactive-request", + AuthenticationErrorCode.LOOPBACK_REDIRECT_URI); } } @@ -87,11 +95,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 a41d1832..567cb280 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 */ @@ -112,6 +117,8 @@ public class InteractiveRequestParameters implements IAcquireTokenParameters { */ private long windowHandle; + private PopParameters proofOfPossession; + private static InteractiveRequestParametersBuilder builder() { return new InteractiveRequestParametersBuilder(); } @@ -123,4 +130,23 @@ public static InteractiveRequestParametersBuilder builder(URI redirectUri) { return builder() .redirectUri(redirectUri); } + + //This Builder class is used to override Lombok's default setter behavior for any fields defined in it + public static class InteractiveRequestParametersBuilder { + + /** + * Sets the PopParameters for this request, allowing the request to retrieve proof-of-possession tokens rather than bearer tokens + * + * For more information, see {@link PopParameters} and https://aka.ms/msal4j-pop + * + * @param httpMethod a valid HTTP method, such as "GET" or "POST" + * @param uri the URI on the downstream protected API which the application is trying to access, e.g. https://graph.microsoft.com/beta/me/profile + * @param nonce a string obtained by calling the resource (e.g. Microsoft Graph) un-authenticated and parsing the WWW-Authenticate header associated with pop authentication scheme and extracting the nonce parameter, or, on subsequent calls, by parsing the Autheticate-Info header and extracting the nextnonce parameter. + */ + public InteractiveRequestParametersBuilder proofOfPossession(HttpMethod httpMethod, URI uri, String nonce) { + this.proofOfPossession = new PopParameters(httpMethod, uri, nonce); + + return this; + } + } } diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/OSHelper.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/OSHelper.java new file mode 100644 index 00000000..b1609f5a --- /dev/null +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/OSHelper.java @@ -0,0 +1,39 @@ +package com.microsoft.aad.msal4j; + +public class OSHelper { + enum OSType{ + MAC, + WINDOWS, + LINUX + } + + private static final String OS; + private static OSType osType; + + static{ + OS = System.getProperty("os.name").toLowerCase(); + if(OS.contains("windows")){ + osType = OSType.WINDOWS; + }else if (OS.contains("mac")){ + osType = OSType.MAC; + }else if (OS.contains("nux") || OS.contains("nix")){ + osType = OSType.LINUX; + } + } + + public static String getOs(){ + return OS; + } + + public static boolean isMac(){ + return OSType.MAC.equals(osType); + } + + public static boolean isWindows(){ + return OSType.WINDOWS.equals(osType); + } + + public static boolean isLinux(){ + return OSType.LINUX.equals(osType); + } +} 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/PopParameters.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/PopParameters.java new file mode 100644 index 00000000..d72ab5a5 --- /dev/null +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/PopParameters.java @@ -0,0 +1,44 @@ +package com.microsoft.aad.msal4j; + +import java.net.URI; + +/** + * Contains parameters used to request a Proof of Possession (PoP) token in supported flows + */ +public class PopParameters { + + HttpMethod httpMethod; + URI uri; + String nonce; + + public HttpMethod getHttpMethod() { + return httpMethod; + } + + public URI getUri() { + return uri; + } + + public String getNonce() { + return nonce; + } + + PopParameters(HttpMethod httpMethod, URI uri, String nonce) { + validatePopAuthScheme(httpMethod, uri); + + this.httpMethod = httpMethod; + this.uri = uri; + this.nonce = nonce; + } + + /** + * Performs any minimum validation to confirm this auth scheme could be valid for a POP request + */ + void validatePopAuthScheme(HttpMethod httpMethod, URI uri) { + //At a minimum HTTP method and host must be non-null + if (httpMethod == null || uri == null || uri.getHost() == null) { + throw new MsalClientException( + "HTTP method and URI host must be non-null", AuthenticationErrorCode.MSALJAVA_BROKERS_ERROR); + } + } +} diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/Prompt.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/Prompt.java index 1c5efd7b..7670f3da 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/Prompt.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/Prompt.java @@ -25,14 +25,6 @@ public enum Prompt { */ CONSENT("consent"), - /** - * An administrator should be prompted to consent on behalf of all users in their organization. - *

- * Deprecated, instead use Prompt.ADMIN_CONSENT - */ - @Deprecated - ADMING_CONSENT("admin_consent"), - /** * An administrator should be prompted to consent on behalf of all users in their organization. */ diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/PublicClientApplication.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/PublicClientApplication.java index 11b19604..e03b9c17 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/PublicClientApplication.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/PublicClientApplication.java @@ -40,7 +40,7 @@ public CompletableFuture acquireToken(UserNamePasswordPar CompletableFuture future; - if (brokerEnabled) { + if (validateBrokerUsage(parameters)) { future = broker.acquireToken(this, parameters); } else { UserNamePasswordRequest userNamePasswordRequest = @@ -77,10 +77,9 @@ public CompletableFuture acquireToken(IntegratedWindowsAu @Override public CompletableFuture acquireToken(DeviceCodeFlowParameters parameters) { - if (!(AuthorityType.AAD.equals(authenticationAuthority.authorityType()) || - AuthorityType.ADFS.equals(authenticationAuthority.authorityType()))) { + if (AuthorityType.B2C.equals(authenticationAuthority.authorityType())) { throw new IllegalArgumentException( - "Invalid authority type. Device Flow is only supported by AAD and ADFS authorities"); + "Invalid authority type. Device Flow is not supported by B2C authority."); } validateNotNull("parameters", parameters); @@ -125,7 +124,7 @@ public CompletableFuture acquireToken(InteractiveRequestP CompletableFuture future; - if (brokerEnabled) { + if (validateBrokerUsage(parameters)) { future = broker.acquireToken(this, parameters); } else { future = executeRequest(interactiveRequest); @@ -140,7 +139,7 @@ public CompletableFuture acquireToken(InteractiveRequestP public CompletableFuture acquireTokenSilently(SilentParameters parameters) throws MalformedURLException { CompletableFuture future; - if (brokerEnabled) { + if (validateBrokerUsage(parameters)) { future = broker.acquireToken(this, parameters); } else { future = super.acquireTokenSilently(parameters); @@ -216,4 +215,61 @@ protected Builder self() { return this; } } + + /** + * Used to determine whether to call into an IBroker instance instead of standard MSAL Java's normal interactive flow, + * and may throw exceptions or log messages if broker-only parameters are used when a broker is not enabled/available + */ + private boolean validateBrokerUsage(InteractiveRequestParameters parameters) { + + //Check if broker-only parameters are being used when a broker is not enabled. If they are, either throw an + // exception saying a broker is required, or provide a clear log message saying the parameter will be ignored + if (!brokerEnabled) { + if (parameters.proofOfPossession() != null) { + throw new MsalClientException( + "InteractiveRequestParameters.proofOfPossession should not be used when broker is not available, see https://aka.ms/msal4j-pop for more information", + AuthenticationErrorCode.MSALJAVA_BROKERS_ERROR ); + } + } + + return brokerEnabled; + } + + /** + * Used to determine whether to call into an IBroker instance instead of standard MSAL Java's normal username/password flow, + * and may throw exceptions or log messages if broker-only parameters are used when a broker is not enabled/available + */ + private boolean validateBrokerUsage(UserNamePasswordParameters parameters) { + + //Check if broker-only parameters are being used when a broker is not enabled. If they are, either throw an + // exception saying a broker is required, or provide a clear log message saying the parameter will be ignored + if (!brokerEnabled) { + if (parameters.proofOfPossession() != null) { + throw new MsalClientException( + "UserNamePasswordParameters.proofOfPossession should not be used when broker is not available, see https://aka.ms/msal4j-pop for more information", + AuthenticationErrorCode.MSALJAVA_BROKERS_ERROR ); + } + } + + return brokerEnabled; + } + + /** + * Used to determine whether to call into an IBroker instance instead of standard MSAL Java's normal silent flow, + * and may throw exceptions or log messages if broker-only parameters are used when a broker is not enabled/available + */ + private boolean validateBrokerUsage(SilentParameters parameters) { + + //Check if broker-only parameters are being used when a broker is not enabled. If they are, either throw an + // exception saying a broker is required, or provide a clear log message saying the parameter will be ignored + if (!brokerEnabled) { + if (parameters.proofOfPossession() != null) { + throw new MsalClientException( + "UserNamePasswordParameters.proofOfPossession should not be used when broker is not available, see https://aka.ms/msal4j-pop for more information", + AuthenticationErrorCode.MSALJAVA_BROKERS_ERROR ); + } + } + + return brokerEnabled; + } } \ No newline at end of file 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..c4c4e9fa 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 @@ -6,6 +6,7 @@ import lombok.*; import lombok.experimental.Accessors; +import java.net.URI; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -54,11 +55,18 @@ 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 */ private String tenant; + private PopParameters proofOfPossession; + private static SilentParametersBuilder builder() { return new SilentParametersBuilder(); @@ -109,4 +117,23 @@ private static Set removeEmptyScope(Set scopes){ } return updatedScopes; } + + //This Builder class is used to override Lombok's default setter behavior for any fields defined in it + public static class SilentParametersBuilder { + + /** + * Sets the PopParameters for this request, allowing the request to retrieve proof-of-possession tokens rather than bearer tokens + * + * For more information, see {@link PopParameters} and https://aka.ms/msal4j-pop + * + * @param httpMethod a valid HTTP method, such as "GET" or "POST" + * @param uri URI to associate with the token + * @param nonce optional nonce value for the token, can be empty or null + */ + public SilentParametersBuilder proofOfPossession(HttpMethod httpMethod, URI uri, String nonce) { + this.proofOfPossession = new PopParameters(httpMethod, uri, nonce); + + return this; + } + } } 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..f9df4454 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 @@ -6,6 +6,7 @@ import lombok.*; import lombok.experimental.Accessors; +import java.net.URI; import java.util.Map; import java.util.Set; @@ -53,11 +54,18 @@ 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 */ private String tenant; + private PopParameters proofOfPossession; + public char[] password() { return password.clone(); } @@ -93,5 +101,20 @@ public UserNamePasswordParametersBuilder password(char[] password) { this.password = password.clone(); return this; } + + /** + * Sets the PopParameters for this request, allowing the request to retrieve proof-of-possession tokens rather than bearer tokens + * + * For more information, see {@link PopParameters} and https://aka.ms/msal4j-pop + * + * @param httpMethod a valid HTTP method, such as "GET" or "POST" + * @param uri URI to associate with the token + * @param nonce optional nonce value for the token, can be empty or null + */ + public UserNamePasswordParametersBuilder proofOfPossession(HttpMethod httpMethod, URI uri, String nonce) { + this.proofOfPossession = new PopParameters(httpMethod, uri, nonce); + + return this; + } } } diff --git a/msal4j-sdk/src/samples/msal-b2c-web-sample/pom.xml b/msal4j-sdk/src/samples/msal-b2c-web-sample/pom.xml index 684a1baf..4bbc97be 100644 --- a/msal4j-sdk/src/samples/msal-b2c-web-sample/pom.xml +++ b/msal4j-sdk/src/samples/msal-b2c-web-sample/pom.xml @@ -33,7 +33,7 @@ org.json json - 20180130 + 20230227 diff --git a/msal4j-sdk/src/samples/msal-obo-sample/pom.xml b/msal4j-sdk/src/samples/msal-obo-sample/pom.xml index 84ed61d3..381e3e89 100644 --- a/msal4j-sdk/src/samples/msal-obo-sample/pom.xml +++ b/msal4j-sdk/src/samples/msal-obo-sample/pom.xml @@ -33,7 +33,7 @@ org.json json - 20180130 + 20230227 org.projectlombok diff --git a/msal4j-sdk/src/samples/msal-web-sample/pom.xml b/msal4j-sdk/src/samples/msal-web-sample/pom.xml index 97498900..aa3e1c1e 100644 --- a/msal4j-sdk/src/samples/msal-web-sample/pom.xml +++ b/msal4j-sdk/src/samples/msal-web-sample/pom.xml @@ -33,7 +33,7 @@ org.json json - 20180130 + 20230227 org.apache.commons diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AuthorityTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AuthorityTest.java index 0db1b159..cd0a8bf4 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AuthorityTest.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AuthorityTest.java @@ -34,11 +34,37 @@ public void testDetectAuthorityType_B2C() throws Exception { Assert.assertEquals(Authority.detectAuthorityType(url), AuthorityType.B2C); } + @DataProvider(name = "ciamAuthorities") + public static Object[][] createCiamAuthorityData() throws MalformedURLException { + return new Object[][]{{new URL("https://msidlabciam1.ciamlogin.com/")}, + {new URL("https://msidlabciam1.ciamlogin.com/d57fb3d4-4b5a-4144-9328-9c1f7d58179d/")}, + {new URL("https://msidlabciam1.ciamlogin.com/msidlabciam1.onmicrosoft.com/")}, + {new URL("https://msidlabciam1.ciamlogin.com/aDomain/")}}; + } + + @Test(dataProvider = "ciamAuthorities") + public void testDetectAuthorityType_CIAM(URL authority) throws Exception { + Assert.assertEquals(Authority.detectAuthorityType(authority), AuthorityType.CIAM); + } + + @DataProvider(name = "validCiamAuthoritiesAndTransformedAuthority") + public static Object[][] createCiamAndTransformedAuthorityData() throws MalformedURLException { + return new Object[][]{{new URL("https://msidlabciam1.ciamlogin.com/"),new URL("https://msidlabciam1.ciamlogin.com/msidlabciam1.onmicrosoft.com/")}, + {new URL("https://msidlabciam1.ciamlogin.com/d57fb3d4-4b5a-4144-9328-9c1f7d58179d"),new URL("https://msidlabciam1.ciamlogin.com/d57fb3d4-4b5a-4144-9328-9c1f7d58179d")}, + {new URL("https://msidlabciam1.ciamlogin.com/msidlabciam1.onmicrosoft.com"),new URL("https://msidlabciam1.ciamlogin.com/msidlabciam1.onmicrosoft.com")}, + {new URL("https://msidlabciam1.ciamlogin.com/aDomain"),new URL("https://msidlabciam1.ciamlogin.com/aDomain")}}; + } + + @Test(dataProvider = "validCiamAuthoritiesAndTransformedAuthority") + public void testCiamAuthorityTransformation(URL authority, URL transformedAuthority) throws Exception{ + Assert.assertEquals(CIAMAuthority.transformAuthority(authority), transformedAuthority); + } + @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = - "B2C 'authority' Uri should have at least 3 segments in the path \\(i.e. https:///tfp///...\\)") + "Valid B2C 'authority' URLs should follow either of these formats.*") public void testB2CAuthorityConstructor_NotEnoughSegments() throws MalformedURLException { - new B2CAuthority(new URL("https://something.com/tfp/somethingelse/")); + new B2CAuthority(new URL("https://something.com/somethingelse/")); } @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "authority should use the 'https' scheme") 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<>(); diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/DeviceCodeFlowTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/DeviceCodeFlowTest.java index 80f9955a..1ac60c59 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/DeviceCodeFlowTest.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/DeviceCodeFlowTest.java @@ -145,7 +145,7 @@ public void deviceCodeFlowTest() throws Exception { } @Test(expectedExceptions = IllegalArgumentException.class, - expectedExceptionsMessageRegExp = "Invalid authority type. Device Flow is only supported by AAD and ADFS authorities") + expectedExceptionsMessageRegExp = "Invalid authority type. Device Flow is not supported by B2C authority.") public void executeAcquireDeviceCode_B2CAuthorityUsed_IllegalArgumentExceptionThrown() throws Exception { From 8c647a8b61e933b3d80e4732f0957eada51f4128 Mon Sep 17 00:00:00 2001 From: Avery-Dunn Date: Tue, 20 Jun 2023 12:20:12 -0700 Subject: [PATCH 23/27] Fix silent issue --- msal4j-brokers/pom.xml | 45 ++++++++++++++++--- .../aad/msal4jbrokers/MsalRuntimeBroker.java | 4 +- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/msal4j-brokers/pom.xml b/msal4j-brokers/pom.xml index ba9c681c..affcd369 100644 --- a/msal4j-brokers/pom.xml +++ b/msal4j-brokers/pom.xml @@ -5,12 +5,11 @@ 4.0.0 com.microsoft.azure msal4j-brokers - 1.0.0-beta + 1.0.2-beta jar msal4j-brokers - Microsoft Authentication Library for Java - Brokers helps you integrate with the broker - on windows machine to secure Access tokens and refresh tokens. + Microsoft Authentication Library for Java - Brokers is a companion package for MSAL Java that allows easy integration with authentication brokers https://github.com/AzureAD/microsoft-authentication-library-for-java @@ -35,12 +34,12 @@ com.microsoft.azure msal4j - 1.14.0-beta + 1.14.1-beta com.microsoft.azure javamsalruntime - 0.13.4 + 0.13.8 org.projectlombok @@ -65,6 +64,42 @@ 1.2.3 test + + commons-io + commons-io + 2.11.0 + test + + + org.seleniumhq.selenium + selenium-api + 3.14.0 + test + + + org.seleniumhq.selenium + selenium-chrome-driver + 3.14.0 + test + + + org.seleniumhq.selenium + selenium-support + 3.14.0 + test + + + com.azure + azure-core + 1.22.0 + test + + + com.azure + azure-security-keyvault-secrets + 4.3.5 + test + diff --git a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java index 68000997..f90e2e09 100644 --- a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java +++ b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java @@ -46,7 +46,7 @@ public CompletableFuture acquireToken(PublicClientApplica //If request has an account ID, MSALRuntime likely has data cached for that account that we can retrieve if (parameters.account() != null) { try { - accountResult = ((ReadAccountResult) interop.readAccountById(parameters.account().homeAccountId(), application.correlationId()).get()).getAccount(); + accountResult = ((ReadAccountResult) interop.readAccountById(parameters.account().homeAccountId().split("\\.")[0], application.correlationId()).get()).getAccount(); } catch (InterruptedException | ExecutionException ex) { throw new MsalClientException(String.format("MSALRuntime async operation interrupted when waiting for result: %s", ex.getMessage()), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); } @@ -142,7 +142,7 @@ public CompletableFuture acquireToken(PublicClientApplica @Override public void removeAccount(PublicClientApplication application, IAccount msalJavaAccount) { try { - Account msalRuntimeAccount = ((ReadAccountResult) interop.readAccountById(msalJavaAccount.homeAccountId(), application.correlationId()).get()).getAccount(); + Account msalRuntimeAccount = ((ReadAccountResult) interop.readAccountById(msalJavaAccount.homeAccountId().split("\\.")[0], application.correlationId()).get()).getAccount(); if (msalRuntimeAccount != null) { interop.signOutSilently(application.clientId(), application.correlationId(), msalRuntimeAccount); From 6fc2c6c197acb1d5336971edb09fcd09f21f91ff Mon Sep 17 00:00:00 2001 From: Avery-Dunn Date: Sun, 8 Oct 2023 16:37:51 -0700 Subject: [PATCH 24/27] Ensure correlation ID is never null --- .../aad/msal4jbrokers/MsalRuntimeBroker.java | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java index 786f7245..916a4a14 100644 --- a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java +++ b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java @@ -13,6 +13,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; @@ -34,12 +35,13 @@ public class MsalRuntimeBroker implements IBroker { @Override public CompletableFuture acquireToken(PublicClientApplication application, SilentParameters parameters) { + String correlationID = application.correlationId() == null ? generateCorrelationID() : application.correlationId(); Account accountResult = null; //If request has an account ID, MSALRuntime likely has data cached for that account that we can retrieve if (parameters.account() != null) { try { - accountResult = ((ReadAccountResult) interop.readAccountById(parameters.account().homeAccountId().split("\\.")[0], application.correlationId()).get()).getAccount(); + accountResult = ((ReadAccountResult) interop.readAccountById(parameters.account().homeAccountId().split("\\.")[0], correlationID).get()).getAccount(); } catch (InterruptedException | ExecutionException ex) { throw new MsalClientException(String.format("MSALRuntime async operation interrupted when waiting for result: %s", ex.getMessage()), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); } @@ -62,8 +64,8 @@ public CompletableFuture acquireToken(PublicClientApplica AuthParameters authParameters = authParamsBuilder.build(); if (accountResult == null) { - return interop.signInSilently(authParameters, application.correlationId()) - .thenCompose(acctResult -> interop.acquireTokenSilently(authParameters, application.correlationId(), ((AuthResult) acctResult).getAccount())) + return interop.signInSilently(authParameters, correlationID) + .thenCompose(acctResult -> interop.acquireTokenSilently(authParameters, correlationID, ((AuthResult) acctResult).getAccount())) .thenApply(authResult -> parseBrokerAuthResult( application.authority(), ((AuthResult) authResult).getIdToken(), @@ -73,7 +75,7 @@ public CompletableFuture acquireToken(PublicClientApplica ((AuthResult) authResult).getAccessTokenExpirationTime(), ((AuthResult) authResult).isPopAuthorization())); } else { - return interop.acquireTokenSilently(authParameters, application.correlationId(), accountResult) + return interop.acquireTokenSilently(authParameters, correlationID, accountResult) .thenApply(authResult -> parseBrokerAuthResult(application.authority(), ((AuthResult) authResult).getIdToken(), ((AuthResult) authResult).getAccessToken(), @@ -89,6 +91,8 @@ public CompletableFuture acquireToken(PublicClientApplica @Override public CompletableFuture acquireToken(PublicClientApplication application, InteractiveRequestParameters parameters) { + String correlationID = application.correlationId() == null ? generateCorrelationID() : application.correlationId(); + try { AuthParameters.AuthParametersBuilder authParamsBuilder = new AuthParameters. AuthParametersBuilder(application.clientId(), @@ -106,8 +110,8 @@ public CompletableFuture acquireToken(PublicClientApplica AuthParameters authParameters = authParamsBuilder.build(); - return interop.signInInteractively(parameters.windowHandle(), authParameters, application.correlationId(), parameters.loginHint()) - .thenCompose(acctResult -> interop.acquireTokenInteractively(parameters.windowHandle(), authParameters, application.correlationId(), ((AuthResult) acctResult).getAccount())) + return interop.signInInteractively(parameters.windowHandle(), authParameters, correlationID, parameters.loginHint()) + .thenCompose(acctResult -> interop.acquireTokenInteractively(parameters.windowHandle(), authParameters, correlationID, ((AuthResult) acctResult).getAccount())) .thenApply(authResult -> parseBrokerAuthResult( application.authority(), ((AuthResult) authResult).getIdToken(), @@ -127,6 +131,8 @@ public CompletableFuture acquireToken(PublicClientApplica @Deprecated @Override public CompletableFuture acquireToken(PublicClientApplication application, UserNamePasswordParameters parameters) { + String correlationID = application.correlationId() == null ? generateCorrelationID() : application.correlationId(); + try { AuthParameters.AuthParametersBuilder authParamsBuilder = new AuthParameters. AuthParametersBuilder(application.clientId(), @@ -143,8 +149,8 @@ public CompletableFuture acquireToken(PublicClientApplica AuthParameters authParameters = authParamsBuilder.build(); - return interop.signInSilently(authParameters, application.correlationId()) - .thenCompose(acctResult -> interop.acquireTokenSilently(authParameters, application.correlationId(), ((AuthResult) acctResult).getAccount())) + return interop.signInSilently(authParameters, correlationID) + .thenCompose(acctResult -> interop.acquireTokenSilently(authParameters, correlationID, ((AuthResult) acctResult).getAccount())) .thenApply(authResult -> parseBrokerAuthResult( application.authority(), ((AuthResult) authResult).getIdToken(), @@ -160,11 +166,13 @@ public CompletableFuture acquireToken(PublicClientApplica @Override public void removeAccount(PublicClientApplication application, IAccount msalJavaAccount) { + String correlationID = application.correlationId() == null ? generateCorrelationID() : application.correlationId(); + try { - Account msalRuntimeAccount = ((ReadAccountResult) interop.readAccountById(msalJavaAccount.homeAccountId().split("\\.")[0], application.correlationId()).get()).getAccount(); + Account msalRuntimeAccount = ((ReadAccountResult) interop.readAccountById(msalJavaAccount.homeAccountId().split("\\.")[0], correlationID).get()).getAccount(); if (msalRuntimeAccount != null) { - interop.signOutSilently(application.clientId(), application.correlationId(), msalRuntimeAccount); + interop.signOutSilently(application.clientId(), correlationID, msalRuntimeAccount); } } catch (MsalInteropException interopException) { throw new MsalClientException(interopException.getErrorMessage(), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); @@ -233,4 +241,9 @@ public void enableBrokerPIILogging(boolean enablePII) { throw new MsalClientException(String.format("Error occurred when calling MSALRuntime PII logging API: %s", ex.getMessage()), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); } } + + //Generates a random correlation ID, used when a correlation ID was not set at the application level + private String generateCorrelationID() { + return UUID.randomUUID().toString(); + } } From d627b7a6eb5970cfa112dc1498fced91e006a282 Mon Sep 17 00:00:00 2001 From: Avery-Dunn <62066438+Avery-Dunn@users.noreply.github.com> Date: Fri, 20 Oct 2023 16:45:44 -0700 Subject: [PATCH 25/27] Broker fixes and feedback (#733) * Delete codeql.yml * Test framework update (#672) * Initial working tests * Remove CIAM extra query parameter * Fix failing tests * Remove duplicate unit tests * Remove duplicate unit tests * Update tests with mocking to use Mockito * Remove testng and powermock, add junit and mockito * Remove AbstractMsalTests and PowerMockTestCase * Fix mistaken null check * Properly scope dependency * Update CIAM tests (#673) * Bump guava from 31.1-jre to 32.0.0-jre in /msal4j-sdk (#671) Bumps [guava](https://github.com/google/guava) from 31.1-jre to 32.0.0-jre. - [Release notes](https://github.com/google/guava/releases) - [Commits](https://github.com/google/guava/commits) --- updated-dependencies: - dependency-name: com.google.guava:guava dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Avery-Dunn * Delete contributing.md (#667) Co-authored-by: Avery-Dunn * Create Contributing.md (#668) Co-authored-by: Avery-Dunn * Version changes for 1.13.9 (#674) * Add space between command and arguments when executing linux command to open browser. Refs #682 (#683) Co-authored-by: Ric Emery * Assorted fixes (#684) * Remove default timeouts and improve exception messages * Fix NPE for on-prem ADFS scenario * Log MSAL message but re-throw exception * Update vulnerable test dependency * Issue-679: Fix for Account Cache; .contains() was not possible and you had to iterate through all elements as workaround. (#681) * Version changes for 1.13.10 (#685) * Move changelog * Move changelog to root * Update issue templates (#707) * Re-add lombok source line (#705) * Version changes for release 1.13.11 (#714) * Update bug report * Delete .github/ISSUE_TEMPLATE/bug_report.md * Update bug_report.yaml * Create FeatureRequest.yaml * Update FeatureRequest.yaml * Set default throttling time to 5 sec (#721) Co-authored-by: Kulyakhtin, Alexander (Ext) * Ensure correlation ID is never null * Rename MsalRuntimeBroker and add builder pattern for better API consistency --------- Signed-off-by: dependabot[bot] Co-authored-by: Bogdan Gavril Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ric Emery Co-authored-by: Ric Emery Co-authored-by: Maximilian Pfeffer Co-authored-by: akulyakhtin Co-authored-by: Kulyakhtin, Alexander (Ext) --- .github/ISSUE_TEMPLATE/FeatureRequest.yaml | 48 ++ .github/ISSUE_TEMPLATE/bug_report.yaml | 115 +++++ .github/workflows/codeql.yml | 46 -- Contributing.md | 18 + README.md | 6 +- msal4j-sdk/changelog.txt => changelog.txt | 16 + .../{MsalRuntimeBroker.java => Broker.java} | 61 ++- .../test/java/test/ProofOfPossessionTest.java | 12 +- msal4j-sdk/README.md | 6 +- msal4j-sdk/bnd.bnd | 2 +- msal4j-sdk/contributing.md | 122 ----- msal4j-sdk/pom.xml | 41 +- .../AcquireTokenInteractiveIT.java | 149 ++++--- .../AcquireTokenSilentIT.java | 157 ++++--- .../AuthorizationCodeIT.java | 113 ++--- .../AzureEnvironmentIT.java | 28 +- .../CachePersistenceIT.java | 58 +-- .../ClientCredentialsIT.java | 96 ++-- .../ConfidentialClientApplicationUnitT.java | 421 ------------------ .../DeviceCodeIT.java | 74 ++- .../EnvironmentsProvider.java | 2 - .../HttpClientIT.java | 56 ++- .../InstanceDiscoveryTest.java | 235 ---------- .../InvalidAuthorityIT.java | 18 +- .../OAuthRequestValidationUnitT.java | 103 ----- .../OnBehalfOfIT.java | 59 +-- .../RefreshTokenIT.java | 40 +- .../SeleniumTest.java | 6 - .../TestConstants.java | 1 + .../TokenCacheIT.java | 69 ++- .../UsernamePasswordIT.java | 93 ++-- .../infrastructure/UserInformationFields.java | 6 +- .../java/labapi/LabConstants.java | 2 +- .../msal4j/AadInstanceDiscoveryProvider.java | 4 +- .../com/microsoft/aad/msal4j/Account.java | 2 + ...AcquireTokenByInteractiveFlowSupplier.java | 9 +- .../aad/msal4j/AuthenticationErrorCode.java | 8 + .../aad/msal4j/DefaultHttpClient.java | 21 +- .../microsoft/aad/msal4j/ThrottlingCache.java | 2 +- .../com/microsoft/aad/msal4j/TokenCache.java | 2 +- .../src/samples/msal-b2c-web-sample/pom.xml | 2 +- .../src/samples/msal-obo-sample/pom.xml | 2 +- .../src/samples/msal-web-sample/pom.xml | 2 +- .../aad/msal4j/AadInstanceDiscoveryTest.java | 179 +++----- .../aad/msal4j/AbstractMsalTests.java | 9 - .../com/microsoft/aad/msal4j/AccountTest.java | 84 +--- .../aad/msal4j/AcquireTokenSilentlyTest.java | 45 +- .../aad/msal4j/AssertionCredentialTest.java | 20 - .../microsoft/aad/msal4j/AuthorityTest.java | 220 +++++---- ...AuthorizationRequestUrlParametersTest.java | 93 ++-- .../aad/msal4j/CacheFormatTests.java | 72 ++- .../com/microsoft/aad/msal4j/ClaimsTest.java | 31 +- .../msal4j/ClientCertificatePkcs12Test.java | 86 ++-- .../aad/msal4j/ClientCertificateTest.java | 64 ++- .../aad/msal4j/ClientCredentialTest.java | 35 ++ .../aad/msal4j/ClientSecretTest.java | 24 - .../aad/msal4j/DefaultHttpClientTest.java | 64 --- .../aad/msal4j/DeviceCodeFlowTest.java | 227 ---------- .../microsoft/aad/msal4j/HttpHeaderTest.java | 72 +-- .../microsoft/aad/msal4j/HttpUtilsTest.java | 22 +- .../microsoft/aad/msal4j/MexParserTest.java | 43 +- .../MsalOauthAuthorizatonGrantTest.java | 21 +- .../msal4j/OAuthRequestValidationTest.java | 186 -------- .../aad/msal4j/OauthHttpRequestTest.java | 99 ---- .../msal4j/PublicClientApplicationTest.java | 69 --- .../aad/msal4j/RequestThrottlingTest.java | 80 ++-- .../aad/msal4j/ServerTelemetryTests.java | 58 +-- .../microsoft/aad/msal4j/TelemetryTests.java | 106 ++--- .../aad/msal4j/TokenRequestExecutorTest.java | 185 ++++---- .../aad/msal4j/TokenResponseTest.java | 33 +- .../aad/msal4j/UIRequiredCacheTest.java | 79 ++-- .../aad/msal4j/WSTrustRequestTest.java | 32 +- .../aad/msal4j/WSTrustResponseTest.java | 44 +- 73 files changed, 1660 insertions(+), 3055 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/FeatureRequest.yaml create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yaml delete mode 100644 .github/workflows/codeql.yml create mode 100644 Contributing.md rename msal4j-sdk/changelog.txt => changelog.txt (93%) rename msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/{MsalRuntimeBroker.java => Broker.java} (86%) delete mode 100644 msal4j-sdk/contributing.md delete mode 100644 msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/ConfidentialClientApplicationUnitT.java delete mode 100644 msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/InstanceDiscoveryTest.java delete mode 100644 msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/OAuthRequestValidationUnitT.java delete mode 100644 msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AbstractMsalTests.java delete mode 100644 msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AssertionCredentialTest.java create mode 100644 msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ClientCredentialTest.java delete mode 100644 msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ClientSecretTest.java delete mode 100644 msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/DefaultHttpClientTest.java delete mode 100644 msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/DeviceCodeFlowTest.java delete mode 100644 msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/OAuthRequestValidationTest.java delete mode 100644 msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/OauthHttpRequestTest.java delete mode 100644 msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/PublicClientApplicationTest.java diff --git a/.github/ISSUE_TEMPLATE/FeatureRequest.yaml b/.github/ISSUE_TEMPLATE/FeatureRequest.yaml new file mode 100644 index 00000000..a112882e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/FeatureRequest.yaml @@ -0,0 +1,48 @@ +name: Feature request +description: Suggest a new feature for MSAL Java +labels: ["feature request", "untriaged", "needs attention"] +title : '[Feature Request] ' +body: +- type: markdown + attributes: + value: | + ## Before submitting your feature request + Please make sure that your question or issue is not already covered in [MSAL documentation](https://learn.microsoft.com/entra/msal/java/) or [samples](https://learn.microsoft.com/azure/active-directory/develop/sample-v2-code?tabs=apptype). + +- type: markdown + attributes: + value: | + ## Feature request for MSAL Java + +- type: dropdown + attributes: + label: MSAL client type + description: Are you using PublicClientApplication (desktop / CLI apps), ConfidentialClientApplication (web apps, web APIs, service-to-service) or ManagedIdentityApplication? + multiple: true + options: + - "Public" + - "Confidential" + - "Managed identity" + validations: + required: true + +- type: textarea + attributes: + label: Problem Statement + description: "Describe the problem or context for this feature request." + validations: + required: true + +- type: textarea + attributes: + label: Proposed solution + description: "Describe the solution you'd like." + validations: + required: false + +- type: textarea + attributes: + label: Alternatives + description: "Describe alternatives you've considered." + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml new file mode 100644 index 00000000..439b5ef8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -0,0 +1,115 @@ +name: Bug report +description: Broken or unintended behavior with MSAL4J library +title: '[Bug] ' +labels: ["untriaged", "needs attention"] +body: +- type: markdown + attributes: + value: | + ## Before submitting your issue + Please make sure that your question or issue is not already covered in existing issues + + **Logs and network traces** + Without logs or traces, it is unlikely that the team can investigate your issue. Capturing logs is described in our [Docs](https://learn.microsoft.com/azure/active-directory/develop/msal-logging-java). + +- type: markdown + attributes: + value: | + ## Issue details + +- type: input + attributes: + label: Library version used + description: "Enter the version of the library where you ran into the issue (e.g. 1.13.10)." + validations: + required: true + +- type: input + attributes: + label: Java version + description: "Enter the Java SDK and Framework version your application is developed in." + validations: + required: true + +- type: dropdown + attributes: + label: Scenario + description: "Are you using PublicClientApplication, ConfidentialClientApplication or ManagedIdentityApplication?" + multiple: true + options: + - "PublicClient (AcquireTokenInteractive, AcquireTokenByUsernamePassword)" + - "ConfidentialClient - web site (AcquireTokenByAuthCode)" + - "ConfidentialClient - web api (AcquireTokenOnBehalfOf)" + - "ConfidentialClient - service to service (AcquireTokenForClient)" + - "ManagedIdentityClient - managed identity" + - "Other - please specify" + validations: + required: true + +- type: dropdown + attributes: + label: Is this a new or an existing app? + description: "Is this a new or existing app?" + multiple: false + options: + - "The app is in production, and I have upgraded to a new version of MSAL" + - "The app is in production, I haven't upgraded MSAL, but started seeing this issue" + - "This is a new app or experiment" + validations: + required: false + +- type: textarea + attributes: + label: Issue description and reproduction steps + description: "Briefly explain the issue you are seeing along with any error messages or stack trace. Provide a link to one of the [standard samples](https://learn.microsoft.com/azure/active-directory/develop/sample-v2-code?tabs=apptype) and steps to reproduce the behavior. Make sure to provide verbose level log messages from MSAL, if available. [Learn more](https://learn.microsoft.com/azure/active-directory/develop/msal-logging-dotnet)" + validations: + required: true + +- type: textarea + attributes: + label: Relevant code snippets + description: "Provide relevant code snippets that can be used to reproduce the issue." + render: csharp + validations: + required: false + +- type: textarea + attributes: + label: Expected behavior + description: "Describe what you expect the behavior to be." + validations: + required: false + +- type: dropdown + attributes: + label: Identity provider + options: + - Microsoft Entra ID (Work and School accounts and Personal Microsoft accounts) + - Azure B2C Basic Policy + - Azure B2C Custom Policy + - Azure Active Directory Federation Services (ADFS) + - Microsoft Entra External ID + - Other + validations: + required: true + +- type: input + attributes: + label: Regression + description: "If this behavior worked before, enter the last working version(s) of MSAL." + placeholder: "MSAL version: " + +- type: textarea + attributes: + label: Solution and workarounds + description: "Possible solution or workarounds, if you know of any." + validations: + required: false + +- type: markdown + attributes: + value: "## Security Reporting" +- type: markdown + attributes: + value: | + If you find a security issue with our libraries or services [please report it to the Microsoft Security Response Center (MSRC)](https://aka.ms/report-security-issue) with as much detail as possible. Your submission may be eligible for a bounty through the [Microsoft Bounty](http://aka.ms/bugbounty) program. Please do not post security issues to GitHub Issues or any other public site. We will contact you shortly upon receiving the information. We encourage you to get notifications of when security incidents occur by visiting [this page](https://www.microsoft.com/msrc/technical-security-notifications) and subscribing to Security Advisory Alerts. diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 4dfaa38a..00000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: "Code Scanning - Action" - -on: - push: - schedule: - - cron: '0 0 * * 0' - -jobs: - CodeQL-Build: - - strategy: - fail-fast: false - - - # CodeQL runs on ubuntu-latest, windows-latest, and macos-latest - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - # Override language selection by uncommenting this and choosing your languages - # with: - # languages: go, javascript, csharp, python, cpp, java - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below). - - name: Autobuild - uses: github/codeql-action/autobuild@v2 - - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 diff --git a/Contributing.md b/Contributing.md new file mode 100644 index 00000000..fb5cdf20 --- /dev/null +++ b/Contributing.md @@ -0,0 +1,18 @@ +# CONTRIBUTING + +Microsoft Authentication Library for Java welcomes new contributors. This document will guide you +through the process. + +## CONTRIBUTOR LICENSE AGREEMENT + +Please visit [https://cla.microsoft.com/](https://cla.microsoft.com/) and sign the Contributor License +Agreement. You only need to do that once. We can not look at your code until you've submitted this request. + + +## Build + +Use Java8. + +## Test + +Unit tests should run as expected. Integration tests require certificate / secrets which are deployed on CI. External contributors are not able to run integration tests manually. diff --git a/README.md b/README.md index 00e86f3d..0ddb3d98 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Quick links: The library supports the following Java environments: - Java 8 (or higher) -Current version - 1.13.8 +Current version - 1.13.11 You can find the changes for each version in the [change log](https://github.com/AzureAD/microsoft-authentication-library-for-java/blob/main/msal4j-sdk/changelog.txt). @@ -28,13 +28,13 @@ Find [the latest package in the Maven repository](https://mvnrepository.com/arti com.microsoft.azure msal4j - 1.13.8 + 1.13.11 ``` ### Gradle ```gradle -implementation group: 'com.microsoft.azure', name: 'com.microsoft.aad.msal4j', version: '1.13.8' +implementation group: 'com.microsoft.azure', name: 'com.microsoft.aad.msal4j', version: '1.13.11' ``` ## Usage diff --git a/msal4j-sdk/changelog.txt b/changelog.txt similarity index 93% rename from msal4j-sdk/changelog.txt rename to changelog.txt index 09b53357..0e222aeb 100644 --- a/msal4j-sdk/changelog.txt +++ b/changelog.txt @@ -8,6 +8,22 @@ Version 1.14.0-beta - Add IBroker interface - Add app-level parameter for enabling the use of auth brokers +Version 1.13.11 +============= +- Hotfix for internal docs generation issue (#705) + +Version 1.13.10 +============= +- Remove default HTTP timeout (#664) +- Add equals/hash logic to Account class based on homeAccountID (#681) +- Fix issue with command to open default browser on Linux (#683) +- Handle null pointer exception in certain ADFS scenarios (#669) + +Version 1.13.9 +============= +- Update automated tests to use JUnit 5/Mockito instead of TestNG/Powermock +- Fix issue with interactive flow on macOS/Linux/Unix systems + Version 1.13.8 ============= - Added support for CIAM authority diff --git a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/Broker.java similarity index 86% rename from msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java rename to msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/Broker.java index 786f7245..30cdd7d9 100644 --- a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/MsalRuntimeBroker.java +++ b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/Broker.java @@ -13,15 +13,18 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -public class MsalRuntimeBroker implements IBroker { - private static final Logger LOG = LoggerFactory.getLogger(MsalRuntimeBroker.class); +public class Broker implements IBroker { + private static final Logger LOG = LoggerFactory.getLogger(Broker.class); private static MsalRuntimeInterop interop; private static Boolean brokerAvailable; + private boolean supportWindows; + static { try { //MsalRuntimeInterop performs various initialization steps in a similar static block, @@ -34,12 +37,13 @@ public class MsalRuntimeBroker implements IBroker { @Override public CompletableFuture acquireToken(PublicClientApplication application, SilentParameters parameters) { + String correlationID = application.correlationId() == null ? generateCorrelationID() : application.correlationId(); Account accountResult = null; //If request has an account ID, MSALRuntime likely has data cached for that account that we can retrieve if (parameters.account() != null) { try { - accountResult = ((ReadAccountResult) interop.readAccountById(parameters.account().homeAccountId().split("\\.")[0], application.correlationId()).get()).getAccount(); + accountResult = ((ReadAccountResult) interop.readAccountById(parameters.account().homeAccountId().split("\\.")[0], correlationID).get()).getAccount(); } catch (InterruptedException | ExecutionException ex) { throw new MsalClientException(String.format("MSALRuntime async operation interrupted when waiting for result: %s", ex.getMessage()), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); } @@ -62,8 +66,8 @@ public CompletableFuture acquireToken(PublicClientApplica AuthParameters authParameters = authParamsBuilder.build(); if (accountResult == null) { - return interop.signInSilently(authParameters, application.correlationId()) - .thenCompose(acctResult -> interop.acquireTokenSilently(authParameters, application.correlationId(), ((AuthResult) acctResult).getAccount())) + return interop.signInSilently(authParameters, correlationID) + .thenCompose(acctResult -> interop.acquireTokenSilently(authParameters, correlationID, ((AuthResult) acctResult).getAccount())) .thenApply(authResult -> parseBrokerAuthResult( application.authority(), ((AuthResult) authResult).getIdToken(), @@ -73,7 +77,7 @@ public CompletableFuture acquireToken(PublicClientApplica ((AuthResult) authResult).getAccessTokenExpirationTime(), ((AuthResult) authResult).isPopAuthorization())); } else { - return interop.acquireTokenSilently(authParameters, application.correlationId(), accountResult) + return interop.acquireTokenSilently(authParameters, correlationID, accountResult) .thenApply(authResult -> parseBrokerAuthResult(application.authority(), ((AuthResult) authResult).getIdToken(), ((AuthResult) authResult).getAccessToken(), @@ -89,6 +93,8 @@ public CompletableFuture acquireToken(PublicClientApplica @Override public CompletableFuture acquireToken(PublicClientApplication application, InteractiveRequestParameters parameters) { + String correlationID = application.correlationId() == null ? generateCorrelationID() : application.correlationId(); + try { AuthParameters.AuthParametersBuilder authParamsBuilder = new AuthParameters. AuthParametersBuilder(application.clientId(), @@ -103,11 +109,11 @@ public CompletableFuture acquireToken(PublicClientApplica parameters.proofOfPossession().getUri(), parameters.proofOfPossession().getNonce()); } - + AuthParameters authParameters = authParamsBuilder.build(); - return interop.signInInteractively(parameters.windowHandle(), authParameters, application.correlationId(), parameters.loginHint()) - .thenCompose(acctResult -> interop.acquireTokenInteractively(parameters.windowHandle(), authParameters, application.correlationId(), ((AuthResult) acctResult).getAccount())) + return interop.signInInteractively(parameters.windowHandle(), authParameters, correlationID, parameters.loginHint()) + .thenCompose(acctResult -> interop.acquireTokenInteractively(parameters.windowHandle(), authParameters, correlationID, ((AuthResult) acctResult).getAccount())) .thenApply(authResult -> parseBrokerAuthResult( application.authority(), ((AuthResult) authResult).getIdToken(), @@ -127,6 +133,8 @@ public CompletableFuture acquireToken(PublicClientApplica @Deprecated @Override public CompletableFuture acquireToken(PublicClientApplication application, UserNamePasswordParameters parameters) { + String correlationID = application.correlationId() == null ? generateCorrelationID() : application.correlationId(); + try { AuthParameters.AuthParametersBuilder authParamsBuilder = new AuthParameters. AuthParametersBuilder(application.clientId(), @@ -143,8 +151,8 @@ public CompletableFuture acquireToken(PublicClientApplica AuthParameters authParameters = authParamsBuilder.build(); - return interop.signInSilently(authParameters, application.correlationId()) - .thenCompose(acctResult -> interop.acquireTokenSilently(authParameters, application.correlationId(), ((AuthResult) acctResult).getAccount())) + return interop.signInSilently(authParameters, correlationID) + .thenCompose(acctResult -> interop.acquireTokenSilently(authParameters, correlationID, ((AuthResult) acctResult).getAccount())) .thenApply(authResult -> parseBrokerAuthResult( application.authority(), ((AuthResult) authResult).getIdToken(), @@ -160,11 +168,13 @@ public CompletableFuture acquireToken(PublicClientApplica @Override public void removeAccount(PublicClientApplication application, IAccount msalJavaAccount) { + String correlationID = application.correlationId() == null ? generateCorrelationID() : application.correlationId(); + try { - Account msalRuntimeAccount = ((ReadAccountResult) interop.readAccountById(msalJavaAccount.homeAccountId().split("\\.")[0], application.correlationId()).get()).getAccount(); + Account msalRuntimeAccount = ((ReadAccountResult) interop.readAccountById(msalJavaAccount.homeAccountId().split("\\.")[0], correlationID).get()).getAccount(); if (msalRuntimeAccount != null) { - interop.signOutSilently(application.clientId(), application.correlationId(), msalRuntimeAccount); + interop.signOutSilently(application.clientId(), correlationID, msalRuntimeAccount); } } catch (MsalInteropException interopException) { throw new MsalClientException(interopException.getErrorMessage(), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); @@ -233,4 +243,29 @@ public void enableBrokerPIILogging(boolean enablePII) { throw new MsalClientException(String.format("Error occurred when calling MSALRuntime PII logging API: %s", ex.getMessage()), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR); } } + + //Generates a random correlation ID, used when a correlation ID was not set at the application level + private String generateCorrelationID() { + return UUID.randomUUID().toString(); + } + + public static class Builder { + private boolean supportWindows = false; + + public Builder() { + } + + public Builder supportWindows(boolean val) { + supportWindows = val; + return this; + } + + public Broker build() { + return new Broker(this); + } + } + + private Broker(Builder builder) { + supportWindows = builder.supportWindows; + } } diff --git a/msal4j-brokers/src/test/java/test/ProofOfPossessionTest.java b/msal4j-brokers/src/test/java/test/ProofOfPossessionTest.java index 07fd5224..2f613958 100644 --- a/msal4j-brokers/src/test/java/test/ProofOfPossessionTest.java +++ b/msal4j-brokers/src/test/java/test/ProofOfPossessionTest.java @@ -1,14 +1,12 @@ package test; import com.microsoft.aad.msal4j.*; -import com.microsoft.aad.msal4jbrokers.MsalRuntimeBroker; +import com.microsoft.aad.msal4jbrokers.Broker; import infrastructure.SeleniumExtensions; import labapi.LabUserProvider; import labapi.User; import org.openqa.selenium.WebDriver; import org.testng.Assert; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; import java.net.MalformedURLException; import java.net.URI; @@ -35,7 +33,7 @@ public void setUp() { public void acquirePopToken_WithBroker() throws Exception { User user = labUserProvider.getDefaultUser(); - MsalRuntimeBroker broker = new MsalRuntimeBroker(); + Broker broker = new Broker.Builder().supportWindows(true).build(); PublicClientApplication pca = createPublicClientApp(user, broker); @@ -75,7 +73,7 @@ public void acquirePopToken_BrowserAndBroker() throws Exception { seleniumDriver.quit(); //Then, get a PoP token silently, using the cache that contains the non-PoP token - MsalRuntimeBroker broker = new MsalRuntimeBroker(); + Broker broker = new Broker.Builder().supportWindows(true).build(); PublicClientApplication pcaWithBroker = createPublicClientApp(user, broker, pcaWithoutBroker.tokenCache().serialize()); @@ -93,7 +91,7 @@ private PublicClientApplication createPublicClientApp(User user) throws Malforme .build(); } - private PublicClientApplication createPublicClientApp(User user, MsalRuntimeBroker broker) throws MalformedURLException { + private PublicClientApplication createPublicClientApp(User user, Broker broker) throws MalformedURLException { return PublicClientApplication.builder(user.getAppId()) .authority(MICROSOFT_AUTHORITY_ORGANIZATIONS) .correlationId(UUID.randomUUID().toString()) @@ -101,7 +99,7 @@ private PublicClientApplication createPublicClientApp(User user, MsalRuntimeBrok .build(); } - private PublicClientApplication createPublicClientApp(User user, MsalRuntimeBroker broker, String cache) throws MalformedURLException { + private PublicClientApplication createPublicClientApp(User user, Broker broker, String cache) throws MalformedURLException { return PublicClientApplication.builder(user.getAppId()) .authority(MICROSOFT_AUTHORITY_ORGANIZATIONS) .correlationId(UUID.randomUUID().toString()) diff --git a/msal4j-sdk/README.md b/msal4j-sdk/README.md index 9e4bc23d..474499bc 100644 --- a/msal4j-sdk/README.md +++ b/msal4j-sdk/README.md @@ -16,7 +16,7 @@ Quick links: The library supports the following Java environments: - Java 8 (or higher) -Current version - 1.14.1-beta +Current version - 1.13.11 You can find the changes for each version in the [change log](https://github.com/AzureAD/microsoft-authentication-library-for-java/blob/master/changelog.txt). @@ -28,13 +28,13 @@ Find [the latest package in the Maven repository](https://mvnrepository.com/arti com.microsoft.azure msal4j - 1.14.1-beta + 1.13.11 ``` ### Gradle ```gradle -compile group: 'com.microsoft.azure', name: 'msal4j', version: '1.14.1-beta' +compile group: 'com.microsoft.azure', name: 'msal4j', version: '1.13.11' ``` ## Usage diff --git a/msal4j-sdk/bnd.bnd b/msal4j-sdk/bnd.bnd index 309b785b..0a14dff8 100644 --- a/msal4j-sdk/bnd.bnd +++ b/msal4j-sdk/bnd.bnd @@ -1,2 +1,2 @@ -Export-Package: com.microsoft.aad.msal4j;version="1.14.1-beta" +Export-Package: com.microsoft.aad.msal4j;version="1.13.11" Automatic-Module-Name: com.microsoft.aad.msal4j diff --git a/msal4j-sdk/contributing.md b/msal4j-sdk/contributing.md deleted file mode 100644 index 82ba843b..00000000 --- a/msal4j-sdk/contributing.md +++ /dev/null @@ -1,122 +0,0 @@ -# CONTRIBUTING - -Microsoft Authentication Library for Java welcomes new contributors. This document will guide you -through the process. - -### CONTRIBUTOR LICENSE AGREEMENT - -Please visit [https://cla.microsoft.com/](https://cla.microsoft.com/) and sign the Contributor License -Agreement. You only need to do that once. We can not look at your code until you've submitted this request. - - -### FORK - -Fork the project [on GitHub](https://github.com/AzureAD/microsoft-authentication-library-for-java) and check out -your copy. - -Example for MSAL Java: - -``` -$ git clone git@github.com:username/microsoft-authentication-library-for-java.git -$ cd microsoft-authentication-library-for-java -$ git remote add upstream git@github.com:AzureAD/microsoft-authentication-library-for-java.git -``` - -Now decide if you want your feature or bug fix to go into the dev branch -or the master branch. **All bug fixes and new features should go into the dev branch.** - -The master branch is effectively frozen; patches that change the SDKs -protocols or API surface area or affect the run-time behavior of the SDK will be rejected. - -Some of our SDKs have bundled dependencies that are not part of the project proper. Any changes to files in those directories or its subdirectories should be sent to their respective -projects. Do not send your patch to us, we cannot accept it. - -In case of doubt, open an issue in the [issue tracker](https://github.com/AzureAD/microsoft-authentication-library-for-java/issues). - -Especially do so if you plan to work on a major change in functionality. Nothing is more -frustrating than seeing your hard work go to waste because your vision -does not align with our goals for the SDK. - - -### BRANCH - -Okay, so you have decided on the proper branch. Create a feature branch -and start hacking: - -``` -$ git checkout -b my-feature-branch -``` - -### COMMIT - -Make sure git knows your name and email address: - -``` -$ git config --global user.name "J. Random User" -$ git config --global user.email "j.random.user@example.com" -``` - -Writing good commit logs is important. A commit log should describe what -changed and why. Follow these guidelines when writing one: - -1. The first line should be 50 characters or less and contain a short - description of the change prefixed with the name of the changed - subsystem (e.g. "net: add localAddress and localPort to Socket"). -2. Keep the second line blank. -3. Wrap all other lines at 72 columns. - -A good commit log looks like this: - -``` -fix: explaining the commit in one line - -Body of commit message is a few lines of text, explaining things -in more detail, possibly giving some background about the issue -being fixed, etc etc. - -The body of the commit message can be several paragraphs, and -please do proper word-wrap and keep columns shorter than about -72 characters or so. That way `git log` will show things -nicely even when it is indented. -``` - -The header line should be meaningful; it is what other people see when they -run `git shortlog` or `git log --oneline`. - -Check the output of `git log --oneline files_that_you_changed` to find out -what directories your changes touch. - - -### REBASE - -Use `git rebase` (not `git merge`) to sync your work from time to time. - -``` -$ git fetch upstream -$ git rebase upstream/v0.1 # or upstream/master -``` - - -### TEST - -Bug fixes and features should come with tests. Add your tests in the -test directory. This varies by repository but often follows the same convention of /src/test. Look at other tests to see how they should be -structured (license boilerplate, common includes, etc.). - - -Make sure that all tests pass. - - -### PUSH - -``` -$ git push origin my-feature-branch -``` - -Go to https://github.com/username/microsoft-authentication-library-for-java.git and select your feature branch. Click -the 'Pull Request' button and fill out the form. - -Pull requests are usually reviewed within a few days. If there are comments -to address, apply your changes in a separate commit and push that to your -feature branch. Post a comment in the pull request afterwards; GitHub does -not send out notifications when you add commits. diff --git a/msal4j-sdk/pom.xml b/msal4j-sdk/pom.xml index d96c4540..847b32e7 100644 --- a/msal4j-sdk/pom.xml +++ b/msal4j-sdk/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.microsoft.azure msal4j - 1.14.1-beta + 1.13.11 jar msal4j @@ -68,29 +68,42 @@ test - org.testng - testng - 7.1.0 + org.junit.jupiter + junit-jupiter-api + 5.9.2 test - org.powermock - powermock-module-testng - 2.0.0 + org.junit.jupiter + junit-jupiter-params + 5.8.1 test - org.powermock - powermock-api-easymock - 2.0.0 + org.junit.jupiter + junit-jupiter-engine + 5.9.2 test - org.easymock - easymock - 4.0.2 + org.mockito + mockito-inline + 4.7.0 test + + org.mockito + mockito-junit-jupiter + 4.7.0 + test + + + net.bytebuddy + byte-buddy + 1.14.5 + test + + org.skyscreamer jsonassert @@ -118,7 +131,7 @@ com.google.guava guava - 31.1-jre + 32.1.1-jre test diff --git a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/AcquireTokenInteractiveIT.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/AcquireTokenInteractiveIT.java index edce1e88..fe2d0b25 100644 --- a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/AcquireTokenInteractiveIT.java +++ b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/AcquireTokenInteractiveIT.java @@ -4,10 +4,18 @@ package com.microsoft.aad.msal4j; import labapi.*; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.testng.Assert; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import java.net.MalformedURLException; import java.net.URI; @@ -17,13 +25,31 @@ import java.util.Map; import java.util.concurrent.ExecutionException; -public class AcquireTokenInteractiveIT extends SeleniumTest { +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class AcquireTokenInteractiveIT extends SeleniumTest { private final static Logger LOG = LoggerFactory.getLogger(AuthorizationCodeIT.class); private Config cfg; - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) - public void acquireTokenInteractive_ManagedUser(String environment) { + + @BeforeAll + public void setupUserProvider() { + setUpLapUserProvider(); + } + + @AfterEach + public void stopBrowser() { + cleanUp(); + } + + @BeforeEach + public void startBrowser() { + startUpBrowser(); + } + + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") + void acquireTokenInteractive_ManagedUser(String environment) { cfg = new Config(environment); User user = labUserProvider.getDefaultUser(cfg.azureEnvironment); @@ -31,56 +57,85 @@ public void acquireTokenInteractive_ManagedUser(String environment) { } @Test() - public void acquireTokenInteractive_ADFSv2019_OnPrem() { + void acquireTokenInteractive_ADFSv2019_OnPrem() { User user = labUserProvider.getOnPremAdfsUser(FederationProvider.ADFS_2019); assertAcquireTokenCommon(user, TestConstants.ADFS_AUTHORITY, TestConstants.ADFS_SCOPE); } - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) - public void acquireTokenInteractive_ADFSv2019_Federated(String environment) { + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") + void acquireTokenInteractive_ADFSv2019_Federated(String environment) { cfg = new Config(environment); User user = labUserProvider.getFederatedAdfsUser(cfg.azureEnvironment, FederationProvider.ADFS_2019); assertAcquireTokenCommon(user, cfg.organizationsAuthority(), cfg.graphDefaultScope()); } - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) - public void acquireTokenInteractive_ADFSv4_Federated(String environment) { + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") + void acquireTokenInteractive_ADFSv4_Federated(String environment) { cfg = new Config(environment); User user = labUserProvider.getFederatedAdfsUser(cfg.azureEnvironment, FederationProvider.ADFS_4); assertAcquireTokenCommon(user, cfg.organizationsAuthority(), cfg.graphDefaultScope()); } - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) - public void acquireTokenInteractive_ADFSv3_Federated(String environment) { + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") + void acquireTokenInteractive_ADFSv3_Federated(String environment) { cfg = new Config(environment); User user = labUserProvider.getFederatedAdfsUser(cfg.azureEnvironment, FederationProvider.ADFS_3); assertAcquireTokenCommon(user, cfg.organizationsAuthority(), cfg.graphDefaultScope()); } - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) - public void acquireTokenInteractive_ADFSv2_Federated(String environment) { + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") + void acquireTokenInteractive_ADFSv2_Federated(String environment) { cfg = new Config(environment); User user = labUserProvider.getFederatedAdfsUser(cfg.azureEnvironment, FederationProvider.ADFS_2); assertAcquireTokenCommon(user, cfg.organizationsAuthority(), cfg.graphDefaultScope()); } + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") + void acquireTokenWithAuthorizationCode_B2C_Local(String environment) { + cfg = new Config(environment); + + User user = labUserProvider.getB2cUser(cfg.azureEnvironment, B2CProvider.LOCAL); + assertAcquireTokenB2C(user, TestConstants.B2C_AUTHORITY); + } + + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") + void acquireTokenWithAuthorizationCode_B2C_LegacyFormat(String environment) { + cfg = new Config(environment); + + User user = labUserProvider.getB2cUser(cfg.azureEnvironment, B2CProvider.LOCAL); + assertAcquireTokenB2C(user, TestConstants.B2C_AUTHORITY_LEGACY_FORMAT); + } + + @Test + void acquireTokenInteractive_ManagedUser_InstanceAware() { + cfg = new Config(AzureEnvironment.AZURE); + + User user = labUserProvider.getDefaultUser(AzureEnvironment.AZURE_US_GOVERNMENT); + assertAcquireTokenInstanceAware(user); + } + @Test - public void acquireTokenInteractive_Ciam() { + void acquireTokenInteractive_Ciam() { User user = labUserProvider.getCiamUser(); Map extraQueryParameters = new HashMap<>(); - extraQueryParameters.put("dc","ESTS-PUB-EUS-AZ1-FD000-TEST1"); PublicClientApplication pca; try { pca = PublicClientApplication.builder( user.getAppId()). authority("https://" + user.getLabName() + ".ciamlogin.com/") - .build(); + .build(); } catch (MalformedURLException ex) { throw new RuntimeException(ex.getMessage()); } @@ -97,7 +152,7 @@ public void acquireTokenInteractive_Ciam() { InteractiveRequestParameters parameters = InteractiveRequestParameters .builder(url) - .scopes(Collections.singleton(TestConstants.GRAPH_DEFAULT_SCOPE)) + .scopes(Collections.singleton(TestConstants.USER_READ_SCOPE)) .extraQueryParameters(extraQueryParameters) .systemBrowserOptions(browserOptions) .build(); @@ -110,31 +165,7 @@ public void acquireTokenInteractive_Ciam() { } assertTokenResultNotNull(result); - Assert.assertEquals(user.getUpn(), result.account().username()); - } - - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) - public void acquireTokenWithAuthorizationCode_B2C_Local(String environment) { - cfg = new Config(environment); - - User user = labUserProvider.getB2cUser(cfg.azureEnvironment, B2CProvider.LOCAL); - assertAcquireTokenB2C(user, TestConstants.B2C_AUTHORITY); - } - - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) - public void acquireTokenWithAuthorizationCode_B2C_LegacyFormat(String environment) { - cfg = new Config(environment); - - User user = labUserProvider.getB2cUser(cfg.azureEnvironment, B2CProvider.LOCAL); - assertAcquireTokenB2C(user, TestConstants.B2C_AUTHORITY_LEGACY_FORMAT); - } - - @Test - public void acquireTokenInteractive_ManagedUser_InstanceAware() { - cfg = new Config(AzureEnvironment.AZURE); - - User user = labUserProvider.getDefaultUser(AzureEnvironment.AZURE_US_GOVERNMENT); - assertAcquireTokenInstanceAware(user); + assertEquals(user.getUpn(), result.account().username()); } private void assertAcquireTokenCommon(User user, String authority, String scope) { @@ -154,7 +185,7 @@ private void assertAcquireTokenCommon(User user, String authority, String scope) scope); assertTokenResultNotNull(result); - Assert.assertEquals(user.getUpn(), result.account().username()); + assertEquals(user.getUpn(), result.account().username()); } private void assertAcquireTokenB2C(User user, String authority) { @@ -187,13 +218,13 @@ private void assertAcquireTokenInstanceAware(User user) { IAuthenticationResult result = acquireTokenInteractive_instanceAware(user, pca, cfg.graphDefaultScope()); assertTokenResultNotNull(result); - Assert.assertEquals(user.getUpn(), result.account().username()); + assertEquals(user.getUpn(), result.account().username()); //This test is using a client app with the login.microsoftonline.com config to get tokens for a login.microsoftonline.us user, // so when using instance aware the result's environment will be for the user/account and not the client app - Assert.assertNotEquals(pca.authenticationAuthority.host, result.environment()); - Assert.assertEquals(result.account().environment(), result.environment()); - Assert.assertEquals(result.account().environment(), pca.getAccounts().join().iterator().next().environment()); + assertNotEquals(pca.authenticationAuthority.host, result.environment()); + assertEquals(result.account().environment(), result.environment()); + assertEquals(result.account().environment(), pca.getAccounts().join().iterator().next().environment()); IAuthenticationResult cachedResult; try { @@ -203,17 +234,7 @@ private void assertAcquireTokenInstanceAware(User user) { } //Ensure that the cached environment matches the original auth result environment (.us) instead of the client app's (.com) - Assert.assertEquals(result.account().environment(), cachedResult.environment()); - } - - //@Test - public void acquireTokensInHomeAndGuestClouds_ArlingtonAccount() throws MalformedURLException, ExecutionException, InterruptedException { - acquireTokensInHomeAndGuestClouds(AzureEnvironment.AZURE_US_GOVERNMENT); - } - - //@Test - public void acquireTokensInHomeAndGuestClouds_MooncakeAccount() throws MalformedURLException, ExecutionException, InterruptedException { - acquireTokensInHomeAndGuestClouds(AzureEnvironment.AZURE_CHINA); + assertEquals(result.account().environment(), cachedResult.environment()); } private IAuthenticationResult acquireTokenSilently(IPublicClientApplication pca, IAccount account, String scope) throws InterruptedException, ExecutionException, MalformedURLException { @@ -251,11 +272,11 @@ public void afterCacheAccess(ITokenCacheAccessContext iTokenCacheAccessContext) IAuthenticationResult result = acquireTokenInteractive(user, publicCloudPca, TestConstants.USER_READ_SCOPE); assertTokenResultNotNull(result); - Assert.assertEquals(user.getHomeUPN(), result.account().username()); + assertEquals(user.getHomeUPN(), result.account().username()); publicCloudPca.removeAccount(publicCloudPca.getAccounts().join().iterator().next()).join(); - Assert.assertEquals(publicCloudPca.getAccounts().join().size(), 0); + assertEquals(publicCloudPca.getAccounts().join().size(), 0); } private IAuthenticationResult acquireTokenInteractive( @@ -289,9 +310,9 @@ private IAuthenticationResult acquireTokenInteractive( } private void assertTokenResultNotNull(IAuthenticationResult result) { - Assert.assertNotNull(result); - Assert.assertNotNull(result.accessToken()); - Assert.assertNotNull(result.idToken()); + assertNotNull(result); + assertNotNull(result.accessToken()); + assertNotNull(result.idToken()); } private IAuthenticationResult acquireTokenInteractive_instanceAware( 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 56d5f7d5..f7eb2bf2 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 @@ -4,9 +4,15 @@ package com.microsoft.aad.msal4j; import labapi.*; -import org.testng.Assert; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.api.BeforeAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.net.MalformedURLException; import java.util.*; @@ -14,18 +20,20 @@ import static com.microsoft.aad.msal4j.TestConstants.KEYVAULT_DEFAULT_SCOPE; -public class AcquireTokenSilentIT { +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class AcquireTokenSilentIT { private LabUserProvider labUserProvider; private Config cfg; - @BeforeClass - public void setUp() { + @BeforeAll + void setUp() { labUserProvider = LabUserProvider.getInstance(); } - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) - public void acquireTokenSilent_OrganizationAuthority_TokenRefreshed(String environment) throws Exception { + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") + void acquireTokenSilent_OrganizationAuthority_TokenRefreshed(String environment) throws Exception { cfg = new Config(environment); // When using common, organization, or consumer tenants, cache has no way @@ -37,8 +45,9 @@ public void acquireTokenSilent_OrganizationAuthority_TokenRefreshed(String envir assertResultNotNull(result); } - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) - public void acquireTokenSilent_LabAuthority_TokenNotRefreshed(String environment) throws Exception { + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") + void acquireTokenSilent_LabAuthority_TokenNotRefreshed(String environment) throws Exception { cfg = new Config(environment); // Access token should be returned from cache, and not using refresh token @@ -58,12 +67,13 @@ public void acquireTokenSilent_LabAuthority_TokenNotRefreshed(String environment assertResultNotNull(result); // Check that access and id tokens are coming from cache - Assert.assertEquals(result.accessToken(), acquireSilentResult.accessToken()); - Assert.assertEquals(result.idToken(), acquireSilentResult.idToken()); + assertEquals(result.accessToken(), acquireSilentResult.accessToken()); + assertEquals(result.idToken(), acquireSilentResult.idToken()); } - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) - public void acquireTokenSilent_ForceRefresh(String environment) throws Exception { + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") + void acquireTokenSilent_ForceRefresh(String environment) throws Exception { cfg = new Config(environment); User user = labUserProvider.getDefaultUser(environment); @@ -84,8 +94,9 @@ public void acquireTokenSilent_ForceRefresh(String environment) throws Exception assertTokensAreNotEqual(result, resultAfterRefresh); } - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) - public void acquireTokenSilent_MultipleAccountsInCache_UseCorrectAccount(String environment) throws Exception { + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") + void acquireTokenSilent_MultipleAccountsInCache_UseCorrectAccount(String environment) throws Exception { cfg = new Config(environment); IPublicClientApplication pca = getPublicClientApplicationWithTokensInCache(); @@ -103,11 +114,12 @@ public void acquireTokenSilent_MultipleAccountsInCache_UseCorrectAccount(String IAuthenticationResult result = acquireTokenSilently(pca, account, cfg.graphDefaultScope(), false); assertResultNotNull(result); - Assert.assertEquals(result.account().username(), user.getUpn()); + assertEquals(result.account().username(), user.getUpn()); } - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) - public void acquireTokenSilent_ADFS2019(String environment) throws Exception { + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") + void acquireTokenSilent_ADFS2019(String environment) throws Exception { cfg = new Config(environment); UserQueryParameters query = new UserQueryParameters(); @@ -136,42 +148,19 @@ public void acquireTokenSilent_ADFS2019(String environment) throws Exception { assertTokensAreNotEqual(result, resultAfterRefresh); } - // Commented out due to unclear B2C behavior causing occasional errors - //@Test - public void acquireTokenSilent_B2C() throws Exception { - UserQueryParameters query = new UserQueryParameters(); - query.parameters.put(UserQueryParameters.USER_TYPE, UserType.B2C); - query.parameters.put(UserQueryParameters.B2C_PROVIDER, B2CProvider.LOCAL); - User user = labUserProvider.getLabUser(query); - - PublicClientApplication pca = PublicClientApplication.builder( - user.getAppId()). - b2cAuthority(TestConstants.B2C_AUTHORITY_ROPC). - build(); - - IAuthenticationResult result = acquireTokenUsernamePassword(user, pca, TestConstants.B2C_READ_SCOPE); - assertResultNotNull(result); - - IAccount account = pca.getAccounts().join().iterator().next(); - IAuthenticationResult resultAfterRefresh = acquireTokenSilently(pca, account, TestConstants.B2C_READ_SCOPE, true); - assertResultNotNull(resultAfterRefresh); - - assertTokensAreNotEqual(result, resultAfterRefresh); - } - - @Test - public void acquireTokenSilent_usingCommonAuthority_returnCachedAt() throws Exception { + void acquireTokenSilent_usingCommonAuthority_returnCachedAt() throws Exception { acquireTokenSilent_returnCachedTokens(cfg.organizationsAuthority()); } @Test - public void acquireTokenSilent_usingTenantSpecificAuthority_returnCachedAt() throws Exception { + void acquireTokenSilent_usingTenantSpecificAuthority_returnCachedAt() throws Exception { acquireTokenSilent_returnCachedTokens(cfg.tenantSpecificAuthority()); } - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) - public void acquireTokenSilent_ConfidentialClient_acquireTokenSilent(String environment) throws Exception { + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") + void acquireTokenSilent_ConfidentialClient_acquireTokenSilent(String environment) throws Exception { cfg = new Config(environment); IConfidentialClientApplication cca = getConfidentialClientApplications(); @@ -184,8 +173,8 @@ public void acquireTokenSilent_ConfidentialClient_acquireTokenSilent(String envi .build()) .get(); - Assert.assertNotNull(result); - Assert.assertNotNull(result.accessToken()); + assertNotNull(result); + assertNotNull(result.accessToken()); String cachedAt = result.accessToken(); @@ -195,11 +184,11 @@ public void acquireTokenSilent_ConfidentialClient_acquireTokenSilent(String envi .build()) .get(); - Assert.assertNotNull(result); - Assert.assertEquals(result.accessToken(), cachedAt); + assertNotNull(result); + assertEquals(result.accessToken(), cachedAt); } - @Test(expectedExceptions = ExecutionException.class) + @Test public void acquireTokenSilent_ConfidentialClient_acquireTokenSilentDifferentScopeThrowsException() throws Exception { cfg = new Config(AzureEnvironment.AZURE); @@ -211,18 +200,19 @@ public void acquireTokenSilent_ConfidentialClient_acquireTokenSilentDifferentSco .build()) .get(); - Assert.assertNotNull(result); - Assert.assertNotNull(result.accessToken()); + assertNotNull(result); + assertNotNull(result.accessToken()); //Acquiring token for different scope, expect exception to be thrown - cca.acquireTokenSilently(SilentParameters - .builder(Collections.singleton(cfg.graphDefaultScope())) - .build()) - .get(); + assertThrows(ExecutionException.class, () -> cca.acquireTokenSilently(SilentParameters + .builder(Collections.singleton(cfg.graphDefaultScope())) + .build()) + .get()); } - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) - public void acquireTokenSilent_WithRefreshOn(String environment) throws Exception { + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") + void acquireTokenSilent_WithRefreshOn(String environment) throws Exception { cfg = new Config(environment); User user = labUserProvider.getDefaultUser(cfg.azureEnvironment); @@ -236,7 +226,7 @@ public void acquireTokenSilent_WithRefreshOn(String environment) throws Exceptio assertResultNotNull(resultOriginal); IAuthenticationResult resultSilent = acquireTokenSilently(pca, resultOriginal.account(), cfg.graphDefaultScope(), false); - Assert.assertNotNull(resultSilent); + assertNotNull(resultSilent); assertTokensAreEqual(resultOriginal, resultSilent); //When this test was made, token responses did not contain the refresh_in field needed for an end-to-end test. @@ -251,8 +241,8 @@ public void acquireTokenSilent_WithRefreshOn(String environment) throws Exceptio IAuthenticationResult resultSilentWithRefreshOn = acquireTokenSilently(pca, resultOriginal.account(), cfg.graphDefaultScope(), false); //Current time is before refreshOn, so token should not have been refreshed - Assert.assertNotNull(resultSilentWithRefreshOn); - Assert.assertEquals(pca.tokenCache.accessTokens.get(key).refreshOn(), Long.toString(currTimestampSec + 60)); + assertNotNull(resultSilentWithRefreshOn); + assertEquals(pca.tokenCache.accessTokens.get(key).refreshOn(), Long.toString(currTimestampSec + 60)); assertTokensAreEqual(resultSilent, resultSilentWithRefreshOn); token = pca.tokenCache.accessTokens.get(key); @@ -261,12 +251,13 @@ public void acquireTokenSilent_WithRefreshOn(String environment) throws Exceptio resultSilentWithRefreshOn = acquireTokenSilently(pca, resultOriginal.account(), cfg.graphDefaultScope(), false); //Current time is after refreshOn, so token should be refreshed - Assert.assertNotNull(resultSilentWithRefreshOn); + assertNotNull(resultSilentWithRefreshOn); assertTokensAreNotEqual(resultSilent, resultSilentWithRefreshOn); } - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) - public void acquireTokenSilent_TenantAsParameter(String environment) throws Exception { + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") + void acquireTokenSilent_TenantAsParameter(String environment) throws Exception { cfg = new Config(environment); User user = labUserProvider.getDefaultUser(environment); @@ -296,8 +287,9 @@ public void acquireTokenSilent_TenantAsParameter(String environment) throws Exce assertTokensAreNotEqual(result, resultWithTenantParam); } - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) - public void acquireTokenSilent_emptyStringScope(String environment) throws Exception { + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") + void acquireTokenSilent_emptyStringScope(String environment) throws Exception { cfg = new Config(environment); User user = labUserProvider.getDefaultUser(environment); @@ -313,11 +305,12 @@ public void acquireTokenSilent_emptyStringScope(String environment) throws Excep IAccount account = pca.getAccounts().join().iterator().next(); IAuthenticationResult silentResult = acquireTokenSilently(pca, account, emptyScope, false); assertResultNotNull(silentResult); - Assert.assertEquals(result.accessToken(), silentResult.accessToken()); + assertEquals(result.accessToken(), silentResult.accessToken()); } - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) - public void acquireTokenSilent_emptyScopeSet(String environment) throws Exception { + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") + void acquireTokenSilent_emptyScopeSet(String environment) throws Exception { cfg = new Config(environment); User user = labUserProvider.getDefaultUser(environment); @@ -342,7 +335,7 @@ public void acquireTokenSilent_emptyScopeSet(String environment) throws Exceptio .get(); assertResultNotNull(silentResult); - Assert.assertEquals(result.accessToken(), silentResult.accessToken()); + assertEquals(result.accessToken(), silentResult.accessToken()); } private IConfidentialClientApplication getConfidentialClientApplications() throws Exception { @@ -368,7 +361,7 @@ private void acquireTokenSilent_returnCachedTokens(String authority) throws Exce IAuthenticationResult interactiveAuthResult = acquireTokenUsernamePassword(user, pca, cfg.graphDefaultScope()); - Assert.assertNotNull(interactiveAuthResult); + assertNotNull(interactiveAuthResult); IAuthenticationResult silentAuthResult = pca.acquireTokenSilently( SilentParameters.builder( @@ -376,8 +369,8 @@ private void acquireTokenSilent_returnCachedTokens(String authority) throws Exce .build()) .get(); - Assert.assertNotNull(silentAuthResult); - Assert.assertEquals(interactiveAuthResult.accessToken(), silentAuthResult.accessToken()); + assertNotNull(silentAuthResult); + assertEquals(interactiveAuthResult.accessToken(), silentAuthResult.accessToken()); } private IPublicClientApplication getPublicClientApplicationWithTokensInCache() @@ -414,18 +407,18 @@ private IAuthenticationResult acquireTokenUsernamePassword(User user, IPublicCli } private void assertResultNotNull(IAuthenticationResult result) { - Assert.assertNotNull(result); - Assert.assertNotNull(result.accessToken()); - Assert.assertNotNull(result.idToken()); + assertNotNull(result); + assertNotNull(result.accessToken()); + assertNotNull(result.idToken()); } private void assertTokensAreNotEqual(IAuthenticationResult result, IAuthenticationResult secondResult) { - Assert.assertNotEquals(result.accessToken(), secondResult.accessToken()); - Assert.assertNotEquals(result.idToken(), secondResult.idToken()); + assertNotEquals(result.accessToken(), secondResult.accessToken()); + assertNotEquals(result.idToken(), secondResult.idToken()); } private void assertTokensAreEqual(IAuthenticationResult result, IAuthenticationResult secondResult) { - Assert.assertEquals(result.accessToken(), secondResult.accessToken()); - Assert.assertEquals(result.idToken(), secondResult.idToken()); + assertEquals(result.accessToken(), secondResult.accessToken()); + assertEquals(result.idToken(), secondResult.idToken()); } } 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 26bbe6d3..8cf235a7 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 @@ -4,10 +4,17 @@ package com.microsoft.aad.msal4j; import labapi.*; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.testng.Assert; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import java.net.MalformedURLException; import java.net.URI; @@ -19,32 +26,34 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; -public class AuthorizationCodeIT extends SeleniumTest { +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class AuthorizationCodeIT extends SeleniumTest { private final static Logger LOG = LoggerFactory.getLogger(AuthorizationCodeIT.class); private Config cfg; - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) - public void acquireTokenWithAuthorizationCode_ManagedUser(String environment) { - cfg = new Config(environment); + @BeforeAll + public void setupUserProvider() { + setUpLapUserProvider(); + } - User user = labUserProvider.getDefaultUser(cfg.azureEnvironment); - assertAcquireTokenAAD(user, null); + @AfterEach + public void stopBrowser() { + cleanUp(); } - //TODO: Re-enable test once list of claims/capabilities and their expected behavior is known - //@Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) - public void acquireTokenWithAuthorizationCode_ManagedUserWithClaimsAndCapabilities(String environment) { + @BeforeEach + public void startBrowser() { + startUpBrowser(); + } + + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") + public void acquireTokenWithAuthorizationCode_ManagedUser(String environment) { cfg = new Config(environment); User user = labUserProvider.getDefaultUser(cfg.azureEnvironment); - - Map> claimsAndCapabilities = new HashMap<>(); - - claimsAndCapabilities.put("claims", Collections.singleton(TestConstants.CLAIMS)); - claimsAndCapabilities.put("clientCapabilities", TestConstants.CLIENT_CAPABILITIES_EMPTY); - - assertAcquireTokenAAD(user, claimsAndCapabilities); + assertAcquireTokenAAD(user, null); } @Test @@ -53,7 +62,8 @@ public void acquireTokenWithAuthorizationCode_ADFSv2019_OnPrem() { assertAcquireTokenADFS2019(user); } - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") public void acquireTokenWithAuthorizationCode_ADFSv2019_Federated(String environment) { cfg = new Config(environment); @@ -61,7 +71,8 @@ public void acquireTokenWithAuthorizationCode_ADFSv2019_Federated(String environ assertAcquireTokenAAD(user, null); } - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") public void acquireTokenWithAuthorizationCode_ADFSv4_Federated(String environment) { cfg = new Config(environment); @@ -70,7 +81,8 @@ public void acquireTokenWithAuthorizationCode_ADFSv4_Federated(String environmen assertAcquireTokenAAD(user, null); } - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") public void acquireTokenWithAuthorizationCode_ADFSv3_Federated(String environment) { cfg = new Config(environment); @@ -78,7 +90,8 @@ public void acquireTokenWithAuthorizationCode_ADFSv3_Federated(String environmen assertAcquireTokenAAD(user, null); } - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") public void acquireTokenWithAuthorizationCode_ADFSv2_Federated(String environment) { cfg = new Config(environment); @@ -86,7 +99,8 @@ public void acquireTokenWithAuthorizationCode_ADFSv2_Federated(String environmen assertAcquireTokenAAD(user, null); } - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") public void acquireTokenWithAuthorizationCode_B2C_Local(String environment) { cfg = new Config(environment); @@ -94,37 +108,6 @@ public void acquireTokenWithAuthorizationCode_B2C_Local(String environment) { assertAcquireTokenB2C(user); } - // failing on azure devOps - //@Test - // TODO Redirect URI localhost in not registered - public void acquireTokenWithAuthorizationCode_B2C_Google() { -/* LabResponse labResponse = labUserProvider.getB2cUser( - B2CIdentityProvider.GOOGLE, - false); - labUserProvider.getUserPassword(labResponse.getUser()); - - String b2CAppId = "b876a048-55a5-4fc5-9403-f5d90cb1c852"; - labResponse.setAppId(b2CAppId);*/ - User user = labUserProvider.getB2cUser(B2CProvider.GOOGLE); - assertAcquireTokenB2C(user); - } - - // TODO uncomment when lab fixes facebook test account - //@Test - // TODO Redirect URI localhost in not registered - public void acquireTokenWithAuthorizationCode_B2C_Facebook() { -/* LabResponse labResponse = labUserProvider.getB2cUser( - B2CIdentityProvider.FACEBOOK, - false); - - - String b2CAppId = "b876a048-55a5-4fc5-9403-f5d90cb1c852"; - labResponse.setAppId(b2CAppId);*/ - User user = labUserProvider.getB2cUser(B2CProvider.FACEBOOK); - - assertAcquireTokenB2C(user); - } - private void assertAcquireTokenADFS2019(User user) { PublicClientApplication pca; try { @@ -142,10 +125,10 @@ private void assertAcquireTokenADFS2019(User user) { authCode, Collections.singleton(TestConstants.ADFS_SCOPE)); - Assert.assertNotNull(result); - Assert.assertNotNull(result.accessToken()); - Assert.assertNotNull(result.idToken()); - Assert.assertEquals(user.getUpn(), result.account().username()); + assertNotNull(result); + assertNotNull(result.accessToken()); + assertNotNull(result.idToken()); + assertEquals(user.getUpn(), result.account().username()); } private void assertAcquireTokenAAD(User user, Map> parameters) { @@ -171,10 +154,10 @@ private void assertAcquireTokenAAD(User user, Map> parameter authCode, Collections.singleton(cfg.graphDefaultScope())); - Assert.assertNotNull(result); - Assert.assertNotNull(result.accessToken()); - Assert.assertNotNull(result.idToken()); - Assert.assertEquals(user.getUpn(), result.account().username()); + assertNotNull(result); + assertNotNull(result.accessToken()); + assertNotNull(result.idToken()); + assertEquals(user.getUpn(), result.account().username()); } private void assertAcquireTokenB2C(User user) { @@ -196,9 +179,9 @@ private void assertAcquireTokenB2C(User user) { String authCode = acquireAuthorizationCodeAutomated(user, cca, null); IAuthenticationResult result = acquireTokenInteractiveB2C(cca, authCode); - Assert.assertNotNull(result); - Assert.assertNotNull(result.accessToken()); - Assert.assertNotNull(result.idToken()); + assertNotNull(result); + assertNotNull(result.accessToken()); + assertNotNull(result.idToken()); } private IAuthenticationResult acquireTokenAuthorizationCodeFlow( diff --git a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/AzureEnvironmentIT.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/AzureEnvironmentIT.java index a46c4dd0..b7d24a74 100644 --- a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/AzureEnvironmentIT.java +++ b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/AzureEnvironmentIT.java @@ -4,27 +4,29 @@ package com.microsoft.aad.msal4j; import labapi.*; -import org.testng.Assert; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.BeforeAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import java.util.Collections; - -public class AzureEnvironmentIT { +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class AzureEnvironmentIT { private LabUserProvider labUserProvider; - @BeforeClass - public void setUp() { + @BeforeAll + void setUp() { labUserProvider = LabUserProvider.getInstance(); } @Test - public void acquireTokenWithUsernamePassword_AzureChina() throws Exception { + void acquireTokenWithUsernamePassword_AzureChina() throws Exception { assertAcquireTokenCommon(AzureEnvironment.AZURE_CHINA); } @Test - public void acquireTokenWithUsernamePassword_AzureGovernment() throws Exception { + void acquireTokenWithUsernamePassword_AzureGovernment() throws Exception { assertAcquireTokenCommon(AzureEnvironment.AZURE_US_GOVERNMENT); } @@ -45,10 +47,10 @@ private void assertAcquireTokenCommon(String azureEnvironment) throws Exception .build()) .get(); - Assert.assertNotNull(result); - Assert.assertNotNull(result.accessToken()); - Assert.assertNotNull(result.idToken()); + assertNotNull(result); + assertNotNull(result.accessToken()); + assertNotNull(result.idToken()); - Assert.assertEquals(user.getUpn(), result.account().username()); + assertEquals(user.getUpn(), result.account().username()); } } diff --git a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/CachePersistenceIT.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/CachePersistenceIT.java index f943a946..d6dc51b5 100644 --- a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/CachePersistenceIT.java +++ b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/CachePersistenceIT.java @@ -4,14 +4,16 @@ import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.PlainJWT; -import org.testng.Assert; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; import java.net.URISyntaxException; import java.util.Collections; -public class CachePersistenceIT { +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class CachePersistenceIT { static class TokenPersistence implements ITokenCacheAccessAspect { String data; @@ -32,7 +34,7 @@ public void afterCacheAccess(ITokenCacheAccessContext iTokenCacheAccessContext) } @Test - public void cacheDeserializationSerializationTest() throws IOException, URISyntaxException { + void cacheDeserializationSerializationTest() throws IOException, URISyntaxException { String dataToInitCache = TestHelper.readResource(this.getClass(), "/cache_data/serialized_cache.json"); String ID_TOKEN_PLACEHOLDER = ""; @@ -50,41 +52,41 @@ public void cacheDeserializationSerializationTest() throws IOException, URISynta PublicClientApplication app = PublicClientApplication.builder("my_client_id") .setTokenCacheAccessAspect(persistenceAspect).build(); - Assert.assertEquals(app.getAccounts().join().size(), 1); - Assert.assertEquals(app.tokenCache.accounts.size(), 1); - Assert.assertEquals(app.tokenCache.accessTokens.size(), 2); - Assert.assertEquals(app.tokenCache.refreshTokens.size(), 1); - Assert.assertEquals(app.tokenCache.idTokens.size(), 1); - Assert.assertEquals(app.tokenCache.appMetadata.size(), 1); + assertEquals(app.getAccounts().join().size(), 1); + assertEquals(app.tokenCache.accounts.size(), 1); + assertEquals(app.tokenCache.accessTokens.size(), 2); + assertEquals(app.tokenCache.refreshTokens.size(), 1); + assertEquals(app.tokenCache.idTokens.size(), 1); + assertEquals(app.tokenCache.appMetadata.size(), 1); // create new instance of app to make sure in memory cache cleared app = PublicClientApplication.builder("my_client_id") .setTokenCacheAccessAspect(persistenceAspect).build(); - Assert.assertEquals(app.getAccounts().join().size(), 1); - Assert.assertEquals(app.tokenCache.accounts.size(), 1); - Assert.assertEquals(app.tokenCache.accessTokens.size(), 2); - Assert.assertEquals(app.tokenCache.refreshTokens.size(), 1); - Assert.assertEquals(app.tokenCache.idTokens.size(), 1); - Assert.assertEquals(app.tokenCache.appMetadata.size(), 1); + assertEquals(app.getAccounts().join().size(), 1); + assertEquals(app.tokenCache.accounts.size(), 1); + assertEquals(app.tokenCache.accessTokens.size(), 2); + assertEquals(app.tokenCache.refreshTokens.size(), 1); + assertEquals(app.tokenCache.idTokens.size(), 1); + assertEquals(app.tokenCache.appMetadata.size(), 1); app.removeAccount(app.getAccounts().join().iterator().next()).join(); - Assert.assertEquals(app.getAccounts().join().size(), 0); - Assert.assertEquals(app.tokenCache.accounts.size(), 0); - Assert.assertEquals(app.tokenCache.accessTokens.size(), 1); - Assert.assertEquals(app.tokenCache.refreshTokens.size(), 0); - Assert.assertEquals(app.tokenCache.idTokens.size(), 0); - Assert.assertEquals(app.tokenCache.appMetadata.size(), 1); + assertEquals(app.getAccounts().join().size(), 0); + assertEquals(app.tokenCache.accounts.size(), 0); + assertEquals(app.tokenCache.accessTokens.size(), 1); + assertEquals(app.tokenCache.refreshTokens.size(), 0); + assertEquals(app.tokenCache.idTokens.size(), 0); + assertEquals(app.tokenCache.appMetadata.size(), 1); app = PublicClientApplication.builder("my_client_id") .setTokenCacheAccessAspect(persistenceAspect).build(); - Assert.assertEquals(app.getAccounts().join().size(), 0); - Assert.assertEquals(app.tokenCache.accounts.size(), 0); - Assert.assertEquals(app.tokenCache.accessTokens.size(), 1); - Assert.assertEquals(app.tokenCache.refreshTokens.size(), 0); - Assert.assertEquals(app.tokenCache.idTokens.size(), 0); - Assert.assertEquals(app.tokenCache.appMetadata.size(), 1); + assertEquals(app.getAccounts().join().size(), 0); + assertEquals(app.tokenCache.accounts.size(), 0); + assertEquals(app.tokenCache.accessTokens.size(), 1); + assertEquals(app.tokenCache.refreshTokens.size(), 0); + assertEquals(app.tokenCache.idTokens.size(), 0); + assertEquals(app.tokenCache.appMetadata.size(), 1); } } diff --git a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/ClientCredentialsIT.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/ClientCredentialsIT.java index e5c5d157..de536eed 100644 --- a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/ClientCredentialsIT.java +++ b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/ClientCredentialsIT.java @@ -7,10 +7,12 @@ import labapi.AzureEnvironment; import labapi.LabUserProvider; import labapi.User; -import org.testng.Assert; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.BeforeAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import java.io.IOException; import java.security.KeyStoreException; @@ -25,25 +27,25 @@ import static com.microsoft.aad.msal4j.TestConstants.KEYVAULT_DEFAULT_SCOPE; -@Test -public class ClientCredentialsIT { +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class ClientCredentialsIT { private IClientCertificate certificate; private LabUserProvider labUserProvider; - @BeforeClass + @BeforeAll void init() throws CertificateException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, NoSuchProviderException, IOException { certificate = CertificateHelper.getClientCertificate(); labUserProvider = LabUserProvider.getInstance(); } @Test - public void acquireTokenClientCredentials_ClientCertificate() throws Exception { + void acquireTokenClientCredentials_ClientCertificate() throws Exception { String clientId = "2afb0add-2f32-4946-ac90-81a02aa4550e"; assertAcquireTokenCommon(clientId, certificate, TestConstants.MICROSOFT_AUTHORITY); } @Test - public void acquireTokenClientCredentials_ClientSecret() throws Exception { + void acquireTokenClientCredentials_ClientSecret() throws Exception { AppCredentialProvider appProvider = new AppCredentialProvider(AzureEnvironment.AZURE); final String clientId = appProvider.getLabVaultAppId(); final String password = appProvider.getLabVaultPassword(); @@ -53,7 +55,7 @@ public void acquireTokenClientCredentials_ClientSecret() throws Exception { } @Test - public void acquireTokenClientCredentials_ClientAssertion() throws Exception { + void acquireTokenClientCredentials_ClientAssertion() throws Exception { String clientId = "2afb0add-2f32-4946-ac90-81a02aa4550e"; ClientAssertion clientAssertion = getClientAssertion(clientId); @@ -64,14 +66,11 @@ public void acquireTokenClientCredentials_ClientAssertion() throws Exception { } @Test - public void acquireTokenClientCredentials_ClientSecret_Ciam() throws Exception { + void acquireTokenClientCredentials_ClientSecret_Ciam() throws Exception { User user = labUserProvider.getCiamUser(); String clientId = user.getAppId(); - Map extraQueryParameters = new HashMap<>(); - extraQueryParameters.put("dc","ESTS-PUB-EUS-AZ1-FD000-TEST1"); - AppCredentialProvider appProvider = new AppCredentialProvider(AzureEnvironment.CIAM); IClientCredential credential = ClientCredentialFactory.createFromSecret(appProvider.getOboAppPassword()); @@ -81,18 +80,16 @@ public void acquireTokenClientCredentials_ClientSecret_Ciam() throws Exception { build(); IAuthenticationResult result = cca.acquireToken(ClientCredentialParameters - .builder(Collections.singleton(TestConstants.GRAPH_DEFAULT_SCOPE)) - .extraQueryParameters(extraQueryParameters) + .builder(Collections.singleton(TestConstants.DEFAULT_SCOPE)) .build()) .get(); - Assert.assertNotNull(result); - Assert.assertNotNull(result.accessToken()); - assertAcquireTokenCommon(clientId, credential, TestConstants.CIAM_AUTHORITY); + assertNotNull(result); + assertNotNull(result.accessToken()); } @Test - public void acquireTokenClientCredentials_Callback() throws Exception { + void acquireTokenClientCredentials_Callback() throws Exception { String clientId = "2afb0add-2f32-4946-ac90-81a02aa4550e"; // Creates a valid client assertion using a callback, and uses it to build the client app and make a request @@ -116,7 +113,7 @@ public void acquireTokenClientCredentials_Callback() throws Exception { } @Test - public void acquireTokenClientCredentials_DefaultCacheLookup() throws Exception { + void acquireTokenClientCredentials_DefaultCacheLookup() throws Exception { AppCredentialProvider appProvider = new AppCredentialProvider(AzureEnvironment.AZURE); final String clientId = appProvider.getLabVaultAppId(); final String password = appProvider.getLabVaultPassword(); @@ -132,15 +129,15 @@ public void acquireTokenClientCredentials_DefaultCacheLookup() throws Exception .build()) .get(); - Assert.assertNotNull(result1); - Assert.assertNotNull(result1.accessToken()); + assertNotNull(result1); + assertNotNull(result1.accessToken()); IAuthenticationResult result2 = cca.acquireToken(ClientCredentialParameters .builder(Collections.singleton(KEYVAULT_DEFAULT_SCOPE)) .build()) .get(); - Assert.assertEquals(result1.accessToken(), result2.accessToken()); + assertEquals(result1.accessToken(), result2.accessToken()); IAuthenticationResult result3 = cca.acquireToken(ClientCredentialParameters .builder(Collections.singleton(KEYVAULT_DEFAULT_SCOPE)) @@ -148,23 +145,18 @@ public void acquireTokenClientCredentials_DefaultCacheLookup() throws Exception .build()) .get(); - Assert.assertNotNull(result3); - Assert.assertNotNull(result3.accessToken()); - Assert.assertNotEquals(result2.accessToken(), result3.accessToken()); - } - - @DataProvider(name = "regionWithAuthority") - public static Object[][] createData() { - return new Object[][]{{"westus", TestConstants.REGIONAL_MICROSOFT_AUTHORITY_BASIC_HOST_WESTUS}, - {"eastus", TestConstants.REGIONAL_MICROSOFT_AUTHORITY_BASIC_HOST_EASTUS}}; + assertNotNull(result3); + assertNotNull(result3.accessToken()); + assertNotEquals(result2.accessToken(), result3.accessToken()); } - @Test(dataProvider = "regionWithAuthority") - public void acquireTokenClientCredentials_Regional(String[] regionWithAuthority) throws Exception { + @Test + void acquireTokenClientCredentials_Regional() throws Exception { String clientId = "2afb0add-2f32-4946-ac90-81a02aa4550e"; - assertAcquireTokenCommon_withRegion(clientId, certificate, regionWithAuthority[0], regionWithAuthority[1]); + assertAcquireTokenCommon_withRegion(clientId, certificate, "westus", TestConstants.REGIONAL_MICROSOFT_AUTHORITY_BASIC_HOST_WESTUS); } + private ClientAssertion getClientAssertion(String clientId) { return JwtHelper.buildJwt( clientId, @@ -184,8 +176,8 @@ private void assertAcquireTokenCommon(String clientId, IClientCredential credent .build()) .get(); - Assert.assertNotNull(result); - Assert.assertNotNull(result.accessToken()); + assertNotNull(result); + assertNotNull(result.accessToken()); } private void assertAcquireTokenCommon_withParameters(String clientId, IClientCredential credential, IClientCredential credentialParam) throws Exception { @@ -200,8 +192,8 @@ private void assertAcquireTokenCommon_withParameters(String clientId, IClientCre .build()) .get(); - Assert.assertNotNull(result); - Assert.assertNotNull(result.accessToken()); + assertNotNull(result); + assertNotNull(result.accessToken()); } private void assertAcquireTokenCommon_withRegion(String clientId, IClientCredential credential, String region, String regionalAuthority) throws Exception { @@ -221,9 +213,9 @@ private void assertAcquireTokenCommon_withRegion(String clientId, IClientCredent .build()) .get(); - Assert.assertNotNull(resultNoRegion); - Assert.assertNotNull(resultNoRegion.accessToken()); - Assert.assertEquals(resultNoRegion.environment(), TestConstants.MICROSOFT_AUTHORITY_BASIC_HOST); + assertNotNull(resultNoRegion); + assertNotNull(resultNoRegion.accessToken()); + assertEquals(TestConstants.MICROSOFT_AUTHORITY_BASIC_HOST, resultNoRegion.environment()); //Ensure regional tokens are properly cached and retrievable IAuthenticationResult resultRegion = ccaRegion.acquireToken(ClientCredentialParameters @@ -231,18 +223,18 @@ private void assertAcquireTokenCommon_withRegion(String clientId, IClientCredent .build()) .get(); - Assert.assertNotNull(resultRegion); - Assert.assertNotNull(resultRegion.accessToken()); - Assert.assertEquals(resultRegion.environment(), regionalAuthority); + assertNotNull(resultRegion); + assertNotNull(resultRegion.accessToken()); + assertEquals(resultRegion.environment(), regionalAuthority); IAuthenticationResult resultRegionCached = ccaRegion.acquireToken(ClientCredentialParameters .builder(Collections.singleton(KEYVAULT_DEFAULT_SCOPE)) .build()) .get(); - Assert.assertNotNull(resultRegionCached); - Assert.assertNotNull(resultRegionCached.accessToken()); - Assert.assertEquals(resultRegionCached.accessToken(), resultRegion.accessToken()); + assertNotNull(resultRegionCached); + assertNotNull(resultRegionCached.accessToken()); + assertEquals(resultRegionCached.accessToken(), resultRegion.accessToken()); //Tokens retrieved from regional endpoints should be interchangeable with non-regional, and vice-versa //For example, if an application doesn't configure a region but gets regional tokens added to its cache, they should be retrievable @@ -252,8 +244,8 @@ private void assertAcquireTokenCommon_withRegion(String clientId, IClientCredent .build()) .get(); - Assert.assertNotNull(resultNoRegion); - Assert.assertNotNull(resultNoRegion.accessToken()); - Assert.assertEquals(resultNoRegion.accessToken(), resultRegion.accessToken()); + assertNotNull(resultNoRegion); + assertNotNull(resultNoRegion.accessToken()); + assertEquals(resultNoRegion.accessToken(), resultRegion.accessToken()); } } 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 deleted file mode 100644 index 5624c60c..00000000 --- a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/ConfidentialClientApplicationUnitT.java +++ /dev/null @@ -1,421 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.aad.msal4j; - -import com.nimbusds.jose.JOSEException; -import com.nimbusds.jose.JWSAlgorithm; -import com.nimbusds.jose.JWSHeader; -import com.nimbusds.jose.crypto.RSASSASigner; -import com.nimbusds.jose.util.Base64; -import com.nimbusds.jose.util.Base64URL; -import com.nimbusds.jwt.JWTClaimsSet; -import com.nimbusds.jwt.SignedJWT; -import com.nimbusds.oauth2.sdk.auth.JWTAuthentication; -import com.nimbusds.oauth2.sdk.auth.PrivateKeyJWT; -import org.easymock.Capture; -import org.easymock.EasyMock; -import org.powermock.api.easymock.PowerMock; -import org.powermock.core.classloader.annotations.PowerMockIgnore; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.testng.PowerMockTestCase; -import org.testng.Assert; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -import java.io.IOException; -import java.net.URI; -import java.net.URLEncoder; -import java.security.*; -import java.security.cert.CertificateException; -import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Future; - -import static com.microsoft.aad.msal4j.TestConstants.KEYVAULT_DEFAULT_SCOPE; -import static org.easymock.EasyMock.*; -import static org.testng.Assert.*; - -@PowerMockIgnore({"javax.net.ssl.*"}) -@PrepareForTest({ConfidentialClientApplication.class, - ClientCertificate.class, UserDiscoveryRequest.class, JwtHelper.class}) -public class ConfidentialClientApplicationUnitT extends PowerMockTestCase { - - private ConfidentialClientApplication app = null; - private IClientCertificate clientCertificate; - - @BeforeClass - private void init() throws - KeyStoreException, IOException, NoSuchAlgorithmException, - CertificateException, UnrecoverableKeyException, NoSuchProviderException { - - clientCertificate = CertificateHelper.getClientCertificate(); - } - - @Test - public void testAcquireTokenAuthCode_ClientCredential() throws Exception { - app = PowerMock.createPartialMock(ConfidentialClientApplication.class, - new String[]{"acquireTokenCommon"}, - ConfidentialClientApplication.builder(TestConfiguration.AAD_CLIENT_ID, - ClientCredentialFactory.createFromSecret(TestConfiguration.AAD_CLIENT_DUMMYSECRET)) - .authority(TestConfiguration.AAD_TENANT_ENDPOINT) - ); - - PowerMock.expectPrivate(app, "acquireTokenCommon", - EasyMock.isA(MsalRequest.class), - EasyMock.isA(AADAuthority.class)).andReturn( - AuthenticationResult.builder(). - accessToken("accessToken"). - expiresOn(new Date().getTime() + 100). - refreshToken("refreshToken"). - idToken("idToken").environment("environment").build()); - - PowerMock.replay(app); - - AuthorizationCodeParameters parameters = AuthorizationCodeParameters.builder - ("auth_code", - new URI(TestConfiguration.AAD_DEFAULT_REDIRECT_URI)) - .scopes(Collections.singleton("default-scope")) - .build(); - - Future result = app.acquireToken(parameters); - - IAuthenticationResult ar = result.get(); - Assert.assertNotNull(ar); - PowerMock.verifyAll(); - } - - @Test - public void testAcquireTokenAuthCode_KeyCredential() throws Exception { - app = PowerMock.createPartialMock(ConfidentialClientApplication.class, - new String[]{"acquireTokenCommon"}, - ConfidentialClientApplication.builder(TestConfiguration.AAD_CLIENT_ID, clientCertificate) - .authority(TestConfiguration.AAD_TENANT_ENDPOINT)); - - PowerMock.expectPrivate(app, "acquireTokenCommon", - EasyMock.isA(MsalRequest.class), - EasyMock.isA(AADAuthority.class)).andReturn( - AuthenticationResult.builder(). - accessToken("accessToken"). - expiresOn(new Date().getTime() + 100). - refreshToken("refreshToken"). - idToken("idToken").environment("environment").build()); - - PowerMock.replay(app); - - AuthorizationCodeParameters parameters = AuthorizationCodeParameters.builder - ("auth_code", - new URI(TestConfiguration.AAD_DEFAULT_REDIRECT_URI)) - .scopes(Collections.singleton("default-scope")) - .build(); - - Future result = app.acquireToken(parameters); - - IAuthenticationResult ar = result.get(); - Assert.assertNotNull(ar); - PowerMock.verifyAll(); - PowerMock.resetAll(app); - } - - @Test - public void testAcquireToken_KeyCred() throws Exception { - app = PowerMock.createPartialMock(ConfidentialClientApplication.class, - new String[]{"acquireTokenCommon"}, - ConfidentialClientApplication.builder(TestConfiguration.AAD_CLIENT_ID, clientCertificate) - .authority(TestConfiguration.AAD_TENANT_ENDPOINT)); - - PowerMock.expectPrivate(app, "acquireTokenCommon", - EasyMock.isA(MsalRequest.class), - EasyMock.isA(AADAuthority.class)).andReturn( - AuthenticationResult.builder(). - accessToken("accessToken"). - expiresOn(new Date().getTime() + 100). - refreshToken("refreshToken"). - idToken("idToken").environment("environment").build()); - - PowerMock.replay(app); - - ClientCredentialParameters parameters = ClientCredentialParameters.builder( - Collections.singleton(TestConfiguration.AAD_RESOURCE_ID)) - .build(); - - Future result = app.acquireToken(parameters); - - IAuthenticationResult ar = result.get(); - assertNotNull(ar); - assertFalse(StringHelper.isBlank(result.get().accessToken())); - PowerMock.verifyAll(); - PowerMock.resetAll(app); - } - - @Test - public void testClientCertificateRebuildsWhenExpired() throws Exception { - PowerMock.mockStaticPartial(JwtHelper.class, "buildJwt"); - long jwtExperiationPeriodMilli = 2000; - ClientAssertion shortExperationJwt = buildShortJwt(TestConfiguration.AAD_CLIENT_ID, - clientCertificate, - TestConfiguration.AAD_TENANT_ENDPOINT, - jwtExperiationPeriodMilli); - - PowerMock.expectPrivate( - JwtHelper.class, - "buildJwt", - EasyMock.isA(String.class), - EasyMock.isA(ClientCertificate.class), - EasyMock.isA(String.class), - EasyMock.anyBoolean()) - .andReturn(shortExperationJwt) - .times(2); // By this being called twice we ensure the client assertion is rebuilt once it has expired - - PowerMock.replay(JwtHelper.class); - app = ConfidentialClientApplication.builder(TestConfiguration.AAD_CLIENT_ID, clientCertificate) - .authority(TestConfiguration.AAD_TENANT_ENDPOINT).build(); - Thread.sleep(jwtExperiationPeriodMilli + 1000); //Have to sleep to ensure that the time period has passed - final PrivateKeyJWT clientAuthentication = (PrivateKeyJWT) app.clientAuthentication(); - assertNotNull(clientAuthentication); - PowerMock.verifyAll(); - } - - private ClientAssertion buildShortJwt(String clientId, - IClientCertificate credential, - String jwtAudience, - long jwtExperiationPeriod) { - final long time = System.currentTimeMillis(); - final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder() - .audience(Collections.singletonList(jwtAudience)) - .issuer(clientId) - .jwtID(UUID.randomUUID().toString()) - .notBeforeTime(new Date(time)) - .expirationTime(new Date(time + jwtExperiationPeriod)) - .subject(clientId) - .build(); - SignedJWT jwt; - try { - JWSHeader.Builder builder = new JWSHeader.Builder(JWSAlgorithm.RS256); - - List certs = new ArrayList<>(); - for (String cert : credential.getEncodedPublicKeyCertificateChain()) { - certs.add(new Base64(cert)); - } - builder.x509CertChain(certs); - - builder.x509CertThumbprint(new Base64URL(credential.publicCertificateHash())); - - jwt = new SignedJWT(builder.build(), claimsSet); - final RSASSASigner signer = new RSASSASigner(credential.privateKey()); - jwt.sign(signer); - } catch (final Exception e) { - throw new MsalClientException(e); - } - return new ClientAssertion(jwt.serialize()); - } - - @Test - public void testClientAssertion_noException() throws Exception{ - SignedJWT jwt = createClientAssertion("issuer"); - - ClientAssertion clientAssertion = new ClientAssertion(jwt.serialize()); - - IClientCredential iClientCredential = ClientCredentialFactory.createFromClientAssertion( - clientAssertion.assertion()); - - ConfidentialClientApplication app = ConfidentialClientApplication - .builder(TestConfiguration.AAD_CLIENT_ID, iClientCredential) - .authority(TestConfiguration.AAD_TENANT_ENDPOINT) - .build(); - - Assert.assertEquals(app.clientId(),TestConfiguration.AAD_CLIENT_ID); - Assert.assertTrue(app.sendX5c()); - - } - - @Test - public void testClientAssertion_acquireToken() throws Exception{ - SignedJWT jwt = createClientAssertion("issuer"); - - ClientAssertion clientAssertion = new ClientAssertion(jwt.serialize()); - ConfidentialClientApplication app = ConfidentialClientApplication - .builder(TestConfiguration.AAD_CLIENT_ID, ClientCredentialFactory.createFromClientAssertion(clientAssertion.assertion())) - .authority(TestConfiguration.AAD_TENANT_ENDPOINT) - .build(); - - String scope = "requestedScope"; - ClientCredentialRequest clientCredentialRequest = getClientCredentialRequest(app, scope); - - IHttpClient httpClientMock = EasyMock.mock(IHttpClient.class); - Capture captureSingleArgument = newCapture(); - expect(httpClientMock.send(capture(captureSingleArgument))).andReturn(new HttpResponse()); - EasyMock.replay(httpClientMock); - - TokenRequestExecutor tokenRequestExecutor = new TokenRequestExecutor(app.authenticationAuthority, clientCredentialRequest, mockedServiceBundle(httpClientMock)); - try { - tokenRequestExecutor.executeTokenRequest(); - } catch(Exception e) { - //Ignored, we only want to check the request that was send. - } - HttpRequest value = captureSingleArgument.getValue(); - String body = value.body(); - Assert.assertTrue(body.contains("grant_type=client_credentials")); - Assert.assertTrue(body.contains("client_assertion=" + clientAssertion.assertion())); - 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) { - ServiceBundle serviceBundle = new ServiceBundle( - null, - httpClientMock, - new TelemetryManager(null, false)); - return serviceBundle; - } - - private ClientCredentialRequest getClientCredentialRequest(ConfidentialClientApplication app, String scope) { - Set scopes = new HashSet<>(); - scopes.add(scope); - - 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, - clientCredentials); - - return new ClientCredentialRequest( - clientCredentials, - app, - requestContext); - } - - @Test(expectedExceptions = MsalClientException.class) - public void testClientAssertion_throwsException() throws Exception{ - SignedJWT jwt = createClientAssertion(null); - - ClientAssertion clientAssertion = new ClientAssertion(jwt.serialize()); - - IClientCredential iClientCredential = ClientCredentialFactory.createFromClientAssertion( - clientAssertion.assertion()); - - ConfidentialClientApplication.builder(TestConfiguration.AAD_CLIENT_ID, iClientCredential).authority(TestConfiguration.AAD_TENANT_ENDPOINT).build(); - - } - - @Test - public void validateAppTokenProviderAsync() throws Exception{ - - SignedJWT jwt = createClientAssertion("issuer"); - - ClientAssertion clientAssertion = new ClientAssertion(jwt.serialize()); - - IClientCredential iClientCredential = ClientCredentialFactory.createFromClientAssertion( - clientAssertion.assertion()); - - Long refreshInSeconds = new Date().getTime() / 1000 + + 800000; - //builds client with AppTokenProvider - ConfidentialClientApplication cca = ConfidentialClientApplication. - builder(TestConfiguration.AAD_CLIENT_ID, iClientCredential) - .appTokenProvider((parameters) -> { - Assert.assertNotNull(parameters.scopes); - Assert.assertNotNull(parameters.correlationId); - Assert.assertNotNull(parameters.tenantId); - return getAppTokenProviderResult("/default", refreshInSeconds); - }) - .build(); - - IAuthenticationResult result1 = cca.acquireToken(ClientCredentialParameters - .builder(Collections.singleton(KEYVAULT_DEFAULT_SCOPE)) - .tenant("tenant1") - .build()) - .get(); - - Assert.assertNotNull(result1.accessToken()); - - Assert.assertEquals(cca.tokenCache.accessTokens.size(), 1); - //check that refreshOn is set correctly when provided by an app developer - Assert.assertNotNull(cca.tokenCache.accessTokens.values().iterator().next().refreshOn()); - Assert.assertEquals(cca.tokenCache.accessTokens.values().iterator().next().refreshOn(), refreshInSeconds.toString()); - System.out.println(cca.tokenCache.accessTokens.values().iterator().next().refreshOn()); - - //Acquire token from cache - - IAuthenticationResult result2 = cca.acquireToken(ClientCredentialParameters - .builder(Collections.singleton(KEYVAULT_DEFAULT_SCOPE)) - .build()) - .get(); - - Assert.assertEquals(result1.accessToken(), result2.accessToken()); - - Assert.assertEquals(cca.tokenCache.accessTokens.size(), 1); - - cca = ConfidentialClientApplication. - builder(TestConfiguration.AAD_CLIENT_ID, iClientCredential) - .appTokenProvider((parameters) -> { - Assert.assertNotNull(parameters.scopes); - Assert.assertNotNull(parameters.correlationId); - Assert.assertNotNull(parameters.tenantId); - return getAppTokenProviderResult("/newScope", 0L); - }) - .build(); - - IAuthenticationResult result3 = cca.acquireToken(ClientCredentialParameters - .builder(Collections.singleton("/newScope")) - .tenant("tenant1") -// .claims(new ClaimsRequest().formatAsClaimsRequest(TestConstants.CLAIMS)) - .build()) - .get(); - - Assert.assertNotEquals(result2.accessToken(), result3.accessToken()); - Assert.assertEquals(cca.tokenCache.accessTokens.size(), 1); - //check that refreshOn is set correctly when a value is not provided by an app developer - Assert.assertNotNull(cca.tokenCache.accessTokens.values().iterator().next().refreshOn()); - System.out.println(cca.tokenCache.accessTokens.values().iterator().next().refreshOn()); - } - - private CompletableFuture getAppTokenProviderResult(String differentScopesForAt, - long refreshInSeconds) - { - long currTimestampSec = new Date().getTime() / 1000; - TokenProviderResult token = new TokenProviderResult(); - token.setAccessToken(TestConstants.DEFAULT_ACCESS_TOKEN + differentScopesForAt); //Used to indicate that there is a new access token for a different set of scopes - token.setTenantId("tenantId"); - token.setExpiresInSeconds(currTimestampSec + 1000000); - token.setRefreshInSeconds(refreshInSeconds); - - return CompletableFuture.completedFuture(token); - } - - private SignedJWT createClientAssertion(String issuer) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException, NoSuchProviderException, JOSEException { - IClientCertificate certificate = CertificateHelper.getClientCertificate(); - - final ClientCertificate credential = (ClientCertificate) certificate; - - final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder() - .issuer(issuer) - .subject("subject") - .build(); - - SignedJWT jwt; - JWSHeader.Builder builder = new JWSHeader.Builder(JWSAlgorithm.RS256); - - List certs = new ArrayList<>(); - for (String cert : credential.getEncodedPublicKeyCertificateChain()) { - certs.add(new Base64(cert)); - } - builder.x509CertChain(certs); - - jwt = new SignedJWT(builder.build(), claimsSet); - final RSASSASigner signer = new RSASSASigner(credential.privateKey()); - - jwt.sign(signer); - return jwt; - } - -} diff --git a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/DeviceCodeIT.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/DeviceCodeIT.java index 4b1d10d1..edd3c8a4 100644 --- a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/DeviceCodeIT.java +++ b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/DeviceCodeIT.java @@ -10,30 +10,33 @@ import org.openqa.selenium.WebElement; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.testng.Assert; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; -import org.testng.util.Strings; - +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.AfterAll; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import java.util.Collections; import java.util.function.Consumer; -@Test -public class DeviceCodeIT { +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class DeviceCodeIT { private final static Logger LOG = LoggerFactory.getLogger(DeviceCodeIT.class); private LabUserProvider labUserProvider; private WebDriver seleniumDriver; - @BeforeClass - public void setUp() { + @BeforeAll + void setUp() { labUserProvider = LabUserProvider.getInstance(); seleniumDriver = SeleniumExtensions.createDefaultWebDriver(); } - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) - public void DeviceCodeFlowADTest(String environment) throws Exception { + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") + void DeviceCodeFlowADTest(String environment) throws Exception { Config cfg = new Config(environment); User user = labUserProvider.getDefaultUser(cfg.azureEnvironment); @@ -51,12 +54,12 @@ public void DeviceCodeFlowADTest(String environment) throws Exception { .build()) .get(); - Assert.assertNotNull(result); - Assert.assertFalse(Strings.isNullOrEmpty(result.accessToken())); + assertNotNull(result); + assertNotNull(result.accessToken()); } @Test() - public void DeviceCodeFlowADFSv2019Test() throws Exception { + void DeviceCodeFlowADFSv2019Test() throws Exception { User user = labUserProvider.getOnPremAdfsUser(FederationProvider.ADFS_2019); @@ -75,12 +78,12 @@ public void DeviceCodeFlowADFSv2019Test() throws Exception { .build()) .get(); - Assert.assertNotNull(result); - Assert.assertFalse(Strings.isNullOrEmpty(result.accessToken())); + assertNotNull(result); + assertNotNull(result.accessToken()); } @Test() - public void DeviceCodeFlowMSATest() throws Exception { + void DeviceCodeFlowMSATest() throws Exception { User user = labUserProvider.getMSAUser(); @@ -99,39 +102,16 @@ public void DeviceCodeFlowMSATest() throws Exception { .build()) .get(); - Assert.assertNotNull(result); - Assert.assertFalse(Strings.isNullOrEmpty(result.accessToken())); + assertNotNull(result); + assertNotNull(result.accessToken()); result = pca.acquireTokenSilently(SilentParameters. builder(Collections.singleton(""), result.account()). build()) .get(); - Assert.assertNotNull(result); - Assert.assertFalse(Strings.isNullOrEmpty(result.accessToken())); - } - - @Test - public void DeviceCodeFlowCiamTest() throws Exception { - User user = labUserProvider.getCiamUser(); - - PublicClientApplication pca = PublicClientApplication.builder( - user.getAppId()). - authority("https://" + user.getLabName() + ".ciamlogin.com/"). - build(); - - Consumer deviceCodeConsumer = (DeviceCode deviceCode) -> { - runAutomatedDeviceCodeFlow(deviceCode, user); - }; - - IAuthenticationResult result = pca.acquireToken(DeviceCodeFlowParameters - .builder(Collections.singleton(""), - deviceCodeConsumer) - .build()) - .get(); - - Assert.assertNotNull(result); - Assert.assertFalse(Strings.isNullOrEmpty(result.accessToken())); + assertNotNull(result); + assertNotNull(result.accessToken()); } private void runAutomatedDeviceCodeFlow(DeviceCode deviceCode, User user) { @@ -183,8 +163,8 @@ private void runAutomatedDeviceCodeFlow(DeviceCode deviceCode, User user) { } } - @AfterClass - public void cleanUp() { + @AfterAll + void cleanUp() { if (seleniumDriver != null) { seleniumDriver.close(); } diff --git a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/EnvironmentsProvider.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/EnvironmentsProvider.java index 22b40ff2..f27c69f4 100644 --- a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/EnvironmentsProvider.java +++ b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/EnvironmentsProvider.java @@ -4,10 +4,8 @@ package com.microsoft.aad.msal4j; import labapi.AzureEnvironment; -import org.testng.annotations.DataProvider; public class EnvironmentsProvider { - @DataProvider(name = "environments") public static Object[][] createData() { return new Object[][]{ {AzureEnvironment.AZURE}, diff --git a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/HttpClientIT.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/HttpClientIT.java index 3bbbf4f7..66f55986 100644 --- a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/HttpClientIT.java +++ b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/HttpClientIT.java @@ -5,32 +5,46 @@ import labapi.LabUserProvider; import labapi.User; -import org.testng.Assert; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.BeforeAll; import java.util.Collections; +import java.util.concurrent.ExecutionException; -public class HttpClientIT { +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class HttpClientIT { private LabUserProvider labUserProvider; - @BeforeClass - public void setUp() { + @BeforeAll + void setUp() { labUserProvider = LabUserProvider.getInstance(); } @Test - public void acquireToken_okHttpClient() throws Exception { + void acquireToken_okHttpClient() throws Exception { User user = labUserProvider.getDefaultUser(); assertAcquireTokenCommon(user, new OkHttpClientAdapter()); } @Test - public void acquireToken_apacheHttpClient() throws Exception { + void acquireToken_apacheHttpClient() throws Exception { User user = labUserProvider.getDefaultUser(); assertAcquireTokenCommon(user, new ApacheHttpClientAdapter()); } + @Test + void acquireToken_readTimeout() throws Exception { + User user = labUserProvider.getDefaultUser(); + + //Set a 1ms read timeout, which will almost certainly occur before the service can respond + assertAcquireTokenCommon_WithTimeout(user, 1); + } + private void assertAcquireTokenCommon(User user, IHttpClient httpClient) throws Exception { PublicClientApplication pca = PublicClientApplication.builder( @@ -46,9 +60,27 @@ private void assertAcquireTokenCommon(User user, IHttpClient httpClient) .build()) .get(); - Assert.assertNotNull(result); - Assert.assertNotNull(result.accessToken()); - Assert.assertNotNull(result.idToken()); - Assert.assertEquals(user.getUpn(), result.account().username()); + assertNotNull(result); + assertNotNull(result.accessToken()); + assertNotNull(result.idToken()); + assertEquals(user.getUpn(), result.account().username()); + } + + private void assertAcquireTokenCommon_WithTimeout(User user, int readTimeout) + throws Exception { + PublicClientApplication pca = PublicClientApplication.builder( + user.getAppId()). + authority(TestConstants.ORGANIZATIONS_AUTHORITY). + readTimeoutForDefaultHttpClient(readTimeout). + build(); + + ExecutionException ex = assertThrows(ExecutionException.class, () -> pca.acquireToken(UserNamePasswordParameters. + builder(Collections.singleton(TestConstants.GRAPH_DEFAULT_SCOPE), + user.getUpn(), + user.getPassword().toCharArray()) + .build()) + .get()); + + assertEquals("com.microsoft.aad.msal4j.MsalClientException: java.net.SocketTimeoutException: Read timed out", ex.getMessage()); } } diff --git a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/InstanceDiscoveryTest.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/InstanceDiscoveryTest.java deleted file mode 100644 index 57bc8fb3..00000000 --- a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/InstanceDiscoveryTest.java +++ /dev/null @@ -1,235 +0,0 @@ -package com.microsoft.aad.msal4j; - -import org.easymock.Capture; -import org.easymock.EasyMock; -import org.powermock.api.easymock.PowerMock; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.testng.Assert; -import org.testng.IObjectFactory; -import org.testng.annotations.DataProvider; -import org.testng.annotations.ObjectFactory; -import org.testng.annotations.Test; - -import java.net.URI; -import java.util.Collections; -import java.util.Date; -import java.util.concurrent.CompletableFuture; - -@PrepareForTest({HttpHelper.class, PublicClientApplication.class}) -public class InstanceDiscoveryTest { - - private PublicClientApplication app; - - @ObjectFactory - public IObjectFactory getObjectFactory() { - return new org.powermock.modules.testng.PowerMockObjectFactory(); - } - - @DataProvider(name = "aadClouds") - private static Object[][] getAadClouds(){ - return new Object[][] {{"https://login.microsoftonline.com/common"} , // #Known to Microsoft - {"https://private.cloud/foo"}//Private Cloud - }; - } - - /** - * when instance_discovery flag is set to true (by default), an instance_discovery is performed for authorityType = AAD - */ - @Test( dataProvider = "aadClouds") - public void aadInstanceDiscoveryTrue(String authority) throws Exception{ - app = PowerMock.createPartialMock(PublicClientApplication.class, - new String[]{"acquireTokenCommon"}, - PublicClientApplication.builder(TestConfiguration.AAD_CLIENT_ID) - .authority(authority)); - - Capture capturedMsalRequest = Capture.newInstance(); - - PowerMock.expectPrivate(app, "acquireTokenCommon", - EasyMock.capture(capturedMsalRequest), EasyMock.isA(AADAuthority.class)).andReturn( - AuthenticationResult.builder(). - accessToken("accessToken"). - expiresOn(new Date().getTime() + 100). - refreshToken("refreshToken"). - idToken("idToken").environment("environment").build()); - - PowerMock.mockStatic(HttpHelper.class); - - HttpResponse instanceDiscoveryResponse = new HttpResponse(); - instanceDiscoveryResponse.statusCode(200); - instanceDiscoveryResponse.body(TestConfiguration.INSTANCE_DISCOVERY_RESPONSE); - - Capture capturedHttpRequest = Capture.newInstance(); - - EasyMock.expect( - HttpHelper.executeHttpRequest( - EasyMock.capture(capturedHttpRequest), - EasyMock.isA(RequestContext.class), - EasyMock.isA(ServiceBundle.class))) - .andReturn(instanceDiscoveryResponse); - - PowerMock.replay(HttpHelper.class, HttpResponse.class); - - CompletableFuture completableFuture = app.acquireToken( - AuthorizationCodeParameters.builder - ("auth_code", - new URI(TestConfiguration.AAD_DEFAULT_REDIRECT_URI)) - .scopes(Collections.singleton("default-scope")) - .build()); - - completableFuture.get(); - Assert.assertEquals(capturedHttpRequest.getValues().size(),1); - - } - - /** - * when instance_discovery flag is set to false, instance_discovery is not performed - */ - @Test (dataProvider = "aadClouds") - public void aadInstanceDiscoveryFalse(String authority) throws Exception { - - app = PowerMock.createPartialMock(PublicClientApplication.class, - new String[]{"acquireTokenCommon"}, - PublicClientApplication.builder(TestConfiguration.AAD_CLIENT_ID) - .authority(authority) - .instanceDiscovery(false)); - - Capture capturedMsalRequest = Capture.newInstance(); - - PowerMock.expectPrivate(app, "acquireTokenCommon", - EasyMock.capture(capturedMsalRequest), EasyMock.isA(AADAuthority.class)).andReturn( - AuthenticationResult.builder(). - accessToken("accessToken"). - expiresOn(new Date().getTime() + 100). - refreshToken("refreshToken"). - idToken("idToken").environment("environment").build()); - - PowerMock.mockStatic(HttpHelper.class); - - HttpResponse instanceDiscoveryResponse = new HttpResponse(); - instanceDiscoveryResponse.statusCode(200); - instanceDiscoveryResponse.body(TestConfiguration.INSTANCE_DISCOVERY_RESPONSE); - - Capture capturedHttpRequest = Capture.newInstance(); - - EasyMock.expect( - HttpHelper.executeHttpRequest( - EasyMock.capture(capturedHttpRequest), - EasyMock.isA(RequestContext.class), - EasyMock.isA(ServiceBundle.class))) - .andReturn(instanceDiscoveryResponse); - - PowerMock.replay(HttpHelper.class, HttpResponse.class); - - CompletableFuture completableFuture = app.acquireToken( - AuthorizationCodeParameters.builder - ("auth_code", - new URI(TestConfiguration.AAD_DEFAULT_REDIRECT_URI)) - .scopes(Collections.singleton("default-scope")) - .build()); - - completableFuture.get(); - Assert.assertEquals(capturedHttpRequest.getValues().size(),0); - } - - /** - * when instance_discovery flag is set to true (by default), an instance_discovery is NOT performed for adfs. - */ - @Test - public void adfsInstanceDiscoveryTrue() throws Exception{ - app = PowerMock.createPartialMock(PublicClientApplication.class, - new String[]{"acquireTokenCommon"}, - PublicClientApplication.builder(TestConstants.ADFS_APP_ID) - .authority("https://contoso.com/adfs") - .instanceDiscovery(true)); - - Capture capturedMsalRequest = Capture.newInstance(); - - PowerMock.expectPrivate(app, "acquireTokenCommon", - EasyMock.capture(capturedMsalRequest), EasyMock.isA(AADAuthority.class)).andReturn( - AuthenticationResult.builder(). - accessToken("accessToken"). - expiresOn(new Date().getTime() + 100). - refreshToken("refreshToken"). - idToken("idToken").environment("environment").build()); - - PowerMock.mockStatic(HttpHelper.class); - - HttpResponse instanceDiscoveryResponse = new HttpResponse(); - instanceDiscoveryResponse.statusCode(200); - instanceDiscoveryResponse.body(TestConfiguration.INSTANCE_DISCOVERY_RESPONSE); - - Capture capturedHttpRequest = Capture.newInstance(); - - EasyMock.expect( - HttpHelper.executeHttpRequest( - EasyMock.capture(capturedHttpRequest), - EasyMock.isA(RequestContext.class), - EasyMock.isA(ServiceBundle.class))) - .andReturn(instanceDiscoveryResponse); - - PowerMock.replay(HttpHelper.class, HttpResponse.class); - - CompletableFuture completableFuture = app.acquireToken( - AuthorizationCodeParameters.builder - ("auth_code", - new URI(TestConfiguration.AAD_DEFAULT_REDIRECT_URI)) - .scopes(Collections.singleton("default-scope")) - .build()); - - completableFuture.get(); - Assert.assertEquals(capturedHttpRequest.getValues().size(),0); - - } - - /** - * when instance_discovery flag is set to true (by default), an instance_discovery is NOT performed for b2c. - */ - @Test - public void b2cInstanceDiscoveryTrue() throws Exception{ - app = PowerMock.createPartialMock(PublicClientApplication.class, - new String[]{"acquireTokenCommon"}, - PublicClientApplication.builder(TestConstants.ADFS_APP_ID) - .b2cAuthority(TestConstants.B2C_MICROSOFTLOGIN_ROPC) - .instanceDiscovery(true)); - - Capture capturedMsalRequest = Capture.newInstance(); - - PowerMock.expectPrivate(app, "acquireTokenCommon", - EasyMock.capture(capturedMsalRequest), EasyMock.isA(AADAuthority.class)).andReturn( - AuthenticationResult.builder(). - accessToken("accessToken"). - expiresOn(new Date().getTime() + 100). - refreshToken("refreshToken"). - idToken("idToken").environment("environment").build()); - - PowerMock.mockStatic(HttpHelper.class); - - HttpResponse instanceDiscoveryResponse = new HttpResponse(); - instanceDiscoveryResponse.statusCode(200); - instanceDiscoveryResponse.body(TestConfiguration.INSTANCE_DISCOVERY_RESPONSE); - - Capture capturedHttpRequest = Capture.newInstance(); - - EasyMock.expect( - HttpHelper.executeHttpRequest( - EasyMock.capture(capturedHttpRequest), - EasyMock.isA(RequestContext.class), - EasyMock.isA(ServiceBundle.class))) - .andReturn(instanceDiscoveryResponse); - - PowerMock.replay(HttpHelper.class, HttpResponse.class); - - CompletableFuture completableFuture = app.acquireToken( - AuthorizationCodeParameters.builder - ("auth_code", - new URI(TestConfiguration.AAD_DEFAULT_REDIRECT_URI)) - .scopes(Collections.singleton("default-scope")) - .build()); - - completableFuture.get(); - Assert.assertEquals(capturedHttpRequest.getValues().size(),0); - - } - - -} diff --git a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/InvalidAuthorityIT.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/InvalidAuthorityIT.java index 07be1538..08de59d3 100644 --- a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/InvalidAuthorityIT.java +++ b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/InvalidAuthorityIT.java @@ -1,16 +1,21 @@ package com.microsoft.aad.msal4j; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.net.URI; import java.util.Collections; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -public class InvalidAuthorityIT extends SeleniumTest{ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class InvalidAuthorityIT extends SeleniumTest{ - @Test(expectedExceptions = ExecutionException.class, expectedExceptionsMessageRegExp = ".*?invalid instance.*?") - public void acquireTokenWithAuthorizationCode_InvalidAuthority() throws Exception{ + @Test + void acquireTokenWithAuthorizationCode_InvalidAuthority() throws Exception{ PublicClientApplication app; app = PublicClientApplication.builder( TestConfiguration.AAD_CLIENT_ID) @@ -21,6 +26,9 @@ public void acquireTokenWithAuthorizationCode_InvalidAuthority() throws Exceptio AuthorizationCodeParameters.builder("auth_code", new URI(TestConfiguration.AAD_DEFAULT_REDIRECT_URI)) .scopes(Collections.singleton("default-scope")) .authorizationCode("auth_code").redirectUri(new URI(TestConfiguration.AAD_DEFAULT_REDIRECT_URI)).build()); - future.get(); + + ExecutionException ex = assertThrows(ExecutionException.class, future::get); + + assertTrue(ex.getMessage().contains("invalid instance")); } } diff --git a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/OAuthRequestValidationUnitT.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/OAuthRequestValidationUnitT.java deleted file mode 100644 index d4931e90..00000000 --- a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/OAuthRequestValidationUnitT.java +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.aad.msal4j; - -import org.apache.commons.lang3.StringUtils; -import org.testng.Assert; -import org.testng.annotations.Test; - -import java.util.*; -import java.util.concurrent.ExecutionException; - -public class OAuthRequestValidationUnitT extends OAuthRequestValidationTest { - @Test - public void oAuthRequest_for_acquireTokenByClientCertificate() throws Exception { - try { - IClientCertificate clientCertificate = CertificateHelper.getClientCertificate(); - - ConfidentialClientApplication app = ConfidentialClientApplication.builder(CLIENT_ID, clientCertificate) - .authority(AUTHORITY) - .validateAuthority(false).build(); - - // Using UserAssertion as Authorization Grants - OnBehalfOfParameters parameters = - OnBehalfOfParameters.builder(Collections.singleton(SCOPES), new UserAssertion(JWT)) - .build(); - - app.acquireToken(parameters).get(); - } catch (ExecutionException ex) { - Assert.assertTrue(ex.getCause() instanceof MsalException); - } - - Map queryParams = splitQuery(query); - Assert.assertEquals(queryParams.size(), 8); - - // validate Authorization Grants query params - Assert.assertEquals(queryParams.get("grant_type"), GRANT_TYPE_JWT); - Assert.assertEquals(queryParams.get("assertion"), JWT); - - // validate Client Authentication query params - Assert.assertFalse(StringUtils.isEmpty(queryParams.get("client_assertion"))); - - Set scopes = new HashSet<>( - Arrays.asList(queryParams.get("scope").split(AbstractMsalAuthorizationGrant.SCOPES_DELIMITER))); - - // validate custom scopes - Assert.assertTrue(scopes.contains(SCOPES)); - - // validate common scopes - Assert.assertTrue(scopes.contains(AbstractMsalAuthorizationGrant.SCOPE_OPEN_ID)); - Assert.assertTrue(scopes.contains(AbstractMsalAuthorizationGrant.SCOPE_PROFILE)); - Assert.assertTrue(scopes.contains(AbstractMsalAuthorizationGrant.SCOPE_OFFLINE_ACCESS)); - - Assert.assertEquals(queryParams.get("client_assertion_type"), CLIENT_ASSERTION_TYPE_JWT); - Assert.assertEquals(queryParams.get("requested_token_use"), ON_BEHALF_OF_USE_JWT); - - Assert.assertEquals(queryParams.get("client_info"), CLIENT_INFO_VALUE); - Assert.assertEquals(queryParams.get("client_id"), CLIENT_ID); - - } - - @Test - public void oAuthRequest_for_acquireTokenByClientAssertion() throws Exception { - - try { - IClientCertificate clientCertificate = CertificateHelper.getClientCertificate(); - - ConfidentialClientApplication app = - ConfidentialClientApplication.builder( - CLIENT_ID, - clientCertificate) - .authority(AUTHORITY) - .validateAuthority(false) - .build(); - - // Using ClientAssertion for Client Authentication and as the authorization grant - - app.acquireToken(ClientCredentialParameters.builder(Collections.singleton(SCOPES)) - .build()) - .get(); - - } catch (ExecutionException ex) { - Assert.assertTrue(ex.getCause() instanceof MsalException); - } - - Map queryParams = splitQuery(query); - - Assert.assertEquals(queryParams.size(), 6); - - // validate Authorization Grants query params - Assert.assertEquals(queryParams.get("grant_type"), CLIENT_CREDENTIALS_GRANT_TYPE); - - // validate Client Authentication query params - Assert.assertTrue(StringUtils.isNotEmpty(queryParams.get("client_assertion"))); - Assert.assertEquals(queryParams.get("client_assertion_type"), CLIENT_ASSERTION_TYPE_JWT); - - // to do validate scopes - Assert.assertEquals(queryParams.get("scope"), "https://SomeResource.azure.net openid profile offline_access"); - - Assert.assertEquals(queryParams.get("client_info"), CLIENT_INFO_VALUE); - Assert.assertEquals(queryParams.get("client_id"), CLIENT_ID); - } -} diff --git a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/OnBehalfOfIT.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/OnBehalfOfIT.java index 71d706c3..bb6930bb 100644 --- a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/OnBehalfOfIT.java +++ b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/OnBehalfOfIT.java @@ -4,18 +4,24 @@ package com.microsoft.aad.msal4j; import labapi.*; -import org.testng.Assert; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import java.util.Collections; -@Test -public class OnBehalfOfIT { +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class OnBehalfOfIT { private Config cfg; - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) - public void acquireTokenWithOBO_Managed(String environment) throws Exception { + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") + void acquireTokenWithOBO_Managed(String environment) throws Exception { cfg = new Config(environment); String accessToken = this.getAccessToken(); @@ -33,12 +39,13 @@ public void acquireTokenWithOBO_Managed(String environment) throws Exception { new UserAssertion(accessToken)).build()). get(); - Assert.assertNotNull(result); - Assert.assertNotNull(result.accessToken()); + assertNotNull(result); + assertNotNull(result.accessToken()); } - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) - public void acquireTokenWithOBO_testCache(String environment) throws Exception { + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") + void acquireTokenWithOBO_testCache(String environment) throws Exception { cfg = new Config(environment); String accessToken = this.getAccessToken(); @@ -56,8 +63,8 @@ public void acquireTokenWithOBO_testCache(String environment) throws Exception { new UserAssertion(accessToken)).build()). get(); - Assert.assertNotNull(result1); - Assert.assertNotNull(result1.accessToken()); + assertNotNull(result1); + assertNotNull(result1.accessToken()); // Same scope and userAssertion, should return cached tokens IAuthenticationResult result2 = @@ -66,7 +73,7 @@ public void acquireTokenWithOBO_testCache(String environment) throws Exception { new UserAssertion(accessToken)).build()). get(); - Assert.assertEquals(result1.accessToken(), result2.accessToken()); + assertEquals(result1.accessToken(), result2.accessToken()); // Scope 2, should return new token IAuthenticationResult result3 = @@ -75,9 +82,9 @@ public void acquireTokenWithOBO_testCache(String environment) throws Exception { new UserAssertion(accessToken)).build()). get(); - Assert.assertNotNull(result3); - Assert.assertNotNull(result3.accessToken()); - Assert.assertNotEquals(result2.accessToken(), result3.accessToken()); + assertNotNull(result3); + assertNotNull(result3.accessToken()); + assertNotEquals(result2.accessToken(), result3.accessToken()); // Scope 2, should return cached token IAuthenticationResult result4 = @@ -86,7 +93,7 @@ public void acquireTokenWithOBO_testCache(String environment) throws Exception { new UserAssertion(accessToken)).build()). get(); - Assert.assertEquals(result3.accessToken(), result4.accessToken()); + assertEquals(result3.accessToken(), result4.accessToken()); // skipCache=true, should return new token IAuthenticationResult result5 = @@ -98,10 +105,10 @@ public void acquireTokenWithOBO_testCache(String environment) throws Exception { .build()). get(); - Assert.assertNotNull(result5); - Assert.assertNotNull(result5.accessToken()); - Assert.assertNotEquals(result5.accessToken(), result4.accessToken()); - Assert.assertNotEquals(result5.accessToken(), result2.accessToken()); + assertNotNull(result5); + assertNotNull(result5.accessToken()); + assertNotEquals(result5.accessToken(), result4.accessToken()); + assertNotEquals(result5.accessToken(), result2.accessToken()); String newAccessToken = this.getAccessToken(); @@ -114,11 +121,11 @@ public void acquireTokenWithOBO_testCache(String environment) throws Exception { .build()). get(); - Assert.assertNotNull(result6); - Assert.assertNotNull(result6.accessToken()); - Assert.assertNotEquals(result6.accessToken(), result5.accessToken()); - Assert.assertNotEquals(result6.accessToken(), result4.accessToken()); - Assert.assertNotEquals(result6.accessToken(), result2.accessToken()); + assertNotNull(result6); + assertNotNull(result6.accessToken()); + assertNotEquals(result6.accessToken(), result5.accessToken()); + assertNotEquals(result6.accessToken(), result4.accessToken()); + assertNotEquals(result6.accessToken(), result2.accessToken()); } private String getAccessToken() throws Exception { diff --git a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/RefreshTokenIT.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/RefreshTokenIT.java index a1e5ebfb..37984846 100644 --- a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/RefreshTokenIT.java +++ b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/RefreshTokenIT.java @@ -5,15 +5,18 @@ import labapi.LabUserProvider; import labapi.User; -import org.testng.Assert; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.Collections; import java.util.concurrent.ExecutionException; - -@Test() -public class RefreshTokenIT { +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class RefreshTokenIT { private String refreshToken; private PublicClientApplication pca; @@ -38,8 +41,9 @@ private void setUp(String environment) throws Exception { refreshToken = result.refreshToken(); } - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) - public void acquireTokenWithRefreshToken(String environment) throws Exception { + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") + void acquireTokenWithRefreshToken(String environment) throws Exception { cfg = new Config(environment); setUp(environment); @@ -51,18 +55,18 @@ public void acquireTokenWithRefreshToken(String environment) throws Exception { .build()) .get(); - Assert.assertNotNull(result); - Assert.assertNotNull(result.accessToken()); - Assert.assertNotNull(result.idToken()); + assertNotNull(result); + assertNotNull(result.accessToken()); + assertNotNull(result.idToken()); } - @Test(expectedExceptions = ExecutionException.class) - public void acquireTokenWithRefreshToken_WrongScopes() throws Exception { - IAuthenticationResult result = pca.acquireToken(RefreshTokenParameters - .builder( - Collections.singleton(TestConstants.KEYVAULT_DEFAULT_SCOPE), - refreshToken) - .build()) - .get(); + @Test + void acquireTokenWithRefreshToken_WrongScopes() throws Exception { + assertThrows(IllegalArgumentException.class, () -> pca.acquireToken(RefreshTokenParameters + .builder( + Collections.singleton(TestConstants.KEYVAULT_DEFAULT_SCOPE), + refreshToken) + .build()) + .get()); } } diff --git a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/SeleniumTest.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/SeleniumTest.java index ccb218b1..f5fe21cb 100644 --- a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/SeleniumTest.java +++ b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/SeleniumTest.java @@ -8,9 +8,6 @@ import labapi.LabUserProvider; import labapi.User; import org.openqa.selenium.WebDriver; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; abstract class SeleniumTest { @@ -18,12 +15,10 @@ abstract class SeleniumTest { WebDriver seleniumDriver; HttpListener httpListener; - @BeforeClass public void setUpLapUserProvider() { labUserProvider = LabUserProvider.getInstance(); } - @AfterMethod public void cleanUp() { seleniumDriver.quit(); if (httpListener != null) { @@ -31,7 +26,6 @@ public void cleanUp() { } } - @BeforeMethod public void startUpBrowser() { seleniumDriver = SeleniumExtensions.createDefaultWebDriver(); } diff --git a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/TestConstants.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/TestConstants.java index d11fcdf8..23f6e621 100644 --- a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/TestConstants.java +++ b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/TestConstants.java @@ -13,6 +13,7 @@ public class TestConstants { public final static String MSIDLAB_VAULT_URL = "https://msidlabs.vault.azure.net/"; public final static String GRAPH_DEFAULT_SCOPE = "https://graph.windows.net/.default"; public final static String USER_READ_SCOPE = "user.read"; + public final static String DEFAULT_SCOPE = ".default"; public final static String B2C_LAB_SCOPE = "https://msidlabb2c.onmicrosoft.com/msaapp/user_impersonation"; public final static String B2C_CONFIDENTIAL_CLIENT_APP_SECRETID = "MSIDLABB2C-MSAapp-AppSecret"; public final static String B2C_CONFIDENTIAL_CLIENT_LAB_APP_ID = "MSIDLABB2C-MSAapp-AppID"; 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 70da5288..d1192f2c 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 @@ -4,26 +4,30 @@ package com.microsoft.aad.msal4j; import labapi.*; -import org.testng.Assert; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.TestInstance; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; -public class TokenCacheIT { +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class TokenCacheIT { private LabUserProvider labUserProvider; - @BeforeClass - public void setUp() { + @BeforeAll + void setUp() { labUserProvider = LabUserProvider.getInstance(); } @Test - public void singleAccountInCache_RemoveAccountTest() throws Exception { + void singleAccountInCache_RemoveAccountTest() throws Exception { User user = labUserProvider.getDefaultUser(); PublicClientApplication pca = PublicClientApplication.builder( @@ -32,7 +36,7 @@ public void singleAccountInCache_RemoveAccountTest() throws Exception { build(); // Check that cache is empty - Assert.assertEquals(pca.getAccounts().join().size(), 0); + assertEquals(pca.getAccounts().join().size(), 0); Map extraQueryParameters = new HashMap<>(); extraQueryParameters.put("test", "test"); @@ -46,16 +50,16 @@ public void singleAccountInCache_RemoveAccountTest() throws Exception { .get(); // Check that cache contains one account - Assert.assertEquals(pca.getAccounts().join().size(), 1); + assertEquals(pca.getAccounts().join().size(), 1); pca.removeAccount(pca.getAccounts().join().iterator().next()).join(); // Check that account has been removed - Assert.assertEquals(pca.getAccounts().join().size(), 0); + assertEquals(pca.getAccounts().join().size(), 0); } @Test - public void twoAccountsInCache_RemoveAccountTest() throws Exception { + void twoAccountsInCache_RemoveAccountTest() throws Exception { User managedUser = labUserProvider.getDefaultUser(); @@ -64,7 +68,7 @@ public void twoAccountsInCache_RemoveAccountTest() throws Exception { authority(TestConstants.ORGANIZATIONS_AUTHORITY). build(); - Assert.assertEquals(pca.getAccounts().join().size(), 0); + assertEquals(pca.getAccounts().join().size(), 0); pca.acquireToken(UserNamePasswordParameters. builder(Collections.singleton(TestConstants.GRAPH_DEFAULT_SCOPE), @@ -73,7 +77,7 @@ public void twoAccountsInCache_RemoveAccountTest() throws Exception { .build()) .get(); - Assert.assertEquals(pca.getAccounts().join().size(), 1); + assertEquals(pca.getAccounts().join().size(), 1); // get lab user for different account User adfsUser = labUserProvider.getFederatedAdfsUser(FederationProvider.ADFS_4); @@ -86,7 +90,7 @@ public void twoAccountsInCache_RemoveAccountTest() throws Exception { .build()) .get(); - Assert.assertEquals(pca.getAccounts().join().size(), 2); + assertEquals(pca.getAccounts().join().size(), 2); Set accounts = pca.getAccounts().join(); IAccount accountLabResponse1 = accounts.stream().filter( @@ -95,16 +99,16 @@ public void twoAccountsInCache_RemoveAccountTest() throws Exception { pca.removeAccount(accountLabResponse1).join(); - Assert.assertEquals(pca.getAccounts().join().size(), 1); + assertEquals(pca.getAccounts().join().size(), 1); IAccount accountLabResponse2 = pca.getAccounts().get().iterator().next(); // Check that the right account was left in the cache - Assert.assertEquals(accountLabResponse2.username(), adfsUser.getUpn()); + assertEquals(accountLabResponse2.username(), adfsUser.getUpn()); } @Test - public void twoAccountsInCache_SameUserDifferentTenants_RemoveAccountTest() throws Exception { + void twoAccountsInCache_SameUserDifferentTenants_RemoveAccountTest() throws Exception { UserQueryParameters query = new UserQueryParameters(); query.parameters.put(UserQueryParameters.USER_TYPE, UserType.GUEST); @@ -117,7 +121,7 @@ public void twoAccountsInCache_SameUserDifferentTenants_RemoveAccountTest() thro "/cache_data/remove-account-test-cache.json"); // check that cache is empty - Assert.assertEquals(dataToInitCache, ""); + assertEquals(dataToInitCache, ""); ITokenCacheAccessAspect persistenceAspect = new TokenPersistence(dataToInitCache); @@ -152,14 +156,14 @@ public void twoAccountsInCache_SameUserDifferentTenants_RemoveAccountTest() thro .get(); // There should be two tokens in cache, with same accounts except for tenant - Assert.assertEquals(pca2.getAccounts().join().iterator().next().getTenantProfiles().size(), 2); + assertEquals(pca2.getAccounts().join().iterator().next().getTenantProfiles().size(), 2); IAccount account = pca2.getAccounts().get().iterator().next(); // RemoveAccount should remove both cache entities pca2.removeAccount(account).join(); - Assert.assertEquals(pca.getAccounts().join().size(), 0); + assertEquals(pca.getAccounts().join().size(), 0); //clean up file TestHelper.deleteFileContent( @@ -167,6 +171,31 @@ public void twoAccountsInCache_SameUserDifferentTenants_RemoveAccountTest() thro "/cache_data/remove-account-test-cache.json"); } + @Test + void retrieveAccounts_ADFSOnPrem() throws Exception { + UserQueryParameters query = new UserQueryParameters(); + query.parameters.put(UserQueryParameters.FEDERATION_PROVIDER, FederationProvider.ADFS_2019); + query.parameters.put(UserQueryParameters.USER_TYPE, UserType.ON_PREM); + + User user = labUserProvider.getLabUser(query); + + PublicClientApplication pca = PublicClientApplication.builder( + TestConstants.ADFS_APP_ID). + authority(TestConstants.ADFS_AUTHORITY). + build(); + + pca.acquireToken(UserNamePasswordParameters. + builder(Collections.singleton(TestConstants.ADFS_SCOPE), + user.getUpn(), + user.getPassword().toCharArray()) + .build()) + .get(); + + assertNotNull(pca.getAccounts().join().iterator().next()); + assertEquals(pca.getAccounts().join().size(), 1); + } + + private static class TokenPersistence implements ITokenCacheAccessAspect { String data; diff --git a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/UsernamePasswordIT.java b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/UsernamePasswordIT.java index 8b9c9fe0..415b2ac9 100644 --- a/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/UsernamePasswordIT.java +++ b/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/UsernamePasswordIT.java @@ -4,27 +4,32 @@ package com.microsoft.aad.msal4j; import labapi.*; -import org.testng.Assert; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.api.BeforeAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import java.util.Collections; import java.util.HashMap; import java.util.Map; -@Test() -public class UsernamePasswordIT { +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class UsernamePasswordIT { private LabUserProvider labUserProvider; private Config cfg; - @BeforeClass - public void setUp() { + @BeforeAll + void setUp() { labUserProvider = LabUserProvider.getInstance(); } - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) - public void acquireTokenWithUsernamePassword_Managed(String environment) throws Exception { + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") + void acquireTokenWithUsernamePassword_Managed(String environment) throws Exception { cfg = new Config(environment); User user = labUserProvider.getDefaultUser(cfg.azureEnvironment); @@ -32,8 +37,9 @@ public void acquireTokenWithUsernamePassword_Managed(String environment) throws assertAcquireTokenCommon(user, cfg.organizationsAuthority(), cfg.graphDefaultScope(), user.getAppId()); } - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) - public void acquireTokenWithUsernamePassword_ADFSv2019_Federated(String environment) throws Exception { + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") + void acquireTokenWithUsernamePassword_ADFSv2019_Federated(String environment) throws Exception { cfg = new Config(environment); UserQueryParameters query = new UserQueryParameters(); @@ -47,7 +53,7 @@ public void acquireTokenWithUsernamePassword_ADFSv2019_Federated(String environm } @Test - public void acquireTokenWithUsernamePassword_ADFSv2019_OnPrem() throws Exception { + void acquireTokenWithUsernamePassword_ADFSv2019_OnPrem() throws Exception { UserQueryParameters query = new UserQueryParameters(); query.parameters.put(UserQueryParameters.FEDERATION_PROVIDER, FederationProvider.ADFS_2019); query.parameters.put(UserQueryParameters.USER_TYPE, UserType.ON_PREM); @@ -57,8 +63,9 @@ public void acquireTokenWithUsernamePassword_ADFSv2019_OnPrem() throws Exception assertAcquireTokenCommon(user, TestConstants.ADFS_AUTHORITY, TestConstants.ADFS_SCOPE, TestConstants.ADFS_APP_ID); } - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) - public void acquireTokenWithUsernamePassword_ADFSv4(String environment) throws Exception { + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") + void acquireTokenWithUsernamePassword_ADFSv4(String environment) throws Exception { cfg = new Config(environment); UserQueryParameters query = new UserQueryParameters(); @@ -71,8 +78,9 @@ public void acquireTokenWithUsernamePassword_ADFSv4(String environment) throws E assertAcquireTokenCommon(user, cfg.organizationsAuthority(), cfg.graphDefaultScope(), user.getAppId()); } - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) - public void acquireTokenWithUsernamePassword_ADFSv3(String environment) throws Exception { + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") + void acquireTokenWithUsernamePassword_ADFSv3(String environment) throws Exception { cfg = new Config(environment); UserQueryParameters query = new UserQueryParameters(); @@ -85,8 +93,9 @@ public void acquireTokenWithUsernamePassword_ADFSv3(String environment) throws E assertAcquireTokenCommon(user, cfg.organizationsAuthority(), cfg.graphDefaultScope(), user.getAppId()); } - @Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class) - public void acquireTokenWithUsernamePassword_ADFSv2(String environment) throws Exception { + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.EnvironmentsProvider#createData") + void acquireTokenWithUsernamePassword_ADFSv2(String environment) throws Exception { cfg = new Config(environment); UserQueryParameters query = new UserQueryParameters(); @@ -100,40 +109,38 @@ public void acquireTokenWithUsernamePassword_ADFSv2(String environment) throws E } @Test - public void acquireTokenWithUsernamePassword_Ciam() throws Exception { + void acquireTokenWithUsernamePassword_AuthorityWithPort() throws Exception { + User user = labUserProvider.getDefaultUser(); + + assertAcquireTokenCommon( + user, + TestConstants.COMMON_AUTHORITY_WITH_PORT, + TestConstants.GRAPH_DEFAULT_SCOPE, + user.getAppId()); + } + + @Test + void acquireTokenWithUsernamePassword_Ciam() throws Exception { Map extraQueryParameters = new HashMap<>(); - extraQueryParameters.put("dc","ESTS-PUB-EUS-AZ1-FD000-TEST1"); User user = labUserProvider.getCiamUser(); PublicClientApplication pca = PublicClientApplication.builder(user.getAppId()) - .authority("https://" + user.getLabName() + ".ciamlogin.com/") - .build(); + .authority("https://" + user.getLabName() + ".ciamlogin.com/") + .build(); IAuthenticationResult result = pca.acquireToken(UserNamePasswordParameters. - builder(Collections.singleton(TestConstants.GRAPH_DEFAULT_SCOPE), + builder(Collections.singleton(TestConstants.USER_READ_SCOPE), user.getUpn(), user.getPassword().toCharArray()) - .extraQueryParameters(extraQueryParameters) + .extraQueryParameters(extraQueryParameters) .build()) .get(); - Assert.assertNotNull(result.accessToken()); + assertNotNull(result.accessToken()); } - @Test - public void acquireTokenWithUsernamePassword_AuthorityWithPort() throws Exception { - User user = labUserProvider.getDefaultUser(); - - assertAcquireTokenCommon( - user, - TestConstants.COMMON_AUTHORITY_WITH_PORT, - TestConstants.GRAPH_DEFAULT_SCOPE, - user.getAppId()); - } - - private void assertAcquireTokenCommonAAD(User user) throws Exception { assertAcquireTokenCommon(user, cfg.organizationsAuthority(), cfg.graphDefaultScope(), user.getAppId()); @@ -156,11 +163,11 @@ private void assertAcquireTokenCommon(User user, String authority, String scope, .get(); assertTokenResultNotNull(result); - Assert.assertEquals(user.getUpn(), result.account().username()); + assertEquals(user.getUpn(), result.account().username()); } @Test - public void acquireTokenWithUsernamePassword_B2C_CustomAuthority() throws Exception { + void acquireTokenWithUsernamePassword_B2C_CustomAuthority() throws Exception { UserQueryParameters query = new UserQueryParameters(); query.parameters.put(UserQueryParameters.USER_TYPE, UserType.B2C); query.parameters.put(UserQueryParameters.B2C_PROVIDER, B2CProvider.LOCAL); @@ -192,7 +199,7 @@ public void acquireTokenWithUsernamePassword_B2C_CustomAuthority() throws Except } @Test - public void acquireTokenWithUsernamePassword_B2C_LoginMicrosoftOnline() throws Exception { + void acquireTokenWithUsernamePassword_B2C_LoginMicrosoftOnline() throws Exception { UserQueryParameters query = new UserQueryParameters(); query.parameters.put(UserQueryParameters.USER_TYPE, UserType.B2C); query.parameters.put(UserQueryParameters.B2C_PROVIDER, B2CProvider.LOCAL); @@ -224,8 +231,8 @@ public void acquireTokenWithUsernamePassword_B2C_LoginMicrosoftOnline() throws E } private void assertTokenResultNotNull(IAuthenticationResult result) { - Assert.assertNotNull(result); - Assert.assertNotNull(result.accessToken()); - Assert.assertNotNull(result.idToken()); + assertNotNull(result); + assertNotNull(result.accessToken()); + assertNotNull(result.idToken()); } } diff --git a/msal4j-sdk/src/integrationtest/java/infrastructure/UserInformationFields.java b/msal4j-sdk/src/integrationtest/java/infrastructure/UserInformationFields.java index 048d3b28..22e1534d 100644 --- a/msal4j-sdk/src/integrationtest/java/infrastructure/UserInformationFields.java +++ b/msal4j-sdk/src/integrationtest/java/infrastructure/UserInformationFields.java @@ -3,11 +3,9 @@ package infrastructure; -import com.microsoft.aad.msal4j.TestConstants; import labapi.FederationProvider; import labapi.LabConstants; import labapi.User; -import org.testng.util.Strings; class UserInformationFields { private final User user; @@ -19,14 +17,14 @@ class UserInformationFields { } String getPasswordInputId() { - if (Strings.isNullOrEmpty(passwordInputId)) { + if (passwordInputId == null || passwordInputId.equals("")) { determineFieldIds(); } return passwordInputId; } String getPasswordSigInButtonId() { - if (Strings.isNullOrEmpty(passwordSigInButtonId)) { + if (passwordSigInButtonId == null || passwordSigInButtonId.equals("")) { determineFieldIds(); } return passwordSigInButtonId; diff --git a/msal4j-sdk/src/integrationtest/java/labapi/LabConstants.java b/msal4j-sdk/src/integrationtest/java/labapi/LabConstants.java index 569acafd..79397eb9 100644 --- a/msal4j-sdk/src/integrationtest/java/labapi/LabConstants.java +++ b/msal4j-sdk/src/integrationtest/java/labapi/LabConstants.java @@ -14,7 +14,7 @@ public class LabConstants { public final static String USER_MSA_USERNAME_URL = "https://msidlabs.vault.azure.net/secrets/MSA-MSIDLAB4-UserName"; public final static String USER_MSA_PASSWORD_URL = "https://msidlabs.vault.azure.net/secrets/MSA-MSIDLAB4-Password"; public final static String OBO_APP_PASSWORD_URL = "https://msidlabs.vault.azure.net/secrets/TodoListServiceV2-OBO"; - public final static String CIAM_KEY_VAULT_SECRET_KEY = "https://msidlabs.vault.azure.net/secrets/MSIDLABCIAM1-cc"; + public final static String CIAM_KEY_VAULT_SECRET_KEY = "https://msidlabs.vault.azure.net/secrets/MSIDLABCIAM2-cc"; public final static String ARLINGTON_APP_ID = "cb7faed4-b8c0-49ee-b421-f5ed16894c83"; public final static String ARLINGTON_OBO_APP_ID = "c0555d2d-02f2-4838-802e-3463422e571d"; diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AadInstanceDiscoveryProvider.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AadInstanceDiscoveryProvider.java index a66094e9..2cbd728a 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AadInstanceDiscoveryProvider.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AadInstanceDiscoveryProvider.java @@ -225,7 +225,7 @@ private static String getInstanceDiscoveryEndpoint(URL authorityUrl) { replace("{port}", String.valueOf(port)); } - private static AadInstanceDiscoveryResponse sendInstanceDiscoveryRequest(URL authorityUrl, + static AadInstanceDiscoveryResponse sendInstanceDiscoveryRequest(URL authorityUrl, MsalRequest msalRequest, ServiceBundle serviceBundle) { @@ -289,7 +289,7 @@ private static IHttpResponse executeRequest(String requestUrl, Map expirationTime) { + LOG.warn(String.format("Listener timed out after %S seconds, no authorization code was returned from the server during that time.", timeFromParameters)); + break; + } result = authorizationResultQueue.poll(100, TimeUnit.MILLISECONDS); } diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthenticationErrorCode.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthenticationErrorCode.java index 87d4615a..0d3f231a 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthenticationErrorCode.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AuthenticationErrorCode.java @@ -123,6 +123,14 @@ public class AuthenticationErrorCode { */ public final static String MISSING_BROKER = "missing_broker"; + /** + * Indicates that a timeout occurred during an HTTP call. If this was thrown in relation to a connection timeout error, + * there is likely a network issue preventing the library from reaching a service, such as being blocked by a firewall. + * If this was thrown in relation to a read timeout error, there is likely an issue in the service itself causing a + * slow response, and this may be resolvable by increasing timeouts. For more details, see https://aka.ms/msal4j-http-client + */ + public final static String HTTP_TIMEOUT = "http_timeout"; + /** * Indicates an error from the MSAL Java/MSALRuntime interop layer used by the Java Brokers package, * and will generally just be forwarding an error message from the interop layer or MSALRuntime itself diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/DefaultHttpClient.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/DefaultHttpClient.java index a563b369..ee60eccc 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/DefaultHttpClient.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/DefaultHttpClient.java @@ -1,25 +1,30 @@ package com.microsoft.aad.msal4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLSocketFactory; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; +import java.net.ConnectException; import java.net.HttpURLConnection; import java.net.Proxy; +import java.net.SocketTimeoutException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.Map; class DefaultHttpClient implements IHttpClient { + private final static Logger LOG = LoggerFactory.getLogger(DefaultHttpClient.class); private final Proxy proxy; private final SSLSocketFactory sslSocketFactory; - public int DEFAULT_CONNECT_TIMEOUT = 10000; - public int DEFAULT_READ_TIMEOUT = 15000; - private int connectTimeout = DEFAULT_CONNECT_TIMEOUT; - private int readTimeout = DEFAULT_READ_TIMEOUT; + //By default, rely on the timeout behavior of the services requests are sent to + private int connectTimeout = 0; + private int readTimeout = 0; DefaultHttpClient(Proxy proxy, SSLSocketFactory sslSocketFactory, Integer connectTimeout, Integer readTimeout) { this.proxy = proxy; @@ -117,6 +122,14 @@ private HttpResponse readResponseFromConnection(final HttpsURLConnection conn) t httpResponse.addHeaders(conn.getHeaderFields()); httpResponse.body(inputStreamToString(is)); return httpResponse; + } catch (SocketTimeoutException readException) { + LOG.error("Timeout while waiting for response from service. If custom timeouts were set, increasing them may resolve this issue. See https://aka.ms/msal4j-http-client for more information and solutions."); + + throw readException; + } catch (ConnectException timeoutException) { + LOG.error("Exception while connecting to service, there may be network issues preventing MSAL Java from connecting. See https://aka.ms/msal4j-http-client for more information and solutions."); + + throw timeoutException; } finally { if (is != null) { is.close(); diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/ThrottlingCache.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/ThrottlingCache.java index 89e1f348..9c8a0584 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/ThrottlingCache.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/ThrottlingCache.java @@ -12,7 +12,7 @@ class ThrottlingCache { static final int MAX_THROTTLING_TIME_SEC = 3600; - static int DEFAULT_THROTTLING_TIME_SEC = 120; + static int DEFAULT_THROTTLING_TIME_SEC = 5; static final int CACHE_SIZE_LIMIT_TO_TRIGGER_EXPIRED_ENTITIES_REMOVAL = 100; // request hash to expiration timestamp diff --git a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/TokenCache.java b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/TokenCache.java index d12dc186..b5032d7c 100644 --- a/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/TokenCache.java +++ b/msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/TokenCache.java @@ -346,7 +346,7 @@ Set getAccounts(String clientId) { ((Account) rootAccounts.get(accCached.homeAccountId())).tenantProfiles.put(accCached.realm(), profile); } - if (accCached.homeAccountId().contains(accCached.localAccountId())) { + if (accCached.localAccountId() != null && accCached.homeAccountId().contains(accCached.localAccountId())) { ((Account) rootAccounts.get(accCached.homeAccountId())).username(accCached.username()); } } diff --git a/msal4j-sdk/src/samples/msal-b2c-web-sample/pom.xml b/msal4j-sdk/src/samples/msal-b2c-web-sample/pom.xml index 4bbc97be..416d4d36 100644 --- a/msal4j-sdk/src/samples/msal-b2c-web-sample/pom.xml +++ b/msal4j-sdk/src/samples/msal-b2c-web-sample/pom.xml @@ -23,7 +23,7 @@ com.microsoft.azure msal4j - 1.14.0-beta + 1.13.11 com.nimbusds diff --git a/msal4j-sdk/src/samples/msal-obo-sample/pom.xml b/msal4j-sdk/src/samples/msal-obo-sample/pom.xml index 381e3e89..860ebce8 100644 --- a/msal4j-sdk/src/samples/msal-obo-sample/pom.xml +++ b/msal4j-sdk/src/samples/msal-obo-sample/pom.xml @@ -23,7 +23,7 @@ com.microsoft.azure msal4j - 1.14.0-beta + 1.13.11 com.nimbusds diff --git a/msal4j-sdk/src/samples/msal-web-sample/pom.xml b/msal4j-sdk/src/samples/msal-web-sample/pom.xml index aa3e1c1e..bb6760fb 100644 --- a/msal4j-sdk/src/samples/msal-web-sample/pom.xml +++ b/msal4j-sdk/src/samples/msal-web-sample/pom.xml @@ -23,7 +23,7 @@ com.microsoft.azure msal4j - 1.14.0-beta + 1.13.11 com.nimbusds diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AadInstanceDiscoveryTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AadInstanceDiscoveryTest.java index 61ac5608..8e5aa477 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AadInstanceDiscoveryTest.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AadInstanceDiscoveryTest.java @@ -3,34 +3,51 @@ package com.microsoft.aad.msal4j; -import org.powermock.api.easymock.PowerMock; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.testng.PowerMockTestCase; -import org.testng.Assert; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.CALLS_REAL_METHODS; +import static org.mockito.Mockito.mockStatic; import java.net.URI; import java.net.URL; -@PrepareForTest(AadInstanceDiscoveryProvider.class) -public class AadInstanceDiscoveryTest extends PowerMockTestCase { +@ExtendWith(MockitoExtension.class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class AadInstanceDiscoveryTest { - @BeforeMethod + String instanceDiscoveryValidResponse; + AuthorizationCodeParameters parameters; + + @BeforeAll + public void init() throws Exception { + instanceDiscoveryValidResponse = TestHelper.readResource( + this.getClass(), + "/instance_discovery_data/aad_instance_discovery_response_valid.json"); + + parameters = AuthorizationCodeParameters.builder( + "code", new URI("http://my.redirect.com")).build(); + } + + @BeforeEach public void setup() { AadInstanceDiscoveryProvider.cache.clear(); } @Test - public void aadInstanceDiscoveryTest_NotSetByDeveloper() throws Exception { + void aadInstanceDiscoveryTest_NotSetByDeveloper() throws Exception { PublicClientApplication app = PublicClientApplication.builder("client_id") .correlationId("correlation_id") .authority("https://login.microsoftonline.com/my_tenant") .build(); - AuthorizationCodeParameters parameters = AuthorizationCodeParameters.builder( - "code", new URI("http://my.redirect.com")).build(); - MsalRequest msalRequest = new AuthorizationCodeRequest( parameters, app, @@ -38,56 +55,33 @@ public void aadInstanceDiscoveryTest_NotSetByDeveloper() throws Exception { URL authority = new URL(app.authority()); - String instanceDiscoveryData = TestHelper.readResource( - this.getClass(), - "/instance_discovery_data/aad_instance_discovery_response_valid.json"); - AadInstanceDiscoveryResponse expectedResponse = JsonHelper.convertJsonToObject( - instanceDiscoveryData, + instanceDiscoveryValidResponse, AadInstanceDiscoveryResponse.class); - PowerMock.mockStaticPartial(AadInstanceDiscoveryProvider.class, "sendInstanceDiscoveryRequest"); + try (MockedStatic mockedInstanceDiscoveryProvider = mockStatic(AadInstanceDiscoveryProvider.class, CALLS_REAL_METHODS)) { - PowerMock.expectPrivate( - AadInstanceDiscoveryProvider.class, - "sendInstanceDiscoveryRequest", - authority, - msalRequest, - app.getServiceBundle()).andReturn(expectedResponse); - - PowerMock.replay(AadInstanceDiscoveryProvider.class); - - InstanceDiscoveryMetadataEntry entry = AadInstanceDiscoveryProvider.getMetadataEntry( - new URL(app.authority()), - false, - msalRequest, - app.getServiceBundle()); + mockedInstanceDiscoveryProvider.when(() -> AadInstanceDiscoveryProvider.sendInstanceDiscoveryRequest(authority, + msalRequest, + app.getServiceBundle())).thenReturn(expectedResponse); - PowerMock.verify(AadInstanceDiscoveryProvider.class); + InstanceDiscoveryMetadataEntry entry = AadInstanceDiscoveryProvider.getMetadataEntry( + authority, + false, + msalRequest, + app.getServiceBundle()); - Assert.assertEquals(entry.preferredNetwork(), "login.microsoftonline.com"); - Assert.assertEquals(entry.preferredCache(), "login.windows.net"); - Assert.assertEquals(entry.aliases().size(), 4); - Assert.assertTrue(entry.aliases().contains("login.microsoftonline.com")); - Assert.assertTrue(entry.aliases().contains("login.windows.net")); - Assert.assertTrue(entry.aliases().contains("login.microsoft.com")); - Assert.assertTrue(entry.aliases().contains("sts.windows.net")); + assertValidResponse(entry); + } } @Test - public void aadInstanceDiscoveryTest_responseSetByDeveloper_validResponse() throws Exception { - - String instanceDiscoveryResponse = TestHelper.readResource( - this.getClass(), - "/instance_discovery_data/aad_instance_discovery_response_valid.json"); + void aadInstanceDiscoveryTest_responseSetByDeveloper_validResponse() throws Exception { PublicClientApplication app = PublicClientApplication.builder("client_id") - .aadInstanceDiscoveryResponse(instanceDiscoveryResponse) + .aadInstanceDiscoveryResponse(instanceDiscoveryValidResponse) .build(); - AuthorizationCodeParameters parameters = AuthorizationCodeParameters.builder( - "code", new URI("http://my.redirect.com")).build(); - MsalRequest msalRequest = new AuthorizationCodeRequest( parameters, app, @@ -95,62 +89,35 @@ public void aadInstanceDiscoveryTest_responseSetByDeveloper_validResponse() thro URL authority = new URL(app.authority()); - PowerMock.mockStaticPartial( - AadInstanceDiscoveryProvider.class, - "sendInstanceDiscoveryRequest"); - - // throw exception if we try to get metadata from network. - PowerMock.expectPrivate( - AadInstanceDiscoveryProvider.class, - "sendInstanceDiscoveryRequest", - authority, - msalRequest, - app.getServiceBundle()).andThrow(new AssertionError()).anyTimes(); - - PowerMock.replay(AadInstanceDiscoveryProvider.class); - InstanceDiscoveryMetadataEntry entry = AadInstanceDiscoveryProvider.getMetadataEntry( authority, false, msalRequest, app.getServiceBundle()); - Assert.assertEquals(entry.preferredNetwork(), "login.microsoftonline.com"); - Assert.assertEquals(entry.preferredCache(), "login.windows.net"); - Assert.assertEquals(entry.aliases().size(), 4); - Assert.assertTrue(entry.aliases().contains("login.microsoftonline.com")); - Assert.assertTrue(entry.aliases().contains("login.windows.net")); - Assert.assertTrue(entry.aliases().contains("login.microsoft.com")); - Assert.assertTrue(entry.aliases().contains("sts.windows.net")); + assertValidResponse(entry); } - @Test(expectedExceptions = MsalClientException.class) - public void aadInstanceDiscoveryTest_responseSetByDeveloper_invalidJson() throws Exception { + @Test + void aadInstanceDiscoveryTest_responseSetByDeveloper_invalidJson() throws Exception { String instanceDiscoveryResponse = TestHelper.readResource( this.getClass(), "/instance_discovery_data/aad_instance_discovery_response_invalid_json.json"); - PublicClientApplication app = PublicClientApplication.builder("client_id") + assertThrows(MsalClientException.class, () -> PublicClientApplication.builder("client_id") .aadInstanceDiscoveryResponse(instanceDiscoveryResponse) - .build(); + .build()); } - @Test() - public void aadInstanceDiscoveryTest_AutoDetectRegion_NoRegionDetected() throws Exception { - - String instanceDiscoveryResponse = TestHelper.readResource( - this.getClass(), - "/instance_discovery_data/aad_instance_discovery_response_valid.json"); + @Test + void aadInstanceDiscoveryTest_AutoDetectRegion_NoRegionDetected() throws Exception { PublicClientApplication app = PublicClientApplication.builder("client_id") - .aadInstanceDiscoveryResponse(instanceDiscoveryResponse) + .aadInstanceDiscoveryResponse(instanceDiscoveryValidResponse) .autoDetectRegion(true) .build(); - AuthorizationCodeParameters parameters = AuthorizationCodeParameters.builder( - "code", new URI("http://my.redirect.com")).build(); - MsalRequest msalRequest = new AuthorizationCodeRequest( parameters, app, @@ -158,32 +125,28 @@ public void aadInstanceDiscoveryTest_AutoDetectRegion_NoRegionDetected() throws URL authority = new URL(app.authority()); - PowerMock.mockStaticPartial( - AadInstanceDiscoveryProvider.class, - "discoverRegion"); + try (MockedStatic mocked = mockStatic(AadInstanceDiscoveryProvider.class, CALLS_REAL_METHODS)) { - PowerMock.expectPrivate( - AadInstanceDiscoveryProvider.class, - "discoverRegion", - msalRequest, - app.getServiceBundle()).andThrow(new AssertionError()).anyTimes(); + mocked.when(() -> AadInstanceDiscoveryProvider.discoverRegion(msalRequest, + app.getServiceBundle())).thenReturn(null); - PowerMock.replay(AadInstanceDiscoveryProvider.class); + InstanceDiscoveryMetadataEntry entry = AadInstanceDiscoveryProvider.getMetadataEntry( + authority, + false, + msalRequest, + app.getServiceBundle()); - InstanceDiscoveryMetadataEntry entry = AadInstanceDiscoveryProvider.getMetadataEntry( - authority, - false, - msalRequest, - app.getServiceBundle()); + assertValidResponse(entry); + } + } - //Region detection will have been performed in the expected discoverRegion method, but these tests (likely) aren't - // being run in an Azure VM and instance discovery will fall back to the global endpoint (login.microsoftonline.com) - Assert.assertEquals(entry.preferredNetwork(), "login.microsoftonline.com"); - Assert.assertEquals(entry.preferredCache(), "login.windows.net"); - Assert.assertEquals(entry.aliases().size(), 4); - Assert.assertTrue(entry.aliases().contains("login.microsoftonline.com")); - Assert.assertTrue(entry.aliases().contains("login.windows.net")); - Assert.assertTrue(entry.aliases().contains("login.microsoft.com")); - Assert.assertTrue(entry.aliases().contains("sts.windows.net")); + void assertValidResponse(InstanceDiscoveryMetadataEntry entry) { + assertEquals(entry.preferredNetwork(), "login.microsoftonline.com"); + assertEquals(entry.preferredCache(), "login.windows.net"); + assertEquals(entry.aliases().size(), 4); + assertTrue(entry.aliases().contains("login.microsoftonline.com")); + assertTrue(entry.aliases().contains("login.windows.net")); + assertTrue(entry.aliases().contains("login.microsoft.com")); + assertTrue(entry.aliases().contains("sts.windows.net")); } } diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AbstractMsalTests.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AbstractMsalTests.java deleted file mode 100644 index a35079c2..00000000 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AbstractMsalTests.java +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.aad.msal4j; - -import org.powermock.modules.testng.PowerMockTestCase; - -public class AbstractMsalTests extends PowerMockTestCase { -} diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AccountTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AccountTest.java index 14a417c0..10807817 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AccountTest.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AccountTest.java @@ -3,8 +3,10 @@ package com.microsoft.aad.msal4j; -import org.testng.Assert; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.net.URISyntaxException; @@ -16,62 +18,8 @@ import static com.microsoft.aad.msal4j.Constants.POINT_DELIMITER; -public class AccountTest { - - // @Test - // hardcoded token secrets should not be used to not trigger CredScan - public void testMultiTenantAccount_AccessTenantProfile() throws IOException, URISyntaxException { - - ITokenCacheAccessAspect accountCache = new CachePersistenceIT.TokenPersistence( - TestHelper.readResource(this.getClass(), - "/cache_data/multi-tenant-account-cache.json")); - - PublicClientApplication app = PublicClientApplication.builder("client_id") - .setTokenCacheAccessAspect(accountCache).build(); - - Assert.assertEquals(app.getAccounts().join().size(), 3); - Iterator acctIterator = app.getAccounts().join().iterator(); - - IAccount curAccount; - while (acctIterator.hasNext()) { - curAccount = acctIterator.next(); - - switch (curAccount.username()) { - case "MultiTenantAccount": { - Assert.assertEquals(curAccount.homeAccountId(), "uid1.utid1"); - Map tenantProfiles = curAccount.getTenantProfiles(); - Assert.assertNotNull(tenantProfiles); - Assert.assertEquals(tenantProfiles.size(), 3); - Assert.assertNotNull(tenantProfiles.get("utid1")); - Assert.assertNotNull(tenantProfiles.get("utid1").getClaims()); - Assert.assertNotNull(tenantProfiles.get("utid2")); - Assert.assertNotNull(tenantProfiles.get("utid2").getClaims()); - Assert.assertNotNull(tenantProfiles.get("utid3")); - Assert.assertNotNull(tenantProfiles.get("utid3").getClaims()); - break; - } - case "SingleTenantAccount": { - Assert.assertEquals(curAccount.homeAccountId(), "uid6.utid5"); - Map tenantProfiles = curAccount.getTenantProfiles(); - Assert.assertNotNull(tenantProfiles); - Assert.assertEquals(tenantProfiles.size(), 1); - Assert.assertNotNull(tenantProfiles.get("utid5")); - Assert.assertNotNull(tenantProfiles.get("utid5").getClaims()); - break; - } - case "TenantProfileNoHome": { - Assert.assertEquals(curAccount.homeAccountId(), "uid5.utid4"); - Map tenantProfiles = curAccount.getTenantProfiles(); - Assert.assertNotNull(tenantProfiles); - Assert.assertEquals(tenantProfiles.size(), 1); - Assert.assertNotNull(tenantProfiles.get("utid4")); - Assert.assertNotNull(tenantProfiles.get("utid4").getClaims()); - break; - } - } - } - } - +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class AccountTest { String getEmptyBase64EncodedJson() { return new String(Base64.getEncoder().encode("{}".getBytes())); @@ -97,7 +45,7 @@ private String getTestIdToken(String environment, String tenant) throws IOExcept } @Test - public void multiCloudAccount_aggregatedInGetAccountsRemoveAccountApis() throws IOException, URISyntaxException { + void multiCloudAccount_aggregatedInGetAccountsRemoveAccountApis() throws IOException, URISyntaxException { String BLACK_FORESRT_TENANT = "de_tid"; String WW_TENTANT = "tid"; String BLACK_FOREST_ENV = "login.microsoftonline.de"; @@ -143,22 +91,22 @@ ITokenCacheAccessAspect init(String data) { Set accounts = pca.getAccounts().join(); - Assert.assertEquals(accounts.size(), 1); + assertEquals(accounts.size(), 1); IAccount account = accounts.iterator().next(); Map tenantProfiles = account.getTenantProfiles(); - Assert.assertEquals(tenantProfiles.size(), 2); + assertEquals(tenantProfiles.size(), 2); - Assert.assertTrue(tenantProfiles.containsKey(BLACK_FORESRT_TENANT)); - Assert.assertTrue(tenantProfiles.containsKey(WW_TENTANT)); + assertTrue(tenantProfiles.containsKey(BLACK_FORESRT_TENANT)); + assertTrue(tenantProfiles.containsKey(WW_TENTANT)); pca.removeAccount(account).join(); accounts = pca.getAccounts().join(); - Assert.assertEquals(accounts.size(), 0); + assertEquals(accounts.size(), 0); - Assert.assertEquals(pca.tokenCache.accounts.size(), 0); - Assert.assertEquals(pca.tokenCache.idTokens.size(), 0); - Assert.assertEquals(pca.tokenCache.refreshTokens.size(), 0); - Assert.assertEquals(pca.tokenCache.accessTokens.size(), 0); + assertEquals(pca.tokenCache.accounts.size(), 0); + assertEquals(pca.tokenCache.idTokens.size(), 0); + assertEquals(pca.tokenCache.refreshTokens.size(), 0); + assertEquals(pca.tokenCache.accessTokens.size(), 0); } } diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AcquireTokenSilentlyTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AcquireTokenSilentlyTest.java index db6ea553..5d0265c0 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AcquireTokenSilentlyTest.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AcquireTokenSilentlyTest.java @@ -3,18 +3,21 @@ package com.microsoft.aad.msal4j; -import org.powermock.modules.testng.PowerMockTestCase; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.Collections; -import java.util.concurrent.CompletionException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; -@Test(groups = {"checkin"}) -public class AcquireTokenSilentlyTest extends PowerMockTestCase { - @Test(expectedExceptions = MsalClientException.class, - expectedExceptionsMessageRegExp = AuthenticationErrorMessage.NO_TOKEN_IN_CACHE) - public void publicAppAcquireTokenSilently_emptyCache_MsalClientException() throws Throwable { +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class AcquireTokenSilentlyTest { + + @Test + void publicAppAcquireTokenSilently_emptyCache_MsalClientException() throws Throwable { PublicClientApplication application = PublicClientApplication .builder(TestConfiguration.AAD_CLIENT_ID) @@ -22,27 +25,27 @@ public void publicAppAcquireTokenSilently_emptyCache_MsalClientException() throw SilentParameters parameters = SilentParameters.builder(Collections.singleton("scope")).build(); - try { - application.acquireTokenSilently(parameters).join(); - } catch (CompletionException ex) { - throw ex.getCause(); - } + CompletableFuture future = application.acquireTokenSilently(parameters); + + ExecutionException ex = assertThrows(ExecutionException.class, future::get); + + assertTrue(ex.getCause() instanceof MsalClientException); + assertTrue(ex.getMessage().contains(AuthenticationErrorMessage.NO_TOKEN_IN_CACHE)); } - @Test(expectedExceptions = MsalClientException.class, - expectedExceptionsMessageRegExp = AuthenticationErrorMessage.NO_TOKEN_IN_CACHE) - public void confidentialAppAcquireTokenSilently_emptyCache_MsalClientException() throws Throwable { + @Test + void confidentialAppAcquireTokenSilently_emptyCache_MsalClientException() throws Throwable { ConfidentialClientApplication application = ConfidentialClientApplication .builder(TestConfiguration.AAD_CLIENT_ID, ClientCredentialFactory.createFromSecret(TestConfiguration.AAD_CLIENT_DUMMYSECRET)) .b2cAuthority(TestConfiguration.B2C_AUTHORITY).build(); SilentParameters parameters = SilentParameters.builder(Collections.singleton("scope")).build(); + CompletableFuture future = application.acquireTokenSilently(parameters); + + ExecutionException ex = assertThrows(ExecutionException.class, future::get); - try { - application.acquireTokenSilently(parameters).join(); - } catch (CompletionException ex) { - throw ex.getCause(); - } + assertTrue(ex.getCause() instanceof MsalClientException); + assertTrue(ex.getMessage().contains(AuthenticationErrorMessage.NO_TOKEN_IN_CACHE)); } } diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AssertionCredentialTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AssertionCredentialTest.java deleted file mode 100644 index 8f7e7450..00000000 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AssertionCredentialTest.java +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.aad.msal4j; - -import org.testng.annotations.Test; - -@Test(groups = {"checkin"}) -public class AssertionCredentialTest { - - @Test(expectedExceptions = NullPointerException.class, expectedExceptionsMessageRegExp = "assertion") - public void testAssertionNull() { - new ClientAssertion(null); - } - - @Test(expectedExceptions = NullPointerException.class, expectedExceptionsMessageRegExp = "assertion") - public void testAssertionEmpty() { - new ClientAssertion(""); - } -} diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AuthorityTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AuthorityTest.java index cd0a8bf4..326e933f 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AuthorityTest.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/AuthorityTest.java @@ -6,197 +6,187 @@ import java.net.MalformedURLException; import java.net.URL; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.testng.Assert; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; -@Test(groups = {"checkin"}) -@PrepareForTest({AADAuthority.class, HttpHelper.class, - JsonHelper.class, AadInstanceDiscoveryResponse.class}) -public class AuthorityTest extends AbstractMsalTests { +import static org.junit.jupiter.api.Assertions.*; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class AuthorityTest { @Test - public void testDetectAuthorityType_AAD() throws Exception { + void testDetectAuthorityType_AAD() throws Exception { URL url = new URL(TestConfiguration.AAD_TENANT_ENDPOINT); - Assert.assertEquals(Authority.detectAuthorityType(url), AuthorityType.AAD); + assertEquals(Authority.detectAuthorityType(url), AuthorityType.AAD); } @Test - public void testDetectAuthorityType_ADFS() throws Exception { + void testDetectAuthorityType_ADFS() throws Exception { URL url = new URL(TestConfiguration.ADFS_TENANT_ENDPOINT); - Assert.assertEquals(Authority.detectAuthorityType(url), AuthorityType.ADFS); + assertEquals(Authority.detectAuthorityType(url), AuthorityType.ADFS); } @Test - public void testDetectAuthorityType_B2C() throws Exception { + void testDetectAuthorityType_B2C() throws Exception { URL url = new URL(TestConfiguration.B2C_AUTHORITY); - Assert.assertEquals(Authority.detectAuthorityType(url), AuthorityType.B2C); + assertEquals(Authority.detectAuthorityType(url), AuthorityType.B2C); } - @DataProvider(name = "ciamAuthorities") - public static Object[][] createCiamAuthorityData() throws MalformedURLException { - return new Object[][]{{new URL("https://msidlabciam1.ciamlogin.com/")}, - {new URL("https://msidlabciam1.ciamlogin.com/d57fb3d4-4b5a-4144-9328-9c1f7d58179d/")}, - {new URL("https://msidlabciam1.ciamlogin.com/msidlabciam1.onmicrosoft.com/")}, - {new URL("https://msidlabciam1.ciamlogin.com/aDomain/")}}; + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.AuthorityTest#ciamAuthorities") + void testDetectAuthorityType_CIAM(URL authority) throws Exception { + assertEquals(Authority.detectAuthorityType(authority), AuthorityType.CIAM); } - @Test(dataProvider = "ciamAuthorities") - public void testDetectAuthorityType_CIAM(URL authority) throws Exception { - Assert.assertEquals(Authority.detectAuthorityType(authority), AuthorityType.CIAM); + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.AuthorityTest#validCiamAuthoritiesAndTransformedAuthority") + void testCiamAuthorityTransformation(URL authority, URL transformedAuthority) throws Exception { + assertEquals(CIAMAuthority.transformAuthority(authority), transformedAuthority); } - @DataProvider(name = "validCiamAuthoritiesAndTransformedAuthority") - public static Object[][] createCiamAndTransformedAuthorityData() throws MalformedURLException { - return new Object[][]{{new URL("https://msidlabciam1.ciamlogin.com/"),new URL("https://msidlabciam1.ciamlogin.com/msidlabciam1.onmicrosoft.com/")}, - {new URL("https://msidlabciam1.ciamlogin.com/d57fb3d4-4b5a-4144-9328-9c1f7d58179d"),new URL("https://msidlabciam1.ciamlogin.com/d57fb3d4-4b5a-4144-9328-9c1f7d58179d")}, - {new URL("https://msidlabciam1.ciamlogin.com/msidlabciam1.onmicrosoft.com"),new URL("https://msidlabciam1.ciamlogin.com/msidlabciam1.onmicrosoft.com")}, - {new URL("https://msidlabciam1.ciamlogin.com/aDomain"),new URL("https://msidlabciam1.ciamlogin.com/aDomain")}}; - } + @Test + void testB2CAuthorityConstructor_NotEnoughSegments() { + + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> + new B2CAuthority(new URL("https://something.com/somethingelse/"))); - @Test(dataProvider = "validCiamAuthoritiesAndTransformedAuthority") - public void testCiamAuthorityTransformation(URL authority, URL transformedAuthority) throws Exception{ - Assert.assertEquals(CIAMAuthority.transformAuthority(authority), transformedAuthority); + assertTrue(ex.getMessage().contains("Valid B2C 'authority' URLs should follow either of these formats")); } - @Test(expectedExceptions = IllegalArgumentException.class, - expectedExceptionsMessageRegExp = - "Valid B2C 'authority' URLs should follow either of these formats.*") - public void testB2CAuthorityConstructor_NotEnoughSegments() throws MalformedURLException { - new B2CAuthority(new URL("https://something.com/somethingelse/")); + @Test + void testAADAuthorityConstructor_HttpAuthority() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> + Authority.validateAuthority(new URL("http://I.com/not/h/t/t/p/s/"))); + + assertTrue(ex.getMessage().contains("authority should use the 'https' scheme")); } - @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "authority should use the 'https' scheme") - public void testAADAuthorityConstructor_HttpAuthority() throws MalformedURLException { - Authority.validateAuthority(new URL("http://I.com/not/h/t/t/p/s/")); + @Test + void testAADAuthorityConstructor_UrlHasFragment() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> + Authority.validateAuthority(new URL("https://I.com/something/#haha"))); + + assertTrue(ex.getMessage().contains("authority is invalid format (contains fragment)")); } - @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "authority is invalid format \\(contains fragment\\)") - public void testAADAuthorityConstructor_UrlHasFragment() throws MalformedURLException { - Authority.validateAuthority(new URL("https://I.com/something/#haha")); + @Test + void testAADAuthorityConstructor_AuthorityHasQuery() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> + Authority.validateAuthority(new URL("https://I.com/not/?query=not-allowed"))); + + assertTrue(ex.getMessage().contains("authority cannot contain query parameters")); } + @ParameterizedTest + @MethodSource("com.microsoft.aad.msal4j.AuthorityTest#authoritiesWithEmptyPath") + void testValidateAuthorityEmptyPathSegments(String authority) { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> + Authority.validateAuthority(new URL(authority))); - @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "authority cannot contain query parameters") - public void testAADAuthorityConstructor_AuthorityHasQuery() - throws MalformedURLException { - Authority.validateAuthority(new URL("https://I.com/not/?query=not-allowed")); + assertEquals(IllegalArgumentExceptionMessages.AUTHORITY_URI_EMPTY_PATH_SEGMENT, ex.getMessage()); } + @Test + void testValidateAuthorityEmptyPath() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> + Authority.validateAuthority(new URL("https://login.microsoftonline.com"))); + + assertEquals(IllegalArgumentExceptionMessages.AUTHORITY_URI_EMPTY_PATH, ex.getMessage()); + } @Test - public void testConstructor_AADAuthority() throws MalformedURLException { + void testConstructor_AADAuthority() throws MalformedURLException { final AADAuthority aa = new AADAuthority(new URL(TestConfiguration.AAD_TENANT_ENDPOINT)); - Assert.assertNotNull(aa); - Assert.assertEquals(aa.authority(), + assertNotNull(aa); + assertEquals(aa.authority(), TestConfiguration.AAD_TENANT_ENDPOINT); - Assert.assertEquals(aa.host(), TestConfiguration.AAD_HOST_NAME); - Assert.assertEquals(aa.tokenEndpoint(), + assertEquals(aa.host(), TestConfiguration.AAD_HOST_NAME); + assertEquals(aa.tokenEndpoint(), TestConfiguration.AAD_TENANT_ENDPOINT + "oauth2/v2.0/token"); - Assert.assertEquals(aa.selfSignedJwtAudience(), + assertEquals(aa.selfSignedJwtAudience(), TestConfiguration.AAD_TENANT_ENDPOINT + "oauth2/v2.0/token"); - Assert.assertEquals(aa.tokenEndpoint(), + assertEquals(aa.tokenEndpoint(), TestConfiguration.AAD_TENANT_ENDPOINT + "oauth2/v2.0/token"); - Assert.assertEquals(aa.authorityType(), AuthorityType.AAD); - Assert.assertFalse(aa.isTenantless()); - Assert.assertEquals(aa.deviceCodeEndpoint(), + assertEquals(aa.authorityType(), AuthorityType.AAD); + assertFalse(aa.isTenantless()); + assertEquals(aa.deviceCodeEndpoint(), TestConfiguration.AAD_TENANT_ENDPOINT + "oauth2/v2.0/devicecode"); } @Test - public void testConstructor_B2CAuthority() throws MalformedURLException { + void testConstructor_B2CAuthority() throws MalformedURLException { final B2CAuthority aa = new B2CAuthority(new URL(TestConfiguration.B2C_AUTHORITY)); - Assert.assertNotNull(aa); - Assert.assertEquals(aa.authority(), + assertNotNull(aa); + assertEquals(aa.authority(), TestConfiguration.B2C_AUTHORITY + "/"); - Assert.assertEquals(aa.host(), TestConfiguration.B2C_HOST_NAME); - Assert.assertEquals(aa.selfSignedJwtAudience(), + assertEquals(aa.host(), TestConfiguration.B2C_HOST_NAME); + assertEquals(aa.selfSignedJwtAudience(), TestConfiguration.B2C_AUTHORITY_ENDPOINT + "/oauth2/v2.0/token?p=" + TestConfiguration.B2C_SIGN_IN_POLICY); - Assert.assertEquals(aa.tokenEndpoint(), + assertEquals(aa.tokenEndpoint(), TestConfiguration.B2C_AUTHORITY_ENDPOINT + "/oauth2/v2.0/token?p=" + TestConfiguration.B2C_SIGN_IN_POLICY); - Assert.assertEquals(aa.authorityType(), AuthorityType.B2C); - Assert.assertEquals(aa.tokenEndpoint(), + assertEquals(aa.authorityType(), AuthorityType.B2C); + assertEquals(aa.tokenEndpoint(), TestConfiguration.B2C_AUTHORITY_ENDPOINT + "/oauth2/v2.0/token?p=" + TestConfiguration.B2C_SIGN_IN_POLICY); - Assert.assertFalse(aa.isTenantless()); + assertFalse(aa.isTenantless()); } @Test - public void testConstructor_ADFSAuthority() throws MalformedURLException { + void testConstructor_ADFSAuthority() throws MalformedURLException { final ADFSAuthority a = new ADFSAuthority(new URL(TestConfiguration.ADFS_TENANT_ENDPOINT)); - Assert.assertNotNull(a); - Assert.assertEquals(a.authority(), TestConfiguration.ADFS_TENANT_ENDPOINT); - Assert.assertEquals(a.host(), TestConfiguration.ADFS_HOST_NAME); - Assert.assertEquals(a.selfSignedJwtAudience(), + assertNotNull(a); + assertEquals(a.authority(), TestConfiguration.ADFS_TENANT_ENDPOINT); + assertEquals(a.host(), TestConfiguration.ADFS_HOST_NAME); + assertEquals(a.selfSignedJwtAudience(), TestConfiguration.ADFS_TENANT_ENDPOINT + ADFSAuthority.TOKEN_ENDPOINT); - Assert.assertEquals(a.authorityType(), AuthorityType.ADFS); + assertEquals(a.authorityType(), AuthorityType.ADFS); - Assert.assertEquals(a.tokenEndpoint(), + assertEquals(a.tokenEndpoint(), TestConfiguration.ADFS_TENANT_ENDPOINT + ADFSAuthority.TOKEN_ENDPOINT); - Assert.assertFalse(a.isTenantless()); + assertFalse(a.isTenantless()); } @Test - public void testB2CAuthority_SameCanonicalAuthority() throws MalformedURLException { + void testB2CAuthority_SameCanonicalAuthority() throws MalformedURLException { PublicClientApplication pca = PublicClientApplication.builder("client_id"). b2cAuthority(TestConfiguration.B2C_AUTHORITY_CUSTOM_PORT).build(); - Assert.assertEquals(pca.authenticationAuthority.authority, - TestConfiguration.B2C_AUTHORITY_CUSTOM_PORT_TAIL_SLASH); + assertEquals(TestConfiguration.B2C_AUTHORITY_CUSTOM_PORT_TAIL_SLASH, + pca.authenticationAuthority.authority); PublicClientApplication pca2 = PublicClientApplication.builder("client_id"). b2cAuthority(TestConfiguration.B2C_AUTHORITY_CUSTOM_PORT_TAIL_SLASH).build(); - Assert.assertEquals(pca2.authenticationAuthority.authority, - TestConfiguration.B2C_AUTHORITY_CUSTOM_PORT_TAIL_SLASH); + assertEquals(TestConfiguration.B2C_AUTHORITY_CUSTOM_PORT_TAIL_SLASH, + pca2.authenticationAuthority.authority); } @Test - public void testNoAuthorityPassedIn_DefaultsToCommonAuthority() { + void testNoAuthorityPassedIn_DefaultsToCommonAuthority() { PublicClientApplication pca = PublicClientApplication.builder("client_id").build(); - Assert.assertEquals(pca.authority(), TestConfiguration.AAD_COMMON_AUTHORITY); - Assert.assertNotNull(pca.authenticationAuthority); - } - - @Test - public void testDoStaticInstanceDiscovery_ValidateTrue_TrustedAuthority() - throws Exception { - final AADAuthority aa = new AADAuthority(new URL(TestConfiguration.AAD_TENANT_ENDPOINT)); - //PS Assert.assertTrue(aa.doStaticInstanceDiscovery(true)); + assertEquals(TestConfiguration.AAD_COMMON_AUTHORITY, pca.authority()); + assertNotNull(pca.authenticationAuthority); } - @Test - public void testDoStaticInstanceDiscovery_ValidateTrue_UntrustedAuthority() - throws Exception { - final AADAuthority aa = new AADAuthority(new URL(TestConfiguration.AAD_UNKNOWN_TENANT_ENDPOINT)); - //PS Assert.assertFalse(aa.doStaticInstanceDiscovery(true)); + static Object[][] validCiamAuthoritiesAndTransformedAuthority() throws MalformedURLException { + return new Object[][]{{new URL("https://msidlabciam1.ciamlogin.com/"), new URL("https://msidlabciam1.ciamlogin.com/msidlabciam1.onmicrosoft.com/")}, + {new URL("https://msidlabciam1.ciamlogin.com/d57fb3d4-4b5a-4144-9328-9c1f7d58179d"), new URL("https://msidlabciam1.ciamlogin.com/d57fb3d4-4b5a-4144-9328-9c1f7d58179d")}, + {new URL("https://msidlabciam1.ciamlogin.com/msidlabciam1.onmicrosoft.com"), new URL("https://msidlabciam1.ciamlogin.com/msidlabciam1.onmicrosoft.com")}, + {new URL("https://msidlabciam1.ciamlogin.com/aDomain"), new URL("https://msidlabciam1.ciamlogin.com/aDomain")}}; } - @Test - public void testDoStaticInstanceDiscovery_ValidateFalse_TrustedAuthority() - throws Exception { - final AADAuthority aa = new AADAuthority(new URL(TestConfiguration.AAD_UNKNOWN_TENANT_ENDPOINT)); - //PS Assert.assertTrue(aa.doStaticInstanceDiscovery(false)); + static Object[][] ciamAuthorities() throws MalformedURLException { + return new Object[][]{{new URL("https://msidlabciam1.ciamlogin.com/")}, + {new URL("https://msidlabciam1.ciamlogin.com/d57fb3d4-4b5a-4144-9328-9c1f7d58179d/")}, + {new URL("https://msidlabciam1.ciamlogin.com/msidlabciam1.onmicrosoft.com/")}, + {new URL("https://msidlabciam1.ciamlogin.com/aDomain/")}}; } - - @DataProvider(name = "authoritiesWithEmptyPath") - public static Object[][] createData() { + static Object[][] authoritiesWithEmptyPath() { return new Object[][]{{"https://login.microsoftonline.com/"}, {"https://login.microsoftonline.com//tenant"}, {"https://login.microsoftonline.com////tenant//path1"}}; } - - @Test(dataProvider = "authoritiesWithEmptyPath", expectedExceptions = IllegalArgumentException.class, - expectedExceptionsMessageRegExp = IllegalArgumentExceptionMessages.AUTHORITY_URI_EMPTY_PATH_SEGMENT) - public void testValidateAuthorityEmptyPathSegments(String authority) throws MalformedURLException { - Authority.validateAuthority(new URL(authority)); - } - - @Test(expectedExceptions = IllegalArgumentException.class, - expectedExceptionsMessageRegExp = IllegalArgumentExceptionMessages.AUTHORITY_URI_EMPTY_PATH) - public void testValidateAuthorityEmptyPath() throws MalformedURLException { - Authority.validateAuthority(new URL("https://login.microsoftonline.com")); - } } 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 66dd4f3a..8c854be1 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 @@ -3,18 +3,23 @@ package com.microsoft.aad.msal4j; -import org.testng.Assert; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLDecoder; import java.util.*; -public class AuthorizationRequestUrlParametersTest { +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class AuthorizationRequestUrlParametersTest { @Test - public void testBuilder_onlyRequiredParameters() throws UnsupportedEncodingException { + void testBuilder_onlyRequiredParameters() throws UnsupportedEncodingException { PublicClientApplication app = PublicClientApplication.builder("client_id").build(); String redirectUri = "http://localhost:8080"; @@ -30,23 +35,23 @@ public void testBuilder_onlyRequiredParameters() throws UnsupportedEncodingExcep .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); + assertEquals(parameters.responseMode(), ResponseMode.FORM_POST); + assertEquals(parameters.redirectUri(), redirectUri); + assertEquals(parameters.scopes().size(), 4); + assertEquals(parameters.extraQueryParameters.size(), 2); - Assert.assertNull(parameters.loginHint()); - Assert.assertNull(parameters.codeChallenge()); - Assert.assertNull(parameters.codeChallengeMethod()); - Assert.assertNull(parameters.correlationId()); - Assert.assertNull(parameters.nonce()); - Assert.assertNull(parameters.prompt()); - Assert.assertNull(parameters.state()); + assertNull(parameters.loginHint()); + assertNull(parameters.codeChallenge()); + assertNull(parameters.codeChallengeMethod()); + assertNull(parameters.correlationId()); + assertNull(parameters.nonce()); + assertNull(parameters.prompt()); + assertNull(parameters.state()); URL authorizationUrl = app.getAuthorizationRequestUrl(parameters); - Assert.assertEquals(authorizationUrl.getHost(), "login.microsoftonline.com"); - Assert.assertEquals(authorizationUrl.getPath(), "/common/oauth2/v2.0/authorize"); + assertEquals(authorizationUrl.getHost(), "login.microsoftonline.com"); + assertEquals(authorizationUrl.getPath(), "/common/oauth2/v2.0/authorize"); Map queryParameters = new HashMap<>(); String query = authorizationUrl.getQuery(); @@ -59,27 +64,27 @@ public void testBuilder_onlyRequiredParameters() throws UnsupportedEncodingExcep URLDecoder.decode(pair.substring(idx + 1), "UTF-8")); } - Assert.assertEquals(queryParameters.get("scope"), "openid profile offline_access scope"); - Assert.assertEquals(queryParameters.get("response_type"), "code"); - 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"); + assertEquals(queryParameters.get("scope"), "openid profile offline_access scope"); + assertEquals(queryParameters.get("response_type"), "code"); + assertEquals(queryParameters.get("redirect_uri"), "http://localhost:8080"); + assertEquals(queryParameters.get("client_id"), "client_id"); + assertEquals(queryParameters.get("response_mode"), "form_post"); + assertEquals(queryParameters.get("id_token_hint"),"test"); } - @Test(expectedExceptions = IllegalArgumentException.class) - public void testBuilder_invalidRequiredParameters() { + @Test + void testBuilder_invalidRequiredParameters() { String redirectUri = ""; Set scope = Collections.singleton("scope"); - AuthorizationRequestUrlParameters parameters = + assertThrows(IllegalArgumentException.class, () -> AuthorizationRequestUrlParameters .builder(redirectUri, scope) - .build(); + .build()); } @Test - public void testBuilder_conflictingParameters() { + void testBuilder_conflictingParameters() { PublicClientApplication app = PublicClientApplication.builder("client_id").build(); String redirectUri = "http://localhost:8080"; @@ -95,7 +100,7 @@ public void testBuilder_conflictingParameters() { } @Test - public void testBuilder_optionalParameters() throws UnsupportedEncodingException { + void testBuilder_optionalParameters() throws UnsupportedEncodingException { Set clientCapabilities = new HashSet<>(); clientCapabilities.add("llt"); clientCapabilities.add("ssm"); @@ -134,23 +139,23 @@ public void testBuilder_optionalParameters() throws UnsupportedEncodingException URLDecoder.decode(pair.substring(idx + 1), "UTF-8")); } - Assert.assertEquals(queryParameters.get("scope"), + assertEquals(queryParameters.get("scope"), "openid profile offline_access scope extraScopeToConsent1 extraScopeToConsent2"); - Assert.assertEquals(queryParameters.get("response_type"), "code"); - Assert.assertEquals(queryParameters.get("redirect_uri"), "http://localhost:8080"); - Assert.assertEquals(queryParameters.get("client_id"), "client_id"); - Assert.assertEquals(queryParameters.get("prompt"), "select_account"); - Assert.assertEquals(queryParameters.get("response_mode"), "query"); - Assert.assertEquals(queryParameters.get("code_challenge"), "challenge"); - Assert.assertEquals(queryParameters.get("code_challenge_method"), "method"); - Assert.assertEquals(queryParameters.get("state"), "app_state"); - Assert.assertEquals(queryParameters.get("nonce"), "app_nonce"); - Assert.assertEquals(queryParameters.get("correlation_id"), "corr_id"); - Assert.assertEquals(queryParameters.get("login_hint"), "hint"); - Assert.assertEquals(queryParameters.get("domain_hint"), "domain_hint"); - Assert.assertEquals(queryParameters.get("claims"), "{\"id_token\":{\"auth_time\":{\"essential\":true}},\"access_token\":{\"auth_time\":{\"essential\":true},\"xms_cc\":{\"values\":[\"llt\",\"ssm\"]}}}"); + assertEquals(queryParameters.get("response_type"), "code"); + assertEquals(queryParameters.get("redirect_uri"), "http://localhost:8080"); + assertEquals(queryParameters.get("client_id"), "client_id"); + assertEquals(queryParameters.get("prompt"), "select_account"); + assertEquals(queryParameters.get("response_mode"), "query"); + assertEquals(queryParameters.get("code_challenge"), "challenge"); + assertEquals(queryParameters.get("code_challenge_method"), "method"); + assertEquals(queryParameters.get("state"), "app_state"); + assertEquals(queryParameters.get("nonce"), "app_nonce"); + assertEquals(queryParameters.get("correlation_id"), "corr_id"); + assertEquals(queryParameters.get("login_hint"), "hint"); + assertEquals(queryParameters.get("domain_hint"), "domain_hint"); + assertEquals(queryParameters.get("claims"), "{\"id_token\":{\"auth_time\":{\"essential\":true}},\"access_token\":{\"auth_time\":{\"essential\":true},\"xms_cc\":{\"values\":[\"llt\",\"ssm\"]}}}"); // CCS routing - Assert.assertEquals(queryParameters.get(HttpHeaders.X_ANCHOR_MAILBOX), String.format(HttpHeaders.X_ANCHOR_MAILBOX_UPN_FORMAT, "hint")); + assertEquals(queryParameters.get(HttpHeaders.X_ANCHOR_MAILBOX), String.format(HttpHeaders.X_ANCHOR_MAILBOX_UPN_FORMAT, "hint")); } } diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/CacheFormatTests.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/CacheFormatTests.java index 6714b82b..7adb399f 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/CacheFormatTests.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/CacheFormatTests.java @@ -7,15 +7,19 @@ import com.nimbusds.oauth2.sdk.http.HTTPResponse; import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; import net.minidev.json.JSONObject; -import org.easymock.EasyMock; import org.json.JSONException; -import org.powermock.api.easymock.PowerMock; import org.skyscreamer.jsonassert.JSONAssert; import org.skyscreamer.jsonassert.JSONCompareMode; import org.skyscreamer.jsonassert.JSONCompareResult; import org.skyscreamer.jsonassert.comparator.DefaultComparator; -import org.testng.Assert; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; import java.io.IOException; import java.net.URI; @@ -28,7 +32,9 @@ import static com.microsoft.aad.msal4j.Constants.POINT_DELIMITER; -public class CacheFormatTests extends AbstractMsalTests { +@ExtendWith(MockitoExtension.class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class CacheFormatTests { String TOKEN_RESPONSE = "/token_response.json"; String TOKEN_RESPONSE_ID_TOKEN = "/token_response_id_token.json"; @@ -53,7 +59,7 @@ public class CacheFormatTests extends AbstractMsalTests { String EXTENDED_EXPIRES_ON_PLACEHOLDER = ""; @Test - public void cacheDeserializationSerializationTest() throws IOException, URISyntaxException, JSONException { + void cacheDeserializationSerializationTest() throws IOException, URISyntaxException, JSONException { ITokenCache tokenCache = new TokenCache(null); String previouslyStoredCache = readResource("/cache_data/serialized_cache.json"); @@ -108,17 +114,17 @@ public void compareValues(String s, Object o, Object o1, JSONCompareResult jsonC } @Test - public void AADTokenCacheEntitiesFormatTest() throws JSONException, IOException, ParseException, URISyntaxException { + void AADTokenCacheEntitiesFormatTest() throws JSONException, IOException, ParseException, URISyntaxException { tokenCacheEntitiesFormatTest("/AAD_cache_data"); } @Test - public void MSATokenCacheEntitiesFormatTest() throws JSONException, IOException, ParseException, URISyntaxException { + void MSATokenCacheEntitiesFormatTest() throws JSONException, IOException, ParseException, URISyntaxException { tokenCacheEntitiesFormatTest("/MSA_cache_data"); } @Test - public void FociTokenCacheEntitiesFormatTest() throws JSONException, IOException, ParseException, URISyntaxException { + void FociTokenCacheEntitiesFormatTest() throws JSONException, IOException, ParseException, URISyntaxException { tokenCacheEntitiesFormatTest("/Foci_cache_data"); } @@ -145,30 +151,18 @@ public void tokenCacheEntitiesFormatTest(String folder) throws URISyntaxExceptio null, new TelemetryManager(null, false)); - TokenRequestExecutor request = PowerMock.createPartialMock( - TokenRequestExecutor.class, new String[]{"createOauthHttpRequest"}, - new AADAuthority(new URL(AUTHORIZE_REQUEST_URL)), msalRequest, serviceBundle); + TokenRequestExecutor request = spy(new TokenRequestExecutor( + new AADAuthority(new URL(AUTHORIZE_REQUEST_URL)), msalRequest, serviceBundle)); + OAuthHttpRequest msalOAuthHttpRequest = mock(OAuthHttpRequest.class); + HTTPResponse httpResponse = mock(HTTPResponse.class); - OAuthHttpRequest msalOAuthHttpRequest = PowerMock.createMock(OAuthHttpRequest.class); - - HTTPResponse httpResponse = PowerMock.createMock(HTTPResponse.class); - - EasyMock.expect(request.createOauthHttpRequest()).andReturn(msalOAuthHttpRequest).times(1); - EasyMock.expect(msalOAuthHttpRequest.send()).andReturn(httpResponse).times(1); - EasyMock.expect(httpResponse.getStatusCode()).andReturn(200).times(1); - EasyMock.expect(httpResponse.getContentAsJSONObject()) - .andReturn( - JSONObjectUtils.parse(tokenResponse)) - .times(1); - httpResponse.ensureStatusCode(200); - EasyMock.expectLastCall(); - - PowerMock.replay(request, msalOAuthHttpRequest, httpResponse); + doReturn(msalOAuthHttpRequest).when(request).createOauthHttpRequest(); + doReturn(httpResponse).when(msalOAuthHttpRequest).send(); + doReturn(200).when(httpResponse).getStatusCode(); + doReturn(JSONObjectUtils.parse(tokenResponse)).when(httpResponse).getContentAsJSONObject(); final AuthenticationResult result = request.executeTokenRequest(); - PowerMock.verifyAll(); - TokenCache tokenCache = new TokenCache(); tokenCache.saveTokens(request, result, "login.microsoftonline.com"); @@ -183,11 +177,11 @@ public void tokenCacheEntitiesFormatTest(String folder) throws URISyntaxExceptio private void validateAccessTokenCacheEntity(String folder, String tokenResponse, TokenCache tokenCache) throws IOException, URISyntaxException, ParseException, JSONException { - Assert.assertEquals(tokenCache.accessTokens.size(), 1); + assertEquals(tokenCache.accessTokens.size(), 1); String keyActual = tokenCache.accessTokens.keySet().stream().findFirst().get(); String keyExpected = readResource(folder + AT_CACHE_ENTITY_KEY); - Assert.assertEquals(keyActual, keyExpected); + assertEquals(keyActual, keyExpected); String valueActual = JsonHelper.mapper.writeValueAsString(tokenCache.accessTokens.get(keyActual)); String valueExpected = readResource(folder + AT_CACHE_ENTITY); @@ -204,11 +198,11 @@ private void validateAccessTokenCacheEntity(String folder, String tokenResponse, private void validateRefreshTokenCacheEntity(String folder, TokenCache tokenCache) throws IOException, URISyntaxException, JSONException { - Assert.assertEquals(tokenCache.refreshTokens.size(), 1); + assertEquals(tokenCache.refreshTokens.size(), 1); String actualKey = tokenCache.refreshTokens.keySet().stream().findFirst().get(); String keyExpected = readResource(folder + RT_CACHE_ENTITY_KEY); - Assert.assertEquals(actualKey, keyExpected); + assertEquals(actualKey, keyExpected); String actualValue = JsonHelper.mapper.writeValueAsString(tokenCache.refreshTokens.get(actualKey)); String valueExpected = readResource(folder + RT_CACHE_ENTITY); @@ -240,11 +234,11 @@ public void compareValues(String s, Object o, Object o1, JSONCompareResult jsonC private void validateIdTokenCacheEntity(String folder, TokenCache tokenCache) throws IOException, URISyntaxException, JSONException { - Assert.assertEquals(tokenCache.idTokens.size(), 1); + assertEquals(tokenCache.idTokens.size(), 1); String actualKey = tokenCache.idTokens.keySet().stream().findFirst().get(); String keyExpected = readResource(folder + ID_TOKEN_CACHE_ENTITY_KEY); - Assert.assertEquals(actualKey, keyExpected); + assertEquals(actualKey, keyExpected); String actualValue = JsonHelper.mapper.writeValueAsString(tokenCache.idTokens.get(actualKey)); String valueExpected = readResource(folder + ID_TOKEN_CACHE_ENTITY); @@ -255,11 +249,11 @@ private void validateIdTokenCacheEntity(String folder, TokenCache tokenCache) private void validateAccountCacheEntity(String folder, TokenCache tokenCache) throws IOException, URISyntaxException, JSONException { - Assert.assertEquals(tokenCache.accounts.size(), 1); + assertEquals(tokenCache.accounts.size(), 1); String actualKey = tokenCache.accounts.keySet().stream().findFirst().get(); String keyExpected = readResource(folder + ACCOUNT_CACHE_ENTITY_KEY); - Assert.assertEquals(actualKey, keyExpected); + assertEquals(actualKey, keyExpected); String actualValue = JsonHelper.mapper.writeValueAsString(tokenCache.accounts.get(actualKey)); String valueExpected = readResource(folder + ACCOUNT_CACHE_ENTITY); @@ -274,11 +268,11 @@ private void validateAppMetadataCacheEntity(String folder, TokenCache tokenCache return; } - Assert.assertEquals(tokenCache.appMetadata.size(), 1); + assertEquals(tokenCache.appMetadata.size(), 1); String actualKey = tokenCache.appMetadata.keySet().stream().findFirst().get(); String keyExpected = readResource(folder + APP_METADATA_ENTITY_KEY); - Assert.assertEquals(actualKey, keyExpected); + assertEquals(actualKey, keyExpected); String actualValue = JsonHelper.mapper.writeValueAsString(tokenCache.appMetadata.get(actualKey)); String valueExpected = readResource(folder + APP_METADATA_CACHE_ENTITY); diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ClaimsTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ClaimsTest.java index 60eb99e8..708ea306 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ClaimsTest.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ClaimsTest.java @@ -3,9 +3,9 @@ package com.microsoft.aad.msal4j; -import org.testng.Assert; -import org.testng.annotations.Test; - +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; @@ -13,10 +13,11 @@ import java.util.HashSet; import java.util.List; -public class ClaimsTest { +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class ClaimsTest { @Test - public void testClaimsRequest_Format() { + void testClaimsRequest_Format() { List values = new ArrayList<>(); values.add("urn:mace:incommon:iap:silver"); @@ -29,11 +30,11 @@ public void testClaimsRequest_Format() { cr.requestClaimInIdToken("sub", new RequestedClaimAdditionalInfo(true, "248289761001", null)); cr.requestClaimInIdToken("auth_time", new RequestedClaimAdditionalInfo(false, null, null)); - Assert.assertEquals(cr.formatAsJSONString(), TestConfiguration.CLAIMS_REQUEST); + assertEquals(cr.formatAsJSONString(), TestConfiguration.CLAIMS_REQUEST); } @Test - public void testClaimsRequest_MergeWithClientCapabilitiesAndClaimsChallenge() throws URISyntaxException { + void testClaimsRequest_MergeWithClientCapabilitiesAndClaimsChallenge() throws URISyntaxException { List values = new ArrayList<>(); values.add("urn:mace:incommon:iap:silver"); @@ -64,18 +65,18 @@ public void testClaimsRequest_MergeWithClientCapabilitiesAndClaimsChallenge() th String mergedClaimsAndChallenge = JsonHelper.mergeJSONString(claimsChallenge, claimsRequest); String mergedAll = JsonHelper.mergeJSONString(claimsChallenge, mergedClaimsAndCapabilities); - Assert.assertEquals(clientCapabilities, TestConfiguration.CLIENT_CAPABILITIES); - Assert.assertEquals(claimsChallenge, TestConfiguration.CLAIMS_CHALLENGE); - Assert.assertEquals(claimsRequest, TestConfiguration.CLAIMS_REQUEST); - Assert.assertEquals(mergedClaimsAndCapabilities, TestConfiguration.MERGED_CLAIMS_AND_CAPABILITIES); - Assert.assertEquals(mergedClaimsAndChallenge, TestConfiguration.MERGED_CLAIMS_AND_CHALLENGE); - Assert.assertEquals(mergedAll, TestConfiguration.MERGED_CLAIMS_CAPABILITIES_AND_CHALLENGE); + assertEquals(clientCapabilities, TestConfiguration.CLIENT_CAPABILITIES); + assertEquals(claimsChallenge, TestConfiguration.CLAIMS_CHALLENGE); + assertEquals(claimsRequest, TestConfiguration.CLAIMS_REQUEST); + assertEquals(mergedClaimsAndCapabilities, TestConfiguration.MERGED_CLAIMS_AND_CAPABILITIES); + assertEquals(mergedClaimsAndChallenge, TestConfiguration.MERGED_CLAIMS_AND_CHALLENGE); + assertEquals(mergedAll, TestConfiguration.MERGED_CLAIMS_CAPABILITIES_AND_CHALLENGE); } @Test - public void testClaimsRequest_StringToClaimsRequest() { + void testClaimsRequest_StringToClaimsRequest() { ClaimsRequest cr = ClaimsRequest.formatAsClaimsRequest(TestConfiguration.CLAIMS_CHALLENGE); - Assert.assertEquals(cr.formatAsJSONString(), TestConfiguration.CLAIMS_CHALLENGE); + assertEquals(cr.formatAsJSONString(), TestConfiguration.CLAIMS_CHALLENGE); } } diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ClientCertificatePkcs12Test.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ClientCertificatePkcs12Test.java index 112a8f11..5af514ac 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ClientCertificatePkcs12Test.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ClientCertificatePkcs12Test.java @@ -3,70 +3,76 @@ package com.microsoft.aad.msal4j; -import org.easymock.EasyMock; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; import java.security.KeyStore; import java.security.KeyStoreSpi; import java.util.Arrays; import java.util.Collections; -import static org.testng.AssertJUnit.assertEquals; - -@Test -public class ClientCertificatePkcs12Test extends AbstractMsalTests { +@ExtendWith(MockitoExtension.class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class ClientCertificatePkcs12Test { private KeyStoreSpi keyStoreSpi; private KeyStore keystore; - @BeforeMethod - public void setUp() throws Exception { - keyStoreSpi = EasyMock.createMock(KeyStoreSpi.class); + @BeforeEach + void setUp() throws Exception { + keyStoreSpi = mock(KeyStoreSpi.class); keystore = new KeyStore(keyStoreSpi, null, "PKCS12") {}; keystore.load(null); } - @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "certificate not loaded from input stream") - public void testNoEntries() throws Exception { - EasyMock.expect(keyStoreSpi.engineAliases()) - .andReturn(Collections.enumeration(Collections.emptyList())).times(1); - EasyMock.replay(keyStoreSpi); + @Test + void testNoEntries() { + doReturn(Collections.enumeration(Collections.emptyList())).when(keyStoreSpi).engineAliases(); + + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> ClientCertificate.getPrivateKeyAlias(keystore)); - ClientCertificate.getPrivateKeyAlias(keystore); + assertTrue(ex.getMessage().contains("certificate not loaded from input stream")); } - @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "certificate not loaded from input stream") - public void testNoPrivateKey() throws Exception { - EasyMock.expect(keyStoreSpi.engineAliases()) - .andReturn(Collections.enumeration(Arrays.asList("CA_cert1", "CA_cert2"))).times(1); - EasyMock.expect(keyStoreSpi.engineEntryInstanceOf("CA_cert1", KeyStore.PrivateKeyEntry.class)).andReturn(false).times(1); - EasyMock.expect(keyStoreSpi.engineEntryInstanceOf("CA_cert2", KeyStore.PrivateKeyEntry.class)).andReturn(false).times(1); - EasyMock.replay(keyStoreSpi); + @Test + void testNoPrivateKey() { + doReturn(Collections.enumeration(Arrays.asList("CA_cert1", "CA_cert2"))).when(keyStoreSpi).engineAliases(); + doReturn(false).when(keyStoreSpi).engineEntryInstanceOf("CA_cert1", KeyStore.PrivateKeyEntry.class); + doReturn(false).when(keyStoreSpi).engineEntryInstanceOf("CA_cert2", KeyStore.PrivateKeyEntry.class); + + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> ClientCertificate.getPrivateKeyAlias(keystore)); - ClientCertificate.getPrivateKeyAlias(keystore); + assertTrue(ex.getMessage().contains("certificate not loaded from input stream")); } - @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "more than one certificate alias found in input stream") - public void testMultiplePrivateKeyAliases() throws Exception { - EasyMock.expect(keyStoreSpi.engineAliases()) - .andReturn(Collections.enumeration(Arrays.asList("private_key1", "private_key2", "CA_cert"))).times(1); - EasyMock.expect(keyStoreSpi.engineEntryInstanceOf("private_key1", KeyStore.PrivateKeyEntry.class)).andReturn(true).times(1); - EasyMock.expect(keyStoreSpi.engineEntryInstanceOf("private_key2", KeyStore.PrivateKeyEntry.class)).andReturn(true).times(1); - EasyMock.expect(keyStoreSpi.engineEntryInstanceOf("CA_cert", KeyStore.PrivateKeyEntry.class)).andReturn(false).times(1); - EasyMock.replay(keyStoreSpi); + @Test + void testMultiplePrivateKeyAliases() { + doReturn(Collections.enumeration(Arrays.asList("private_key1", "private_key2", "CA_cert"))).when(keyStoreSpi).engineAliases(); + doReturn(true).when(keyStoreSpi).engineEntryInstanceOf("private_key1", KeyStore.PrivateKeyEntry.class); + doReturn(true).when(keyStoreSpi).engineEntryInstanceOf("private_key2", KeyStore.PrivateKeyEntry.class); + lenient().doReturn(false).when(keyStoreSpi).engineEntryInstanceOf("CA_cert", KeyStore.PrivateKeyEntry.class); + + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> ClientCertificate.getPrivateKeyAlias(keystore)); - ClientCertificate.getPrivateKeyAlias(keystore); + assertTrue(ex.getMessage().contains("more than one certificate alias found in input stream")); } @Test - public void testMultipleEntriesButOnlyOnePrivateKey() throws Exception { - EasyMock.expect(keyStoreSpi.engineAliases()) - .andReturn(Collections.enumeration(Arrays.asList("CA_cert1", "private_key", "CA_cert2"))).times(1); - EasyMock.expect(keyStoreSpi.engineEntryInstanceOf("CA_cert1", KeyStore.PrivateKeyEntry.class)).andReturn(false).times(1); - EasyMock.expect(keyStoreSpi.engineEntryInstanceOf("private_key", KeyStore.PrivateKeyEntry.class)).andReturn(true).times(1); - EasyMock.expect(keyStoreSpi.engineEntryInstanceOf("CA_cert2", KeyStore.PrivateKeyEntry.class)).andReturn(false).times(1); - EasyMock.replay(keyStoreSpi); + void testMultipleEntriesButOnlyOnePrivateKey() throws Exception { + + doReturn(Collections.enumeration(Arrays.asList("CA_cert1", "private_key", "CA_cert2"))).when(keyStoreSpi).engineAliases(); + doReturn(false).when(keyStoreSpi).engineEntryInstanceOf("CA_cert1", KeyStore.PrivateKeyEntry.class); + doReturn(true).when(keyStoreSpi).engineEntryInstanceOf("private_key", KeyStore.PrivateKeyEntry.class); + doReturn(false).when(keyStoreSpi).engineEntryInstanceOf("CA_cert2", KeyStore.PrivateKeyEntry.class); String privateKeyAlias = ClientCertificate.getPrivateKeyAlias(keystore); assertEquals("private_key", privateKeyAlias); diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ClientCertificateTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ClientCertificateTest.java index c54aef87..7241a141 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ClientCertificateTest.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ClientCertificateTest.java @@ -3,54 +3,48 @@ package com.microsoft.aad.msal4j; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import java.math.BigInteger; import java.security.PrivateKey; import java.security.interfaces.RSAPrivateKey; -import org.easymock.EasyMock; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.testng.annotations.Test; - -@Test(groups = {"checkin"}) -@PrepareForTest({RSAPrivateKey.class}) -public class ClientCertificateTest extends AbstractMsalTests { +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class ClientCertificateTest { - @Test(expectedExceptions = NullPointerException.class, expectedExceptionsMessageRegExp = "PrivateKey is null or empty") - public void testNullKey() { - ClientCertificate.create((PrivateKey) null, null); - } + @Test + void testNullKey() { + NullPointerException ex = assertThrows(NullPointerException.class, () -> ClientCertificate.create((PrivateKey) null, null)); - @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "certificate key size must be at least 2048") - public void testInvalidKeysize() { - final RSAPrivateKey key = EasyMock.createMock(RSAPrivateKey.class); - final BigInteger modulus = EasyMock.createMock(BigInteger.class); - EasyMock.expect(modulus.bitLength()).andReturn(2047).times(1); - EasyMock.expect(key.getModulus()).andReturn(modulus).times(1); - EasyMock.replay(modulus, key); - ClientCertificate.create(key, null); + assertEquals("PrivateKey is null or empty", ex.getMessage()); } @Test - public void testGetClient() { - final RSAPrivateKey key = EasyMock.createMock(RSAPrivateKey.class); - final BigInteger modulus = EasyMock.createMock(BigInteger.class); - EasyMock.expect(modulus.bitLength()).andReturn(2048).times(1); - EasyMock.expect(key.getModulus()).andReturn(modulus).times(1); - EasyMock.replay(modulus, key); - final ClientCertificate kc = ClientCertificate.create(key, null); - assertNotNull(kc); + void testInvalidKeysize() { + final RSAPrivateKey key = mock(RSAPrivateKey.class); + final BigInteger modulus = mock(BigInteger.class); + doReturn(2047).when(modulus).bitLength(); + doReturn(modulus).when(key).getModulus(); + + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> ClientCertificate.create(key, null)); + + assertEquals("certificate key size must be at least 2048", ex.getMessage()); } @Test - public void testGetKey() { - final RSAPrivateKey key = EasyMock.createMock(RSAPrivateKey.class); - final BigInteger modulus = EasyMock.createMock(BigInteger.class); - EasyMock.expect(modulus.bitLength()).andReturn(2048).times(1); - EasyMock.expect(key.getModulus()).andReturn(modulus).times(1); - EasyMock.replay(modulus, key); + void testGetClient() { + final RSAPrivateKey key = mock(RSAPrivateKey.class); + final BigInteger modulus = mock(BigInteger.class); + doReturn(2048).when(modulus).bitLength(); + doReturn(modulus).when(key).getModulus(); + final ClientCertificate kc = ClientCertificate.create(key, null); assertNotNull(kc); } diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ClientCredentialTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ClientCredentialTest.java new file mode 100644 index 00000000..b3e73cbe --- /dev/null +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ClientCredentialTest.java @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.aad.msal4j; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class ClientCredentialTest { + + @Test + void testAssertionNullAndEmpty() { + assertThrows(NullPointerException.class, () -> + new ClientAssertion("")); + + assertThrows(NullPointerException.class, () -> + new ClientAssertion(null)); + } + + @Test + void testSecretNullAndEmpty() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> + new ClientSecret("")); + + assertTrue(ex.getMessage().contains("clientSecret is null or empty")); + + assertThrows(IllegalArgumentException.class, () -> + new ClientSecret(null)); + + assertTrue(ex.getMessage().contains("clientSecret is null or empty")); + } +} diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ClientSecretTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ClientSecretTest.java deleted file mode 100644 index d058c8cf..00000000 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ClientSecretTest.java +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.aad.msal4j; - -import org.testng.annotations.Test; - -/** - * - */ -public class ClientSecretTest { - - @Test(expectedExceptions = IllegalArgumentException.class, - expectedExceptionsMessageRegExp = "clientSecret is null or empty") - public void testConstructorNullClientId() { - new ClientSecret(null); - } - - @Test(expectedExceptions = IllegalArgumentException.class, - expectedExceptionsMessageRegExp = "clientSecret is null or empty") - public void testConstructorEmptyClientId() { - new ClientSecret(""); - } -} diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/DefaultHttpClientTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/DefaultHttpClientTest.java deleted file mode 100644 index 12ea22eb..00000000 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/DefaultHttpClientTest.java +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.aad.msal4j; - -import org.apache.commons.io.IOUtils; -import org.easymock.EasyMock; -import org.powermock.api.easymock.PowerMock; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.testng.PowerMockTestCase; -import org.testng.Assert; -import org.testng.annotations.Test; - -import javax.net.ssl.HttpsURLConnection; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.powermock.api.easymock.PowerMock.expectPrivate; - -@PrepareForTest({DefaultHttpClient.class}) -public class DefaultHttpClientTest extends PowerMockTestCase { - - @Test - public void ValidNotOkHttpResponse() throws Exception { - String TEST_URL = "https://somehost.com"; - - HttpsURLConnection mockCon = PowerMock.createMock(HttpsURLConnection.class); - - EasyMock.expect(mockCon.getResponseCode()) - .andReturn(HttpURLConnection.HTTP_INTERNAL_ERROR).times(1); - - String errorResponse = "Error Message"; - InputStream inputStream = IOUtils.toInputStream(errorResponse, "UTF-8"); - EasyMock.expect(mockCon.getErrorStream()).andReturn(inputStream).times(1); - - Map> expectedHeaders = new HashMap<>(); - expectedHeaders.put("header1", Arrays.asList("val1", "val2")); - - EasyMock.expect(mockCon.getHeaderFields()).andReturn(expectedHeaders).times(1); - - mockCon.setReadTimeout(0); - mockCon.setConnectTimeout(0); - - DefaultHttpClient httpClient = - PowerMock.createPartialMock(DefaultHttpClient.class, "openConnection"); - - expectPrivate(httpClient, "openConnection", EasyMock.isA(URL.class)).andReturn(mockCon); - - PowerMock.replayAll(mockCon, httpClient); - - - HttpRequest httpRequest = new HttpRequest(HttpMethod.GET, TEST_URL); - IHttpResponse response = httpClient.send(httpRequest); - - - Assert.assertEquals(response.body(), errorResponse); - Assert.assertEquals(response.headers(), expectedHeaders); - } -} diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/DeviceCodeFlowTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/DeviceCodeFlowTest.java deleted file mode 100644 index 1ac60c59..00000000 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/DeviceCodeFlowTest.java +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.aad.msal4j; - -import com.nimbusds.oauth2.sdk.http.HTTPResponse; -import org.easymock.Capture; -import org.easymock.EasyMock; -import org.powermock.api.easymock.PowerMock; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.testng.PowerMockTestCase; -import org.testng.Assert; -import org.testng.IObjectFactory; -import org.testng.annotations.ObjectFactory; -import org.testng.annotations.Test; - -import java.net.URL; -import java.net.URLEncoder; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; - - -@Test(groups = {"checkin"}) -@PrepareForTest({HttpHelper.class, PublicClientApplication.class}) -public class DeviceCodeFlowTest extends PowerMockTestCase { - private PublicClientApplication app = null; - - @ObjectFactory - public IObjectFactory getObjectFactory() { - return new org.powermock.modules.testng.PowerMockObjectFactory(); - } - - public static Map getQueryMap(String query) { - Map map = new HashMap<>(); - for (String param : query.split("&")) { - map.put(param.split("=")[0], param.split("=")[1]); - } - return map; - } - - String deviceCodeJsonResponse = "{\n" + - " \"user_code\": \"DW83JNP2P\",\n" + - " \"device_code\": \"DAQABAAEAAADRNYRQ3dhRFEeqWvq-yi6QodK2pb1iAA\",\n" + - " \"verification_uri\": \"https://aka.ms/devicelogin\",\n" + - " \"expires_in\": 900,\n" + - " \"interval\": 5,\n" + - " \"message\": \"To sign in, use a web browser to open the page https://aka.ms/devicelogin and enter the code DW83JNP2P to authenticate.\"\n" + - "}"; - - @SuppressWarnings("unchecked") - @Test - public void deviceCodeFlowTest() throws Exception { - app = PowerMock.createPartialMock(PublicClientApplication.class, - new String[]{"acquireTokenCommon"}, - PublicClientApplication.builder(TestConfiguration.AAD_CLIENT_ID) - .authority(TestConfiguration.AAD_TENANT_ENDPOINT)); - - Capture capturedMsalRequest = Capture.newInstance(); - - PowerMock.expectPrivate(app, "acquireTokenCommon", - EasyMock.capture(capturedMsalRequest), EasyMock.isA(AADAuthority.class)).andReturn( - AuthenticationResult.builder(). - accessToken("accessToken"). - expiresOn(new Date().getTime() + 100). - refreshToken("refreshToken"). - idToken("idToken").environment("environment").build()); - - PowerMock.mockStatic(HttpHelper.class); - - HttpResponse instanceDiscoveryResponse = new HttpResponse(); - instanceDiscoveryResponse.statusCode(200); - instanceDiscoveryResponse.body(TestConfiguration.INSTANCE_DISCOVERY_RESPONSE); - - EasyMock.expect( - HttpHelper.executeHttpRequest( - EasyMock.isA(HttpRequest.class), - EasyMock.isA(RequestContext.class), - EasyMock.isA(ServiceBundle.class))) - .andReturn(instanceDiscoveryResponse); - - HttpResponse deviceCodeResponse = new HttpResponse(); - deviceCodeResponse.statusCode(200); - deviceCodeResponse.body(deviceCodeJsonResponse); - - Capture capturedHttpRequest = Capture.newInstance(); - - EasyMock.expect( - HttpHelper.executeHttpRequest( - EasyMock.capture(capturedHttpRequest), - EasyMock.isA(RequestContext.class), - EasyMock.isA(ServiceBundle.class))) - .andReturn(deviceCodeResponse); - - PowerMock.replay(HttpHelper.class, HttpResponse.class); - - AtomicReference deviceCodeCorrelationId = new AtomicReference<>(); - - Consumer deviceCodeConsumer = (DeviceCode deviceCode) -> { - - // validate returned Device Code object - Assert.assertNotNull(deviceCode); - Assert.assertEquals(deviceCode.userCode(), "DW83JNP2P"); - Assert.assertEquals(deviceCode.deviceCode(), "DAQABAAEAAADRNYRQ3dhRFEeqWvq-yi6QodK2pb1iAA"); - Assert.assertEquals(deviceCode.verificationUri(), "https://aka.ms/devicelogin"); - Assert.assertEquals(deviceCode.expiresIn(), 900); - Assert.assertEquals(deviceCode.interval(), 5); - Assert.assertEquals(deviceCode.message(), "To sign in, use a web browser" + - " to open the page https://aka.ms/devicelogin and enter the code DW83JNP2P to authenticate."); - Assert.assertNotNull(deviceCode.correlationId()); - - deviceCodeCorrelationId.set(deviceCode.correlationId()); - }; - - PowerMock.replay(app); - - IAuthenticationResult authResult = app.acquireToken( - DeviceCodeFlowParameters.builder(Collections.singleton(TestConfiguration.AAD_RESOURCE_ID), deviceCodeConsumer) - .build()) - .get(); - - // validate HTTP GET request used to get device code - URL url = capturedHttpRequest.getValue().url(); - Assert.assertEquals(url.getAuthority(), TestConfiguration.AAD_PREFERRED_NETWORK_ENV_ALIAS); - Assert.assertEquals(url.getPath(), - "/" + TestConfiguration.AAD_TENANT_NAME + "/" + AADAuthority.DEVICE_CODE_ENDPOINT); - - String expectedScope = URLEncoder.encode(AbstractMsalAuthorizationGrant.COMMON_SCOPES_PARAM + - AbstractMsalAuthorizationGrant.SCOPES_DELIMITER + TestConfiguration.AAD_RESOURCE_ID, "UTF-8"); - String expectedBody = String.format("scope=%s&client_id=%s", expectedScope, TestConfiguration.AAD_CLIENT_ID); - - String body = capturedHttpRequest.getValue().body(); - Assert.assertEquals(body, expectedBody); - - // make sure same correlation id is used for acquireDeviceCode and acquireTokenByDeviceCode calls - Assert.assertEquals(capturedMsalRequest.getValue().headers().getReadonlyHeaderMap(). - get(HttpHeaders.CORRELATION_ID_HEADER_NAME), deviceCodeCorrelationId.get()); - Assert.assertNotNull(authResult); - - PowerMock.verify(); - } - - @Test(expectedExceptions = IllegalArgumentException.class, - expectedExceptionsMessageRegExp = "Invalid authority type. Device Flow is not supported by B2C authority.") - public void executeAcquireDeviceCode_B2CAuthorityUsed_IllegalArgumentExceptionThrown() - throws Exception { - - app = PublicClientApplication.builder("client_id") - .b2cAuthority(TestConfiguration.B2C_AUTHORITY) - .validateAuthority(false).build(); - - app.acquireToken - (DeviceCodeFlowParameters - .builder(Collections.singleton(TestConfiguration.AAD_RESOURCE_ID), (DeviceCode deviceCode) -> { - }) - .build()); - } - - @Test - public void executeAcquireDeviceCode_AuthenticaionPendingErrorReturned_AuthenticationExceptionThrown() - throws Exception { - - TelemetryManager telemetryManager = new TelemetryManager(null, false); - - AtomicReference> futureReference = - new AtomicReference<>(); - - Consumer deviceCodeConsumer = (DeviceCode deviceCode) -> { - }; - - app = PublicClientApplication.builder("client_id") - .authority(TestConfiguration.AAD_TENANT_ENDPOINT) - .validateAuthority(false) - .correlationId("corr_id") - .build(); - - DeviceCodeFlowParameters parameters = - DeviceCodeFlowParameters.builder(Collections.singleton("default-scope"), deviceCodeConsumer) - .build(); - - final DeviceCodeFlowRequest dcr = new DeviceCodeFlowRequest( - parameters, - futureReference, - app, - new RequestContext(app, PublicApi.ACQUIRE_TOKEN_BY_DEVICE_CODE_FLOW, parameters)); - - - TokenRequestExecutor request = PowerMock.createPartialMock( - TokenRequestExecutor.class, new String[]{"createOauthHttpRequest"}, - new AADAuthority(new URL(TestConstants.ORGANIZATIONS_AUTHORITY)), - dcr, new ServiceBundle(null, null, telemetryManager)); - - OAuthHttpRequest msalOAuthHttpRequest = PowerMock.createMock(OAuthHttpRequest.class); - - HTTPResponse httpResponse = new HTTPResponse(HTTPResponse.SC_BAD_REQUEST); - - String content = "{\"error\":\"authorization_pending\"," + - "\"error_description\":\"AADSTS70016: Pending end-user authorization.\\r\\n" + - "Trace ID: 6c9dd244-0c65-4ea6-b121-0afd1c640200\\r\\n" + - "Correlation ID: ff60101b-cb23-4a52-82cb-9966f466327a\\r\\n" + - "Timestamp: 2018-03-14 20:15:43Z\"," + - "\"error_codes\":[70016],\"timestamp\":\"2018-03-14 20:15:43Z\"," + - "\"trace_id\":\"6c9dd244-0c65-4ea6-b121-0afd1c640200\"," + - "\"correlation_id\":\"ff60101b-cb23-4a52-82cb-9966f466327a\"}"; - - httpResponse.setContent(content); - httpResponse.setContentType(HTTPContentType.ApplicationJSON.contentType); - - EasyMock.expect(request.createOauthHttpRequest()).andReturn(msalOAuthHttpRequest).times(1); - EasyMock.expect(msalOAuthHttpRequest.send()).andReturn(httpResponse).times(1); - - PowerMock.replay(request, msalOAuthHttpRequest); - - try { - request.executeTokenRequest(); - Assert.fail("Expected MsalException was not thrown"); - } catch (MsalServiceException ex) { - - Assert.assertEquals(ex.errorCode(), AuthenticationErrorCode.AUTHORIZATION_PENDING); - } - PowerMock.verifyAll(); - } -} \ No newline at end of file diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/HttpHeaderTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/HttpHeaderTest.java index 435dffe1..fb4f4757 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/HttpHeaderTest.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/HttpHeaderTest.java @@ -3,18 +3,24 @@ package com.microsoft.aad.msal4j; -import org.testng.Assert; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.api.BeforeAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import java.util.Collections; import java.util.HashMap; import java.util.Map; -@Test(groups = {"checkin"}) -public class HttpHeaderTest { +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class HttpHeaderTest { @Test - public void testHttpHeaderConstructor() { + void testHttpHeaderConstructor() { PublicClientApplication app = PublicClientApplication .builder("client-id") @@ -34,17 +40,17 @@ public void testHttpHeaderConstructor() { Map httpHeaderMap = httpHeaders.getReadonlyHeaderMap(); - Assert.assertEquals(httpHeaderMap.get(HttpHeaders.PRODUCT_HEADER_NAME), HttpHeaders.PRODUCT_HEADER_VALUE); - Assert.assertEquals(httpHeaderMap.get(HttpHeaders.PRODUCT_VERSION_HEADER_NAME), HttpHeaders.PRODUCT_VERSION_HEADER_VALUE); - Assert.assertEquals(httpHeaderMap.get(HttpHeaders.OS_HEADER_NAME), HttpHeaders.OS_HEADER_VALUE); - Assert.assertEquals(httpHeaderMap.get(HttpHeaders.CPU_HEADER_NAME), HttpHeaders.CPU_HEADER_VALUE); - Assert.assertEquals(httpHeaderMap.get(HttpHeaders.APPLICATION_NAME_HEADER_NAME), "app-name"); - Assert.assertEquals(httpHeaderMap.get(HttpHeaders.APPLICATION_VERSION_HEADER_NAME), "app-version"); - Assert.assertEquals(httpHeaderMap.get(HttpHeaders.CORRELATION_ID_HEADER_NAME), "correlation-id"); + assertEquals(httpHeaderMap.get(HttpHeaders.PRODUCT_HEADER_NAME), HttpHeaders.PRODUCT_HEADER_VALUE); + assertEquals(httpHeaderMap.get(HttpHeaders.PRODUCT_VERSION_HEADER_NAME), HttpHeaders.PRODUCT_VERSION_HEADER_VALUE); + assertEquals(httpHeaderMap.get(HttpHeaders.OS_HEADER_NAME), HttpHeaders.OS_HEADER_VALUE); + assertEquals(httpHeaderMap.get(HttpHeaders.CPU_HEADER_NAME), HttpHeaders.CPU_HEADER_VALUE); + assertEquals(httpHeaderMap.get(HttpHeaders.APPLICATION_NAME_HEADER_NAME), "app-name"); + assertEquals(httpHeaderMap.get(HttpHeaders.APPLICATION_VERSION_HEADER_NAME), "app-version"); + assertEquals(httpHeaderMap.get(HttpHeaders.CORRELATION_ID_HEADER_NAME), "correlation-id"); } @Test - public void testHttpHeaderConstructor_valuesNotSet() { + void testHttpHeaderConstructor_valuesNotSet() { PublicClientApplication app = PublicClientApplication .builder("client-id") @@ -61,17 +67,17 @@ public void testHttpHeaderConstructor_valuesNotSet() { Map httpHeaderMap = httpHeaders.getReadonlyHeaderMap(); - Assert.assertEquals(httpHeaderMap.get(HttpHeaders.PRODUCT_HEADER_NAME), HttpHeaders.PRODUCT_HEADER_VALUE); - Assert.assertEquals(httpHeaderMap.get(HttpHeaders.PRODUCT_VERSION_HEADER_NAME), HttpHeaders.PRODUCT_VERSION_HEADER_VALUE); - Assert.assertEquals(httpHeaderMap.get(HttpHeaders.OS_HEADER_NAME), HttpHeaders.OS_HEADER_VALUE); - Assert.assertEquals(httpHeaderMap.get(HttpHeaders.CPU_HEADER_NAME), HttpHeaders.CPU_HEADER_VALUE); - Assert.assertNull(httpHeaderMap.get(HttpHeaders.APPLICATION_NAME_HEADER_NAME)); - Assert.assertNull(httpHeaderMap.get(HttpHeaders.APPLICATION_VERSION_HEADER_NAME)); - Assert.assertNotNull(httpHeaderMap.get(HttpHeaders.CORRELATION_ID_HEADER_NAME)); + assertEquals(httpHeaderMap.get(HttpHeaders.PRODUCT_HEADER_NAME), HttpHeaders.PRODUCT_HEADER_VALUE); + assertEquals(httpHeaderMap.get(HttpHeaders.PRODUCT_VERSION_HEADER_NAME), HttpHeaders.PRODUCT_VERSION_HEADER_VALUE); + assertEquals(httpHeaderMap.get(HttpHeaders.OS_HEADER_NAME), HttpHeaders.OS_HEADER_VALUE); + assertEquals(httpHeaderMap.get(HttpHeaders.CPU_HEADER_NAME), HttpHeaders.CPU_HEADER_VALUE); + assertNull(httpHeaderMap.get(HttpHeaders.APPLICATION_NAME_HEADER_NAME)); + assertNull(httpHeaderMap.get(HttpHeaders.APPLICATION_VERSION_HEADER_NAME)); + assertNotNull(httpHeaderMap.get(HttpHeaders.CORRELATION_ID_HEADER_NAME)); } @Test - public void testHttpHeaderConstructor_userIdentifierUPN() { + void testHttpHeaderConstructor_userIdentifierUPN() { PublicClientApplication app = PublicClientApplication .builder("client-id") @@ -93,11 +99,11 @@ public void testHttpHeaderConstructor_userIdentifierUPN() { Map httpHeaderMap = httpHeaders.getReadonlyHeaderMap(); String expectedValue = String.format(HttpHeaders.X_ANCHOR_MAILBOX_UPN_FORMAT, upn); - Assert.assertEquals(httpHeaderMap.get(HttpHeaders.X_ANCHOR_MAILBOX), expectedValue); + assertEquals(httpHeaderMap.get(HttpHeaders.X_ANCHOR_MAILBOX), expectedValue); } @Test - public void testHttpHeaderConstructor_userIdentifierHomeAccountId() { + void testHttpHeaderConstructor_userIdentifierHomeAccountId() { PublicClientApplication app = PublicClientApplication .builder("client-id") @@ -118,11 +124,11 @@ public void testHttpHeaderConstructor_userIdentifierHomeAccountId() { Map httpHeaderMap = httpHeaders.getReadonlyHeaderMap(); - Assert.assertEquals(httpHeaderMap.get(HttpHeaders.X_ANCHOR_MAILBOX), "oid:userObjectId@userTenantId"); + assertEquals(httpHeaderMap.get(HttpHeaders.X_ANCHOR_MAILBOX), "oid:userObjectId@userTenantId"); } @Test - public void testHttpHeaderConstructor_extraHttpHeadersOverwriteLibraryHeaders() { + void testHttpHeaderConstructor_extraHttpHeadersOverwriteLibraryHeaders() { PublicClientApplication app = PublicClientApplication .builder("client-id") @@ -154,17 +160,17 @@ public void testHttpHeaderConstructor_extraHttpHeadersOverwriteLibraryHeaders() Map httpHeaderMap = httpHeaders.getReadonlyHeaderMap(); // Standard headers - Assert.assertEquals(httpHeaderMap.get(HttpHeaders.PRODUCT_HEADER_NAME), HttpHeaders.PRODUCT_HEADER_VALUE); - Assert.assertEquals(httpHeaderMap.get(HttpHeaders.PRODUCT_VERSION_HEADER_NAME), HttpHeaders.PRODUCT_VERSION_HEADER_VALUE); - Assert.assertEquals(httpHeaderMap.get(HttpHeaders.OS_HEADER_NAME), HttpHeaders.OS_HEADER_VALUE); - Assert.assertEquals(httpHeaderMap.get(HttpHeaders.CPU_HEADER_NAME), HttpHeaders.CPU_HEADER_VALUE); - Assert.assertEquals(httpHeaderMap.get(HttpHeaders.APPLICATION_VERSION_HEADER_NAME), "app-version"); - Assert.assertEquals(httpHeaderMap.get(HttpHeaders.CORRELATION_ID_HEADER_NAME), "correlation-id"); + assertEquals(httpHeaderMap.get(HttpHeaders.PRODUCT_HEADER_NAME), HttpHeaders.PRODUCT_HEADER_VALUE); + assertEquals(httpHeaderMap.get(HttpHeaders.PRODUCT_VERSION_HEADER_NAME), HttpHeaders.PRODUCT_VERSION_HEADER_VALUE); + assertEquals(httpHeaderMap.get(HttpHeaders.OS_HEADER_NAME), HttpHeaders.OS_HEADER_VALUE); + assertEquals(httpHeaderMap.get(HttpHeaders.CPU_HEADER_NAME), HttpHeaders.CPU_HEADER_VALUE); + assertEquals(httpHeaderMap.get(HttpHeaders.APPLICATION_VERSION_HEADER_NAME), "app-version"); + assertEquals(httpHeaderMap.get(HttpHeaders.CORRELATION_ID_HEADER_NAME), "correlation-id"); // Overwritten standard header - Assert.assertEquals(httpHeaderMap.get(HttpHeaders.APPLICATION_NAME_HEADER_NAME), uniqueAppName); + assertEquals(httpHeaderMap.get(HttpHeaders.APPLICATION_NAME_HEADER_NAME), uniqueAppName); // Extra header - Assert.assertEquals(httpHeaderMap.get(uniqueHeaderKey), uniqueHeaderValue); + assertEquals(httpHeaderMap.get(uniqueHeaderKey), uniqueHeaderValue); } } diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/HttpUtilsTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/HttpUtilsTest.java index 46b3da59..901fb358 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/HttpUtilsTest.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/HttpUtilsTest.java @@ -3,8 +3,10 @@ package com.microsoft.aad.msal4j; -import org.testng.Assert; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import java.util.Arrays; import java.util.Collections; @@ -12,26 +14,26 @@ import java.util.List; import java.util.Map; -@Test(groups = {"checkin"}) -public class HttpUtilsTest { +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class HttpUtilsTest { private final String COOKIE_HEADER_NAME = "Set-Cookie"; private final String COOKIE_HEADER_VALUE_1 = "298zf09hf012fh2"; private final String COOKIE_HEADER_VALUE_2 = "u32t4o3tb3gg43"; @Test - public void testHttpUtils_singleValueHeader() { + void testHttpUtils_singleValueHeader() { Map> singleValuedHeader = new HashMap>() {{ put(COOKIE_HEADER_NAME, Collections.singletonList(COOKIE_HEADER_VALUE_1)); }}; String headerValue = HttpUtils.headerValue(singleValuedHeader, COOKIE_HEADER_NAME); - Assert.assertEquals(headerValue, COOKIE_HEADER_VALUE_1); + assertEquals(headerValue, COOKIE_HEADER_VALUE_1); } @Test - public void testHttpUtils_multiValueHeader() { + void testHttpUtils_multiValueHeader() { Map> multiValuedHeader = new HashMap>() {{ put(COOKIE_HEADER_NAME, Arrays.asList(COOKIE_HEADER_VALUE_1, COOKIE_HEADER_VALUE_2)); @@ -39,17 +41,17 @@ public void testHttpUtils_multiValueHeader() { String headerValue = HttpUtils.headerValue(multiValuedHeader, COOKIE_HEADER_NAME); String expectedValue = COOKIE_HEADER_VALUE_1 + "," + COOKIE_HEADER_VALUE_2; - Assert.assertEquals(headerValue, expectedValue); + assertEquals(headerValue, expectedValue); } @Test - public void testHttpUtils_HeaderValueNull() { + void testHttpUtils_HeaderValueNull() { Map> nullValuedHeader = new HashMap>() {{ put(COOKIE_HEADER_NAME, null); }}; String headerValue = HttpUtils.headerValue(nullValuedHeader, COOKIE_HEADER_NAME); - Assert.assertNull(headerValue); + assertNull(headerValue); } } diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/MexParserTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/MexParserTest.java index 948d6a36..131357ad 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/MexParserTest.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/MexParserTest.java @@ -6,26 +6,33 @@ import java.io.BufferedReader; import java.io.FileReader; -import org.testng.Assert; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.Test; -@Test(groups = {"checkin"}) -public class MexParserTest { - - @BeforeTest - public void setup() { +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.AfterAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class MexParserTest { + + @BeforeAll + void setup() { System.setProperty("javax.xml.parsers.DocumentBuilderFactory", "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl"); } - @AfterTest - public void cleanup() { + @AfterAll + void cleanup() { System.clearProperty("javax.xml.parsers.DocumentBuilderFactory"); } @Test - public void testMexParsing() throws Exception { + void testMexParsingWs13() throws Exception { StringBuilder sb = new StringBuilder(); try (BufferedReader br = new BufferedReader(new FileReader( @@ -41,12 +48,12 @@ public void testMexParsing() throws Exception { } BindingPolicy endpoint = MexParser.getWsTrustEndpointFromMexResponse(sb.toString(), false); - Assert.assertEquals(endpoint.getUrl(), + assertEquals(endpoint.getUrl(), "https://msft.sts.microsoft.com/adfs/services/trust/13/usernamemixed"); } @Test - public void testMexParsingWs2005() throws Exception { + void testMexParsingWs2005() throws Exception { StringBuilder sb = new StringBuilder(); try (BufferedReader br = new BufferedReader(new FileReader( @@ -62,11 +69,11 @@ public void testMexParsingWs2005() throws Exception { } BindingPolicy endpoint = MexParser.getWsTrustEndpointFromMexResponse(sb .toString(), false); - Assert.assertEquals(endpoint.getUrl(), "https://msft.sts.microsoft.com/adfs/services/trust/2005/usernamemixed"); + assertEquals(endpoint.getUrl(), "https://msft.sts.microsoft.com/adfs/services/trust/2005/usernamemixed"); } @Test - public void testMexParsingIntegrated() throws Exception { + void testMexParsingIntegrated() throws Exception { StringBuilder sb = new StringBuilder(); try (BufferedReader br = new BufferedReader(new FileReader( @@ -82,7 +89,7 @@ public void testMexParsingIntegrated() throws Exception { } BindingPolicy endpoint = MexParser.getPolicyFromMexResponseForIntegrated(sb .toString(), false); - Assert.assertEquals(endpoint.getUrl(), + assertEquals(endpoint.getUrl(), "https://msft.sts.microsoft.com/adfs/services/trust/13/windowstransport"); } -} +} \ No newline at end of file diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/MsalOauthAuthorizatonGrantTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/MsalOauthAuthorizatonGrantTest.java index e465b409..3fcc6cd0 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/MsalOauthAuthorizatonGrantTest.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/MsalOauthAuthorizatonGrantTest.java @@ -5,32 +5,31 @@ import com.nimbusds.oauth2.sdk.AuthorizationCode; import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant; -import org.testng.Assert; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import static org.junit.jupiter.api.Assertions.assertNotNull; import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; -/** - * - */ -public class MsalOauthAuthorizatonGrantTest { +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class MsalOauthAuthorizatonGrantTest { @Test - public void testConstructor() { + void testConstructor() { final OAuthAuthorizationGrant grant = new OAuthAuthorizationGrant(null, new HashMap<>()); - Assert.assertNotNull(grant); + assertNotNull(grant); } @Test - public void testToParameters() throws URISyntaxException { + void testToParameters() throws URISyntaxException { final OAuthAuthorizationGrant grant = new OAuthAuthorizationGrant( new AuthorizationCodeGrant(new AuthorizationCode("grant"), new URI("http://microsoft.com")), null); - Assert.assertNotNull(grant); - Assert.assertNotNull(grant.toParameters()); + assertNotNull(grant); + assertNotNull(grant.toParameters()); } } diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/OAuthRequestValidationTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/OAuthRequestValidationTest.java deleted file mode 100644 index 9e3bf1ed..00000000 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/OAuthRequestValidationTest.java +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.aad.msal4j; - -import static com.microsoft.aad.msal4j.TestConfiguration.INSTANCE_DISCOVERY_RESPONSE; -import static org.powermock.api.support.membermodification.MemberMatcher.method; -import static org.powermock.api.support.membermodification.MemberModifier.replace; - -import com.nimbusds.jose.JOSEException; -import com.nimbusds.jose.JWSAlgorithm; -import com.nimbusds.jose.JWSHeader; -import com.nimbusds.jose.JWSSigner; -import com.nimbusds.jose.crypto.RSASSASigner; -import com.nimbusds.jwt.JWTClaimsSet; -import com.nimbusds.jwt.SignedJWT; -import org.powermock.core.classloader.annotations.PowerMockIgnore; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.testng.Assert; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import java.io.UnsupportedEncodingException; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.net.URLDecoder; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.security.interfaces.RSAPrivateKey; -import java.security.interfaces.RSAPublicKey; -import java.util.*; -import java.util.concurrent.ExecutionException; - -import org.easymock.EasyMock; -import org.powermock.api.easymock.PowerMock; - -@PowerMockIgnore({"javax.net.ssl.*"}) -@PrepareForTest({com.microsoft.aad.msal4j.OAuthHttpRequest.class, HttpHelper.class}) -public class OAuthRequestValidationTest extends AbstractMsalTests { - - final static String AUTHORITY = "https://loginXXX.windows.net/path/"; - - final static String CLIENT_ID = "ClientId"; - private final static String CLIENT_DUMMYSECRET = "ClientDummyPsw"; - - final static String SCOPES = "https://SomeResource.azure.net"; - private final static String DEFAULT_SCOPES = "openid profile offline_access"; - - final static String GRANT_TYPE_JWT = "urn:ietf:params:oauth:grant-type:jwt-bearer"; - final static String CLIENT_ASSERTION_TYPE_JWT = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; - final static String ON_BEHALF_OF_USE_JWT = "on_behalf_of"; - - final static String CLIENT_CREDENTIALS_GRANT_TYPE = "client_credentials"; - - final static String CLIENT_INFO_VALUE = "1"; - - final static String JWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ikpva" + - "G4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"; - - static String query; - - @SuppressWarnings("unchecked") - @BeforeMethod - public void init() throws Exception { - - PowerMock.mockStatic(HttpHelper.class); - HttpResponse httpResponse = new HttpResponse(); - httpResponse.body(INSTANCE_DISCOVERY_RESPONSE); - httpResponse.statusCode(HttpHelper.HTTP_STATUS_200); - EasyMock.expect( - HttpHelper.executeHttpRequest( - EasyMock.isA(HttpRequest.class), - EasyMock.isA(RequestContext.class), - EasyMock.isA(ServiceBundle.class))) - .andReturn(httpResponse); - - PowerMock.replay(HttpHelper.class); - - replace(method(OAuthHttpRequest.class, "send")). - with(new InvocationHandler() { - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - query = ((OAuthHttpRequest) proxy).getQuery(); - throw new MsalException("", AuthenticationErrorCode.UNKNOWN); - } - }); - } - - public static Map splitQuery(String query) throws UnsupportedEncodingException { - Map query_pairs = new LinkedHashMap<>(); - String[] pairs = query.split("&"); - for (String pair : pairs) { - int idx = pair.indexOf("="); - query_pairs.put(URLDecoder.decode(pair.substring(0, idx), "UTF-8"), URLDecoder.decode(pair.substring(idx + 1), "UTF-8")); - } - return query_pairs; - } - - private String getRSAjwt() throws NoSuchAlgorithmException, JOSEException { - // RSA signatures require a public and private RSA key pair, the public key - // must be made known to the JWS recipient in order to verify the signatures - KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("RSA"); - keyGenerator.initialize(2048); - - KeyPair kp = keyGenerator.genKeyPair(); - RSAPublicKey publicKey = (RSAPublicKey) kp.getPublic(); - RSAPrivateKey privateKey = (RSAPrivateKey) kp.getPrivate(); - - // Create RSA-signer with the private key - JWSSigner signer = new RSASSASigner(privateKey); - - // Prepare JWT with claims set - JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder(); - builder.issuer("alice"); - builder.subject("alice"); - List aud = new ArrayList(); - aud.add("https://app-one.com"); - aud.add("https://app-two.com"); - builder.audience(aud); - // Set expiration in 10 minutes - builder.expirationTime(new Date(new Date().getTime() + 1000 * 60 * 10)); - builder.notBeforeTime(new Date()); - builder.issueTime(new Date()); - builder.jwtID(UUID.randomUUID().toString()); - - JWTClaimsSet jwtClaims = builder.build(); - - SignedJWT signedJWT = new SignedJWT( - new JWSHeader(JWSAlgorithm.RS256), - jwtClaims); - - // Compute the RSA signature - signedJWT.sign(signer); - - return signedJWT.serialize(); - } - - @Test - public void oAuthRequest_for_acquireTokenByUserAssertion() throws Exception { - ConfidentialClientApplication app = - ConfidentialClientApplication.builder(CLIENT_ID, ClientCredentialFactory.createFromSecret(CLIENT_DUMMYSECRET)) - .authority(AUTHORITY) - .validateAuthority(false).build(); - - try { - // Using UserAssertion as Authorization Grants - OnBehalfOfParameters parameters = - OnBehalfOfParameters.builder(Collections.singleton(SCOPES), new UserAssertion(JWT)) - .build(); - - app.acquireToken(parameters).get(); - - } catch (ExecutionException ex) { - Assert.assertTrue(ex.getCause() instanceof MsalException); - } - - Map queryParams = splitQuery(query); - Assert.assertEquals(7, queryParams.size()); - - // validate Authorization Grants query params - Assert.assertEquals(GRANT_TYPE_JWT, queryParams.get("grant_type")); - Assert.assertEquals(JWT, queryParams.get("assertion")); - - // validate Client Authentication query params - Assert.assertEquals(CLIENT_ID, queryParams.get("client_id")); - Assert.assertEquals(CLIENT_DUMMYSECRET, queryParams.get("client_secret")); - - - Set scopes = new HashSet<>( - Arrays.asList(queryParams.get("scope").split(AbstractMsalAuthorizationGrant.SCOPES_DELIMITER))); - - // validate custom scopes - Assert.assertTrue(scopes.contains(SCOPES)); - - // validate common scopes - Assert.assertTrue(scopes.contains(AbstractMsalAuthorizationGrant.SCOPE_OPEN_ID)); - Assert.assertTrue(scopes.contains(AbstractMsalAuthorizationGrant.SCOPE_PROFILE)); - Assert.assertTrue(scopes.contains(AbstractMsalAuthorizationGrant.SCOPE_OFFLINE_ACCESS)); - - Assert.assertEquals("on_behalf_of", queryParams.get("requested_token_use")); - - Assert.assertEquals(CLIENT_INFO_VALUE, queryParams.get("client_info")); - } -} - diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/OauthHttpRequestTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/OauthHttpRequestTest.java deleted file mode 100644 index 8ef4f776..00000000 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/OauthHttpRequestTest.java +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.aad.msal4j; - -import com.nimbusds.oauth2.sdk.http.HTTPRequest.Method; -import com.nimbusds.oauth2.sdk.http.HTTPResponse; -import org.easymock.EasyMock; -import org.powermock.api.easymock.PowerMock; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.reflect.Whitebox; -import org.testng.Assert; -import org.testng.annotations.Test; - -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.testng.Assert.assertNotNull; - -@Test(groups = {"checkin"}) -@PrepareForTest({OAuthHttpRequest.class}) -public class OauthHttpRequestTest extends AbstractMsalTests { - - @Test - public void testConstructor() throws MalformedURLException { - final OAuthHttpRequest request = new OAuthHttpRequest( - Method.POST, - new URL("http://login.windows.net"), - null, - null, - null); - assertNotNull(request); - } - - @Test(expectedExceptions = IOException.class, expectedExceptionsMessageRegExp = "Couldn't parse Content-Type header: Invalid Content-Type value: Invalid content type string") - public void testCreateResponseContentTypeParsingFailure() throws Exception { - - final OAuthHttpRequest request = new OAuthHttpRequest( - Method.GET, - new URL("https://" + TestConfiguration.AAD_HOST_NAME), - null, - null, - null); - - final HttpResponse httpResponse = PowerMock - .createMock(HttpResponse.class); - - EasyMock.expect(httpResponse.statusCode()).andReturn(200).times(1); - - Map> headers = new HashMap<>(); - headers.put("Location", Collections.singletonList("https://location.pl")); - headers.put("Content-Type", Collections.singletonList("invalid-content")); - - EasyMock.expect(httpResponse.headers()).andReturn(headers).times(3); - EasyMock.expect(httpResponse.body()).andReturn("").times(1); - PowerMock.replay(httpResponse); - - final HTTPResponse response = Whitebox.invokeMethod(request, - "createOauthHttpResponseFromHttpResponse", httpResponse); - } - - @Test - public void testCreateResponse() throws Exception { - final OAuthHttpRequest request = new OAuthHttpRequest( - Method.GET, - new URL("https://" + TestConfiguration.AAD_HOST_NAME), - null, - null, - null); - - final HttpResponse httpResponse = PowerMock - .createMock(HttpResponse.class); - - EasyMock.expect(httpResponse.statusCode()).andReturn(200).times(1); - - Map> headers = new HashMap<>(); - headers.put("Location", Collections.singletonList("https://location.pl")); - headers.put("Content-Type", Collections.singletonList("application/x-www-form-urlencoded")); - - EasyMock.expect(httpResponse.headers()).andReturn(headers).times(3); - EasyMock.expect(httpResponse.body()).andReturn("").times(1); - PowerMock.replay(httpResponse); - - final HTTPResponse response = Whitebox.invokeMethod(request, - "createOauthHttpResponseFromHttpResponse", httpResponse); - - Assert.assertNotNull(response); - Assert.assertEquals(response.getLocation().getAuthority(), - "location.pl"); - Assert.assertEquals(response.getLocation().getScheme(), "https"); - Assert.assertNull(response.getContent()); - PowerMock.verifyAll(); - } -} diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/PublicClientApplicationTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/PublicClientApplicationTest.java deleted file mode 100644 index 16adab0e..00000000 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/PublicClientApplicationTest.java +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.aad.msal4j; - -import org.easymock.EasyMock; -import org.powermock.api.easymock.PowerMock; -import org.powermock.core.classloader.annotations.PowerMockIgnore; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.testng.PowerMockTestCase; -import org.testng.Assert; -import org.testng.annotations.Test; - -import java.util.Collections; -import java.util.Date; -import java.util.Map; -import java.util.concurrent.Future; - -@PowerMockIgnore({"javax.net.ssl.*"}) -@Test(groups = {"checkin"}) -@PrepareForTest({PublicClientApplication.class, - ClientCertificate.class, UserDiscoveryRequest.class}) -public class PublicClientApplicationTest extends PowerMockTestCase { - - private PublicClientApplication app = null; - - @SuppressWarnings("unchecked") - @Test - public void testAcquireToken_Username_Password() throws Exception { - app = PowerMock.createPartialMock(PublicClientApplication.class, - new String[]{"acquireTokenCommon"}, - PublicClientApplication.builder(TestConfiguration.AAD_CLIENT_ID) - .authority(TestConfiguration.AAD_TENANT_ENDPOINT)); - - PowerMock.expectPrivate(app, "acquireTokenCommon", - EasyMock.isA(MsalRequest.class), - EasyMock.isA(AADAuthority.class)) - .andReturn(AuthenticationResult.builder(). - accessToken("accessToken"). - expiresOn(new Date().getTime() + 100). - refreshToken("refreshToken"). - idToken("idToken").environment("environment").build() - ); - - UserDiscoveryResponse response = EasyMock - .createMock(UserDiscoveryResponse.class); - EasyMock.expect(response.isAccountFederated()).andReturn(false); - - PowerMock.mockStatic(UserDiscoveryRequest.class); - EasyMock.expect( - UserDiscoveryRequest.execute( - EasyMock.isA(String.class), - EasyMock.isA(Map.class), - EasyMock.isA(RequestContext.class), - EasyMock.isA(ServiceBundle.class))).andReturn(response); - - PowerMock.replay(app, response, UserDiscoveryRequest.class); - - Future result = app.acquireToken( - UserNamePasswordParameters - .builder(Collections.singleton("scopes"), "username", "password".toCharArray()) - .build()); - - IAuthenticationResult ar = result.get(); - Assert.assertNotNull(ar); - PowerMock.verifyAll(); - PowerMock.resetAll(app); - } -} diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/RequestThrottlingTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/RequestThrottlingTest.java index cd04b926..990b59b4 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/RequestThrottlingTest.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/RequestThrottlingTest.java @@ -4,27 +4,49 @@ package com.microsoft.aad.msal4j; import com.nimbusds.oauth2.sdk.http.HTTPResponse; -import org.easymock.EasyMock; -import org.powermock.api.easymock.PowerMock; -import org.testng.Assert; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import java.net.URI; import java.net.URISyntaxException; import java.util.*; -import static org.easymock.EasyMock.anyObject; - -public class RequestThrottlingTest extends AbstractMsalTests { +@ExtendWith(MockitoExtension.class) +@TestInstance(TestInstance.Lifecycle.PER_METHOD) +class RequestThrottlingTest { public final Integer THROTTLE_IN_SEC = 1; + public TokenEndpointResponseType responseType; + IHttpClient httpClientMock = mock(IHttpClient.class); + - @BeforeMethod + @BeforeEach void init() { ThrottlingCache.DEFAULT_THROTTLING_TIME_SEC = THROTTLE_IN_SEC; } + @AfterEach + void check() throws Exception { + + //throttlingTest() makes three non-throttled calls, so for a test without a retry there should be + // 3 invocations of httpClientMock.send(), and 6 invocations if the calls are set to retry + if (responseType == TokenEndpointResponseType.STATUS_CODE_500) { + verify(httpClientMock, times(6)).send(any()); + } else { + verify(httpClientMock, times(3)).send(any()); + } + } + private AuthorizationCodeParameters getAcquireTokenApiParameters(String scope) throws URISyntaxException { return AuthorizationCodeParameters .builder("auth_code", @@ -66,9 +88,9 @@ private enum TokenEndpointResponseType { } private PublicClientApplication getClientApplicationMockedWithOneTokenEndpointResponse( - TokenEndpointResponseType responseType) + TokenEndpointResponseType type) throws Exception { - IHttpClient httpClientMock = EasyMock.createMock(IHttpClient.class); + responseType = type; HttpResponse httpResponse = new HttpResponse(); Map> headers = new HashMap<>(); @@ -102,17 +124,9 @@ private PublicClientApplication getClientApplicationMockedWithOneTokenEndpointRe headers.put("Content-Type", Arrays.asList("application/json")); httpResponse.addHeaders(headers); - if (responseType == TokenEndpointResponseType.STATUS_CODE_500) { - // expected to called two times due to retry logic - EasyMock.expect(httpClientMock.send(anyObject())).andReturn(httpResponse).times(2); - } else { - EasyMock.expect(httpClientMock.send(anyObject())).andReturn(httpResponse).times(1); - } - - PublicClientApplication app = getPublicClientApp(httpClientMock); + doReturn(httpResponse).when(httpClientMock).send(any()); - PowerMock.replayAll(httpClientMock); - return app; + return getPublicClientApp(httpClientMock); } private void throttlingTest(TokenEndpointResponseType tokenEndpointResponseType) throws Exception { @@ -125,11 +139,9 @@ private void throttlingTest(TokenEndpointResponseType tokenEndpointResponseType) app.acquireToken(getAcquireTokenApiParameters("scope1")).join(); } catch (Exception ex) { if (!(ex.getCause() instanceof MsalServiceException)) { - Assert.fail("Unexpected exception"); + fail("Unexpected exception"); } } - PowerMock.verifyAll(); - PowerMock.resetAll(); // repeat same request #1, should be throttled try { @@ -137,7 +149,7 @@ private void throttlingTest(TokenEndpointResponseType tokenEndpointResponseType) app.acquireToken(getAcquireTokenApiParameters("scope1")).join(); } catch (Exception ex) { if (!(ex.getCause() instanceof MsalThrottlingException)) { - Assert.fail("Unexpected exception"); + fail("Unexpected exception"); } } @@ -147,11 +159,9 @@ private void throttlingTest(TokenEndpointResponseType tokenEndpointResponseType) app.acquireToken(getAcquireTokenApiParameters("scope2")).join(); } catch (Exception ex) { if (!(ex.getCause() instanceof MsalServiceException)) { - Assert.fail("Unexpected exception"); + fail("Unexpected exception"); } } - PowerMock.verifyAll(); - PowerMock.resetAll(); // repeat request #1, should not be throttled after // throttling for this request got expired @@ -161,37 +171,35 @@ private void throttlingTest(TokenEndpointResponseType tokenEndpointResponseType) app.acquireToken(getAcquireTokenApiParameters("scope1")).join(); } catch (Exception ex) { if (!(ex.getCause() instanceof MsalServiceException)) { - Assert.fail("Unexpected exception"); + fail("Unexpected exception"); } } - PowerMock.verifyAll(); - PowerMock.resetAll(); } @Test - public void STSResponseContains_RetryAfterHeader() throws Exception { + void STSResponseContains_RetryAfterHeader() throws Exception { throttlingTest(TokenEndpointResponseType.RETRY_AFTER_HEADER); } @Test - public void STSResponseContains_StatusCode429() throws Exception { + void STSResponseContains_StatusCode429() throws Exception { throttlingTest(TokenEndpointResponseType.STATUS_CODE_429); } @Test - public void STSResponseContains_StatusCode429_RetryAfterHeader() throws Exception { + void STSResponseContains_StatusCode429_RetryAfterHeader() throws Exception { // using big value for DEFAULT_THROTTLING_TIME_SEC to make sure that RetryAfterHeader value used instead ThrottlingCache.DEFAULT_THROTTLING_TIME_SEC = 1000; throttlingTest(TokenEndpointResponseType.STATUS_CODE_429_RETRY_AFTER_HEADER); } @Test - public void STSResponseContains_StatusCode500() throws Exception { + void STSResponseContains_StatusCode500() throws Exception { throttlingTest(TokenEndpointResponseType.STATUS_CODE_500); } @Test - public void STSResponseContains_StatusCode500_RetryAfterHeader() throws Exception { + void STSResponseContains_StatusCode500_RetryAfterHeader() throws Exception { // using big value for DEFAULT_THROTTLING_TIME_SEC to make sure that RetryAfterHeader value used instead ThrottlingCache.DEFAULT_THROTTLING_TIME_SEC = 1000; throttlingTest(TokenEndpointResponseType.STATUS_CODE_500_RETRY_AFTER_HEADER); diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ServerTelemetryTests.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ServerTelemetryTests.java index 6d826cf5..ae9c2e65 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ServerTelemetryTests.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/ServerTelemetryTests.java @@ -3,8 +3,11 @@ package com.microsoft.aad.msal4j; -import org.testng.Assert; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import java.nio.charset.StandardCharsets; import java.util.Arrays; @@ -16,6 +19,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class ServerTelemetryTests { private static final String SCHEMA_VERSION = "5"; @@ -42,28 +46,28 @@ public void serverTelemetryHeaders_correctSchema() { List currentRequestHeader = Arrays.asList(headers.get(CURRENT_REQUEST_HEADER_NAME).split("\\|")); // ["5", "831,"] - Assert.assertEquals(currentRequestHeader.size(), 2); - Assert.assertEquals(currentRequestHeader.get(0), SCHEMA_VERSION); + assertEquals(currentRequestHeader.size(), 2); + assertEquals(currentRequestHeader.get(0), SCHEMA_VERSION); // ["831", ""] List secondSegment = Arrays.asList(currentRequestHeader.get(1).split(",")); - Assert.assertEquals(secondSegment.get(0), String.valueOf(PublicApi.ACQUIRE_TOKEN_BY_AUTHORIZATION_CODE.getApiId())); - Assert.assertEquals(secondSegment.get(1), ""); + assertEquals(secondSegment.get(0), String.valueOf(PublicApi.ACQUIRE_TOKEN_BY_AUTHORIZATION_CODE.getApiId())); + assertEquals(secondSegment.get(1), ""); // Previous request test List previousRequestHeader = Arrays.asList(headers.get(LAST_REQUEST_HEADER_NAME).split("\\|")); // ["5","0","831,936732c6-74b9-4783-aad9-fa205eae8763","invalid_grant"] - Assert.assertEquals(previousRequestHeader.size(), 4); - Assert.assertEquals(previousRequestHeader.get(0), SCHEMA_VERSION); - Assert.assertEquals(previousRequestHeader.get(1), "0"); - Assert.assertEquals(previousRequestHeader.get(3), ERROR); + assertEquals(previousRequestHeader.size(), 4); + assertEquals(previousRequestHeader.get(0), SCHEMA_VERSION); + assertEquals(previousRequestHeader.get(1), "0"); + assertEquals(previousRequestHeader.get(3), ERROR); List thirdSegment = Arrays.asList(previousRequestHeader.get(2).split(",")); - Assert.assertEquals(thirdSegment.get(0), PUBLIC_API_ID); - Assert.assertEquals(thirdSegment.get(1), correlationId); + assertEquals(thirdSegment.get(0), PUBLIC_API_ID); + assertEquals(thirdSegment.get(1), correlationId); } @Test @@ -76,7 +80,7 @@ public void serverTelemetryHeaders_previewsRequestNull() { Map headers = serverSideTelemetry.getServerTelemetryHeaderMap(); - Assert.assertEquals(headers.get(LAST_REQUEST_HEADER_NAME), "5|3|||"); + assertEquals(headers.get(LAST_REQUEST_HEADER_NAME), "5|3|||"); } @Test @@ -95,7 +99,7 @@ public void serverTelemetryHeader_testMaximumHeaderSize() { byte[] lastRequestBytes = lastRequest.getBytes(StandardCharsets.UTF_8); - Assert.assertTrue(lastRequestBytes.length <= 350); + assertTrue(lastRequestBytes.length <= 350); } @Test @@ -124,30 +128,30 @@ public void serverTelemetryHeaders_multipleThreadsWrite() { List previousRequestHeader = Arrays.asList(headers.get(LAST_REQUEST_HEADER_NAME).split("\\|")); - Assert.assertEquals(previousRequestHeader.get(1), "10"); + assertEquals(previousRequestHeader.get(1), "10"); List thirdSegment = Arrays.asList(previousRequestHeader.get(2).split(",")); - Assert.assertEquals(thirdSegment.size(), 12); + assertEquals(thirdSegment.size(), 12); List fourthSegment = Arrays.asList(previousRequestHeader.get(3).split(",")); - Assert.assertEquals(fourthSegment.size(), 6); + assertEquals(fourthSegment.size(), 6); - Assert.assertTrue(headers.get(LAST_REQUEST_HEADER_NAME).getBytes(StandardCharsets.UTF_8).length < 350); + assertTrue(headers.get(LAST_REQUEST_HEADER_NAME).getBytes(StandardCharsets.UTF_8).length < 350); // Not all requests fit into first header, so they would get dispatched in the next request Map secondRequest = serverSideTelemetry.getServerTelemetryHeaderMap(); previousRequestHeader = Arrays.asList(secondRequest.get(LAST_REQUEST_HEADER_NAME).split("\\|")); - Assert.assertEquals(previousRequestHeader.get(1), "0"); + assertEquals(previousRequestHeader.get(1), "0"); thirdSegment = Arrays.asList(previousRequestHeader.get(2).split(",")); - Assert.assertEquals(thirdSegment.size(), 8); + assertEquals(thirdSegment.size(), 8); fourthSegment = Arrays.asList(previousRequestHeader.get(3).split(",")); - Assert.assertEquals(fourthSegment.size(), 4); + assertEquals(fourthSegment.size(), 4); - Assert.assertTrue(secondRequest.get(LAST_REQUEST_HEADER_NAME).getBytes(StandardCharsets.UTF_8).length < 350); + assertTrue(secondRequest.get(LAST_REQUEST_HEADER_NAME).getBytes(StandardCharsets.UTF_8).length < 350); } @@ -160,7 +164,7 @@ public void serverTelemetryHeaders_testRegionTelemetry() throws Exception { Map headers = serverSideTelemetry.getServerTelemetryHeaderMap(); - Assert.assertEquals(headers.get(CURRENT_REQUEST_HEADER_NAME), "5|831,,,0,0|"); + assertEquals(headers.get(CURRENT_REQUEST_HEADER_NAME), "5|831,,,0,0|"); serverSideTelemetry.getCurrentRequest().regionUsed("westus"); serverSideTelemetry.getCurrentRequest().regionSource(RegionTelemetry.REGION_SOURCE_IMDS.telemetryValue); @@ -168,14 +172,14 @@ public void serverTelemetryHeaders_testRegionTelemetry() throws Exception { headers = serverSideTelemetry.getServerTelemetryHeaderMap(); - Assert.assertEquals(headers.get(CURRENT_REQUEST_HEADER_NAME), "5|831,,westus,4,4|"); + assertEquals(headers.get(CURRENT_REQUEST_HEADER_NAME), "5|831,,westus,4,4|"); serverSideTelemetry.getCurrentRequest().regionUsed("centralus"); serverSideTelemetry.getCurrentRequest().regionSource(RegionTelemetry.REGION_SOURCE_ENV_VARIABLE.telemetryValue); serverSideTelemetry.getCurrentRequest().regionOutcome(RegionTelemetry.REGION_OUTCOME_DEVELOPER_AUTODETECT_MISMATCH.telemetryValue); headers = serverSideTelemetry.getServerTelemetryHeaderMap(); - Assert.assertEquals(headers.get(CURRENT_REQUEST_HEADER_NAME), "5|831,,centralus,3,3|"); + assertEquals(headers.get(CURRENT_REQUEST_HEADER_NAME), "5|831,,centralus,3,3|"); PublicClientApplication pca = PublicClientApplication.builder( "client"). @@ -191,11 +195,11 @@ public void serverTelemetryHeaders_testRegionTelemetry() throws Exception { .build()) .get(); - Assert.fail("Expected MsalException was not thrown"); + fail("Expected MsalException was not thrown"); } catch (Exception ex) { headers = pca.getServiceBundle().getServerSideTelemetry().getServerTelemetryHeaderMap(); - Assert.assertEquals(headers.get(CURRENT_REQUEST_HEADER_NAME), "5|300,,,1,2|"); + assertEquals(headers.get(CURRENT_REQUEST_HEADER_NAME), "5|300,,,0,0|"); } } diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/TelemetryTests.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/TelemetryTests.java index 7f159d36..1b299ae0 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/TelemetryTests.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/TelemetryTests.java @@ -3,9 +3,15 @@ package com.microsoft.aad.msal4j; -import org.testng.Assert; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.AfterEach; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; import java.net.URI; import java.net.URISyntaxException; @@ -14,8 +20,8 @@ import java.util.List; import java.util.function.Consumer; -@Test(groups = {"checkin"}) -public class TelemetryTests { +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class TelemetryTests { private List> eventsReceived = new ArrayList<>(); private String tenantId = "tenantId123"; @@ -34,22 +40,22 @@ private class MyTelemetryConsumer { }; } - @AfterMethod - private void cleanUp() { + @AfterEach + void cleanUp() { eventsReceived.clear(); } @Test - public void telemetryConsumerRegistration_ConsumerNotNullTest() { + void telemetryConsumerRegistration_ConsumerNotNullTest() { PublicClientApplication app = PublicClientApplication.builder("a1b2c3") .telemetryConsumer(new MyTelemetryConsumer().telemetryConsumer) .build(); - Assert.assertNotNull(app.telemetryConsumer()); + assertNotNull(app.telemetryConsumer()); } @Test - public void telemetryManagerFlush_EventCountTest() { + void telemetryManagerFlush_EventCountTest() { Consumer>> telemetryConsumer = new MyTelemetryConsumer().telemetryConsumer; TelemetryManager telemetryManager = new TelemetryManager(telemetryConsumer, false); @@ -68,11 +74,11 @@ public void telemetryManagerFlush_EventCountTest() { telemetryManager.flush(reqId, clientId); // 1 Default event, 1 API event, 1 Http event - Assert.assertEquals(eventsReceived.size(), 3); + assertEquals(eventsReceived.size(), 3); } @Test - public void onSendFailureTrue_SkipEventsIfSuccessfulTest() { + void onSendFailureTrue_SkipEventsIfSuccessfulTest() { Consumer>> telemetryConsumer = new MyTelemetryConsumer().telemetryConsumer; // Only send on failure @@ -93,7 +99,7 @@ public void onSendFailureTrue_SkipEventsIfSuccessfulTest() { telemetryManager.flush(reqId, clientId); // API event was successful, so count should be 0 - Assert.assertEquals(eventsReceived.size(), 0); + assertEquals(eventsReceived.size(), 0); eventsReceived.clear(); String reqId2 = telemetryManager.generateRequestId(); @@ -110,27 +116,27 @@ public void onSendFailureTrue_SkipEventsIfSuccessfulTest() { telemetryManager.flush(reqId2, clientId); // API event failed, so count should be 3 (1 default, 1 Api, 1 http) - Assert.assertEquals(eventsReceived.size(), 3); + assertEquals(eventsReceived.size(), 3); } @Test - public void telemetryInternalApi_ScrubTenantFromUriTest() throws Exception { - Assert.assertEquals(Event.scrubTenant(new URI("https://login.microsoftonline.com/common/oauth2/v2.0/token")), + void telemetryInternalApi_ScrubTenantFromUriTest() throws Exception { + assertEquals(Event.scrubTenant(new URI("https://login.microsoftonline.com/common/oauth2/v2.0/token")), "https://login.microsoftonline.com//oauth2/v2.0/token"); - Assert.assertEquals(Event.scrubTenant(new URI("https://login.microsoftonline.com/common")), + assertEquals(Event.scrubTenant(new URI("https://login.microsoftonline.com/common")), "https://login.microsoftonline.com/"); - Assert.assertEquals(Event.scrubTenant(new URI("https://login.microsoftonline.com/tfp/msidlabb2c.onmicrosoft.com/B2C_1_ROPC_Auth")), + assertEquals(Event.scrubTenant(new URI("https://login.microsoftonline.com/tfp/msidlabb2c.onmicrosoft.com/B2C_1_ROPC_Auth")), "https://login.microsoftonline.com/tfp//B2C_1_ROPC_Auth"); - Assert.assertNull(Event.scrubTenant(new URI("https://msidlabb2c.b2clogin.com/tfp/msidlabb2c.onmicrosoft.com/B2C_1_ROPC_Auth"))); + assertNull(Event.scrubTenant(new URI("https://msidlabb2c.b2clogin.com/tfp/msidlabb2c.onmicrosoft.com/B2C_1_ROPC_Auth"))); - Assert.assertNull(Event.scrubTenant(new URI("https://login.contoso.com/adfs"))); + assertNull(Event.scrubTenant(new URI("https://login.contoso.com/adfs"))); } @Test - public void telemetryContainsDefaultEventTest() { + void telemetryContainsDefaultEventTest() { Consumer>> telemetryConsumer = new MyTelemetryConsumer().telemetryConsumer; // Only send on failure @@ -149,11 +155,11 @@ public void telemetryContainsDefaultEventTest() { telemetryManager.flush(reqId, clientId); - Assert.assertEquals(eventsReceived.get(0).get("event_name"), "msal.default_event"); + assertEquals(eventsReceived.get(0).get("event_name"), "msal.default_event"); } @Test - public void telemetryFlushEventWithoutStopping_OrphanedEventIncludedTest() { + void telemetryFlushEventWithoutStopping_OrphanedEventIncludedTest() { Consumer>> telemetryConsumer = new MyTelemetryConsumer().telemetryConsumer; TelemetryManager telemetryManager = new TelemetryManager(telemetryConsumer, false); @@ -171,12 +177,12 @@ public void telemetryFlushEventWithoutStopping_OrphanedEventIncludedTest() { telemetryManager.stopEvent(reqId, apiEvent1); telemetryManager.flush(reqId, clientId); - Assert.assertEquals(eventsReceived.size(), 3); - Assert.assertTrue(eventsReceived.stream().anyMatch(event -> event.get("event_name").equals("msal.http_event"))); + assertEquals(eventsReceived.size(), 3); + assertTrue(eventsReceived.stream().anyMatch(event -> event.get("event_name").equals("msal.http_event"))); } @Test - public void telemetryStopEventWithoutStarting_NoExceptionThrownTest() { + void telemetryStopEventWithoutStarting_NoExceptionThrownTest() { Consumer>> telemetryConsumer = new MyTelemetryConsumer().telemetryConsumer; TelemetryManager telemetryManager = new TelemetryManager(telemetryConsumer, false); @@ -196,12 +202,12 @@ public void telemetryStopEventWithoutStarting_NoExceptionThrownTest() { telemetryManager.flush(reqId, clientId); - Assert.assertEquals(eventsReceived.size(), 2); - Assert.assertFalse(eventsReceived.stream().anyMatch(event -> event.get("event_name").equals("msal.http_event"))); + assertEquals(eventsReceived.size(), 2); + assertFalse(eventsReceived.stream().anyMatch(event -> event.get("event_name").equals("msal.http_event"))); } @Test - public void piiLoggingEnabled_ApiEventHashTest() { + void piiLoggingEnabled_ApiEventHashTest() { Consumer>> telemetryConsumer = new MyTelemetryConsumer().telemetryConsumer; TelemetryManager telemetryManager = new TelemetryManager(telemetryConsumer, false); @@ -215,12 +221,12 @@ public void piiLoggingEnabled_ApiEventHashTest() { apiEvent.setWasSuccessful(true); telemetryManager.stopEvent(reqId, apiEvent); - Assert.assertNotNull(apiEvent.get("msal.tenant_id")); - Assert.assertNotEquals(apiEvent.get("msal.tenant_id"), tenantId); + assertNotNull(apiEvent.get("msal.tenant_id")); + assertNotEquals(apiEvent.get("msal.tenant_id"), tenantId); } @Test - public void piiLoggingEnabledFalse_TenantIdUserIdSetToNullTest() { + void piiLoggingEnabledFalse_TenantIdUserIdSetToNullTest() { Consumer>> telemetryConsumer = new MyTelemetryConsumer().telemetryConsumer; TelemetryManager telemetryManager = new TelemetryManager(telemetryConsumer, false); @@ -234,11 +240,11 @@ public void piiLoggingEnabledFalse_TenantIdUserIdSetToNullTest() { apiEvent.setWasSuccessful(true); telemetryManager.stopEvent(reqId, apiEvent); - Assert.assertNull(apiEvent.get("msal.tenant_id")); + assertNull(apiEvent.get("msal.tenant_id")); } @Test - public void authorityNotInTrustedHostList_AuthorityIsNullTest() throws URISyntaxException { + void authorityNotInTrustedHostList_AuthorityIsNullTest() throws URISyntaxException { Consumer>> telemetryConsumer = new MyTelemetryConsumer().telemetryConsumer; TelemetryManager telemetryManager = new TelemetryManager(telemetryConsumer, false); @@ -250,7 +256,7 @@ public void authorityNotInTrustedHostList_AuthorityIsNullTest() throws URISyntax apiEvent.setWasSuccessful(true); telemetryManager.stopEvent(reqId, apiEvent); - Assert.assertEquals(apiEvent.get("msal.authority"), "https://login.microsoftonline.com"); + assertEquals(apiEvent.get("msal.authority"), "https://login.microsoftonline.com"); ApiEvent apiEvent2 = new ApiEvent(false); @@ -259,40 +265,40 @@ public void authorityNotInTrustedHostList_AuthorityIsNullTest() throws URISyntax apiEvent2.setWasSuccessful(true); telemetryManager.stopEvent(reqId, apiEvent2); - Assert.assertNull(apiEvent2.get("msal.authority")); + assertNull(apiEvent2.get("msal.authority")); } @Test - public void xmsCliTelemetryTest_CorrectFormatTest() { + void xmsCliTelemetryTest_CorrectFormatTest() { String responseHeader = "1,0,0,,"; XmsClientTelemetryInfo info = XmsClientTelemetryInfo.parseXmsTelemetryInfo(responseHeader); - Assert.assertEquals(info.getServerErrorCode(), "0"); - Assert.assertEquals(info.getServerSubErrorCode(), "0"); - Assert.assertEquals(info.getTokenAge(), ""); - Assert.assertEquals(info.getSpeInfo(), ""); + assertEquals(info.getServerErrorCode(), "0"); + assertEquals(info.getServerSubErrorCode(), "0"); + assertEquals(info.getTokenAge(), ""); + assertEquals(info.getSpeInfo(), ""); } @Test - public void xmsCliTelemetryTest_IncorrectFormatTest() { + void xmsCliTelemetryTest_IncorrectFormatTest() { String responseHeader = "1,2,3,4,5,6"; XmsClientTelemetryInfo info = XmsClientTelemetryInfo.parseXmsTelemetryInfo(responseHeader); - Assert.assertNull(info.getServerErrorCode()); - Assert.assertNull(info.getServerSubErrorCode()); - Assert.assertNull(info.getTokenAge()); - Assert.assertNull(info.getSpeInfo()); + assertNull(info.getServerErrorCode()); + assertNull(info.getServerSubErrorCode()); + assertNull(info.getTokenAge()); + assertNull(info.getSpeInfo()); } @Test - public void xmsCliTelemetryTest_IncorrectHeaderTest() { + void xmsCliTelemetryTest_IncorrectHeaderTest() { String responseHeader = "3,0,0,,"; XmsClientTelemetryInfo info = XmsClientTelemetryInfo.parseXmsTelemetryInfo(responseHeader); - Assert.assertNull(info); + assertNull(info); } - private ApiEvent createApiEvent(Boolean logPii) { + ApiEvent createApiEvent(Boolean logPii) { ApiEvent apiEvent1; try { apiEvent1 = new ApiEvent(logPii); @@ -305,7 +311,7 @@ private ApiEvent createApiEvent(Boolean logPii) { return apiEvent1; } - private HttpEvent createHttpEvent() { + HttpEvent createHttpEvent() { HttpEvent httpEvent1; try { httpEvent1 = new HttpEvent(); diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/TokenRequestExecutorTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/TokenRequestExecutorTest.java index efe3ac80..d430e304 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/TokenRequestExecutorTest.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/TokenRequestExecutorTest.java @@ -5,14 +5,22 @@ import com.nimbusds.oauth2.sdk.ParseException; import com.nimbusds.oauth2.sdk.SerializeException; -import com.nimbusds.oauth2.sdk.TokenErrorResponse; import com.nimbusds.oauth2.sdk.http.HTTPResponse; import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; -import org.easymock.EasyMock; -import org.powermock.api.easymock.PowerMock; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.testng.Assert; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; import java.io.IOException; import java.net.MalformedURLException; @@ -22,18 +30,18 @@ import java.util.Collections; import java.util.HashMap; -@Test(groups = {"checkin"}) -@PrepareForTest(TokenErrorResponse.class) -public class TokenRequestExecutorTest extends AbstractMsalTests { +@ExtendWith(MockitoExtension.class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class TokenRequestExecutorTest { @Test - public void executeOAuthRequest_SCBadRequestErrorInvalidGrant_InteractionRequiredException() + void executeOAuthRequest_SCBadRequestErrorInvalidGrant_InteractionRequiredException() throws SerializeException, ParseException, MsalException, IOException, URISyntaxException { TokenRequestExecutor request = createMockedTokenRequest(); - OAuthHttpRequest msalOAuthHttpRequest = PowerMock.createMock(OAuthHttpRequest.class); + OAuthHttpRequest msalOAuthHttpRequest = mock(OAuthHttpRequest.class); HTTPResponse httpResponse = new HTTPResponse(HTTPResponse.SC_BAD_REQUEST); @@ -50,29 +58,26 @@ public void executeOAuthRequest_SCBadRequestErrorInvalidGrant_InteractionRequire httpResponse.setContent(content); httpResponse.setContentType(HTTPContentType.ApplicationJSON.contentType); - EasyMock.expect(request.createOauthHttpRequest()).andReturn(msalOAuthHttpRequest).times(1); - EasyMock.expect(msalOAuthHttpRequest.send()).andReturn(httpResponse).times(1); - PowerMock.replay(request, msalOAuthHttpRequest); + doReturn(msalOAuthHttpRequest).when(request).createOauthHttpRequest(); + doReturn(httpResponse).when(msalOAuthHttpRequest).send(); try { request.executeTokenRequest(); - Assert.fail("Expected MsalServiceException was not thrown"); + fail("Expected MsalServiceException was not thrown"); } catch (MsalInteractionRequiredException ex) { - Assert.assertEquals(claims.replace("\\", ""), ex.claims()); - Assert.assertEquals(ex.reason(), InteractionRequiredExceptionReason.BASIC_ACTION); + assertEquals(claims.replace("\\", ""), ex.claims()); + assertEquals(ex.reason(), InteractionRequiredExceptionReason.BASIC_ACTION); } - PowerMock.verifyAll(); } @Test - public void executeOAuthRequest_SCBadRequestErrorInvalidGrant_SubErrorFilteredServiceExceptionThrown() + void executeOAuthRequest_SCBadRequestErrorInvalidGrant_SubErrorFilteredServiceExceptionThrown() throws SerializeException, ParseException, MsalException, IOException, URISyntaxException { TokenRequestExecutor request = createMockedTokenRequest(); - OAuthHttpRequest msalOAuthHttpRequest = PowerMock - .createMock(OAuthHttpRequest.class); + OAuthHttpRequest msalOAuthHttpRequest = mock(OAuthHttpRequest.class); HTTPResponse httpResponse = new HTTPResponse(HTTPResponse.SC_BAD_REQUEST); @@ -89,19 +94,16 @@ public void executeOAuthRequest_SCBadRequestErrorInvalidGrant_SubErrorFilteredSe httpResponse.setContent(content); httpResponse.setContentType(HTTPContentType.ApplicationJSON.contentType); - EasyMock.expect(request.createOauthHttpRequest()).andReturn(msalOAuthHttpRequest).times(1); - EasyMock.expect(msalOAuthHttpRequest.send()).andReturn(httpResponse).times(1); - - PowerMock.replay(request, msalOAuthHttpRequest); + doReturn(msalOAuthHttpRequest).when(request).createOauthHttpRequest(); + doReturn(httpResponse).when(msalOAuthHttpRequest).send(); try { request.executeTokenRequest(); - Assert.fail("Expected MsalServiceException was not thrown"); + fail("Expected MsalServiceException was not thrown"); } catch (MsalServiceException ex) { - Assert.assertEquals(claims.replace("\\", ""), ex.claims()); - Assert.assertTrue(!(ex instanceof MsalInteractionRequiredException)); + assertEquals(claims.replace("\\", ""), ex.claims()); + assertTrue(!(ex instanceof MsalInteractionRequiredException)); } - PowerMock.verifyAll(); } private TokenRequestExecutor createMockedTokenRequest() throws URISyntaxException, MalformedURLException { @@ -121,15 +123,12 @@ private TokenRequestExecutor createMockedTokenRequest() throws URISyntaxExceptio new DefaultHttpClient(null, null, null, null), new TelemetryManager(null, false)); - return PowerMock.createPartialMock( - TokenRequestExecutor.class, new String[]{"createOauthHttpRequest"}, - new AADAuthority(new URL(TestConstants.ORGANIZATIONS_AUTHORITY)), - refreshTokenRequest, - serviceBundle); + return spy(new TokenRequestExecutor( + new AADAuthority(new URL(TestConstants.ORGANIZATIONS_AUTHORITY)), refreshTokenRequest, serviceBundle)); } @Test - public void testConstructor() throws MalformedURLException, + void testConstructor() throws MalformedURLException, URISyntaxException { PublicClientApplication app = PublicClientApplication.builder("id").correlationId("corr-id").build(); @@ -148,11 +147,11 @@ public void testConstructor() throws MalformedURLException, new AADAuthority(new URL(TestConstants.ORGANIZATIONS_AUTHORITY)), acr, new ServiceBundle(null, null, null)); - Assert.assertNotNull(request); + assertNotNull(request); } @Test - public void testToOAuthRequestNonEmptyCorrelationId() + void testToOAuthRequestNonEmptyCorrelationId() throws MalformedURLException, SerializeException, URISyntaxException, ParseException { PublicClientApplication app = PublicClientApplication.builder("id").correlationId("corr-id").build(); @@ -171,16 +170,16 @@ public void testToOAuthRequestNonEmptyCorrelationId() new AADAuthority(new URL(TestConstants.ORGANIZATIONS_AUTHORITY)), acr, new ServiceBundle(null, null, null)); - Assert.assertNotNull(request); + assertNotNull(request); OAuthHttpRequest req = request.createOauthHttpRequest(); - Assert.assertNotNull(req); - Assert.assertEquals( + assertNotNull(req); + assertEquals( "corr-id", req.getExtraHeaderParams().get(HttpHeaders.CORRELATION_ID_HEADER_NAME)); } @Test - public void testToOAuthRequestNullCorrelationId_NullClientAuth() + void testToOAuthRequestNullCorrelationId_NullClientAuth() throws MalformedURLException, SerializeException, URISyntaxException, ParseException { @@ -200,13 +199,13 @@ public void testToOAuthRequestNullCorrelationId_NullClientAuth() new AADAuthority(new URL(TestConstants.ORGANIZATIONS_AUTHORITY)), acr, new ServiceBundle(null, null, null)); - Assert.assertNotNull(request); + assertNotNull(request); final OAuthHttpRequest req = request.createOauthHttpRequest(); - Assert.assertNotNull(req); + assertNotNull(req); } @Test - public void testExecuteOAuth_Success() throws SerializeException, ParseException, MsalException, + void testExecuteOAuth_Success() throws SerializeException, ParseException, MsalException, IOException, URISyntaxException { PublicClientApplication app = PublicClientApplication.builder("id").correlationId("corr-id").build(); @@ -226,45 +225,33 @@ public void testExecuteOAuth_Success() throws SerializeException, ParseException null, new TelemetryManager(null, false)); - final TokenRequestExecutor request = PowerMock.createPartialMock( - TokenRequestExecutor.class, new String[]{"createOauthHttpRequest"}, - new AADAuthority(new URL(TestConstants.ORGANIZATIONS_AUTHORITY)), acr, serviceBundle); - - final OAuthHttpRequest msalOAuthHttpRequest = PowerMock - .createMock(OAuthHttpRequest.class); - - final HTTPResponse httpResponse = PowerMock - .createMock(HTTPResponse.class); - - EasyMock.expect(request.createOauthHttpRequest()) - .andReturn(msalOAuthHttpRequest).times(1); - EasyMock.expect(msalOAuthHttpRequest.send()).andReturn(httpResponse) - .times(1); - EasyMock.expect(httpResponse.getContentAsJSONObject()) - .andReturn( - JSONObjectUtils - .parse(TestConfiguration.TOKEN_ENDPOINT_OK_RESPONSE)) - .times(1); - httpResponse.ensureStatusCode(200); - EasyMock.expectLastCall(); + final TokenRequestExecutor request = spy(new TokenRequestExecutor( + new AADAuthority(new URL(TestConstants.ORGANIZATIONS_AUTHORITY)), acr, serviceBundle)); + + final OAuthHttpRequest msalOAuthHttpRequest = mock(OAuthHttpRequest.class); + + final HTTPResponse httpResponse = mock(HTTPResponse.class); + + doReturn(msalOAuthHttpRequest).when(request).createOauthHttpRequest(); + doReturn(httpResponse).when(msalOAuthHttpRequest).send(); + doReturn(JSONObjectUtils.parse(TestConfiguration.TOKEN_ENDPOINT_OK_RESPONSE)).when(httpResponse).getContentAsJSONObject(); - EasyMock.expect(httpResponse.getStatusCode()).andReturn(200).times(1); + httpResponse.ensureStatusCode(200); - PowerMock.replay(request, msalOAuthHttpRequest, httpResponse); + doReturn(200).when(httpResponse).getStatusCode(); final AuthenticationResult result = request.executeTokenRequest(); - PowerMock.verifyAll(); - Assert.assertNotNull(result.account()); - Assert.assertNotNull(result.account().homeAccountId()); - Assert.assertEquals(result.account().username(), "idlab@msidlab4.onmicrosoft.com"); + assertNotNull(result.account()); + assertNotNull(result.account().homeAccountId()); + assertEquals(result.account().username(), "idlab@msidlab4.onmicrosoft.com"); - Assert.assertFalse(StringHelper.isBlank(result.accessToken())); - Assert.assertFalse(StringHelper.isBlank(result.refreshToken())); + assertFalse(StringHelper.isBlank(result.accessToken())); + assertFalse(StringHelper.isBlank(result.refreshToken())); } - @Test(expectedExceptions = MsalException.class) - public void testExecuteOAuth_Failure() throws SerializeException, + @Test + void testExecuteOAuth_Failure() throws SerializeException, ParseException, MsalException, IOException, URISyntaxException { PublicClientApplication app = PublicClientApplication.builder("id").correlationId("corr-id").build(); @@ -284,41 +271,27 @@ public void testExecuteOAuth_Failure() throws SerializeException, null, new TelemetryManager(null, false)); - final TokenRequestExecutor request = PowerMock.createPartialMock( - TokenRequestExecutor.class, new String[]{"createOauthHttpRequest"}, - new AADAuthority(new URL(TestConstants.ORGANIZATIONS_AUTHORITY)), acr, serviceBundle); - final OAuthHttpRequest msalOAuthHttpRequest = PowerMock - .createMock(OAuthHttpRequest.class); - - final HTTPResponse httpResponse = PowerMock - .createMock(HTTPResponse.class); - EasyMock.expect(request.createOauthHttpRequest()) - .andReturn(msalOAuthHttpRequest).times(1); - EasyMock.expect(msalOAuthHttpRequest.send()).andReturn(httpResponse) - .times(1); - EasyMock.expect(httpResponse.getStatusCode()).andReturn(402).times(3); - EasyMock.expect(httpResponse.getStatusMessage()).andReturn("403 Forbidden"); - EasyMock.expect(httpResponse.getHeaderMap()).andReturn(new HashMap<>()); - EasyMock.expect(httpResponse.getContent()).andReturn(TestConfiguration.HTTP_ERROR_RESPONSE); + final TokenRequestExecutor request = spy(new TokenRequestExecutor( + new AADAuthority(new URL(TestConstants.ORGANIZATIONS_AUTHORITY)), acr, serviceBundle)); + final OAuthHttpRequest msalOAuthHttpRequest = mock(OAuthHttpRequest.class); - final ErrorResponse errorResponse = PowerMock.createMock(ErrorResponse.class); + final HTTPResponse httpResponse = mock(HTTPResponse.class); + doReturn(msalOAuthHttpRequest).when(request).createOauthHttpRequest(); + doReturn(httpResponse).when(msalOAuthHttpRequest).send(); + lenient().doReturn(402).when(httpResponse).getStatusCode(); + doReturn("403 Forbidden").when(httpResponse).getStatusMessage(); + doReturn(new HashMap<>()).when(httpResponse).getHeaderMap(); + doReturn(TestConfiguration.HTTP_ERROR_RESPONSE).when(httpResponse).getContent(); - EasyMock.expect(errorResponse.error()).andReturn("invalid_request"); + final ErrorResponse errorResponse = mock(ErrorResponse.class); - EasyMock.expect(httpResponse.getHeaderValue("User-Agent")).andReturn(null); - EasyMock.expect(httpResponse.getHeaderValue("x-ms-request-id")).andReturn(null); - EasyMock.expect(httpResponse.getHeaderValue("x-ms-clitelem")).andReturn(null); - EasyMock.expect(httpResponse.getStatusCode()).andReturn(402).times(1); + lenient().doReturn("invalid_request").when(errorResponse).error(); + lenient().doReturn(null).when(httpResponse).getHeaderValue("User-Agent"); + lenient().doReturn(null).when(httpResponse).getHeaderValue("x-ms-request-id"); + lenient().doReturn(null).when(httpResponse).getHeaderValue("x-ms-clitelem"); + doReturn(402).when(httpResponse).getStatusCode(); - PowerMock.replay(request, msalOAuthHttpRequest, httpResponse, - TokenErrorResponse.class, errorResponse); - try { - request.executeTokenRequest(); - PowerMock.verifyAll(); - } finally { - PowerMock.reset(request, msalOAuthHttpRequest, httpResponse, - TokenErrorResponse.class, errorResponse); - } + assertThrows(MsalException.class, request::executeTokenRequest); } } \ No newline at end of file diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/TokenResponseTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/TokenResponseTest.java index 2eaa76f8..0744c224 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/TokenResponseTest.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/TokenResponseTest.java @@ -11,11 +11,14 @@ import com.nimbusds.oauth2.sdk.token.RefreshToken; import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; import com.nimbusds.openid.connect.sdk.token.OIDCTokens; -import org.testng.Assert; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; -@Test(groups = {"checkin"}) -public class TokenResponseTest extends AbstractMsalTests { +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class TokenResponseTest { private final String idToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik5HVEZ2ZEstZnl0aEV1THdqcHdBSk9NOW4tQSJ9." + "eyJhdWQiOiIyMTZlZjgxZC1mM2IyLTQ3ZDQtYWQyMS1hNGRmNDliNTZkZWUiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5l" @@ -39,11 +42,11 @@ public void testConstructor() throws ParseException { final TokenResponse response = new TokenResponse( new BearerAccessToken("access_token"), new RefreshToken( "refresh_token"), idToken, null, null, expiresIn, extExpiresIn, null, refreshIn); - Assert.assertNotNull(response); + assertNotNull(response); OIDCTokens tokens = response.getOIDCTokens(); - Assert.assertNotNull(tokens); + assertNotNull(tokens); final JWT jwt = tokens.getIDToken(); - Assert.assertTrue(jwt.getJWTClaimsSet().getClaims().size() >= 0); + assertTrue(jwt.getJWTClaimsSet().getClaims().size() >= 0); } @Test @@ -52,12 +55,12 @@ public void testParseJsonObject() final TokenResponse response = TokenResponse .parseJsonObject(JSONObjectUtils .parse(TestConfiguration.TOKEN_ENDPOINT_OK_RESPONSE)); - Assert.assertNotNull(response); + assertNotNull(response); OIDCTokens tokens = response.getOIDCTokens(); - Assert.assertNotNull(tokens); - Assert.assertNotNull(tokens.getIDToken()); - Assert.assertFalse(StringHelper.isBlank(tokens.getIDTokenString())); - Assert.assertFalse(StringHelper.isBlank(response.getScope())); + assertNotNull(tokens); + assertNotNull(tokens.getIDToken()); + assertFalse(StringHelper.isBlank(tokens.getIDTokenString())); + assertFalse(StringHelper.isBlank(response.getScope())); } @Test @@ -67,10 +70,10 @@ public void testEmptyIdToken() { new RefreshToken("refresh_token"), "", null, null, expiresIn, extExpiresIn, null, refreshIn); - Assert.assertNotNull(response); + assertNotNull(response); OIDCTokens tokens = response.getOIDCTokens(); - Assert.assertNotNull(tokens); + assertNotNull(tokens); final AccessToken accessToken = tokens.getAccessToken(); - Assert.assertNotNull(accessToken); + assertNotNull(accessToken); } } diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/UIRequiredCacheTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/UIRequiredCacheTest.java index 8c51897d..00e29882 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/UIRequiredCacheTest.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/UIRequiredCacheTest.java @@ -4,21 +4,27 @@ package com.microsoft.aad.msal4j; import com.nimbusds.oauth2.sdk.http.HTTPResponse; -import org.easymock.EasyMock; -import org.powermock.api.easymock.PowerMock; -import org.testng.Assert; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.BeforeAll; +import org.mockito.junit.jupiter.MockitoExtension; import java.util.*; -import static org.easymock.EasyMock.anyObject; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; -public class UIRequiredCacheTest extends AbstractMsalTests { +@ExtendWith(MockitoExtension.class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class UIRequiredCacheTest { - public final Integer CACHING_TIME_SEC = 2; + final Integer CACHING_TIME_SEC = 2; - @BeforeClass + @BeforeAll void init() { InteractionRequiredCache.DEFAULT_CACHING_TIME_SEC = CACHING_TIME_SEC; } @@ -68,37 +74,35 @@ private HttpResponse getHttpResponse(int statusCode, String body) { private PublicClientApplication getApp_MockedWith_InvalidGrantTokenEndpointResponse() throws Exception { - IHttpClient httpClientMock = EasyMock.createMock(IHttpClient.class); + IHttpClient httpClientMock = mock(IHttpClient.class); HttpResponse httpResponse = getHttpResponse(400, TestConfiguration.TOKEN_ENDPOINT_INVALID_GRANT_ERROR_RESPONSE); - EasyMock.expect(httpClientMock.send(anyObject())).andReturn(httpResponse).times(1); - PowerMock.replayAll(httpClientMock); + doReturn(httpResponse).when(httpClientMock).send(any()); return getPublicClientApp(httpClientMock); } private PublicClientApplication getApp_MockedWith_OKTokenEndpointResponse_InvalidGrantTokenEndpointResponse() throws Exception { - IHttpClient httpClientMock = EasyMock.createMock(IHttpClient.class); + IHttpClient httpClientMock = mock(IHttpClient.class); HttpResponse httpResponse = getHttpResponse(HTTPResponse.SC_OK, TestConfiguration.TOKEN_ENDPOINT_OK_RESPONSE); - EasyMock.expect(httpClientMock.send(anyObject())).andReturn(httpResponse).times(1); + lenient().doReturn(httpResponse).when(httpClientMock).send(any()); httpResponse = getHttpResponse(HTTPResponse.SC_UNAUTHORIZED, TestConfiguration.TOKEN_ENDPOINT_INVALID_GRANT_ERROR_RESPONSE); - EasyMock.expect(httpClientMock.send(anyObject())).andReturn(httpResponse).times(1); + lenient().doReturn(httpResponse).when(httpClientMock).send(any()); PublicClientApplication app = getPublicClientApp(httpClientMock); - PowerMock.replayAll(httpClientMock); return app; } @Test - public void RefreshTokenRequest_STSResponseInvalidGrantError_repeatedRequestsServedFromCache() throws Exception { + void RefreshTokenRequest_STSResponseInvalidGrantError_repeatedRequestsServedFromCache() throws Exception { InteractionRequiredCache.clear(); // refresh token request #1 to token endpoint @@ -109,11 +113,9 @@ public void RefreshTokenRequest_STSResponseInvalidGrantError_repeatedRequestsSer app.acquireToken(getAcquireTokenApiParameters("scope1")).join(); } catch (Exception ex) { if (!(ex.getCause() instanceof MsalInteractionRequiredException)) { - Assert.fail("Unexpected exception"); + fail("Unexpected exception"); } } - PowerMock.verifyAll(); - PowerMock.resetAll(); // repeat same request #1, cached response should be returned try { @@ -121,7 +123,7 @@ public void RefreshTokenRequest_STSResponseInvalidGrantError_repeatedRequestsSer app.acquireToken(getAcquireTokenApiParameters("scope1")).join(); } catch (Exception ex) { if (!(ex.getCause() instanceof MsalInteractionRequiredException)) { - Assert.fail("Unexpected exception"); + fail("Unexpected exception"); } } @@ -132,11 +134,9 @@ public void RefreshTokenRequest_STSResponseInvalidGrantError_repeatedRequestsSer app.acquireToken(getAcquireTokenApiParameters("scope2")).join(); } catch (Exception ex) { if (!(ex.getCause() instanceof MsalInteractionRequiredException)) { - Assert.fail("Unexpected exception"); + fail("Unexpected exception"); } } - PowerMock.verifyAll(); - PowerMock.resetAll(); // repeat request #1, should not be served from cache (cache entry should be expired) // request to token endpoint should be sent @@ -146,15 +146,13 @@ public void RefreshTokenRequest_STSResponseInvalidGrantError_repeatedRequestsSer app.acquireToken(getAcquireTokenApiParameters("scope1")).join(); } catch (Exception ex) { if (!(ex.getCause() instanceof MsalServiceException)) { - Assert.fail("Unexpected exception"); + fail("Unexpected exception"); } } - PowerMock.verifyAll(); - PowerMock.resetAll(); } @Test - public void SilentRequest_STSResponseInvalidGrantError_repeatedRequestsServedFromCache() throws Exception { + void SilentRequest_STSResponseInvalidGrantError_repeatedRequestsServedFromCache() throws Exception { InteractionRequiredCache.clear(); PublicClientApplication @@ -170,22 +168,7 @@ public void SilentRequest_STSResponseInvalidGrantError_repeatedRequestsServedFro app.acquireTokenSilently(silentParameters).join(); } catch (Exception ex) { if (!(ex.getCause() instanceof MsalInteractionRequiredException)) { - Assert.fail("Unexpected exception"); - } - } - PowerMock.verifyAll(); - PowerMock.resetAll(); - - // repeat same silent #1, cached response should be returned - try { - SilentParameters silentParameters = SilentParameters.builder( - Collections.singleton("scope1"), - app.getAccounts().join().iterator().next()) - .build(); - app.acquireTokenSilently(silentParameters).join(); - } catch (Exception ex) { - if (!(ex.getCause() instanceof MsalInteractionRequiredException)) { - Assert.fail("Unexpected exception"); + fail("Unexpected exception"); } } @@ -201,11 +184,9 @@ public void SilentRequest_STSResponseInvalidGrantError_repeatedRequestsServedFro app.acquireTokenSilently(silentParameters).join(); } catch (Exception ex) { if (!(ex.getCause() instanceof MsalInteractionRequiredException)) { - Assert.fail("Unexpected exception"); + fail("Unexpected exception"); } } - PowerMock.verifyAll(); - PowerMock.resetAll(); // repeat request #1, should not be served from cache (cache entry should be expired) // request to token endpoint should be sent @@ -220,10 +201,8 @@ public void SilentRequest_STSResponseInvalidGrantError_repeatedRequestsServedFro app.acquireTokenSilently(silentParameters).join(); } catch (Exception ex) { if (!(ex.getCause() instanceof MsalServiceException)) { - Assert.fail("Unexpected exception"); + fail("Unexpected exception"); } } - PowerMock.verifyAll(); - PowerMock.resetAll(); } } diff --git a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/WSTrustRequestTest.java b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/WSTrustRequestTest.java index 151b7804..dd020db8 100644 --- a/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/WSTrustRequestTest.java +++ b/msal4j-sdk/src/test/java/com/microsoft/aad/msal4j/WSTrustRequestTest.java @@ -4,43 +4,45 @@ package com.microsoft.aad.msal4j; import org.apache.commons.text.StringEscapeUtils; -import org.testng.Assert; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; -@Test(groups = {"checkin"}) -public class WSTrustRequestTest { +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class WSTrustRequestTest { @Test - public void buildMessage_cloudAudienceUrnNotNull() throws Exception { + void buildMessage_cloudAudienceUrnNotNull() throws Exception { String msg = WSTrustRequest.buildMessage("address", "username", "password", WSTrustVersion.WSTRUST2005, "cloudAudienceUrn").toString(); - Assert.assertTrue(msg.contains("cloudAudienceUrn")); + assertTrue(msg.contains("cloudAudienceUrn")); } @Test - public void buildMessage_cloudAudienceUrnNull() throws Exception { + void buildMessage_cloudAudienceUrnNull() throws Exception { String msg = WSTrustRequest.buildMessage("address", "username", "password", WSTrustVersion.WSTRUST2005, null).toString(); - Assert.assertTrue(msg.contains("" + WSTrustRequest.DEFAULT_APPLIES_TO + "")); + assertTrue(msg.contains("" + WSTrustRequest.DEFAULT_APPLIES_TO + "")); } @Test - public void buildMessage_cloudAudienceUrnEmpty() throws Exception { + void buildMessage_cloudAudienceUrnEmpty() throws Exception { String msg = WSTrustRequest.buildMessage("address", "username", "password", WSTrustVersion.WSTRUST2005, "").toString(); - Assert.assertTrue(msg.contains("" + WSTrustRequest.DEFAULT_APPLIES_TO + "")); + assertTrue(msg.contains("" + WSTrustRequest.DEFAULT_APPLIES_TO + "")); } @Test - public void buildMessage_integrated() throws Exception { + void buildMessage_integrated() throws Exception { String msg = WSTrustRequest.buildMessage("address", null, null, WSTrustVersion.WSTRUST13, "cloudAudienceUrn").toString(); - Assert.assertTrue(msg.contains("cloudAudienceUrn")); - Assert.assertTrue(!msg.contains("cloudAudienceUrn")); + assertTrue(!msg.contains(" Date: Wed, 25 Oct 2023 10:41:58 -0700 Subject: [PATCH 26/27] Version changes for msal4j-brokers 1.0.3-beta and msal4j 1.14.3-beta (#734) * Ensure correlation ID is never null * Version changes for msal4j-brokers 1.0.3-beta and msal4j 1.14.3-beta * Ensure that builder values for supported OS's are used --- changelog.txt | 2 +- msal4j-brokers/pom.xml | 6 +++--- .../java/com/microsoft/aad/msal4jbrokers/Broker.java | 11 ++++++++++- msal4j-sdk/README.md | 6 +++--- msal4j-sdk/bnd.bnd | 2 +- msal4j-sdk/pom.xml | 2 +- 6 files changed, 19 insertions(+), 10 deletions(-) diff --git a/changelog.txt b/changelog.txt index 0e222aeb..d5a50e3a 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,4 +1,4 @@ -Version 1.14.1-beta +Version 1.14.3-beta ============= - Add proof-of-possession token support - Add MSALRuntime logging support diff --git a/msal4j-brokers/pom.xml b/msal4j-brokers/pom.xml index affcd369..85874f20 100644 --- a/msal4j-brokers/pom.xml +++ b/msal4j-brokers/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.microsoft.azure msal4j-brokers - 1.0.2-beta + 1.0.3-beta jar msal4j-brokers @@ -34,12 +34,12 @@ com.microsoft.azure msal4j - 1.14.1-beta + 1.14.3-beta com.microsoft.azure javamsalruntime - 0.13.8 + 0.13.10 org.projectlombok diff --git a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/Broker.java b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/Broker.java index 30cdd7d9..da56b08f 100644 --- a/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/Broker.java +++ b/msal4j-brokers/src/main/java/com/microsoft/aad/msal4jbrokers/Broker.java @@ -255,6 +255,9 @@ public static class Builder { public Builder() { } + /** + * When set to true, MSAL Java will attempt to use the broker when the application is running on a Windows OS + */ public Builder supportWindows(boolean val) { supportWindows = val; return this; @@ -266,6 +269,12 @@ public Broker build() { } private Broker(Builder builder) { - supportWindows = builder.supportWindows; + this.supportWindows = builder.supportWindows; + + //This will be expanded to cover other OS options, but for now it is only Windows. Since Windows is the only + // option, if app developer doesn't want to use the broker on Windows then they shouldn't use the Broker at all + if (!this.supportWindows) { + throw new MsalClientException("At least one operating system support option must be used when building the Broker instance", AuthenticationErrorCode.MSALJAVA_BROKERS_ERROR); + } } } diff --git a/msal4j-sdk/README.md b/msal4j-sdk/README.md index 474499bc..bb0a5569 100644 --- a/msal4j-sdk/README.md +++ b/msal4j-sdk/README.md @@ -16,7 +16,7 @@ Quick links: The library supports the following Java environments: - Java 8 (or higher) -Current version - 1.13.11 +Current version - 1.14.3-beta You can find the changes for each version in the [change log](https://github.com/AzureAD/microsoft-authentication-library-for-java/blob/master/changelog.txt). @@ -28,13 +28,13 @@ Find [the latest package in the Maven repository](https://mvnrepository.com/arti com.microsoft.azure msal4j - 1.13.11 + 1.14.3-beta ``` ### Gradle ```gradle -compile group: 'com.microsoft.azure', name: 'msal4j', version: '1.13.11' +compile group: 'com.microsoft.azure', name: 'msal4j', version: '1.14.3-beta' ``` ## Usage diff --git a/msal4j-sdk/bnd.bnd b/msal4j-sdk/bnd.bnd index 0a14dff8..775c26e0 100644 --- a/msal4j-sdk/bnd.bnd +++ b/msal4j-sdk/bnd.bnd @@ -1,2 +1,2 @@ -Export-Package: com.microsoft.aad.msal4j;version="1.13.11" +Export-Package: com.microsoft.aad.msal4j;version="1.14.3-beta" Automatic-Module-Name: com.microsoft.aad.msal4j diff --git a/msal4j-sdk/pom.xml b/msal4j-sdk/pom.xml index 847b32e7..6817f12f 100644 --- a/msal4j-sdk/pom.xml +++ b/msal4j-sdk/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.microsoft.azure msal4j - 1.13.11 + 1.14.3-beta jar msal4j From 27c3feef8c4b6e4cd1c27bdecc32e28ef22d4224 Mon Sep 17 00:00:00 2001 From: Avery-Dunn <62066438+Avery-Dunn@users.noreply.github.com> Date: Thu, 26 Oct 2023 15:16:30 -0700 Subject: [PATCH 27/27] Release 1.14.0/1.0.0 version changes (#736) * Delete codeql.yml * Test framework update (#672) * Initial working tests * Remove CIAM extra query parameter * Fix failing tests * Remove duplicate unit tests * Remove duplicate unit tests * Update tests with mocking to use Mockito * Remove testng and powermock, add junit and mockito * Remove AbstractMsalTests and PowerMockTestCase * Fix mistaken null check * Properly scope dependency * Update CIAM tests (#673) * Bump guava from 31.1-jre to 32.0.0-jre in /msal4j-sdk (#671) Bumps [guava](https://github.com/google/guava) from 31.1-jre to 32.0.0-jre. - [Release notes](https://github.com/google/guava/releases) - [Commits](https://github.com/google/guava/commits) --- updated-dependencies: - dependency-name: com.google.guava:guava dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Avery-Dunn * Delete contributing.md (#667) Co-authored-by: Avery-Dunn * Create Contributing.md (#668) Co-authored-by: Avery-Dunn * Version changes for 1.13.9 (#674) * Add space between command and arguments when executing linux command to open browser. Refs #682 (#683) Co-authored-by: Ric Emery * Assorted fixes (#684) * Remove default timeouts and improve exception messages * Fix NPE for on-prem ADFS scenario * Log MSAL message but re-throw exception * Update vulnerable test dependency * Issue-679: Fix for Account Cache; .contains() was not possible and you had to iterate through all elements as workaround. (#681) * Version changes for 1.13.10 (#685) * Move changelog * Move changelog to root * Update issue templates (#707) * Re-add lombok source line (#705) * Version changes for release 1.13.11 (#714) * Update bug report * Delete .github/ISSUE_TEMPLATE/bug_report.md * Update bug_report.yaml * Create FeatureRequest.yaml * Update FeatureRequest.yaml * Set default throttling time to 5 sec (#721) Co-authored-by: Kulyakhtin, Alexander (Ext) * Version changes for 1.14.0 msal4j and 1.0.0 msal4j-brokers --------- Signed-off-by: dependabot[bot] Co-authored-by: Bogdan Gavril Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ric Emery Co-authored-by: Ric Emery Co-authored-by: Maximilian Pfeffer Co-authored-by: akulyakhtin Co-authored-by: Kulyakhtin, Alexander (Ext) --- changelog.txt | 13 ++++++++----- msal4j-brokers/changelog.txt | 4 ++-- msal4j-brokers/pom.xml | 4 ++-- msal4j-sdk/README.md | 6 +++--- msal4j-sdk/bnd.bnd | 2 +- msal4j-sdk/pom.xml | 3 +-- 6 files changed, 17 insertions(+), 15 deletions(-) diff --git a/changelog.txt b/changelog.txt index d5a50e3a..81d5fd17 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,4 +1,11 @@ -Version 1.14.3-beta +Version 1.14.0 +============= +- GA release of MSAL Java Brokers package +- Add support for acquiring bearer and proof-of-possession tokens using WAM as the broker (#590) +- Default throttling time for password grant requests lowered to 5 seconds (#721) +- Fix internal docs generation issue (#705) + +Version 1.14.1-beta ============= - Add proof-of-possession token support - Add MSALRuntime logging support @@ -8,10 +15,6 @@ Version 1.14.0-beta - Add IBroker interface - Add app-level parameter for enabling the use of auth brokers -Version 1.13.11 -============= -- Hotfix for internal docs generation issue (#705) - Version 1.13.10 ============= - Remove default HTTP timeout (#664) diff --git a/msal4j-brokers/changelog.txt b/msal4j-brokers/changelog.txt index fc496f34..ff5f398a 100644 --- a/msal4j-brokers/changelog.txt +++ b/msal4j-brokers/changelog.txt @@ -1,4 +1,4 @@ -Version 1.0.0-beta +Version 1.0.0 ============= - Initial release -- Provides the API and dependencies needed to utilize auth brokers through MSALRuntime \ No newline at end of file +- Provides the API and dependencies needed to acquire tokens via WAM \ No newline at end of file diff --git a/msal4j-brokers/pom.xml b/msal4j-brokers/pom.xml index 85874f20..646e23a7 100644 --- a/msal4j-brokers/pom.xml +++ b/msal4j-brokers/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.microsoft.azure msal4j-brokers - 1.0.3-beta + 1.0.0 jar msal4j-brokers @@ -34,7 +34,7 @@ com.microsoft.azure msal4j - 1.14.3-beta + 1.14.0 com.microsoft.azure diff --git a/msal4j-sdk/README.md b/msal4j-sdk/README.md index bb0a5569..cbc5bbe0 100644 --- a/msal4j-sdk/README.md +++ b/msal4j-sdk/README.md @@ -16,7 +16,7 @@ Quick links: The library supports the following Java environments: - Java 8 (or higher) -Current version - 1.14.3-beta +Current version - 1.14.0 You can find the changes for each version in the [change log](https://github.com/AzureAD/microsoft-authentication-library-for-java/blob/master/changelog.txt). @@ -28,13 +28,13 @@ Find [the latest package in the Maven repository](https://mvnrepository.com/arti com.microsoft.azure msal4j - 1.14.3-beta + 1.14.0 ``` ### Gradle ```gradle -compile group: 'com.microsoft.azure', name: 'msal4j', version: '1.14.3-beta' +compile group: 'com.microsoft.azure', name: 'msal4j', version: '1.14.0' ``` ## Usage diff --git a/msal4j-sdk/bnd.bnd b/msal4j-sdk/bnd.bnd index 775c26e0..e9acb269 100644 --- a/msal4j-sdk/bnd.bnd +++ b/msal4j-sdk/bnd.bnd @@ -1,2 +1,2 @@ -Export-Package: com.microsoft.aad.msal4j;version="1.14.3-beta" +Export-Package: com.microsoft.aad.msal4j;version="1.14.0" Automatic-Module-Name: com.microsoft.aad.msal4j diff --git a/msal4j-sdk/pom.xml b/msal4j-sdk/pom.xml index 6817f12f..4dc08d26 100644 --- a/msal4j-sdk/pom.xml +++ b/msal4j-sdk/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.microsoft.azure msal4j - 1.14.3-beta + 1.14.0 jar msal4j @@ -103,7 +103,6 @@ 1.14.5 test - org.skyscreamer jsonassert