|
47 | 47 | import org.cloudfoundry.identity.uaa.user.UaaUser; |
48 | 48 | import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; |
49 | 49 | import org.cloudfoundry.identity.uaa.util.AlphanumericRandomValueStringGenerator; |
| 50 | +import org.cloudfoundry.identity.uaa.util.TimeService; |
50 | 51 | import org.cloudfoundry.identity.uaa.util.JsonUtils; |
51 | 52 | import org.cloudfoundry.identity.uaa.util.SessionUtils; |
52 | 53 | import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.ZoneResolutionMode; |
@@ -1023,25 +1024,40 @@ void clientIdentityProviderWithoutAllowedProvidersForPasswordGrantWorksInOtherZo |
1023 | 1024 |
|
1024 | 1025 | @Test |
1025 | 1026 | void getToken_withPasswordGrantType_resultsInUserLastLogonTimestampUpdate() throws Exception { |
1026 | | - long delayTime = 15; |
| 1027 | + TimeService timeService = webApplicationContext.getBean(TimeService.class); |
| 1028 | + long delayTime = 2; |
1027 | 1029 | String username = "testuser" + generator.generate(); |
1028 | 1030 | String userScopes = "uaa.user"; |
1029 | 1031 | ScimUser user = setUpUser(jdbcScimUserProvisioning, jdbcScimGroupMembershipManager, jdbcScimGroupProvisioning, username, userScopes, OriginKeys.UAA, IdentityZone.getUaaZoneId()); |
1030 | 1032 | webApplicationContext.getBean(UaaUserDatabase.class).updateLastLogonTime(user.getId()); |
1031 | 1033 | webApplicationContext.getBean(UaaUserDatabase.class).updateLastLogonTime(user.getId()); |
1032 | | - |
| 1034 | + // On a fast processor there isn't enough granularity in the time; ensure the clock has moved |
| 1035 | + // before each password grant so we get distinct last-logon timestamps. Only sleep when needed. |
| 1036 | + // Use the same TimeService as production so we observe the same clock. |
| 1037 | + long afterSetup = timeService.getCurrentTimeMillis(); |
| 1038 | + ensureClockMoved(timeService, afterSetup, delayTime); |
1033 | 1039 | String accessToken = getAccessTokenForPasswordGrant(username); |
1034 | 1040 | Long firstTimestamp = getPreviousLogonTime(accessToken); |
1035 | | - //simulate two sequential tests |
1036 | | - //on a fast processor, there isn't enough granularity in the time |
1037 | | - Thread.sleep(delayTime); |
| 1041 | + long afterFirstGrant = timeService.getCurrentTimeMillis(); |
| 1042 | + ensureClockMoved(timeService, afterFirstGrant, delayTime); |
1038 | 1043 | String accessToken2 = getAccessTokenForPasswordGrant(username); |
1039 | 1044 | Long secondTimestamp = getPreviousLogonTime(accessToken2); |
1040 | 1045 |
|
1041 | 1046 | assertThat(secondTimestamp).isNotEqualTo(firstTimestamp); |
1042 | 1047 | assertThat(firstTimestamp).isLessThan(secondTimestamp); |
1043 | 1048 | } |
1044 | 1049 |
|
| 1050 | + /** |
| 1051 | + * Waits until the application's {@link TimeService} clock has advanced past {@code notBefore}. |
| 1052 | + * Only sleeps when the clock has not yet moved, so fast runs avoid unnecessary delay. |
| 1053 | + * Uses the same TimeService as production (e.g. last-logon updates) so we observe the same clock. |
| 1054 | + */ |
| 1055 | + private void ensureClockMoved(TimeService timeService, long notBefore, long sleepMs) throws InterruptedException { |
| 1056 | + while (timeService.getCurrentTimeMillis() <= notBefore) { |
| 1057 | + Thread.sleep(sleepMs); |
| 1058 | + } |
| 1059 | + } |
| 1060 | + |
1045 | 1061 | private String getAccessTokenForPasswordGrant(String username) throws Exception { |
1046 | 1062 | String response = mockMvc.perform( |
1047 | 1063 | post("/oauth/token") |
|
0 commit comments