Extend group search attribute functionality to account for use case where only the leaf group is required

This commit is contained in:
Alice Wood 2023-01-23 16:33:41 -05:00 committed by Pedro Igor
parent 9bc30f4705
commit 7e56938b74
4 changed files with 42 additions and 8 deletions

View file

@ -146,4 +146,8 @@ public interface GroupsResource {
@GET @GET
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
List<GroupRepresentation> query(@QueryParam("q") String searchQuery); List<GroupRepresentation> query(@QueryParam("q") String searchQuery);
@GET
@Produces(MediaType.APPLICATION_JSON)
List<GroupRepresentation> query(@QueryParam("q") String searchQuery, @QueryParam("populateHierarchy") boolean populateHierarchy);
} }

View file

@ -150,8 +150,10 @@ public class ModelToRepresentation {
return rep; return rep;
} }
public static Stream<GroupRepresentation> searchGroupsByAttributes(KeycloakSession session, RealmModel realm, boolean full, Map<String,String> attributes, Integer first, Integer max) { public static Stream<GroupRepresentation> searchGroupsByAttributes(KeycloakSession session, RealmModel realm, boolean full, boolean populateHierarchy, Map<String,String> attributes, Integer first, Integer max) {
return session.groups().searchGroupsByAttributes(realm, attributes, first, max) Stream<GroupModel> groups = session.groups().searchGroupsByAttributes(realm, attributes, first, max);
if(populateHierarchy) {
groups = groups
// We need to return whole group hierarchy when any child group fulfills the attribute search, // We need to return whole group hierarchy when any child group fulfills the attribute search,
// therefore for each group from the result, we need to find root group // therefore for each group from the result, we need to find root group
.map(group -> { .map(group -> {
@ -162,10 +164,10 @@ public class ModelToRepresentation {
}) })
// More child groups of one root can fulfill the search, so we need to filter duplicates // More child groups of one root can fulfill the search, so we need to filter duplicates
.filter(StreamsUtil.distinctByKey(GroupModel::getId)) .filter(StreamsUtil.distinctByKey(GroupModel::getId));
}
// and then turn the result into GroupRepresentations creating whole hierarchy of child groups for each root group // and then turn the result into GroupRepresentations creating whole hierarchy of child groups for each root group
.map(g -> toGroupHierarchy(g, full, attributes)); return groups.map(g -> toGroupHierarchy(g, full, attributes));
} }
@Deprecated @Deprecated

View file

@ -79,12 +79,13 @@ public class GroupsResource {
@QueryParam("exact") @DefaultValue("false") Boolean exact, @QueryParam("exact") @DefaultValue("false") Boolean exact,
@QueryParam("first") Integer firstResult, @QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResults, @QueryParam("max") Integer maxResults,
@QueryParam("briefRepresentation") @DefaultValue("true") boolean briefRepresentation) { @QueryParam("briefRepresentation") @DefaultValue("true") boolean briefRepresentation,
@QueryParam("populateHierarchy") @DefaultValue("true") boolean populateHierarchy) {
auth.groups().requireList(); auth.groups().requireList();
if (Objects.nonNull(searchQuery)) { if (Objects.nonNull(searchQuery)) {
Map<String, String> attributes = SearchQueryUtils.getFields(searchQuery); Map<String, String> attributes = SearchQueryUtils.getFields(searchQuery);
return ModelToRepresentation.searchGroupsByAttributes(session, realm, !briefRepresentation, attributes, firstResult, maxResults); return ModelToRepresentation.searchGroupsByAttributes(session, realm, !briefRepresentation, populateHierarchy, attributes, firstResult, maxResults);
} else if (Objects.nonNull(search)) { } else if (Objects.nonNull(search)) {
return ModelToRepresentation.searchForGroupByName(session, realm, !briefRepresentation, search.trim(), exact, firstResult, maxResults); return ModelToRepresentation.searchForGroupByName(session, realm, !briefRepresentation, search.trim(), exact, firstResult, maxResults);
} else if(Objects.nonNull(firstResult) && Objects.nonNull(maxResults)) { } else if(Objects.nonNull(firstResult) && Objects.nonNull(maxResults)) {

View file

@ -4,6 +4,7 @@ import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.keycloak.testsuite.auth.page.AuthRealm.TEST; import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
import java.util.Arrays; import java.util.Arrays;
@ -143,11 +144,37 @@ public class GroupSearchTest extends AbstractGroupTest {
} }
} }
@Test
public void testNestedGroupQuerySearchNoHierarchy() throws Exception {
configureSearchableAttributes(ATTR_URL_NAME, ATTR_ORG_NAME, ATTR_QUOTES_NAME);
try (Creator<GroupResource> parentGroupCreator = Creator.create(testRealmResource(), parentGroup)) {
parentGroupCreator.resource().subGroup(childGroup);
GroupRepresentation testGroup = new GroupRepresentation();
testGroup.setName("test_child");
parentGroupCreator.resource().subGroup(testGroup);
// query for the child group by org name
GroupsResource search = testRealmResource().groups();
String searchQuery = String.format("%s:%s", ATTR_ORG_NAME, "childOrg");
List<GroupRepresentation> found = search.query(searchQuery, false);
assertThat(found.size(), is(1));
assertThat(found.get(0).getName(), is(equalTo(CHILD_GROUP)));
String path = found.get(0).getPath();
assertThat(path, is(String.format("/%s/%s", PARENT_GROUP, CHILD_GROUP)));
} finally {
resetSearchableAttributes();
}
}
private void search(String searchQuery, String... expectedGroupIds) { private void search(String searchQuery, String... expectedGroupIds) {
GroupsResource search = testRealmResource().groups(); GroupsResource search = testRealmResource().groups();
List<String> found = search.query(searchQuery).stream() List<String> found = search.query(searchQuery).stream()
.map(GroupRepresentation::getName) .map(GroupRepresentation::getName)
.collect(Collectors.toList()); .collect(Collectors.toList());
assertThat(found, hasSize(expectedGroupIds.length));
assertThat(found, containsInAnyOrder(expectedGroupIds)); assertThat(found, containsInAnyOrder(expectedGroupIds));
} }