Search and Filter for the count endpoint

This commit is contained in:
Leon Graser 2019-08-16 16:09:03 +02:00 committed by Hynek Mlnařík
parent ed2d392a3d
commit 01a42f417f
9 changed files with 699 additions and 7 deletions

View file

@ -111,11 +111,46 @@ public interface UsersResource {
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
Response create(UserRepresentation userRepresentation); Response create(UserRepresentation userRepresentation);
/**
* Returns the number of users that can be viewed.
*
* @return number of users
*/
@Path("count") @Path("count")
@GET @GET
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
Integer count(); Integer count();
/**
* Returns the number of users that can be viewed and match the given search criteria.
* If none is specified this is equivalent to {{@link #count()}}.
*
* @param search criteria to search for
* @return number of users matching the search criteria
*/
@Path("count")
@GET
@Produces(MediaType.APPLICATION_JSON)
Integer count(@QueryParam("search") String search);
/**
* Returns the number of users that can be viewed and match the given filters.
* If none of the filters is specified this is equivalent to {{@link #count()}}.
*
* @param last last name field of a user
* @param first first name field of a user
* @param email email field of a user
* @param username username field of a user
* @return number of users matching the given filters
*/
@Path("count")
@GET
@Produces(MediaType.APPLICATION_JSON)
Integer count(@QueryParam("lastName") String last,
@QueryParam("firstName") String first,
@QueryParam("email") String email,
@QueryParam("username") String username);
@Path("{id}") @Path("{id}")
UserResource get(@PathParam("id") String id); UserResource get(@PathParam("id") String id);

View file

@ -550,6 +550,31 @@ public class UserCacheSession implements UserCache {
return getUsersCount(realm, false); return getUsersCount(realm, false);
} }
@Override
public int getUsersCount(RealmModel realm, Set<String> groupIds) {
return getDelegate().getUsersCount(realm, groupIds);
}
@Override
public int getUsersCount(String search, RealmModel realm) {
return getDelegate().getUsersCount(search, realm);
}
@Override
public int getUsersCount(String search, RealmModel realm, Set<String> groupIds) {
return getDelegate().getUsersCount(search, realm, groupIds);
}
@Override
public int getUsersCount(Map<String, String> params, RealmModel realm) {
return getDelegate().getUsersCount(params, realm);
}
@Override
public int getUsersCount(Map<String, String> params, RealmModel realm, Set<String> groupIds) {
return getDelegate().getUsersCount(params, realm, groupIds);
}
@Override @Override
public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts) { public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts) {
return getDelegate().getUsers(realm, firstResult, maxResults, includeServiceAccounts); return getDelegate().getUsers(realm, firstResult, maxResults, includeServiceAccounts);

View file

@ -52,6 +52,7 @@ import javax.persistence.EntityManager;
import javax.persistence.TypedQuery; import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root; import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery; import javax.persistence.criteria.Subquery;
@ -65,7 +66,6 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.persistence.LockModeType; import javax.persistence.LockModeType;
import javax.persistence.criteria.Expression;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -607,6 +607,132 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
return getUsersCount(realm, false); return getUsersCount(realm, false);
} }
@Override
public int getUsersCount(RealmModel realm, Set<String> groupIds) {
if (groupIds == null || groupIds.isEmpty()) {
return 0;
}
TypedQuery<Long> query = em.createNamedQuery("userCountInGroups", Long.class);
query.setParameter("realmId", realm.getId());
query.setParameter("groupIds", groupIds);
Long count = query.getSingleResult();
return count.intValue();
}
@Override
public int getUsersCount(String search, RealmModel realm) {
TypedQuery<Long> query = em.createNamedQuery("searchForUserCount", Long.class);
query.setParameter("realmId", realm.getId());
query.setParameter("search", "%" + search.toLowerCase() + "%");
Long count = query.getSingleResult();
return count.intValue();
}
@Override
public int getUsersCount(String search, RealmModel realm, Set<String> groupIds) {
if (groupIds == null || groupIds.isEmpty()) {
return 0;
}
TypedQuery<Long> query = em.createNamedQuery("searchForUserCountInGroups", Long.class);
query.setParameter("realmId", realm.getId());
query.setParameter("search", "%" + search.toLowerCase() + "%");
query.setParameter("groupIds", groupIds);
Long count = query.getSingleResult();
return count.intValue();
}
@Override
public int getUsersCount(Map<String, String> params, RealmModel realm) {
CriteriaBuilder qb = em.getCriteriaBuilder();
CriteriaQuery<Long> userQuery = qb.createQuery(Long.class);
Root<UserEntity> from = userQuery.from(UserEntity.class);
Expression<Long> count = qb.count(from);
userQuery = userQuery.select(count);
List<Predicate> restrictions = new ArrayList<>();
restrictions.add(qb.equal(from.get("realmId"), realm.getId()));
for (Map.Entry<String, String> entry : params.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if (key == null || value == null) {
continue;
}
switch (key) {
case UserModel.USERNAME:
restrictions.add(qb.like(from.get("username"), "%" + value + "%"));
break;
case UserModel.FIRST_NAME:
restrictions.add(qb.like(from.get("firstName"), "%" + value + "%"));
break;
case UserModel.LAST_NAME:
restrictions.add(qb.like(from.get("lastName"), "%" + value + "%"));
break;
case UserModel.EMAIL:
restrictions.add(qb.like(from.get("email"), "%" + value + "%"));
break;
}
}
userQuery = userQuery.where(restrictions.toArray(new Predicate[0]));
TypedQuery<Long> query = em.createQuery(userQuery);
Long result = query.getSingleResult();
return result.intValue();
}
@Override
public int getUsersCount(Map<String, String> params, RealmModel realm, Set<String> groupIds) {
if (groupIds == null || groupIds.isEmpty()) {
return 0;
}
CriteriaBuilder qb = em.getCriteriaBuilder();
CriteriaQuery<Long> userQuery = qb.createQuery(Long.class);
Root<UserGroupMembershipEntity> from = userQuery.from(UserGroupMembershipEntity.class);
Expression<Long> count = qb.count(from.get("user"));
userQuery = userQuery.select(count);
List<Predicate> restrictions = new ArrayList<>();
restrictions.add(qb.equal(from.get("user").get("realmId"), realm.getId()));
restrictions.add(from.get("groupId").in(groupIds));
for (Map.Entry<String, String> entry : params.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if (key == null || value == null) {
continue;
}
switch (key) {
case UserModel.USERNAME:
restrictions.add(qb.like(from.get("user").get("username"), "%" + value + "%"));
break;
case UserModel.FIRST_NAME:
restrictions.add(qb.like(from.get("user").get("firstName"), "%" + value + "%"));
break;
case UserModel.LAST_NAME:
restrictions.add(qb.like(from.get("user").get("lastName"), "%" + value + "%"));
break;
case UserModel.EMAIL:
restrictions.add(qb.like(from.get("user").get("email"), "%" + value + "%"));
break;
}
}
userQuery = userQuery.where(restrictions.toArray(new Predicate[0]));
TypedQuery<Long> query = em.createQuery(userQuery);
Long result = query.getSingleResult();
return result.intValue();
}
@Override @Override
public List<UserModel> getUsers(RealmModel realm) { public List<UserModel> getUsers(RealmModel realm) {
return getUsers(realm, false); return getUsers(realm, false);

View file

@ -46,6 +46,8 @@ import java.util.Collection;
@NamedQuery(name="getAllUsersByRealmExcludeServiceAccount", query="select u from UserEntity u where u.realmId = :realmId and (u.serviceAccountClientLink is null) order by u.username"), @NamedQuery(name="getAllUsersByRealmExcludeServiceAccount", query="select u from UserEntity u where u.realmId = :realmId and (u.serviceAccountClientLink is null) order by u.username"),
@NamedQuery(name="searchForUser", query="select u from UserEntity u where u.realmId = :realmId and (u.serviceAccountClientLink is null) and " + @NamedQuery(name="searchForUser", query="select u from UserEntity u where u.realmId = :realmId and (u.serviceAccountClientLink is null) and " +
"( lower(u.username) like :search or lower(concat(u.firstName, ' ', u.lastName)) like :search or u.email like :search ) order by u.username"), "( lower(u.username) like :search or lower(concat(u.firstName, ' ', u.lastName)) like :search or u.email like :search ) order by u.username"),
@NamedQuery(name="searchForUserCount", query="select count(u) from UserEntity u where u.realmId = :realmId and (u.serviceAccountClientLink is null) and " +
"( lower(u.username) like :search or lower(concat(u.firstName, ' ', u.lastName)) like :search or u.email like :search )"),
@NamedQuery(name="getRealmUserByUsername", query="select u from UserEntity u where u.username = :username and u.realmId = :realmId"), @NamedQuery(name="getRealmUserByUsername", query="select u from UserEntity u where u.username = :username and u.realmId = :realmId"),
@NamedQuery(name="getRealmUserByEmail", query="select u from UserEntity u where u.email = :email and u.realmId = :realmId"), @NamedQuery(name="getRealmUserByEmail", query="select u from UserEntity u where u.email = :email and u.realmId = :realmId"),
@NamedQuery(name="getRealmUserByLastName", query="select u from UserEntity u where u.lastName = :lastName and u.realmId = :realmId"), @NamedQuery(name="getRealmUserByLastName", query="select u from UserEntity u where u.lastName = :lastName and u.realmId = :realmId"),

View file

@ -40,7 +40,10 @@ import java.io.Serializable;
@NamedQuery(name="deleteUserGroupMembershipByRealm", query="delete from UserGroupMembershipEntity mapping where mapping.user IN (select u from UserEntity u where u.realmId=:realmId)"), @NamedQuery(name="deleteUserGroupMembershipByRealm", query="delete from UserGroupMembershipEntity mapping where mapping.user IN (select u from UserEntity u where u.realmId=:realmId)"),
@NamedQuery(name="deleteUserGroupMembershipsByRealmAndLink", query="delete from UserGroupMembershipEntity mapping where mapping.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link)"), @NamedQuery(name="deleteUserGroupMembershipsByRealmAndLink", query="delete from UserGroupMembershipEntity mapping where mapping.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link)"),
@NamedQuery(name="deleteUserGroupMembershipsByGroup", query="delete from UserGroupMembershipEntity m where m.groupId = :groupId"), @NamedQuery(name="deleteUserGroupMembershipsByGroup", query="delete from UserGroupMembershipEntity m where m.groupId = :groupId"),
@NamedQuery(name="deleteUserGroupMembershipsByUser", query="delete from UserGroupMembershipEntity m where m.user = :user") @NamedQuery(name="deleteUserGroupMembershipsByUser", query="delete from UserGroupMembershipEntity m where m.user = :user"),
@NamedQuery(name="searchForUserCountInGroups", query="select count(m.user) from UserGroupMembershipEntity m where m.user.realmId = :realmId and (m.user.serviceAccountClientLink is null) and " +
"( lower(m.user.username) like :search or lower(concat(m.user.firstName, ' ', m.user.lastName)) like :search or m.user.email like :search ) and m.group.id in :groupIds"),
@NamedQuery(name="userCountInGroups", query="select count(m.user) from UserGroupMembershipEntity m where m.user.realmId = :realmId and m.group.id in :groupIds")
}) })
@Table(name="USER_GROUP_MEMBERSHIP") @Table(name="USER_GROUP_MEMBERSHIP")
@Entity @Entity

