Deletion of all objects when realm is being removed

Closes #11076
This commit is contained in:
vramik 2022-04-01 19:13:09 +02:00 committed by Hynek Mlnařík
parent 29233f33c8
commit 2ecf250e37
38 changed files with 405 additions and 318 deletions

View file

@ -477,16 +477,16 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
cluster.registerListener(REALM_CLEAR_CACHE_EVENTS, (ClusterEvent event) -> {
if (event instanceof ClearCacheEvent) {
sessionFactory.invalidate(ObjectType._ALL_);
sessionFactory.invalidate(null, ObjectType._ALL_);
}
});
cluster.registerListener(REALM_INVALIDATION_EVENTS, (ClusterEvent event) -> {
if (event instanceof RealmUpdatedEvent) {
RealmUpdatedEvent rr = (RealmUpdatedEvent) event;
sessionFactory.invalidate(ObjectType.REALM, rr.getId());
sessionFactory.invalidate(null, ObjectType.REALM, rr.getId());
} else if (event instanceof RealmRemovedEvent) {
RealmRemovedEvent rr = (RealmRemovedEvent) event;
sessionFactory.invalidate(ObjectType.REALM, rr.getId());
sessionFactory.invalidate(null, ObjectType.REALM, rr.getId());
}
});
}

View file

