From 6d91ab674b2e700b282946c635436a30b0c40445 Mon Sep 17 00:00:00 2001 From: mposolda Date: Thu, 23 Nov 2017 16:38:24 +0100 Subject: [PATCH] KEYCLOAK-5895 CrossDC: NotSerializableException when opening sessions tab in admin console --- .../InfinispanUserSessionProvider.java | 15 +--- .../crossdc/SessionExpirationCrossDCTest.java | 71 +++++++++++++++++++ .../model/UserSessionProviderOfflineTest.java | 30 ++++++-- 3 files changed, 97 insertions(+), 19 deletions(-) diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java index 5015130785..c41b3e2c12 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java @@ -56,6 +56,7 @@ import org.keycloak.models.sessions.infinispan.util.FuturesHelper; import org.keycloak.models.sessions.infinispan.util.InfinispanUtil; import org.keycloak.models.utils.SessionTimeoutHelper; +import java.io.Serializable; import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -294,11 +295,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider { Stream stream = cache.entrySet().stream() .filter(UserSessionPredicate.create(realm.getId()).client(clientUuid)) .map(Mappers.userSessionEntity()) - // Filter out client sessions that have been invalidated in the meantime - .filter(userSession -> { - final UUID clientSessionId = userSession.getAuthenticatedClientSessions().get(clientUuid); - return clientSessionId != null && clientSessionCacheDecorated.containsKey(clientSessionId); - }) .sorted(Comparators.userSessionLastSessionRefresh()); if (firstResult > 0) { @@ -393,19 +389,10 @@ public class InfinispanUserSessionProvider implements UserSessionProvider { Cache> cache = getCache(offline); cache = CacheDecorators.skipCacheLoaders(cache); - Cache> clientSessionCache = getClientSessionCache(offline); - Cache> clientSessionCacheDecorated = CacheDecorators.skipCacheLoaders(clientSessionCache); - final String clientUuid = client.getId(); return cache.entrySet().stream() .filter(UserSessionPredicate.create(realm.getId()).client(clientUuid)) - // Filter out client sessions that have been invalidated in the meantime - .map(Mappers.userSessionEntity()) - .filter(userSession -> { - final UUID clientSessionId = userSession.getAuthenticatedClientSessions().get(clientUuid); - return clientSessionId != null && clientSessionCacheDecorated.containsKey(clientSessionId); - }) .count(); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/SessionExpirationCrossDCTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/SessionExpirationCrossDCTest.java index 4099d276c5..a81f30f6d3 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/SessionExpirationCrossDCTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/SessionExpirationCrossDCTest.java @@ -20,6 +20,8 @@ package org.keycloak.testsuite.crossdc; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; import javax.ws.rs.NotFoundException; @@ -457,6 +459,75 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest { } + @Test + public void testLogoutWithAllStartedNodes( + @JmxInfinispanCacheStatistics(dc=DC.FIRST, managementPortProperty = "cache.server.management.port", cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics, + @JmxInfinispanCacheStatistics(dc=DC.SECOND, managementPortProperty = "cache.server.2.management.port", cacheName=InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics, + @JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception { + + // Start node2 on every DC + startBackendNode(DC.FIRST, 1); + startBackendNode(DC.SECOND, 1); + + // Create sessions. Don't include remote stats. Size is smaller because of distributed cache + List responses = createInitialSessions(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME, + false, cacheDc1Statistics, cacheDc2Statistics, false); + + // Simulate displaying sessions in admin console + Retry.execute(() -> { + assertTestAppActiveSessionsCount(SESSIONS_COUNT); + }, 50, 50); + + + // Logout realm and check sessions not anymore in admin console + getAdminClient().realm(REALM_NAME).logoutAll(); + + Retry.execute(() -> { + assertTestAppActiveSessionsCount(0); + }, 50, 50); + + + // Login again and check sessions back in + responses = createInitialSessions(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME, + false, cacheDc1Statistics, cacheDc2Statistics, false); + + Retry.execute(() -> { + assertTestAppActiveSessionsCount(SESSIONS_COUNT); + }, 50, 50); + + + // Logout user and check sessions not anymore in admin console + ApiUtil.findUserByUsernameId(getAdminClient().realm(REALM_NAME), "login-test").logout(); + + Retry.execute(() -> { + assertTestAppActiveSessionsCount(0); + }, 50, 50); + + // Stop both nodes + stopBackendNode(DC.FIRST, 1); + stopBackendNode(DC.SECOND, 1); + } + + private void assertTestAppActiveSessionsCount(int expectedSessionsCount) { + List> sessions = getAdminClient().realm(REALM_NAME).getClientSessionStats(); + + Optional> optional = sessions.stream().filter((Map map) -> { + return map.get("clientId").equals("test-app"); + }).findFirst(); + + if (expectedSessionsCount == 0) { + // No sessions present. Statistics for the client not included + Assert.assertFalse(optional.isPresent()); + } else { + Map testAppSessions = optional.get(); + Assert.assertEquals(expectedSessionsCount, Integer.parseInt(testAppSessions.get("active"))); + } + + List userSessions = ApiUtil.findClientByClientId(getAdminClient().realm(REALM_NAME), "test-app").getUserSessions(0, 100); + Assert.assertEquals(expectedSessionsCount, userSessions.size()); + } + + // AUTH SESSIONS diff --git a/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java b/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java index 265ecd79da..d5085b0fea 100644 --- a/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java +++ b/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java @@ -140,14 +140,13 @@ public class UserSessionProviderOfflineTest { // Assert userSession revoked testApp = realm.getClientByClientId("test-app"); thirdparty = realm.getClientByClientId("third-party"); - Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, testApp)); + + // Still 2 sessions. The count of sessions by client may not be accurate after revoke due the + // performance optimizations (the "127.0.0.1" session still has another client "thirdparty" in it) + Assert.assertEquals(2, session.sessions().getOfflineSessionsCount(realm, testApp)); Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, thirdparty)); - List testAppSessions = session.sessions().getOfflineUserSessions(realm, testApp, 0, 10); List thirdpartySessions = session.sessions().getOfflineUserSessions(realm, thirdparty, 0, 10); - Assert.assertEquals(1, testAppSessions.size()); - Assert.assertEquals("127.0.0.3", testAppSessions.get(0).getIpAddress()); - Assert.assertEquals("user2", testAppSessions.get(0).getUser().getUsername()); Assert.assertEquals(1, thirdpartySessions.size()); Assert.assertEquals("127.0.0.1", thirdpartySessions.get(0).getIpAddress()); Assert.assertEquals("user1", thirdpartySessions.get(0).getUser().getUsername()); @@ -160,6 +159,27 @@ public class UserSessionProviderOfflineTest { clients = sessionManager.findClientsWithOfflineToken(realm, user2); Assert.assertEquals(1, clients.size()); Assert.assertEquals("test-app", clients.iterator().next().getClientId()); + + // Revoke the second session for user1 too. + sessionManager.revokeOfflineToken(user1, thirdparty); + + resetSession(); + + testApp = realm.getClientByClientId("test-app"); + thirdparty = realm.getClientByClientId("third-party"); + + // Accurate count now. All sessions of user1 cleared + Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, testApp)); + Assert.assertEquals(0, session.sessions().getOfflineSessionsCount(realm, thirdparty)); + + List testAppSessions = session.sessions().getOfflineUserSessions(realm, testApp, 0, 10); + + Assert.assertEquals(1, testAppSessions.size()); + Assert.assertEquals("127.0.0.3", testAppSessions.get(0).getIpAddress()); + Assert.assertEquals("user2", testAppSessions.get(0).getUser().getUsername()); + + clients = sessionManager.findClientsWithOfflineToken(realm, user1); + Assert.assertEquals(0, clients.size()); } @Test