Skip to content

Commit 17da3ee

Browse files
thomasdarimonthmlnarik
authored andcommitted
KEYCLOAK-18380 Fix Groups search by name returns unwanted groups
Previously the group search did not apply a given search query as filter for groups along the group path. We now filter the found groups with the given group search query if present.
1 parent bd55694 commit 17da3ee

File tree

2 files changed

+79
-2
lines changed

2 files changed

+79
-2
lines changed

server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.keycloak.representations.idm.*;
3939
import org.keycloak.representations.idm.authorization.*;
4040
import org.keycloak.storage.StorageId;
41+
import org.keycloak.utils.StringUtil;
4142

4243
import java.util.ArrayList;
4344
import java.util.HashMap;
@@ -144,7 +145,7 @@ public static GroupRepresentation toRepresentation(GroupModel group, boolean ful
144145

145146
public static Stream<GroupRepresentation> searchForGroupByName(RealmModel realm, boolean full, String search, Integer first, Integer max) {
146147
return realm.searchForGroupByNameStream(search, first, max)
147-
.map(g -> toGroupHierarchy(g, full));
148+
.map(g -> toGroupHierarchy(g, full, search));
148149
}
149150

150151
public static Stream<GroupRepresentation> searchForGroupByName(UserModel user, boolean full, String search, Integer first, Integer max) {
@@ -173,13 +174,28 @@ public static Stream<GroupRepresentation> toGroupHierarchy(UserModel user, boole
173174
}
174175

175176
public static GroupRepresentation toGroupHierarchy(GroupModel group, boolean full) {
177+
return toGroupHierarchy(group, full, null);
178+
}
179+
180+
public static GroupRepresentation toGroupHierarchy(GroupModel group, boolean full, String search) {
176181
GroupRepresentation rep = toRepresentation(group, full);
177182
List<GroupRepresentation> subGroups = group.getSubGroupsStream()
178-
.map(subGroup -> toGroupHierarchy(subGroup, full)).collect(Collectors.toList());
183+
.filter(g -> groupMatchesSearchOrIsPathElement(g, search))
184+
.map(subGroup -> toGroupHierarchy(subGroup, full, search)).collect(Collectors.toList());
179185
rep.setSubGroups(subGroups);
180186
return rep;
181187
}
182188

189+
private static boolean groupMatchesSearchOrIsPathElement(GroupModel group, String search) {
190+
if (StringUtil.isBlank(search)) {
191+
return true;
192+
}
193+
if (group.getName().contains(search)) {
194+
return true;
195+
}
196+
return group.getSubGroupsStream().findAny().isPresent();
197+
}
198+
183199
public static UserRepresentation toRepresentation(KeycloakSession session, RealmModel realm, UserModel user) {
184200
UserRepresentation rep = new UserRepresentation();
185201
rep.setId(user.getId());

testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupTest.java

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
import java.util.List;
6161
import java.util.Map;
6262
import java.util.UUID;
63+
import java.util.stream.Collectors;
6364
import javax.ws.rs.ClientErrorException;
6465
import javax.ws.rs.core.Response.Status;
6566
import static org.hamcrest.Matchers.*;
@@ -839,6 +840,66 @@ public void adminEndpointAccessibleWhenAdminRoleAssignedToGroup() {
839840
}
840841
}
841842

843+
/**
844+
* Groups search with query returns unwanted groups
845+
* @link https://issues.redhat.com/browse/KEYCLOAK-18380
846+
*/
847+
@Test
848+
public void searchForGroupsShouldOnlyReturnMatchingElementsOrIntermediatePaths() {
849+
850+
/*
851+
* /g1/g1.1-bubu
852+
* /g1/g1.2-test1234
853+
* /g2-test1234
854+
* /g3/g3.1-test1234/g3.1.1
855+
*/
856+
String needle = "test1234";
857+
GroupRepresentation g1 = GroupBuilder.create().name("g1").build();
858+
GroupRepresentation g1_1 = GroupBuilder.create().name("g1.1-bubu").build();
859+
GroupRepresentation g1_2 = GroupBuilder.create().name("g1.2-" + needle).build();
860+
GroupRepresentation g2 = GroupBuilder.create().name("g2-" + needle).build();
861+
GroupRepresentation g3 = GroupBuilder.create().name("g3").build();
862+
GroupRepresentation g3_1 = GroupBuilder.create().name("g3.1-" + needle).build();
863+
GroupRepresentation g3_1_1 = GroupBuilder.create().name("g3.1.1").build();
864+
865+
String realmName = AuthRealm.TEST;
866+
RealmResource realm = adminClient.realms().realm(realmName);
867+
868+
createGroup(realm, g1);
869+
createGroup(realm, g2);
870+
createGroup(realm, g3);
871+
addSubGroup(realm, g1, g1_1);
872+
addSubGroup(realm, g1, g1_2);
873+
addSubGroup(realm, g3, g3_1);
874+
addSubGroup(realm, g3_1, g3_1_1);
875+
876+
try {
877+
// we search for "test1234" and expect only /g1/g1.2-test1234, /g2-test1234 and /g3/g3.1-test1234 as a result
878+
List<GroupRepresentation> result = realm.groups().groups(needle, 0, 100);
879+
// ensure stable sorting to make tests pass
880+
result = result.stream().sorted(Comparator.comparing(GroupRepresentation::getName)).collect(Collectors.toList());
881+
assertEquals(3, result.size());
882+
assertEquals("g1", result.get(0).getName());
883+
assertEquals(1, result.get(0).getSubGroups().size());
884+
assertEquals("g1.2-" + needle, result.get(0).getSubGroups().get(0).getName());
885+
assertEquals("g2-" + needle, result.get(1).getName());
886+
assertEquals("g3", result.get(2).getName());
887+
assertEquals(1, result.get(2).getSubGroups().size());
888+
assertEquals("g3.1-" + needle, result.get(2).getSubGroups().get(0).getName());
889+
} finally {
890+
if (g1.getId() != null) {
891+
realm.groups().group(g1.getId()).remove();
892+
}
893+
894+
if (g2.getId() != null) {
895+
realm.groups().group(g2.getId()).remove();
896+
}
897+
898+
if (g3.getId() != null) {
899+
realm.groups().group(g3.getId()).remove();
900+
}
901+
}
902+
}
842903

843904
/**
844905
* Verifies that the role assigned to a user's group is correctly handled by Keycloak Admin endpoint.

0 commit comments

Comments
 (0)