Use SessionExpirationUtils for validate user and client sessions
Check client session is valid in TokenManager Closes #24936 Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
parent
f32cd91792
commit
f7044ba5c2
10 changed files with 367 additions and 65 deletions
|
@ -328,4 +328,10 @@ one of the future {project_name} releases. It might be {project_name} 27 release
|
|||
|
||||
`org.keycloak.common.util.Resteasy` has been deprecated. You should use the `org.keycloak.util.KeycloakSessionUtil` to obtain the `KeycloakSession` instead.
|
||||
|
||||
It is highly recommended to avoid obtaining the `KeycloakSession` by means other than when creating your custom provider.
|
||||
It is highly recommended to avoid obtaining the `KeycloakSession` by means other than when creating your custom provider.
|
||||
|
||||
= Small changes in session lifespan and idle calculations
|
||||
|
||||
In previous versions the session max lifespan and idle timeout calculation was slightly different when validating if a session was still valid. Since now that validation uses the same code than the rest of the project.
|
||||
|
||||
If the session is using the remember me feature, the idle timeout and max lifespan are the maximum value between the common SSO and the remember me configuration values.
|
|
@ -21,6 +21,7 @@ import java.util.Collections;
|
|||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
@ -319,4 +320,34 @@ public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSes
|
|||
return copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restartClientSession() {
|
||||
ClientSessionUpdateTask task = new ClientSessionUpdateTask() {
|
||||
|
||||
@Override
|
||||
public void runUpdate(AuthenticatedClientSessionEntity entity) {
|
||||
UserSessionModel userSession = getUserSession();
|
||||
entity.setAction(null);
|
||||
entity.setRedirectUri(null);
|
||||
entity.setCurrentRefreshToken(null);
|
||||
entity.setCurrentRefreshTokenUseCount(-1);
|
||||
entity.setTimestamp(Time.currentTime());
|
||||
entity.getNotes().clear();
|
||||
entity.getNotes().put(AuthenticatedClientSessionModel.STARTED_AT_NOTE, String.valueOf(entity.getTimestamp()));
|
||||
entity.getNotes().put(AuthenticatedClientSessionModel.USER_SESSION_STARTED_AT_NOTE, String.valueOf(userSession.getStarted()));
|
||||
entity.getNotes().put(AuthenticatedClientSessionEntity.CLIENT_ID_NOTE, getClient().getId());
|
||||
if (userSession.isRememberMe()) {
|
||||
entity.getNotes().put(AuthenticatedClientSessionModel.USER_SESSION_REMEMBER_ME_NOTE, "true");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOffline() {
|
||||
return offline;
|
||||
}
|
||||
};
|
||||
|
||||
update(task);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -90,15 +90,8 @@ public class MergedUpdate<S extends SessionEntity> implements SessionUpdateTask<
|
|||
result.childUpdates.add(child);
|
||||
} else {
|
||||
|
||||
// Merge the operations. REMOVE is special case as other operations are not needed then.
|
||||
CacheOperation mergedOp = result.getOperation().merge(child.getOperation(), session);
|
||||
if (mergedOp == CacheOperation.REMOVE) {
|
||||
result = new MergedUpdate<>(child.getOperation(), child.getCrossDCMessageStatus(sessionWrapper), lifespanMs, maxIdleTimeMs);
|
||||
result.childUpdates.add(child);
|
||||
return result;
|
||||
}
|
||||
|
||||
result.operation = mergedOp;
|
||||
// Merge the operations.
|
||||
result.operation = result.getOperation().merge(child.getOperation(), session);
|
||||
|
||||
// Check if we need to send message to other DCs and how critical it is
|
||||
CrossDCMessageStatus currentDCStatus = result.getCrossDCMessageStatus(sessionWrapper);
|
||||
|
@ -109,6 +102,13 @@ public class MergedUpdate<S extends SessionEntity> implements SessionUpdateTask<
|
|||
result.crossDCMessageStatus = currentDCStatus.merge(childDCStatus);
|
||||
}
|
||||
|
||||
// REMOVE is special case as other operations are not needed then.
|
||||
if (result.operation == CacheOperation.REMOVE) {
|
||||
result = new MergedUpdate<>(result.operation, result.crossDCMessageStatus, lifespanMs, maxIdleTimeMs);
|
||||
result.childUpdates.add(child);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Finally add another update to the result
|
||||
result.childUpdates.add(child);
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ package org.keycloak.models;
|
|||
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.sessions.CommonClientSessionModel;
|
||||
|
||||
/**
|
||||
|
@ -72,4 +73,20 @@ public interface AuthenticatedClientSessionModel extends CommonClientSessionMode
|
|||
void setNote(String name, String value);
|
||||
void removeNote(String name);
|
||||
Map<String, String> getNotes();
|
||||
|
||||
default void restartClientSession() {
|
||||
setAction(null);
|
||||
setRedirectUri(null);
|
||||
setCurrentRefreshToken(null);
|
||||
setCurrentRefreshTokenUseCount(-1);
|
||||
setTimestamp(Time.currentTime());
|
||||
for (String note : getNotes().keySet()) {
|
||||
if (!AuthenticatedClientSessionModel.USER_SESSION_STARTED_AT_NOTE.equals(note)
|
||||
&& !AuthenticatedClientSessionModel.STARTED_AT_NOTE.equals(note)
|
||||
&& !AuthenticatedClientSessionModel.USER_SESSION_REMEMBER_ME_NOTE.equals(note)) {
|
||||
removeNote(note);
|
||||
}
|
||||
}
|
||||
getNotes().put(AuthenticatedClientSessionModel.STARTED_AT_NOTE, String.valueOf(getTimestamp()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -153,7 +153,7 @@ public class TokenManager {
|
|||
if (userSession != null) {
|
||||
|
||||
// Revoke timeouted offline userSession
|
||||
if (!AuthenticationManager.isOfflineSessionValid(realm, userSession)) {
|
||||
if (!AuthenticationManager.isSessionValid(realm, userSession)) {
|
||||
sessionManager.revokeOfflineUserSession(userSession);
|
||||
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Offline session not active", "Offline session not active");
|
||||
}
|
||||
|
@ -198,6 +198,12 @@ public class TokenManager {
|
|||
}
|
||||
}
|
||||
|
||||
if (!AuthenticationManager.isClientSessionValid(realm, client, userSession, clientSession)) {
|
||||
logger.debug("Client session not active");
|
||||
userSession.removeAuthenticatedClientSessions(Collections.singletonList(client.getId()));
|
||||
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Client session not active");
|
||||
}
|
||||
|
||||
if (oldToken.isIssuedBeforeSessionStart(clientSession.getStarted())) {
|
||||
logger.debug("refresh token issued before the client session started");
|
||||
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "refresh token issued before the client session started");
|
||||
|
@ -567,7 +573,10 @@ public class TokenManager {
|
|||
ClientModel client = authSession.getClient();
|
||||
|
||||
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId());
|
||||
if (clientSession == null) {
|
||||
if (clientSession != null && !AuthenticationManager.isClientSessionValid(userSession.getRealm(), client, userSession, clientSession)) {
|
||||
// session exists but not active so re-start it
|
||||
clientSession.restartClientSession();
|
||||
} else if (clientSession == null) {
|
||||
clientSession = session.sessions().createClientSession(userSession.getRealm(), client, userSession);
|
||||
}
|
||||
|
||||
|
|
|
@ -65,6 +65,7 @@ import org.keycloak.models.UserModel;
|
|||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.utils.DefaultRequiredActions;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.SessionExpirationUtils;
|
||||
import org.keycloak.models.utils.SessionTimeoutHelper;
|
||||
import org.keycloak.models.utils.SystemClientUtil;
|
||||
import org.keycloak.protocol.LoginProtocol;
|
||||
|
@ -107,10 +108,10 @@ import java.util.Map;
|
|||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.keycloak.models.light.LightweightUserAdapter.isLightweightUser;
|
||||
import static org.keycloak.models.UserSessionModel.CORRESPONDING_SESSION_ID;
|
||||
import static org.keycloak.protocol.oidc.grants.device.DeviceGrantType.isOAuth2DeviceVerificationFlow;
|
||||
|
||||
|
@ -175,35 +176,33 @@ public class AuthenticationManager {
|
|||
logger.debug("No user session");
|
||||
return false;
|
||||
}
|
||||
int currentTime = Time.currentTime();
|
||||
long currentTime = Time.currentTimeMillis();
|
||||
long lifespan = SessionExpirationUtils.calculateUserSessionMaxLifespanTimestamp(userSession.isOffline(),
|
||||
userSession.isRememberMe(), TimeUnit.SECONDS.toMillis(userSession.getStarted()), realm);
|
||||
long idle = SessionExpirationUtils.calculateUserSessionIdleTimestamp(userSession.isOffline(),
|
||||
userSession.isRememberMe(), TimeUnit.SECONDS.toMillis(userSession.getLastSessionRefresh()), realm);
|
||||
|
||||
// Additional time window is added for the case when session was updated in different DC and the update to current DC was postponed
|
||||
int maxIdle = userSession.isRememberMe() && realm.getSsoSessionIdleTimeoutRememberMe() > 0 ?
|
||||
realm.getSsoSessionIdleTimeoutRememberMe() : realm.getSsoSessionIdleTimeout();
|
||||
int maxLifespan = userSession.isRememberMe() && realm.getSsoSessionMaxLifespanRememberMe() > 0 ?
|
||||
realm.getSsoSessionMaxLifespanRememberMe() : realm.getSsoSessionMaxLifespan();
|
||||
|
||||
boolean sessionIdleOk = maxIdle > currentTime - userSession.getLastSessionRefresh() - SessionTimeoutHelper.IDLE_TIMEOUT_WINDOW_SECONDS;
|
||||
boolean sessionMaxOk = maxLifespan > currentTime - userSession.getStarted();
|
||||
boolean sessionIdleOk = idle > currentTime - TimeUnit.SECONDS.toMillis(SessionTimeoutHelper.IDLE_TIMEOUT_WINDOW_SECONDS);
|
||||
boolean sessionMaxOk = lifespan == -1L || lifespan > currentTime;
|
||||
return sessionIdleOk && sessionMaxOk;
|
||||
}
|
||||
|
||||
public static boolean isOfflineSessionValid(RealmModel realm, UserSessionModel userSession) {
|
||||
if (userSession == null) {
|
||||
logger.debug("No offline user session");
|
||||
public static boolean isClientSessionValid(RealmModel realm, ClientModel client,
|
||||
UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
|
||||
if (userSession == null || clientSession == null) {
|
||||
logger.debug("No user session");
|
||||
return false;
|
||||
}
|
||||
int currentTime = Time.currentTime();
|
||||
// Additional time window is added for the case when session was updated in different DC and the update to current DC was postponed
|
||||
int maxIdle = realm.getOfflineSessionIdleTimeout() + SessionTimeoutHelper.IDLE_TIMEOUT_WINDOW_SECONDS;
|
||||
long currentTime = Time.currentTimeMillis();
|
||||
long lifespan = SessionExpirationUtils.calculateClientSessionMaxLifespanTimestamp(userSession.isOffline(),
|
||||
userSession.isRememberMe(), TimeUnit.SECONDS.toMillis(clientSession.getStarted()),
|
||||
TimeUnit.SECONDS.toMillis(userSession.getStarted()), realm, client);
|
||||
long idle = SessionExpirationUtils.calculateClientSessionIdleTimestamp(userSession.isOffline(),
|
||||
userSession.isRememberMe(), TimeUnit.SECONDS.toMillis(clientSession.getTimestamp()), realm, client);
|
||||
|
||||
// KEYCLOAK-7688 Offline Session Max for Offline Token
|
||||
if (realm.isOfflineSessionMaxLifespanEnabled()) {
|
||||
int max = userSession.getStarted() + realm.getOfflineSessionMaxLifespan();
|
||||
return userSession.getLastSessionRefresh() + maxIdle > currentTime && max > currentTime;
|
||||
} else {
|
||||
return userSession.getLastSessionRefresh() + maxIdle > currentTime;
|
||||
}
|
||||
boolean sessionIdleOk = idle > currentTime - TimeUnit.SECONDS.toMillis(SessionTimeoutHelper.IDLE_TIMEOUT_WINDOW_SECONDS);
|
||||
boolean sessionMaxOk = lifespan == -1L || lifespan > currentTime;
|
||||
return sessionIdleOk && sessionMaxOk;
|
||||
}
|
||||
|
||||
public static boolean expireUserSessionCookie(KeycloakSession session, UserSessionModel userSession, RealmModel realm, UriInfo uriInfo, HttpHeaders headers, ClientConnection connection) {
|
||||
|
@ -1427,7 +1426,7 @@ public class AuthenticationManager {
|
|||
// Check if accessToken was for the offline session.
|
||||
if (!isCookie) {
|
||||
UserSessionModel offlineUserSession = session.sessions().getOfflineUserSession(realm, token.getSessionState());
|
||||
if (isOfflineSessionValid(realm, offlineUserSession)) {
|
||||
if (isSessionValid(realm, offlineUserSession)) {
|
||||
user = offlineUserSession.getUser();
|
||||
ClientModel client = realm.getClientByClientId(token.getIssuedFor());
|
||||
if (!isClientValid(offlineUserSession, client, token)) {
|
||||
|
|
|
@ -54,7 +54,7 @@ public class UserSessionUtil {
|
|||
return userSession;
|
||||
} else {
|
||||
offlineUserSession = new UserSessionCrossDCManager(session).getUserSessionWithClient(realm, token.getSessionId(), true, client.getId());
|
||||
if (AuthenticationManager.isOfflineSessionValid(realm, offlineUserSession)) {
|
||||
if (AuthenticationManager.isSessionValid(realm, offlineUserSession)) {
|
||||
checkTokenIssuedAt(realm, token, offlineUserSession, event, client);
|
||||
event.session(offlineUserSession);
|
||||
return offlineUserSession;
|
||||
|
|
|
@ -918,6 +918,7 @@ public class LoginTest extends AbstractTestRealmKeycloakTest {
|
|||
public void loginRememberMeExpiredIdle() throws Exception {
|
||||
try (Closeable c = new RealmAttributeUpdater(adminClient.realm("test"))
|
||||
.setSsoSessionIdleTimeoutRememberMe(1)
|
||||
.setSsoSessionIdleTimeout(1) // max of both values
|
||||
.setRememberMe(true)
|
||||
.update()) {
|
||||
// login form shown after redirect from app
|
||||
|
|
|
@ -1146,6 +1146,96 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void refreshTokenUserSessionMaxLifespanModifiedAfterTokenRefresh() throws Exception {
|
||||
oauth.scope(OAuth2Constants.OFFLINE_ACCESS);
|
||||
oauth.clientId("offline-client");
|
||||
oauth.redirectUri(offlineClientAppUri);
|
||||
|
||||
RealmResource realmResource = adminClient.realm("test");
|
||||
getTestingClient().testing().setTestingInfinispanTimeService();
|
||||
|
||||
int[] prev = changeOfflineSessionSettings(true, 7200, 7200, 7200, 7200);
|
||||
try {
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
EventRepresentation loginEvent = events.expectLogin().client("offline-client")
|
||||
.detail(Details.REDIRECT_URI, offlineClientAppUri).assertEvent();
|
||||
|
||||
String sessionId = loginEvent.getSessionId();
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "secret1");
|
||||
assertEquals(TokenUtil.TOKEN_TYPE_OFFLINE, oauth.parseRefreshToken(tokenResponse.getRefreshToken()).getType());
|
||||
assertTrue("Invalid ExpiresIn", 0 < tokenResponse.getRefreshExpiresIn() && tokenResponse.getRefreshExpiresIn() <= 7200);
|
||||
String clientSessionId = getOfflineClientSessionUuid(sessionId, loginEvent.getClientId());
|
||||
assertEquals(2, checkIfUserAndClientSessionExist(sessionId, loginEvent.getClientId(), clientSessionId));
|
||||
|
||||
events.poll();
|
||||
|
||||
RealmRepresentation rep = realmResource.toRepresentation();
|
||||
rep.setOfflineSessionMaxLifespan(3600);
|
||||
realmResource.update(rep);
|
||||
|
||||
setTimeOffset(3700);
|
||||
tokenResponse = oauth.doRefreshTokenRequest(tokenResponse.getRefreshToken(), "secret1");
|
||||
assertEquals(400, tokenResponse.getStatusCode());
|
||||
assertNull(tokenResponse.getAccessToken());
|
||||
assertNull(tokenResponse.getRefreshToken());
|
||||
events.expect(EventType.REFRESH_TOKEN).session(sessionId).client("offline-client").error(Errors.INVALID_TOKEN).user((String) null).assertEvent();
|
||||
assertEquals(0, checkIfUserAndClientSessionExist(sessionId, loginEvent.getClientId(), clientSessionId));
|
||||
} finally {
|
||||
changeOfflineSessionSettings(false, prev[0], prev[1], prev[2], prev[3]);
|
||||
getTestingClient().testing().revertTestingInfinispanTimeService();
|
||||
events.clear();
|
||||
resetTimeOffset();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void refreshTokenClientSessionMaxLifespanModifiedAfterTokenRefresh() throws Exception {
|
||||
oauth.scope(OAuth2Constants.OFFLINE_ACCESS);
|
||||
oauth.clientId("offline-client");
|
||||
oauth.redirectUri(offlineClientAppUri);
|
||||
|
||||
RealmResource realmResource = adminClient.realm("test");
|
||||
getTestingClient().testing().setTestingInfinispanTimeService();
|
||||
|
||||
int[] prev = changeOfflineSessionSettings(true, 7200, 7200, 7200, 7200);
|
||||
try {
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
EventRepresentation loginEvent = events.expectLogin().client("offline-client")
|
||||
.detail(Details.REDIRECT_URI, offlineClientAppUri).assertEvent();
|
||||
|
||||
String sessionId = loginEvent.getSessionId();
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "secret1");
|
||||
assertEquals(TokenUtil.TOKEN_TYPE_OFFLINE, oauth.parseRefreshToken(tokenResponse.getRefreshToken()).getType());
|
||||
assertTrue("Invalid ExpiresIn", 0 < tokenResponse.getRefreshExpiresIn() && tokenResponse.getRefreshExpiresIn() <= 7200);
|
||||
String clientSessionId = getOfflineClientSessionUuid(sessionId, loginEvent.getClientId());
|
||||
assertEquals(2, checkIfUserAndClientSessionExist(sessionId, loginEvent.getClientId(), clientSessionId));
|
||||
|
||||
events.poll();
|
||||
|
||||
RealmRepresentation rep = realmResource.toRepresentation();
|
||||
rep.setClientOfflineSessionMaxLifespan(3600);
|
||||
realmResource.update(rep);
|
||||
|
||||
setTimeOffset(3700);
|
||||
tokenResponse = oauth.doRefreshTokenRequest(tokenResponse.getRefreshToken(), "secret1");
|
||||
assertEquals(400, tokenResponse.getStatusCode());
|
||||
assertNull(tokenResponse.getAccessToken());
|
||||
assertNull(tokenResponse.getRefreshToken());
|
||||
events.expect(EventType.REFRESH_TOKEN).client("offline-client").error(Errors.INVALID_TOKEN).session(sessionId).user((String) null).assertEvent();
|
||||
assertEquals(1, checkIfUserAndClientSessionExist(sessionId, loginEvent.getClientId(), clientSessionId));
|
||||
} finally {
|
||||
changeOfflineSessionSettings(false, prev[0], prev[1], prev[2], prev[3]);
|
||||
getTestingClient().testing().revertTestingInfinispanTimeService();
|
||||
events.clear();
|
||||
resetTimeOffset();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShortOfflineSessionMax() throws Exception {
|
||||
int prevOfflineSession[] = null;
|
||||
|
|
|
@ -1163,14 +1163,13 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
|
|||
@Test
|
||||
public void testUserSessionRefreshAndIdleRememberMe() throws Exception {
|
||||
RealmResource testRealm = adminClient.realm("test");
|
||||
RealmRepresentation testRealmRep = testRealm.toRepresentation();
|
||||
Boolean previousRememberMe = testRealmRep.isRememberMe();
|
||||
int originalIdleRememberMe = testRealmRep.getSsoSessionIdleTimeoutRememberMe();
|
||||
|
||||
try {
|
||||
testRealmRep.setRememberMe(true);
|
||||
testRealm.update(testRealmRep);
|
||||
|
||||
try (Closeable realmUpdater = new RealmAttributeUpdater(testRealm)
|
||||
.updateWith(r -> {
|
||||
r.setRememberMe(true);
|
||||
r.setSsoSessionIdleTimeoutRememberMe(500);
|
||||
r.setSsoSessionIdleTimeout(100);
|
||||
}).update()) {
|
||||
oauth.doRememberMeLogin("test-user@localhost", "password");
|
||||
|
||||
EventRepresentation loginEvent = events.expectLogin().assertEvent();
|
||||
|
@ -1185,7 +1184,7 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
|
|||
String refreshId = oauth.parseRefreshToken(tokenResponse.getRefreshToken()).getId();
|
||||
int last = testingClient.testing().getLastSessionRefresh("test", sessionId, false);
|
||||
|
||||
setTimeOffset(2);
|
||||
setTimeOffset(110 + SessionTimeoutHelper.IDLE_TIMEOUT_WINDOW_SECONDS);
|
||||
tokenResponse = oauth.doRefreshTokenRequest(tokenResponse.getRefreshToken(), "password");
|
||||
oauth.verifyToken(tokenResponse.getAccessToken());
|
||||
oauth.parseRefreshToken(tokenResponse.getRefreshToken());
|
||||
|
@ -1194,12 +1193,9 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
|
|||
int next = testingClient.testing().getLastSessionRefresh("test", sessionId, false);
|
||||
Assert.assertNotEquals(last, next);
|
||||
|
||||
testRealmRep.setSsoSessionIdleTimeoutRememberMe(1);
|
||||
testRealm.update(testRealmRep);
|
||||
|
||||
events.clear();
|
||||
// Needs to add some additional time due the tollerance allowed by IDLE_TIMEOUT_WINDOW_SECONDS
|
||||
setTimeOffset(6 + SessionTimeoutHelper.IDLE_TIMEOUT_WINDOW_SECONDS);
|
||||
setTimeOffset(620 + 2 * SessionTimeoutHelper.IDLE_TIMEOUT_WINDOW_SECONDS);
|
||||
tokenResponse = oauth.doRefreshTokenRequest(tokenResponse.getRefreshToken(), "password");
|
||||
|
||||
// test idle remember me timeout
|
||||
|
@ -1211,9 +1207,6 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
|
|||
events.clear();
|
||||
|
||||
} finally {
|
||||
testRealmRep.setSsoSessionIdleTimeoutRememberMe(originalIdleRememberMe);
|
||||
testRealmRep.setRememberMe(previousRememberMe);
|
||||
testRealm.update(testRealmRep);
|
||||
resetTimeOffset();
|
||||
}
|
||||
}
|
||||
|
@ -1399,6 +1392,168 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void refreshTokenUserSessionMaxLifespanModifiedAfterTokenRefresh() throws Exception {
|
||||
RealmResource realmResource = adminClient.realm("test");
|
||||
getTestingClient().testing().setTestingInfinispanTimeService();
|
||||
|
||||
try (Closeable realmUpdater = new RealmAttributeUpdater(realmResource)
|
||||
.updateWith(r -> {
|
||||
r.setSsoSessionMaxLifespan(7200);
|
||||
r.setSsoSessionIdleTimeout(7200);
|
||||
r.setClientSessionMaxLifespan(7200);
|
||||
r.setClientSessionIdleTimeout(7200);
|
||||
}).update()) {
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
EventRepresentation loginEvent = events.expectLogin().assertEvent();
|
||||
|
||||
String sessionId = loginEvent.getSessionId();
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
|
||||
assertTrue("Invalid ExpiresIn", 0 < tokenResponse.getRefreshExpiresIn() && tokenResponse.getRefreshExpiresIn() <= 7200);
|
||||
final String clientSessionId = getClientSessionUuid(sessionId, loginEvent.getClientId());
|
||||
assertEquals(2, checkIfUserAndClientSessionExist(sessionId, loginEvent.getClientId(), clientSessionId));
|
||||
|
||||
events.poll();
|
||||
|
||||
RealmRepresentation rep = realmResource.toRepresentation();
|
||||
rep.setSsoSessionMaxLifespan(3600);
|
||||
realmResource.update(rep);
|
||||
|
||||
setTimeOffset(3700);
|
||||
tokenResponse = oauth.doRefreshTokenRequest(tokenResponse.getRefreshToken(), "password");
|
||||
assertEquals(400, tokenResponse.getStatusCode());
|
||||
assertNull(tokenResponse.getAccessToken());
|
||||
assertNull(tokenResponse.getRefreshToken());
|
||||
events.expect(EventType.REFRESH_TOKEN).error(Errors.INVALID_TOKEN).session(sessionId).user((String) null).assertEvent();
|
||||
assertEquals(0, checkIfUserAndClientSessionExist(sessionId, loginEvent.getClientId(), clientSessionId));
|
||||
} finally {
|
||||
getTestingClient().testing().revertTestingInfinispanTimeService();
|
||||
events.clear();
|
||||
resetTimeOffset();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void refreshTokenClientSessionMaxLifespanModifiedAfterTokenRefresh() throws Exception {
|
||||
RealmResource realmResource = adminClient.realm("test");
|
||||
getTestingClient().testing().setTestingInfinispanTimeService();
|
||||
|
||||
try (Closeable realmUpdater = new RealmAttributeUpdater(realmResource)
|
||||
.updateWith(r -> {
|
||||
r.setSsoSessionMaxLifespan(7200);
|
||||
r.setSsoSessionIdleTimeout(7200);
|
||||
r.setClientSessionMaxLifespan(7200);
|
||||
r.setClientSessionIdleTimeout(7200);
|
||||
}).update()) {
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
EventRepresentation loginEvent = events.expectLogin().assertEvent();
|
||||
|
||||
String sessionId = loginEvent.getSessionId();
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
|
||||
assertTrue("Invalid ExpiresIn", 0 < tokenResponse.getRefreshExpiresIn() && tokenResponse.getRefreshExpiresIn() <= 7200);
|
||||
String clientSessionId = getClientSessionUuid(sessionId, loginEvent.getClientId());
|
||||
assertEquals(2, checkIfUserAndClientSessionExist(sessionId, loginEvent.getClientId(), clientSessionId));
|
||||
|
||||
events.poll();
|
||||
|
||||
RealmRepresentation rep = realmResource.toRepresentation();
|
||||
rep.setClientSessionMaxLifespan(3600);
|
||||
realmResource.update(rep);
|
||||
|
||||
setTimeOffset(3700);
|
||||
tokenResponse = oauth.doRefreshTokenRequest(tokenResponse.getRefreshToken(), "password");
|
||||
assertEquals(400, tokenResponse.getStatusCode());
|
||||
assertNull(tokenResponse.getAccessToken());
|
||||
assertNull(tokenResponse.getRefreshToken());
|
||||
events.expect(EventType.REFRESH_TOKEN).error(Errors.INVALID_TOKEN).session(sessionId).user((String) null).assertEvent();
|
||||
assertEquals(1, checkIfUserAndClientSessionExist(sessionId, loginEvent.getClientId(), clientSessionId));
|
||||
|
||||
setTimeOffset(4200);
|
||||
oauth.doSilentLogin();
|
||||
loginEvent = events.expectLogin().assertEvent();
|
||||
sessionId = loginEvent.getSessionId();
|
||||
code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
tokenResponse = oauth.doAccessTokenRequest(code, "password");
|
||||
assertTrue("Invalid ExpiresIn", 0 < tokenResponse.getRefreshExpiresIn() && tokenResponse.getRefreshExpiresIn() <= 3000);
|
||||
events.expectCodeToToken(loginEvent.getDetails().get(Details.CODE_ID), sessionId).assertEvent();
|
||||
|
||||
clientSessionId = getClientSessionUuid(sessionId, loginEvent.getClientId());
|
||||
assertEquals(2, checkIfUserAndClientSessionExist(sessionId, loginEvent.getClientId(), clientSessionId));
|
||||
|
||||
setTimeOffset(7300);
|
||||
tokenResponse = oauth.doRefreshTokenRequest(tokenResponse.getRefreshToken(), "password");
|
||||
assertEquals(400, tokenResponse.getStatusCode());
|
||||
assertNull(tokenResponse.getAccessToken());
|
||||
assertNull(tokenResponse.getRefreshToken());
|
||||
events.expect(EventType.REFRESH_TOKEN).error(Errors.INVALID_TOKEN).user((String) null).assertEvent();
|
||||
assertEquals(0, checkIfUserAndClientSessionExist(sessionId, loginEvent.getClientId(), clientSessionId));
|
||||
} finally {
|
||||
getTestingClient().testing().revertTestingInfinispanTimeService();
|
||||
events.clear();
|
||||
resetTimeOffset();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void silentLoginClientSessionMaxLifespanModifiedAfterTokenRefresh() throws Exception {
|
||||
RealmResource realmResource = adminClient.realm("test");
|
||||
getTestingClient().testing().setTestingInfinispanTimeService();
|
||||
|
||||
try (Closeable realmUpdater = new RealmAttributeUpdater(realmResource)
|
||||
.updateWith(r -> {
|
||||
r.setSsoSessionMaxLifespan(7200);
|
||||
r.setSsoSessionIdleTimeout(7200);
|
||||
r.setClientSessionMaxLifespan(7200);
|
||||
r.setClientSessionIdleTimeout(7200);
|
||||
}).update()) {
|
||||
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
EventRepresentation loginEvent = events.expectLogin().assertEvent();
|
||||
|
||||
String sessionId = loginEvent.getSessionId();
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
|
||||
assertTrue("Invalid ExpiresIn", 0 < tokenResponse.getRefreshExpiresIn() && tokenResponse.getRefreshExpiresIn() <= 7200);
|
||||
String clientSessionId = getClientSessionUuid(sessionId, loginEvent.getClientId());
|
||||
assertEquals(2, checkIfUserAndClientSessionExist(sessionId, loginEvent.getClientId(), clientSessionId));
|
||||
|
||||
events.poll();
|
||||
|
||||
RealmRepresentation rep = realmResource.toRepresentation();
|
||||
rep.setClientSessionMaxLifespan(3600);
|
||||
realmResource.update(rep);
|
||||
|
||||
setTimeOffset(4200);
|
||||
oauth.doSilentLogin();
|
||||
loginEvent = events.expectLogin().assertEvent();
|
||||
sessionId = loginEvent.getSessionId();
|
||||
code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
tokenResponse = oauth.doAccessTokenRequest(code, "password");
|
||||
assertTrue("Invalid ExpiresIn", 0 < tokenResponse.getRefreshExpiresIn() && tokenResponse.getRefreshExpiresIn() <= 3000);
|
||||
events.expectCodeToToken(loginEvent.getDetails().get(Details.CODE_ID), sessionId).assertEvent();
|
||||
|
||||
clientSessionId = getClientSessionUuid(sessionId, loginEvent.getClientId());
|
||||
assertEquals(2, checkIfUserAndClientSessionExist(sessionId, loginEvent.getClientId(), clientSessionId));
|
||||
|
||||
setTimeOffset(7300);
|
||||
tokenResponse = oauth.doRefreshTokenRequest(tokenResponse.getRefreshToken(), "password");
|
||||
assertEquals(400, tokenResponse.getStatusCode());
|
||||
assertNull(tokenResponse.getAccessToken());
|
||||
assertNull(tokenResponse.getRefreshToken());
|
||||
events.expect(EventType.REFRESH_TOKEN).error(Errors.INVALID_TOKEN).user((String) null).assertEvent();
|
||||
assertEquals(0, checkIfUserAndClientSessionExist(sessionId, loginEvent.getClientId(), clientSessionId));
|
||||
} finally {
|
||||
getTestingClient().testing().revertTestingInfinispanTimeService();
|
||||
events.clear();
|
||||
resetTimeOffset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* KEYCLOAK-1267
|
||||
* @throws Exception
|
||||
|
@ -1407,13 +1562,13 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
|
|||
public void refreshTokenUserSessionMaxLifespanWithRememberMe() throws Exception {
|
||||
|
||||
RealmResource testRealm = adminClient.realm("test");
|
||||
RealmRepresentation testRealmRep = testRealm.toRepresentation();
|
||||
Boolean previousRememberMe = testRealmRep.isRememberMe();
|
||||
int previousSsoMaxLifespanRememberMe = testRealmRep.getSsoSessionMaxLifespanRememberMe();
|
||||
|
||||
try {
|
||||
testRealmRep.setRememberMe(true);
|
||||
testRealm.update(testRealmRep);
|
||||
try (Closeable realmUpdater = new RealmAttributeUpdater(testRealm)
|
||||
.updateWith(r -> {
|
||||
r.setRememberMe(true);
|
||||
r.setSsoSessionMaxLifespanRememberMe(100);
|
||||
r.setSsoSessionMaxLifespan(50);
|
||||
}).update()) {
|
||||
|
||||
oauth.doRememberMeLogin("test-user@localhost", "password");
|
||||
|
||||
|
@ -1428,10 +1583,7 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
|
|||
|
||||
String refreshId = oauth.parseRefreshToken(tokenResponse.getRefreshToken()).getId();
|
||||
|
||||
testRealmRep.setSsoSessionMaxLifespanRememberMe(1);
|
||||
testRealm.update(testRealmRep);
|
||||
|
||||
setTimeOffset(2);
|
||||
setTimeOffset(110);
|
||||
|
||||
tokenResponse = oauth.doRefreshTokenRequest(tokenResponse.getRefreshToken(), "password");
|
||||
|
||||
|
@ -1443,9 +1595,6 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
|
|||
events.clear();
|
||||
|
||||
} finally {
|
||||
testRealmRep.setSsoSessionMaxLifespanRememberMe(previousSsoMaxLifespanRememberMe);
|
||||
testRealmRep.setRememberMe(previousRememberMe);
|
||||
testRealm.update(testRealmRep);
|
||||
resetTimeOffset();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue