Add cascade removal of client session on user session removal for HotRod

Closes #12096
This commit is contained in:
Michal Hajas 2022-05-25 10:43:47 +02:00 committed by Hynek Mlnařík
parent 1a98765fb7
commit 9b36ea0269
6 changed files with 177 additions and 78 deletions

View file

@ -56,10 +56,10 @@ public class HotRodMapStorage<K, E extends AbstractHotRodEntity, V extends HotRo
private static final Logger LOG = Logger.getLogger(HotRodMapStorage.class);
private final RemoteCache<K, E> remoteCache;
private final StringKeyConverter<K> keyConverter;
private final HotRodEntityDescriptor<E, V> storedEntityDescriptor;
protected final StringKeyConverter<K> keyConverter;
protected final HotRodEntityDescriptor<E, V> storedEntityDescriptor;
private final Function<E, V> delegateProducer;
private final DeepCloner cloner;
protected final DeepCloner cloner;
public HotRodMapStorage(RemoteCache<K, E> remoteCache, StringKeyConverter<K> keyConverter, HotRodEntityDescriptor<E, V> storedEntityDescriptor, DeepCloner cloner) {
this.remoteCache = remoteCache;
@ -191,7 +191,13 @@ public class HotRodMapStorage<K, E extends AbstractHotRodEntity, V extends HotRo
@Override
public MapKeycloakTransaction<V, M> createTransaction(KeycloakSession session) {
Map<SearchableModelField<? super M>, MapModelCriteriaBuilder.UpdatePredicatesFunc<K, V, M>> fieldPredicates = MapFieldPredicates.getPredicates((Class<M>) storedEntityDescriptor.getModelTypeClass());
return new ConcurrentHashMapKeycloakTransaction<>(this, keyConverter, cloner, fieldPredicates);
MapKeycloakTransaction<V, M> sessionTransaction = session.getAttribute("map-transaction-" + hashCode(), MapKeycloakTransaction.class);
if (sessionTransaction == null) {
Map<SearchableModelField<? super M>, MapModelCriteriaBuilder.UpdatePredicatesFunc<K, V, M>> fieldPredicates = MapFieldPredicates.getPredicates((Class<M>) storedEntityDescriptor.getModelTypeClass());
sessionTransaction = new ConcurrentHashMapKeycloakTransaction<>(this, keyConverter, cloner, fieldPredicates);
session.setAttribute("map-transaction-" + hashCode(), sessionTransaction);
}
return sessionTransaction;
}
}

View file