@ -18,7 +18,6 @@ package org.keycloak.models.map.authSession;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.common.AbstractMapProviderFactory;
import org.keycloak.sessions.AuthenticationSessionProvider;
import org.keycloak.sessions.AuthenticationSessionProviderFactory;
import org.keycloak.sessions.RootAuthenticationSessionModel;
@ -26,15 +25,15 @@ import org.keycloak.sessions.RootAuthenticationSessionModel;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public class MapRootAuthenticationSessionProviderFactory extends AbstractMapProviderFactory<AuthenticationSessionProvider, MapRootAuthenticationSessionEntity, RootAuthenticationSessionModel>
implements AuthenticationSessionProviderFactory {
public class MapRootAuthenticationSessionProviderFactory extends AbstractMapProviderFactory<MapRootAuthenticationSessionProvider, MapRootAuthenticationSessionEntity, RootAuthenticationSessionModel>
implements AuthenticationSessionProviderFactory<MapRootAuthenticationSessionProvider> {
public MapRootAuthenticationSessionProviderFactory() {
super(RootAuthenticationSessionModel.class);
super(RootAuthenticationSessionModel.class, MapRootAuthenticationSessionProvider.class);
}
@Override
public AuthenticationSessionProvider create(KeycloakSession session) {
public MapRootAuthenticationSessionProvider createNew(KeycloakSession session) {
return new MapRootAuthenticationSessionProvider(session, getStorage(session));
}

View file

@ -17,21 +17,8 @@
package org.keycloak.models.map.client;
import org.jboss.logging.Logger;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientModel.ClientUpdatedEvent;
import org.keycloak.models.ClientModel.SearchableFields;
import org.keycloak.models.ClientProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.map.common.TimeAdapter;
import org.keycloak.models.map.storage.MapKeycloakTransaction;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@ -41,18 +28,30 @@ import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.jboss.logging.Logger;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientModel.ClientUpdatedEvent;
import org.keycloak.models.ClientModel.SearchableFields;
import org.keycloak.models.ClientProvider;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.map.common.TimeAdapter;
import org.keycloak.models.map.storage.MapKeycloakTransaction;
import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator;
import org.keycloak.models.map.storage.MapStorage;
import org.keycloak.models.map.storage.criteria.DefaultModelCriteria;
import static org.keycloak.common.util.StackUtil.getShortStackTrace;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator;
import org.keycloak.models.map.storage.criteria.DefaultModelCriteria;
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.CLIENT_AFTER_REMOVE;
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.CLIENT_BEFORE_REMOVE;
import static org.keycloak.models.map.storage.QueryParameters.Order.ASCENDING;
import static org.keycloak.models.map.storage.QueryParameters.withCriteria;
import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria;
import java.util.HashSet;
public class MapClientProvider implements ClientProvider {
private static final Logger LOG = Logger.getLogger(MapClientProvider.class);
@ -196,33 +195,19 @@ public class MapClientProvider implements ClientProvider {
@Override
public boolean removeClient(RealmModel realm, String id) {
if (id == null) {
return false;
}
if (id == null) return false;
LOG.tracef("removeClient(%s, %s)%s", realm, id, getShortStackTrace());
// TODO: Sending an event (and client role removal) should be extracted to store layer
final ClientModel client = getClientById(realm, id);
if (client == null) return false;
session.users().preRemove(realm, client);
session.roles().removeRoles(client);
session.getKeycloakSessionFactory().publish(new ClientModel.ClientRemovedEvent() {
@Override
public ClientModel getClient() {
return client;
}
@Override
public KeycloakSession getKeycloakSession() {
return session;
}
});
// TODO: ^^^^^^^ Up to here
session.invalidate(CLIENT_BEFORE_REMOVE, realm, client);
tx.delete(id);
session.invalidate(CLIENT_AFTER_REMOVE, client);
return true;
}
@ -374,6 +359,14 @@ public class MapClientProvider implements ClientProvider {
}
}
public void preRemove(RealmModel realm) {
LOG.tracef("preRemove(%s)%s", realm, getShortStackTrace());
DefaultModelCriteria<ClientModel> mcb = criteria();
mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId());
tx.delete(withCriteria(mcb));
}
@Override
public void close() {

View file

@ -16,72 +16,54 @@
*/
package org.keycloak.models.map.client;
import org.keycloak.models.ClientModel;
import org.keycloak.models.map.common.AbstractMapProviderFactory;
import org.keycloak.models.ClientProvider;
import org.keycloak.models.ClientProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleContainerModel.RoleRemovedEvent;
import org.keycloak.models.RoleModel;
import org.keycloak.provider.ProviderEvent;
import org.keycloak.provider.ProviderEventListener;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.keycloak.models.ClientModel;
import org.keycloak.models.map.common.AbstractMapProviderFactory;
import org.keycloak.models.ClientProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.provider.InvalidationHandler;
import org.keycloak.provider.InvalidationHandler.InvalidableObjectType;
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.CLIENT_AFTER_REMOVE;
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.REALM_BEFORE_REMOVE;
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.ROLE_BEFORE_REMOVE;
/**
*
* @author hmlnarik
*/
public class MapClientProviderFactory extends AbstractMapProviderFactory<ClientProvider, MapClientEntity, ClientModel> implements ClientProviderFactory, ProviderEventListener {
public class MapClientProviderFactory extends AbstractMapProviderFactory<MapClientProvider, MapClientEntity, ClientModel> implements ClientProviderFactory<MapClientProvider>, InvalidationHandler {
private final ConcurrentHashMap<String, ConcurrentMap<String, Long>> REGISTERED_NODES_STORE = new ConcurrentHashMap<>();
private Runnable onClose;
public MapClientProviderFactory() {
super(ClientModel.class);
super(ClientModel.class, MapClientProvider.class);
}
@Override
public void postInit(KeycloakSessionFactory factory) {
factory.register(this);
onClose = () -> factory.unregister(this);
}
@Override
public MapClientProvider create(KeycloakSession session) {
public MapClientProvider createNew(KeycloakSession session) {
return new MapClientProvider(session, getStorage(session), REGISTERED_NODES_STORE);
}
@Override
public void close() {
super.close();
onClose.run();
}
@Override
public String getHelpText() {
return "Client provider";
}
@Override
public void onEvent(ProviderEvent event) {
if (event instanceof RoleContainerModel.RoleRemovedEvent) {
RoleRemovedEvent e = (RoleContainerModel.RoleRemovedEvent) event;
RoleModel role = e.getRole();
RoleContainerModel container = role.getContainer();
RealmModel realm;
if (container instanceof RealmModel) {
realm = (RealmModel) container;
} else if (container instanceof ClientModel) {
realm = ((ClientModel) container).getRealm();
} else {
return;
}
((MapClientProvider) e.getKeycloakSession().getProvider(ClientProvider.class)).preRemove(realm, role);
public void invalidate(KeycloakSession session, InvalidableObjectType type, Object... params) {
if (type == REALM_BEFORE_REMOVE) {
create(session).preRemove((RealmModel) params[0]);
} else if (type == ROLE_BEFORE_REMOVE) {
create(session).preRemove((RealmModel) params[0], (RoleModel) params[1]);
} else if (type == CLIENT_AFTER_REMOVE) {
session.getKeycloakSessionFactory().publish(new ClientModel.ClientRemovedEvent() {
@Override public ClientModel getClient() { return (ClientModel) params[0]; }
@Override public KeycloakSession getKeycloakSession() { return session; }
});
}
}
}

View file

@ -23,7 +23,6 @@ import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.jboss.logging.Logger;
import static org.keycloak.common.util.StackUtil.getShortStackTrace;
import org.keycloak.models.ClientScopeModel.SearchableFields;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.ClientScopeProvider;
@ -36,6 +35,9 @@ import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator;
import org.keycloak.models.map.storage.criteria.DefaultModelCriteria;
import org.keycloak.models.utils.KeycloakModelUtils;
import static org.keycloak.common.util.StackUtil.getShortStackTrace;
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.CLIENT_SCOPE_AFTER_REMOVE;
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.CLIENT_SCOPE_BEFORE_REMOVE;
import static org.keycloak.models.map.storage.QueryParameters.Order.ASCENDING;
import static org.keycloak.models.map.storage.QueryParameters.withCriteria;
import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria;
@ -106,23 +108,12 @@ public class MapClientScopeProvider implements ClientScopeProvider {
ClientScopeModel clientScope = getClientScopeById(realm, id);
if (clientScope == null) return false;
session.users().preRemove(clientScope);
realm.removeDefaultClientScope(clientScope);
session.getKeycloakSessionFactory().publish(new ClientScopeModel.ClientScopeRemovedEvent() {
@Override
public KeycloakSession getKeycloakSession() {
return session;
}
@Override
public ClientScopeModel getClientScope() {
return clientScope;
}
});
session.invalidate(CLIENT_SCOPE_BEFORE_REMOVE, realm, clientScope);
tx.delete(id);
session.invalidate(CLIENT_SCOPE_AFTER_REMOVE, clientScope);
return true;
}
@ -150,6 +141,14 @@ public class MapClientScopeProvider implements ClientScopeProvider {
: entityToAdapterFunc(realm).apply(entity);
}
public void preRemove(RealmModel realm) {
LOG.tracef("preRemove(%s)%s", realm, getShortStackTrace());
DefaultModelCriteria<ClientScopeModel> mcb = criteria();
mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId());
tx.delete(withCriteria(mcb));
}
@Override
public void close() {
}

View file

@ -17,19 +17,24 @@
package org.keycloak.models.map.clientscope;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.ClientScopeProvider;
import org.keycloak.models.ClientScopeProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.map.common.AbstractMapProviderFactory;
import org.keycloak.provider.InvalidationHandler;
public class MapClientScopeProviderFactory extends AbstractMapProviderFactory<ClientScopeProvider, MapClientScopeEntity, ClientScopeModel> implements ClientScopeProviderFactory {
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.CLIENT_SCOPE_AFTER_REMOVE;
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.CLIENT_SCOPE_BEFORE_REMOVE;
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.REALM_BEFORE_REMOVE;
public class MapClientScopeProviderFactory extends AbstractMapProviderFactory<MapClientScopeProvider, MapClientScopeEntity, ClientScopeModel> implements ClientScopeProviderFactory<MapClientScopeProvider>, InvalidationHandler {
public MapClientScopeProviderFactory() {
super(ClientScopeModel.class);
super(ClientScopeModel.class, MapClientScopeProvider.class);
}
@Override
public ClientScopeProvider create(KeycloakSession session) {
public MapClientScopeProvider createNew(KeycloakSession session) {
return new MapClientScopeProvider(session, getStorage(session));
}
@ -37,4 +42,18 @@ public class MapClientScopeProviderFactory extends AbstractMapProviderFactory<Cl
public String getHelpText() {
return "Client scope provider";
}
@Override
public void invalidate(KeycloakSession session, InvalidableObjectType type, Object... params) {
if (type == REALM_BEFORE_REMOVE) {
create(session).preRemove((RealmModel) params[0]);
} else if (type == CLIENT_SCOPE_BEFORE_REMOVE) {
((RealmModel) params[0]).removeDefaultClientScope((ClientScopeModel) params[1]);
} else if (type == CLIENT_SCOPE_AFTER_REMOVE) {
session.getKeycloakSessionFactory().publish(new ClientScopeModel.ClientScopeRemovedEvent() {
@Override public ClientScopeModel getClientScope() { return (ClientScopeModel) params[0]; }
@Override public KeycloakSession getKeycloakSession() { return session; }
});
}
}
}

View file

@ -16,6 +16,7 @@
*/
package org.keycloak.models.map.common;
import java.util.concurrent.atomic.AtomicInteger;
import org.keycloak.Config.Scope;
import org.keycloak.common.Profile;
import org.keycloak.models.KeycloakSession;
@ -25,9 +26,11 @@ import org.keycloak.models.map.storage.MapStorageSpi;
import org.keycloak.component.AmphibianProviderFactory;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.EnvironmentDependentProviderFactory;
import org.keycloak.provider.InvalidationHandler;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.jboss.logging.Logger;
import static org.keycloak.models.utils.KeycloakModelUtils.getComponentFactory;
/**
@ -42,13 +45,59 @@ public abstract class AbstractMapProviderFactory<T extends Provider, V extends A
protected final Logger LOG = Logger.getLogger(getClass());
public static final AtomicInteger uniqueCounter = new AtomicInteger();
private final String uniqueKey = getClass().getName() + uniqueCounter.incrementAndGet();
protected final Class<M> modelType;
private final Class<T> providerType;
private Scope storageConfigScope;
@SuppressWarnings("unchecked")
protected AbstractMapProviderFactory(Class<M> modelType) {
protected AbstractMapProviderFactory(Class<M> modelType, Class<T> providerType) {
this.modelType = modelType;
this.providerType = providerType;
}
public enum MapProviderObjectType implements InvalidationHandler.InvalidableObjectType {
CLIENT_BEFORE_REMOVE,
CLIENT_AFTER_REMOVE,
CLIENT_SCOPE_BEFORE_REMOVE,
CLIENT_SCOPE_AFTER_REMOVE,
GROUP_BEFORE_REMOVE,
GROUP_AFTER_REMOVE,
REALM_BEFORE_REMOVE,
REALM_AFTER_REMOVE,
ROLE_BEFORE_REMOVE,
ROLE_AFTER_REMOVE,
USER_BEFORE_REMOVE,
USER_AFTER_REMOVE
}
/**
* Creates new instance of a provider.
*
* @param session
* @return See description.
*/
public abstract T createNew(KeycloakSession session);
/**
* Returns instance of a provider. If the instance is already created within
* the session (it's found in session attributes), it's returned from there,
* otherwise new instance is created (and stored among the session attributes).
*
* @param session
* @return See description.
*/
@Override
public T create(KeycloakSession session) {
T provider = session.getAttribute(uniqueKey, providerType);
if (provider != null) {
return provider;
}
provider = createNew(session);
session.setAttribute(uniqueKey, provider);
return provider;
}
@Override

View file

@ -36,10 +36,11 @@ import org.keycloak.models.map.storage.criteria.DefaultModelCriteria;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.keycloak.common.util.StackUtil.getShortStackTrace;
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.GROUP_AFTER_REMOVE;
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.GROUP_BEFORE_REMOVE;
import static org.keycloak.models.map.storage.QueryParameters.Order.ASCENDING;
import static org.keycloak.models.map.storage.QueryParameters.withCriteria;
import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria;
@ -214,34 +215,12 @@ public class MapGroupProvider implements GroupProvider {
LOG.tracef("removeGroup(%s, %s)%s", realm, group, getShortStackTrace());
if (group == null) return false;
// TODO: Sending an event (, user group removal and realm default groups) should be extracted to store layer
session.getKeycloakSessionFactory().publish(new GroupModel.GroupRemovedEvent() {
@Override
public RealmModel getRealm() {
return realm;
}
@Override
public GroupModel getGroup() {
return group;
}
@Override
public KeycloakSession getKeycloakSession() {
return session;
}
});
session.users().preRemove(realm, group);
realm.removeDefaultGroup(group);
group.getSubGroupsStream().collect(Collectors.toSet()).forEach(subGroup -> session.groups().removeGroup(realm, subGroup));
// TODO: ^^^^^^^ Up to here
session.invalidate(GROUP_BEFORE_REMOVE, realm, group);
tx.delete(group.getId());
session.invalidate(GROUP_AFTER_REMOVE, realm, group);
return true;
}
@ -306,9 +285,16 @@ public class MapGroupProvider implements GroupProvider {
}
}
public void preRemove(RealmModel realm) {
LOG.tracef("preRemove(%s)%s", realm, getShortStackTrace());
DefaultModelCriteria<GroupModel> mcb = criteria();
mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId());
tx.delete(withCriteria(mcb));
}
@Override
public void close() {
}
}

View file

@ -16,70 +16,58 @@
*/
package org.keycloak.models.map.group;
import org.keycloak.models.ClientModel;
import java.util.stream.Collectors;
import org.keycloak.models.GroupModel;
import org.keycloak.models.GroupProvider;
import org.keycloak.models.GroupProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleContainerModel.RoleRemovedEvent;
import org.keycloak.models.RoleModel;
import org.keycloak.provider.ProviderEvent;
import org.keycloak.provider.ProviderEventListener;
import org.keycloak.models.map.common.AbstractMapProviderFactory;
import org.keycloak.provider.InvalidationHandler;
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.GROUP_AFTER_REMOVE;
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.GROUP_BEFORE_REMOVE;
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.REALM_BEFORE_REMOVE;
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.ROLE_BEFORE_REMOVE;
/**
*
* @author mhajas
*/
public class MapGroupProviderFactory extends AbstractMapProviderFactory<GroupProvider, MapGroupEntity, GroupModel> implements GroupProviderFactory, ProviderEventListener {
private Runnable onClose;
public class MapGroupProviderFactory extends AbstractMapProviderFactory<MapGroupProvider, MapGroupEntity, GroupModel> implements GroupProviderFactory<MapGroupProvider>, InvalidationHandler {
public MapGroupProviderFactory() {
super(GroupModel.class);
super(GroupModel.class, MapGroupProvider.class);
}
@Override
public void postInit(KeycloakSessionFactory factory) {
factory.register(this);
onClose = () -> factory.unregister(this);
}
@Override
public MapGroupProvider create(KeycloakSession session) {
public MapGroupProvider createNew(KeycloakSession session) {
return new MapGroupProvider(session, getStorage(session));
}
@Override
public void close() {
super.close();
onClose.run();
}
@Override
public String getHelpText() {
return "Group provider";
}
@Override
public void onEvent(ProviderEvent event) {
if (event instanceof RoleContainerModel.RoleRemovedEvent) {
RoleRemovedEvent e = (RoleContainerModel.RoleRemovedEvent) event;
RoleModel role = e.getRole();
RoleContainerModel container = role.getContainer();
RealmModel realm;
if (container instanceof RealmModel) {
realm = (RealmModel) container;
} else if (container instanceof ClientModel) {
realm = ((ClientModel) container).getRealm();
} else {
return;
}
((MapGroupProvider) e.getKeycloakSession().getProvider(GroupProvider.class)).preRemove(realm, role);
public void invalidate(KeycloakSession session, InvalidableObjectType type, Object... params) {
if (type == REALM_BEFORE_REMOVE) {
create(session).preRemove((RealmModel) params[0]);
} else if (type == ROLE_BEFORE_REMOVE) {
create(session).preRemove((RealmModel) params[0], (RoleModel) params[1]);
} else if (type == GROUP_BEFORE_REMOVE) {
RealmModel realm = (RealmModel) params[0];
GroupModel group = (GroupModel) params[1];
realm.removeDefaultGroup(group);
group.getSubGroupsStream().collect(Collectors.toSet()).forEach(subGroup -> create(session).removeGroup(realm, subGroup));
} else if (type == GROUP_AFTER_REMOVE) {
session.getKeycloakSessionFactory().publish(new GroupModel.GroupRemovedEvent() {
@Override public RealmModel getRealm() { return (RealmModel) params[0]; }
@Override public GroupModel getGroup() { return (GroupModel) params[1]; }
@Override public KeycloakSession getKeycloakSession() { return session; }
});
}
}
}

View file

@ -17,43 +17,28 @@
package org.keycloak.models.map.loginFailure;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserLoginFailureProvider;
import org.keycloak.models.UserLoginFailureProviderFactory;
import org.keycloak.models.UserLoginFailureModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.AbstractMapProviderFactory;
import org.keycloak.provider.ProviderEvent;
import org.keycloak.provider.ProviderEventListener;
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;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public class MapUserLoginFailureProviderFactory extends AbstractMapProviderFactory<UserLoginFailureProvider, MapUserLoginFailureEntity, UserLoginFailureModel>
implements UserLoginFailureProviderFactory, ProviderEventListener {
private Runnable onClose;
public class MapUserLoginFailureProviderFactory extends AbstractMapProviderFactory<MapUserLoginFailureProvider, MapUserLoginFailureEntity, UserLoginFailureModel>
implements UserLoginFailureProviderFactory<MapUserLoginFailureProvider>, InvalidationHandler {
public MapUserLoginFailureProviderFactory() {
super(UserLoginFailureModel.class);
super(UserLoginFailureModel.class, MapUserLoginFailureProvider.class);
}
@Override
public void postInit(KeycloakSessionFactory factory) {
factory.register(this);
onClose = () -> factory.unregister(this);
}
@Override
public void close() {
super.close();
onClose.run();
}
@Override
public MapUserLoginFailureProvider create(KeycloakSession session) {
public MapUserLoginFailureProvider createNew(KeycloakSession session) {
return new MapUserLoginFailureProvider(session, getStorage(session));
}
@ -63,17 +48,11 @@ public class MapUserLoginFailureProviderFactory extends AbstractMapProviderFacto
}
@Override
public void onEvent(ProviderEvent event) {
if (event instanceof UserModel.UserRemovedEvent) {
UserModel.UserRemovedEvent userRemovedEvent = (UserModel.UserRemovedEvent) event;
MapUserLoginFailureProvider provider = MapUserLoginFailureProviderFactory.this.create(userRemovedEvent.getKeycloakSession());
provider.removeUserLoginFailure(userRemovedEvent.getRealm(), userRemovedEvent.getUser().getId());
} else if (event instanceof RealmModel.RealmRemovedEvent) {
RealmModel.RealmRemovedEvent realmRemovedEvent = (RealmModel.RealmRemovedEvent) event;
MapUserLoginFailureProvider provider = MapUserLoginFailureProviderFactory.this.create(realmRemovedEvent.getKeycloakSession());
provider.removeAllUserLoginFailures(realmRemovedEvent.getRealm());
public void invalidate(KeycloakSession session, InvalidableObjectType type, Object... params) {
if (type == REALM_BEFORE_REMOVE) {
create(session).removeAllUserLoginFailures((RealmModel) params[0]);
} else if (type == USER_BEFORE_REMOVE) {
create(session).removeUserLoginFailure((RealmModel) params[0], ((UserModel) params[1]).getId());
}
}

View file

@ -22,7 +22,6 @@ import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import org.jboss.logging.Logger;
import static org.keycloak.common.util.StackUtil.getShortStackTrace;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.GroupModel;
@ -37,6 +36,10 @@ import org.keycloak.models.map.storage.MapStorage;
import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator;
import org.keycloak.models.map.storage.criteria.DefaultModelCriteria;
import org.keycloak.models.utils.KeycloakModelUtils;
import static org.keycloak.common.util.StackUtil.getShortStackTrace;
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.REALM_AFTER_REMOVE;
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.REALM_BEFORE_REMOVE;
import static org.keycloak.models.map.storage.QueryParameters.Order.ASCENDING;
import static org.keycloak.models.map.storage.QueryParameters.withCriteria;
import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria;
@ -135,27 +138,12 @@ public class MapRealmProvider implements RealmProvider {
if (realm == null) return false;
session.users().preRemove(realm);
session.clients().removeClients(realm);
session.clientScopes().removeClientScopes(realm);
session.roles().removeRoles(realm);
realm.getTopLevelGroupsStream().forEach(realm::removeGroup);
// TODO: Sending an event should be extracted to store layer
session.getKeycloakSessionFactory().publish(new RealmModel.RealmRemovedEvent() {
@Override
public RealmModel getRealm() {
return realm;
}
@Override
public KeycloakSession getKeycloakSession() {
return session;
}
});
// TODO: ^^^^^^^ Up to here
session.invalidate(REALM_BEFORE_REMOVE, realm);
tx.delete(id);
session.invalidate(REALM_AFTER_REMOVE, realm);
return true;
}

View file

@ -19,22 +19,34 @@ package org.keycloak.models.map.realm;
import org.keycloak.models.map.common.AbstractMapProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmProvider;
import org.keycloak.models.RealmProviderFactory;
import org.keycloak.provider.InvalidationHandler;
public class MapRealmProviderFactory extends AbstractMapProviderFactory<RealmProvider, MapRealmEntity, RealmModel> implements RealmProviderFactory {
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.REALM_AFTER_REMOVE;
public class MapRealmProviderFactory extends AbstractMapProviderFactory<MapRealmProvider, MapRealmEntity, RealmModel> implements RealmProviderFactory<MapRealmProvider>, InvalidationHandler {
public MapRealmProviderFactory() {
super(RealmModel.class);
super(RealmModel.class, MapRealmProvider.class);
}
@Override
public RealmProvider create(KeycloakSession session) {
public MapRealmProvider createNew(KeycloakSession session) {
return new MapRealmProvider(session, getStorage(session));
}
@Override
public String getHelpText() {
return "Realm provider";
}
}
@Override
public void invalidate(KeycloakSession session, InvalidableObjectType type, Object... params) {
if (type == REALM_AFTER_REMOVE) {
session.getKeycloakSessionFactory().publish(new RealmModel.RealmRemovedEvent() {
@Override public RealmModel getRealm() { return (RealmModel) params[0]; }
@Override public KeycloakSession getKeycloakSession() { return session; }
});
}
}
}

View file

@ -17,30 +17,29 @@
package org.keycloak.models.map.role;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Stream;
import org.jboss.logging.Logger;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.map.storage.MapKeycloakTransaction;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Stream;
import org.keycloak.models.map.storage.MapStorage;
import static org.keycloak.common.util.StackUtil.getShortStackTrace;
import static org.keycloak.models.map.storage.QueryParameters.Order.ASCENDING;
import static org.keycloak.models.map.storage.QueryParameters.withCriteria;
import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel.SearchableFields;
import org.keycloak.models.RoleProvider;
import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator;
import org.keycloak.models.map.storage.criteria.DefaultModelCriteria;
import static org.keycloak.common.util.StackUtil.getShortStackTrace;
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.ROLE_AFTER_REMOVE;
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.ROLE_BEFORE_REMOVE;
import static org.keycloak.models.map.storage.QueryParameters.Order.ASCENDING;
import static org.keycloak.models.map.storage.QueryParameters.withCriteria;
import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria;
public class MapRoleProvider implements RoleProvider {
private static final Logger LOG = Logger.getLogger(MapRoleProvider.class);
@ -161,24 +160,12 @@ public class MapRoleProvider implements RoleProvider {
RealmModel realm = role.isClientRole() ? ((ClientModel)role.getContainer()).getRealm() : (RealmModel)role.getContainer();
session.users().preRemove(realm, role);
// TODO: Sending an event should be extracted to store layer
session.getKeycloakSessionFactory().publish(new RoleContainerModel.RoleRemovedEvent() {
@Override
public RoleModel getRole() {
return role;
}
@Override
public KeycloakSession getKeycloakSession() {
return session;
}
});
// TODO: ^^^^^^^ Up to here
session.invalidate(ROLE_BEFORE_REMOVE, realm, role);
tx.delete(role.getId());
session.invalidate(ROLE_AFTER_REMOVE, realm, role);
return true;
}
@ -284,6 +271,14 @@ public class MapRoleProvider implements RoleProvider {
.map(entityToAdapterFunc(client.getRealm()));
}
public void preRemove(RealmModel realm) {
LOG.tracef("preRemove(%s)%s", realm, getShortStackTrace());
DefaultModelCriteria<RoleModel> mcb = criteria();
mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId());
tx.delete(withCriteria(mcb));
}
@Override
public void close() {
}

View file

@ -16,20 +16,27 @@
*/
package org.keycloak.models.map.role;
import org.keycloak.models.ClientModel;
import org.keycloak.models.map.common.AbstractMapProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.RoleProvider;
import org.keycloak.models.RoleProviderFactory;
import org.keycloak.provider.InvalidationHandler;
public class MapRoleProviderFactory extends AbstractMapProviderFactory<RoleProvider, MapRoleEntity, RoleModel> implements RoleProviderFactory {
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.CLIENT_BEFORE_REMOVE;
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.REALM_BEFORE_REMOVE;
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.ROLE_AFTER_REMOVE;
public class MapRoleProviderFactory extends AbstractMapProviderFactory<MapRoleProvider, MapRoleEntity, RoleModel> implements RoleProviderFactory<MapRoleProvider>, InvalidationHandler {
public MapRoleProviderFactory() {
super(RoleModel.class);
super(RoleModel.class, MapRoleProvider.class);
}
@Override
public RoleProvider create(KeycloakSession session) {
public MapRoleProvider createNew(KeycloakSession session) {
return new MapRoleProvider(session, getStorage(session));
}
@ -37,4 +44,18 @@ public class MapRoleProviderFactory extends AbstractMapProviderFactory<RoleProvi
public String getHelpText() {
return "Role provider";
}
@Override
public void invalidate(KeycloakSession session, InvalidableObjectType type, Object... params) {
if (type == REALM_BEFORE_REMOVE) {
create(session).preRemove((RealmModel) params[0]);
} else if (type == CLIENT_BEFORE_REMOVE) {
create(session).removeRoles((ClientModel) params[1]);
} else if (type == ROLE_AFTER_REMOVE) {
session.getKeycloakSessionFactory().publish(new RoleContainerModel.RoleRemovedEvent() {
@Override public RoleModel getRole() { return (RoleModel) params[1]; }
@Override public KeycloakSession getKeycloakSession() { return session; }
});
}
}
}

View file

@ -72,6 +72,8 @@ import static org.keycloak.models.UserModel.EMAIL_VERIFIED;
import static org.keycloak.models.UserModel.FIRST_NAME;
import static org.keycloak.models.UserModel.LAST_NAME;
import static org.keycloak.models.UserModel.USERNAME;
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.USER_AFTER_REMOVE;
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.USER_BEFORE_REMOVE;
import static org.keycloak.models.map.storage.QueryParameters.Order.ASCENDING;
import static org.keycloak.models.map.storage.QueryParameters.withCriteria;
import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria;
@ -727,7 +729,11 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
String userId = user.getId();
Optional<MapUserEntity> userById = getEntityById(realm, userId);
if (userById.isPresent()) {
session.invalidate(USER_BEFORE_REMOVE, realm, user);
tx.delete(userId);
session.invalidate(USER_AFTER_REMOVE, realm, user);
return true;
}

View file

@ -17,24 +17,35 @@
package org.keycloak.models.map.user;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider;
import org.keycloak.models.UserProviderFactory;
import org.keycloak.models.map.common.AbstractMapProviderFactory;
import org.keycloak.provider.InvalidationHandler;
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.CLIENT_BEFORE_REMOVE;
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.CLIENT_SCOPE_BEFORE_REMOVE;
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.GROUP_BEFORE_REMOVE;
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.REALM_BEFORE_REMOVE;
import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.ROLE_BEFORE_REMOVE;
/**
*
* @author mhajas
*/
public class MapUserProviderFactory extends AbstractMapProviderFactory<UserProvider, MapUserEntity, UserModel> implements UserProviderFactory {
public class MapUserProviderFactory extends AbstractMapProviderFactory<MapUserProvider, MapUserEntity, UserModel> implements UserProviderFactory<MapUserProvider>, InvalidationHandler {
public MapUserProviderFactory() {
super(UserModel.class);
super(UserModel.class, MapUserProvider.class);
}
@Override
public UserProvider create(KeycloakSession session) {
public MapUserProvider createNew(KeycloakSession session) {
return new MapUserProvider(session, getStorage(session));
}
@ -43,4 +54,18 @@ public class MapUserProviderFactory extends AbstractMapProviderFactory<UserProvi
return "User provider";
}
@Override
public void invalidate(KeycloakSession session, InvalidableObjectType type, Object... params) {
if (type == REALM_BEFORE_REMOVE) {
create(session).preRemove((RealmModel) params[0]);
} else if (type == ROLE_BEFORE_REMOVE) {
create(session).preRemove((RealmModel) params[0], (RoleModel) params[1]);
} else if (type == CLIENT_SCOPE_BEFORE_REMOVE) {
create(session).preRemove((ClientScopeModel) params[1]);
} else if (type == CLIENT_BEFORE_REMOVE) {
create(session).preRemove((RealmModel) params[0], (ClientModel) params[1]);
} else if (type == GROUP_BEFORE_REMOVE) {
create(session).preRemove((RealmModel) params[0], (GroupModel) params[1]);
}
}
}

View file

@ -22,6 +22,7 @@ import org.keycloak.component.AmphibianProviderFactory;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.UserSessionProvider;
@ -30,27 +31,28 @@ 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.ProviderEvent;
import org.keycloak.provider.ProviderEventListener;
import org.keycloak.provider.InvalidationHandler;
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>
*/
public class MapUserSessionProviderFactory<UK, CK> implements AmphibianProviderFactory<UserSessionProvider>, UserSessionProviderFactory, ProviderEventListener, EnvironmentDependentProviderFactory {
public class MapUserSessionProviderFactory<UK, CK> implements AmphibianProviderFactory<UserSessionProvider>, UserSessionProviderFactory, EnvironmentDependentProviderFactory, InvalidationHandler {
public static final String CONFIG_STORAGE_USER_SESSIONS = "storage-user-sessions";
public static final String CONFIG_STORAGE_CLIENT_SESSIONS = "storage-client-sessions";
public static final String PROVIDER_ID = AbstractMapProviderFactory.PROVIDER_ID;
private final String uniqueKey = getClass().getName() + uniqueCounter.incrementAndGet();
private Scope storageConfigScopeUserSessions;
private Scope storageConfigScopeClientSessions;
private Runnable onClose;
@Override
public String getId() {
return PROVIDER_ID;
@ -64,14 +66,11 @@ public class MapUserSessionProviderFactory<UK, CK> implements AmphibianProviderF
@Override
public void postInit(KeycloakSessionFactory factory) {
factory.register(this);
onClose = () -> factory.unregister(this);
}
@Override
public void close() {
AmphibianProviderFactory.super.close();
onClose.run();
}
@Override
@ -79,7 +78,12 @@ public class MapUserSessionProviderFactory<UK, CK> implements AmphibianProviderF
}
@Override
@SuppressWarnings("unchecked")
public MapUserSessionProvider create(KeycloakSession session) {
MapUserSessionProvider provider = session.getAttribute(uniqueKey, MapUserSessionProvider.class);
if (provider != null) return provider;
MapStorageProviderFactory storageProviderFactoryUs = (MapStorageProviderFactory) getComponentFactory(session.getKeycloakSessionFactory(),
MapStorageProvider.class, storageConfigScopeUserSessions, MapStorageSpi.NAME);
final MapStorageProvider factoryUs = storageProviderFactoryUs.create(session);
@ -90,7 +94,9 @@ public class MapUserSessionProviderFactory<UK, CK> implements AmphibianProviderF
final MapStorageProvider factoryCs = storageProviderFactoryCs.create(session);
MapStorage clientSessionStore = factoryCs.getStorage(AuthenticatedClientSessionModel.class);
return new MapUserSessionProvider(session, userSessionStore, clientSessionStore);
provider = new MapUserSessionProvider(session, userSessionStore, clientSessionStore);
session.setAttribute(uniqueKey, provider);
return provider;
}
@Override
@ -99,12 +105,9 @@ public class MapUserSessionProviderFactory<UK, CK> implements AmphibianProviderF
}
@Override
public void onEvent(ProviderEvent event) {
if (event instanceof UserModel.UserRemovedEvent) {
UserModel.UserRemovedEvent userRemovedEvent = (UserModel.UserRemovedEvent) event;
MapUserSessionProvider provider = MapUserSessionProviderFactory.this.create(userRemovedEvent.getKeycloakSession());
provider.removeUserSessions(userRemovedEvent.getRealm(), userRemovedEvent.getUser());
public void invalidate(KeycloakSession session, InvalidableObjectType type, Object... params) {
if (type == USER_BEFORE_REMOVE) {
create(session).removeUserSessions((RealmModel) params[0], (UserModel) params[1]);
}
}

View file

@ -19,5 +19,5 @@ package org.keycloak.models;
import org.keycloak.provider.ProviderFactory;
public interface ClientProviderFactory extends ProviderFactory<ClientProvider> {
public interface ClientProviderFactory<T extends ClientProvider> extends ProviderFactory<T> {
}

View file

@ -19,5 +19,5 @@ package org.keycloak.models;
import org.keycloak.provider.ProviderFactory;
public interface ClientScopeProviderFactory extends ProviderFactory<ClientScopeProvider> {
public interface ClientScopeProviderFactory<T extends ClientScopeProvider> extends ProviderFactory<T> {
}

View file

@ -19,5 +19,5 @@ package org.keycloak.models;
import org.keycloak.provider.ProviderFactory;
public interface GroupProviderFactory extends ProviderFactory<GroupProvider> {
public interface GroupProviderFactory<T extends GroupProvider> extends ProviderFactory<T> {
}

View file

@ -23,5 +23,5 @@ import org.keycloak.provider.ProviderFactory;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface RealmProviderFactory extends ProviderFactory<RealmProvider> {
public interface RealmProviderFactory<T extends RealmProvider> extends ProviderFactory<T> {
}

View file

@ -19,5 +19,5 @@ package org.keycloak.models;
import org.keycloak.provider.ProviderFactory;
public interface RoleProviderFactory extends ProviderFactory<RoleProvider> {
public interface RoleProviderFactory<T extends RoleProvider> extends ProviderFactory<T> {
}

View file

@ -21,6 +21,6 @@ import org.keycloak.provider.ProviderFactory;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public interface UserLoginFailureProviderFactory extends ProviderFactory<UserLoginFailureProvider> {
public interface UserLoginFailureProviderFactory<T extends UserLoginFailureProvider> extends ProviderFactory<T> {
}

View file

@ -23,5 +23,5 @@ import org.keycloak.provider.ProviderFactory;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface UserProviderFactory extends ProviderFactory<UserProvider> {
public interface UserProviderFactory<T extends UserProvider> extends ProviderFactory<T> {
}

View file

@ -22,5 +22,5 @@ import org.keycloak.provider.ProviderFactory;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface AuthenticationSessionProviderFactory extends ProviderFactory<AuthenticationSessionProvider> {
public interface AuthenticationSessionProviderFactory<T extends AuthenticationSessionProvider> extends ProviderFactory<T> {
}

View file

@ -19,7 +19,7 @@ package org.keycloak.models;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.cache.UserCache;
import org.keycloak.provider.InvalidationHandler;
import org.keycloak.provider.InvalidationHandler.InvalidableObjectType;
import org.keycloak.provider.Provider;
import org.keycloak.services.clientpolicy.ClientPolicyManager;
import org.keycloak.sessions.AuthenticationSessionProvider;
@ -33,7 +33,7 @@ import java.util.function.Function;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface KeycloakSession extends InvalidationHandler {
public interface KeycloakSession {
KeycloakContext getContext();
@ -128,10 +128,9 @@ public interface KeycloakSession extends InvalidationHandler {
/**
* Invalidates intermediate states of the given objects, both immediately and at the end of this session.
* @param type Type of the objects to invalidate
* @param ids Identifiers of the invalidated objects
* @param params Parameters used for the invalidation
*/
@Override
void invalidate(InvalidableObjectType type, Object... ids);
void invalidate(InvalidableObjectType type, Object... params);
void enlistForClose(Provider provider);

View file

@ -29,11 +29,9 @@ import org.keycloak.storage.client.ClientStorageProvider;
import org.keycloak.storage.client.ClientStorageProviderModel;
import org.keycloak.storage.role.RoleStorageProvider;
import org.keycloak.storage.role.RoleStorageProviderModel;
import org.keycloak.utils.StringUtil;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

View file

@ -16,6 +16,8 @@
*/
package org.keycloak.provider;
import org.keycloak.models.KeycloakSession;
/**
* Handles invalidation requests. This interface is specifically implemented by
* providers that implement a cache of objects that might change in the outside.
@ -40,9 +42,10 @@ public interface InvalidationHandler {
/**
* Invalidates intermediate states of the given objects
* @param session KeycloakSession
* @param type Type of the objects to invalidate
* @param ids Identifiers of the invalidated objects
* @param params Parameters used for the invalidation
*/
void invalidate(InvalidableObjectType type, Object... ids);
void invalidate(KeycloakSession session, InvalidableObjectType type, Object... params);
}

View file

@ -23,6 +23,7 @@ import org.keycloak.common.util.StackUtil;
import org.keycloak.component.ComponentFactoryProviderFactory;
import org.keycloak.component.ComponentModel;
import org.keycloak.component.ComponentModelScope;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.provider.InvalidationHandler;
@ -155,7 +156,7 @@ public class DefaultComponentFactoryProviderFactory implements ComponentFactoryP
}
@Override
public void invalidate(InvalidableObjectType type, Object... ids) {
public void invalidate(KeycloakSession session, InvalidableObjectType type, Object... ids) {
if (LOG.isDebugEnabled()) {
LOG.debugf("Invalidating %s: %s", type, Arrays.asList(ids));
}
@ -169,25 +170,25 @@ public class DefaultComponentFactoryProviderFactory implements ComponentFactoryP
Stream.of(ids)
.map(componentsMap.get()::remove).filter(Objects::nonNull)
.forEach(ProviderFactory::close);
propagateInvalidation(componentsMap.get(), type, ids);
propagateInvalidation(session, componentsMap.get(), type, ids);
} else if (type == ObjectType.REALM || type == ObjectType.PROVIDER_FACTORY) {
Stream.of(ids)
.map(dependentInvalidations::get).filter(Objects::nonNull).flatMap(Collection::stream)
.map(componentsMap.get()::remove).filter(Objects::nonNull)
.forEach(ProviderFactory::close);
Stream.of(ids).forEach(dependentInvalidations::remove);
propagateInvalidation(componentsMap.get(), type, ids);
propagateInvalidation(session, componentsMap.get(), type, ids);
} else {
propagateInvalidation(componentsMap.get(), type, ids);
propagateInvalidation(session, componentsMap.get(), type, ids);
}
}
private void propagateInvalidation(ConcurrentMap<String, ProviderFactory> componentsMap, InvalidableObjectType type, Object[] ids) {
private void propagateInvalidation(KeycloakSession session, ConcurrentMap<String, ProviderFactory> componentsMap, InvalidableObjectType type, Object[] ids) {
componentsMap.values()
.stream()
.filter(InvalidationHandler.class::isInstance)
.map(InvalidationHandler.class::cast)
.forEach(ih -> ih.invalidate(type, ids));
.forEach(ih -> ih.invalidate(session, type, ids));
}
@Override

View file

@ -41,10 +41,11 @@ import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.cache.CacheRealmProvider;
import org.keycloak.models.cache.UserCache;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.provider.InvalidationHandler.InvalidableObjectType;
import org.keycloak.provider.InvalidationHandler.ObjectType;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.services.clientpolicy.ClientPolicyManager;
import org.keycloak.services.clientpolicy.DefaultClientPolicyManager;
import org.keycloak.sessions.AuthenticationSessionProvider;
import org.keycloak.storage.ClientStorageManager;
import org.keycloak.storage.ClientScopeStorageManager;
@ -169,8 +170,10 @@ public class DefaultKeycloakSession implements KeycloakSession {
@Override
public void invalidate(InvalidableObjectType type, Object... ids) {
factory.invalidate(type, ids);
invalidationMap.computeIfAbsent(type, o -> new HashSet<>()).addAll(Arrays.asList(ids));
factory.invalidate(this, type, ids);
if (type == ObjectType.PROVIDER_FACTORY) {
invalidationMap.computeIfAbsent(type, o -> new HashSet<>()).addAll(Arrays.asList(ids));
}
}
@Override
@ -524,7 +527,7 @@ public class DefaultKeycloakSession implements KeycloakSession {
providers.values().forEach(safeClose);
closable.forEach(safeClose);
for (Entry<InvalidableObjectType, Set<Object>> me : invalidationMap.entrySet()) {
factory.invalidate(me.getKey(), me.getValue().toArray());
factory.invalidate(this, me.getKey(), me.getValue().toArray());
}
}

View file

@ -174,7 +174,7 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
checkProvider();
boolean cfChanged = false;
for (ProviderFactory factory : undeployed) {
invalidate(ObjectType.PROVIDER_FACTORY, factory.getClass());
invalidate(null, ObjectType.PROVIDER_FACTORY, factory.getClass());
factory.close();
cfChanged |= (componentFactoryPF == factory);
}
@ -359,13 +359,13 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
}
@Override
public void invalidate(InvalidableObjectType type, Object... ids) {
public void invalidate(KeycloakSession session, InvalidableObjectType type, Object... ids) {
factoriesMap.values().stream()
.map(Map::values)
.flatMap(Collection::stream)
.filter(InvalidationHandler.class::isInstance)
.map(InvalidationHandler.class::cast)
.forEach(ih -> ih.invalidate(type, ids));
.forEach(ih -> ih.invalidate(session, type, ids));
}
@Override

View file

@ -36,6 +36,7 @@ import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.common.Profile;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.common.util.Time;
import org.keycloak.models.RealmProvider;
import org.keycloak.models.cache.CacheRealmProvider;
import org.keycloak.models.cache.UserCache;
import org.keycloak.representations.idm.ClientRepresentation;
@ -720,6 +721,25 @@ public abstract class AbstractKeycloakTest {
return isProduct ? Profile.PRODUCT_NAME : Profile.PROJECT_NAME;
}
/**
* MapRealmProvider uses session.invalidate() instead of calling e.g.
* session.clients().removeClients(realm); for clients (where clients are being removed one by one)
*
* Therefore it doesn't call session.users().preRemove(realm, client) for each client.
* Due to that JpaUserFederatedStorageProvider.preRemove(realm, client) is not called.
* So there remains objects in the database in user federation related tables after realm removal.
*
* Same for roles etc.
*
* Legacy federated storage is NOT supposed to work with map storage, so this method
* returns true if realm provider is "jpa" to be able to skip particular tests.
*/
protected boolean isJpaRealmProvider() {
String realmProvider = testingClient.server()
.fetchString(s -> s.getKeycloakSessionFactory().getProviderFactory(RealmProvider.class).getId());
return Objects.equals(realmProvider, "\"jpa\"");
}
protected boolean isRealmCacheEnabled() {
String realmCache = testingClient.server()
.fetchString(s -> s.getKeycloakSessionFactory().getProviderFactory(CacheRealmProvider.class));

View file

@ -18,6 +18,7 @@ package org.keycloak.testsuite.federation.storage;
import org.junit.After;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.admin.client.resource.RealmResource;
@ -42,7 +43,6 @@ import javax.ws.rs.NotFoundException;
import java.io.File;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
@ -61,6 +61,8 @@ public class FederatedStorageExportImportTest extends AbstractAuthTest {
@Before
public void setDirs() {
Assume.assumeTrue("RealmProvider is not 'jpa'", isJpaRealmProvider());
File baseDir = new File(System.getProperty("auth.server.config.dir", "target"));
exportFileAbsolutePath = new File (baseDir, "singleFile-full.json").getAbsolutePath();

View file

@ -57,7 +57,10 @@ import org.keycloak.testsuite.util.OAuthClient;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
import org.junit.Assume;
import org.keycloak.models.RealmProvider;
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
import org.keycloak.testsuite.util.ContainerAssume;
@ -95,6 +98,8 @@ public class UserStorageFailureTest extends AbstractTestRealmKeycloakTest {
@Before
public void addProvidersBeforeTest() {
Assume.assumeTrue("RealmProvider is not 'jpa'", isJpaRealmProvider());
ComponentRepresentation memProvider = new ComponentRepresentation();
memProvider.setName("failure");
memProvider.setProviderId(FailableHardcodedStorageProviderFactory.PROVIDER_ID);

View file

@ -25,6 +25,7 @@ import java.util.List;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@ -89,6 +90,8 @@ public class UserStorageOTPTest extends AbstractTestRealmKeycloakTest {
@Before
public void addProvidersBeforeTest() throws URISyntaxException, IOException {
Assume.assumeTrue("RealmProvider is not 'jpa'", isJpaRealmProvider());
ComponentRepresentation dummyProvider = new ComponentRepresentation();
dummyProvider.setName("dummy");
dummyProvider.setId(componentId);

View file

@ -39,6 +39,8 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import org.junit.Assume;
import org.junit.Before;
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
@ -53,6 +55,11 @@ public class SyncFederationTest extends AbstractAuthTest {
private static final Logger log = Logger.getLogger(SyncFederationTest.class);
@Before
public void enabled() {
Assume.assumeTrue("RealmProvider is not 'jpa'", isJpaRealmProvider());
}
/**
* Test that period sync is triggered when creating a synchronized User Storage Provider
*

View file

@ -19,6 +19,7 @@ package org.keycloak.testsuite.model;
import org.junit.After;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.component.ComponentModel;
@ -59,6 +60,7 @@ public class UserConsentWithUserStorageModelTest extends AbstractTestRealmKeyclo
@Before
public void before() {
Assume.assumeTrue("RealmProvider is not 'jpa'", isJpaRealmProvider());
testingClient.server().run(UserConsentWithUserStorageModelTest::setupEnv);
}

View file

@ -143,11 +143,11 @@ public class ConcurrentHashMapStorageTest extends KeycloakModelTest {
assertClientsPersisted(component1Id, component2Id, idMain, id1, id2);
// Invalidate one component and check that the storage still contains what it should
getFactory().invalidate(ObjectType.COMPONENT, component1Id);
getFactory().invalidate(null, ObjectType.COMPONENT, component1Id);
assertClientsPersisted(component1Id, component2Id, idMain, id1, id2);
// Invalidate whole realm and check that the storage still contains what it should
getFactory().invalidate(ObjectType.REALM, realmId);
getFactory().invalidate(null, ObjectType.REALM, realmId);
assertClientsPersisted(component1Id, component2Id, idMain, id1, id2);
// Refresh factory (akin server restart) and check that the storage still contains what it should