KEYCLOAK-8150 Improve loading user list

This commit is contained in:
Hynek Mlnarik 2018-08-29 16:25:44 +02:00 committed by Hynek Mlnařík
parent df76afb513
commit bee3894cdf
6 changed files with 90 additions and 10 deletions

View file

@ -42,6 +42,16 @@ public interface UsersResource {
@QueryParam("first") Integer firstResult, @QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResults); @QueryParam("max") Integer maxResults);
@GET
@Produces(MediaType.APPLICATION_JSON)
List<UserRepresentation> search(@QueryParam("username") String username,
@QueryParam("firstName") String firstName,
@QueryParam("lastName") String lastName,
@QueryParam("email") String email,
@QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResults,
@QueryParam("briefRepresentation") Boolean briefRepresentation);
@GET @GET
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
List<UserRepresentation> search(@QueryParam("username") String username); List<UserRepresentation> search(@QueryParam("username") String username);
@ -65,6 +75,29 @@ public interface UsersResource {
@QueryParam("first") Integer firstResult, @QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResults); @QueryParam("max") Integer maxResults);
/**
* Search for users whose username or email matches the value provided by {@code search}. The {@code search}
* argument also allows finding users by specific attributes as follows:
*
* <ul>
* <li><i>id:</i> - Find users by identifier. For instance, <i>id:aa497859-bbf5-44ac-bf1a-74dbffcaf197</i></li>
* </ul>
*
* @param search the value to search. It can be the username, email or any of the supported options to query based on user attributes
* @param firstResult the position of the first result to retrieve
* @param maxResults the maximum number of results to retreive
* @param briefRepresentation Only return basic information (only guaranteed to return id, username, created, first and last name,
* email, enabled state, email verification state, federation link, and access.
* Note that it means that namely user attributes, required actions, and not before are not returned.)
* @return a list of {@link UserRepresentation}
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
List<UserRepresentation> search(@QueryParam("search") String search,
@QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResults,
@QueryParam("briefRepresentation") Boolean briefRepresentation);
@GET @GET
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
List<UserRepresentation> list(@QueryParam("first") Integer firstResult, List<UserRepresentation> list(@QueryParam("first") Integer firstResult,

View file

@ -166,6 +166,21 @@ public class ModelToRepresentation {
return rep; return rep;
} }
public static UserRepresentation toBriefRepresentation(UserModel user) {
UserRepresentation rep = new UserRepresentation();
rep.setId(user.getId());
rep.setUsername(user.getUsername());
rep.setCreatedTimestamp(user.getCreatedTimestamp());
rep.setLastName(user.getLastName());
rep.setFirstName(user.getFirstName());
rep.setEmail(user.getEmail());
rep.setEnabled(user.isEnabled());
rep.setEmailVerified(user.isEmailVerified());
rep.setFederationLink(user.getFederationLink());
return rep;
}
public static EventRepresentation toRepresentation(Event event) { public static EventRepresentation toRepresentation(Event event) {
EventRepresentation rep = new EventRepresentation(); EventRepresentation rep = new EventRepresentation();
rep.setTime(event.getTime()); rep.setTime(event.getTime());

View file

@ -179,7 +179,8 @@ public class UsersResource {
@QueryParam("email") String email, @QueryParam("email") String email,
@QueryParam("username") String username, @QueryParam("username") String username,
@QueryParam("first") Integer firstResult, @QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResults) { @QueryParam("max") Integer maxResults,
@QueryParam("briefRepresentation") Boolean briefRepresentation) {
auth.users().requireQuery(); auth.users().requireQuery();
firstResult = firstResult != null ? firstResult : -1; firstResult = firstResult != null ? firstResult : -1;
@ -216,9 +217,12 @@ public class UsersResource {
} }
boolean canViewGlobal = auth.users().canView(); boolean canViewGlobal = auth.users().canView();
boolean briefRepresentationB = briefRepresentation != null && briefRepresentation;
for (UserModel user : userModels) { for (UserModel user : userModels) {
if (!canViewGlobal && !auth.users().canView(user)) continue; if (!canViewGlobal && !auth.users().canView(user)) continue;
UserRepresentation userRep = ModelToRepresentation.toRepresentation(session, realm, user); UserRepresentation userRep = briefRepresentationB
? ModelToRepresentation.toBriefRepresentation(user)
: ModelToRepresentation.toRepresentation(session, realm, user);
userRep.setAccess(auth.users().getAccess(user)); userRep.setAccess(auth.users().getAccess(user));
results.add(userRep); results.add(userRep);
} }

View file

@ -86,12 +86,8 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals; import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.*;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.keycloak.testsuite.Assert.assertNames; import static org.keycloak.testsuite.Assert.assertNames;
/** /**
@ -1407,14 +1403,36 @@ public class UserTest extends AbstractAdminTest {
UsersResource users = adminClient.realms().realm("test").users(); UsersResource users = adminClient.realms().realm("test").users();
for (int i = 0; i < 110; i++) { for (int i = 0; i < 110; i++) {
users.create(UserBuilder.create().username("test-" + i).build()).close(); users.create(UserBuilder.create().username("test-" + i).addAttribute("aName", "aValue").build()).close();
}
List<UserRepresentation> result = users.search("test", null, null);
assertEquals(100, result.size());
for (UserRepresentation user : result) {
assertThat(user.getAttributes(), Matchers.notNullValue());
assertThat(user.getAttributes().keySet(), Matchers.hasSize(1));
assertThat(user.getAttributes(), Matchers.hasEntry(is("aName"), Matchers.contains("aValue")));
} }
assertEquals(100, users.search("test", null, null).size());
assertEquals(105, users.search("test", 0, 105).size()); assertEquals(105, users.search("test", 0, 105).size());
assertEquals(111, users.search("test", 0, 1000).size()); assertEquals(111, users.search("test", 0, 1000).size());
} }
@Test
public void defaultMaxResultsBrief() {
UsersResource users = adminClient.realms().realm("test").users();
for (int i = 0; i < 110; i++) {
users.create(UserBuilder.create().username("test-" + i).addAttribute("aName", "aValue").build()).close();
}
List<UserRepresentation> result = users.search("test", null, null, true);
assertEquals(100, result.size());
for (UserRepresentation user : result) {
assertThat(user.getAttributes(), Matchers.nullValue());
}
}
private void switchEditUsernameAllowedOn(boolean enable) { private void switchEditUsernameAllowedOn(boolean enable) {
RealmRepresentation rep = realm.toRepresentation(); RealmRepresentation rep = realm.toRepresentation();
rep.setEditUsernameAllowed(enable); rep.setEditUsernameAllowed(enable);

View file

@ -82,6 +82,15 @@ public class UserBuilder {
return this; return this;
} }
public UserBuilder addAttribute(String name, String... values) {
if (rep.getAttributes() == null) {
rep.setAttributes(new HashMap<>());
}
rep.getAttributes().put(name, Arrays.asList(values));
return this;
}
/** /**
* This method makes sure that there is one single password for the user. * This method makes sure that there is one single password for the user.
*/ */

View file

@ -235,6 +235,7 @@ module.controller('UserListCtrl', function($scope, realm, User, UserSearchState,
UserSearchState.query.realm = realm.realm; UserSearchState.query.realm = realm.realm;
$scope.query = UserSearchState.query; $scope.query = UserSearchState.query;
$scope.query.briefRepresentation = 'true';
if (!UserSearchState.isFirstSearch) $scope.searchQuery(); if (!UserSearchState.isFirstSearch) $scope.searchQuery();
}; };