[KEYCLOAK-2538] - groups pagination and group search

This commit is contained in:
Levente NAGY 2017-06-06 18:32:48 +02:00
parent 9be9e30ad6
commit c4da7637d6
11 changed files with 206 additions and 231 deletions

View file

@ -20,12 +20,7 @@ package org.keycloak.admin.client.resource;
import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.representations.idm.GroupRepresentation; import org.keycloak.representations.idm.GroupRepresentation;
import javax.ws.rs.Consumes; import javax.ws.rs.*;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.util.List; import java.util.List;
@ -35,11 +30,43 @@ import java.util.List;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public interface GroupsResource { public interface GroupsResource {
/**
* Get all groups.
* @return A list containing all groups.
*/
@GET @GET
@NoCache @NoCache
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
List<GroupRepresentation> groups(); List<GroupRepresentation> groups();
/**
* Get groups by pagination params.
* @param first index of the first element
* @param max max number of occurrences
* @return A list containing the slice of all groups.
*/
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
List<GroupRepresentation> groups(@QueryParam("first") Integer first, @QueryParam("max") Integer max);
/**
* Get groups by pagination params.
* @param search max number of occurrences
* @param first index of the first element
* @param max max number of occurrences
* @return A list containing the slice of all groups.
*/
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
List<GroupRepresentation> groups(@QueryParam("search") String search,
@QueryParam("first") Integer first,
@QueryParam("max") Integer max);
/** /**
* create or add a top level realm groupSet or create child. This will update the group and set the parent if it exists. Create it and set the parent * create or add a top level realm groupSet or create child. This will update the group and set the parent if it exists. Create it and set the parent
* if the group doesn't exist. * if the group doesn't exist.

View file

@ -20,32 +20,12 @@ package org.keycloak.models.cache.infinispan;
import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.common.enums.SslRequired; import org.keycloak.common.enums.SslRequired;
import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentModel;
import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.*;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientTemplateModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.OTPPolicy;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel;
import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.cache.CachedRealmModel; import org.keycloak.models.cache.CachedRealmModel;
import org.keycloak.models.cache.infinispan.entities.CachedRealm; import org.keycloak.models.cache.infinispan.entities.CachedRealm;
import org.keycloak.storage.UserStorageProvider; import org.keycloak.storage.UserStorageProvider;
import java.util.ArrayList; import java.util.*;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
/** /**
@ -328,7 +308,7 @@ public class RealmAdapter implements CachedRealmModel {
getDelegateForUpdate(); getDelegateForUpdate();
updated.setLoginWithEmailAllowed(loginWithEmailAllowed); updated.setLoginWithEmailAllowed(loginWithEmailAllowed);
} }
@Override @Override
public boolean isDuplicateEmailsAllowed() { public boolean isDuplicateEmailsAllowed() {
if (isUpdated()) return updated.isDuplicateEmailsAllowed(); if (isUpdated()) return updated.isDuplicateEmailsAllowed();
@ -797,9 +777,9 @@ public class RealmAdapter implements CachedRealmModel {
@Override @Override
public void setEnabledEventTypes(Set<String> enabledEventTypes) { public void setEnabledEventTypes(Set<String> enabledEventTypes) {
getDelegateForUpdate(); getDelegateForUpdate();
updated.setEnabledEventTypes(enabledEventTypes); updated.setEnabledEventTypes(enabledEventTypes);
} }
@Override @Override
public boolean isAdminEventsEnabled() { public boolean isAdminEventsEnabled() {
if (isUpdated()) return updated.isAdminEventsEnabled(); if (isUpdated()) return updated.isAdminEventsEnabled();
@ -823,7 +803,7 @@ public class RealmAdapter implements CachedRealmModel {
getDelegateForUpdate(); getDelegateForUpdate();
updated.setAdminEventsDetailsEnabled(enabled); updated.setAdminEventsDetailsEnabled(enabled);
} }
@Override @Override
public ClientModel getMasterAdminClient() { public ClientModel getMasterAdminClient() {
return cached.getMasterAdminClient()==null ? null : cacheSession.getRealm(Config.getAdminRealm()).getClientById(cached.getMasterAdminClient()); return cached.getMasterAdminClient()==null ? null : cacheSession.getRealm(Config.getAdminRealm()).getClientById(cached.getMasterAdminClient());
@ -1225,6 +1205,16 @@ public class RealmAdapter implements CachedRealmModel {
return cacheSession.getTopLevelGroups(this); return cacheSession.getTopLevelGroups(this);
} }
@Override
public List<GroupModel> getTopLevelGroups(Integer first, Integer max) {
return cacheSession.getTopLevelGroups(this, first, max);
}
@Override
public List<GroupModel> searchForGroupByName(String search, Integer first, Integer max) {
return cacheSession.searchForGroupByName(this, search, first, max);
}
@Override @Override
public boolean removeGroup(GroupModel group) { public boolean removeGroup(GroupModel group) {
return cacheSession.removeGroup(this, group); return cacheSession.removeGroup(this, group);

View file

@ -19,50 +19,15 @@ package org.keycloak.models.cache.infinispan;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.cluster.ClusterProvider; import org.keycloak.cluster.ClusterProvider;
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
import org.keycloak.migration.MigrationModel; import org.keycloak.migration.MigrationModel;
import org.keycloak.models.ClientModel; import org.keycloak.models.*;
import org.keycloak.models.ClientTemplateModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakTransaction;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmProvider;
import org.keycloak.models.RoleModel;
import org.keycloak.models.cache.CacheRealmProvider; import org.keycloak.models.cache.CacheRealmProvider;
import org.keycloak.models.cache.CachedRealmModel; import org.keycloak.models.cache.CachedRealmModel;
import org.keycloak.models.cache.infinispan.entities.CachedClient; import org.keycloak.models.cache.infinispan.entities.*;
import org.keycloak.models.cache.infinispan.entities.CachedClientRole; import org.keycloak.models.cache.infinispan.events.*;
import org.keycloak.models.cache.infinispan.entities.CachedClientTemplate;
import org.keycloak.models.cache.infinispan.entities.CachedGroup;
import org.keycloak.models.cache.infinispan.entities.CachedRealm;
import org.keycloak.models.cache.infinispan.entities.CachedRealmRole;
import org.keycloak.models.cache.infinispan.entities.CachedRole;
import org.keycloak.models.cache.infinispan.entities.ClientListQuery;
import org.keycloak.models.cache.infinispan.entities.GroupListQuery;
import org.keycloak.models.cache.infinispan.entities.RealmListQuery;
import org.keycloak.models.cache.infinispan.entities.RoleListQuery;
import org.keycloak.models.cache.infinispan.events.ClientAddedEvent;
import org.keycloak.models.cache.infinispan.events.ClientRemovedEvent;
import org.keycloak.models.cache.infinispan.events.ClientTemplateEvent;
import org.keycloak.models.cache.infinispan.events.ClientUpdatedEvent;
import org.keycloak.models.cache.infinispan.events.GroupAddedEvent;
import org.keycloak.models.cache.infinispan.events.GroupMovedEvent;
import org.keycloak.models.cache.infinispan.events.GroupRemovedEvent;
import org.keycloak.models.cache.infinispan.events.GroupUpdatedEvent;
import org.keycloak.models.cache.infinispan.events.RealmRemovedEvent;
import org.keycloak.models.cache.infinispan.events.RealmUpdatedEvent;
import org.keycloak.models.cache.infinispan.events.RoleAddedEvent;
import org.keycloak.models.cache.infinispan.events.RoleRemovedEvent;
import org.keycloak.models.cache.infinispan.events.RoleUpdatedEvent;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.HashMap; import java.util.*;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/** /**
@ -912,6 +877,47 @@ public class RealmCacheSession implements CacheRealmProvider {
return list; return list;
} }
@Override
public List<GroupModel> getTopLevelGroups(RealmModel realm, Integer first, Integer max) {
String cacheKey = getTopGroupsQueryCacheKey(realm.getId() + first + max);
boolean queryDB = invalidations.contains(cacheKey) || listInvalidations.contains(realm.getId() + first + max);
if (queryDB) {
return getDelegate().getTopLevelGroups(realm, first, max);
}
GroupListQuery query = cache.get(cacheKey, GroupListQuery.class);
if (Objects.nonNull(query)) {
logger.tracev("getTopLevelGroups cache hit: {0}", realm.getName());
}
if (Objects.isNull(query)) {
Long loaded = cache.getCurrentRevision(cacheKey);
List<GroupModel> model = getDelegate().getTopLevelGroups(realm, first, max);
if (model == null) return null;
Set<String> ids = new HashSet<>();
for (GroupModel client : model) ids.add(client.getId());
query = new GroupListQuery(loaded, cacheKey, realm, ids);
logger.tracev("adding realm getTopLevelGroups cache miss: realm {0} key {1}", realm.getName(), cacheKey);
cache.addRevisioned(query, startupRevision);
return model;
}
List<GroupModel> list = new LinkedList<>();
for (String id : query.getGroups()) {
GroupModel group = session.realms().getGroupById(id, realm);
if (Objects.isNull(group)) {
invalidations.add(cacheKey);
return getDelegate().getTopLevelGroups(realm);
}
list.add(group);
}
return list;
}
@Override
public List<GroupModel> searchForGroupByName(RealmModel realm, String search, Integer first, Integer max) {
return getDelegate().searchForGroupByName(realm, search, first, max);
}
@Override @Override
public boolean removeGroup(RealmModel realm, GroupModel group) { public boolean removeGroup(RealmModel realm, GroupModel group) {
invalidateGroup(group.getId(), realm.getId(), true); invalidateGroup(group.getId(), realm.getId(), true);

View file

@ -20,29 +20,13 @@ package org.keycloak.models.jpa;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.connections.jpa.util.JpaUtils; import org.keycloak.connections.jpa.util.JpaUtils;
import org.keycloak.migration.MigrationModel; import org.keycloak.migration.MigrationModel;
import org.keycloak.models.ClientModel; import org.keycloak.models.*;
import org.keycloak.models.ClientTemplateModel; import org.keycloak.models.jpa.entities.*;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmProvider;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.jpa.entities.ClientEntity;
import org.keycloak.models.jpa.entities.ClientTemplateEntity;
import org.keycloak.models.jpa.entities.GroupEntity;
import org.keycloak.models.jpa.entities.RealmEntity;
import org.keycloak.models.jpa.entities.RoleEntity;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.TypedQuery; import javax.persistence.TypedQuery;
import java.util.ArrayList; import java.util.*;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -349,6 +333,24 @@ public class JpaRealmProvider implements RealmProvider {
Collectors.toList(), Collections::unmodifiableList)); Collectors.toList(), Collections::unmodifiableList));
} }
@Override
public List<GroupModel> getTopLevelGroups(RealmModel realm, Integer first, Integer max) {
List<String> groupIds = em.createNamedQuery("getTopLevelGroupIds", String.class)
.setParameter("realm", realm.getId())
.setFirstResult(first)
.setMaxResults(max)
.getResultList();
List<GroupModel> list = new ArrayList<>();
if(Objects.nonNull(groupIds) && !groupIds.isEmpty()) {
for (String id : groupIds) {
GroupModel group = getGroupById(id, realm);
list.add(group);
}
}
return Collections.unmodifiableList(list);
}
@Override @Override
public boolean removeGroup(RealmModel realm, GroupModel group) { public boolean removeGroup(RealmModel realm, GroupModel group) {
if (group == null) { if (group == null) {
@ -519,4 +521,21 @@ public class JpaRealmProvider implements RealmProvider {
ClientTemplateAdapter adapter = new ClientTemplateAdapter(realm, em, session, app); ClientTemplateAdapter adapter = new ClientTemplateAdapter(realm, em, session, app);
return adapter; return adapter;
} }
@Override
public List<GroupModel> searchForGroupByName(RealmModel realm, String search, Integer first, Integer max) {
TypedQuery<String> query = em.createNamedQuery("getGroupIdsByNameContaining", String.class)
.setParameter("realm", realm.getId())
.setParameter("search", search);
if(Objects.nonNull(first) && Objects.nonNull(max)) {
query= query.setFirstResult(first).setMaxResults(max);
}
List<String> groups = query.getResultList();
if (Objects.isNull(groups)) return Collections.EMPTY_LIST;
List<GroupModel> list = new LinkedList<>();
for (String id : groups) {
list.add(session.realms().getGroupById(id, realm));
}
return Collections.unmodifiableList(list);
}
} }

View file

@ -22,53 +22,13 @@ import org.keycloak.common.enums.SslRequired;
import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentFactory; import org.keycloak.component.ComponentFactory;
import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentModel;
import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.*;
import org.keycloak.models.AuthenticationFlowModel; import org.keycloak.models.jpa.entities.*;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientTemplateModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelException;
import org.keycloak.models.OTPPolicy;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel;
import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.jpa.entities.AuthenticationExecutionEntity;
import org.keycloak.models.jpa.entities.AuthenticationFlowEntity;
import org.keycloak.models.jpa.entities.AuthenticatorConfigEntity;
import org.keycloak.models.jpa.entities.ClientEntity;
import org.keycloak.models.jpa.entities.ClientTemplateEntity;
import org.keycloak.models.jpa.entities.ComponentConfigEntity;
import org.keycloak.models.jpa.entities.ComponentEntity;
import org.keycloak.models.jpa.entities.GroupEntity;
import org.keycloak.models.jpa.entities.IdentityProviderEntity;
import org.keycloak.models.jpa.entities.IdentityProviderMapperEntity;
import org.keycloak.models.jpa.entities.RealmAttributeEntity;
import org.keycloak.models.jpa.entities.RealmAttributes;
import org.keycloak.models.jpa.entities.RealmEntity;
import org.keycloak.models.jpa.entities.RequiredActionProviderEntity;
import org.keycloak.models.jpa.entities.RequiredCredentialEntity;
import org.keycloak.models.jpa.entities.RoleEntity;
import org.keycloak.models.utils.ComponentUtil; import org.keycloak.models.utils.ComponentUtil;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import java.util.ArrayList; import java.util.*;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -360,7 +320,7 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
realm.setVerifyEmail(verifyEmail); realm.setVerifyEmail(verifyEmail);
em.flush(); em.flush();
} }
@Override @Override
public boolean isLoginWithEmailAllowed() { public boolean isLoginWithEmailAllowed() {
return realm.isLoginWithEmailAllowed(); return realm.isLoginWithEmailAllowed();
@ -372,7 +332,7 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
if (loginWithEmailAllowed) realm.setDuplicateEmailsAllowed(false); if (loginWithEmailAllowed) realm.setDuplicateEmailsAllowed(false);
em.flush(); em.flush();
} }
@Override @Override
public boolean isDuplicateEmailsAllowed() { public boolean isDuplicateEmailsAllowed() {
return realm.isDuplicateEmailsAllowed(); return realm.isDuplicateEmailsAllowed();
@ -1719,6 +1679,16 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
return session.realms().getTopLevelGroups(this); return session.realms().getTopLevelGroups(this);
} }
@Override
public List<GroupModel> getTopLevelGroups(Integer first, Integer max) {
return session.realms().getTopLevelGroups(this, first, max);
}
@Override
public List<GroupModel> searchForGroupByName(String search, Integer first, Integer max) {
return session.realms().searchForGroupByName(this, search, first, max);
}
@Override @Override
public boolean removeGroup(GroupModel group) { public boolean removeGroup(GroupModel group) {
return session.realms().removeGroup(this, group); return session.realms().removeGroup(this, group);

View file

@ -17,19 +17,7 @@
package org.keycloak.models.jpa.entities; package org.keycloak.models.jpa.entities;
import javax.persistence.Access; import javax.persistence.*;
import javax.persistence.AccessType;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@ -39,6 +27,8 @@ import java.util.Collection;
*/ */
@NamedQueries({ @NamedQueries({
@NamedQuery(name="getGroupIdsByParent", query="select u.id from GroupEntity u where u.parent = :parent"), @NamedQuery(name="getGroupIdsByParent", query="select u.id from GroupEntity u where u.parent = :parent"),
@NamedQuery(name="getGroupIdsByNameContaining", query="select u.id from GroupEntity u where u.realm.id = :realm and u.name like concat('%',:search,'%') order by u.name ASC"),
@NamedQuery(name="getTopLevelGroupIds", query="select u.id from GroupEntity u where u.parent is null and u.realm.id = :realm")
}) })
@Entity @Entity
@Table(name="KEYCLOAK_GROUP") @Table(name="KEYCLOAK_GROUP")

View file

@ -18,7 +18,6 @@ package org.keycloak.models.cache;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.provider.ProviderEvent; import org.keycloak.provider.ProviderEvent;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;

View file

@ -17,17 +17,6 @@
package org.keycloak.models.utils; package org.keycloak.models.utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.Resource;
@ -42,60 +31,15 @@ import org.keycloak.credential.CredentialModel;
import org.keycloak.events.Event; import org.keycloak.events.Event;
import org.keycloak.events.admin.AdminEvent; import org.keycloak.events.admin.AdminEvent;
import org.keycloak.events.admin.AuthDetails; import org.keycloak.events.admin.AuthDetails;
import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.*;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientTemplateModel;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelException;
import org.keycloak.models.OTPPolicy;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel;
import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserConsentModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.idm.AdminEventRepresentation; import org.keycloak.representations.idm.*;
import org.keycloak.representations.idm.AuthDetailsRepresentation; import org.keycloak.representations.idm.authorization.*;
import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ClientTemplateRepresentation;
import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.ConfigPropertyRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserConsentRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.UserSessionRepresentation;
import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceOwnerRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.storage.StorageId; import org.keycloak.storage.StorageId;
import java.util.*;
import java.util.stream.Collectors;
/** /**
* @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 $
@ -147,10 +91,32 @@ public class ModelToRepresentation {
return rep; return rep;
} }
public static List<GroupRepresentation> searchForGroupByName(RealmModel realm, String search, Integer first, Integer max) {
List<GroupRepresentation> result = new LinkedList<>();
List<GroupModel> groups = realm.searchForGroupByName(search, first, max);
if (Objects.isNull(groups)) return result;
for (GroupModel group : groups) {
GroupRepresentation rep = toGroupHierarchy(group, false);
result.add(rep);
}
return result;
}
public static List<GroupRepresentation> toGroupHierarchy(RealmModel realm, boolean full, Integer first, Integer max) {
List<GroupRepresentation> hierarchy = new LinkedList<>();
List<GroupModel> groups = realm.getTopLevelGroups(first, max);
if (Objects.isNull(groups)) return hierarchy;
for (GroupModel group : groups) {
GroupRepresentation rep = toGroupHierarchy(group, full);
hierarchy.add(rep);
}
return hierarchy;
}
public static List<GroupRepresentation> toGroupHierarchy(RealmModel realm, boolean full) { public static List<GroupRepresentation> toGroupHierarchy(RealmModel realm, boolean full) {
List<GroupRepresentation> hierarchy = new LinkedList<>(); List<GroupRepresentation> hierarchy = new LinkedList<>();
List<GroupModel> groups = realm.getTopLevelGroups(); List<GroupModel> groups = realm.getTopLevelGroups();
if (groups == null) return hierarchy; if (Objects.isNull(groups)) return hierarchy;
for (GroupModel group : groups) { for (GroupModel group : groups) {
GroupRepresentation rep = toGroupHierarchy(group, full); GroupRepresentation rep = toGroupHierarchy(group, full);
hierarchy.add(rep); hierarchy.add(rep);

View file

@ -23,11 +23,7 @@ import org.keycloak.provider.ProviderEvent;
import org.keycloak.storage.UserStorageProvider; import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.UserStorageProviderModel; import org.keycloak.storage.UserStorageProviderModel;
import java.util.Collections; import java.util.*;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -147,11 +143,11 @@ public interface RealmModel extends RoleContainerModel {
boolean isVerifyEmail(); boolean isVerifyEmail();
void setVerifyEmail(boolean verifyEmail); void setVerifyEmail(boolean verifyEmail);
boolean isLoginWithEmailAllowed(); boolean isLoginWithEmailAllowed();
void setLoginWithEmailAllowed(boolean loginWithEmailAllowed); void setLoginWithEmailAllowed(boolean loginWithEmailAllowed);
boolean isDuplicateEmailsAllowed(); boolean isDuplicateEmailsAllowed();
void setDuplicateEmailsAllowed(boolean duplicateEmailsAllowed); void setDuplicateEmailsAllowed(boolean duplicateEmailsAllowed);
@ -402,6 +398,8 @@ public interface RealmModel extends RoleContainerModel {
GroupModel getGroupById(String id); GroupModel getGroupById(String id);
List<GroupModel> getGroups(); List<GroupModel> getGroups();
List<GroupModel> getTopLevelGroups(); List<GroupModel> getTopLevelGroups();
List<GroupModel> getTopLevelGroups(Integer first, Integer max);
List<GroupModel> searchForGroupByName(String search, Integer first, Integer max);
boolean removeGroup(GroupModel group); boolean removeGroup(GroupModel group);
void moveGroup(GroupModel group, GroupModel toParent); void moveGroup(GroupModel group, GroupModel toParent);

View file

@ -42,6 +42,10 @@ public interface RealmProvider extends Provider {
List<GroupModel> getTopLevelGroups(RealmModel realm); List<GroupModel> getTopLevelGroups(RealmModel realm);
List<GroupModel> getTopLevelGroups(RealmModel realm, Integer first, Integer max);
List<GroupModel> searchForGroupByName(RealmModel realm, String search, Integer first, Integer max);
boolean removeGroup(RealmModel realm, GroupModel group); boolean removeGroup(RealmModel realm, GroupModel group);
GroupModel createGroup(RealmModel realm, String name); GroupModel createGroup(RealmModel realm, String name);
@ -85,8 +89,6 @@ public interface RealmProvider extends Provider {
ClientTemplateModel getClientTemplateById(String id, RealmModel realm); ClientTemplateModel getClientTemplateById(String id, RealmModel realm);
GroupModel getGroupById(String id, RealmModel realm); GroupModel getGroupById(String id, RealmModel realm);
List<RealmModel> getRealms(); List<RealmModel> getRealms();
boolean removeRealm(String id); boolean removeRealm(String id);
void close(); void close();

View file

@ -26,20 +26,16 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.representations.idm.GroupRepresentation; import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.services.ErrorResponse;
import javax.ws.rs.Consumes; import javax.ws.rs.*;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context; import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
import java.net.URI; import java.net.URI;
import java.util.List; import java.util.List;
import org.keycloak.services.ErrorResponse; import java.util.Objects;
/** /**
* @resource Groups * @resource Groups
@ -71,10 +67,22 @@ public class GroupsResource {
@GET @GET
@NoCache @NoCache
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public List<GroupRepresentation> getGroups() { public List<GroupRepresentation> getGroupsByName(@QueryParam("search") String search,
@QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResults) {
auth.requireView(); auth.requireView();
return ModelToRepresentation.toGroupHierarchy(realm, false); List<GroupRepresentation> results;
if (Objects.nonNull(search)) {
results = ModelToRepresentation.searchForGroupByName(realm, search.trim(), firstResult, maxResults);
} else if(Objects.nonNull(firstResult) && Objects.nonNull(maxResults)) {
results = ModelToRepresentation.toGroupHierarchy(realm, false, firstResult, maxResults);
} else {
results = ModelToRepresentation.toGroupHierarchy(realm, false);
}
return results;
} }
/** /**
@ -109,7 +117,7 @@ public class GroupsResource {
return ErrorResponse.exists("Top level group named '" + rep.getName() + "' already exists."); return ErrorResponse.exists("Top level group named '" + rep.getName() + "' already exists.");
} }
} }
GroupModel child = null; GroupModel child = null;
Response.ResponseBuilder builder = Response.status(204); Response.ResponseBuilder builder = Response.status(204);
if (rep.getId() != null) { if (rep.getId() != null) {