diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java index b6d4bd110bd79..ab7b3e33905b7 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java @@ -42,6 +42,8 @@ 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 DeleteRoleMappingResponse deleteRoleMapping(DeleteRoleMappingRequest requ DeleteRoleMappingResponse::fromXContent, emptySet()); } + /** + * Asynchronously retrieves roles from the native roles store. + * See + * the docs 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 listener) { + restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::getRoles, options, + GetRolesResponse::fromXContent, listener, emptySet()); + } + + /** + * Retrieves roles from the native roles store. + * See + * the docs 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 diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityRequestConverters.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityRequestConverters.java index b07c68f999873..a1123de725110 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityRequestConverters.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityRequestConverters.java @@ -32,6 +32,7 @@ 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 @@ static Request deleteRole(DeleteRoleRequest deleteRoleRequest) { 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)); diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetRolesRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetRolesRequest.java new file mode 100644 index 0000000000000..7692e24c28bb5 --- /dev/null +++ b/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 roleNames; + + public GetRolesRequest(final String... roleNames) { + if (roleNames != null) { + this.roleNames = Collections.unmodifiableSet(Sets.newHashSet(roleNames)); + } else { + this.roleNames = Collections.emptySet(); + } + } + + public Set 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); + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetRolesResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetRolesResponse.java new file mode 100644 index 0000000000000..91b7527c3235e --- /dev/null +++ b/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 roles; + + public GetRolesResponse(List roles) { + this.roles = Collections.unmodifiableList(roles); + } + + public List getRoles() { + return roles; + } + + public static GetRolesResponse fromXContent(XContentParser parser) throws IOException { + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation); + final List 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); + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/IndicesPrivileges.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/IndicesPrivileges.java index e693a4fea34fa..393b8613f25e7 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/IndicesPrivileges.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/IndicesPrivileges.java @@ -249,7 +249,7 @@ public static final class Builder { private @Nullable Collection deniedFields = null; private @Nullable String query = null; - private Builder() { + public Builder() { } public Builder indices(String... indices) { diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/Role.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/Role.java index 78265196ee819..e332971a512fd 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/Role.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/Role.java @@ -21,15 +21,11 @@ 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.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 PARSER = new ConstructingObjectParser<>("role_descriptor", false, - constructorObjects -> { + public static final ConstructingObjectParser 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 = (Collection) constructorObjects[i++]; final Collection runAsPrivilege = (Collection) constructorObjects[i++]; - final Map metadata = (Map) constructorObjects[i]; - return new Role(clusterPrivileges, globalApplicationPrivileges, indicesPrivileges, applicationResourcePrivileges, - runAsPrivilege, metadata); + final Map metadata = (Map) constructorObjects[i++]; + final Map transientMetadata = (Map) 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 clusterPrivileges; private final @Nullable GlobalPrivileges globalApplicationPrivileges; private final Set indicesPrivileges; private final Set applicationResourcePrivileges; private final Set runAsPrivilege; private final Map metadata; - - private Role(@Nullable Collection clusterPrivileges, @Nullable GlobalPrivileges globalApplicationPrivileges, - @Nullable Collection indicesPrivileges, - @Nullable Collection applicationResourcePrivileges, @Nullable Collection runAsPrivilege, - @Nullable Map metadata) { + private final Map transientMetadata; + + private Role(String name, @Nullable Collection clusterPrivileges, + @Nullable GlobalPrivileges globalApplicationPrivileges, + @Nullable Collection indicesPrivileges, + @Nullable Collection applicationResourcePrivileges, + @Nullable Collection runAsPrivilege, @Nullable Map metadata, + @Nullable Map 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 @@ private Role(@Nullable Collection clusterPrivileges, @Nullable GlobalPri // 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 getClusterPrivileges() { @@ -136,55 +150,67 @@ public boolean equals(Object o) { 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 static Builder builder() { public static final class Builder { + private @Nullable String name = null; private @Nullable Collection clusterPrivileges = null; private @Nullable GlobalPrivileges globalApplicationPrivileges = null; private @Nullable Collection indicesPrivileges = null; private @Nullable Collection applicationResourcePrivileges = null; private @Nullable Collection runAsPrivilege = null; private @Nullable Map metadata = null; + private @Nullable Map 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 Builder clusterPrivileges(Collection clusterPrivileges) { return this; } - public Builder glabalApplicationPrivileges(GlobalPrivileges globalApplicationPrivileges) { + public Builder globalApplicationPrivileges(GlobalPrivileges globalApplicationPrivileges) { this.globalApplicationPrivileges = globalApplicationPrivileges; return this; } @@ -257,9 +294,15 @@ public Builder metadata(Map metadata) { return this; } + public Builder transientMetadata(Map 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 static class ClusterPrivilegeName { 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"; diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityRequestConvertersTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityRequestConvertersTests.java index eb1d030b0f6b8..dcfa9210094de 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityRequestConvertersTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityRequestConvertersTests.java @@ -32,6 +32,7 @@ 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 void testDeleteRoleMapping() throws IOException { 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()); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java index a8477bc6c9452..8d36381eeaa06 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java @@ -52,6 +52,8 @@ 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.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 void onFailure(Exception e) { } } + 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 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 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 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 listener; + + //tag::get-roles-execute-listener + listener = new ActionListener() { + @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 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 void testAuthenticate() throws Exception { //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)); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetRolesRequestTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetRolesRequestTests.java new file mode 100644 index 0000000000000..4bf970d096bcf --- /dev/null +++ b/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))); + } +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetRolesResponseTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetRolesResponseTests.java new file mode 100644 index 0000000000000..41de52a8cef75 --- /dev/null +++ b/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 expectedMetadata = new HashMap<>(); + expectedMetadata.put("version", 1); + final Map 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 roles = new ArrayList<>(); + IndicesPrivileges indicesPrivileges = new IndicesPrivileges.Builder() + .indices("index1", "index2") + .privileges("write", "monitor", "delete") + .grantedFields("field1", "field2") + .deniedFields("field3", "field4") + .build(); + Map metadata = new HashMap<>(); + metadata.put("key", "value"); + Map 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 metadata2 = new HashMap<>(); + metadata.put("other_key", "other_value"); + Map 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 roles = new ArrayList<>(); + IndicesPrivileges indicesPrivileges = new IndicesPrivileges.Builder() + .indices("index1", "index2") + .privileges("write", "monitor", "delete") + .grantedFields("field1", "field2") + .deniedFields("field3", "field4") + .build(); + Map metadata = new HashMap(); + metadata.put("key", "value"); + Map 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 metadata = new HashMap(); + metadata.put("key", "value"); + Map 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 newRoles = original.getRoles().stream().collect(Collectors.toList()); + newRoles.remove(0); + newRoles.add(role); + return new GetRolesResponse(newRoles); + } + } +} diff --git a/docs/java-rest/high-level/security/get-roles.asciidoc b/docs/java-rest/high-level/security/get-roles.asciidoc new file mode 100644 index 0000000000000..9ecf36353c3e8 --- /dev/null +++ b/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] +-------------------------------------------------- \ No newline at end of file diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc index 02158673da210..a7233dd9a7536 100644 --- a/docs/java-rest/high-level/supported-apis.asciidoc +++ b/docs/java-rest/high-level/supported-apis.asciidoc @@ -366,6 +366,7 @@ The Java High Level REST Client supports the following Security APIs: * <> * <> * <> +* <<{upid}-get-roles>> * <> * <<{upid}-clear-roles-cache>> * <<{upid}-clear-realm-cache>>