Remove offline sessions when deleting a realm

This commit is contained in:
Martin Kanis 2022-07-14 21:43:01 +02:00 committed by Hynek Mlnařík
parent b563028f42
commit c8a6846ee0
3 changed files with 54 additions and 17 deletions

View file

@ -34,7 +34,6 @@ import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator;
import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; import org.keycloak.models.map.storage.criteria.DefaultModelCriteria;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -351,8 +350,6 @@ public class MapUserSessionProvider implements UserSessionProvider {
@Override @Override
public void onRealmRemoved(RealmModel realm) { public void onRealmRemoved(RealmModel realm) {
LOG.tracef("onRealmRemoved(%s)%s", realm, getShortStackTrace()); LOG.tracef("onRealmRemoved(%s)%s", realm, getShortStackTrace());
removeUserSessions(realm);
} }
@Override @Override
@ -526,6 +523,19 @@ public class MapUserSessionProvider implements UserSessionProvider {
} }
/**
* Removes all online and offline user sessions that belong to the provided {@link RealmModel}.
* @param realm
*/
protected void removeAllUserSessions(RealmModel realm) {
DefaultModelCriteria<UserSessionModel> mcb = criteria();
mcb = mcb.compare(UserSessionModel.SearchableFields.REALM_ID, Operator.EQ, realm.getId());
LOG.tracef("removeAllUserSessions(%s)%s", realm, getShortStackTrace());
userSessionTx.delete(withCriteria(mcb));
}
private Stream<MapUserSessionEntity> getOfflineUserSessionEntityStream(RealmModel realm, String userSessionId) { private Stream<MapUserSessionEntity> getOfflineUserSessionEntityStream(RealmModel realm, String userSessionId) {
if (userSessionId == null) { if (userSessionId == null) {
return Stream.empty(); return Stream.empty();

View file

@ -16,30 +16,17 @@
*/ */
package org.keycloak.models.map.userSession; package org.keycloak.models.map.userSession;
import org.keycloak.Config.Scope;
import org.keycloak.common.Profile;
import org.keycloak.component.AmphibianProviderFactory;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.UserSessionProviderFactory; import org.keycloak.models.UserSessionProviderFactory;
import org.keycloak.models.map.client.MapClientProvider;
import org.keycloak.models.map.common.AbstractMapProviderFactory; import org.keycloak.models.map.common.AbstractMapProviderFactory;
import org.keycloak.models.map.storage.MapStorage;
import org.keycloak.models.map.storage.MapStorageProvider;
import org.keycloak.models.map.storage.MapStorageProviderFactory;
import org.keycloak.models.map.storage.MapStorageSpi;
import org.keycloak.provider.EnvironmentDependentProviderFactory;
import org.keycloak.provider.InvalidationHandler; import org.keycloak.provider.InvalidationHandler;
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.REALM_BEFORE_REMOVE;
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.USER_BEFORE_REMOVE; import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.USER_BEFORE_REMOVE;
import static org.keycloak.models.map.common.AbstractMapProviderFactory.uniqueCounter;
import static org.keycloak.models.utils.KeycloakModelUtils.getComponentFactory;
/** /**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a> * @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
@ -64,6 +51,8 @@ public class MapUserSessionProviderFactory extends AbstractMapProviderFactory<Ma
public void invalidate(KeycloakSession session, InvalidableObjectType type, Object... params) { public void invalidate(KeycloakSession session, InvalidableObjectType type, Object... params) {
if (type == USER_BEFORE_REMOVE) { if (type == USER_BEFORE_REMOVE) {
create(session).removeUserSessions((RealmModel) params[0], (UserModel) params[1]); create(session).removeUserSessions((RealmModel) params[0], (UserModel) params[1]);
} else if (type == REALM_BEFORE_REMOVE) {
create(session).removeAllUserSessions((RealmModel) params[0]);
} }
} }

View file

@ -16,6 +16,7 @@
*/ */
package org.keycloak.testsuite.model.session; package org.keycloak.testsuite.model.session;
import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.commons.CacheException; import org.infinispan.commons.CacheException;
import org.keycloak.models.AuthenticatedClientSessionModel; import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
@ -27,6 +28,10 @@ import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider; import org.keycloak.models.UserProvider;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.models.UserSessionProvider; import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.map.storage.ModelEntityUtil;
import org.keycloak.models.map.storage.hotRod.connections.DefaultHotRodConnectionProviderFactory;
import org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionProvider;
import org.keycloak.models.map.storage.hotRod.userSession.HotRodUserSessionEntity;
import org.keycloak.models.session.UserSessionPersisterProvider; import org.keycloak.models.session.UserSessionPersisterProvider;
import org.keycloak.models.sessions.infinispan.InfinispanUserSessionProvider; import org.keycloak.models.sessions.infinispan.InfinispanUserSessionProvider;
import org.keycloak.models.sessions.infinispan.InfinispanUserSessionProviderFactory; import org.keycloak.models.sessions.infinispan.InfinispanUserSessionProviderFactory;
@ -123,6 +128,39 @@ public class OfflineSessionPersistenceTest extends KeycloakModelTest {
} }
} }
@Test
@RequireProvider(value = HotRodConnectionProvider.class, only = DefaultHotRodConnectionProviderFactory.PROVIDER_ID)
public void testOfflineSessionsRemovedAfterDeleteRealm() {
String realmId2 = inComittedTransaction(session -> { return prepareRealm(session, "realm2").getId(); });
List<String> userIds2 = withRealm(realmId2, (session, realm) -> IntStream.range(0, USER_COUNT)
.mapToObj(i -> session.users().addUser(realm, "user2-" + i))
.map(UserModel::getId)
.collect(Collectors.toList())
);
try {
List<String> offlineSessionIds2 = createOfflineSessions(realmId2, userIds2);
assertOfflineSessionsExist(realmId2, offlineSessionIds2);
// Simulate server restart
reinitializeKeycloakSessionFactory();
assertOfflineSessionsExist(realmId2, offlineSessionIds2);
inComittedTransaction(session -> {
session.realms().removeRealm(realmId2);
});
inComittedTransaction(session -> {
HotRodConnectionProvider provider = session.getProvider(HotRodConnectionProvider.class);
RemoteCache<String, HotRodUserSessionEntity> remoteCache = provider.getRemoteCache(ModelEntityUtil.getModelName(UserSessionModel.class));
assertThat(remoteCache, Matchers.anEmptyMap());
});
} finally {
withRealm(realmId2, (session, realm) -> realm == null ? false : new RealmManager(session).removeRealm(realm));
}
}
@Test @Test
public void testPersistenceSingleNode() { public void testPersistenceSingleNode() {
List<String> offlineSessionIds = createOfflineSessions(realmId, userIds); List<String> offlineSessionIds = createOfflineSessions(realmId, userIds);