From 8f860c810ea98083ff32375f1f298f5d0a799755 Mon Sep 17 00:00:00 2001 From: Paul Schwabauer Date: Mon, 28 Apr 2025 19:35:37 +0200 Subject: [PATCH] Fix duplicate users on searching attributes with multiple entries Closes #39246 Signed-off-by: koplas --- .../java/org/keycloak/models/jpa/JpaUserProvider.java | 10 +++++----- .../java/org/keycloak/testsuite/admin/UserTest.java | 7 ++++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java index 2f5999ddb64..2ad3c26c3d9 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java @@ -618,7 +618,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, JpaUs CriteriaQuery queryBuilder = builder.createQuery(Long.class); Root root = queryBuilder.from(UserEntity.class); - queryBuilder.select(builder.count(root)); + queryBuilder.select(builder.countDistinct(root)); List predicates = new ArrayList<>(); @@ -647,7 +647,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, JpaUs Root groupMembership = queryBuilder.from(UserGroupMembershipEntity.class); Join userJoin = groupMembership.join("user"); - queryBuilder.select(builder.count(userJoin)); + queryBuilder.select(builder.countDistinct(userJoin)); List predicates = new ArrayList<>(); @@ -669,7 +669,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, JpaUs CriteriaBuilder qb = em.getCriteriaBuilder(); CriteriaQuery userQuery = qb.createQuery(Long.class); Root from = userQuery.from(UserEntity.class); - Expression count = qb.count(from); + Expression count = qb.countDistinct(from); userQuery = userQuery.select(count); List restrictions = predicates(params, from, Map.of()); @@ -694,7 +694,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, JpaUs CriteriaQuery countQuery = cb.createQuery(Long.class); Root root = countQuery.from(UserEntity.class); - countQuery.select(cb.count(root)); + countQuery.select(cb.countDistinct(root)); List restrictions = predicates(params, root, Map.of()); restrictions.add(cb.equal(root.get("realmId"), realm.getId())); @@ -808,7 +808,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, JpaUs predicates.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.USERS, this, realm, builder, queryBuilder, root)); - queryBuilder.where(predicates.toArray(Predicate[]::new)).orderBy(builder.asc(root.get(UserModel.USERNAME))); + queryBuilder.distinct(true).where(predicates.toArray(Predicate[]::new)).orderBy(builder.asc(root.get(UserModel.USERNAME))); TypedQuery query = em.createQuery(queryBuilder); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java index c81291d323b..7c8c138776d 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java @@ -782,7 +782,7 @@ public class UserTest extends AbstractAdminTest { addAttribute(user, "test", Collections.singletonList("test" + i)); addAttribute(user, "test" + i, Collections.singletonList("test" + i)); - addAttribute(user, "attr", Collections.singletonList("common")); + addAttribute(user, "attr", Arrays.asList("common", "common2")); ids.add(createUser(user)); } @@ -819,6 +819,11 @@ public class UserTest extends AbstractAdminTest { attributes = new HashMap<>(); attributes.put("attr", "common"); assertThat(realm.users().count(null, null, null, null, null, null, null, mapToSearchQuery(attributes)), is(9)); + + attributes = new HashMap<>(); + attributes.put("attr", "common"); + attributes.put(UserModel.EXACT, Boolean.FALSE.toString()); + assertThat(realm.users().count(null, null, null, null, null, null, null, mapToSearchQuery(attributes)), is(9)); } @Test