From 914b226d115b20c8a93244e8e5f2c596409117f7 Mon Sep 17 00:00:00 2001 From: Plamen Kostov Date: Fri, 22 May 2020 20:49:46 +0300 Subject: [PATCH] [KEYCLOAK-14282] Create additional filtering for GET /users endpoint for enabled/disabled users --- .../admin/client/resource/UsersResource.java | 1 + .../keycloak/models/jpa/JpaUserProvider.java | 5 +- .../java/org/keycloak/models/UserModel.java | 1 + .../storage/user/UserQueryProvider.java | 1 + .../resources/admin/UsersResource.java | 8 +++ .../keycloak/testsuite/admin/UserTest.java | 60 ++++++++++++++++++- 6 files changed, 74 insertions(+), 2 deletions(-) diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UsersResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UsersResource.java index 36eb962b87..88fe5ec806 100755 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UsersResource.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UsersResource.java @@ -50,6 +50,7 @@ public interface UsersResource { @QueryParam("email") String email, @QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResults, + @QueryParam("enabled") Boolean enabled, @QueryParam("briefRepresentation") Boolean briefRepresentation); @GET 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 925c64cfff..696cc84d96 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 @@ -870,10 +870,13 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore { case LAST_NAME: case EMAIL: if (Boolean.valueOf(attributes.getOrDefault(UserModel.EXACT, Boolean.FALSE.toString()))) { - predicates.add(builder.equal(builder.lower(root.get(key)), value.toLowerCase())); + predicates.add(builder.equal(builder.lower(root.get(key)), value.toLowerCase())); } else { predicates.add(builder.like(builder.lower(root.get(key)), "%" + value.toLowerCase() + "%")); } + break; + case UserModel.ENABLED: + predicates.add(builder.equal(builder.lower(root.get(key)), Boolean.parseBoolean(value.toLowerCase()))); } } diff --git a/server-spi/src/main/java/org/keycloak/models/UserModel.java b/server-spi/src/main/java/org/keycloak/models/UserModel.java index 650608ed34..273e6e0495 100755 --- a/server-spi/src/main/java/org/keycloak/models/UserModel.java +++ b/server-spi/src/main/java/org/keycloak/models/UserModel.java @@ -35,6 +35,7 @@ public interface UserModel extends RoleMapperModel { String LAST_NAME = "lastName"; String EMAIL = "email"; String LOCALE = "locale"; + String ENABLED = "enabled"; String INCLUDE_SERVICE_ACCOUNT = "keycloak.session.realm.users.query.include_service_account"; String GROUPS = "keycloak.session.realm.users.query.groups"; String SEARCH = "keycloak.session.realm.users.query.search"; diff --git a/server-spi/src/main/java/org/keycloak/storage/user/UserQueryProvider.java b/server-spi/src/main/java/org/keycloak/storage/user/UserQueryProvider.java index 06121d9724..445000d4d0 100644 --- a/server-spi/src/main/java/org/keycloak/storage/user/UserQueryProvider.java +++ b/server-spi/src/main/java/org/keycloak/storage/user/UserQueryProvider.java @@ -204,6 +204,7 @@ public interface UserQueryProvider { * "last" - last name * "email" - email * "username" - username + * "enabled" - is user enabled (true/false) * * If possible, implementations should treat the parameter values as patterns i.e. in RDMBS terms use LIKE. * This method is used by the REST API when querying users. diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java index 1083833abc..7e5ed61799 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java @@ -189,6 +189,7 @@ public class UsersResource { * @param first * @param email * @param username + * @param enabled Boolean representing if user is enabled or not * @param first Pagination offset * @param maxResults Maximum results size (defaults to 100) * @return @@ -203,6 +204,7 @@ public class UsersResource { @QueryParam("username") String username, @QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResults, + @QueryParam("enabled") Boolean enabled, @QueryParam("briefRepresentation") Boolean briefRepresentation, @QueryParam("exact") Boolean exact) { UserPermissionEvaluator userPermissionEvaluator = auth.users(); @@ -222,6 +224,9 @@ public class UsersResource { } else { Map attributes = new HashMap<>(); attributes.put(UserModel.SEARCH, search.trim()); + if (enabled != null) { + attributes.put(UserModel.ENABLED, enabled.toString()); + } return searchForUser(attributes, realm, userPermissionEvaluator, briefRepresentation, firstResult, maxResults, false); } } else if (last != null || first != null || email != null || username != null) { @@ -238,6 +243,9 @@ public class UsersResource { if (username != null) { attributes.put(UserModel.USERNAME, username); } + if (enabled != null) { + attributes.put(UserModel.ENABLED, enabled.toString()); + } if (exact != null) { attributes.put(UserModel.EXACT, exact.toString()); } 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 04bbe7e36c..1d57cb30f9 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 @@ -32,7 +32,6 @@ import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RoleMappingResource; import org.keycloak.admin.client.resource.UserResource; import org.keycloak.admin.client.resource.UsersResource; -import org.keycloak.broker.provider.util.SimpleHttp; import org.keycloak.common.VerificationException; import org.keycloak.common.util.Base64; import org.keycloak.common.util.ObjectUtil; @@ -626,6 +625,65 @@ public class UserTest extends AbstractAdminTest { assertEquals(1, users.size()); } + @Test + public void searchByEnabled() { + String userCommonName = "enabled-disabled-user"; + + UserRepresentation user1 = new UserRepresentation(); + user1.setUsername(userCommonName + "1"); + user1.setRequiredActions(Collections.emptyList()); + user1.setEnabled(true); + createUser(user1); + + UserRepresentation user2 = new UserRepresentation(); + user2.setUsername(userCommonName + "2"); + user2.setRequiredActions(Collections.emptyList()); + user2.setEnabled(false); + createUser(user2); + + List enabledUsers = realm.users().search(null, null, null, null, null, null, true, false); + assertEquals(1, enabledUsers.size()); + + List enabledUsersWithFilter = realm.users().search(userCommonName, null, null, null, null, null, true, true); + assertEquals(1, enabledUsersWithFilter.size()); + assertEquals(user1.getUsername(), enabledUsersWithFilter.get(0).getUsername()); + + List disabledUsers = realm.users().search(userCommonName, null, null, null, null, null, false, false); + assertEquals(1, disabledUsers.size()); + assertEquals(user2.getUsername(), disabledUsers.get(0).getUsername()); + + List allUsers = realm.users().search(userCommonName, null, null, null, 0, 100, null, true); + assertEquals(2, allUsers.size()); + } + + @Test + public void searchWithFilters() { + createUser(); + + UserRepresentation user = new UserRepresentation(); + user.setUsername("user2"); + user.setFirstName("First"); + user.setLastName("Last"); + user.setEmail("user2@localhost"); + user.setRequiredActions(Collections.emptyList()); + user.setEnabled(false); + createUser(user); + + List searchFirstNameAndDisabled = realm.users().search(null, "First", null, null, null, null, false, true); + assertEquals(1, searchFirstNameAndDisabled.size()); + assertEquals(user.getUsername(), searchFirstNameAndDisabled.get(0).getUsername()); + + List searchLastNameAndEnabled = realm.users().search(null, null, "Last", null, null, null, true, false); + assertEquals(0, searchLastNameAndEnabled.size()); + + List searchEmailAndDisabled = realm.users().search(null, null, null, "user2@localhost", 0, 50, false, true); + assertEquals(1, searchEmailAndDisabled.size()); + assertEquals(user.getUsername(), searchEmailAndDisabled.get(0).getUsername()); + + List searchInvalidSizeAndDisabled = realm.users().search(null, null, null, null, 10, 20, null, false); + assertEquals(0, searchInvalidSizeAndDisabled.size()); + } + @Test public void searchById() { String expectedUserId = createUsers().get(0);