Browse Source

HLRC: Add get users action (#36332)

This commit adds get user action to the high level rest client.
Nick Knize 6 years ago
parent
commit
4b17055035

+ 29 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java

@@ -50,6 +50,8 @@ import org.elasticsearch.client.security.GetSslCertificatesRequest;
 import org.elasticsearch.client.security.GetSslCertificatesResponse;
 import org.elasticsearch.client.security.GetUserPrivilegesRequest;
 import org.elasticsearch.client.security.GetUserPrivilegesResponse;
+import org.elasticsearch.client.security.GetUsersRequest;
+import org.elasticsearch.client.security.GetUsersResponse;
 import org.elasticsearch.client.security.HasPrivilegesRequest;
 import org.elasticsearch.client.security.HasPrivilegesResponse;
 import org.elasticsearch.client.security.InvalidateTokenRequest;
@@ -81,6 +83,33 @@ public final class SecurityClient {
         this.restHighLevelClient = restHighLevelClient;
     }
 
+    /**
+     * Get a user, or list of users, in the native realm synchronously.
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-user.html">
+     * the docs</a> for more information.
+     * @param request the request with the user's name
+     * @param options the request options (e.g., headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @return the response from the get users call
+     * @throws IOException in case there is a problem sending the request or parsing back the response
+     */
+    public GetUsersResponse getUsers(GetUsersRequest request, RequestOptions options) throws IOException {
+        return restHighLevelClient.performRequestAndParseEntity(request, SecurityRequestConverters::getUsers, options,
+            GetUsersResponse::fromXContent, emptySet());
+    }
+
+    /**
+     * Get a user, or list of users, in the native realm asynchronously.
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-user.html">
+     * the docs</a> for more information.
+     * @param request the request with the user's name
+     * @param options the request options (e.g., headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @param listener the listener to be notified upon request completion
+     */
+    public void getUsersAsync(GetUsersRequest request, RequestOptions options, ActionListener<GetUsersResponse> listener) {
+        restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::getUsers, options,
+            GetUsersResponse::fromXContent, listener, emptySet());
+    }
+
     /**
      * Create/update a user in the native realm synchronously.
      * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-users.html">

+ 10 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityRequestConverters.java

@@ -36,6 +36,7 @@ import org.elasticsearch.client.security.EnableUserRequest;
 import org.elasticsearch.client.security.GetPrivilegesRequest;
 import org.elasticsearch.client.security.GetRoleMappingsRequest;
 import org.elasticsearch.client.security.GetRolesRequest;
+import org.elasticsearch.client.security.GetUsersRequest;
 import org.elasticsearch.client.security.HasPrivilegesRequest;
 import org.elasticsearch.client.security.InvalidateTokenRequest;
 import org.elasticsearch.client.security.PutPrivilegesRequest;
@@ -67,6 +68,15 @@ final class SecurityRequestConverters {
         return request;
     }
 
+    static Request getUsers(GetUsersRequest getUsersRequest) {
+        RequestConverters.EndpointBuilder builder = new RequestConverters.EndpointBuilder()
+            .addPathPartAsIs("_security/user");
+        if (getUsersRequest.getUsernames().size() > 0) {
+            builder.addPathPart(Strings.collectionToCommaDelimitedString(getUsersRequest.getUsernames()));
+        }
+        return new Request(HttpGet.METHOD_NAME, builder.build());
+    }
+
     static Request putUser(PutUserRequest putUserRequest) throws IOException {
         String endpoint = new RequestConverters.EndpointBuilder()
             .addPathPartAsIs("_security/user")

+ 58 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetUsersRequest.java

@@ -0,0 +1,58 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.elasticsearch.client.security;
+
+import org.elasticsearch.client.Validatable;
+import org.elasticsearch.common.util.set.Sets;
+
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Request object to retrieve users from the native realm
+ */
+public class GetUsersRequest implements Validatable {
+    private final Set<String> usernames;
+
+    public GetUsersRequest(final String... usernames) {
+        if (usernames != null) {
+            this.usernames = Collections.unmodifiableSet(Sets.newHashSet(usernames));
+        } else {
+            this.usernames = Collections.emptySet();
+        }
+    }
+
+    public Set<String> getUsernames() {
+        return usernames;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof GetUsersRequest)) return false;
+        GetUsersRequest that = (GetUsersRequest) o;
+        return Objects.equals(usernames, that.usernames);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(usernames);
+    }
+}

