Skip to content

Commit cacad3d

Browse files
SomkaPeAvery-DunnRomanNosachev
authored
1.8.0 release (#314)
* Exception Improvements (#254) * Add null checks for MsalException error code references * Better exception handling for invalid tokens * Better exception handling for invalid tokens * Sync with changes to Azure-Samples/ms-identity-java-desktop (#259) * extra scopes for consent during authorizaion * typo * minor * HTTPClient default timeouts (#264) * Add default timeouts for DefaultHttpClient * Handle 'stay signed in' confirmation page in DeviceCodeIT tests * Small best-practices changes * append extra scopes as suffix * 1.6.2 release (#268) * fixing integ test * Tenant Profiles (#263) * Classes for tenant profile functionality * Implement tenant profile feature * Tests for tenant profile feature * Simplify tenant profile class structure * 1.6.2 release * Classes for tenant profile redesign * Tests for tenant profile redesign * Adjust sample cached ID tokens to have realistic headers * Redesign how Tenant Pofiles are added to Accounts * New error code for JWT parse exceptions * Add claims and tenant profiles fields to Account * Remove annotation excluding realm field from comparisons * Use more generic token * Remove ID token claims field from Account * Minor changes for clarity * Adjust tests for tenant profile design refactor * Refactor tenant profile structure * Minor fixes * Minor fixes * Minor fixes * Simplify tenant profile class Co-authored-by: SomkaPe <[email protected]> * Improve HTTP client timeouts (#275) * 1.6.2 release (#269) * 1.6.2 release * Make DefaultHttpClient timeouts settable * Refactor timeout names Co-authored-by: SomkaPe <[email protected]> * Bewaters certchain (#276) * Support for certificate chain * 1.7.0 release (#277) * Update DefaultHttpClient.java * Fixed parsing ClientInfo: on some accounts, the server response contained characters that are incorrect for Base64 encoding, but acceptable for Base64URL (#282) * sendX5c api (#285) * refactoring (#287) * refactoring * refactoring * refactoring * Add AcquireTokenSilent tests for B2C and ADFS2019, refactor duplicate code in tests (#293) * Add public constants for cloud endpoints (#298) * Add public constants for cloud endpoints * Add license header * Added javadocs * Removed unneeded test * Make IAccount serializable (#297) * Make IAccount objects serializable * Make AuthenticationResult objects not serializable * Add tenant profile/id claims to auth result (#300) * Add tenant profile/id claims to auth result * Minor fix * treat null password as default one - empty string (#304) * treat null password as default one - empty string * Support for refresh_in (#305) * Support for refresh_in * Tests for refresh_in * Add extra null check * Add test for refreshOn cache persistence * refresh on is optional field (#312) * refresh on optional field * 1.8.0 Release (#313) 1.8.0 release Co-authored-by: Avery-Dunn <[email protected]> Co-authored-by: Roman Nosachev <[email protected]>
1 parent 3f7498d commit cacad3d

File tree

21 files changed

+291
-83
lines changed

21 files changed

+291
-83
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Quick links:
1616
The library supports the following Java environments:
1717
- Java 8 (or higher)
1818

19-
Current version - 1.7.1
19+
Current version - 1.8.0
2020

2121
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).
2222

@@ -28,13 +28,13 @@ Find [the latest package in the Maven repository](https://mvnrepository.com/arti
2828
<dependency>
2929
<groupId>com.microsoft.azure</groupId>
3030
<artifactId>msal4j</artifactId>
31-
<version>1.7.1</version>
31+
<version>1.8.0</version>
3232
</dependency>
3333
```
3434
### Gradle
3535

3636
```
37-
compile group: 'com.microsoft.azure', name: 'msal4j', version: '1.7.1'
37+
compile group: 'com.microsoft.azure', name: 'msal4j', version: '1.8.0'
3838
```
3939

4040
## Usage

changelog.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
Version 1.8.0
2+
=============
3+
- ITenantProfile added to IAuthenticationResult for easier access to ID token claims
4+
- IAccount is now serializable
5+
- Support for refresh_in field in token response
6+
- New utility class, AzureCloudEndpoint, for national cloud endpoint URLs
7+
18
Version 1.7.1
29
=============
310
- sendX5c API added to IConfidentialClientApplication to specify if the x5c claim

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<modelVersion>4.0.0</modelVersion>
44
<groupId>com.microsoft.azure</groupId>
55
<artifactId>msal4j</artifactId>
6-
<version>1.7.1</version>
6+
<version>1.8.0</version>
77
<packaging>jar</packaging>
88
<name>msal4j</name>
99
<description>

src/integrationtest/java/com.microsoft.aad.msal4j/AcquireTokenSilentIT.java

Lines changed: 146 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
import org.testng.annotations.BeforeClass;
99
import org.testng.annotations.Test;
1010

11+
import java.net.MalformedURLException;
1112
import java.util.Collections;
13+
import java.util.Date;
1214
import java.util.Set;
1315
import java.util.concurrent.ExecutionException;
1416

@@ -33,15 +35,8 @@ public void acquireTokenSilent_OrganizationAuthority_TokenRefreshed(String envir
3335
IPublicClientApplication pca = getPublicClientApplicationWithTokensInCache();
3436

3537
IAccount account = pca.getAccounts().join().iterator().next();
36-
SilentParameters parameters = SilentParameters.builder(
37-
Collections.singleton(cfg.graphDefaultScope()),
38-
account).build();
39-
40-
IAuthenticationResult result = pca.acquireTokenSilently(parameters).get();
41-
42-
Assert.assertNotNull(result);
43-
Assert.assertNotNull(result.accessToken());
44-
Assert.assertNotNull(result.idToken());
38+
IAuthenticationResult result = acquireTokenSilently(pca, account, cfg.graphDefaultScope(), false);
39+
assertResultNotNull(result);
4540
}
4641

4742
@Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class)
@@ -58,22 +53,12 @@ public void acquireTokenSilent_LabAuthority_TokenNotRefreshed(String environment
5853
authority(cfg.organizationsAuthority()).
5954
build();
6055

61-
IAuthenticationResult result = pca.acquireToken(UserNamePasswordParameters.
62-
builder(Collections.singleton(cfg.graphDefaultScope()),
63-
user.getUpn(),
64-
user.getPassword().toCharArray())
65-
.build())
66-
.get();
56+
IAuthenticationResult result = acquireTokenUsernamePassword(user, pca, cfg.graphDefaultScope());
6757

6858
IAccount account = pca.getAccounts().join().iterator().next();
69-
SilentParameters parameters = SilentParameters.builder(
70-
Collections.singleton(cfg.graphDefaultScope()), account).
71-
build();
72-
73-
IAuthenticationResult acquireSilentResult = pca.acquireTokenSilently(parameters).get();
59+
IAuthenticationResult acquireSilentResult = acquireTokenSilently(pca, account, cfg.graphDefaultScope(), false);
60+
assertResultNotNull(result);
7461

75-
Assert.assertNotNull(acquireSilentResult.accessToken());
76-
Assert.assertNotNull(result.idToken());
7762
// Check that access and id tokens are coming from cache
7863
Assert.assertEquals(result.accessToken(), acquireSilentResult.accessToken());
7964
Assert.assertEquals(result.idToken(), acquireSilentResult.idToken());
@@ -90,27 +75,15 @@ public void acquireTokenSilent_ForceRefresh(String environment) throws Exception
9075
authority(cfg.organizationsAuthority()).
9176
build();
9277

93-
IAuthenticationResult result = pca.acquireToken(UserNamePasswordParameters.
94-
builder(Collections.singleton(cfg.graphDefaultScope()),
95-
user.getUpn(),
96-
user.getPassword().toCharArray())
97-
.build())
98-
.get();
78+
IAuthenticationResult result = acquireTokenUsernamePassword(user, pca, cfg.graphDefaultScope());
79+
assertResultNotNull(result);
9980

10081
IAccount account = pca.getAccounts().join().iterator().next();
101-
SilentParameters parameters = SilentParameters.builder(
102-
Collections.singleton(cfg.graphDefaultScope()), account).
103-
forceRefresh(true).
104-
build();
105-
106-
IAuthenticationResult resultAfterRefresh = pca.acquireTokenSilently(parameters).get();
82+
IAuthenticationResult resultAfterRefresh = acquireTokenSilently(pca, account, cfg.graphDefaultScope(), true);
83+
assertResultNotNull(resultAfterRefresh);
10784

108-
Assert.assertNotNull(resultAfterRefresh);
109-
Assert.assertNotNull(resultAfterRefresh.accessToken());
110-
Assert.assertNotNull(resultAfterRefresh.idToken());
11185
// Check that new refresh and id tokens are being returned
112-
Assert.assertNotEquals(result.accessToken(), resultAfterRefresh.accessToken());
113-
Assert.assertNotEquals(result.idToken(), resultAfterRefresh.idToken());
86+
assertResultRefreshed(result, resultAfterRefresh);
11487
}
11588

11689
@Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class)
@@ -123,31 +96,73 @@ public void acquireTokenSilent_MultipleAccountsInCache_UseCorrectAccount(String
12396
User user = labUserProvider.getFederatedAdfsUser(cfg.azureEnvironment, FederationProvider.ADFS_4);
12497

12598
// acquire token for different account
126-
pca.acquireToken(UserNamePasswordParameters.
127-
builder(Collections.singleton(cfg.graphDefaultScope()),
128-
user.getUpn(),
129-
user.getPassword().toCharArray())
130-
.build())
131-
.get();
99+
acquireTokenUsernamePassword(user, pca, cfg.graphDefaultScope());
132100

133101
Set<IAccount> accounts = pca.getAccounts().join();
134102
IAccount account = accounts.stream().filter(
135103
x -> x.username().equalsIgnoreCase(
136104
user.getUpn())).findFirst().orElse(null);
137105

138-
SilentParameters parameters = SilentParameters.builder(
139-
Collections.singleton(cfg.graphDefaultScope()), account).
140-
forceRefresh(true).
106+
IAuthenticationResult result = acquireTokenSilently(pca, account, cfg.graphDefaultScope(), false);
107+
assertResultNotNull(result);
108+
Assert.assertEquals(result.account().username(), user.getUpn());
109+
}
110+
111+
@Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class)
112+
public void acquireTokenSilent_ADFS2019(String environment) throws Exception{
113+
cfg = new Config(environment);
114+
115+
UserQueryParameters query = new UserQueryParameters();
116+
query.parameters.put(UserQueryParameters.AZURE_ENVIRONMENT, cfg.azureEnvironment);
117+
query.parameters.put(UserQueryParameters.FEDERATION_PROVIDER, FederationProvider.ADFS_2019);
118+
query.parameters.put(UserQueryParameters.USER_TYPE, UserType.FEDERATED);
119+
120+
User user = labUserProvider.getLabUser(query);
121+
122+
PublicClientApplication pca = PublicClientApplication.builder(
123+
user.getAppId()).
124+
authority(cfg.organizationsAuthority()).
141125
build();
142126

143-
IAuthenticationResult result = pca.acquireTokenSilently(parameters).get();
127+
IAuthenticationResult result = acquireTokenUsernamePassword(user, pca, cfg.graphDefaultScope());
128+
assertResultNotNull(result);
144129

145-
Assert.assertNotNull(result);
146-
Assert.assertNotNull(result.accessToken());
147-
Assert.assertNotNull(result.idToken());
148-
Assert.assertEquals(result.account().username(), user.getUpn());
130+
IAccount account = pca.getAccounts().join().iterator().next();
131+
IAuthenticationResult acquireSilentResult = acquireTokenSilently(pca, account, TestConstants.ADFS_SCOPE, false);
132+
assertResultNotNull(acquireSilentResult);
133+
134+
account = pca.getAccounts().join().iterator().next();
135+
IAuthenticationResult resultAfterRefresh = acquireTokenSilently(pca, account, TestConstants.ADFS_SCOPE, true);
136+
assertResultNotNull(resultAfterRefresh);
137+
138+
assertResultRefreshed(result, resultAfterRefresh);
139+
}
140+
141+
// Commented out due to unclear B2C behavior causing occasional errors
142+
//@Test
143+
public void acquireTokenSilent_B2C() throws Exception{
144+
UserQueryParameters query = new UserQueryParameters();
145+
query.parameters.put(UserQueryParameters.USER_TYPE, UserType.B2C);
146+
query.parameters.put(UserQueryParameters.B2C_PROVIDER, B2CProvider.LOCAL);
147+
User user = labUserProvider.getLabUser(query);
148+
149+
PublicClientApplication pca = PublicClientApplication.builder(
150+
user.getAppId()).
151+
b2cAuthority(TestConstants.B2C_AUTHORITY_ROPC).
152+
build();
153+
154+
IAuthenticationResult result = acquireTokenUsernamePassword(user, pca, TestConstants.B2C_READ_SCOPE);
155+
assertResultNotNull(result);
156+
157+
IAccount account = pca.getAccounts().join().iterator().next();
158+
IAuthenticationResult resultAfterRefresh = acquireTokenSilently(pca, account, TestConstants.B2C_READ_SCOPE, true);
159+
assertResultNotNull(resultAfterRefresh);
160+
161+
assertResultRefreshed(result, resultAfterRefresh);
149162
}
150163

164+
165+
151166
@Test
152167
public void acquireTokenSilent_usingCommonAuthority_returnCachedAt() throws Exception {
153168
acquireTokenSilent_returnCachedTokens(cfg.organizationsAuthority());
@@ -205,6 +220,50 @@ public void acquireTokenSilent_ConfidentialClient_acquireTokenSilentDifferentSco
205220
.get();
206221
}
207222

223+
@Test(dataProvider = "environments", dataProviderClass = EnvironmentsProvider.class)
224+
public void acquireTokenSilent_WithRefreshOn(String environment) throws Exception{
225+
cfg = new Config(environment);
226+
227+
User user = labUserProvider.getDefaultUser(cfg.azureEnvironment);
228+
229+
PublicClientApplication pca = PublicClientApplication.builder(
230+
user.getAppId()).
231+
authority(cfg.organizationsAuthority()).
232+
build();
233+
234+
IAuthenticationResult resultOriginal = acquireTokenUsernamePassword(user, pca, cfg.graphDefaultScope());
235+
assertResultNotNull(resultOriginal);
236+
237+
IAuthenticationResult resultSilent = acquireTokenSilently(pca, resultOriginal.account(), cfg.graphDefaultScope(), false);
238+
Assert.assertNotNull(resultSilent);
239+
assertTokensAreEqual(resultOriginal, resultSilent);
240+
241+
//When this test was made, token responses did not contain the refresh_in field needed for an end-to-end test.
242+
//In order to test silent flow behavior as though the service returned refresh_in, we manually change a cached
243+
// token's refreshOn value from 0 (default if refresh_in missing) to a minute before/after the current time
244+
String key = pca.tokenCache.accessTokens.keySet().iterator().next();
245+
AccessTokenCacheEntity token = pca.tokenCache.accessTokens.get(key);
246+
long currTimestampSec = new Date().getTime() / 1000;
247+
248+
token.refreshOn(Long.toString(currTimestampSec + 60));
249+
pca.tokenCache.accessTokens.put(key, token);
250+
251+
IAuthenticationResult resultSilentWithRefreshOn = acquireTokenSilently(pca, resultOriginal.account(), cfg.graphDefaultScope(), false);
252+
//Current time is before refreshOn, so token should not have been refreshed
253+
Assert.assertNotNull(resultSilentWithRefreshOn);
254+
Assert.assertEquals(pca.tokenCache.accessTokens.get(key).refreshOn(), Long.toString(currTimestampSec + 60));
255+
assertTokensAreEqual(resultSilent, resultSilentWithRefreshOn);
256+
257+
token = pca.tokenCache.accessTokens.get(key);
258+
token.refreshOn(Long.toString(currTimestampSec - 60));
259+
pca.tokenCache.accessTokens.put(key, token);
260+
261+
resultSilentWithRefreshOn = acquireTokenSilently(pca, resultOriginal.account(), cfg.graphDefaultScope(), false);
262+
//Current time is after refreshOn, so token should be refreshed
263+
Assert.assertNotNull(resultSilentWithRefreshOn);
264+
assertResultRefreshed(resultSilent, resultSilentWithRefreshOn);
265+
}
266+
208267
private IConfidentialClientApplication getConfidentialClientApplications() throws Exception{
209268
String clientId = cfg.appProvider.getOboAppId();
210269
String password = cfg.appProvider.getOboAppPassword();
@@ -226,12 +285,7 @@ private void acquireTokenSilent_returnCachedTokens(String authority) throws Exce
226285
authority(authority).
227286
build();
228287

229-
IAuthenticationResult interactiveAuthResult = pca.acquireToken(UserNamePasswordParameters.
230-
builder(Collections.singleton(cfg.graphDefaultScope()),
231-
user.getUpn(),
232-
user.getPassword().toCharArray())
233-
.build())
234-
.get();
288+
IAuthenticationResult interactiveAuthResult = acquireTokenUsernamePassword(user, pca, cfg.graphDefaultScope());
235289

236290
Assert.assertNotNull(interactiveAuthResult);
237291

@@ -254,12 +308,40 @@ private IPublicClientApplication getPublicClientApplicationWithTokensInCache()
254308
authority(cfg.organizationsAuthority()).
255309
build();
256310

257-
pca.acquireToken(
258-
UserNamePasswordParameters.builder(
259-
Collections.singleton(cfg.graphDefaultScope()),
311+
acquireTokenUsernamePassword(user, pca, cfg.graphDefaultScope());
312+
return pca;
313+
}
314+
315+
private IAuthenticationResult acquireTokenSilently(IPublicClientApplication pca, IAccount account, String scope, Boolean forceRefresh) throws InterruptedException, ExecutionException, MalformedURLException {
316+
return pca.acquireTokenSilently(SilentParameters.
317+
builder(Collections.singleton(scope), account).
318+
forceRefresh(forceRefresh).
319+
build())
320+
.get();
321+
}
322+
323+
private IAuthenticationResult acquireTokenUsernamePassword(User user, IPublicClientApplication pca, String scope) throws InterruptedException, ExecutionException {
324+
return pca.acquireToken(UserNamePasswordParameters.
325+
builder(Collections.singleton(scope),
260326
user.getUpn(),
261327
user.getPassword().toCharArray())
262-
.build()).get();
263-
return pca;
328+
.build())
329+
.get();
330+
}
331+
332+
private void assertResultNotNull(IAuthenticationResult result) {
333+
Assert.assertNotNull(result);
334+
Assert.assertNotNull(result.accessToken());
335+
Assert.assertNotNull(result.idToken());
336+
}
337+
338+
private void assertResultRefreshed(IAuthenticationResult result, IAuthenticationResult resultAfterRefresh) {
339+
Assert.assertNotEquals(result.accessToken(), resultAfterRefresh.accessToken());
340+
Assert.assertNotEquals(result.idToken(), resultAfterRefresh.idToken());
341+
}
342+
343+
private void assertTokensAreEqual(IAuthenticationResult result, IAuthenticationResult resultAfterRefresh) {
344+
Assert.assertEquals(result.accessToken(), resultAfterRefresh.accessToken());
345+
Assert.assertEquals(result.idToken(), resultAfterRefresh.idToken());
264346
}
265347
}

src/integrationtest/java/com.microsoft.aad.msal4j/CachePersistenceIT.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22
// Licensed under the MIT License.
33
package com.microsoft.aad.msal4j;
44

5+
import com.nimbusds.jwt.JWTClaimsSet;
6+
import com.nimbusds.jwt.PlainJWT;
57
import org.testng.Assert;
68
import org.testng.annotations.Test;
79

810
import java.io.IOException;
911
import java.net.URISyntaxException;
12+
import java.util.Collections;
1013

1114
public class CachePersistenceIT {
1215

@@ -32,6 +35,16 @@ public void afterCacheAccess(ITokenCacheAccessContext iTokenCacheAccessContext)
3235
public void cacheDeserializationSerializationTest() throws IOException, URISyntaxException {
3336
String dataToInitCache = TestHelper.readResource(this.getClass(), "/cache_data/serialized_cache.json");
3437

38+
String ID_TOKEN_PLACEHOLDER = "<idToken_placeholder>";
39+
JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
40+
.audience(Collections.singletonList("jwtAudience"))
41+
.issuer("issuer")
42+
.subject("subject")
43+
.build();
44+
PlainJWT jwt = new PlainJWT(claimsSet);
45+
46+
dataToInitCache = dataToInitCache.replace(ID_TOKEN_PLACEHOLDER, jwt.serialize());
47+
3548
ITokenCacheAccessAspect persistenceAspect = new TokenPersistence(dataToInitCache);
3649

3750
PublicClientApplication app = PublicClientApplication.builder("my_client_id")

src/main/java/com/microsoft/aad/msal4j/AccessTokenCacheEntity.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ class AccessTokenCacheEntity extends Credential {
3434
@JsonProperty("extended_expires_on")
3535
private String extExpiresOn;
3636

37+
@JsonProperty("refresh_on")
38+
private String refreshOn;
39+
3740
String getKey() {
3841
List<String> keyParts = new ArrayList<>();
3942

0 commit comments

Comments
 (0)