Selaa lähdekoodia

[HLRC] Add support for get roles API (#35787)

This commits adds support for the Get Roles API to the HLRC

Relates: #29827
Ioannis Kakavas 7 vuotta sitten
vanhempi
commit
8daa854f90

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

@@ -42,6 +42,8 @@ import org.elasticsearch.client.security.GetPrivilegesRequest;
 import org.elasticsearch.client.security.GetPrivilegesResponse;
 import org.elasticsearch.client.security.GetRoleMappingsRequest;
 import org.elasticsearch.client.security.GetRoleMappingsResponse;
+import org.elasticsearch.client.security.GetRolesRequest;
+import org.elasticsearch.client.security.GetRolesResponse;
 import org.elasticsearch.client.security.GetSslCertificatesRequest;
 import org.elasticsearch.client.security.GetSslCertificatesResponse;
 import org.elasticsearch.client.security.HasPrivilegesRequest;
@@ -407,6 +409,35 @@ public final class SecurityClient {
                 DeleteRoleMappingResponse::fromXContent, emptySet());
     }
 
+    /**
+     * Asynchronously retrieves roles from the native roles store.
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-role.html">
+     * the docs</a> for more.
+     *
+     * @param request  the request with the roles to get
+     * @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 getRolesAsync(GetRolesRequest request, RequestOptions options, ActionListener<GetRolesResponse> listener) {
+        restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::getRoles, options,
+            GetRolesResponse::fromXContent, listener, emptySet());
+    }
+
+    /**
+     * Retrieves roles from the native roles store.
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-role.html">
+     * the docs</a> for more.
+     *
+     * @param request the request with the roles to get
+     * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @return the response from the delete role call
+     * @throws IOException in case there is a problem sending the request or parsing back the response
+     */
+    public GetRolesResponse getRoles(final GetRolesRequest request, final RequestOptions options) throws IOException {
+        return restHighLevelClient.performRequestAndParseEntity(request, SecurityRequestConverters::getRoles, options,
+            GetRolesResponse::fromXContent, emptySet());
+    }
+
     /**
      * Asynchronously delete a role mapping.
      * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-delete-role-mapping.html">

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

@@ -32,6 +32,7 @@ import org.elasticsearch.client.security.GetPrivilegesRequest;
 import org.elasticsearch.client.security.DeleteRoleMappingRequest;
 import org.elasticsearch.client.security.DeleteRoleRequest;
 import org.elasticsearch.client.security.InvalidateTokenRequest;
+import org.elasticsearch.client.security.GetRolesRequest;
 import org.elasticsearch.client.security.PutRoleMappingRequest;
 import org.elasticsearch.client.security.HasPrivilegesRequest;
 import org.elasticsearch.client.security.DisableUserRequest;
@@ -170,6 +171,15 @@ final class SecurityRequestConverters {
         return request;
     }
 
+    static Request getRoles(GetRolesRequest getRolesRequest) {
+        RequestConverters.EndpointBuilder builder = new RequestConverters.EndpointBuilder();
+        builder.addPathPartAsIs("_xpack/security/role");
+        if (getRolesRequest.getRoleNames().size() > 0) {
+            builder.addPathPart(Strings.collectionToCommaDelimitedString(getRolesRequest.getRoleNames()));
+        }
+        return new Request(HttpGet.METHOD_NAME, builder.build());
+    }
+
     static Request createToken(CreateTokenRequest createTokenRequest) throws IOException {
         Request request = new Request(HttpPost.METHOD_NAME, "/_xpack/security/oauth2/token");
         request.setEntity(createEntity(createTokenRequest, REQUEST_BODY_CONTENT_TYPE));

+ 64 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetRolesRequest.java

@@ -0,0 +1,64 @@
+/*
+ * 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 roles from the native roles store
+ */
+public final class GetRolesRequest implements Validatable {
+
+    private final Set<String> roleNames;
+
+    public GetRolesRequest(final String... roleNames) {
+        if (roleNames != null) {
+            this.roleNames = Collections.unmodifiableSet(Sets.newHashSet(roleNames));
+        } else {
+            this.roleNames = Collections.emptySet();
+        }
+    }
+
+    public Set<String> getRoleNames() {
+        return roleNames;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final GetRolesRequest that = (GetRolesRequest) o;
+        return Objects.equals(roleNames, that.roleNames);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(roleNames);
+    }
+}

+ 71 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetRolesResponse.java

