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 ba72a4d4a56..b5561f4803b 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 @@ -59,6 +59,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -806,19 +807,10 @@ public List searchForUser(String search, RealmModel realm) { @Override public List searchForUser(String search, RealmModel realm, int firstResult, int maxResults) { - TypedQuery query = em.createNamedQuery("searchForUser", UserEntity.class); - query.setParameter("realmId", realm.getId()); - query.setParameter("search", "%" + search.toLowerCase() + "%"); - if (firstResult != -1) { - query.setFirstResult(firstResult); - } - if (maxResults != -1) { - query.setMaxResults(maxResults); - } - List results = query.getResultList(); - List users = new LinkedList<>(); - for (UserEntity entity : results) users.add(new UserAdapter(session, realm, em, entity)); - return users; + Map attributes = new HashMap<>(); + attributes.put(UserModel.SEARCH, search); + session.setAttribute(UserModel.INCLUDE_SERVICE_ACCOUNT, false); + return searchForUser(attributes, realm, firstResult, maxResults); } @Override diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java index ddcabd63f41..de739708b4d 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java @@ -44,8 +44,6 @@ @NamedQueries({ @NamedQuery(name="getAllUsersByRealm", query="select u from UserEntity u where u.realmId = :realmId 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 " + - "( lower(u.username) like :search or lower(concat(coalesce(u.firstName, ''), ' ', coalesce(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(coalesce(u.firstName, ''), ' ', coalesce(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"), diff --git a/services/src/main/java/org/keycloak/storage/UserStorageManager.java b/services/src/main/java/org/keycloak/storage/UserStorageManager.java index 0cc2756a1a8..1db4a7bff55 100755 --- a/services/src/main/java/org/keycloak/storage/UserStorageManager.java +++ b/services/src/main/java/org/keycloak/storage/UserStorageManager.java @@ -580,7 +580,11 @@ public List searchForUser(Map attributes, RealmModel public List searchForUser(Map attributes, RealmModel realm, int firstResult, int maxResults) { List results = query((provider, first, max) -> { if (provider instanceof UserQueryProvider) { - return ((UserQueryProvider)provider).searchForUser(attributes, realm, first, max); + if (attributes.containsKey(UserModel.SEARCH)) { + return ((UserQueryProvider)provider).searchForUser(attributes.get(UserModel.SEARCH), realm, first, max); + } else { + return ((UserQueryProvider)provider).searchForUser(attributes, realm, first, max); + } } return Collections.EMPTY_LIST; diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/BackwardsCompatibilityUserStorage.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/BackwardsCompatibilityUserStorage.java index 492589f427d..f6c1f242cc1 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/BackwardsCompatibilityUserStorage.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/BackwardsCompatibilityUserStorage.java @@ -18,10 +18,13 @@ package org.keycloak.testsuite.federation; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import org.jboss.logging.Logger; import org.keycloak.common.util.Time; @@ -31,6 +34,7 @@ import org.keycloak.credential.CredentialInputValidator; import org.keycloak.credential.CredentialModel; import org.keycloak.credential.hash.PasswordHashProvider; +import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.OTPPolicy; import org.keycloak.models.PasswordPolicy; @@ -44,6 +48,7 @@ import org.keycloak.storage.UserStorageProvider; import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage; import org.keycloak.storage.user.UserLookupProvider; +import org.keycloak.storage.user.UserQueryProvider; import org.keycloak.storage.user.UserRegistrationProvider; /** @@ -54,7 +59,8 @@ * * @author Marek Posolda */ -public class BackwardsCompatibilityUserStorage implements UserLookupProvider, UserStorageProvider, UserRegistrationProvider, CredentialInputUpdater, CredentialInputValidator { +public class BackwardsCompatibilityUserStorage implements UserLookupProvider, UserStorageProvider, UserRegistrationProvider, + CredentialInputUpdater, CredentialInputValidator, UserQueryProvider { private static final Logger log = Logger.getLogger(BackwardsCompatibilityUserStorage.class); @@ -304,6 +310,69 @@ public boolean removeUser(RealmModel realm, UserModel user) { return users.remove(user.getUsername()) != null; } + + // UserQueryProvider methods + + @Override + public int getUsersCount(RealmModel realm) { + return users.size(); + } + + @Override + public List getUsers(RealmModel realm) { + return getUsers(realm, -1, -1); + } + + @Override + public List getUsers(RealmModel realm, int firstResult, int maxResults) { + return users.values() + .stream() + .skip(firstResult).limit(maxResults) + .map(myUser -> createUser(realm, myUser.username)) + .collect(Collectors.toList()); + } + + @Override + public List searchForUser(String search, RealmModel realm) { + return searchForUser(search, realm, -1, -1); + } + + @Override + public List searchForUser(String search, RealmModel realm, int firstResult, int maxResults) { + UserModel user = getUserByUsername(search, realm); + return user == null ? Collections.emptyList() : Arrays.asList(user); + } + + @Override + public List searchForUser(Map params, RealmModel realm) { + // Assume that this is not supported + return Collections.emptyList(); + } + + @Override + public List searchForUser(Map params, RealmModel realm, int firstResult, int maxResults) { + // Assume that this is not supported + return Collections.emptyList(); + } + + @Override + public List getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) { + // Assume that this is not supported + return Collections.emptyList(); + } + + @Override + public List getGroupMembers(RealmModel realm, GroupModel group) { + // Assume that this is not supported + return Collections.emptyList(); + } + + @Override + public List searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) { + // Assume that this is not supported + return Collections.emptyList(); + } + @Override public void close() { } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/BackwardsCompatibilityUserStorageTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/BackwardsCompatibilityUserStorageTest.java index cf15fc65dfd..c250219f9a4 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/BackwardsCompatibilityUserStorageTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/BackwardsCompatibilityUserStorageTest.java @@ -250,6 +250,17 @@ public void testDisableCredentialsInUserStorage() { loginSuccessAndLogout("otp1", "pass"); } + + @Test + public void testSearchUserStorage() { + String userId = addUserAndResetPassword("searching", "pass"); + getCleanup().addUserId(userId); + + // Uses same parameters as admin console when searching users + List users = testRealmResource().users().search("searching", 0, 20, true); + Assert.assertNames(users, "searching"); + } + // return created totpSecret private String setupOTPForUserWithRequiredAction(String userId) { // Add required action to the user to reset OTP