+ 134 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetUsersResponse.java

@@ -0,0 +1,134 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.elasticsearch.client.security;
+
+import org.elasticsearch.client.security.user.User;
+import org.elasticsearch.common.Nullable;
+import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.XContentParser.Token;
+import org.elasticsearch.common.xcontent.XContentParserUtils;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
+import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg;
+
+/**
+ * Response when requesting zero or more users.
+ * Returns a List of {@link User} objects
+ */
+public class GetUsersResponse {
+    private final Set<User> users;
+    private final Set<User> enabledUsers;
+
+    public GetUsersResponse(Set<User> users, Set<User> enabledUsers) {
+        this.users = Collections.unmodifiableSet(users);
+        this.enabledUsers = Collections.unmodifiableSet(enabledUsers);
+    }
+
+    public Set<User> getUsers() {
+        return users;
+    }
+
+    public Set<User> getEnabledUsers() {
+        return enabledUsers;
+    }
+
+    public static GetUsersResponse fromXContent(XContentParser parser) throws IOException {
+        XContentParserUtils.ensureExpectedToken(Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation);
+        final Set<User> users = new HashSet<>();
+        final Set<User> enabledUsers = new HashSet<>();
+        Token token;
+        while ((token = parser.nextToken()) != Token.END_OBJECT) {
+            XContentParserUtils.ensureExpectedToken(Token.FIELD_NAME, token, parser::getTokenLocation);
+            ParsedUser parsedUser = USER_PARSER.parse(parser, parser.currentName());
+            users.add(parsedUser.user);
+            if (parsedUser.enabled) {
+                enabledUsers.add(parsedUser.user);
+            }
+        }
+        return new GetUsersResponse(users, enabledUsers);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof GetUsersResponse)) return false;
+        GetUsersResponse that = (GetUsersResponse) o;
+        return Objects.equals(users, that.users);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(users);
+    }
+
+    public static final ParseField USERNAME = new ParseField("username");
+    public static final ParseField ROLES = new ParseField("roles");
+    public static final ParseField FULL_NAME = new ParseField("full_name");
+    public static final ParseField EMAIL = new ParseField("email");
+    public static final ParseField METADATA = new ParseField("metadata");
+    public static final ParseField ENABLED = new ParseField("enabled");
+
+    @SuppressWarnings("unchecked")
+    public static final ConstructingObjectParser<ParsedUser, String> USER_PARSER = new ConstructingObjectParser<>("user_info",
+        (constructorObjects) -> {
+            int i = 0;
+            final String username = (String) constructorObjects[i++];
+            final Collection<String> roles = (Collection<String>) constructorObjects[i++];
+            final Map<String, Object> metadata = (Map<String, Object>) constructorObjects[i++];
+            final Boolean enabled = (Boolean) constructorObjects[i++];
+            final String fullName = (String) constructorObjects[i++];
+            final String email = (String) constructorObjects[i++];
+            return new ParsedUser(username, roles, metadata, enabled, fullName, email);
+        });
+
+    static {
+        USER_PARSER.declareString(constructorArg(), USERNAME);
+        USER_PARSER.declareStringArray(constructorArg(), ROLES);
+        USER_PARSER.declareObject(constructorArg(), (parser, c) -> parser.map(), METADATA);
+        USER_PARSER.declareBoolean(constructorArg(), ENABLED);
+        USER_PARSER.declareStringOrNull(optionalConstructorArg(), FULL_NAME);
+        USER_PARSER.declareStringOrNull(optionalConstructorArg(), EMAIL);
+    }
+
+    protected static final class ParsedUser {
+        protected User user;
+        protected boolean enabled;
+
+        public ParsedUser(String username, Collection<String> roles, Map<String, Object> metadata, Boolean enabled,
+                          @Nullable String fullName, @Nullable String email) {
+            String checkedUsername = username = Objects.requireNonNull(username, "`username` is required, cannot be null");
+            Collection<String> checkedRoles = Collections.unmodifiableSet(new HashSet<>(
+                Objects.requireNonNull(roles, "`roles` is required, cannot be null. Pass an empty Collection instead.")));
+            Map<String, Object> checkedMetadata = Collections
+                .unmodifiableMap(Objects.requireNonNull(metadata, "`metadata` is required, cannot be null. Pass an empty map instead."));
+            this.user = new User(checkedUsername, checkedRoles, checkedMetadata, fullName, email);
+            this.enabled = enabled;
+        }
+    }
+}

