From e9a44d5f92e1dc3a0d420cfd240a41f4ab69f0fa Mon Sep 17 00:00:00 2001 From: Avery-Dunn Date: Sun, 23 Oct 2022 16:36:46 -0700 Subject: [PATCH 01/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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()));