@ -17,39 +17,25 @@
package org.keycloak.models.map.storage.hotRod;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDelegate;
import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDescriptor;
import org.keycloak.models.map.common.StringKeyConverter;
import org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionProvider;
import org.keycloak.models.map.storage.MapStorage;
import org.keycloak.models.map.storage.MapStorageProvider;
import org.keycloak.models.map.storage.MapStorageProviderFactory;
public class HotRodMapStorageProvider implements MapStorageProvider {
private final KeycloakSession session;
private final HotRodMapStorageProviderFactory factory;
private final HotRodConnectionProvider connectionProvider;
private final DeepCloner cloner;
public HotRodMapStorageProvider(HotRodMapStorageProviderFactory factory, HotRodConnectionProvider connectionProvider, DeepCloner cloner) {
public HotRodMapStorageProvider(KeycloakSession session, HotRodMapStorageProviderFactory factory) {
this.session = session;
this.factory = factory;
this.connectionProvider = connectionProvider;
this.cloner = cloner;
}
@Override
public <V extends AbstractEntity, M> MapStorage<V, M> getStorage(Class<M> modelType, MapStorageProviderFactory.Flag... flags) {
HotRodMapStorage storage = getHotRodStorage(modelType, flags);
return storage;
}
@SuppressWarnings("unchecked")
public <E extends AbstractHotRodEntity, V extends HotRodEntityDelegate<E> & AbstractEntity, M> HotRodMapStorage<String, E, V, M> getHotRodStorage(Class<M> modelType, MapStorageProviderFactory.Flag... flags) {
HotRodEntityDescriptor<E, V> entityDescriptor = (HotRodEntityDescriptor<E, V>) factory.getEntityDescriptor(modelType);
return new HotRodMapStorage<>(connectionProvider.getRemoteCache(entityDescriptor.getCacheName()), StringKeyConverter.StringKey.INSTANCE, entityDescriptor, cloner);
return (MapStorage<V, M>) factory.getHotRodStorage(session, modelType, flags);
}
@Override

View file

@ -45,6 +45,8 @@ import org.keycloak.models.map.authorization.entity.MapScopeEntity;
import org.keycloak.models.map.authSession.MapAuthenticationSessionEntity;
import org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntity;
import org.keycloak.models.map.clientscope.MapClientScopeEntity;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.StringKeyConverter;
import org.keycloak.models.map.group.MapGroupEntity;
import org.keycloak.models.map.loginFailure.MapUserLoginFailureEntity;
import org.keycloak.models.map.realm.MapRealmEntity;
@ -73,6 +75,8 @@ import org.keycloak.models.map.storage.hotRod.authorization.HotRodResourceServer
import org.keycloak.models.map.storage.hotRod.authorization.HotRodResourceServerEntityDelegate;
import org.keycloak.models.map.storage.hotRod.authorization.HotRodScopeEntity;
import org.keycloak.models.map.storage.hotRod.authorization.HotRodScopeEntityDelegate;
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDelegate;
import org.keycloak.models.map.storage.hotRod.loginFailure.HotRodUserLoginFailureEntity;
import org.keycloak.models.map.storage.hotRod.loginFailure.HotRodUserLoginFailureEntityDelegate;
import org.keycloak.models.map.storage.hotRod.role.HotRodRoleEntity;
@ -113,6 +117,7 @@ import org.keycloak.models.map.storage.hotRod.userSession.HotRodAuthenticatedCli
import org.keycloak.models.map.storage.hotRod.userSession.HotRodAuthenticatedClientSessionEntityDelegate;
import org.keycloak.models.map.storage.hotRod.userSession.HotRodUserSessionEntity;
import org.keycloak.models.map.storage.hotRod.userSession.HotRodUserSessionEntityDelegate;
import org.keycloak.models.map.storage.hotRod.userSession.HotRodUserSessionMapStorage;
import org.keycloak.models.map.user.MapUserConsentEntity;
import org.keycloak.models.map.user.MapUserCredentialEntity;
import org.keycloak.models.map.user.MapUserEntity;
@ -124,11 +129,14 @@ import org.keycloak.sessions.RootAuthenticationSessionModel;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class HotRodMapStorageProviderFactory implements AmphibianProviderFactory<MapStorageProvider>, MapStorageProviderFactory, EnvironmentDependentProviderFactory {
public static final String PROVIDER_ID = "hotrod";
private static final Logger LOG = Logger.getLogger(HotRodMapStorageProviderFactory.class);
private final Map<Class<?>, HotRodMapStorage> storages = new ConcurrentHashMap<>();
private final static DeepCloner CLONER = new DeepCloner.Builder()
.constructor(MapRootAuthenticationSessionEntity.class, HotRodRootAuthenticationSessionEntityDelegate::new)
@ -289,19 +297,30 @@ public class HotRodMapStorageProviderFactory implements AmphibianProviderFactory
@Override
public MapStorageProvider create(KeycloakSession session) {
HotRodConnectionProvider cacheProvider = session.getProvider(HotRodConnectionProvider.class);
if (cacheProvider == null) {
throw new IllegalStateException("Cannot find HotRodConnectionProvider interface implementation");
}
return new HotRodMapStorageProvider(this, cacheProvider, CLONER);
return new HotRodMapStorageProvider(session, this);
}
public HotRodEntityDescriptor<?, ?> getEntityDescriptor(Class<?> c) {
return ENTITY_DESCRIPTOR_MAP.get(c);
}
public <E extends AbstractHotRodEntity, V extends HotRodEntityDelegate<E> & AbstractEntity, M> HotRodMapStorage<String, E, V, M> getHotRodStorage(KeycloakSession session, Class<M> modelType, MapStorageProviderFactory.Flag... flags) {
if (modelType == UserSessionModel.class) getHotRodStorage(session, AuthenticatedClientSessionModel.class, flags);
return storages.computeIfAbsent(modelType, c -> createHotRodStorage(session, modelType, flags));
}
private <E extends AbstractHotRodEntity, V extends HotRodEntityDelegate<E> & AbstractEntity, M> HotRodMapStorage<String, E, V, M> createHotRodStorage(KeycloakSession session, Class<M> modelType, MapStorageProviderFactory.Flag... flags) {
HotRodConnectionProvider connectionProvider = session.getProvider(HotRodConnectionProvider.class);
HotRodEntityDescriptor<E, V> entityDescriptor = (HotRodEntityDescriptor<E, V>) getEntityDescriptor(modelType);
if (modelType == UserSessionModel.class) {
HotRodMapStorage clientSessionStore = getHotRodStorage(session, AuthenticatedClientSessionModel.class);
return new HotRodUserSessionMapStorage(clientSessionStore, connectionProvider.getRemoteCache(entityDescriptor.getCacheName()), StringKeyConverter.StringKey.INSTANCE, entityDescriptor, CLONER);
}
return new HotRodMapStorage<>(connectionProvider.getRemoteCache(entityDescriptor.getCacheName()), StringKeyConverter.StringKey.INSTANCE, entityDescriptor, CLONER);
}
@Override
public void init(Config.Scope config) {

View file

@ -0,0 +1,62 @@
/*
* Copyright 2022 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.map.storage.hotRod.userSession;
import org.infinispan.client.hotrod.RemoteCache;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.common.StringKeyConverter;
import org.keycloak.models.map.storage.MapKeycloakTransaction;
import org.keycloak.models.map.storage.chm.MapFieldPredicates;
import org.keycloak.models.map.storage.chm.MapModelCriteriaBuilder;
import org.keycloak.models.map.storage.chm.UserSessionCascadeRemovalTransaction;
import org.keycloak.models.map.storage.hotRod.HotRodMapStorage;
import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDescriptor;
import org.keycloak.storage.SearchableModelField;
import java.util.Map;
public class HotRodUserSessionMapStorage<K> extends HotRodMapStorage<K, HotRodUserSessionEntity, HotRodUserSessionEntityDelegate, UserSessionModel> {
private final HotRodMapStorage<K, HotRodAuthenticatedClientSessionEntity, HotRodAuthenticatedClientSessionEntityDelegate, AuthenticatedClientSessionModel> hotRodClientSessionStore;
public HotRodUserSessionMapStorage(HotRodMapStorage<K, HotRodAuthenticatedClientSessionEntity, HotRodAuthenticatedClientSessionEntityDelegate, AuthenticatedClientSessionModel> hotRodClientSessionStore,
RemoteCache<K, HotRodUserSessionEntity> remoteCache,
StringKeyConverter<K> keyConverter,
HotRodEntityDescriptor<HotRodUserSessionEntity, HotRodUserSessionEntityDelegate> storedEntityDescriptor,
DeepCloner cloner
) {
super(remoteCache, keyConverter, storedEntityDescriptor, cloner);
this.hotRodClientSessionStore = hotRodClientSessionStore;
}
@Override
public MapKeycloakTransaction<HotRodUserSessionEntityDelegate, UserSessionModel> createTransaction(KeycloakSession session) {
MapKeycloakTransaction<HotRodUserSessionEntityDelegate, UserSessionModel> sessionTransaction = session.getAttribute("map-transaction-" + hashCode(), MapKeycloakTransaction.class);
if (sessionTransaction == null) {
Map<SearchableModelField<? super UserSessionModel>, MapModelCriteriaBuilder.UpdatePredicatesFunc<K, HotRodUserSessionEntityDelegate, UserSessionModel>> fieldPredicates = MapFieldPredicates.getPredicates((Class<UserSessionModel>) storedEntityDescriptor.getModelTypeClass());
sessionTransaction = new UserSessionCascadeRemovalTransaction<>(this, hotRodClientSessionStore.createTransaction(session), keyConverter, cloner, fieldPredicates);
session.setAttribute("map-transaction-" + hashCode(), sessionTransaction);
}
return sessionTransaction;
}
}

View file

@ -0,0 +1,72 @@
/*
* Copyright 2022 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.map.storage.chm;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.common.StringKeyConverter;
import org.keycloak.models.map.storage.MapKeycloakTransaction;
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
import org.keycloak.models.map.storage.QueryParameters;
import org.keycloak.models.map.storage.criteria.DefaultModelCriteria;
import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity;
import org.keycloak.models.map.userSession.MapUserSessionEntity;
import org.keycloak.storage.SearchableModelField;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static org.keycloak.models.map.storage.QueryParameters.withCriteria;
public class UserSessionCascadeRemovalTransaction<K, V extends MapUserSessionEntity> extends ConcurrentHashMapKeycloakTransaction<K, V, UserSessionModel> {
private final MapKeycloakTransaction<? extends MapAuthenticatedClientSessionEntity, AuthenticatedClientSessionModel> clientSessionTr;
public UserSessionCascadeRemovalTransaction(ConcurrentHashMapCrudOperations<V, UserSessionModel> userSessionConcurrentHashMapStorage,
MapKeycloakTransaction<? extends MapAuthenticatedClientSessionEntity, AuthenticatedClientSessionModel> clientSessionTr,
StringKeyConverter<K> keyConverter,
DeepCloner cloner,
Map<SearchableModelField<? super UserSessionModel>,
MapModelCriteriaBuilder.UpdatePredicatesFunc<K,
V,
UserSessionModel>> fieldPredicates) {
super(userSessionConcurrentHashMapStorage, keyConverter, cloner, fieldPredicates);
this.clientSessionTr = clientSessionTr;
}
@Override
public long delete(QueryParameters<UserSessionModel> queryParameters) {
Set<String> ids = read(queryParameters).map(AbstractEntity::getId).collect(Collectors.toSet());
DefaultModelCriteria<AuthenticatedClientSessionModel> csMcb = DefaultModelCriteria.<AuthenticatedClientSessionModel>criteria()
.compare(AuthenticatedClientSessionModel.SearchableFields.USER_SESSION_ID, ModelCriteriaBuilder.Operator.IN, ids);
clientSessionTr.delete(withCriteria(csMcb));
return super.delete(queryParameters);
}
@Override
public boolean delete(String key) {
DefaultModelCriteria<AuthenticatedClientSessionModel> csMcb = DefaultModelCriteria.<AuthenticatedClientSessionModel>criteria()
.compare(AuthenticatedClientSessionModel.SearchableFields.USER_SESSION_ID, ModelCriteriaBuilder.Operator.EQ, key);
clientSessionTr.delete(withCriteria(csMcb));
return super.delete(key);
}
}

View file

@ -20,22 +20,10 @@ import org.keycloak.models.map.common.StringKeyConverter;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.storage.MapKeycloakTransaction;
import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator;
import org.keycloak.models.map.storage.QueryParameters;
import org.keycloak.models.map.storage.chm.MapModelCriteriaBuilder.UpdatePredicatesFunc;
import org.keycloak.models.map.storage.criteria.DefaultModelCriteria;
import org.keycloak.models.map.userSession.MapAuthenticatedClientSessionEntity;
import org.keycloak.models.map.userSession.MapUserSessionEntity;
import org.keycloak.storage.SearchableModelField;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static org.keycloak.models.map.storage.QueryParameters.withCriteria;
/**
* User session storage with a naive implementation of referential integrity in client to user session relation, restricted to
@ -47,40 +35,6 @@ public class UserSessionConcurrentHashMapStorage<K> extends ConcurrentHashMapSto
private final ConcurrentHashMapStorage<K, MapAuthenticatedClientSessionEntity, AuthenticatedClientSessionModel> clientSessionStore;
private class Transaction extends ConcurrentHashMapKeycloakTransaction<K, MapUserSessionEntity, UserSessionModel> {
private final MapKeycloakTransaction<MapAuthenticatedClientSessionEntity, AuthenticatedClientSessionModel> clientSessionTr;
public Transaction(MapKeycloakTransaction<MapAuthenticatedClientSessionEntity, AuthenticatedClientSessionModel> clientSessionTr,
StringKeyConverter<K> keyConverter,
DeepCloner cloner,
Map<SearchableModelField<? super UserSessionModel>,
UpdatePredicatesFunc<K,
MapUserSessionEntity,
UserSessionModel>> fieldPredicates) {
super(UserSessionConcurrentHashMapStorage.this, keyConverter, cloner, fieldPredicates);
this.clientSessionTr = clientSessionTr;
}
@Override
public long delete(QueryParameters<UserSessionModel> queryParameters) {
Set<String> ids = read(queryParameters).map(AbstractEntity::getId).collect(Collectors.toSet());
DefaultModelCriteria<AuthenticatedClientSessionModel> csMcb = DefaultModelCriteria.<AuthenticatedClientSessionModel>criteria()
.compare(AuthenticatedClientSessionModel.SearchableFields.USER_SESSION_ID, Operator.IN, ids);
clientSessionTr.delete(withCriteria(csMcb));
return super.delete(queryParameters);
}
@Override
public boolean delete(String key) {
DefaultModelCriteria<AuthenticatedClientSessionModel> csMcb = DefaultModelCriteria.<AuthenticatedClientSessionModel>criteria()
.compare(AuthenticatedClientSessionModel.SearchableFields.USER_SESSION_ID, Operator.EQ, key);
clientSessionTr.delete(withCriteria(csMcb));
return super.delete(key);
}
}
@SuppressWarnings("unchecked")
public UserSessionConcurrentHashMapStorage(ConcurrentHashMapStorage<K, MapAuthenticatedClientSessionEntity, AuthenticatedClientSessionModel> clientSessionStore,
StringKeyConverter<K> keyConverter, DeepCloner cloner) {
@ -94,7 +48,7 @@ public class UserSessionConcurrentHashMapStorage<K> extends ConcurrentHashMapSto
MapKeycloakTransaction<MapUserSessionEntity, UserSessionModel> sessionTransaction = session.getAttribute("map-transaction-" + hashCode(), MapKeycloakTransaction.class);
if (sessionTransaction == null) {
sessionTransaction = new Transaction(clientSessionStore.createTransaction(session), clientSessionStore.getKeyConverter(), cloner, fieldPredicates);
sessionTransaction = new UserSessionCascadeRemovalTransaction<>(this, clientSessionStore.createTransaction(session), clientSessionStore.getKeyConverter(), cloner, fieldPredicates);
session.setAttribute("map-transaction-" + hashCode(), sessionTransaction);
}
return sessionTransaction;