+ 0 - 1
client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/User.java

@@ -29,7 +29,6 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 
-
 /**
  * A user to be utilized with security APIs.
  * Can be an existing authenticated user or it can be a new user to be enrolled to the native realm.

+ 28 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityIT.java

@@ -28,6 +28,8 @@ import org.elasticsearch.client.security.DeleteUserRequest;
 import org.elasticsearch.client.security.DeleteUserResponse;
 import org.elasticsearch.client.security.GetRolesRequest;
 import org.elasticsearch.client.security.GetRolesResponse;
+import org.elasticsearch.client.security.GetUsersRequest;
+import org.elasticsearch.client.security.GetUsersResponse;
 import org.elasticsearch.client.security.PutRoleRequest;
 import org.elasticsearch.client.security.PutRoleResponse;
 import org.elasticsearch.client.security.PutUserRequest;
@@ -42,6 +44,7 @@ import org.elasticsearch.client.security.user.privileges.IndicesPrivilegesTests;
 import org.elasticsearch.client.security.user.privileges.Role;
 import org.elasticsearch.common.CharArrays;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Base64;
 import java.util.HashMap;
@@ -74,6 +77,22 @@ public class SecurityIT extends ESRestHighLevelClientTestCase {
         highLevelClient().getLowLevelClient().performRequest(deleteUserRequest);
     }
 
+    public void testGetUser() throws Exception {
+        final SecurityClient securityClient = highLevelClient().security();
+        // create user
+        final PutUserRequest putUserRequest = randomPutUserRequest(randomBoolean());
+        final PutUserResponse putUserResponse = execute(putUserRequest, securityClient::putUser, securityClient::putUserAsync);
+        // assert user created
+        assertThat(putUserResponse.isCreated(), is(true));
+        // get user
+        final GetUsersRequest getUsersRequest = new GetUsersRequest(putUserRequest.getUser().getUsername());
+        final GetUsersResponse getUsersResponse = execute(getUsersRequest, securityClient::getUsers, securityClient::getUsersAsync);
+        // assert user was correctly retrieved
+        ArrayList<User> users = new ArrayList<>();
+        users.addAll(getUsersResponse.getUsers());
+        assertThat(users.get(0), is(putUserRequest.getUser()));
+    }
+
     public void testAuthenticate() throws Exception {
         final SecurityClient securityClient = highLevelClient().security();
         // test fixture: put enabled user
@@ -89,6 +108,15 @@ public class SecurityIT extends ESRestHighLevelClientTestCase {
         assertThat(authenticateResponse.getUser(), is(putUserRequest.getUser()));
         assertThat(authenticateResponse.enabled(), is(true));
 
+        // get user
+        final GetUsersRequest getUsersRequest =
+            new GetUsersRequest(putUserRequest.getUser().getUsername());
+        final GetUsersResponse getUsersResponse =
+            execute(getUsersRequest, securityClient::getUsers, securityClient::getUsersAsync);
+        ArrayList<User> users = new ArrayList<>();
+        users.addAll(getUsersResponse.getUsers());
+        assertThat(users.get(0), is(putUserRequest.getUser()));
+
         // delete user
         final DeleteUserRequest deleteUserRequest =
             new DeleteUserRequest(putUserRequest.getUser().getUsername(), putUserRequest.getRefreshPolicy());

+ 16 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityRequestConvertersTests.java

@@ -34,6 +34,7 @@ import org.elasticsearch.client.security.EnableUserRequest;
 import org.elasticsearch.client.security.GetPrivilegesRequest;
 import org.elasticsearch.client.security.GetRoleMappingsRequest;
 import org.elasticsearch.client.security.GetRolesRequest;
+import org.elasticsearch.client.security.GetUsersRequest;
 import org.elasticsearch.client.security.PutPrivilegesRequest;
 import org.elasticsearch.client.security.PutRoleMappingRequest;
 import org.elasticsearch.client.security.PutRoleRequest;
@@ -101,6 +102,21 @@ public class SecurityRequestConvertersTests extends ESTestCase {
         assertNull(request.getEntity());
     }
 
+    public void testGetUsers() {
+        final String[] users = randomArray(0, 5, String[]::new, () -> randomAlphaOfLength(5));
+        GetUsersRequest getUsersRequest = new GetUsersRequest(users);
+        Request request = SecurityRequestConverters.getUsers(getUsersRequest);
+        assertEquals(HttpGet.METHOD_NAME, request.getMethod());
+        if (users.length == 0) {
+            assertEquals("/_security/user", request.getEndpoint());
+        } else {
+            assertEquals("/_security/user/" + Strings.collectionToCommaDelimitedString(getUsersRequest.getUsernames()),
+                request.getEndpoint());
+        }
+        assertNull(request.getEntity());
+        assertEquals(Collections.emptyMap(), request.getParameters());
+    }
+
     public void testPutRoleMapping() throws IOException {
         final String username = randomAlphaOfLengthBetween(4, 7);
         final String rolename = randomAlphaOfLengthBetween(4, 7);

+ 92 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java

@@ -54,6 +54,8 @@ import org.elasticsearch.client.security.GetRolesRequest;
 import org.elasticsearch.client.security.GetRolesResponse;
 import org.elasticsearch.client.security.GetSslCertificatesResponse;
 import org.elasticsearch.client.security.GetUserPrivilegesResponse;
+import org.elasticsearch.client.security.GetUsersRequest;
+import org.elasticsearch.client.security.GetUsersResponse;
 import org.elasticsearch.client.security.HasPrivilegesRequest;
 import org.elasticsearch.client.security.HasPrivilegesResponse;
 import org.elasticsearch.client.security.InvalidateTokenRequest;
@@ -109,6 +111,96 @@ import static org.hamcrest.Matchers.nullValue;
 
 public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
 
+    public void testGetUsers() throws Exception {
+        final RestHighLevelClient client = highLevelClient();
+        String[] usernames = new String[] {"user1", "user2", "user3"};
+        addUser(client, usernames[0], randomAlphaOfLength(4));
+        addUser(client, usernames[1], randomAlphaOfLength(4));
+        addUser(client, usernames[2], randomAlphaOfLength(4));
+        {
+            //tag::get-users-request
+            GetUsersRequest request = new GetUsersRequest(usernames[0]);
+            //end::get-users-request
+            //tag::get-users-execute
+            GetUsersResponse response = client.security().getUsers(request, RequestOptions.DEFAULT);
+            //end::get-users-execute
+            //tag::get-users-response
+            List<User> users = new ArrayList<>(1);
+            users.addAll(response.getUsers());
+            //end::get-users-response
+
+            assertNotNull(response);
+            assertThat(users.size(), equalTo(1));
+            assertThat(users.get(0), is(usernames[0]));
+        }
+
+        {
+            //tag::get-users-list-request
+            GetUsersRequest request = new GetUsersRequest(usernames);
+            GetUsersResponse response = client.security().getUsers(request, RequestOptions.DEFAULT);
+            //end::get-users-list-request
+
+            List<User> users = new ArrayList<>(3);
+            users.addAll(response.getUsers());
+            assertNotNull(response);
+            assertThat(users.size(), equalTo(3));
+            assertThat(users.get(0).getUsername(), equalTo(usernames[0]));
+            assertThat(users.get(1).getUsername(), equalTo(usernames[1]));
+            assertThat(users.get(2).getUsername(), equalTo(usernames[2]));
+            assertThat(users.size(), equalTo(3));
+        }
+
+        {
+            //tag::get-users-all-request
+            GetUsersRequest request = new GetUsersRequest();
+            GetUsersResponse response = client.security().getUsers(request, RequestOptions.DEFAULT);
+            //end::get-users-all-request
+
+            List<User> users = new ArrayList<>(3);
+            users.addAll(response.getUsers());
+            assertNotNull(response);
+            // 4 system users plus the three we created
+            assertThat(users.size(), equalTo(7));
+        }
+
+        {
+            GetUsersRequest request = new GetUsersRequest(usernames[0]);
+            ActionListener<GetUsersResponse> listener;
+
+            //tag::get-roles-execute-listener
+            listener = new ActionListener<GetUsersResponse>() {
+                @Override
+                public void onResponse(GetUsersResponse getRolesResponse) {
+                    // <1>
+                }
+
+                @Override
+                public void onFailure(Exception e) {
+                    // <2>
+                }
+            };
+            //end::get-users-execute-listener
+
+            assertNotNull(listener);
+
+            // Replace the empty listener by a blocking listener in test
+            final PlainActionFuture<GetUsersResponse> future = new PlainActionFuture<>();
+            listener = future;
+
+            //tag::get-users-execute-async
+            client.security().getUsersAsync(request, RequestOptions.DEFAULT, listener); // <1>
+            //end::get-users-execute-async
+
+            final GetUsersResponse response = future.get(30, TimeUnit.SECONDS);
+            List<User> users = new ArrayList<>(1);
+            users.addAll(response.getUsers());
+            assertNotNull(response);
+            assertThat(users.size(), equalTo(1));
+            assertThat(users.get(0).getUsername(), equalTo(usernames[0]));
+        }
+    }
+
+
     public void testPutUser() throws Exception {
         RestHighLevelClient client = highLevelClient();
 

+ 11 - 11
client/rest-high-level/src/test/java/org/elasticsearch/client/security/AuthenticateResponseTests.java

@@ -38,12 +38,12 @@ public class AuthenticateResponseTests extends ESTestCase {
 
     public void testFromXContent() throws IOException {
         xContentTester(
-                this::createParser,
-                this::createTestInstance,
-                this::toXContent,
-                AuthenticateResponse::fromXContent)
-                .supportsUnknownFields(false)
-                .test();
+            this::createParser,
+            this::createTestInstance,
+            this::toXContent,
+            AuthenticateResponse::fromXContent)
+            .supportsUnknownFields(false)
+            .test();
     }
 
     public void testEqualsAndHashCode() {
@@ -108,7 +108,7 @@ public class AuthenticateResponseTests extends ESTestCase {
     private AuthenticateResponse copy(AuthenticateResponse response) {
         final User originalUser = response.getUser();
         final User copyUser = new User(originalUser.getUsername(), originalUser.getRoles(), originalUser.getMetadata(),
-                originalUser.getFullName(), originalUser.getEmail());
+            originalUser.getFullName(), originalUser.getEmail());
         return new AuthenticateResponse(copyUser, response.enabled(), response.getAuthenticationRealm(),
             response.getLookupRealm());
     }
@@ -117,9 +117,9 @@ public class AuthenticateResponseTests extends ESTestCase {
         final User originalUser = response.getUser();
         switch (randomIntBetween(1, 8)) {
             case 1:
-            return new AuthenticateResponse(new User(originalUser.getUsername() + "wrong", originalUser.getRoles(),
+                return new AuthenticateResponse(new User(originalUser.getUsername() + "wrong", originalUser.getRoles(),
                     originalUser.getMetadata(), originalUser.getFullName(), originalUser.getEmail()), response.enabled(),
-                response.getAuthenticationRealm(), response.getLookupRealm());
+                    response.getAuthenticationRealm(), response.getLookupRealm());
             case 2:
                 final Collection<String> wrongRoles = new ArrayList<>(originalUser.getRoles());
                 wrongRoles.add(randomAlphaOfLengthBetween(1, 4));
@@ -134,11 +134,11 @@ public class AuthenticateResponseTests extends ESTestCase {
                     response.getLookupRealm());
             case 4:
                 return new AuthenticateResponse(new User(originalUser.getUsername(), originalUser.getRoles(), originalUser.getMetadata(),
-                        originalUser.getFullName() + "wrong", originalUser.getEmail()), response.enabled(),
+                    originalUser.getFullName() + "wrong", originalUser.getEmail()), response.enabled(),
                     response.getAuthenticationRealm(), response.getLookupRealm());
             case 5:
                 return new AuthenticateResponse(new User(originalUser.getUsername(), originalUser.getRoles(), originalUser.getMetadata(),
-                        originalUser.getFullName(), originalUser.getEmail() + "wrong"), response.enabled(),
+                    originalUser.getFullName(), originalUser.getEmail() + "wrong"), response.enabled(),
                     response.getAuthenticationRealm(), response.getLookupRealm());
             case 6:
                 return new AuthenticateResponse(new User(originalUser.getUsername(), originalUser.getRoles(), originalUser.getMetadata(),

+ 53 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetUsersRequestTests.java

@@ -0,0 +1,53 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.elasticsearch.client.security;
+
+import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.test.EqualsHashCodeTestUtils;
+
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.equalTo;
+
+public class GetUsersRequestTests extends ESTestCase {
+
+    public void testGetUsersRequest() {
+        final String[] users = randomArray(0, 5, String[]::new, () -> randomAlphaOfLength(5));
+        GetUsersRequest getUsersRequest = new GetUsersRequest(users);
+        assertThat(getUsersRequest.getUsernames().size(), equalTo(users.length));
+        assertThat(getUsersRequest.getUsernames(), containsInAnyOrder(users));
+    }
+
+    public void testEqualsHashCode() {
+        final String[] users = randomArray(0, 5, String[]::new, () -> randomAlphaOfLength(5));
+        final GetUsersRequest getUsersRequest = new GetUsersRequest(users);
+        assertNotNull(getUsersRequest);
+        EqualsHashCodeTestUtils.checkEqualsAndHashCode(getUsersRequest, (original) -> {
+            return new GetUsersRequest(original.getUsernames().toArray(new String[0]));
+        });
+        EqualsHashCodeTestUtils.checkEqualsAndHashCode(getUsersRequest, (original) -> {
+            return new GetUsersRequest(original.getUsernames().toArray(new String[0]));
+        }, GetUsersRequestTests::mutateTestItem);
+    }
+
+    private static GetUsersRequest mutateTestItem(GetUsersRequest original) {
+        final int minRoles = original.getUsernames().isEmpty() ? 1 : 0;
+        return new GetUsersRequest(randomArray(minRoles, 5, String[]::new, () -> randomAlphaOfLength(6)));
+    }
+
+}

+ 126 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetUsersResponseTests.java

@@ -0,0 +1,126 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.elasticsearch.client.security;
+
+import org.elasticsearch.client.security.user.User;
+import org.elasticsearch.common.xcontent.DeprecationHandler;
+import org.elasticsearch.common.xcontent.NamedXContentRegistry;
+import org.elasticsearch.common.xcontent.XContentType;
+import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.test.EqualsHashCodeTestUtils;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.hamcrest.Matchers.equalTo;
+
+/** tests the Response for getting users from the security HLRC */
+public class GetUsersResponseTests extends ESTestCase {
+    public void testFromXContent() throws IOException {
+        String json =
+            "{\n" +
+                "  \"jacknich\": {\n" +
+                "    \"username\": \"jacknich\",\n" +
+                "    \"roles\": [\n" +
+                "      \"admin\", \"other_role1\"\n" +
+                "    ],\n" +
+                "    \"full_name\": \"Jack Nicholson\",\n" +
+                "    \"email\": \"jacknich@example.com\",\n" +
+                "    \"metadata\": { \"intelligence\" : 7 },\n" +
+                "    \"enabled\": true\n" +
+                "  }\n" +
+                "}";
+        final GetUsersResponse response = GetUsersResponse.fromXContent((XContentType.JSON.xContent().createParser(
+            new NamedXContentRegistry(Collections.emptyList()), new DeprecationHandler() {
+                @Override
+                public void usedDeprecatedName(String usedName, String modernName) {
+                }
+
+                @Override
+                public void usedDeprecatedField(String usedName, String replacedWith) {
+                }
+            }, json)));
+        assertThat(response.getUsers().size(), equalTo(1));
+        final User user = response.getUsers().iterator().next();
+        assertThat(user.getUsername(), equalTo("jacknich"));
+        assertThat(user.getRoles().size(), equalTo(2));
+        assertThat(user.getFullName(), equalTo("Jack Nicholson"));
+        assertThat(user.getEmail(), equalTo("jacknich@example.com"));
+        final Map<String, Object> metadata = new HashMap<>();
+        metadata.put("intelligence", 7);
+        assertThat(metadata, equalTo(user.getMetadata()));
+    }
+
+    public void testEqualsHashCode() {
+        final Set<User> users = new HashSet<>();
+        final Set<User> enabledUsers = new HashSet<>();
+        Map<String, Object> metadata = new HashMap<>();
+        metadata.put("intelligence", 1);
+        final User user1 = new User("testUser1", Arrays.asList(new String[] {"admin", "other_role1"}),
+            metadata, "Test User 1", null);
+        users.add(user1);
+        enabledUsers.add(user1);
+        Map<String, Object> metadata2 = new HashMap<>();
+        metadata2.put("intelligence", 9);
+        metadata2.put("specialty", "geo");
+        final User user2 = new User("testUser2", Arrays.asList(new String[] {"admin"}),
+            metadata, "Test User 2", "testuser2@example.com");
+        users.add(user2);
+        enabledUsers.add(user2);
+        final GetUsersResponse getUsersResponse = new GetUsersResponse(users, enabledUsers);
+        assertNotNull(getUsersResponse);
+        EqualsHashCodeTestUtils.checkEqualsAndHashCode(getUsersResponse, (original) -> {
+            return new GetUsersResponse(original.getUsers(), original.getEnabledUsers());
+        });
+        EqualsHashCodeTestUtils.checkEqualsAndHashCode(getUsersResponse, (original) -> {
+            return new GetUsersResponse(original.getUsers(), original.getEnabledUsers());
+        }, GetUsersResponseTests::mutateTestItem);
+    }
+
+    private static GetUsersResponse mutateTestItem(GetUsersResponse original) {
+        if (randomBoolean()) {
+            final Set<User> users = new HashSet<>();
+            final Set<User> enabledUsers = new HashSet<>();
+            Map<String, Object> metadata = new HashMap<>();
+            metadata.put("intelligence", 1);
+            final User user1 = new User("testUser1", Arrays.asList(new String[] {"admin", "other_role1"}),
+                metadata, "Test User 1", null);
+            users.add(user1);
+            enabledUsers.add(user1);
+            return new GetUsersResponse(users, enabledUsers);
+        }
+        Map<String, Object> metadata = new HashMap<>();
+        metadata.put("intelligence", 5);  // change intelligence
+        final User user1 = new User("testUser1", Arrays.asList(new String[] {"admin", "other_role1"}),
+            metadata, "Test User 1", null);
+        Set<User> newUsers = original.getUsers().stream().collect(Collectors.toSet());
+        Set<User> enabledUsers = original.getEnabledUsers().stream().collect(Collectors.toSet());
+        newUsers.clear();
+        enabledUsers.clear();
+        newUsers.add(user1);
+        enabledUsers.add(user1);
+        return new GetUsersResponse(newUsers, enabledUsers);
+    }
+}