View file

@ -24,6 +24,8 @@ import org.keycloak.models.UserModel;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/** /**
* Optional capability interface implemented by UserStorageProviders. * Optional capability interface implemented by UserStorageProviders.
@ -43,6 +45,99 @@ public interface UserQueryProvider {
*/ */
int getUsersCount(RealmModel realm); int getUsersCount(RealmModel realm);
/**
* Returns the number of users that are in at least one of the groups
* given.
*
* @param realm the realm
* @param groupIds set of groups id to check for
* @return the number of users that are in at least one of the groups
*/
default int getUsersCount(RealmModel realm, Set<String> groupIds) {
if (groupIds == null || groupIds.isEmpty()) {
return 0;
}
return countUsersInGroups(getUsers(realm), groupIds);
}
/**
* Returns the number of users that match the given criteria.
*
* @param search search criteria
* @param realm the realm
* @return number of users that match the search
*/
default int getUsersCount(String search, RealmModel realm) {
return searchForUser(search, realm).size();
}
/**
* Returns the number of users that match the given criteria and are in
* at least one of the groups given.
*
* @param search search criteria
* @param realm the realm
* @param groupIds set of groups to check for
* @return number of users that match the search and given groups
*/
default int getUsersCount(String search, RealmModel realm, Set<String> groupIds) {
if (groupIds == null || groupIds.isEmpty()) {
return 0;
}
List<UserModel> users = searchForUser(search, realm);
return countUsersInGroups(users, groupIds);
}
/**
* Returns the number of users that match the given filter parameters.
*
* @param params filter parameters
* @param realm the realm
* @return number of users that match the given filters
*/
default int getUsersCount(Map<String, String> params, RealmModel realm) {
return searchForUser(params, realm).size();
}
/**
* Returns the number of users that match the given filter parameters and is in
* at least one of the given groups.
*
* @param params filter parameters
* @param realm the realm
* @param groupIds set if groups to check for
* @return number of users that match the given filters and groups
*/
default int getUsersCount(Map<String, String> params, RealmModel realm, Set<String> groupIds) {
if (groupIds == null || groupIds.isEmpty()) {
return 0;
}
List<UserModel> users = searchForUser(params, realm);
return countUsersInGroups(users, groupIds);
}
/**
* Returns the number of users from the given list of users that are in at
* least one of the groups given in the groups set.
*
* @param users list of users to check
* @param groupIds id of groups that should be checked for
* @return number of users that are in at least one of the groups
*/
static int countUsersInGroups(List<UserModel> users, Set<String> groupIds) {
return (int) users.stream().filter(u -> {
for (GroupModel group : u.getGroups()) {
if (groupIds.contains(group.getId())) {
return true;
}
}
return false;
}).count();
}
/** /**
* Returns the number of users. * Returns the number of users.
* *

View file

@ -235,14 +235,73 @@ public class UsersResource {
return toRepresentation(realm, userPermissionEvaluator, briefRepresentation, userModels); return toRepresentation(realm, userPermissionEvaluator, briefRepresentation, userModels);
} }
/**
* Returns the number of users that match the given criteria.
* It can be called in three different ways.
* 1. Don't specify any criteria and pass {@code null}. The number of all
* users within that realm will be returned.
* <p>
* 2. If {@code search} is specified other criteria such as {@code last} will
* be ignored even though you set them. The {@code search} string will be
* matched against the first and last name, the username and the email of a
* user.
* <p>
* 3. If {@code search} is unspecified but any of {@code last}, {@code first},
* {@code email} or {@code username} those criteria are matched against their
* respective fields on a user entity. Combined with a logical and.
*
* @param search arbitrary search string for all the fields below
* @param last last name filter
* @param first first name filter
* @param email email filter
* @param username username filter
* @return the number of users that match the given criteria
*/
@Path("count") @Path("count")
@GET @GET
@NoCache @NoCache
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public Integer getUsersCount() { public Integer getUsersCount(@QueryParam("search") String search,
auth.users().requireView(); @QueryParam("lastName") String last,
@QueryParam("firstName") String first,
@QueryParam("email") String email,
@QueryParam("username") String username) {
UserPermissionEvaluator userPermissionEvaluator = auth.users();
userPermissionEvaluator.requireQuery();
if (search != null) {
if (search.startsWith(SEARCH_ID_PARAMETER)) {
UserModel userModel = session.users().getUserById(search.substring(SEARCH_ID_PARAMETER.length()).trim(), realm);
return userModel != null && userPermissionEvaluator.canView(userModel) ? 1 : 0;
} else if (userPermissionEvaluator.canView()) {
return session.users().getUsersCount(search.trim(), realm);
} else {
return session.users().getUsersCount(search.trim(), realm, auth.groups().getGroupsWithViewPermission());
}
} else if (last != null || first != null || email != null || username != null) {
Map<String, String> parameters = new HashMap<>();
if (last != null) {
parameters.put(UserModel.LAST_NAME, last);
}
if (first != null) {
parameters.put(UserModel.FIRST_NAME, first);
}
if (email != null) {
parameters.put(UserModel.EMAIL, email);
}
if (username != null) {
parameters.put(UserModel.USERNAME, username);
}
if (userPermissionEvaluator.canView()) {
return session.users().getUsersCount(parameters, realm);
} else {
return session.users().getUsersCount(parameters, realm, auth.groups().getGroupsWithViewPermission());
}
} else if (userPermissionEvaluator.canView()) {
return session.users().getUsersCount(realm); return session.users().getUsersCount(realm);
} else {
return session.users().getUsersCount(realm, auth.groups().getGroupsWithViewPermission());
}
} }
private List<UserRepresentation> searchForUser(Map<String, String> attributes, RealmModel realm, UserPermissionEvaluator usersEvaluator, Boolean briefRepresentation, Integer firstResult, Integer maxResults, Boolean includeServiceAccounts) { private List<UserRepresentation> searchForUser(Map<String, String> attributes, RealmModel realm, UserPermissionEvaluator usersEvaluator, Boolean briefRepresentation, Integer firstResult, Integer maxResults, Boolean includeServiceAccounts) {

View file

@ -462,6 +462,31 @@ public class UserStorageManager implements UserProvider, OnUserCache, OnCreateCo
return getUsersCount(realm, false); return getUsersCount(realm, false);
} }
@Override
public int getUsersCount(RealmModel realm, Set<String> groupIds) {
return localStorage().getUsersCount(realm, groupIds);
}
@Override
public int getUsersCount(String search, RealmModel realm) {
return localStorage().getUsersCount(search, realm);
}
@Override
public int getUsersCount(String search, RealmModel realm, Set<String> groupIds) {
return localStorage().getUsersCount(search, realm, groupIds);
}
@Override
public int getUsersCount(Map<String, String> params, RealmModel realm) {
return localStorage().getUsersCount(params, realm);
}
@Override
public int getUsersCount(Map<String, String> params, RealmModel realm, Set<String> groupIds) {
return localStorage().getUsersCount(params, realm, groupIds);
}
@FunctionalInterface @FunctionalInterface
interface PaginatedQuery { interface PaginatedQuery {
List<UserModel> query(Object provider, int first, int max); List<UserModel> query(Object provider, int first, int max);

View file

@ -0,0 +1,322 @@
/*
* Copyright 2019 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.admin;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.AuthorizationResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.ManagementPermissionRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.authorization.DecisionStrategy;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation;
import org.keycloak.representations.idm.authorization.UserPolicyRepresentation;
import org.keycloak.testsuite.util.AdminClientUtil;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
public class UsersTest extends AbstractAdminTest {
@Before
public void cleanUsers() {
List<UserRepresentation> userRepresentations = realm.users().list();
for (UserRepresentation user : userRepresentations) {
realm.users().delete(user.getId());
}
}
@Test
public void countUsersWithViewPermission() {
createUser(realmId, "user1", "password", "user1FirstName", "user1LastName", "user1@example.com");
createUser(realmId, "user2", "password", "user2FirstName", "user2LastName", "user2@example.com");
assertThat(realm.users().count(), is(2));
}
@Test
public void countUsersBySearchWithViewPermission() {
createUser(realmId, "user1", "password", "user1FirstName", "user1LastName", "user1@example.com");
createUser(realmId, "user2", "password", "user2FirstName", "user2LastName", "user2@example.com");
//search all
assertThat(realm.users().count("user"), is(2));
//search first name
assertThat(realm.users().count("FirstName"), is(2));
assertThat(realm.users().count("user2FirstName"), is(1));
//search last name
assertThat(realm.users().count("LastName"), is(2));
assertThat(realm.users().count("user2LastName"), is(1));
//search in email
assertThat(realm.users().count("@example.com"), is(2));
assertThat(realm.users().count("user1@example.com"), is(1));
//search for something not existing
assertThat(realm.users().count("notExisting"), is(0));
//search for empty string
assertThat(realm.users().count(""), is(2));
//search not specified (defaults to simply /count)
assertThat(realm.users().count(null), is(2));
}
@Test
public void countUsersByFiltersWithViewPermission() {
createUser(realmId, "user1", "password", "user1FirstName", "user1LastName", "user1@example.com");
createUser(realmId, "user2", "password", "user2FirstName", "user2LastName", "user2@example.com");
//search username
assertThat(realm.users().count(null, null, null, "user"), is(2));
assertThat(realm.users().count(null, null, null, "user1"), is(1));
assertThat(realm.users().count(null, null, null, "notExisting"), is(0));
assertThat(realm.users().count(null, null, null, ""), is(2));
//search first name
assertThat(realm.users().count(null, "FirstName", null, null), is(2));
assertThat(realm.users().count(null, "user2FirstName", null, null), is(1));
assertThat(realm.users().count(null, "notExisting", null, null), is(0));
assertThat(realm.users().count(null, "", null, null), is(2));
//search last name
assertThat(realm.users().count("LastName", null, null, null), is(2));
assertThat(realm.users().count("user2LastName", null, null, null), is(1));
assertThat(realm.users().count("notExisting", null, null, null), is(0));
assertThat(realm.users().count("", null, null, null), is(2));
//search in email
assertThat(realm.users().count(null, null, "@example.com", null), is(2));
assertThat(realm.users().count(null, null, "user1@example.com", null), is(1));
assertThat(realm.users().count(null, null, "user1@test.com", null), is(0));
assertThat(realm.users().count(null, null, "", null), is(2));
//search for combinations
assertThat(realm.users().count("LastName", "FirstName", null, null), is(2));
assertThat(realm.users().count("user1LastName", "FirstName", null, null), is(1));
assertThat(realm.users().count("user1LastName", "", null, null), is(1));
assertThat(realm.users().count("LastName", "", null, null), is(2));
assertThat(realm.users().count("LastName", "", null, null), is(2));
assertThat(realm.users().count(null, null, "@example.com", "user"), is(2));
//search not specified (defaults to simply /count)
assertThat(realm.users().count(null, null, null, null), is(2));
assertThat(realm.users().count("", "", "", ""), is(2));
}
@Test
public void countUsersWithGroupViewPermission() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException {
RealmResource testRealmResource = setupTestEnvironmentWithPermissions(true);
assertThat(testRealmResource.users().count(), is(3));
}
@Test
public void countUsersBySearchWithGroupViewPermission() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException {
RealmResource testRealmResource = setupTestEnvironmentWithPermissions(true);
//search all
assertThat(testRealmResource.users().count("user"), is(3));
//search first name
assertThat(testRealmResource.users().count("FirstName"), is(3));
assertThat(testRealmResource.users().count("user2FirstName"), is(1));
//search last name
assertThat(testRealmResource.users().count("LastName"), is(3));
assertThat(testRealmResource.users().count("user2LastName"), is(1));
//search in email
assertThat(testRealmResource.users().count("@example.com"), is(3));
assertThat(testRealmResource.users().count("user1@example.com"), is(1));
//search for something not existing
assertThat(testRealmResource.users().count("notExisting"), is(0));
//search for empty string
assertThat(testRealmResource.users().count(""), is(3));
//search not specified (defaults to simply /count)
assertThat(testRealmResource.users().count(null), is(3));
}
@Test
public void countUsersByFiltersWithGroupViewPermission() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException {
RealmResource testRealmResource = setupTestEnvironmentWithPermissions(true);
//search username
assertThat(testRealmResource.users().count(null, null, null, "user"), is(3));
assertThat(testRealmResource.users().count(null, null, null, "user1"), is(1));
assertThat(testRealmResource.users().count(null, null, null, "notExisting"), is(0));
assertThat(testRealmResource.users().count(null, null, null, ""), is(3));
//search first name
assertThat(testRealmResource.users().count(null, "FirstName", null, null), is(3));
assertThat(testRealmResource.users().count(null, "user2FirstName", null, null), is(1));
assertThat(testRealmResource.users().count(null, "notExisting", null, null), is(0));
assertThat(testRealmResource.users().count(null, "", null, null), is(3));
//search last name
assertThat(testRealmResource.users().count("LastName", null, null, null), is(3));
assertThat(testRealmResource.users().count("user2LastName", null, null, null), is(1));
assertThat(testRealmResource.users().count("notExisting", null, null, null), is(0));
assertThat(testRealmResource.users().count("", null, null, null), is(3));
//search in email
assertThat(testRealmResource.users().count(null, null, "@example.com", null), is(3));
assertThat(testRealmResource.users().count(null, null, "user1@example.com", null), is(1));
assertThat(testRealmResource.users().count(null, null, "user1@test.com", null), is(0));
assertThat(testRealmResource.users().count(null, null, "", null), is(3));
//search for combinations
assertThat(testRealmResource.users().count("LastName", "FirstName", null, null), is(3));
assertThat(testRealmResource.users().count("user1LastName", "FirstName", null, null), is(1));
assertThat(testRealmResource.users().count("user1LastName", "", null, null), is(1));
assertThat(testRealmResource.users().count("LastName", "", null, null), is(3));
assertThat(testRealmResource.users().count("LastName", "", null, null), is(3));
assertThat(testRealmResource.users().count(null, null, "@example.com", "user"), is(3));
//search not specified (defaults to simply /count)
assertThat(testRealmResource.users().count(null, null, null, null), is(3));
assertThat(testRealmResource.users().count("", "", "", ""), is(3));
}
@Test
public void countUsersWithNoViewPermission() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, KeyManagementException {
RealmResource testRealmResource = setupTestEnvironmentWithPermissions(false);
assertThat(testRealmResource.users().count(), is(0));
}
@Test
public void countUsersBySearchWithNoViewPermission() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException {
RealmResource testRealmResource = setupTestEnvironmentWithPermissions(false);
//search all
assertThat(testRealmResource.users().count("user"), is(0));
//search first name
assertThat(testRealmResource.users().count("FirstName"), is(0));
assertThat(testRealmResource.users().count("user2FirstName"), is(0));
//search last name
assertThat(testRealmResource.users().count("LastName"), is(0));
assertThat(testRealmResource.users().count("user2LastName"), is(0));
//search in email
assertThat(testRealmResource.users().count("@example.com"), is(0));
assertThat(testRealmResource.users().count("user1@example.com"), is(0));
//search for something not existing
assertThat(testRealmResource.users().count("notExisting"), is(0));
//search for empty string
assertThat(testRealmResource.users().count(""), is(0));
//search not specified (defaults to simply /count)
assertThat(testRealmResource.users().count(null), is(0));
}
@Test
public void countUsersByFiltersWithNoViewPermission() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException {
RealmResource testRealmResource = setupTestEnvironmentWithPermissions(false);
//search username
assertThat(testRealmResource.users().count(null, null, null, "user"), is(0));
assertThat(testRealmResource.users().count(null, null, null, "user1"), is(0));
assertThat(testRealmResource.users().count(null, null, null, "notExisting"), is(0));
assertThat(testRealmResource.users().count(null, null, null, ""), is(0));
//search first name
assertThat(testRealmResource.users().count(null, "FirstName", null, null), is(0));
assertThat(testRealmResource.users().count(null, "user2FirstName", null, null), is(0));
assertThat(testRealmResource.users().count(null, "notExisting", null, null), is(0));
assertThat(testRealmResource.users().count(null, "", null, null), is(0));
//search last name
assertThat(testRealmResource.users().count("LastName", null, null, null), is(0));
assertThat(testRealmResource.users().count("user2LastName", null, null, null), is(0));
assertThat(testRealmResource.users().count("notExisting", null, null, null), is(0));
assertThat(testRealmResource.users().count("", null, null, null), is(0));
//search in email
assertThat(testRealmResource.users().count(null, null, "@example.com", null), is(0));
assertThat(testRealmResource.users().count(null, null, "user1@example.com", null), is(0));
assertThat(testRealmResource.users().count(null, null, "user1@test.com", null), is(0));
assertThat(testRealmResource.users().count(null, null, "", null), is(0));
//search for combinations
assertThat(testRealmResource.users().count("LastName", "FirstName", null, null), is(0));
assertThat(testRealmResource.users().count("user1LastName", "FirstName", null, null), is(0));
assertThat(testRealmResource.users().count("user1LastName", "", null, null), is(0));
assertThat(testRealmResource.users().count("LastName", "", null, null), is(0));
assertThat(testRealmResource.users().count("LastName", "", null, null), is(0));
assertThat(testRealmResource.users().count(null, null, "@example.com", "user"), is(0));
//search not specified (defaults to simply /count)
assertThat(testRealmResource.users().count(null, null, null, null), is(0));
assertThat(testRealmResource.users().count("", "", "", ""), is(0));
}
private RealmResource setupTestEnvironmentWithPermissions(boolean grp1ViewPermissions) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException {
String testUserId = createUser(realmId, "test-user", "password", "", "", "");
//assign 'query-users' role to test user
ClientRepresentation clientRepresentation = realm.clients().findByClientId("realm-management").get(0);
String realmManagementId = clientRepresentation.getId();
RoleRepresentation roleRepresentation = realm.clients().get(realmManagementId).roles().get("query-users").toRepresentation();
realm.users().get(testUserId).roles().clientLevel(realmManagementId).add(Collections.singletonList(roleRepresentation));
//create test users and groups
List<GroupRepresentation> groups = setupUsersInGroupsWithPermissions();
if (grp1ViewPermissions) {
AuthorizationResource authorizationResource = realm.clients().get(realmManagementId).authorization();
//create a user policy for the test user
UserPolicyRepresentation policy = new UserPolicyRepresentation();
String policyName = "test-policy";
policy.setName(policyName);
policy.setUsers(Collections.singleton(testUserId));
authorizationResource.policies().user().create(policy);
PolicyRepresentation policyRepresentation = authorizationResource.policies().findByName(policyName);
//add the policy to grp1
Optional<GroupRepresentation> optional = groups.stream().filter(g -> g.getName().equals("grp1")).findFirst();
assertThat(optional.isPresent(), is(true));
GroupRepresentation grp1 = optional.get();
ScopePermissionRepresentation scopePermissionRepresentation = authorizationResource.permissions().scope().findByName("view.members.permission.group." + grp1.getId());
scopePermissionRepresentation.setPolicies(Collections.singleton(policyRepresentation.getId()));
scopePermissionRepresentation.setDecisionStrategy(DecisionStrategy.UNANIMOUS);
authorizationResource.permissions().scope().findById(scopePermissionRepresentation.getId()).update(scopePermissionRepresentation);
}
Keycloak testUserClient = AdminClientUtil.createAdminClient(true, realm.toRepresentation().getRealm(), "test-user", "password", "admin-cli", "");
return testUserClient.realm(realm.toRepresentation().getRealm());
}
private List<GroupRepresentation> setupUsersInGroupsWithPermissions() {
//create two groups
GroupRepresentation grp1 = createGroupWithPermissions("grp1");
GroupRepresentation grp2 = createGroupWithPermissions("grp2");
//create test users
String user1Id = createUser(realmId, "user1", "password", "user1FirstName", "user1LastName", "user1@example.com");
String user2Id = createUser(realmId, "user2", "password", "user2FirstName", "user2LastName", "user2@example.com");
String user3Id = createUser(realmId, "user3", "password", "user3FirstName", "user3LastName", "user3@example.com");
String user4Id = createUser(realmId, "user4", "password", "user4FirstName", "user4LastName", "user4@example.com");
//add users to groups
realm.users().get(user1Id).joinGroup(grp1.getId());
realm.users().get(user2Id).joinGroup(grp1.getId());
realm.users().get(user3Id).joinGroup(grp1.getId());
realm.users().get(user4Id).joinGroup(grp2.getId());
List<GroupRepresentation> groups = new ArrayList<>();
groups.add(grp1);
groups.add(grp2);
return groups;
}
private GroupRepresentation createGroupWithPermissions(String name) {
GroupRepresentation grp = new GroupRepresentation();
grp.setName(name);
realm.groups().add(grp);
Optional<GroupRepresentation> optional = realm.groups().groups().stream().filter(g -> g.getName().equals(name)).findFirst();
assertThat(optional.isPresent(), is(true));
grp = optional.get();
String id = grp.getId();
//enable the permissions
realm.groups().group(id).setPermissions(new ManagementPermissionRepresentation(true));
assertThat(realm.groups().group(id).getPermissions().isEnabled(), is(true));
return grp;
}
}