Fix stale client session is present in user session

Closes #17570
This commit is contained in:
Michal Hajas 2023-03-10 13:43:46 +01:00 committed by Hynek Mlnařík
parent 637c47ac0e
commit 2a5b5c4a40
2 changed files with 53 additions and 5 deletions

View file

@ -37,7 +37,6 @@ import org.keycloak.storage.SearchableModelField;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@ -66,7 +65,6 @@ public class HotRodUserSessionTransaction<K> extends ConcurrentHashMapKeycloakTr
}
private MapAuthenticatedClientSessionEntity wrapClientSessionEntityToClientSessionAwareDelegate(MapAuthenticatedClientSessionEntity d) {
if (!clientSessionTransaction.exists(d.getId())) return null;
return new MapAuthenticatedClientSessionEntityDelegate(new HotRodAuthenticatedClientSessionEntityDelegateProvider(d) {
@Override
public MapAuthenticatedClientSessionEntity loadClientSessionFromDatabase() {
@ -79,20 +77,33 @@ public class HotRodUserSessionTransaction<K> extends ConcurrentHashMapKeycloakTr
if (entity == null) return null;
return new MapUserSessionEntityDelegate(new SimpleDelegateProvider<>(entity)) {
private boolean filterAndRemoveNotExpired(MapAuthenticatedClientSessionEntity clientSession) {
if (!clientSessionTransaction.exists(clientSession.getId())) {
// If client session does not exist, remove the reference to it from userSessionEntity loaded in this transaction
entity.removeAuthenticatedClientSession(clientSession.getClientId());
return false;
}
return true;
}
@Override
public Set<MapAuthenticatedClientSessionEntity> getAuthenticatedClientSessions() {
Set<MapAuthenticatedClientSessionEntity> clientSessions = super.getAuthenticatedClientSessions();
return clientSessions == null ? null : clientSessions.stream()
// Find whether client session still exists in Infinispan and if not, remove the reference from user session
.filter(this::filterAndRemoveNotExpired)
.map(HotRodUserSessionTransaction.this::wrapClientSessionEntityToClientSessionAwareDelegate)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
}
@Override
public Optional<MapAuthenticatedClientSessionEntity> getAuthenticatedClientSession(String clientUUID) {
return super.getAuthenticatedClientSession(clientUUID)
.map(HotRodUserSessionTransaction.this::wrapClientSessionEntityToClientSessionAwareDelegate)
.filter(Objects::nonNull);
// Find whether client session still exists in Infinispan and if not, remove the reference from user sessionZ
.filter(this::filterAndRemoveNotExpired)
.map(HotRodUserSessionTransaction.this::wrapClientSessionEntityToClientSessionAwareDelegate);
}
@Override

View file

@ -40,6 +40,10 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.aMapWithSize;
import static org.hamcrest.Matchers.anEmptyMap;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.notNullValue;
import static org.keycloak.protocol.oidc.OIDCConfigAttributes.CLIENT_SESSION_IDLE_TIMEOUT;
import static org.keycloak.protocol.oidc.OIDCConfigAttributes.CLIENT_SESSION_MAX_LIFESPAN;
@RequireProvider(UserSessionProvider.class)
@RequireProvider(value = HotRodConnectionProvider.class, only = DefaultHotRodConnectionProviderFactory.PROVIDER_ID)
@ -114,6 +118,39 @@ public class HotRodUserSessionClientSessionRelationshipTest extends KeycloakMode
});
}
@Test
public void testExpiredClientSessionReferenceIsNotPresentInUserSession() {
// Set lower client session timeouts
withRealm(realmId, (session, realm) -> {
ClientModel client = realm.getClientByClientId(CLIENT0_CLIENT_ID);
client.setAttribute(CLIENT_SESSION_IDLE_TIMEOUT, "60");
client.setAttribute(CLIENT_SESSION_MAX_LIFESPAN, "65");
return null;
});
AtomicReference<String> uSessionId = new AtomicReference<>();
AtomicReference<String> cSessionId = new AtomicReference<>();
prepareSessions(uSessionId, cSessionId);
// Move in time when client session should be expired but user session not
setTimeOffset(70);
// Try to create a new client session for the same user session
withRealm(realmId, (session, realm) -> {
ClientModel client = realm.getClientByClientId(CLIENT0_CLIENT_ID);
UserSessionModel uSession = session.sessions().getUserSession(realm, uSessionId.get());
assertThat(uSession.getAuthenticatedClientSessions(), anEmptyMap());
assertThat(session.sessions().createClientSession(realm, client, uSession), notNullValue());
return null;
});
// Check session does not contain a reference to expired client session
assertCacheContains(remoteCache -> {
HotRodUserSessionEntity hotRodUserSessionEntity = remoteCache.get(uSessionId.get());
assertThat(hotRodUserSessionEntity.authenticatedClientSessions, hasSize(1));
});
}
private void assertCacheContains(Consumer<RemoteCache<String, HotRodUserSessionEntity>> checker) {
withRealm(realmId, (session, realm) -> {
HotRodConnectionProvider provider = session.getProvider(HotRodConnectionProvider.class);