keycloak-developers
Changes
integration/admin-client/src/main/java/org/keycloak/admin/client/resource/GroupsResource.java 39(+33 -6)
Details
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/GroupsResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/GroupsResource.java
index 7807094..ce6db74 100755
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/GroupsResource.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/GroupsResource.java
@@ -20,12 +20,7 @@ package org.keycloak.admin.client.resource;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.representations.idm.GroupRepresentation;
-import javax.ws.rs.Consumes;
-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.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;
@@ -35,12 +30,44 @@ import java.util.List;
* @version $Revision: 1 $
*/
public interface GroupsResource {
+
+ /**
+ * Get all groups.
+ * @return A list containing all groups.
+ */
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
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
* if the group doesn't exist.
*
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
index 0bed826..d396463 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
@@ -20,32 +20,12 @@ package org.keycloak.models.cache.infinispan;
import org.keycloak.Config;
import org.keycloak.common.enums.SslRequired;
import org.keycloak.component.ComponentModel;
-import org.keycloak.models.AuthenticationExecutionModel;
-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.*;
import org.keycloak.models.cache.CachedRealmModel;
import org.keycloak.models.cache.infinispan.entities.CachedRealm;
import org.keycloak.storage.UserStorageProvider;
-import java.util.ArrayList;
-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.*;
import java.util.concurrent.ConcurrentHashMap;
/**
@@ -328,7 +308,7 @@ public class RealmAdapter implements CachedRealmModel {
getDelegateForUpdate();
updated.setLoginWithEmailAllowed(loginWithEmailAllowed);
}
-
+
@Override
public boolean isDuplicateEmailsAllowed() {
if (isUpdated()) return updated.isDuplicateEmailsAllowed();
@@ -797,9 +777,9 @@ public class RealmAdapter implements CachedRealmModel {
@Override
public void setEnabledEventTypes(Set<String> enabledEventTypes) {
getDelegateForUpdate();
- updated.setEnabledEventTypes(enabledEventTypes);
+ updated.setEnabledEventTypes(enabledEventTypes);
}
-
+
@Override
public boolean isAdminEventsEnabled() {
if (isUpdated()) return updated.isAdminEventsEnabled();
@@ -823,7 +803,7 @@ public class RealmAdapter implements CachedRealmModel {
getDelegateForUpdate();
updated.setAdminEventsDetailsEnabled(enabled);
}
-
+
@Override
public ClientModel getMasterAdminClient() {
return cached.getMasterAdminClient()==null ? null : cacheSession.getRealm(Config.getAdminRealm()).getClientById(cached.getMasterAdminClient());
@@ -1226,6 +1206,16 @@ public class RealmAdapter implements CachedRealmModel {
}
@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
public boolean removeGroup(GroupModel group) {
return cacheSession.removeGroup(this, group);
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java
index 52c1309..47267ad 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java
@@ -19,50 +19,15 @@ package org.keycloak.models.cache.infinispan;
import org.jboss.logging.Logger;
import org.keycloak.cluster.ClusterProvider;
-import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
import org.keycloak.migration.MigrationModel;
-import org.keycloak.models.ClientModel;
-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.*;
import org.keycloak.models.cache.CacheRealmProvider;
import org.keycloak.models.cache.CachedRealmModel;
-import org.keycloak.models.cache.infinispan.entities.CachedClient;
-import org.keycloak.models.cache.infinispan.entities.CachedClientRole;
-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.cache.infinispan.entities.*;
+import org.keycloak.models.cache.infinispan.events.*;
import org.keycloak.models.utils.KeycloakModelUtils;
-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.*;
/**
@@ -913,6 +878,47 @@ public class RealmCacheSession implements CacheRealmProvider {
}
@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
public boolean removeGroup(RealmModel realm, GroupModel group) {
invalidateGroup(group.getId(), realm.getId(), true);
listInvalidations.add(realm.getId());
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupEntity.java
index 98b130a..77bc44d 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupEntity.java
@@ -17,19 +17,7 @@
package org.keycloak.models.jpa.entities;
-import javax.persistence.Access;
-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 javax.persistence.*;
import java.util.ArrayList;
import java.util.Collection;
@@ -39,6 +27,8 @@ import java.util.Collection;
*/
@NamedQueries({
@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
@Table(name="KEYCLOAK_GROUP")
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
index 718d19a..683f7a3 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
@@ -20,29 +20,13 @@ package org.keycloak.models.jpa;
import org.jboss.logging.Logger;
import org.keycloak.connections.jpa.util.JpaUtils;
import org.keycloak.migration.MigrationModel;
-import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientTemplateModel;
-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.*;
+import org.keycloak.models.jpa.entities.*;
import org.keycloak.models.utils.KeycloakModelUtils;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
import java.util.stream.Collectors;
/**
@@ -350,6 +334,24 @@ public class JpaRealmProvider implements RealmProvider {
}
@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
public boolean removeGroup(RealmModel realm, GroupModel group) {
if (group == null) {
return false;
@@ -519,4 +521,21 @@ public class JpaRealmProvider implements RealmProvider {
ClientTemplateAdapter adapter = new ClientTemplateAdapter(realm, em, session, app);
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);
+ }
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 33ca943..e53ef2f 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -22,53 +22,13 @@ import org.keycloak.common.enums.SslRequired;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentFactory;
import org.keycloak.component.ComponentModel;
-import org.keycloak.models.AuthenticationExecutionModel;
-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.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.*;
+import org.keycloak.models.jpa.entities.*;
import org.keycloak.models.utils.ComponentUtil;
import org.keycloak.models.utils.KeycloakModelUtils;
import javax.persistence.EntityManager;
-import java.util.ArrayList;
-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.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -360,7 +320,7 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
realm.setVerifyEmail(verifyEmail);
em.flush();
}
-
+
@Override
public boolean isLoginWithEmailAllowed() {
return realm.isLoginWithEmailAllowed();
@@ -372,7 +332,7 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
if (loginWithEmailAllowed) realm.setDuplicateEmailsAllowed(false);
em.flush();
}
-
+
@Override
public boolean isDuplicateEmailsAllowed() {
return realm.isDuplicateEmailsAllowed();
@@ -1720,6 +1680,16 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
}
@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
public boolean removeGroup(GroupModel group) {
return session.realms().removeGroup(this, group);
}
diff --git a/server-spi/src/main/java/org/keycloak/models/RealmModel.java b/server-spi/src/main/java/org/keycloak/models/RealmModel.java
index f6484d6..f90a9d7 100755
--- a/server-spi/src/main/java/org/keycloak/models/RealmModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/RealmModel.java
@@ -23,11 +23,7 @@ import org.keycloak.provider.ProviderEvent;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.UserStorageProviderModel;
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -147,11 +143,11 @@ public interface RealmModel extends RoleContainerModel {
boolean isVerifyEmail();
void setVerifyEmail(boolean verifyEmail);
-
+
boolean isLoginWithEmailAllowed();
void setLoginWithEmailAllowed(boolean loginWithEmailAllowed);
-
+
boolean isDuplicateEmailsAllowed();
void setDuplicateEmailsAllowed(boolean duplicateEmailsAllowed);
@@ -402,6 +398,8 @@ public interface RealmModel extends RoleContainerModel {
GroupModel getGroupById(String id);
List<GroupModel> getGroups();
List<GroupModel> getTopLevelGroups();
+ List<GroupModel> getTopLevelGroups(Integer first, Integer max);
+ List<GroupModel> searchForGroupByName(String search, Integer first, Integer max);
boolean removeGroup(GroupModel group);
void moveGroup(GroupModel group, GroupModel toParent);
diff --git a/server-spi/src/main/java/org/keycloak/models/RealmProvider.java b/server-spi/src/main/java/org/keycloak/models/RealmProvider.java
index 4e6070f..3e1a1c9 100755
--- a/server-spi/src/main/java/org/keycloak/models/RealmProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/RealmProvider.java
@@ -42,6 +42,10 @@ public interface RealmProvider extends Provider {
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);
GroupModel createGroup(RealmModel realm, String name);
@@ -85,8 +89,6 @@ public interface RealmProvider extends Provider {
ClientTemplateModel getClientTemplateById(String id, RealmModel realm);
GroupModel getGroupById(String id, RealmModel realm);
-
-
List<RealmModel> getRealms();
boolean removeRealm(String id);
void close();
diff --git a/server-spi-private/src/main/java/org/keycloak/models/cache/CachedRealmModel.java b/server-spi-private/src/main/java/org/keycloak/models/cache/CachedRealmModel.java
index 16a965f..09111cc 100644
--- a/server-spi-private/src/main/java/org/keycloak/models/cache/CachedRealmModel.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/cache/CachedRealmModel.java
@@ -18,7 +18,6 @@ package org.keycloak.models.cache;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserModel;
import org.keycloak.provider.ProviderEvent;
import java.util.concurrent.ConcurrentHashMap;
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index 7431ed2..ab32a22 100755
--- a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -17,17 +17,6 @@
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.model.Policy;
import org.keycloak.authorization.model.Resource;
@@ -42,60 +31,15 @@ import org.keycloak.credential.CredentialModel;
import org.keycloak.events.Event;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.events.admin.AuthDetails;
-import org.keycloak.models.AuthenticationExecutionModel;
-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.models.*;
import org.keycloak.provider.ProviderConfigProperty;
-import org.keycloak.representations.idm.AdminEventRepresentation;
-import org.keycloak.representations.idm.AuthDetailsRepresentation;
-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.representations.idm.*;
+import org.keycloak.representations.idm.authorization.*;
import org.keycloak.storage.StorageId;
+import java.util.*;
+import java.util.stream.Collectors;
+
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
@@ -147,10 +91,32 @@ public class ModelToRepresentation {
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) {
List<GroupRepresentation> hierarchy = new LinkedList<>();
List<GroupModel> groups = realm.getTopLevelGroups();
- if (groups == null) return hierarchy;
+ if (Objects.isNull(groups)) return hierarchy;
for (GroupModel group : groups) {
GroupRepresentation rep = toGroupHierarchy(group, full);
hierarchy.add(rep);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/GroupsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/GroupsResource.java
index 5be1c0d..7c61fe9 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/GroupsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/GroupsResource.java
@@ -26,20 +26,16 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.representations.idm.GroupRepresentation;
+import org.keycloak.services.ErrorResponse;
-import javax.ws.rs.Consumes;
-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.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.util.List;
-import org.keycloak.services.ErrorResponse;
+import java.util.Objects;
/**
* @resource Groups
@@ -71,10 +67,22 @@ public class GroupsResource {
@GET
@NoCache
@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();
- 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.");
}
}
-
+
GroupModel child = null;
Response.ResponseBuilder builder = Response.status(204);
if (rep.getId() != null) {