@@ -0,0 +1,71 @@
+/*
+ * 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.privileges.Role;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.XContentParserUtils;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Response when requesting one or more roles.
+ * Returns a List of {@link Role} objects
+ */
+public final class GetRolesResponse {
+
+    private final List<Role> roles;
+
+    public GetRolesResponse(List<Role> roles) {
+        this.roles = Collections.unmodifiableList(roles);
+    }
+
+    public List<Role> getRoles() {
+        return roles;
+    }
+
+    public static GetRolesResponse fromXContent(XContentParser parser) throws IOException {
+        XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation);
+        final List<Role> roles = new ArrayList<>();
+        XContentParser.Token token;
+        while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+            XContentParserUtils.ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation);
+            roles.add(Role.PARSER.parse(parser, parser.currentName()));
+        }
+        return new GetRolesResponse(roles);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        GetRolesResponse response = (GetRolesResponse) o;
+        return Objects.equals(roles, response.roles);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(roles);
+    }
+}

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

@@ -249,7 +249,7 @@ public final class IndicesPrivileges implements ToXContentObject {
         private @Nullable Collection<String> deniedFields = null;
         private @Nullable String query = null;
 
-        private Builder() {
+        public Builder() {
         }
 
         public Builder indices(String... indices) {

+ 96 - 52
client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/Role.java

@@ -21,15 +21,11 @@ package org.elasticsearch.client.security.user.privileges;
 
 import org.elasticsearch.common.Nullable;
 import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.xcontent.ConstructingObjectParser;
-import org.elasticsearch.common.xcontent.ToXContentObject;
-import org.elasticsearch.common.xcontent.XContentBuilder;
-import org.elasticsearch.common.xcontent.XContentHelper;
 import org.elasticsearch.common.xcontent.XContentParser;
-import org.elasticsearch.common.xcontent.XContentType;
 import org.elasticsearch.common.xcontent.ObjectParser.ValueType;
 
-import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -42,10 +38,9 @@ import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constru
 import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg;
 
 /**
- * Represents an aggregation of privileges. This does not have a name
- * identifier.
+ * Represents an aggregation of privileges.
  */
-public final class Role implements ToXContentObject {
+public final class Role {
 
     public static final ParseField CLUSTER = new ParseField("cluster");
     public static final ParseField GLOBAL = new ParseField("global");
@@ -53,10 +48,11 @@ public final class Role implements ToXContentObject {
     public static final ParseField APPLICATIONS = new ParseField("applications");
     public static final ParseField RUN_AS = new ParseField("run_as");
     public static final ParseField METADATA = new ParseField("metadata");
+    public static final ParseField TRANSIENT_METADATA = new ParseField("transient_metadata");
 
     @SuppressWarnings("unchecked")
-    private static final ConstructingObjectParser<Role, Void> PARSER = new ConstructingObjectParser<>("role_descriptor", false,
-            constructorObjects -> {
+    public static final ConstructingObjectParser<Role, String> PARSER = new ConstructingObjectParser<>("role_descriptor", false,
+        (constructorObjects, roleName) -> {
                 // Don't ignore unknown fields. It is dangerous if the object we parse is also
                 // part of a request that we build later on, and the fields that we now ignore
                 // will end up being implicitly set to null in that request.
@@ -67,31 +63,44 @@ public final class Role implements ToXContentObject {
                 final Collection<ApplicationResourcePrivileges> applicationResourcePrivileges =
                         (Collection<ApplicationResourcePrivileges>) constructorObjects[i++];
                 final Collection<String> runAsPrivilege = (Collection<String>) constructorObjects[i++];
-                final Map<String, Object> metadata = (Map<String, Object>) constructorObjects[i];
-                return new Role(clusterPrivileges, globalApplicationPrivileges, indicesPrivileges, applicationResourcePrivileges,
-                        runAsPrivilege, metadata);
+                final Map<String, Object> metadata = (Map<String, Object>) constructorObjects[i++];
+                final Map<String, Object> transientMetadata = (Map<String, Object>) constructorObjects[i];
+            return new Role(roleName, clusterPrivileges, globalApplicationPrivileges, indicesPrivileges, applicationResourcePrivileges,
+                    runAsPrivilege, metadata, transientMetadata);
             });
 
     static {
         PARSER.declareStringArray(optionalConstructorArg(), CLUSTER);
-        PARSER.declareObject(optionalConstructorArg(), GlobalPrivileges.PARSER, GLOBAL);
-        PARSER.declareFieldArray(optionalConstructorArg(), IndicesPrivileges.PARSER, INDICES, ValueType.OBJECT_ARRAY);
-        PARSER.declareFieldArray(optionalConstructorArg(), ApplicationResourcePrivileges.PARSER, APPLICATIONS, ValueType.OBJECT_ARRAY);
+        PARSER.declareObject(optionalConstructorArg(), (parser,c)-> GlobalPrivileges.PARSER.parse(parser,null), GLOBAL);
+        PARSER.declareFieldArray(optionalConstructorArg(), (parser,c)->IndicesPrivileges.PARSER.parse(parser,null), INDICES,
+            ValueType.OBJECT_ARRAY);
+        PARSER.declareFieldArray(optionalConstructorArg(), (parser,c)->ApplicationResourcePrivileges.PARSER.parse(parser,null),
+            APPLICATIONS, ValueType.OBJECT_ARRAY);
         PARSER.declareStringArray(optionalConstructorArg(), RUN_AS);
         PARSER.declareObject(constructorArg(), (parser, c) -> parser.map(), METADATA);
+        PARSER.declareObject(constructorArg(), (parser, c) -> parser.map(), TRANSIENT_METADATA);
     }
 
+    private final String name;
     private final Set<String> clusterPrivileges;
     private final @Nullable GlobalPrivileges globalApplicationPrivileges;
     private final Set<IndicesPrivileges> indicesPrivileges;
     private final Set<ApplicationResourcePrivileges> applicationResourcePrivileges;
     private final Set<String> runAsPrivilege;
     private final Map<String, Object> metadata;
-
-    private Role(@Nullable Collection<String> clusterPrivileges, @Nullable GlobalPrivileges globalApplicationPrivileges,
-            @Nullable Collection<IndicesPrivileges> indicesPrivileges,
-            @Nullable Collection<ApplicationResourcePrivileges> applicationResourcePrivileges, @Nullable Collection<String> runAsPrivilege,
-            @Nullable Map<String, Object> metadata) {
+    private final Map<String, Object> transientMetadata;
+
+    private Role(String name, @Nullable Collection<String> clusterPrivileges,
+                 @Nullable GlobalPrivileges globalApplicationPrivileges,
+                 @Nullable Collection<IndicesPrivileges> indicesPrivileges,
+                 @Nullable Collection<ApplicationResourcePrivileges> applicationResourcePrivileges,
+                 @Nullable Collection<String> runAsPrivilege, @Nullable Map<String, Object> metadata,
+                 @Nullable Map<String, Object> transientMetadata) {
+        if (Strings.hasText(name) == false){
+            throw new IllegalArgumentException("role name must be provided");
+        } else {
+            this.name = name;
+        }
         // no cluster privileges are granted unless otherwise specified
         this.clusterPrivileges = Collections
                 .unmodifiableSet(clusterPrivileges != null ? new HashSet<>(clusterPrivileges) : Collections.emptySet());
@@ -105,6 +114,11 @@ public final class Role implements ToXContentObject {
         // no run as privileges are granted unless otherwise specified
         this.runAsPrivilege = Collections.unmodifiableSet(runAsPrivilege != null ? new HashSet<>(runAsPrivilege) : Collections.emptySet());
         this.metadata = metadata != null ? Collections.unmodifiableMap(metadata) : Collections.emptyMap();
+        this.transientMetadata = transientMetadata != null ? Collections.unmodifiableMap(transientMetadata) : Collections.emptyMap();
+    }
+
+    public String getName() {
+        return name;
     }
 
     public Set<String> getClusterPrivileges() {
@@ -136,55 +150,67 @@ public final class Role implements ToXContentObject {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         Role that = (Role) o;
-        return clusterPrivileges.equals(that.clusterPrivileges)
-                && Objects.equals(globalApplicationPrivileges, that.globalApplicationPrivileges)
-                && indicesPrivileges.equals(that.indicesPrivileges)
-                && applicationResourcePrivileges.equals(that.applicationResourcePrivileges)
-                && runAsPrivilege.equals(that.runAsPrivilege)
-                && metadata.equals(that.metadata);
+        return name.equals(that.name)
+            && clusterPrivileges.equals(that.clusterPrivileges)
+            && Objects.equals(globalApplicationPrivileges, that.globalApplicationPrivileges)
+            && indicesPrivileges.equals(that.indicesPrivileges)
+            && applicationResourcePrivileges.equals(that.applicationResourcePrivileges)
+            && runAsPrivilege.equals(that.runAsPrivilege)
+            && metadata.equals(that.metadata)
+            && transientMetadata.equals(that.transientMetadata);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(clusterPrivileges, globalApplicationPrivileges, indicesPrivileges, applicationResourcePrivileges,
-                runAsPrivilege, metadata);
+        return Objects.hash(name, clusterPrivileges, globalApplicationPrivileges, indicesPrivileges, applicationResourcePrivileges,
+            runAsPrivilege, metadata, transientMetadata);
     }
 
     @Override
     public String toString() {
-        try {
-            return XContentHelper.toXContent(this, XContentType.JSON, true).utf8ToString();
-        } catch (IOException e) {
-            throw new RuntimeException("Unexpected", e);
-        }
-    }
-
-    @Override
-    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
-        builder.startObject();
+        StringBuilder sb = new StringBuilder("{");
+        sb.append("Name=").append(name).append(",");
         if (false == clusterPrivileges.isEmpty()) {
-            builder.field(CLUSTER.getPreferredName(), clusterPrivileges);
+            sb.append("ClusterPrivileges=");
+            sb.append(clusterPrivileges.toString());
+            sb.append(", ");
         }
-        if (null != globalApplicationPrivileges) {
-            builder.field(GLOBAL.getPreferredName(), globalApplicationPrivileges);
+        if (globalApplicationPrivileges != null) {
+            sb.append("GlobalApplcationPrivileges=");
+            sb.append(globalApplicationPrivileges.toString());
+            sb.append(", ");
         }
         if (false == indicesPrivileges.isEmpty()) {
-            builder.field(INDICES.getPreferredName(), indicesPrivileges);
+            sb.append("IndicesPrivileges=");
+            sb.append(indicesPrivileges.toString());
+            sb.append(", ");
         }
         if (false == applicationResourcePrivileges.isEmpty()) {
-            builder.field(APPLICATIONS.getPreferredName(), applicationResourcePrivileges);
+            sb.append("ApplicationPrivileges=");
+            sb.append(applicationResourcePrivileges.toString());
+            sb.append(", ");
         }
         if (false == runAsPrivilege.isEmpty()) {
-            builder.field(RUN_AS.getPreferredName(), runAsPrivilege);
+            sb.append("RunAsPrivilege=");
+            sb.append(runAsPrivilege.toString());
+            sb.append(", ");
         }
         if (false == metadata.isEmpty()) {
-            builder.field(METADATA.getPreferredName(), metadata);
+            sb.append("Metadata=[");
+            sb.append(metadata.toString());
+            sb.append("], ");
         }
-        return builder.endObject();
+        if (false == transientMetadata.isEmpty()) {
+            sb.append("TransientMetadata=[");
+            sb.append(transientMetadata.toString());
+            sb.append("] ");
+        }
+        sb.append("}");
+        return sb.toString();
     }
 
-    public static Role fromXContent(XContentParser parser) {
-        return PARSER.apply(parser, null);
+    public static Role fromXContent(XContentParser parser, String name) {
+        return PARSER.apply(parser, name);
     }
 
     public static Builder builder() {
@@ -193,16 +219,27 @@ public final class Role implements ToXContentObject {
 
     public static final class Builder {
 
+        private @Nullable String name = null;
         private @Nullable Collection<String> clusterPrivileges = null;
         private @Nullable GlobalPrivileges globalApplicationPrivileges = null;
         private @Nullable Collection<IndicesPrivileges> indicesPrivileges = null;
         private @Nullable Collection<ApplicationResourcePrivileges> applicationResourcePrivileges = null;
         private @Nullable Collection<String> runAsPrivilege = null;
         private @Nullable Map<String, Object> metadata = null;
+        private @Nullable Map<String, Object> transientMetadata = null;
 
         private Builder() {
         }
 
+        public Builder name(String name) {
+            if (Strings.hasText(name) == false){
+                throw new IllegalArgumentException("role name must be provided");
+            } else {
+                this.name = name;
+            }
+            return this;
+        }
+
         public Builder clusterPrivileges(String... clusterPrivileges) {
             return clusterPrivileges(Arrays
                     .asList(Objects.requireNonNull(clusterPrivileges, "Cluster privileges cannot be null. Pass an empty array instead.")));
@@ -214,7 +251,7 @@ public final class Role implements ToXContentObject {
             return this;
         }
 
-        public Builder glabalApplicationPrivileges(GlobalPrivileges globalApplicationPrivileges) {
+        public Builder globalApplicationPrivileges(GlobalPrivileges globalApplicationPrivileges) {
             this.globalApplicationPrivileges = globalApplicationPrivileges;
             return this;
         }
@@ -257,9 +294,15 @@ public final class Role implements ToXContentObject {
             return this;
         }
 
+        public Builder transientMetadata(Map<String, Object> transientMetadata) {
+            this.transientMetadata =
+                Objects.requireNonNull(transientMetadata, "Transient metadata cannot be null. Pass an empty map instead.");
+            return this;
+        }
+
         public Role build() {
-            return new Role(clusterPrivileges, globalApplicationPrivileges, indicesPrivileges, applicationResourcePrivileges,
-                    runAsPrivilege, metadata);
+            return new Role(name, clusterPrivileges, globalApplicationPrivileges, indicesPrivileges, applicationResourcePrivileges,
+                runAsPrivilege, metadata, transientMetadata);
         }
     }
 
@@ -282,6 +325,7 @@ public final class Role implements ToXContentObject {
         public static final String TRANSPORT_CLIENT = "transport_client";
         public static final String MANAGE_SECURITY = "manage_security";
         public static final String MANAGE_SAML = "manage_saml";
+        public static final String MANAGE_TOKEN = "manage_token";
         public static final String MANAGE_PIPELINE = "manage_pipeline";
         public static final String MANAGE_CCR = "manage_ccr";
         public static final String READ_CCR = "read_ccr";

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

@@ -32,6 +32,7 @@ import org.elasticsearch.client.security.EnableUserRequest;
 import org.elasticsearch.client.security.GetPrivilegesRequest;
 import org.elasticsearch.client.security.GetRoleMappingsRequest;
 import org.elasticsearch.client.security.ChangePasswordRequest;
+import org.elasticsearch.client.security.GetRolesRequest;
 import org.elasticsearch.client.security.PutRoleMappingRequest;
 import org.elasticsearch.client.security.PutUserRequest;
 import org.elasticsearch.client.security.RefreshPolicy;
@@ -202,6 +203,22 @@ public class SecurityRequestConvertersTests extends ESTestCase {
         assertNull(request.getEntity());
     }
 
+    public void testGetRoles() {
+        final String[] roles = randomArray(0, 5, String[]::new, () -> randomAlphaOfLength(5));
+        final GetRolesRequest getRolesRequest = new GetRolesRequest(roles);
+        final Request request = SecurityRequestConverters.getRoles(getRolesRequest);
+
+        assertEquals(HttpGet.METHOD_NAME, request.getMethod());
+        if (roles.length == 0) {
+            assertEquals("/_xpack/security/role", request.getEndpoint());
+        } else {
+            assertEquals("/_xpack/security/role/" + Strings.collectionToCommaDelimitedString(getRolesRequest.getRoleNames()),
+                request.getEndpoint());
+        }
+        assertNull(request.getEntity());
+        assertEquals(Collections.emptyMap(), request.getParameters());
+    }
+
     public void testDeleteRole() {
         final String name = randomAlphaOfLengthBetween(1, 12);
         final RefreshPolicy refreshPolicy = randomFrom(RefreshPolicy.values());

+ 87 - 1
client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java

@@ -52,6 +52,8 @@ import org.elasticsearch.client.security.GetPrivilegesRequest;
 import org.elasticsearch.client.security.GetPrivilegesResponse;
 import org.elasticsearch.client.security.GetRoleMappingsRequest;
 import org.elasticsearch.client.security.GetRoleMappingsResponse;
+import org.elasticsearch.client.security.GetRolesRequest;
+import org.elasticsearch.client.security.GetRolesResponse;
 import org.elasticsearch.client.security.GetSslCertificatesResponse;
 import org.elasticsearch.client.security.HasPrivilegesRequest;
 import org.elasticsearch.client.security.HasPrivilegesResponse;
@@ -67,6 +69,7 @@ import org.elasticsearch.client.security.support.expressiondsl.RoleMapperExpress
 import org.elasticsearch.client.security.support.expressiondsl.expressions.AnyRoleMapperExpression;
 import org.elasticsearch.client.security.support.expressiondsl.fields.FieldRoleMapperExpression;
 import org.elasticsearch.client.security.user.User;
+import org.elasticsearch.client.security.user.privileges.Role;
 import org.elasticsearch.client.security.user.privileges.ApplicationPrivilege;
 import org.elasticsearch.client.security.user.privileges.IndicesPrivileges;
 import org.elasticsearch.common.Strings;
@@ -401,6 +404,89 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
         }
     }
 
+    public void testGetRoles() throws Exception {
+        final RestHighLevelClient client = highLevelClient();
+        addRole("my_role");
+        addRole("my_role2");
+        addRole("my_role3");
+        {
+            //tag::get-roles-request
+            GetRolesRequest request = new GetRolesRequest("my_role");
+            //end::get-roles-request
+            //tag::get-roles-execute
+            GetRolesResponse response = client.security().getRoles(request, RequestOptions.DEFAULT);
+            //end::get-roles-execute
+            //tag::get-roles-response
+            List<Role> roles = response.getRoles();
+            //end::get-roles-response
+
+            assertNotNull(response);
+            assertThat(roles.size(), equalTo(1));
+            assertThat(roles.get(0).getName(), equalTo("my_role"));
+            assertThat(roles.get(0).getClusterPrivileges().contains("all"), equalTo(true));
+        }
+
+        {
+            //tag::get-roles-list-request
+            GetRolesRequest request = new GetRolesRequest("my_role", "my_role2");
+            GetRolesResponse response = client.security().getRoles(request, RequestOptions.DEFAULT);
+            //end::get-roles-list-request
+
+            List<Role> roles = response.getRoles();
+            assertNotNull(response);
+            assertThat(roles.size(), equalTo(2));
+            assertThat(roles.get(0).getClusterPrivileges().contains("all"), equalTo(true));
+            assertThat(roles.get(1).getClusterPrivileges().contains("all"), equalTo(true));
+        }
+
+        {
+            //tag::get-roles-all-request
+            GetRolesRequest request = new GetRolesRequest();
+            GetRolesResponse response = client.security().getRoles(request, RequestOptions.DEFAULT);
+            //end::get-roles-all-request
+
+            List<Role> roles = response.getRoles();
+            assertNotNull(response);
+            // 21 system roles plus the three we created
+            assertThat(roles.size(), equalTo(24));
+        }
+
+        {
+            GetRolesRequest request = new GetRolesRequest("my_role");
+            ActionListener<GetRolesResponse> listener;
+
+            //tag::get-roles-execute-listener
+            listener = new ActionListener<GetRolesResponse>() {
+                @Override
+                public void onResponse(GetRolesResponse getRolesResponse) {
+                    // <1>
+                }
+
+                @Override
+                public void onFailure(Exception e) {
+                    // <2>
+                }
+            };
+            //end::get-roles-execute-listener
+
+            assertNotNull(listener);
+
+            // Replace the empty listener by a blocking listener in test
+            final PlainActionFuture<GetRolesResponse> future = new PlainActionFuture<>();
+            listener = future;
+
+            //tag::get-roles-execute-async
+            client.security().getRolesAsync(request, RequestOptions.DEFAULT, listener); // <1>
+            //end::get-roles-execute-async
+
+            final GetRolesResponse response = future.get(30, TimeUnit.SECONDS);
+            assertNotNull(response);
+            assertThat(response.getRoles().size(), equalTo(1));
+            assertThat(response.getRoles().get(0).getName(), equalTo("my_role"));
+            assertThat(response.getRoles().get(0).getClusterPrivileges().contains("all"), equalTo(true));
+        }
+    }
+
     public void testAuthenticate() throws Exception {
         RestHighLevelClient client = highLevelClient();
         {
@@ -414,7 +500,7 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
             //end::authenticate-response
 
             assertThat(user.getUsername(), is("test_user"));
-            assertThat(user.getRoles(), contains(new String[] {"superuser"}));
+            assertThat(user.getRoles(), contains(new String[]{"superuser"}));
             assertThat(user.getFullName(), nullValue());
             assertThat(user.getEmail(), nullValue());
             assertThat(user.getMetadata().isEmpty(), is(true));

+ 52 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetRolesRequestTests.java

@@ -0,0 +1,52 @@
+/*
+ * 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 GetRolesRequestTests extends ESTestCase {
+
+    public void testGetRolesRequest() {
+        final String[] roles = randomArray(0, 5, String[]::new, () -> randomAlphaOfLength(5));
+        final GetRolesRequest getRolesRequest = new GetRolesRequest(roles);
+        assertThat(getRolesRequest.getRoleNames().size(), equalTo(roles.length));
+        assertThat(getRolesRequest.getRoleNames(), containsInAnyOrder(roles));
+    }
+
+    public void testEqualsHashCode() {
+        final String[] roles = randomArray(0, 5, String[]::new, () -> randomAlphaOfLength(5));
+        final GetRolesRequest getRolesRequest = new GetRolesRequest(roles);
+        assertNotNull(getRolesRequest);
+        EqualsHashCodeTestUtils.checkEqualsAndHashCode(getRolesRequest, (original) -> {
+            return new GetRolesRequest(original.getRoleNames().toArray(new String[0]));
+        });
+        EqualsHashCodeTestUtils.checkEqualsAndHashCode(getRolesRequest, (original) -> {
+            return new GetRolesRequest(original.getRoleNames().toArray(new String[0]));
+        }, GetRolesRequestTests::mutateTestItem);
+    }
+
+    private static GetRolesRequest mutateTestItem(GetRolesRequest original) {
+        return new GetRolesRequest(randomArray(0, 5, String[]::new, () -> randomAlphaOfLength(5)));
+    }
+}

+ 199 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetRolesResponseTests.java

@@ -0,0 +1,199 @@
+/*
+ * 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.privileges.IndicesPrivileges;
+import org.elasticsearch.client.security.user.privileges.Role;
+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.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static org.hamcrest.Matchers.equalTo;
+
+public class GetRolesResponseTests extends ESTestCase {
+
+    public void testFromXContent() throws IOException {
+        String json =
+            "{\n" +
+                "  \"my_admin_role\": {\n" +
+                "    \"cluster\" : [ \"all\" ],\n" +
+                "    \"indices\" : [\n" +
+                "      {\n" +
+                "        \"names\" : [ \"index1\", \"index2\" ],\n" +
+                "        \"privileges\" : [ \"all\" ],\n" +
+                "        \"field_security\" : {\n" +
+                "          \"grant\" : [ \"title\", \"body\" ]}\n" +
+                "      }\n" +
+                "    ],\n" +
+                "    \"applications\" : [ ],\n" +
+                "    \"run_as\" : [ \"other_user\" ],\n" +
+                "    \"metadata\" : {\n" +
+                "      \"version\" : 1\n" +
+                "    },\n" +
+                "    \"transient_metadata\" : {\n" +
+                "      \"enabled\" : true\n" +
+                "    }\n" +
+                "  }\n" +
+                "}";
+        final GetRolesResponse response = GetRolesResponse.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.getRoles().size(), equalTo(1));
+        final Role role = response.getRoles().get(0);
+        assertThat(role.getName(), equalTo("my_admin_role"));
+        assertThat(role.getClusterPrivileges().size(), equalTo(1));
+        IndicesPrivileges expectedIndicesPrivileges = new IndicesPrivileges.Builder()
+            .indices("index1", "index2")
+            .privileges("all")
+            .grantedFields("title", "body")
+            .build();
+        assertThat(role.getIndicesPrivileges().contains(expectedIndicesPrivileges), equalTo(true));
+        final Map<String, Object> expectedMetadata = new HashMap<>();
+        expectedMetadata.put("version", 1);
+        final Map<String, Object> expectedTransientMetadata = new HashMap<>();
+        expectedTransientMetadata.put("enabled", true);
+        final Role expectedRole = Role.builder()
+            .name("my_admin_role")
+            .clusterPrivileges("all")
+            .indicesPrivileges(expectedIndicesPrivileges)
+            .runAsPrivilege("other_user")
+            .metadata(expectedMetadata)
+            .transientMetadata(expectedTransientMetadata)
+            .build();
+        assertThat(role, equalTo(expectedRole));
+    }
+
+    public void testEqualsHashCode() {
+        final List<Role> roles = new ArrayList<>();
+        IndicesPrivileges indicesPrivileges = new IndicesPrivileges.Builder()
+            .indices("index1", "index2")
+            .privileges("write", "monitor", "delete")
+            .grantedFields("field1", "field2")
+            .deniedFields("field3", "field4")
+            .build();
+        Map<String, Object> metadata = new HashMap<>();
+        metadata.put("key", "value");
+        Map<String, Object> transientMetadata = new HashMap<>();
+        transientMetadata.put("transient_key", "transient_value");
+        final Role role = Role.builder()
+            .name("role_name")
+            .clusterPrivileges("monitor", "manage", "manage_saml")
+            .indicesPrivileges(indicesPrivileges)
+            .runAsPrivilege("run_as_user")
+            .metadata(metadata)
+            .transientMetadata(transientMetadata)
+            .build();
+        roles.add(role);
+        IndicesPrivileges indicesPrivileges2 = new IndicesPrivileges.Builder()
+            .indices("other_index1", "other_index2")
+            .privileges("write", "monitor", "delete")
+            .grantedFields("other_field1", "other_field2")
+            .deniedFields("other_field3", "other_field4")
+            .build();
+        Map<String, Object> metadata2 = new HashMap<>();
+        metadata.put("other_key", "other_value");
+        Map<String, Object> transientMetadata2 = new HashMap<>();
+        transientMetadata2.put("other_transient_key", "other_transient_value");
+        final Role role2 = Role.builder()
+            .name("role2_name")
+            .clusterPrivileges("monitor", "manage", "manage_saml")
+            .indicesPrivileges(indicesPrivileges2)
+            .runAsPrivilege("other_run_as_user")
+            .metadata(metadata2)
+            .transientMetadata(transientMetadata2)
+            .build();
+        roles.add(role2);
+        final GetRolesResponse getRolesResponse = new GetRolesResponse(roles);
+        assertNotNull(getRolesResponse);
+        EqualsHashCodeTestUtils.checkEqualsAndHashCode(getRolesResponse, (original) -> {
+            return new GetRolesResponse(original.getRoles());
+        });
+        EqualsHashCodeTestUtils.checkEqualsAndHashCode(getRolesResponse, (original) -> {
+            return new GetRolesResponse(original.getRoles());
+        }, GetRolesResponseTests::mutateTestItem);
+
+    }
+
+    private static GetRolesResponse mutateTestItem(GetRolesResponse original) {
+        if (randomBoolean()) {
+            final List<Role> roles = new ArrayList<>();
+            IndicesPrivileges indicesPrivileges = new IndicesPrivileges.Builder()
+                .indices("index1", "index2")
+                .privileges("write", "monitor", "delete")
+                .grantedFields("field1", "field2")
+                .deniedFields("field3", "field4")
+                .build();
+            Map<String, Object> metadata = new HashMap<String, Object>();
+            metadata.put("key", "value");
+            Map<String, Object> transientMetadata = new HashMap<>();
+            transientMetadata.put("transient_key", "transient_value");
+            final Role role = Role.builder()
+                .name("role_name")
+                .clusterPrivileges("monitor", "manage", "manage_saml")
+                .indicesPrivileges(indicesPrivileges)
+                .runAsPrivilege("run_as_user")
+                .metadata(metadata)
+                .transientMetadata(transientMetadata)
+                .build();
+            roles.add(role);
+            return new GetRolesResponse(roles);
+        } else {
+            IndicesPrivileges indicesPrivileges = new IndicesPrivileges.Builder()
+                .indices("index1_changed", "index2")
+                .privileges("write", "monitor", "delete")
+                .grantedFields("field1", "field2")
+                .deniedFields("field3", "field4")
+                .build();
+            Map<String, Object> metadata = new HashMap<String, Object>();
+            metadata.put("key", "value");
+            Map<String, Object> transientMetadata = new HashMap<>();
+            transientMetadata.put("transient_key", "transient_value");
+            final Role role = Role.builder()
+                .name("role_name")
+                .clusterPrivileges("monitor", "manage", "manage_saml")
+                .indicesPrivileges(indicesPrivileges)
+                .runAsPrivilege("run_as_user")
+                .metadata(metadata)
+                .transientMetadata(transientMetadata)
+                .build();
+            List<Role> newRoles = original.getRoles().stream().collect(Collectors.toList());
+            newRoles.remove(0);
+            newRoles.add(role);
+            return new GetRolesResponse(newRoles);
+        }
+    }
+}

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

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

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

@@ -366,6 +366,7 @@ The Java High Level REST Client supports the following Security APIs:
 * <<java-rest-high-security-enable-user>>
 * <<java-rest-high-security-disable-user>>
 * <<java-rest-high-security-change-password>>
+* <<{upid}-get-roles>>
 * <<java-rest-high-security-delete-role>>
 * <<{upid}-clear-roles-cache>>
 * <<{upid}-clear-realm-cache>>