User REST Admin API - count and search returns different amount of users

Closes #45219

Signed-off-by: Martin Kanis <mkanis@redhat.com>
This commit is contained in:
Martin Kanis 2026-01-13 14:18:24 +01:00 committed by Pedro Igor
parent fcc9ade022
commit 4f91b5246e
3 changed files with 99 additions and 1 deletions

View file

@ -401,6 +401,38 @@ public interface UsersResource {
@QueryParam("idpUserId") String idpUserId,
@QueryParam("q") String searchQuery);
/**
* Returns the number of users that can be viewed and match the given filters.
* Includes support for exact matching.
*
* @param search arbitrary search string for all the fields below
* @param last last name field of a user
* @param first first name field of a user
* @param email email field of a user
* @param emailVerified emailVerified field of a user
* @param username username field of a user
* @param enabled Boolean representing if user is enabled or not
* @param idpAlias The alias of an Identity Provider linked to the user
* @param idpUserId The userId at an Identity Provider linked to the user
* @param exact Boolean which defines whether the params must match exactly
* @param searchQuery A query to search for custom attributes
* @return number of users matching the given filters
*/
@Path("count")
@GET
@Produces(MediaType.APPLICATION_JSON)
Integer count(@QueryParam("search") String search,
@QueryParam("lastName") String last,
@QueryParam("firstName") String first,
@QueryParam("email") String email,
@QueryParam("emailVerified") Boolean emailVerified,
@QueryParam("username") String username,
@QueryParam("enabled") Boolean enabled,
@QueryParam("idpAlias") String idpAlias,
@QueryParam("idpUserId") String idpUserId,
@QueryParam("exact") Boolean exact,
@QueryParam("q") String searchQuery);
/**
* Returns the number of users with the given status for emailVerified.
* If none of the filters is specified this is equivalent to {{@link #count()}}.

View file

@ -450,7 +450,8 @@ public class UsersResource {
return session.users().getUsersCount(realm, parameters);
}
}
} else if (last != null || first != null || email != null || username != null || emailVerified != null || enabled != null || !searchAttributes.isEmpty()) {
} else if (last != null || first != null || email != null || username != null || emailVerified != null
|| idpAlias != null || idpUserId != null || enabled != null || exact != null || !searchAttributes.isEmpty()) {
Map<String, String> parameters = new HashMap<>();
if (last != null) {
parameters.put(UserModel.LAST_NAME, last);

View file

@ -788,4 +788,69 @@ public class UserSearchTest extends AbstractUserTest {
users = managedRealm.admin().users().search("username", 0, 20);
assertEquals(9, users.size());
}
@Test
public void testCountAndSearchConsistencyWithMissingParameters() {
createUser("user1", "user1@example.com");
// Count users before creating service account (should be 1 regular user)
Integer countBefore = managedRealm.admin().users().count(null, null, null, null, null, null, null, null, null, true, null);
assertEquals(1, countBefore.intValue(), "Should have 1 regular user before service account creation");
ClientRepresentation client = new ClientRepresentation();
client.setClientId("service-account-client");
client.setServiceAccountsEnabled(true);
client.setEnabled(true);
client.setPublicClient(false);
client.setSecret("secret");
client.setRedirectUris(Arrays.asList("http://localhost"));
String clientId = ApiUtil.getCreatedId(managedRealm.admin().clients().create(client));
// Count users after creating service account (should be 2: 1 regular + 1 service account)
Integer countAfter = managedRealm.admin().users().count(null, null, null, null, null, null, null, null, null, true, null);
assertEquals(2, countAfter.intValue(), "Should have 2 users after service account creation (1 regular + 1 service account)");
// exact=true inconsistency with service accounts
Integer count = managedRealm.admin().users().count(null, null, null, null, null, null, null, null, null, true, null);
List<UserRepresentation> users = managedRealm.admin().users().search(null, null, null, null, 0, 10, null, null, true);
assertEquals(count.intValue(), users.size(), "Count and search should return same number with exact=true");
// idpAlias parameter with service account inclusion inconsistency
Integer countWithIdp = managedRealm.admin().users().count(null, null, null, null, null, null, null, "nonexistent-idp", null, null);
List<UserRepresentation> usersWithIdp = managedRealm.admin().users().search(null, null, null, null, null, "nonexistent-idp", null, 0, 10, null, null);
assertEquals(countWithIdp.intValue(), usersWithIdp.size(), "Count and search should return same number with idpAlias parameter");
// idpUserId parameter with same inconsistency
Integer countWithIdpUserId = managedRealm.admin().users().count(null, null, null, null, null, null, null, null, "nonexistent-user-id", null);
List<UserRepresentation> usersWithIdpUserId = managedRealm.admin().users().search(null, null, null, null, null, null, "nonexistent-user-id", 0, 10, null, null);
assertEquals(countWithIdpUserId.intValue(), usersWithIdpUserId.size(), "Count and search should return same number with idpUserId parameter");
managedRealm.admin().clients().get(clientId).remove();
}
@Test
public void testCountAndSearchConsistencyWithIdpParameters() {
createUser("user1", "user1@example.com");
createUser("user2", "user2@example.com");
addSampleIdentityProvider("test-idp", 0);
// Link only user1 to the IDP
String user1Id = managedRealm.admin().users().search("user1").get(0).getId();
FederatedIdentityRepresentation link = new FederatedIdentityRepresentation();
link.setUserId("external-user-id");
link.setUserName("user1");
addFederatedIdentity(user1Id, "test-idp", link);
// search with idpAlias should return 1 user, count should also return 1
Integer countWithIdpAlias = managedRealm.admin().users().count(null, null, null, null, null, null, null, "test-idp", null, null);
List<UserRepresentation> usersWithIdpAlias = managedRealm.admin().users().search(null, null, null, null, null, "test-idp", null, 0, 10, null, null);
assertEquals(countWithIdpAlias.intValue(), usersWithIdpAlias.size(), "Count and search should return same number with idpAlias parameter");
// search with idpUserId should return 1 user, count should also return 1
Integer countWithIdpUserId = managedRealm.admin().users().count(null, null, null, null, null, null, null, null, "external-user-id", null);
List<UserRepresentation> usersWithIdpUserId = managedRealm.admin().users().search(null, null, null, null, null, null, "external-user-id", 0, 10, null, null);
assertEquals(countWithIdpUserId.intValue(), usersWithIdpUserId.size(), "Count and search should return same number with idpUserId parameter");
}
}