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 50c1f8bd828..1d773a9d7ee 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 @@ -20,6 +20,8 @@ package org.elasticsearch.client; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.client.security.DeleteRoleRequest; +import org.elasticsearch.client.security.DeleteRoleResponse; import org.elasticsearch.client.security.PutRoleMappingRequest; import org.elasticsearch.client.security.PutRoleMappingResponse; import org.elasticsearch.client.security.DisableUserRequest; @@ -36,6 +38,7 @@ import org.elasticsearch.client.security.DeleteRoleMappingResponse; import java.io.IOException; import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; /** * A wrapper for the {@link RestHighLevelClient} that provides methods for accessing the Security APIs. @@ -252,4 +255,31 @@ public final class SecurityClient { DeleteRoleMappingResponse::fromXContent, listener, emptySet()); } + /** + * Removes role from the native realm. + * See + * the docs for more. + * @param request the request with the role to delete + * @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 DeleteRoleResponse deleteRole(DeleteRoleRequest request, RequestOptions options) throws IOException { + return restHighLevelClient.performRequestAndParseEntity(request, SecurityRequestConverters::deleteRole, options, + DeleteRoleResponse::fromXContent, singleton(404)); + } + + /** + * Removes role from the native realm. + * See + * the docs for more. + * @param request the request with the role to delete + * @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 deleteRoleAsync(DeleteRoleRequest request, RequestOptions options, ActionListener listener) { + restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::deleteRole, options, + DeleteRoleResponse::fromXContent, listener, singleton(404)); + } + } 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 d64389d3948..a8a975d28ad 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 @@ -22,11 +22,12 @@ package org.elasticsearch.client; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; +import org.elasticsearch.client.security.DeleteRoleMappingRequest; +import org.elasticsearch.client.security.DeleteRoleRequest; import org.elasticsearch.client.security.PutRoleMappingRequest; import org.elasticsearch.client.security.DisableUserRequest; import org.elasticsearch.client.security.EnableUserRequest; import org.elasticsearch.client.security.ChangePasswordRequest; -import org.elasticsearch.client.security.DeleteRoleMappingRequest; import org.elasticsearch.client.security.PutUserRequest; import org.elasticsearch.client.security.SetUserEnabledRequest; @@ -96,7 +97,7 @@ final class SecurityRequestConverters { return request; } - static Request deleteRoleMapping(DeleteRoleMappingRequest deleteRoleMappingRequest) throws IOException { + static Request deleteRoleMapping(DeleteRoleMappingRequest deleteRoleMappingRequest) { final String endpoint = new RequestConverters.EndpointBuilder() .addPathPartAsIs("_xpack/security/role_mapping") .addPathPart(deleteRoleMappingRequest.getName()) @@ -107,4 +108,14 @@ final class SecurityRequestConverters { return request; } + static Request deleteRole(DeleteRoleRequest deleteRoleRequest) { + String endpoint = new RequestConverters.EndpointBuilder() + .addPathPartAsIs("_xpack/security/role") + .addPathPart(deleteRoleRequest.getName()) + .build(); + Request request = new Request(HttpDelete.METHOD_NAME, endpoint); + RequestConverters.Params params = new RequestConverters.Params(request); + params.withRefreshPolicy(deleteRoleRequest.getRefreshPolicy()); + return request; + } } diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/DeleteRoleRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/DeleteRoleRequest.java new file mode 100644 index 00000000000..39c9a9b7afb --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/DeleteRoleRequest.java @@ -0,0 +1,50 @@ +/* + * 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 java.util.Objects; + +/** + * A request delete a role from the security index + */ +public final class DeleteRoleRequest implements Validatable { + + private final String name; + private final RefreshPolicy refreshPolicy; + + public DeleteRoleRequest(String name) { + this(name, RefreshPolicy.IMMEDIATE); + } + + public DeleteRoleRequest(String name, RefreshPolicy refreshPolicy) { + this.name = Objects.requireNonNull(name, "name is required"); + this.refreshPolicy = Objects.requireNonNull(refreshPolicy, "refresh policy is required"); + } + + public String getName() { + return name; + } + + public RefreshPolicy getRefreshPolicy() { + return refreshPolicy; + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/DeleteRoleResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/DeleteRoleResponse.java new file mode 100644 index 00000000000..3db10bb9702 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/DeleteRoleResponse.java @@ -0,0 +1,55 @@ +/* + * 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.common.ParseField; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.XContentParser; + +import java.io.IOException; + +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; + +/** + * Response for a role being deleted from the native realm + */ +public final class DeleteRoleResponse { + + private final boolean found; + + public DeleteRoleResponse(boolean found) { + this.found = found; + } + + public boolean isFound() { + return this.found; + } + + private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>("delete_role_response", + true, args -> new DeleteRoleResponse((boolean) args[0])); + + static { + PARSER.declareBoolean(constructorArg(), new ParseField("found")); + } + + public static DeleteRoleResponse fromXContent(XContentParser parser) throws IOException { + return PARSER.parse(parser, null); + } +} 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 1c4df942d8c..4267e238580 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 @@ -22,10 +22,11 @@ package org.elasticsearch.client; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; +import org.elasticsearch.client.security.DeleteRoleMappingRequest; +import org.elasticsearch.client.security.DeleteRoleRequest; import org.elasticsearch.client.security.DisableUserRequest; import org.elasticsearch.client.security.EnableUserRequest; import org.elasticsearch.client.security.ChangePasswordRequest; -import org.elasticsearch.client.security.DeleteRoleMappingRequest; import org.elasticsearch.client.security.PutRoleMappingRequest; import org.elasticsearch.client.security.PutUserRequest; import org.elasticsearch.client.security.RefreshPolicy; @@ -177,4 +178,15 @@ public class SecurityRequestConvertersTests extends ESTestCase { assertNull(request.getEntity()); } + public void testDeleteRole() { + final String name = randomAlphaOfLengthBetween(1, 12); + final RefreshPolicy refreshPolicy = randomFrom(RefreshPolicy.values()); + final Map expectedParams = getExpectedParamsFromRefreshPolicy(refreshPolicy); + DeleteRoleRequest deleteRoleRequest = new DeleteRoleRequest(name, refreshPolicy); + Request request = SecurityRequestConverters.deleteRole(deleteRoleRequest); + assertEquals(HttpDelete.METHOD_NAME, request.getMethod()); + assertEquals("/_xpack/security/role/" + name, request.getEndpoint()); + assertEquals(expectedParams, request.getParameters()); + assertNull(request.getEntity()); + } } 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 4bc6f75de53..ac1b9b6753a 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 @@ -19,14 +19,20 @@ package org.elasticsearch.client.documentation; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.nio.entity.NStringEntity; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.LatchedActionListener; import org.elasticsearch.client.ESRestHighLevelClientTestCase; +import org.elasticsearch.client.Request; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.client.security.ChangePasswordRequest; import org.elasticsearch.client.security.DeleteRoleMappingRequest; import org.elasticsearch.client.security.DeleteRoleMappingResponse; +import org.elasticsearch.client.security.DeleteRoleRequest; +import org.elasticsearch.client.security.DeleteRoleResponse; import org.elasticsearch.client.security.DisableUserRequest; import org.elasticsearch.client.security.EmptyResponse; import org.elasticsearch.client.security.EnableUserRequest; @@ -36,18 +42,23 @@ import org.elasticsearch.client.security.PutRoleMappingResponse; import org.elasticsearch.client.security.PutUserRequest; import org.elasticsearch.client.security.PutUserResponse; import org.elasticsearch.client.security.RefreshPolicy; -import org.elasticsearch.client.security.support.expressiondsl.RoleMapperExpression; -import org.elasticsearch.client.security.support.expressiondsl.fields.FieldRoleMapperExpression; import org.elasticsearch.client.security.support.CertificateInfo; +import org.elasticsearch.client.security.support.expressiondsl.RoleMapperExpression; import org.elasticsearch.client.security.support.expressiondsl.expressions.AnyRoleMapperExpression; +import org.elasticsearch.client.security.support.expressiondsl.fields.FieldRoleMapperExpression; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.xcontent.XContentBuilder; import org.hamcrest.Matchers; +import java.io.IOException; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; + public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase { public void testPutUser() throws Exception { @@ -417,4 +428,72 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase { } } + public void testDeleteRole() throws Exception { + RestHighLevelClient client = highLevelClient(); + addRole("testrole"); + + { + // tag::delete-role-request + DeleteRoleRequest deleteRoleRequest = new DeleteRoleRequest( + "testrole"); // <1> + // end::delete-role-request + + // tag::delete-role-execute + DeleteRoleResponse deleteRoleResponse = client.security().deleteRole(deleteRoleRequest, RequestOptions.DEFAULT); + // end::delete-role-execute + + // tag::delete-role-response + boolean found = deleteRoleResponse.isFound(); // <1> + // end::delete-role-response + assertTrue(found); + + // check if deleting the already deleted role again will give us a different response + deleteRoleResponse = client.security().deleteRole(deleteRoleRequest, RequestOptions.DEFAULT); + assertFalse(deleteRoleResponse.isFound()); + } + + { + DeleteRoleRequest deleteRoleRequest = new DeleteRoleRequest("testrole"); + + ActionListener listener; + //tag::delete-role-execute-listener + listener = new ActionListener() { + @Override + public void onResponse(DeleteRoleResponse deleteRoleResponse) { + // <1> + } + + @Override + public void onFailure(Exception e) { + // <2> + } + }; + //end::delete-role-execute-listener + + // Replace the empty listener by a blocking listener in test + final CountDownLatch latch = new CountDownLatch(1); + listener = new LatchedActionListener<>(listener, latch); + + //tag::delete-role-execute-async + client.security().deleteRoleAsync(deleteRoleRequest, RequestOptions.DEFAULT, listener); // <1> + //end::delete-role-execute-async + + assertTrue(latch.await(30L, TimeUnit.SECONDS)); + } + } + + // TODO: move all calls to high-level REST client once APIs for adding new role exist + private void addRole(String roleName) throws IOException { + Request addRoleRequest = new Request(HttpPost.METHOD_NAME, "/_xpack/security/role/" + roleName); + try (XContentBuilder builder = jsonBuilder()) { + builder.startObject(); + { + builder.array("cluster", "all"); + } + builder.endObject(); + addRoleRequest.setEntity(new NStringEntity(Strings.toString(builder), ContentType.APPLICATION_JSON)); + } + client().performRequest(addRoleRequest); + } + } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/DeleteRoleResponseTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/DeleteRoleResponseTests.java new file mode 100644 index 00000000000..a2c08fcf881 --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/DeleteRoleResponseTests.java @@ -0,0 +1,59 @@ +/* + * 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.common.bytes.BytesReference; +import org.elasticsearch.common.xcontent.NamedXContentRegistry; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.test.ESTestCase; + +import java.io.IOException; + +public class DeleteRoleResponseTests extends ESTestCase { + + public void testBasicParsing() throws IOException { + XContentType contentType = randomFrom(XContentType.values()); + final boolean found = randomBoolean(); + XContentBuilder builder = XContentFactory.contentBuilder(contentType).startObject() + .field("found", found).endObject(); + BytesReference bytes = BytesReference.bytes(builder); + + DeleteRoleResponse response = parse(builder.contentType(), bytes); + assertEquals(found, response.isFound()); + } + + public void testParsingWithMissingField() throws IOException { + XContentType contentType = randomFrom(XContentType.values()); + XContentBuilder builder = XContentFactory.contentBuilder(contentType).startObject().endObject(); + BytesReference bytes = BytesReference.bytes(builder); + + expectThrows(IllegalArgumentException.class, () -> parse(builder.contentType(), bytes)); + } + + private DeleteRoleResponse parse(XContentType contentType, BytesReference bytes) throws IOException { + XContentParser parser = XContentFactory.xContent(contentType) + .createParser(NamedXContentRegistry.EMPTY, null, bytes.streamInput()); + parser.nextToken(); + return DeleteRoleResponse.fromXContent(parser); + } + +} diff --git a/docs/java-rest/high-level/document/delete.asciidoc b/docs/java-rest/high-level/document/delete.asciidoc index 1b32fca7042..12fede7af80 100644 --- a/docs/java-rest/high-level/document/delete.asciidoc +++ b/docs/java-rest/high-level/document/delete.asciidoc @@ -10,7 +10,7 @@ [id="{upid}-{api}-request"] ==== Delete Request -A +{request}+ requires the following arguments: +A +{request}+ has no arguments ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- diff --git a/docs/java-rest/high-level/security/delete-role.asciidoc b/docs/java-rest/high-level/security/delete-role.asciidoc new file mode 100644 index 00000000000..0086b89bb68 --- /dev/null +++ b/docs/java-rest/high-level/security/delete-role.asciidoc @@ -0,0 +1,33 @@ +-- +:api: delete-role +:request: DeleteRoleRequest +:response: DeleteRoleResponse +-- + +[id="{upid}-{api}"] +=== Delete Role API + +[id="{upid}-{api}-request"] +==== Delete Role Request + +A +{request}+ has a single argument + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests-file}[{api}-request] +-------------------------------------------------- +<1> role to delete + +include::../execution.asciidoc[] + +[id="{upid}-{api}-response"] +==== Delete Response + +The returned +{response}+ allows to retrieve information about the executed + operation as follows: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests-file}[{api}-response] +-------------------------------------------------- +<1> whether the given role was found diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc index 28ef96d5eae..6cde79a22e5 100644 --- a/docs/java-rest/high-level/supported-apis.asciidoc +++ b/docs/java-rest/high-level/supported-apis.asciidoc @@ -310,12 +310,16 @@ include::rollup/get_rollup_caps.asciidoc[] == Security APIs +:upid: {mainid}-security +:doc-tests-file: {doc-tests}/SecurityDocumentationIT.java + The Java High Level REST Client supports the following Security APIs: * <> * <> * <> * <> +* <> * <> * <> * <> @@ -324,6 +328,7 @@ include::security/put-user.asciidoc[] include::security/enable-user.asciidoc[] include::security/disable-user.asciidoc[] include::security/change-password.asciidoc[] +include::security/delete-role.asciidoc[] include::security/get-certificates.asciidoc[] include::security/put-role-mapping.asciidoc[] include::security/delete-role-mapping.asciidoc[]