KEYCLOAK-13442 Backwards compatibility in users searching. searchForUser(String, RealmModel, int, int) is no longer called when searching users from the admin console

This commit is contained in:
mposolda 2020-03-26 14:29:52 +01:00 committed by Hynek Mlnařík
parent 4b6e46d1a9
commit 6f62c0ed98
5 changed files with 91 additions and 17 deletions

View file

@ -60,6 +60,7 @@ import javax.persistence.criteria.Subquery;
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;
@ -814,19 +815,10 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
@Override
public List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults) {
TypedQuery<UserEntity> 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<UserEntity> results = query.getResultList();
List<UserModel> users = new LinkedList<>();
for (UserEntity entity : results) users.add(new UserAdapter(session, realm, em, entity));
return users;
Map<String, String> attributes = new HashMap<>();
attributes.put(UserModel.SEARCH, search);
session.setAttribute(UserModel.INCLUDE_SERVICE_ACCOUNT, false);
return searchForUser(attributes, realm, firstResult, maxResults);
}
@Override

View file

@ -44,8 +44,6 @@ import java.util.Collection;
@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"),

View file

@ -588,7 +588,11 @@ public class UserStorageManager implements UserProvider, OnUserCache, OnCreateCo
public List<UserModel> searchForUser(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults) {
List<UserModel> 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;

View file

@ -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.CredentialInputUpdater;
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.StorageId;
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 @@ import org.keycloak.storage.user.UserRegistrationProvider;
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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 class BackwardsCompatibilityUserStorage implements UserLookupProvider, Us
return users.remove(user.getUsername()) != null;
}
// UserQueryProvider methods
@Override
public int getUsersCount(RealmModel realm) {
return users.size();
}
@Override
public List<UserModel> getUsers(RealmModel realm) {
return getUsers(realm, -1, -1);
}
@Override
public List<UserModel> 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<UserModel> searchForUser(String search, RealmModel realm) {
return searchForUser(search, realm, -1, -1);
}
@Override
public List<UserModel> 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<UserModel> searchForUser(Map<String, String> params, RealmModel realm) {
// Assume that this is not supported
return Collections.emptyList();
}
@Override
public List<UserModel> searchForUser(Map<String, String> params, RealmModel realm, int firstResult, int maxResults) {
// Assume that this is not supported
return Collections.emptyList();
}
@Override
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
// Assume that this is not supported
return Collections.emptyList();
}
@Override
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group) {
// Assume that this is not supported
return Collections.emptyList();
}
@Override
public List<UserModel> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) {
// Assume that this is not supported
return Collections.emptyList();
}
@Override
public void close() {
}

View file

@ -250,6 +250,17 @@ public class BackwardsCompatibilityUserStorageTest extends AbstractAuthTest {
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<UserRepresentation> 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