KEYCLOAK-17215 Slowness issue while hitting /auth/admin/realms/$REALM/clients?viewableOnly=true after DELETE a role

This commit is contained in:
rmartinc 2021-03-03 12:16:16 +01:00 committed by Hynek Mlnařík
parent c3b9c66941
commit 0a0caa07d6
19 changed files with 332 additions and 51 deletions

View file

@ -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();

View file

@ -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

View file

@ -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);
}
}
} }

View file

@ -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);
} }

View file

@ -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() {

View file

@ -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);
}
}
} }

View file

@ -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);
} }

View file

@ -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()

View file

@ -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);
}
}
} }

View file

@ -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

View file

@ -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;

View file

@ -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);
} }

View file

@ -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;
} }

View file

@ -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);
} }

View file

@ -38,9 +38,10 @@ public interface ClientModel extends ClientScopeModel, RoleContainerModel, Prot
String X509CERTIFICATE = "X509Certificate"; String X509CERTIFICATE = "X509Certificate";
public static class SearchableFields { public static class SearchableFields {
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 {

View file

@ -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);
} }

View file

@ -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() {

View file

@ -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();

View file

@ -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;
});
}
}