KEYCLOAK-15146 Add support for searching users by emailVerified status
We now allow to search for users by their emailVerified status. This enables users to easily find users and deal with incomplete user accounts.
This commit is contained in:
parent
fbe18e67c3
commit
12576e339d
6 changed files with 124 additions and 3 deletions
|
@ -53,6 +53,26 @@ public interface UsersResource {
|
||||||
@QueryParam("enabled") Boolean enabled,
|
@QueryParam("enabled") Boolean enabled,
|
||||||
@QueryParam("briefRepresentation") Boolean briefRepresentation);
|
@QueryParam("briefRepresentation") Boolean briefRepresentation);
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
List<UserRepresentation> search(@QueryParam("username") String username,
|
||||||
|
@QueryParam("firstName") String firstName,
|
||||||
|
@QueryParam("lastName") String lastName,
|
||||||
|
@QueryParam("email") String email,
|
||||||
|
@QueryParam("emailVerified") Boolean emailVerified,
|
||||||
|
@QueryParam("first") Integer firstResult,
|
||||||
|
@QueryParam("max") Integer maxResults,
|
||||||
|
@QueryParam("enabled") Boolean enabled,
|
||||||
|
@QueryParam("briefRepresentation") Boolean briefRepresentation);
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
List<UserRepresentation> search(@QueryParam("emailVerified") Boolean emailVerified,
|
||||||
|
@QueryParam("first") Integer firstResult,
|
||||||
|
@QueryParam("max") Integer maxResults,
|
||||||
|
@QueryParam("enabled") Boolean enabled,
|
||||||
|
@QueryParam("briefRepresentation") Boolean briefRepresentation);
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
List<UserRepresentation> search(@QueryParam("username") String username);
|
List<UserRepresentation> search(@QueryParam("username") String username);
|
||||||
|
@ -156,6 +176,38 @@ public interface UsersResource {
|
||||||
@QueryParam("email") String email,
|
@QueryParam("email") String email,
|
||||||
@QueryParam("username") String username);
|
@QueryParam("username") String username);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of users that can be viewed and match the given filters.
|
||||||
|
* If none of the filters is specified this is equivalent to {{@link #count()}}.
|
||||||
|
*
|
||||||
|
* @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
|
||||||
|
* @return number of users matching the given filters
|
||||||
|
*/
|
||||||
|
@Path("count")
|
||||||
|
@GET
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
Integer count(@QueryParam("lastName") String last,
|
||||||
|
@QueryParam("firstName") String first,
|
||||||
|
@QueryParam("email") String email,
|
||||||
|
@QueryParam("emailVerified") Boolean emailVerified,
|
||||||
|
@QueryParam("username") String username);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of users with the given status for emailVerified.
|
||||||
|
* If none of the filters is specified this is equivalent to {{@link #count()}}.
|
||||||
|
*
|
||||||
|
* @param emailVerified emailVerified field of a user
|
||||||
|
* @return number of users matching the given filters
|
||||||
|
*/
|
||||||
|
@Path("count")
|
||||||
|
@GET
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
Integer countEmailVerified(@QueryParam("emailVerified") Boolean emailVerified);
|
||||||
|
|
||||||
@Path("{id}")
|
@Path("{id}")
|
||||||
UserResource get(@PathParam("id") String id);
|
UserResource get(@PathParam("id") String id);
|
||||||
|
|
||||||
|
|
|
@ -77,6 +77,7 @@ import javax.persistence.LockModeType;
|
||||||
public class JpaUserProvider implements UserProvider, UserCredentialStore {
|
public class JpaUserProvider implements UserProvider, UserCredentialStore {
|
||||||
|
|
||||||
private static final String EMAIL = "email";
|
private static final String EMAIL = "email";
|
||||||
|
private static final String EMAIL_VERIFIED = "emailVerified";
|
||||||
private static final String USERNAME = "username";
|
private static final String USERNAME = "username";
|
||||||
private static final String FIRST_NAME = "firstName";
|
private static final String FIRST_NAME = "firstName";
|
||||||
private static final String LAST_NAME = "lastName";
|
private static final String LAST_NAME = "lastName";
|
||||||
|
@ -685,6 +686,9 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
|
||||||
case UserModel.EMAIL:
|
case UserModel.EMAIL:
|
||||||
restrictions.add(qb.like(from.get("email"), "%" + value + "%"));
|
restrictions.add(qb.like(from.get("email"), "%" + value + "%"));
|
||||||
break;
|
break;
|
||||||
|
case UserModel.EMAIL_VERIFIED:
|
||||||
|
restrictions.add(qb.equal(from.get("emailVerified"), Boolean.parseBoolean(value.toLowerCase())));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -731,6 +735,9 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
|
||||||
case UserModel.EMAIL:
|
case UserModel.EMAIL:
|
||||||
restrictions.add(qb.like(from.get("user").get("email"), "%" + value + "%"));
|
restrictions.add(qb.like(from.get("user").get("email"), "%" + value + "%"));
|
||||||
break;
|
break;
|
||||||
|
case UserModel.EMAIL_VERIFIED:
|
||||||
|
restrictions.add(qb.equal(from.get("emailVerified"), Boolean.parseBoolean(value.toLowerCase())));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -873,6 +880,9 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
|
||||||
predicates.add(builder.like(builder.lower(root.get(key)), "%" + value.toLowerCase() + "%"));
|
predicates.add(builder.like(builder.lower(root.get(key)), "%" + value.toLowerCase() + "%"));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case EMAIL_VERIFIED:
|
||||||
|
predicates.add(builder.equal(root.get(key), Boolean.parseBoolean(value.toLowerCase())));
|
||||||
|
break;
|
||||||
case UserModel.ENABLED:
|
case UserModel.ENABLED:
|
||||||
predicates.add(builder.equal(builder.lower(root.get(key)), Boolean.parseBoolean(value.toLowerCase())));
|
predicates.add(builder.equal(builder.lower(root.get(key)), Boolean.parseBoolean(value.toLowerCase())));
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ public interface UserModel extends RoleMapperModel {
|
||||||
String FIRST_NAME = "firstName";
|
String FIRST_NAME = "firstName";
|
||||||
String LAST_NAME = "lastName";
|
String LAST_NAME = "lastName";
|
||||||
String EMAIL = "email";
|
String EMAIL = "email";
|
||||||
|
String EMAIL_VERIFIED = "emailVerified";
|
||||||
String LOCALE = "locale";
|
String LOCALE = "locale";
|
||||||
String ENABLED = "enabled";
|
String ENABLED = "enabled";
|
||||||
String INCLUDE_SERVICE_ACCOUNT = "keycloak.session.realm.users.query.include_service_account";
|
String INCLUDE_SERVICE_ACCOUNT = "keycloak.session.realm.users.query.include_service_account";
|
||||||
|
|
|
@ -231,6 +231,7 @@ public class UsersResource {
|
||||||
@QueryParam("firstName") String first,
|
@QueryParam("firstName") String first,
|
||||||
@QueryParam("email") String email,
|
@QueryParam("email") String email,
|
||||||
@QueryParam("username") String username,
|
@QueryParam("username") String username,
|
||||||
|
@QueryParam("emailVerified") Boolean emailVerified,
|
||||||
@QueryParam("first") Integer firstResult,
|
@QueryParam("first") Integer firstResult,
|
||||||
@QueryParam("max") Integer maxResults,
|
@QueryParam("max") Integer maxResults,
|
||||||
@QueryParam("enabled") Boolean enabled,
|
@QueryParam("enabled") Boolean enabled,
|
||||||
|
@ -248,7 +249,7 @@ public class UsersResource {
|
||||||
if (search.startsWith(SEARCH_ID_PARAMETER)) {
|
if (search.startsWith(SEARCH_ID_PARAMETER)) {
|
||||||
UserModel userModel = session.users().getUserById(search.substring(SEARCH_ID_PARAMETER.length()).trim(), realm);
|
UserModel userModel = session.users().getUserById(search.substring(SEARCH_ID_PARAMETER.length()).trim(), realm);
|
||||||
if (userModel != null) {
|
if (userModel != null) {
|
||||||
userModels = Arrays.asList(userModel);
|
userModels = Collections.singletonList(userModel);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Map<String, String> attributes = new HashMap<>();
|
Map<String, String> attributes = new HashMap<>();
|
||||||
|
@ -258,7 +259,7 @@ public class UsersResource {
|
||||||
}
|
}
|
||||||
return searchForUser(attributes, realm, userPermissionEvaluator, briefRepresentation, firstResult, maxResults, false);
|
return searchForUser(attributes, realm, userPermissionEvaluator, briefRepresentation, firstResult, maxResults, false);
|
||||||
}
|
}
|
||||||
} else if (last != null || first != null || email != null || username != null || enabled != null || exact != null) {
|
} else if (last != null || first != null || email != null || username != null || emailVerified != null || enabled != null || exact != null) {
|
||||||
Map<String, String> attributes = new HashMap<>();
|
Map<String, String> attributes = new HashMap<>();
|
||||||
if (last != null) {
|
if (last != null) {
|
||||||
attributes.put(UserModel.LAST_NAME, last);
|
attributes.put(UserModel.LAST_NAME, last);
|
||||||
|
@ -278,6 +279,9 @@ public class UsersResource {
|
||||||
if (exact != null) {
|
if (exact != null) {
|
||||||
attributes.put(UserModel.EXACT, exact.toString());
|
attributes.put(UserModel.EXACT, exact.toString());
|
||||||
}
|
}
|
||||||
|
if (emailVerified != null) {
|
||||||
|
attributes.put(UserModel.EMAIL_VERIFIED, emailVerified.toString());
|
||||||
|
}
|
||||||
return searchForUser(attributes, realm, userPermissionEvaluator, briefRepresentation, firstResult, maxResults, true);
|
return searchForUser(attributes, realm, userPermissionEvaluator, briefRepresentation, firstResult, maxResults, true);
|
||||||
} else {
|
} else {
|
||||||
return searchForUser(new HashMap<>(), realm, userPermissionEvaluator, briefRepresentation, firstResult, maxResults, false);
|
return searchForUser(new HashMap<>(), realm, userPermissionEvaluator, briefRepresentation, firstResult, maxResults, false);
|
||||||
|
@ -316,6 +320,7 @@ public class UsersResource {
|
||||||
@QueryParam("lastName") String last,
|
@QueryParam("lastName") String last,
|
||||||
@QueryParam("firstName") String first,
|
@QueryParam("firstName") String first,
|
||||||
@QueryParam("email") String email,
|
@QueryParam("email") String email,
|
||||||
|
@QueryParam("emailVerified") Boolean emailVerified,
|
||||||
@QueryParam("username") String username) {
|
@QueryParam("username") String username) {
|
||||||
UserPermissionEvaluator userPermissionEvaluator = auth.users();
|
UserPermissionEvaluator userPermissionEvaluator = auth.users();
|
||||||
userPermissionEvaluator.requireQuery();
|
userPermissionEvaluator.requireQuery();
|
||||||
|
@ -329,7 +334,7 @@ public class UsersResource {
|
||||||
} else {
|
} else {
|
||||||
return session.users().getUsersCount(search.trim(), realm, auth.groups().getGroupsWithViewPermission());
|
return session.users().getUsersCount(search.trim(), realm, auth.groups().getGroupsWithViewPermission());
|
||||||
}
|
}
|
||||||
} else if (last != null || first != null || email != null || username != null) {
|
} else if (last != null || first != null || email != null || username != null || emailVerified != null) {
|
||||||
Map<String, String> parameters = new HashMap<>();
|
Map<String, String> parameters = new HashMap<>();
|
||||||
if (last != null) {
|
if (last != null) {
|
||||||
parameters.put(UserModel.LAST_NAME, last);
|
parameters.put(UserModel.LAST_NAME, last);
|
||||||
|
@ -343,6 +348,9 @@ public class UsersResource {
|
||||||
if (username != null) {
|
if (username != null) {
|
||||||
parameters.put(UserModel.USERNAME, username);
|
parameters.put(UserModel.USERNAME, username);
|
||||||
}
|
}
|
||||||
|
if (emailVerified != null) {
|
||||||
|
parameters.put(UserModel.EMAIL_VERIFIED, emailVerified.toString());
|
||||||
|
}
|
||||||
if (userPermissionEvaluator.canView()) {
|
if (userPermissionEvaluator.canView()) {
|
||||||
return session.users().getUsersCount(parameters, realm);
|
return session.users().getUsersCount(parameters, realm);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -76,6 +76,7 @@ import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
|
@ -492,6 +493,12 @@ public abstract class AbstractKeycloakTest {
|
||||||
return ApiUtil.createUserWithAdminClient(adminClient.realm(realm), homer);
|
return ApiUtil.createUserWithAdminClient(adminClient.realm(realm), homer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String createUser(String realm, String username, String password, String firstName, String lastName, String email, Consumer<UserRepresentation> customizer) {
|
||||||
|
UserRepresentation user = createUserRepresentation(username, email, firstName, lastName, true, password);
|
||||||
|
customizer.accept(user);
|
||||||
|
return ApiUtil.createUserWithAdminClient(adminClient.realm(realm), user);
|
||||||
|
}
|
||||||
|
|
||||||
public String createUser(String realm, String username, String password, String firstName, String lastName, String email) {
|
public String createUser(String realm, String username, String password, String firstName, String lastName, String email) {
|
||||||
UserRepresentation homer = createUserRepresentation(username, email, firstName, lastName, true, password);
|
UserRepresentation homer = createUserRepresentation(username, email, firstName, lastName, true, password);
|
||||||
return ApiUtil.createUserWithAdminClient(adminClient.realm(realm), homer);
|
return ApiUtil.createUserWithAdminClient(adminClient.realm(realm), homer);
|
||||||
|
|
|
@ -44,6 +44,8 @@ import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
|
import static org.hamcrest.Matchers.empty;
|
||||||
|
import static org.hamcrest.Matchers.not;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
public class UsersTest extends AbstractAdminTest {
|
public class UsersTest extends AbstractAdminTest {
|
||||||
|
@ -56,6 +58,47 @@ public class UsersTest extends AbstractAdminTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://issues.redhat.com/browse/KEYCLOAK-15146
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void findUsersByEmailVerifiedStatus() {
|
||||||
|
|
||||||
|
createUser(realmId, "user1", "password", "user1FirstName", "user1LastName", "user1@example.com", rep -> rep.setEmailVerified(true));
|
||||||
|
createUser(realmId, "user2", "password", "user2FirstName", "user2LastName", "user2@example.com", rep -> rep.setEmailVerified(false));
|
||||||
|
|
||||||
|
boolean emailVerified;
|
||||||
|
emailVerified = true;
|
||||||
|
List<UserRepresentation> usersEmailVerified = realm.users().search(null, null, null, null, emailVerified, null, null, null, true);
|
||||||
|
assertThat(usersEmailVerified, is(not(empty())));
|
||||||
|
assertThat(usersEmailVerified.get(0).getUsername(), is("user1"));
|
||||||
|
|
||||||
|
emailVerified = false;
|
||||||
|
List<UserRepresentation> usersEmailNotVerified = realm.users().search(null, null, null, null, emailVerified, null, null, null, true);
|
||||||
|
assertThat(usersEmailNotVerified, is(not(empty())));
|
||||||
|
assertThat(usersEmailNotVerified.get(0).getUsername(), is("user2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://issues.redhat.com/browse/KEYCLOAK-15146
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void countUsersByEmailVerifiedStatus() {
|
||||||
|
|
||||||
|
createUser(realmId, "user1", "password", "user1FirstName", "user1LastName", "user1@example.com", rep -> rep.setEmailVerified(true));
|
||||||
|
createUser(realmId, "user2", "password", "user2FirstName", "user2LastName", "user2@example.com", rep -> rep.setEmailVerified(false));
|
||||||
|
createUser(realmId, "user3", "password", "user3FirstName", "user3LastName", "user3@example.com", rep -> rep.setEmailVerified(true));
|
||||||
|
|
||||||
|
boolean emailVerified;
|
||||||
|
emailVerified = true;
|
||||||
|
assertThat(realm.users().countEmailVerified(emailVerified), is(2));
|
||||||
|
assertThat(realm.users().count(null,null,null,emailVerified,null), is(2));
|
||||||
|
|
||||||
|
emailVerified = false;
|
||||||
|
assertThat(realm.users().countEmailVerified(emailVerified), is(1));
|
||||||
|
assertThat(realm.users().count(null,null,null,emailVerified,null), is(1));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void countUsersWithViewPermission() {
|
public void countUsersWithViewPermission() {
|
||||||
createUser(realmId, "user1", "password", "user1FirstName", "user1LastName", "user1@example.com");
|
createUser(realmId, "user1", "password", "user1FirstName", "user1LastName", "user1@example.com");
|
||||||
|
|
Loading…
Reference in a new issue