Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…tion-library-for-java into avdunn/ciam-custom
  • Loading branch information
Avery-Dunn committed Aug 23, 2024
2 parents 031e209 + 260656e commit 7e7ea1c
Show file tree
Hide file tree
Showing 34 changed files with 421 additions and 164 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Quick links:
The library supports the following Java environments:
- Java 8 (or higher)

Current version - 1.16.1
Current version - 1.16.2

You can find the changes for each version in the [change log](https://github.com/AzureAD/microsoft-authentication-library-for-java/blob/main/msal4j-sdk/changelog.txt).

Expand All @@ -28,13 +28,13 @@ Find [the latest package in the Maven repository](https://mvnrepository.com/arti
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>msal4j</artifactId>
<version>1.16.1</version>
<version>1.16.2</version>
</dependency>
```
### Gradle

```gradle
implementation group: 'com.microsoft.azure', name: 'com.microsoft.aad.msal4j', version: '1.16.1'
implementation group: 'com.microsoft.azure', name: 'com.microsoft.aad.msal4j', version: '1.16.2'
```

## Usage
Expand Down
6 changes: 6 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
Version 1.16.2
=============
- Use SHA256 thumbprints in non-ADFS cert flows (#840)
- Reduce logging level of cache miss messages (#844)
- Make ManagedIdentitySourceType enum public (#845)

Version 1.16.1
=============
- Add missing refreshOn metadata (#838)
Expand Down
2 changes: 1 addition & 1 deletion msal4j-persistence-extension/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>msal4j</artifactId>
<version>1.15.0</version>
<version>1.15.1</version>
</dependency>

<dependency>
Expand Down
6 changes: 3 additions & 3 deletions msal4j-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Quick links:
The library supports the following Java environments:
- Java 8 (or higher)

Current version - 1.16.1
Current version - 1.16.2

You can find the changes for each version in the [change log](https://github.com/AzureAD/microsoft-authentication-library-for-java/blob/master/changelog.txt).

Expand All @@ -28,13 +28,13 @@ Find [the latest package in the Maven repository](https://mvnrepository.com/arti
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>msal4j</artifactId>
<version>1.16.1</version>
<version>1.16.2</version>
</dependency>
```
### Gradle

```gradle
compile group: 'com.microsoft.azure', name: 'msal4j', version: '1.16.1'
compile group: 'com.microsoft.azure', name: 'msal4j', version: '1.16.2'
```

## Usage
Expand Down
2 changes: 1 addition & 1 deletion msal4j-sdk/bnd.bnd
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
Export-Package: com.microsoft.aad.msal4j;version="1.16.1"
Export-Package: com.microsoft.aad.msal4j;version="1.16.2"
Automatic-Module-Name: com.microsoft.aad.msal4j
2 changes: 1 addition & 1 deletion msal4j-sdk/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.microsoft.azure</groupId>
<artifactId>msal4j</artifactId>
<version>1.16.1</version>
<version>1.16.2</version>
<packaging>jar</packaging>
<name>msal4j</name>
<description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ void acquireTokenSilent_ConfidentialClient_acquireTokenSilent(String environment
}

@Test
public void acquireTokenSilent_ConfidentialClient_acquireTokenSilentDifferentScopeThrowsException()
void acquireTokenSilent_ConfidentialClient_acquireTokenSilentDifferentScopeThrowsException()
throws Exception {
cfg = new Config(AzureEnvironment.AZURE);

Expand Down Expand Up @@ -344,6 +344,48 @@ void acquireTokenSilent_emptyScopeSet(String environment) throws Exception {
assertEquals(result.accessToken(), silentResult.accessToken());
}

@Test
public void acquireTokenSilent_ClaimsForceRefresh() throws Exception {
cfg = new Config(AzureEnvironment.AZURE);
User user = labUserProvider.getDefaultUser(AzureEnvironment.AZURE);

Set<String> scopes = new HashSet<>();
PublicClientApplication pca = PublicClientApplication.builder(
user.getAppId()).
authority(cfg.organizationsAuthority()).
build();

IAuthenticationResult result = pca.acquireToken(UserNamePasswordParameters.
builder(scopes,
user.getUpn(),
user.getPassword().toCharArray())
.build())
.get();

assertResultNotNull(result);

IAuthenticationResult silentResultWithoutClaims = pca.acquireTokenSilently(SilentParameters.
builder(scopes, result.account())
.build())
.get();

assertResultNotNull(silentResultWithoutClaims);
assertEquals(result.accessToken(), silentResultWithoutClaims.accessToken());

//If claims are added to a silent request, it should trigger the refresh flow and return a new token
ClaimsRequest cr = new ClaimsRequest();
cr.requestClaimInAccessToken("email", null);

IAuthenticationResult silentResultWithClaims = pca.acquireTokenSilently(SilentParameters.
builder(scopes, result.account())
.claims(cr)
.build())
.get();

assertResultNotNull(silentResultWithClaims);
assertNotEquals(result.accessToken(), silentResultWithClaims.accessToken());
}

private IConfidentialClientApplication getConfidentialClientApplications() throws Exception {
String clientId = cfg.appProvider.getOboAppId();
String password = cfg.appProvider.getOboAppPassword();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ private ClientAssertion getClientAssertion(String clientId) {
clientId,
(ClientCertificate) certificate,
"https://login.microsoftonline.com/common/oauth2/v2.0/token",
true);
true, false);
}

private void assertAcquireTokenCommon(String clientId, IClientCredential credential, String authority) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@
//base class for all sources that support managed identity
abstract class AbstractManagedIdentitySource {

protected static final String TIMEOUT_ERROR = "[Managed Identity] Authentication unavailable. The request to the managed identity endpoint timed out.";
private static final Logger LOG = LoggerFactory.getLogger(AbstractManagedIdentitySource.class);
private static final String MANAGED_IDENTITY_NO_RESPONSE_RECEIVED = "[Managed Identity] Authentication unavailable. No response received from the managed identity endpoint.";

protected final ManagedIdentityRequest managedIdentityRequest;
protected final ServiceBundle serviceBundle;
ManagedIdentitySourceType managedIdentitySourceType;
ManagedIdentityIdType idType;
String userAssignedId;

@Getter
@Setter
Expand All @@ -40,6 +41,8 @@ public AbstractManagedIdentitySource(MsalRequest msalRequest, ServiceBundle serv
this.managedIdentityRequest = (ManagedIdentityRequest) msalRequest;
this.managedIdentitySourceType = sourceType;
this.serviceBundle = serviceBundle;
this.idType = ((ManagedIdentityApplication) msalRequest.application()).getManagedIdentityId().getIdType();
this.userAssignedId = ((ManagedIdentityApplication) msalRequest.application()).getManagedIdentityId().getUserAssignedId();
}

public ManagedIdentityResponse getManagedIdentityResponse(
Expand All @@ -49,15 +52,9 @@ public ManagedIdentityResponse getManagedIdentityResponse(
IHttpResponse response;

try {

HttpRequest httpRequest = managedIdentityRequest.method.equals(HttpMethod.GET) ?
new HttpRequest(HttpMethod.GET,
managedIdentityRequest.computeURI().toString(),
managedIdentityRequest.headers) :
new HttpRequest(HttpMethod.POST,
HttpRequest httpRequest = new HttpRequest(managedIdentityRequest.method,
managedIdentityRequest.computeURI().toString(),
managedIdentityRequest.headers,
managedIdentityRequest.getBodyAsString());
managedIdentityRequest.headers);
response = serviceBundle.getHttpHelper().executeHttpRequest(httpRequest, managedIdentityRequest.requestContext(), serviceBundle);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
class AcquireTokenSilentSupplier extends AuthenticationResultSupplier {

private SilentRequest silentRequest;
protected static final int ACCESS_TOKEN_EXPIRE_BUFFER_IN_SEC = 5 * 60;

AcquireTokenSilentSupplier(AbstractApplicationBase clientApplication, SilentRequest silentRequest) {
super(clientApplication, silentRequest);
Expand All @@ -22,6 +23,7 @@ class AcquireTokenSilentSupplier extends AuthenticationResultSupplier {

@Override
AuthenticationResult execute() throws Exception {
boolean shouldRefresh;
Authority requestAuthority = silentRequest.requestAuthority();
if (requestAuthority.authorityType != AuthorityType.B2C) {
requestAuthority =
Expand Down Expand Up @@ -53,29 +55,9 @@ AuthenticationResult execute() throws Exception {
clientApplication.serviceBundle().getServerSideTelemetry().incrementSilentSuccessfulCount();
}

//Determine if the current token needs to be refreshed according to the refresh_in value
long currTimeStampSec = new Date().getTime() / 1000;
boolean afterRefreshOn = res.refreshOn() != null && res.refreshOn() > 0 &&
res.refreshOn() < currTimeStampSec && res.expiresOn() >= currTimeStampSec;

if (silentRequest.parameters().forceRefresh() || afterRefreshOn || StringHelper.isBlank(res.accessToken())) {

//As of version 3 of the telemetry schema, there is a field for collecting data about why a token was refreshed,
// so here we set the telemetry value based on the cause of the refresh
if (silentRequest.parameters().forceRefresh()) {
clientApplication.serviceBundle().getServerSideTelemetry().getCurrentRequest().cacheInfo(
CacheTelemetry.REFRESH_FORCE_REFRESH.telemetryValue);
} else if (afterRefreshOn) {
clientApplication.serviceBundle().getServerSideTelemetry().getCurrentRequest().cacheInfo(
CacheTelemetry.REFRESH_REFRESH_IN.telemetryValue);
} else if (res.expiresOn() < currTimeStampSec) {
clientApplication.serviceBundle().getServerSideTelemetry().getCurrentRequest().cacheInfo(
CacheTelemetry.REFRESH_ACCESS_TOKEN_EXPIRED.telemetryValue);
} else if (StringHelper.isBlank(res.accessToken())) {
clientApplication.serviceBundle().getServerSideTelemetry().getCurrentRequest().cacheInfo(
CacheTelemetry.REFRESH_NO_ACCESS_TOKEN.telemetryValue);
}
shouldRefresh = shouldRefresh(silentRequest.parameters(), res);

if (shouldRefresh || clientApplication.serviceBundle().getServerSideTelemetry().getCurrentRequest().cacheInfo() == CacheTelemetry.REFRESH_REFRESH_IN.telemetryValue) {
if (!StringHelper.isBlank(res.refreshToken())) {
//There are certain scenarios where the cached authority may differ from the client app's authority,
// such as when a request is instance aware. Unless overridden by SilentParameters.authorityUrl, the
Expand All @@ -84,29 +66,7 @@ AuthenticationResult execute() throws Exception {
requestAuthority = Authority.createAuthority(new URL(requestAuthority.authority().replace(requestAuthority.host(),
res.account().environment())));
}

RefreshTokenRequest refreshTokenRequest = new RefreshTokenRequest(
RefreshTokenParameters.builder(silentRequest.parameters().scopes(), res.refreshToken()).build(),
silentRequest.application(),
silentRequest.requestContext(),
silentRequest);

AcquireTokenByAuthorizationGrantSupplier acquireTokenByAuthorisationGrantSupplier =
new AcquireTokenByAuthorizationGrantSupplier(clientApplication, refreshTokenRequest, requestAuthority);

try {
res = acquireTokenByAuthorisationGrantSupplier.execute();

res.metadata().tokenSource(TokenSource.IDENTITY_PROVIDER);

log.info("Access token refreshed successfully.");
} catch (MsalServiceException ex) {
//If the token refresh attempt threw a MsalServiceException but the refresh attempt was done
// only because of refreshOn, then simply return the existing cached token
if (afterRefreshOn && !(silentRequest.parameters().forceRefresh() || StringHelper.isBlank(res.accessToken()))) {
return res;
} else throw ex;
}
res = makeRefreshRequest(res, requestAuthority);
} else {
res = null;
}
Expand All @@ -120,4 +80,81 @@ AuthenticationResult execute() throws Exception {

return res;
}

private AuthenticationResult makeRefreshRequest(AuthenticationResult cachedResult, Authority requestAuthority) throws Exception {
RefreshTokenRequest refreshTokenRequest = new RefreshTokenRequest(
RefreshTokenParameters.builder(silentRequest.parameters().scopes(), cachedResult.refreshToken()).build(),
silentRequest.application(),
silentRequest.requestContext(),
silentRequest);

AcquireTokenByAuthorizationGrantSupplier acquireTokenByAuthorisationGrantSupplier =
new AcquireTokenByAuthorizationGrantSupplier(clientApplication, refreshTokenRequest, requestAuthority);

try {
AuthenticationResult refreshedResult = acquireTokenByAuthorisationGrantSupplier.execute();

refreshedResult.metadata().tokenSource(TokenSource.IDENTITY_PROVIDER);

log.info("Access token refreshed successfully.");
return refreshedResult;
} catch (MsalServiceException ex) {
//If the token refresh attempt threw a MsalServiceException but the refresh attempt was done
// only because of refreshOn, then simply return the existing cached token rather than throw an exception
if (clientApplication.serviceBundle().getServerSideTelemetry().getCurrentRequest().cacheInfo() == CacheTelemetry.REFRESH_REFRESH_IN.telemetryValue) {
return cachedResult;
}
throw ex;
}
}

//Handles any logic to determine if a token should be refreshed, based on the request parameters and the status of cached tokens
private boolean shouldRefresh(SilentParameters parameters, AuthenticationResult cachedResult) {

//If forceRefresh is true, no reason to check any other option
if (parameters.forceRefresh()) {
setCacheTelemetry(CacheTelemetry.REFRESH_FORCE_REFRESH.telemetryValue);
log.debug("Refreshing access token because forceRefresh parameter is true.");
return true;
}

//If the request contains claims then the token should be refreshed, to ensure that the returned token has the correct claims
// Note: these are the types of claims found in (for example) a claims challenge, and do not include client capabilities
if (parameters.claims() != null) {
setCacheTelemetry(CacheTelemetry.REFRESH_FORCE_REFRESH.telemetryValue);
log.debug("Refreshing access token because the claims parameter is not null.");
return true;
}

long currTimeStampSec = new Date().getTime() / 1000;

//If the access token is expired or within 5 minutes of becoming expired, refresh it
if (!StringHelper.isBlank(cachedResult.accessToken()) && cachedResult.expiresOn() < (currTimeStampSec - ACCESS_TOKEN_EXPIRE_BUFFER_IN_SEC)) {
setCacheTelemetry(CacheTelemetry.REFRESH_ACCESS_TOKEN_EXPIRED.telemetryValue);
log.debug("Refreshing access token because it is expired.");
return true;
}

//Certain long-lived tokens will have a 'refresh on' time that indicates a refresh should be attempted long before the token would expire
if (!StringHelper.isBlank(cachedResult.accessToken()) &&
cachedResult.refreshOn() != null && cachedResult.refreshOn() > 0 &&
cachedResult.refreshOn() < currTimeStampSec && cachedResult.expiresOn() >= (currTimeStampSec + ACCESS_TOKEN_EXPIRE_BUFFER_IN_SEC)){
setCacheTelemetry(CacheTelemetry.REFRESH_REFRESH_IN.telemetryValue);
log.debug("Attempting to refresh access token because it is after the refreshOn time.");
return true;
}

//If there is a refresh token but no access token, we should use the refresh token to get the access token
if (StringHelper.isBlank(cachedResult.accessToken()) && !StringHelper.isBlank(cachedResult.refreshToken())) {
setCacheTelemetry(CacheTelemetry.REFRESH_NO_ACCESS_TOKEN.telemetryValue);
log.debug("Refreshing access token because it was missing from the cache.");
return true;
}

return false;
}

private void setCacheTelemetry(int cacheInfoValue){
clientApplication.serviceBundle().getServerSideTelemetry().getCurrentRequest().cacheInfo(cacheInfoValue);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,9 @@ public void createManagedIdentityRequest(String resource) {
managedIdentityRequest.queryParameters.put("api-version", Collections.singletonList(APP_SERVICE_MSI_API_VERSION));
managedIdentityRequest.queryParameters.put("resource", Collections.singletonList(resource));

if (!StringHelper.isNullOrBlank(getManagedIdentityUserAssignedClientId()))
{
LOG.info("[Managed Identity] Adding user assigned client id to the request.");
managedIdentityRequest.queryParameters.put(Constants.MANAGED_IDENTITY_CLIENT_ID, Collections.singletonList(getManagedIdentityUserAssignedClientId()));
}

if (!StringHelper.isNullOrBlank(getManagedIdentityUserAssignedResourceId()))
{
LOG.info("[Managed Identity] Adding user assigned resource id to the request.");
managedIdentityRequest.queryParameters.put(Constants.MANAGED_IDENTITY_RESOURCE_ID, Collections.singletonList(getManagedIdentityUserAssignedResourceId()));
if (this.idType != null && !StringHelper.isNullOrBlank(this.userAssignedId)) {
LOG.info("[Managed Identity] Adding user assigned ID to the request for App Service Managed Identity.");
managedIdentityRequest.addUserAssignedIdToQuery(this.idType, this.userAssignedId);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,17 @@ public IAuthenticationResult get() {
msalRequest.requestContext().correlationId(),
error);

clientApplication.log.warn(
LogHelper.createMessage(
String.format("Execution of %s failed: %s", this.getClass(), ex.getMessage()),
msalRequest.headers().getHeaderCorrelationIdValue()));
String logMessage = LogHelper.createMessage(
String.format("Execution of %s failed: %s", this.getClass(), ex.getMessage()),
msalRequest.headers().getHeaderCorrelationIdValue());
if (ex instanceof MsalClientException) {
MsalClientException exception = (MsalClientException) ex;
if (exception.errorCode() != null && exception.errorCode().equalsIgnoreCase(AuthenticationErrorCode.CACHE_MISS)) {
clientApplication.log.debug(logMessage);
}
} else {
clientApplication.log.warn(logMessage);
}

throw new CompletionException(ex);
}
Expand Down
Loading

0 comments on commit 7e7ea1c

Please sign in to comment.