+ 48 - 0
docs/java-rest/high-level/security/get-users.asciidoc

@@ -0,0 +1,48 @@
+
+--
+:api: get-users
+:request: GetUsersRequest
+:respnse: GetUsersResponse
+--
+
+[id="{upid}-{api}"]
+=== Get Users API
+
+[id="{upid}-{api}-request"]
+==== Get Users Request
+
+Retrieving a user can be performed using the `security().getUsers()`
+method and by setting the username on +{request}+:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-request]
+--------------------------------------------------
+
+Retrieving multiple users can be performed using the `security().getUsers()`
+method and by setting multiple usernames on +{request}+:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-list-request]
+--------------------------------------------------
+
+Retrieving all users can be performed using the `security().getUsers()`
+method without specifying any usernames on +{request}+:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-all-request]
+--------------------------------------------------
+
+include::../execution.asciidoc[]
+
+[id="{upid}-{api}-response"]
+==== Get Users Response
+
+The returned +{response}+ allows getting information about the retrieved users as follows.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-response]
+--------------------------------------------------

+ 2 - 0
docs/java-rest/high-level/supported-apis.asciidoc

@@ -387,6 +387,7 @@ include::rollup/get_rollup_index_caps.asciidoc[]
 The Java High Level REST Client supports the following Security APIs:
 
 * <<java-rest-high-security-put-user>>
+* <<{upid}-get-users>>
 * <<{upid}-delete-user>>
 * <<java-rest-high-security-enable-user>>
 * <<java-rest-high-security-disable-user>>
@@ -410,6 +411,7 @@ The Java High Level REST Client supports the following Security APIs:
 * <<{upid}-delete-privileges>>
 
 include::security/put-user.asciidoc[]
+include::security/get-users.asciidoc[]
 include::security/delete-user.asciidoc[]
 include::security/enable-user.asciidoc[]
 include::security/disable-user.asciidoc[]