KEYCLOAK-17215 Slowness issue while hitting /auth/admin/realms/$REALM/clients?viewableOnly=true after DELETE a role
This commit is contained in:
parent
c3b9c66941
commit
0a0caa07d6
19 changed files with 332 additions and 51 deletions
|
@ -1062,11 +1062,6 @@ public class RealmCacheSession implements CacheRealmProvider {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void preRemove(RealmModel realm, RoleModel role) {
|
|
||||||
getGroupDelegate().preRemove(realm, role);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addGroupEventIfAbsent(InvalidationEvent eventToAdd) {
|
private void addGroupEventIfAbsent(InvalidationEvent eventToAdd) {
|
||||||
String groupId = eventToAdd.getId();
|
String groupId = eventToAdd.getId();
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,7 @@ import org.keycloak.models.ModelException;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RealmProvider;
|
import org.keycloak.models.RealmProvider;
|
||||||
import org.keycloak.models.RoleContainerModel;
|
import org.keycloak.models.RoleContainerModel;
|
||||||
|
import org.keycloak.models.RoleContainerModel.RoleRemovedEvent;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.RoleProvider;
|
import org.keycloak.models.RoleProvider;
|
||||||
import org.keycloak.models.jpa.entities.ClientEntity;
|
import org.keycloak.models.jpa.entities.ClientEntity;
|
||||||
|
@ -335,14 +336,20 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, ClientSc
|
||||||
}
|
}
|
||||||
String compositeRoleTable = JpaUtils.getTableNameForNativeQuery("COMPOSITE_ROLE", em);
|
String compositeRoleTable = JpaUtils.getTableNameForNativeQuery("COMPOSITE_ROLE", em);
|
||||||
em.createNativeQuery("delete from " + compositeRoleTable + " where CHILD_ROLE = :role").setParameter("role", roleEntity).executeUpdate();
|
em.createNativeQuery("delete from " + compositeRoleTable + " where CHILD_ROLE = :role").setParameter("role", roleEntity).executeUpdate();
|
||||||
realm.getClientsStream().forEach(c -> c.deleteScopeMapping(role));
|
|
||||||
em.createNamedQuery("deleteClientScopeRoleMappingByRole").setParameter("role", roleEntity).executeUpdate();
|
em.createNamedQuery("deleteClientScopeRoleMappingByRole").setParameter("role", roleEntity).executeUpdate();
|
||||||
session.groups().preRemove(realm, role);
|
|
||||||
|
|
||||||
em.flush();
|
em.flush();
|
||||||
em.remove(roleEntity);
|
em.remove(roleEntity);
|
||||||
|
|
||||||
session.getKeycloakSessionFactory().publish(new RoleContainerModel.RoleRemovedEvent() {
|
session.getKeycloakSessionFactory().publish(roleRemovedEvent(role));
|
||||||
|
|
||||||
|
em.flush();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public RoleRemovedEvent roleRemovedEvent(RoleModel role) {
|
||||||
|
return new RoleContainerModel.RoleRemovedEvent() {
|
||||||
@Override
|
@Override
|
||||||
public RoleModel getRole() {
|
public RoleModel getRole() {
|
||||||
return role;
|
return role;
|
||||||
|
@ -352,11 +359,7 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, ClientSc
|
||||||
public KeycloakSession getKeycloakSession() {
|
public KeycloakSession getKeycloakSession() {
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
em.flush();
|
|
||||||
return true;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -584,11 +587,14 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, ClientSc
|
||||||
subGroup.setParent(null);
|
subGroup.setParent(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void preRemove(RealmModel realm, RoleModel role) {
|
public void preRemove(RealmModel realm, RoleModel role) {
|
||||||
// GroupProvider method implementation starts here
|
// GroupProvider method implementation starts here
|
||||||
em.createNamedQuery("deleteGroupRoleMappingsByRole").setParameter("roleId", role.getId()).executeUpdate();
|
em.createNamedQuery("deleteGroupRoleMappingsByRole").setParameter("roleId", role.getId()).executeUpdate();
|
||||||
// GroupProvider method implementation ends here
|
// GroupProvider method implementation ends here
|
||||||
|
|
||||||
|
// ClientProvider implementation
|
||||||
|
String clientScopeMapping = JpaUtils.getTableNameForNativeQuery("SCOPE_MAPPING", em);
|
||||||
|
em.createNativeQuery("delete from " + clientScopeMapping + " where ROLE_ID = :role").setParameter("role", role.getId()).executeUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -21,16 +21,24 @@ import org.keycloak.Config;
|
||||||
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.RealmProvider;
|
|
||||||
import org.keycloak.models.RealmProviderFactory;
|
import org.keycloak.models.RealmProviderFactory;
|
||||||
|
|
||||||
import javax.persistence.EntityManager;
|
import javax.persistence.EntityManager;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class JpaRealmProviderFactory implements RealmProviderFactory {
|
public class JpaRealmProviderFactory implements RealmProviderFactory, ProviderEventListener {
|
||||||
|
|
||||||
|
private Runnable onClose;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(Config.Scope config) {
|
public void init(Config.Scope config) {
|
||||||
|
@ -38,7 +46,8 @@ public class JpaRealmProviderFactory implements RealmProviderFactory {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void postInit(KeycloakSessionFactory factory) {
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
factory.register(this);
|
||||||
|
onClose = () -> factory.unregister(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -47,13 +56,31 @@ public class JpaRealmProviderFactory implements RealmProviderFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RealmProvider create(KeycloakSession session) {
|
public JpaRealmProvider create(KeycloakSession session) {
|
||||||
EntityManager em = session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
EntityManager em = session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
||||||
return new JpaRealmProvider(session, em);
|
return new JpaRealmProvider(session, em);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
|
onClose.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
create(e.getKeycloakSession()).preRemove(realm, role);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,7 @@ public class MapRootAuthenticationSessionProvider implements AuthenticationSessi
|
||||||
public MapRootAuthenticationSessionProvider(KeycloakSession session, MapStorage<UUID, MapRootAuthenticationSessionEntity, RootAuthenticationSessionModel> sessionStore) {
|
public MapRootAuthenticationSessionProvider(KeycloakSession session, MapStorage<UUID, MapRootAuthenticationSessionEntity, RootAuthenticationSessionModel> sessionStore) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.sessionStore = sessionStore;
|
this.sessionStore = sessionStore;
|
||||||
this.tx = sessionStore.createTransaction();
|
this.tx = sessionStore.createTransaction(session);
|
||||||
|
|
||||||
session.getTransactionManager().enlistAfterCompletion(tx);
|
session.getTransactionManager().enlistAfterCompletion(tx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import org.keycloak.models.ClientProvider;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.ModelDuplicateException;
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
|
|
||||||
import org.keycloak.models.map.storage.MapKeycloakTransaction;
|
import org.keycloak.models.map.storage.MapKeycloakTransaction;
|
||||||
import org.keycloak.models.map.common.Serialization;
|
import org.keycloak.models.map.common.Serialization;
|
||||||
|
@ -64,7 +65,7 @@ public class MapClientProvider implements ClientProvider {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.clientStore = clientStore;
|
this.clientStore = clientStore;
|
||||||
this.clientRegisteredNodesStore = clientRegisteredNodesStore;
|
this.clientRegisteredNodesStore = clientRegisteredNodesStore;
|
||||||
this.tx = clientStore.createTransaction();
|
this.tx = clientStore.createTransaction(session);
|
||||||
session.getTransactionManager().enlist(tx);
|
session.getTransactionManager().enlist(tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -321,6 +322,18 @@ public class MapClientProvider implements ClientProvider {
|
||||||
.collect(Collectors.toMap(ClientScopeModel::getName, Function.identity()));
|
.collect(Collectors.toMap(ClientScopeModel::getName, Function.identity()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void preRemove(RealmModel realm, RoleModel role) {
|
||||||
|
ModelCriteriaBuilder<ClientModel> mcb = clientStore.createCriteriaBuilder()
|
||||||
|
.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId())
|
||||||
|
.compare(SearchableFields.SCOPE_MAPPING_ROLE, Operator.EQ, role.getId());
|
||||||
|
try (Stream<MapClientEntity> toRemove = tx.getUpdatedNotRemoved(mcb)) {
|
||||||
|
toRemove
|
||||||
|
.map(clientEntity -> session.clients().getClientById(realm, clientEntity.getId().toString()))
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.forEach(clientModel -> clientModel.deleteScopeMapping(role));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
|
|
||||||
|
|
|
@ -22,31 +22,66 @@ import org.keycloak.models.ClientProvider;
|
||||||
import org.keycloak.models.ClientProviderFactory;
|
import org.keycloak.models.ClientProviderFactory;
|
||||||
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.RoleContainerModel;
|
||||||
|
import org.keycloak.models.RoleContainerModel.RoleRemovedEvent;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import org.keycloak.models.map.storage.MapStorageProvider;
|
import org.keycloak.models.map.storage.MapStorageProvider;
|
||||||
import org.keycloak.models.map.storage.MapStorage;
|
import org.keycloak.models.map.storage.MapStorage;
|
||||||
|
import org.keycloak.provider.ProviderEvent;
|
||||||
|
import org.keycloak.provider.ProviderEventListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author hmlnarik
|
* @author hmlnarik
|
||||||
*/
|
*/
|
||||||
public class MapClientProviderFactory extends AbstractMapProviderFactory<ClientProvider> implements ClientProviderFactory {
|
public class MapClientProviderFactory extends AbstractMapProviderFactory<ClientProvider> implements ClientProviderFactory, ProviderEventListener {
|
||||||
|
|
||||||
private final ConcurrentHashMap<UUID, ConcurrentMap<String, Integer>> REGISTERED_NODES_STORE = new ConcurrentHashMap<>();
|
private final ConcurrentHashMap<UUID, ConcurrentMap<String, Integer>> REGISTERED_NODES_STORE = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private MapStorage<UUID, MapClientEntity, ClientModel> store;
|
private MapStorage<UUID, MapClientEntity, ClientModel> store;
|
||||||
|
|
||||||
|
private Runnable onClose;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void postInit(KeycloakSessionFactory factory) {
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
MapStorageProvider sp = (MapStorageProvider) factory.getProviderFactory(MapStorageProvider.class);
|
MapStorageProvider sp = (MapStorageProvider) factory.getProviderFactory(MapStorageProvider.class);
|
||||||
this.store = sp.getStorage("clients", UUID.class, MapClientEntity.class, ClientModel.class);
|
this.store = sp.getStorage("clients", UUID.class, MapClientEntity.class, ClientModel.class);
|
||||||
|
|
||||||
|
factory.register(this);
|
||||||
|
onClose = () -> factory.unregister(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ClientProvider create(KeycloakSession session) {
|
public MapClientProvider create(KeycloakSession session) {
|
||||||
return new MapClientProvider(session, store, REGISTERED_NODES_STORE);
|
return new MapClientProvider(session, store, REGISTERED_NODES_STORE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
super.close();
|
||||||
|
onClose.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
create(e.getKeycloakSession()).preRemove(realm, role);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ public class MapClientScopeProvider implements ClientScopeProvider {
|
||||||
public MapClientScopeProvider(KeycloakSession session, MapStorage<UUID, MapClientScopeEntity, ClientScopeModel> clientScopeStore) {
|
public MapClientScopeProvider(KeycloakSession session, MapStorage<UUID, MapClientScopeEntity, ClientScopeModel> clientScopeStore) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.clientScopeStore = clientScopeStore;
|
this.clientScopeStore = clientScopeStore;
|
||||||
this.tx = clientScopeStore.createTransaction();
|
this.tx = clientScopeStore.createTransaction(session);
|
||||||
session.getTransactionManager().enlist(tx);
|
session.getTransactionManager().enlist(tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ public class MapGroupProvider implements GroupProvider {
|
||||||
public MapGroupProvider(KeycloakSession session, MapStorage<UUID, MapGroupEntity, GroupModel> groupStore) {
|
public MapGroupProvider(KeycloakSession session, MapStorage<UUID, MapGroupEntity, GroupModel> groupStore) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.groupStore = groupStore;
|
this.groupStore = groupStore;
|
||||||
this.tx = groupStore.createTransaction();
|
this.tx = groupStore.createTransaction(session);
|
||||||
session.getTransactionManager().enlist(tx);
|
session.getTransactionManager().enlist(tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,7 +295,6 @@ public class MapGroupProvider implements GroupProvider {
|
||||||
subGroup.setParent(null);
|
subGroup.setParent(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void preRemove(RealmModel realm, RoleModel role) {
|
public void preRemove(RealmModel realm, RoleModel role) {
|
||||||
LOG.tracef("preRemove(%s, %s)%s", realm, role, getShortStackTrace());
|
LOG.tracef("preRemove(%s, %s)%s", realm, role, getShortStackTrace());
|
||||||
ModelCriteriaBuilder<GroupModel> mcb = groupStore.createCriteriaBuilder()
|
ModelCriteriaBuilder<GroupModel> mcb = groupStore.createCriteriaBuilder()
|
||||||
|
|
|
@ -17,34 +17,70 @@
|
||||||
|
|
||||||
package org.keycloak.models.map.group;
|
package org.keycloak.models.map.group;
|
||||||
|
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
import org.keycloak.models.GroupProvider;
|
import org.keycloak.models.GroupProvider;
|
||||||
import org.keycloak.models.GroupProviderFactory;
|
import org.keycloak.models.GroupProviderFactory;
|
||||||
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.RoleContainerModel;
|
||||||
|
import org.keycloak.models.RoleContainerModel.RoleRemovedEvent;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
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.MapStorage;
|
||||||
import org.keycloak.models.map.storage.MapStorageProvider;
|
import org.keycloak.models.map.storage.MapStorageProvider;
|
||||||
|
|
||||||
|
import org.keycloak.provider.ProviderEvent;
|
||||||
|
import org.keycloak.provider.ProviderEventListener;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author mhajas
|
* @author mhajas
|
||||||
*/
|
*/
|
||||||
public class MapGroupProviderFactory extends AbstractMapProviderFactory<GroupProvider> implements GroupProviderFactory {
|
public class MapGroupProviderFactory extends AbstractMapProviderFactory<GroupProvider> implements GroupProviderFactory, ProviderEventListener {
|
||||||
|
|
||||||
private MapStorage<UUID, MapGroupEntity, GroupModel> store;
|
private MapStorage<UUID, MapGroupEntity, GroupModel> store;
|
||||||
|
|
||||||
|
private Runnable onClose;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void postInit(KeycloakSessionFactory factory) {
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
MapStorageProvider sp = (MapStorageProvider) factory.getProviderFactory(MapStorageProvider.class);
|
MapStorageProvider sp = (MapStorageProvider) factory.getProviderFactory(MapStorageProvider.class);
|
||||||
this.store = sp.getStorage("groups", UUID.class, MapGroupEntity.class, GroupModel.class);
|
this.store = sp.getStorage("groups", UUID.class, MapGroupEntity.class, GroupModel.class);
|
||||||
|
|
||||||
|
factory.register(this);
|
||||||
|
onClose = () -> factory.unregister(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GroupProvider create(KeycloakSession session) {
|
public MapGroupProvider create(KeycloakSession session) {
|
||||||
return new MapGroupProvider(session, store);
|
return new MapGroupProvider(session, store);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
super.close();
|
||||||
|
onClose.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
create(e.getKeycloakSession()).preRemove(realm, role);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,6 @@ import java.util.Comparator;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import org.keycloak.models.map.storage.MapStorage;
|
import org.keycloak.models.map.storage.MapStorage;
|
||||||
|
@ -67,7 +66,7 @@ public class MapRoleProvider implements RoleProvider {
|
||||||
public MapRoleProvider(KeycloakSession session, MapStorage<UUID, MapRoleEntity, RoleModel> roleStore) {
|
public MapRoleProvider(KeycloakSession session, MapStorage<UUID, MapRoleEntity, RoleModel> roleStore) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.roleStore = roleStore;
|
this.roleStore = roleStore;
|
||||||
this.tx = roleStore.createTransaction();
|
this.tx = roleStore.createTransaction(session);
|
||||||
session.getTransactionManager().enlist(tx);
|
session.getTransactionManager().enlist(tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,8 +209,6 @@ public class MapRoleProvider implements RoleProvider {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
session.groups().preRemove(realm, role);
|
|
||||||
|
|
||||||
// TODO: Sending an event should be extracted to store layer
|
// TODO: Sending an event should be extracted to store layer
|
||||||
session.getKeycloakSessionFactory().publish(new RoleContainerModel.RoleRemovedEvent() {
|
session.getKeycloakSessionFactory().publish(new RoleContainerModel.RoleRemovedEvent() {
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -62,6 +62,7 @@ public class MapFieldPredicates {
|
||||||
static {
|
static {
|
||||||
put(CLIENT_PREDICATES, ClientModel.SearchableFields.REALM_ID, AbstractClientEntity::getRealmId);
|
put(CLIENT_PREDICATES, ClientModel.SearchableFields.REALM_ID, AbstractClientEntity::getRealmId);
|
||||||
put(CLIENT_PREDICATES, ClientModel.SearchableFields.CLIENT_ID, AbstractClientEntity::getClientId);
|
put(CLIENT_PREDICATES, ClientModel.SearchableFields.CLIENT_ID, AbstractClientEntity::getClientId);
|
||||||
|
put(CLIENT_PREDICATES, ClientModel.SearchableFields.SCOPE_MAPPING_ROLE, MapFieldPredicates::checkScopeMappingRole);
|
||||||
|
|
||||||
put(CLIENT_SCOPE_PREDICATES, ClientScopeModel.SearchableFields.REALM_ID, AbstractClientScopeEntity::getRealmId);
|
put(CLIENT_SCOPE_PREDICATES, ClientScopeModel.SearchableFields.REALM_ID, AbstractClientScopeEntity::getRealmId);
|
||||||
put(CLIENT_SCOPE_PREDICATES, ClientScopeModel.SearchableFields.NAME, AbstractClientScopeEntity::getName);
|
put(CLIENT_SCOPE_PREDICATES, ClientScopeModel.SearchableFields.NAME, AbstractClientScopeEntity::getName);
|
||||||
|
@ -136,6 +137,13 @@ public class MapFieldPredicates {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static MapModelCriteriaBuilder<Object, AbstractClientEntity<Object>, ClientModel> checkScopeMappingRole(MapModelCriteriaBuilder<Object, AbstractClientEntity<Object>, ClientModel> mcb, Operator op, Object[] values) {
|
||||||
|
String roleIdS = ensureEqSingleValue(ClientModel.SearchableFields.SCOPE_MAPPING_ROLE, "role_id", op, values);
|
||||||
|
Function<AbstractClientEntity<Object>, ?> getter;
|
||||||
|
getter = ce -> ce.getScopeMappings().contains(roleIdS);
|
||||||
|
return mcb.fieldCompare(Boolean.TRUE::equals, getter);
|
||||||
|
}
|
||||||
|
|
||||||
private static MapModelCriteriaBuilder<Object, AbstractGroupEntity<Object>, GroupModel> checkGrantedGroupRole(MapModelCriteriaBuilder<Object, AbstractGroupEntity<Object>, GroupModel> mcb, Operator op, Object[] values) {
|
private static MapModelCriteriaBuilder<Object, AbstractGroupEntity<Object>, GroupModel> checkGrantedGroupRole(MapModelCriteriaBuilder<Object, AbstractGroupEntity<Object>, GroupModel> mcb, Operator op, Object[] values) {
|
||||||
String roleIdS = ensureEqSingleValue(GroupModel.SearchableFields.ASSIGNED_ROLE, "role_id", op, values);
|
String roleIdS = ensureEqSingleValue(GroupModel.SearchableFields.ASSIGNED_ROLE, "role_id", op, values);
|
||||||
Function<AbstractGroupEntity<Object>, ?> getter;
|
Function<AbstractGroupEntity<Object>, ?> getter;
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.models.map.storage;
|
package org.keycloak.models.map.storage;
|
||||||
|
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.map.common.AbstractEntity;
|
import org.keycloak.models.map.common.AbstractEntity;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
@ -127,6 +128,6 @@ public interface MapStorage<K, V extends AbstractEntity<K>, M> {
|
||||||
*
|
*
|
||||||
* @return See description.
|
* @return See description.
|
||||||
*/
|
*/
|
||||||
public MapKeycloakTransaction<K, V, M> createTransaction();
|
public MapKeycloakTransaction<K, V, M> createTransaction(KeycloakSession session);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.models.map.storage.chm;
|
package org.keycloak.models.map.storage.chm;
|
||||||
|
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.map.storage.MapModelCriteriaBuilder;
|
import org.keycloak.models.map.storage.MapModelCriteriaBuilder;
|
||||||
import org.keycloak.models.map.common.AbstractEntity;
|
import org.keycloak.models.map.common.AbstractEntity;
|
||||||
import org.keycloak.models.map.storage.MapFieldPredicates;
|
import org.keycloak.models.map.storage.MapFieldPredicates;
|
||||||
|
@ -101,8 +102,10 @@ public class ConcurrentHashMapStorage<K, V extends AbstractEntity<K>, M> impleme
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MapKeycloakTransaction<K, V, M> createTransaction() {
|
@SuppressWarnings("unchecked")
|
||||||
return new MapKeycloakTransaction<>(this);
|
public MapKeycloakTransaction<K, V, M> createTransaction(KeycloakSession session) {
|
||||||
|
MapKeycloakTransaction sessionTransaction = session.getAttribute("map-transaction-" + hashCode(), MapKeycloakTransaction.class);
|
||||||
|
return sessionTransaction == null ? new MapKeycloakTransaction<>(this) : (MapKeycloakTransaction<K, V, M>) sessionTransaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -83,7 +83,7 @@ public class MapUserProvider implements UserProvider.Streams, UserCredentialStor
|
||||||
public MapUserProvider(KeycloakSession session, MapStorage<UUID, MapUserEntity, UserModel> store) {
|
public MapUserProvider(KeycloakSession session, MapStorage<UUID, MapUserEntity, UserModel> store) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.userStore = store;
|
this.userStore = store;
|
||||||
this.tx = userStore.createTransaction();
|
this.tx = userStore.createTransaction(session);
|
||||||
session.getTransactionManager().enlist(tx);
|
session.getTransactionManager().enlist(tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@ public interface ClientModel extends ClientScopeModel, RoleContainerModel, Prot
|
||||||
public static final SearchableModelField<ClientModel> ID = new SearchableModelField<>("id", String.class);
|
public static final SearchableModelField<ClientModel> ID = new SearchableModelField<>("id", String.class);
|
||||||
public static final SearchableModelField<ClientModel> REALM_ID = new SearchableModelField<>("realmId", String.class);
|
public static final SearchableModelField<ClientModel> REALM_ID = new SearchableModelField<>("realmId", String.class);
|
||||||
public static final SearchableModelField<ClientModel> CLIENT_ID = new SearchableModelField<>("clientId", String.class);
|
public static final SearchableModelField<ClientModel> CLIENT_ID = new SearchableModelField<>("clientId", String.class);
|
||||||
|
public static final SearchableModelField<ClientModel> SCOPE_MAPPING_ROLE = new SearchableModelField<>("scopeMappingRole", String.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ClientCreationEvent extends ProviderEvent {
|
interface ClientCreationEvent extends ProviderEvent {
|
||||||
|
|
|
@ -287,12 +287,4 @@ public interface GroupProvider extends Provider, GroupLookupProvider {
|
||||||
* @throws ModelDuplicateException If there is already a top level group name with the same name
|
* @throws ModelDuplicateException If there is already a top level group name with the same name
|
||||||
*/
|
*/
|
||||||
void addTopLevelGroup(RealmModel realm, GroupModel subGroup);
|
void addTopLevelGroup(RealmModel realm, GroupModel subGroup);
|
||||||
|
|
||||||
/**
|
|
||||||
* This function is called when a role is removed; this serves for removing references from groups to roles.
|
|
||||||
*
|
|
||||||
* @param realm Realm.
|
|
||||||
* @param role Role which will be removed.
|
|
||||||
*/
|
|
||||||
void preRemove(RealmModel realm, RoleModel role);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,11 +124,6 @@ public class GroupStorageManager extends AbstractStorageManager<GroupStorageProv
|
||||||
session.groupLocalStorage().addTopLevelGroup(realm, subGroup);
|
session.groupLocalStorage().addTopLevelGroup(realm, subGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void preRemove(RealmModel realm, RoleModel role) {
|
|
||||||
session.groupLocalStorage().preRemove(realm, role);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
|
|
||||||
|
|
|
@ -595,6 +595,57 @@ public class ClientTest extends AbstractAdminTest {
|
||||||
Assert.assertNames(scopesResource.clientLevel(accountMgmtId).listEffective());
|
Assert.assertNames(scopesResource.clientLevel(accountMgmtId).listEffective());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void scopesRoleRemoval() {
|
||||||
|
// clientA to test scope mappins
|
||||||
|
Response response = realm.clients().create(ClientBuilder.create().clientId("clientA").fullScopeEnabled(false).build());
|
||||||
|
String idA = ApiUtil.getCreatedId(response);
|
||||||
|
getCleanup().addClientUuid(idA);
|
||||||
|
response.close();
|
||||||
|
assertAdminEvents.poll();
|
||||||
|
|
||||||
|
// clientB to create a client role for clientA
|
||||||
|
response = realm.clients().create(ClientBuilder.create().clientId("clientB").fullScopeEnabled(false).build());
|
||||||
|
String idB = ApiUtil.getCreatedId(response);
|
||||||
|
getCleanup().addClientUuid(idB);
|
||||||
|
response.close();
|
||||||
|
assertAdminEvents.poll();
|
||||||
|
|
||||||
|
RoleMappingResource scopesResource = realm.clients().get(idA).getScopeMappings();
|
||||||
|
|
||||||
|
// create a realm role and a role in clientB
|
||||||
|
RoleRepresentation realmRoleRep = RoleBuilder.create().name("realm-role").build();
|
||||||
|
realm.roles().create(realmRoleRep);
|
||||||
|
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.roleResourcePath(realmRoleRep.getName()), realmRoleRep, ResourceType.REALM_ROLE);
|
||||||
|
RoleRepresentation clientBRoleRep = RoleBuilder.create().name("clientB-role").build();
|
||||||
|
realm.clients().get(idB).roles().create(clientBRoleRep);
|
||||||
|
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.clientRoleResourcePath(idB, clientBRoleRep.getName()), clientBRoleRep, ResourceType.CLIENT_ROLE);
|
||||||
|
|
||||||
|
// assing to clientA both roles to the scope mappings
|
||||||
|
realmRoleRep = realm.roles().get(realmRoleRep.getName()).toRepresentation();
|
||||||
|
clientBRoleRep = realm.clients().get(idB).roles().get(clientBRoleRep.getName()).toRepresentation();
|
||||||
|
scopesResource.realmLevel().add(Collections.singletonList(realmRoleRep));
|
||||||
|
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.clientScopeMappingsRealmLevelPath(idA), Collections.singletonList(realmRoleRep), ResourceType.REALM_SCOPE_MAPPING);
|
||||||
|
scopesResource.clientLevel(idB).add(Collections.singletonList(clientBRoleRep));
|
||||||
|
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.clientScopeMappingsClientLevelPath(idA, idB), Collections.singletonList(clientBRoleRep), ResourceType.CLIENT_SCOPE_MAPPING);
|
||||||
|
|
||||||
|
// assert the roles are there
|
||||||
|
Assert.assertNames(scopesResource.realmLevel().listAll(), realmRoleRep.getName());
|
||||||
|
Assert.assertNames(scopesResource.clientLevel(idB).listAll(), clientBRoleRep.getName());
|
||||||
|
|
||||||
|
// delete realm role and check everything is refreshed ok
|
||||||
|
realm.roles().deleteRole(realmRoleRep.getName());
|
||||||
|
assertAdminEvents.assertEvent(realmId, OperationType.DELETE, AdminEventPaths.roleResourcePath(realmRoleRep.getName()), ResourceType.REALM_ROLE);
|
||||||
|
Assert.assertNames(scopesResource.realmLevel().listAll());
|
||||||
|
Assert.assertNames(scopesResource.clientLevel(idB).listAll(), clientBRoleRep.getName());
|
||||||
|
|
||||||
|
// delete client role and check everything is refreshed ok
|
||||||
|
realm.clients().get(idB).roles().deleteRole(clientBRoleRep.getName());
|
||||||
|
assertAdminEvents.assertEvent(realmId, OperationType.DELETE, AdminEventPaths.clientRoleResourcePath(idB, clientBRoleRep.getName()), ResourceType.CLIENT_ROLE);
|
||||||
|
Assert.assertNames(scopesResource.realmLevel().listAll());
|
||||||
|
Assert.assertNames(scopesResource.clientLevel(idB).listAll());
|
||||||
|
}
|
||||||
|
|
||||||
public void protocolMappersTest(String clientDbId, ProtocolMappersResource mappersResource) {
|
public void protocolMappersTest(String clientDbId, ProtocolMappersResource mappersResource) {
|
||||||
// assert default mappers found
|
// assert default mappers found
|
||||||
List<ProtocolMapperRepresentation> protocolMappers = mappersResource.getMappers();
|
List<ProtocolMapperRepresentation> protocolMappers = mappersResource.getMappers();
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 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.testsuite.model;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.ClientProvider;
|
||||||
|
import org.keycloak.models.Constants;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RealmProvider;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.RoleProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author rmartinc
|
||||||
|
*/
|
||||||
|
@RequireProvider(RealmProvider.class)
|
||||||
|
@RequireProvider(ClientProvider.class)
|
||||||
|
@RequireProvider(RoleProvider.class)
|
||||||
|
public class ClientModelTest extends KeycloakModelTest {
|
||||||
|
|
||||||
|
private String realmId;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createEnvironment(KeycloakSession s) {
|
||||||
|
RealmModel realm = s.realms().createRealm("realm");
|
||||||
|
realm.setDefaultRole(s.roles().addRealmRole(realm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + realm.getName()));
|
||||||
|
this.realmId = realm.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cleanEnvironment(KeycloakSession s) {
|
||||||
|
s.realms().removeRealm(realmId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testScopeMappingRoleRemoval() {
|
||||||
|
// create two clients, one realm role and one client role and assign both to one of the clients
|
||||||
|
inComittedTransaction(1, (session , i) -> {
|
||||||
|
final RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
ClientModel client1 = session.clients().addClient(realm, "client1");
|
||||||
|
ClientModel client2 = session.clients().addClient(realm, "client2");
|
||||||
|
RoleModel realmRole = session.roles().addRealmRole(realm, "realm-role");
|
||||||
|
RoleModel client2Role = session.roles().addClientRole(client2, "client2-role");
|
||||||
|
client1.addScopeMapping(realmRole);
|
||||||
|
client1.addScopeMapping(client2Role);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// check everything is OK
|
||||||
|
inComittedTransaction(1, (session, i) -> {
|
||||||
|
final RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
final ClientModel client1 = session.clients().getClientByClientId(realm, "client1");
|
||||||
|
assertThat(client1.getScopeMappingsStream().count(), is(2L));
|
||||||
|
assertThat(client1.getScopeMappingsStream().filter(r -> r.getName().equals("realm-role")).count(), is(1L));
|
||||||
|
assertThat(client1.getScopeMappingsStream().filter(r -> r.getName().equals("client2-role")).count(), is(1L));
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// remove the realm role
|
||||||
|
inComittedTransaction(1, (session, i) -> {
|
||||||
|
final RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
final RoleModel role = session.roles().getRealmRole(realm, "realm-role");
|
||||||
|
session.roles().removeRole(role);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// check it is removed
|
||||||
|
inComittedTransaction(1, (session, i) -> {
|
||||||
|
final RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
final ClientModel client1 = session.clients().getClientByClientId(realm, "client1");
|
||||||
|
assertThat(client1.getScopeMappingsStream().count(), is(1L));
|
||||||
|
assertThat(client1.getScopeMappingsStream().filter(r -> r.getName().equals("client2-role")).count(), is(1L));
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// remove client role
|
||||||
|
inComittedTransaction(1, (session, i) -> {
|
||||||
|
final RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
final ClientModel client2 = session.clients().getClientByClientId(realm, "client2");
|
||||||
|
final RoleModel role = session.roles().getClientRole(client2, "client2-role");
|
||||||
|
session.roles().removeRole(role);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// check both clients are removed
|
||||||
|
inComittedTransaction(1, (session, i) -> {
|
||||||
|
final RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
final ClientModel client1 = session.clients().getClientByClientId(realm, "client1");
|
||||||
|
assertThat(client1.getScopeMappingsStream().count(), is(0L));
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// remove clients
|
||||||
|
inComittedTransaction(1, (session , i) -> {
|
||||||
|
final RealmModel realm = session.realms().getRealm(realmId);
|
||||||
|
final ClientModel client1 = session.clients().getClientByClientId(realm, "client1");
|
||||||
|
final ClientModel client2 = session.clients().getClientByClientId(realm, "client2");
|
||||||
|
session.clients().removeClient(realm, client1.getId());
|
||||||
|
session.clients().removeClient(realm, client2.getId());
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue