|
11 | 11 | import static org.junit.jupiter.api.Assertions.assertNotNull; |
12 | 12 | import static org.junit.jupiter.api.Assertions.assertEquals; |
13 | 13 | import static org.junit.jupiter.api.Assertions.assertInstanceOf; |
| 14 | +import static org.mockito.Mockito.*; |
14 | 15 |
|
15 | 16 | import java.io.IOException; |
16 | 17 | import java.net.URISyntaxException; |
17 | 18 | import java.nio.file.Files; |
18 | 19 | import java.nio.file.Paths; |
19 | 20 | import java.util.Collections; |
| 21 | +import java.util.HashMap; |
20 | 22 | import java.util.concurrent.CompletableFuture; |
21 | 23 | import java.util.concurrent.ExecutionException; |
| 24 | +import java.util.concurrent.TimeUnit; |
22 | 25 |
|
23 | 26 |
|
24 | 27 | @TestInstance(TestInstance.Lifecycle.PER_CLASS) |
@@ -116,6 +119,97 @@ void confidentialAppAcquireTokenSilently_claimsSkipCache() throws Throwable { |
116 | 119 | assertInstanceOf(MsalInteractionRequiredException.class, ex.getCause()); |
117 | 120 | } |
118 | 121 |
|
| 122 | + @Test |
| 123 | + void testTokenRefreshReasons() throws Exception { |
| 124 | + DefaultHttpClient httpClientMock = mock(DefaultHttpClient.class); |
| 125 | + |
| 126 | + ConfidentialClientApplication cca = |
| 127 | + ConfidentialClientApplication.builder("clientId", ClientCredentialFactory.createFromSecret("password")) |
| 128 | + .authority("https://login.microsoftonline.com/tenant/") |
| 129 | + .instanceDiscovery(false) |
| 130 | + .validateAuthority(false) |
| 131 | + .httpClient(httpClientMock) |
| 132 | + .build(); |
| 133 | + |
| 134 | + HashMap<String, String> responseParameters = new HashMap<>(); |
| 135 | + |
| 136 | + //Acquire a token that expires at the same time it is acquired, so it will expire before the next acquire token call |
| 137 | + responseParameters.put("access_token", "expiredToken"); |
| 138 | + responseParameters.put("id_token", TestHelper.createIdToken(new HashMap<>())); |
| 139 | + responseParameters.put("expires_in", "0"); |
| 140 | + TestHelper.createTokenRequestMock(httpClientMock, TestHelper.getSuccessfulTokenResponse(responseParameters), 200); |
| 141 | + |
| 142 | + OnBehalfOfParameters parameters = OnBehalfOfParameters.builder(Collections.singleton("someScopes"), new UserAssertion(TestHelper.signedAssertion)).build(); |
| 143 | + IAuthenticationResult result = cca.acquireToken(parameters).get(); |
| 144 | + |
| 145 | + //There should be one token in the cache, and no refresh behavior should have happened yet |
| 146 | + assertRefreshedToken(result, "expiredToken", CacheRefreshReason.NOT_APPLICABLE, cca.tokenCache.accessTokens.size()); |
| 147 | + |
| 148 | + //Attempt to retrieve the cached token, however it is expired and should be refreshed. |
| 149 | + // In this test, it will be replaced with a token that expires in 1 minute |
| 150 | + responseParameters.put("access_token", "nearlyExpiredToken"); |
| 151 | + responseParameters.put("expires_in", "60"); |
| 152 | + TestHelper.createTokenRequestMock(httpClientMock, TestHelper.getSuccessfulTokenResponse(responseParameters), 200); |
| 153 | + |
| 154 | + SilentParameters silentParameters = SilentParameters.builder(Collections.singleton("someScopes"), result.account()).build(); |
| 155 | + result = cca.acquireTokenSilently(silentParameters).get(); |
| 156 | + |
| 157 | + //Ensure there is still one token in the cache, however it is the new refreshed token rather than the token from the first mocked call |
| 158 | + assertRefreshedToken(result, "nearlyExpiredToken", CacheRefreshReason.EXPIRED, cca.tokenCache.accessTokens.size()); |
| 159 | + |
| 160 | + //Attempt to retrieve the cached token, however it is within the 5-minute buffer and should be refreshed. |
| 161 | + // In this test, it will be replaced with a token that expires in 1 hour but has a refresh_in time of 1 second |
| 162 | + responseParameters.put("access_token", "refreshInToken"); |
| 163 | + responseParameters.put("expires_in", "3600"); |
| 164 | + responseParameters.put("refresh_in", "1"); |
| 165 | + TestHelper.createTokenRequestMock(httpClientMock, TestHelper.getSuccessfulTokenResponse(responseParameters), 200); |
| 166 | + |
| 167 | + silentParameters = SilentParameters.builder(Collections.singleton("someScopes"), result.account()).build(); |
| 168 | + result = cca.acquireTokenSilently(silentParameters).get(); |
| 169 | + |
| 170 | + assertRefreshedToken(result, "refreshInToken", CacheRefreshReason.EXPIRED, cca.tokenCache.accessTokens.size()); |
| 171 | + |
| 172 | + //Attempt to retrieve the cached token, however it is within the 5-minute buffer and should be refreshed. |
| 173 | + // In this test, it will be replaced with a token that expires in 1 hour (and does not have a valid refresh_in time) |
| 174 | + responseParameters.put("access_token", "normalToken"); |
| 175 | + responseParameters.put("expires_in", "3600"); |
| 176 | + responseParameters.put("refresh_in", "0"); |
| 177 | + TestHelper.createTokenRequestMock(httpClientMock, TestHelper.getSuccessfulTokenResponse(responseParameters), 200); |
| 178 | + |
| 179 | + //refresh_in values are in seconds, so we must wait to guarantee it is past the proactive refresh time |
| 180 | + TimeUnit.SECONDS.sleep(2); |
| 181 | + |
| 182 | + silentParameters = SilentParameters.builder(Collections.singleton("someScopes"), result.account()).build(); |
| 183 | + result = cca.acquireTokenSilently(silentParameters).get(); |
| 184 | + |
| 185 | + assertRefreshedToken(result, "normalToken", CacheRefreshReason.PROACTIVE_REFRESH, cca.tokenCache.accessTokens.size()); |
| 186 | + |
| 187 | + //Force the token to be refreshed |
| 188 | + responseParameters.put("access_token", "forcedRefreshToken"); |
| 189 | + TestHelper.createTokenRequestMock(httpClientMock, TestHelper.getSuccessfulTokenResponse(responseParameters), 200); |
| 190 | + |
| 191 | + silentParameters = SilentParameters.builder(Collections.singleton("someScopes"), result.account()).forceRefresh(true).build(); |
| 192 | + result = cca.acquireTokenSilently(silentParameters).get(); |
| 193 | + |
| 194 | + assertRefreshedToken(result, "forcedRefreshToken", CacheRefreshReason.FORCE_REFRESH, cca.tokenCache.accessTokens.size()); |
| 195 | + |
| 196 | + //Finally, force a refresh by setting claims |
| 197 | + responseParameters.put("access_token", "claimsToken"); |
| 198 | + TestHelper.createTokenRequestMock(httpClientMock, TestHelper.getSuccessfulTokenResponse(responseParameters), 200); |
| 199 | + |
| 200 | + silentParameters = SilentParameters.builder(Collections.singleton("someScopes"), result.account()).claims(new ClaimsRequest()).build(); |
| 201 | + result = cca.acquireTokenSilently(silentParameters).get(); |
| 202 | + |
| 203 | + assertRefreshedToken(result, "claimsToken", CacheRefreshReason.CLAIMS, cca.tokenCache.accessTokens.size()); |
| 204 | + } |
| 205 | + |
| 206 | + //Asserts that there is one expected token in the cache, and that it was refreshed with the expected reason |
| 207 | + private void assertRefreshedToken(IAuthenticationResult result, String expectedToken, CacheRefreshReason expectedReason, int cacheSize) { |
| 208 | + assertEquals(1, cacheSize); |
| 209 | + assertEquals(expectedToken, result.accessToken()); |
| 210 | + assertEquals(expectedReason, result.metadata().cacheRefreshReason()); |
| 211 | + } |
| 212 | + |
119 | 213 | String readResource(String resource) { |
120 | 214 | try { |
121 | 215 | return new String(Files.readAllBytes(Paths.get(getClass().getResource(resource).toURI()))); |
|
0 commit comments