Wildcard search not working for custom user attributes

Closes #32451

Signed-off-by: vramik <vramik@redhat.com>
This commit is contained in:
vramik 2024-10-02 11:56:07 +02:00 committed by Pedro Igor
parent 35eba8be8c
commit b7eaa9b0cb
4 changed files with 50 additions and 20 deletions

View file

@ -10,6 +10,8 @@ Search for a user to view detailed information about the user, such as the user'
.Prerequisite
* You are in the realm where the user exists.
== Default search
.Procedure
. Click *Users* in the main menu. This *Users* page is displayed.
. Type the full name, last name, first name, or email address of the user you want to search for in the search box. The search returns all users that match your criteria.
@ -19,8 +21,21 @@ The criteria used to match users depends on the syntax used on the search box:
.. `"somevalue"` -> performs exact search of the string `"somevalue"`;
.. `\*somevalue*` -> performs infix search, akin to a `LIKE '%somevalue%'` DB query;
.. `somevalue*` or `somevalue` -> performs prefix search, akin to a `LIKE 'somevalue%'` DB query.
+
NOTE: Searches performed in the *Users* page encompasses searching both {project_name}'s database and configured user federated backends, such as LDAP. Users found in federated backends will be imported into {project_name}'s database if they don't already exist there.
+
.Additional resources
== Attribute search
.Procedure
. Click *Users* in the main menu. This *Users* page is displayed.
. Click *Default search* button and switch it to *Attribute search*.
. Click *Select attributes* button and specify the attributes to search by.
. Check *Exact search* checkbox to perform exact match or keep it unchecked to use an infix search for attribute values.
. Click *Search* button to perform the search. It returns all users that match the criteria.
[NOTE]
====
Searches performed in the *Users* page encompass both {project_name}'s database and configured user federation backends, such as LDAP. Users found in federated backends will be imported into {project_name}'s database if they don't already exist there.
====
.Additional Resources
* For more information on user federation, see <<_user-storage-federation,User Federation>>.

View file

@ -114,6 +114,12 @@ public interface UsersResource {
@Consumes(MediaType.APPLICATION_JSON)
List<UserRepresentation> searchByAttributes(@QueryParam("q") String searchQuery);
@GET
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
List<UserRepresentation> searchByAttributes(@QueryParam("q") String searchQuery,
@QueryParam("exact") Boolean exact);
@GET
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)

View file

@ -294,7 +294,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
return null;
}
StorageId clientStorageId = null;
StorageId clientStorageId;
if ( entity.getClientId() == null) {
clientStorageId = new StorageId(entity.getClientStorageProvider(), entity.getExternalClientId());
} else {
@ -625,7 +625,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
predicates.add(builder.or(getSearchOptionPredicateArray(stringToSearch, builder, root)));
}
queryBuilder.where(predicates.toArray(new Predicate[0]));
queryBuilder.where(predicates.toArray(Predicate[]::new));
return em.createQuery(queryBuilder).getSingleResult().intValue();
}
@ -654,7 +654,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
predicates.add(groupMembership.get("groupId").in(groupIds));
queryBuilder.where(predicates.toArray(new Predicate[0]));
queryBuilder.where(predicates.toArray(Predicate[]::new));
return em.createQuery(queryBuilder).getSingleResult().intValue();
}
@ -670,7 +670,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
List<Predicate> restrictions = predicates(params, from, Map.of());
restrictions.add(qb.equal(from.get("realmId"), realm.getId()));
userQuery = userQuery.where(restrictions.toArray(new Predicate[0]));
userQuery = userQuery.where(restrictions.toArray(Predicate[]::new));
TypedQuery<Long> query = em.createQuery(userQuery);
Long result = query.getSingleResult();
@ -695,7 +695,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
groupsWithPermissionsSubquery(countQuery, groupIds, root, restrictions);
countQuery.where(restrictions.toArray(new Predicate[0]));
countQuery.where(restrictions.toArray(Predicate[]::new));
TypedQuery<Long> query = em.createQuery(countQuery);
Long result = query.getSingleResult();
@ -952,7 +952,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
orPredicates.add(builder.like(builder.lower(from.get(LAST_NAME)), value, ESCAPE_BACKSLASH));
}
return orPredicates.toArray(new Predicate[0]);
return orPredicates.toArray(Predicate[]::new);
}
private UserEntity userInEntityManagerContext(String id) {
@ -1027,9 +1027,15 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
builder.equal(attributesJoin.get("name"), key),
builder.equal(attributesJoin.get("longValueHashLowerCase"), JpaHashUtils.hashForAttributeValueLowerCase(value))));
} else {
attributePredicates.add(builder.and(
if (Boolean.parseBoolean(attributes.get(UserModel.EXACT))) {
attributePredicates.add(builder.and(
builder.equal(attributesJoin.get("name"), key),
builder.equal(builder.lower(attributesJoin.get("value")), value.toLowerCase())));
} else {
attributePredicates.add(builder.and(
builder.equal(attributesJoin.get("name"), key),
builder.like(builder.lower(attributesJoin.get("value")), "%" + value.toLowerCase() + "%")));
}
}
break;
case UserModel.INCLUDE_SERVICE_ACCOUNT: {
@ -1043,7 +1049,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
}
if (!attributePredicates.isEmpty()) {
predicates.add(builder.and(attributePredicates.toArray(new Predicate[0])));
predicates.add(builder.and(attributePredicates.toArray(Predicate[]::new)));
}
return predicates;
@ -1074,11 +1080,11 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
Expression<String> groupId = from.get("groupId");
subs.add(cb.like(from1.get("name"), cb.concat("group.resource.", groupId)));
subquery1.where(subs.toArray(new Predicate[0]));
subquery1.where(subs.toArray(Predicate[]::new));
subPredicates.add(cb.exists(subquery1));
subquery.where(subPredicates.toArray(new Predicate[0]));
subquery.where(subPredicates.toArray(Predicate[]::new));
restrictions.add(cb.exists(subquery));
}

View file

@ -918,13 +918,16 @@ public class UserTest extends AbstractAdminTest {
public void searchByMultipleAttributes() {
createUsers();
Map<String, String> attributes = new HashMap<>();
attributes.put("test", "test1");
attributes.put("attr", "common");
attributes.put("test1", "test1");
List<UserRepresentation> users = realm.users().searchByAttributes(mapToSearchQuery(Map.of("username", "user", "test", "test1", "attr", "common", "test1", "test1")));
assertThat(users, hasSize(1));
List<UserRepresentation> users = realm.users().searchByAttributes(mapToSearchQuery(attributes));
assertEquals(1, users.size());
//custom user attribute should use wildcard search by default
users = realm.users().searchByAttributes(mapToSearchQuery(Map.of("username", "user", "test", "est", "attr", "mm", "test1", "test1")));
assertThat(users, hasSize(1));
//with exact=true the user shouldn't be returned
users = realm.users().searchByAttributes(mapToSearchQuery(Map.of("test", "est", "attr", "mm", "test1", "test1")), Boolean.TRUE);
assertThat(users, hasSize(0));
}
@Test