Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
e9a44d5
Add IBroker implementation for MSALRuntime
Avery-Dunn Oct 23, 2022
31aa38d
Merge branches 'avdunn/msalruntime-broker' and 'dev' of https://githu…
Avery-Dunn Dec 15, 2022
8a8df73
Remove dll used during testing
Avery-Dunn Dec 19, 2022
5b8e235
Integrate broker steps to relevant flows in PublicClientApplication
Avery-Dunn Dec 20, 2022
ade0717
Add logic to cancel MsalRuntimeFutures
Avery-Dunn Jan 4, 2023
77e9304
Expand javadocs and exception handling
Avery-Dunn Jan 5, 2023
45b4a40
Address code review comments
Avery-Dunn Jan 5, 2023
3df9af2
Simplify future chaining, address code review comments
Avery-Dunn Jan 5, 2023
2fd56ec
Reorganize future chaining, fix testing issues
Avery-Dunn Jan 11, 2023
fdf9ef7
Adjust how broker availability is checked
Avery-Dunn Jan 12, 2023
9152677
Create automated test
Avery-Dunn Jan 12, 2023
dc79a19
Adjust startup logic
Avery-Dunn Jan 12, 2023
ab4f576
Correct version number for interop
Avery-Dunn Jan 18, 2023
a518eda
Correct broker versioning
Avery-Dunn Jan 18, 2023
40da5b0
Merge branch 'dev' of https://github.com/AzureAD/microsoft-authentica…
Avery-Dunn Jan 24, 2023
9349e7e
Move broker tests to MSAL Java package
Avery-Dunn Jan 24, 2023
b95362b
Remove usage of msal4j-brokers from msal4j
Avery-Dunn Jan 26, 2023
f684388
Add missing SLFJ dependency
Avery-Dunn Jan 30, 2023
0ba0522
Merge branch 'dev' of https://github.com/AzureAD/microsoft-authentica…
Avery-Dunn Jan 30, 2023
8e73565
Use newest msal4j
Avery-Dunn Jan 30, 2023
de9c85b
Bump javamsalruntime version number
Avery-Dunn Jan 31, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions msal4j-brokers/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.microsoft.azure</groupId>
<artifactId>msal4j-brokers</artifactId>
<version>0.0.1</version>
<version>1.0.0-beta</version>
<packaging>jar</packaging>
<name>msal4j-brokers</name>
<description>
Expand All @@ -26,18 +26,39 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/com.microsoft.azure/msal4j -->
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>msal4j</artifactId>
<version>1.13.2</version>
<version>1.13.4</version>
</dependency>
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>javamsalruntime</artifactId>
<version>0.13.4</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
<scope>test</scope>
</dependency>
</dependencies>

<!-- force https -->
Expand All @@ -60,7 +81,6 @@
</pluginRepository>
</pluginRepositories>
<build>
<sourceDirectory>${project.build.directory}/delombok</sourceDirectory>
<plugins>
<plugin>
<groupId>org.projectlombok</groupId>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.microsoft.aad.msal4jbrokers;

import com.microsoft.aad.msal4j.IAuthenticationResult;
import com.microsoft.aad.msal4j.IBroker;
import com.microsoft.aad.msal4j.InteractiveRequestParameters;
import com.microsoft.aad.msal4j.PublicClientApplication;
import com.microsoft.aad.msal4j.SilentParameters;
import com.microsoft.aad.msal4j.UserNamePasswordParameters;
import com.microsoft.aad.msal4j.MsalClientException;
import com.microsoft.aad.msal4j.AuthenticationErrorCode;
import com.microsoft.aad.msal4j.IAccount;
import com.microsoft.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.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(MsalRuntimeBroker.class);

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) {
throw new MsalClientException(String.format("Could not initialize MSALRuntime: %s", e.getErrorMessage()), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR);
}
}

@Override
public CompletableFuture<IAuthenticationResult> acquireToken(PublicClientApplication application, SilentParameters parameters) {
Account accountResult = null;

//If request has an account ID, MSALRuntime likely has data cached for that account that we can retrieve
if (parameters.account() != null) {
try {
accountResult = ((ReadAccountResult) interop.readAccountById(parameters.account().homeAccountId(), application.correlationId()).get()).getAccount();
} catch (InterruptedException | ExecutionException ex) {
throw new MsalClientException(String.format("MSALRuntime async operation interrupted when waiting for result: %s", ex.getMessage()), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR);
}
}

try {
AuthParameters authParameters = new AuthParameters
.AuthParametersBuilder(application.clientId(),
application.authority(),
String.join(" ", parameters.scopes()))
.build();

if (accountResult == null) {
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())

);
}
} catch (MsalInteropException interopException) {
throw new MsalClientException(interopException.getErrorMessage(), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR);
}
}

@Override
public CompletableFuture<IAuthenticationResult> acquireToken(PublicClientApplication application, InteractiveRequestParameters parameters) {
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())
);
} catch (MsalInteropException interopException) {
throw new MsalClientException(interopException.getErrorMessage(), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR);
}
}

/**
* @deprecated
*/
@Deprecated
@Override
public CompletableFuture<IAuthenticationResult> acquireToken(PublicClientApplication application, UserNamePasswordParameters parameters) {
try {
AuthParameters authParameters =
new AuthParameters
.AuthParametersBuilder(application.clientId(),
application.authority(),
String.join(" ", parameters.scopes()))
.build();

authParameters.setUsernamePassword(parameters.username(), new String(parameters.password()));

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()));
} catch (MsalInteropException interopException) {
throw new MsalClientException(interopException.getErrorMessage(), AuthenticationErrorCode.MSALRUNTIME_INTEROP_ERROR);
}
}

@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 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);
}
}

/**
* 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() {
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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,22 @@ 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 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_package_error";

/**
* Indicates an error in the MSAL Java Brokers package
*/
public final static String MSALJAVA_BROKERS_ERROR = "brokers_package_error";
}
68 changes: 45 additions & 23 deletions msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/IBroker.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,58 +3,80 @@

package com.microsoft.aad.msal4j;

import java.util.Set;
import com.nimbusds.jwt.JWTParser;

import java.net.URL;
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
*/

default boolean isAvailable(){
return false;
}
/**
* Acquire a token silently, i.e. without direct user interaction
*
* This may be accomplished by returning tokens from a token cache, using cached refresh tokens to get new tokens,
* or via any authentication flow where a user is not prompted to enter credentials
*
* @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 CompletableFuture<IAuthenticationResult> 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
*
* @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 CompletableFuture<IAuthenticationResult> 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
*
* @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 CompletableFuture<IAuthenticationResult> acquireToken(PublicClientApplication application, UserNamePasswordParameters parameters) {
throw new MsalClientException("Broker implementation missing", AuthenticationErrorCode.MISSING_BROKER);
}

default CompletableFuture removeAccount(IAccount account) {
default void removeAccount(PublicClientApplication application, IAccount account) throws MsalClientException {
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
*/
default IAuthenticationResult parseBrokerAuthResult(String authority, String idToken, String accessToken,
String accountId, String clientInfo,
long accessTokenExpirationTime) {

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) {
throw new MsalClientException(String.format("Exception when converting broker result to MSAL Java AuthenticationResult: %s", e.getMessage()), AuthenticationErrorCode.MSALJAVA_BROKERS_ERROR);
}
return builder.build();
}
}
Loading