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 d3b38aaf9e9d2..68bb9b9a28b99 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
@@ -29,6 +29,8 @@
import org.elasticsearch.client.security.ClearRolesCacheResponse;
import org.elasticsearch.client.security.CreateTokenRequest;
import org.elasticsearch.client.security.CreateTokenResponse;
+import org.elasticsearch.client.security.DeletePrivilegesRequest;
+import org.elasticsearch.client.security.DeletePrivilegesResponse;
import org.elasticsearch.client.security.DeleteRoleMappingRequest;
import org.elasticsearch.client.security.DeleteRoleMappingResponse;
import org.elasticsearch.client.security.DeleteRoleRequest;
@@ -221,7 +223,7 @@ public void disableUserAsync(DisableUserRequest request, RequestOptions options,
* See
* the docs for more.
*
- * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+ * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
* @return the responsee from the authenticate user call
*/
public AuthenticateResponse authenticate(RequestOptions options) throws IOException {
@@ -234,8 +236,8 @@ public AuthenticateResponse authenticate(RequestOptions options) throws IOExcept
* See
* the docs for more.
*
- * @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
+ * @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 authenticateAsync(RequestOptions options, ActionListener listener) {
restHighLevelClient.performRequestAsyncAndParseEntity(AuthenticateRequest.INSTANCE, AuthenticateRequest::getRequest, options,
@@ -473,4 +475,32 @@ public void invalidateTokenAsync(InvalidateTokenRequest request, RequestOptions
InvalidateTokenResponse::fromXContent, listener, emptySet());
}
+ /**
+ * Removes application privilege(s)
+ * See
+ * the docs for more.
+ * @param request the request with the application privilege 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 application privilege call
+ * @throws IOException in case there is a problem sending the request or parsing back the response
+ */
+ public DeletePrivilegesResponse deletePrivileges(DeletePrivilegesRequest request, RequestOptions options) throws IOException {
+ return restHighLevelClient.performRequestAndParseEntity(request, SecurityRequestConverters::deletePrivileges, options,
+ DeletePrivilegesResponse::fromXContent, singleton(404));
+ }
+
+ /**
+ * Asynchronously removes an application privilege
+ * See
+ * the docs for more.
+ * @param request the request with the application privilege 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 deletePrivilegesAsync(DeletePrivilegesRequest request, RequestOptions options,
+ ActionListener listener) {
+ restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::deletePrivileges, options,
+ DeletePrivilegesResponse::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 5958a763eeebc..160aa1fd82b0a 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
@@ -19,21 +19,22 @@
package org.elasticsearch.client;
-import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
+import org.elasticsearch.client.security.ChangePasswordRequest;
import org.elasticsearch.client.security.ClearRealmCacheRequest;
import org.elasticsearch.client.security.ClearRolesCacheRequest;
import org.elasticsearch.client.security.CreateTokenRequest;
+import org.elasticsearch.client.security.DeletePrivilegesRequest;
import org.elasticsearch.client.security.DeleteRoleMappingRequest;
import org.elasticsearch.client.security.DeleteRoleRequest;
-import org.elasticsearch.client.security.InvalidateTokenRequest;
-import org.elasticsearch.client.security.PutRoleMappingRequest;
import org.elasticsearch.client.security.DisableUserRequest;
import org.elasticsearch.client.security.EnableUserRequest;
import org.elasticsearch.client.security.GetRoleMappingsRequest;
-import org.elasticsearch.client.security.ChangePasswordRequest;
+import org.elasticsearch.client.security.InvalidateTokenRequest;
+import org.elasticsearch.client.security.PutRoleMappingRequest;
import org.elasticsearch.client.security.PutUserRequest;
import org.elasticsearch.client.security.SetUserEnabledRequest;
import org.elasticsearch.common.Strings;
@@ -172,4 +173,16 @@ static Request invalidateToken(InvalidateTokenRequest invalidateTokenRequest) th
request.setEntity(createEntity(invalidateTokenRequest, REQUEST_BODY_CONTENT_TYPE));
return request;
}
+
+ static Request deletePrivileges(DeletePrivilegesRequest deletePrivilegeRequest) {
+ String endpoint = new RequestConverters.EndpointBuilder()
+ .addPathPartAsIs("_xpack/security/privilege")
+ .addPathPart(deletePrivilegeRequest.getApplication())
+ .addCommaSeparatedPathParts(deletePrivilegeRequest.getPrivileges())
+ .build();
+ Request request = new Request(HttpDelete.METHOD_NAME, endpoint);
+ RequestConverters.Params params = new RequestConverters.Params(request);
+ params.withRefreshPolicy(deletePrivilegeRequest.getRefreshPolicy());
+ return request;
+ }
}
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/DeletePrivilegesRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/DeletePrivilegesRequest.java
new file mode 100644
index 0000000000000..7ea416fc339c3
--- /dev/null
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/DeletePrivilegesRequest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.Nullable;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.util.CollectionUtils;
+
+/**
+ * A request to delete application privileges
+ */
+public final class DeletePrivilegesRequest implements Validatable {
+
+ private final String application;
+ private final String[] privileges;
+ private final RefreshPolicy refreshPolicy;
+
+ /**
+ * Creates a new {@link DeletePrivilegesRequest} using the default {@link RefreshPolicy#getDefault()} refresh policy.
+ *
+ * @param application the name of the application for which the privileges will be deleted
+ * @param privileges the privileges to delete
+ */
+ public DeletePrivilegesRequest(String application, String... privileges) {
+ this(application, privileges, null);
+ }
+
+ /**
+ * Creates a new {@link DeletePrivilegesRequest}.
+ *
+ * @param application the name of the application for which the privileges will be deleted
+ * @param privileges the privileges to delete
+ * @param refreshPolicy the refresh policy {@link RefreshPolicy} for the request, defaults to {@link RefreshPolicy#getDefault()}
+ */
+ public DeletePrivilegesRequest(String application, String[] privileges, @Nullable RefreshPolicy refreshPolicy) {
+ if (Strings.hasText(application) == false) {
+ throw new IllegalArgumentException("application name is required");
+ }
+ if (CollectionUtils.isEmpty(privileges)) {
+ throw new IllegalArgumentException("privileges are required");
+ }
+ this.application = application;
+ this.privileges = privileges;
+ this.refreshPolicy = (refreshPolicy == null) ? RefreshPolicy.getDefault() : refreshPolicy;
+ }
+
+ public String getApplication() {
+ return application;
+ }
+
+ public String[] getPrivileges() {
+ return privileges;
+ }
+
+ public RefreshPolicy getRefreshPolicy() {
+ return refreshPolicy;
+ }
+}
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/DeletePrivilegesResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/DeletePrivilegesResponse.java
new file mode 100644
index 0000000000000..fd6e30df10544
--- /dev/null
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/DeletePrivilegesResponse.java
@@ -0,0 +1,91 @@
+/*
+ * 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.xcontent.XContentParser;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
+
+/**
+ * Response for application privileges deletion
+ */
+public final class DeletePrivilegesResponse {
+
+ private final String application;
+ private final List privileges;
+
+ DeletePrivilegesResponse(String application, List privileges) {
+ this.application = Objects.requireNonNull(application, "application is required");
+ this.privileges = Objects.requireNonNull(privileges, "privileges are required");
+ }
+
+ public String getApplication() {
+ return application;
+ }
+
+ /**
+ * Indicates if the given privilege was successfully found and deleted from the list of application privileges.
+ *
+ * @param privilege the privilege
+ * @return true if the privilege was found and deleted, false otherwise.
+ */
+ public boolean isFound(final String privilege) {
+ return privileges.contains(privilege);
+ }
+
+ public static DeletePrivilegesResponse fromXContent(XContentParser parser) throws IOException {
+ XContentParser.Token token = parser.currentToken();
+ if (token == null) {
+ token = parser.nextToken();
+ }
+ ensureExpectedToken(XContentParser.Token.START_OBJECT, token, parser::getTokenLocation);
+ token = parser.nextToken();
+ ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation);
+ final String application = parser.currentName();
+ final List foundAndDeletedPrivileges = new ArrayList<>();
+ token = parser.nextToken();
+ if (token == XContentParser.Token.START_OBJECT) {
+ while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+ if (token == XContentParser.Token.FIELD_NAME) {
+ String privilege = parser.currentName();
+ token = parser.nextToken();
+ if (token == XContentParser.Token.START_OBJECT) {
+ String currentFieldName = null;
+ while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+ if (token == XContentParser.Token.FIELD_NAME) {
+ currentFieldName = parser.currentName();
+ } else if (token == XContentParser.Token.VALUE_BOOLEAN) {
+ if ("found".equals(currentFieldName) && parser.booleanValue()) {
+ foundAndDeletedPrivileges.add(privilege);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return new DeletePrivilegesResponse(application, foundAndDeletedPrivileges);
+ }
+}
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 d2679906af207..199356de2902e 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
@@ -24,6 +24,7 @@
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.elasticsearch.client.security.CreateTokenRequest;
+import org.elasticsearch.client.security.DeletePrivilegesRequest;
import org.elasticsearch.client.security.DeleteRoleMappingRequest;
import org.elasticsearch.client.security.DeleteRoleRequest;
import org.elasticsearch.client.security.DisableUserRequest;
@@ -241,4 +242,19 @@ public void testCreateTokenWithClientCredentialsGrant() throws Exception {
assertEquals(0, request.getParameters().size());
assertToXContentBody(createTokenRequest, request.getEntity());
}
+
+ public void testDeletePrivileges() {
+ final String application = randomAlphaOfLengthBetween(1, 12);
+ final List privileges = randomSubsetOf(randomIntBetween(1, 3), "read", "write", "all");
+ final RefreshPolicy refreshPolicy = randomFrom(RefreshPolicy.values());
+ final Map expectedParams = getExpectedParamsFromRefreshPolicy(refreshPolicy);
+ DeletePrivilegesRequest deletePrivilegesRequest =
+ new DeletePrivilegesRequest(application, privileges.toArray(Strings.EMPTY_ARRAY), refreshPolicy);
+ Request request = SecurityRequestConverters.deletePrivileges(deletePrivilegesRequest);
+ assertEquals(HttpDelete.METHOD_NAME, request.getMethod());
+ assertEquals("/_xpack/security/privilege/" + application + "/" + Strings.collectionToCommaDelimitedString(privileges),
+ 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 ffa30e16c0468..71cfdd4ba5b89 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
@@ -28,6 +28,7 @@
import org.elasticsearch.client.ESRestHighLevelClientTestCase;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.RequestOptions;
+import org.elasticsearch.client.Response;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.security.AuthenticateResponse;
import org.elasticsearch.client.security.ChangePasswordRequest;
@@ -37,6 +38,8 @@
import org.elasticsearch.client.security.ClearRolesCacheResponse;
import org.elasticsearch.client.security.CreateTokenRequest;
import org.elasticsearch.client.security.CreateTokenResponse;
+import org.elasticsearch.client.security.DeletePrivilegesRequest;
+import org.elasticsearch.client.security.DeletePrivilegesResponse;
import org.elasticsearch.client.security.DeleteRoleMappingRequest;
import org.elasticsearch.client.security.DeleteRoleMappingResponse;
import org.elasticsearch.client.security.DeleteRoleRequest;
@@ -55,13 +58,14 @@
import org.elasticsearch.client.security.PutUserRequest;
import org.elasticsearch.client.security.PutUserResponse;
import org.elasticsearch.client.security.RefreshPolicy;
+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.client.security.user.User;
-import org.elasticsearch.client.security.support.CertificateInfo;
-import org.elasticsearch.client.security.support.expressiondsl.expressions.AnyRoleMapperExpression;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.rest.RestStatus;
import org.hamcrest.Matchers;
import java.io.IOException;
@@ -916,4 +920,78 @@ public void onFailure(Exception e) {
// See https://github.com/elastic/elasticsearch/issues/35115
}
}
+
+ public void testDeletePrivilege() throws Exception {
+ RestHighLevelClient client = highLevelClient();
+ {
+ final Request createPrivilegeRequest = new Request("POST", "/_xpack/security/privilege");
+ createPrivilegeRequest.setJsonEntity("{" +
+ " \"testapp\": {" +
+ " \"read\": {" +
+ " \"actions\": [ \"action:login\", \"data:read/*\" ]" +
+ " }," +
+ " \"write\": {" +
+ " \"actions\": [ \"action:login\", \"data:write/*\" ]" +
+ " }," +
+ " \"all\": {" +
+ " \"actions\": [ \"action:login\", \"data:write/*\" ]" +
+ " }" +
+ " }" +
+ "}");
+
+ final Response createPrivilegeResponse = client.getLowLevelClient().performRequest(createPrivilegeRequest);
+ assertEquals(RestStatus.OK.getStatus(), createPrivilegeResponse.getStatusLine().getStatusCode());
+ }
+ {
+ // tag::delete-privileges-request
+ DeletePrivilegesRequest request = new DeletePrivilegesRequest(
+ "testapp", // <1>
+ "read", "write"); // <2>
+ // end::delete-privileges-request
+
+ // tag::delete-privileges-execute
+ DeletePrivilegesResponse response = client.security().deletePrivileges(request, RequestOptions.DEFAULT);
+ // end::delete-privileges-execute
+
+ // tag::delete-privileges-response
+ String application = response.getApplication(); // <1>
+ boolean found = response.isFound("read"); // <2>
+ // end::delete-privileges-response
+ assertThat(application, equalTo("testapp"));
+ assertTrue(response.isFound("write"));
+ assertTrue(found);
+
+ // check if deleting the already deleted privileges again will give us a different response
+ response = client.security().deletePrivileges(request, RequestOptions.DEFAULT);
+ assertFalse(response.isFound("write"));
+ }
+ {
+ DeletePrivilegesRequest deletePrivilegesRequest = new DeletePrivilegesRequest("testapp", "all");
+
+ ActionListener listener;
+ //tag::delete-privileges-execute-listener
+ listener = new ActionListener() {
+ @Override
+ public void onResponse(DeletePrivilegesResponse deletePrivilegesResponse) {
+ // <1>
+ }
+
+ @Override
+ public void onFailure(Exception e) {
+ // <2>
+ }
+ };
+ //end::delete-privileges-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-privileges-execute-async
+ client.security().deletePrivilegesAsync(deletePrivilegesRequest, RequestOptions.DEFAULT, listener); // <1>
+ //end::delete-privileges-execute-async
+
+ assertTrue(latch.await(30L, TimeUnit.SECONDS));
+ }
+ }
}
diff --git a/docs/java-rest/high-level/security/delete-privileges.asciidoc b/docs/java-rest/high-level/security/delete-privileges.asciidoc
new file mode 100644
index 0000000000000..7f32d75107b97
--- /dev/null
+++ b/docs/java-rest/high-level/security/delete-privileges.asciidoc
@@ -0,0 +1,37 @@
+--
+:api: delete-privileges
+:request: DeletePrivilegesRequest
+:response: DeletePrivilegesResponse
+--
+
+[id="{upid}-{api}"]
+=== Delete Privileges API
+
+This API can be used to delete application privileges.
+
+[id="{upid}-{api}-request"]
+==== Delete Application Privileges Request
+
+A +{request}+ has two arguments
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-request]
+--------------------------------------------------
+<1> the name of application
+<2> the name(s) of the privileges to delete that belong to the given application
+
+include::../execution.asciidoc[]
+
+[id="{upid}-{api}-response"]
+==== Delete Application Privileges 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> the name of the application
+<2> whether the given privilege was found and deleted
diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc
index 7deb214d29628..8ec6ac7a31164 100644
--- a/docs/java-rest/high-level/supported-apis.asciidoc
+++ b/docs/java-rest/high-level/supported-apis.asciidoc
@@ -351,12 +351,14 @@ The Java High Level REST Client supports the following Security APIs:
* <>
* <>
* <<{upid}-invalidate-token>>
+* <<{upid}-delete-privileges>>
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/delete-privileges.asciidoc[]
include::security/clear-roles-cache.asciidoc[]
include::security/clear-realm-cache.asciidoc[]
include::security/authenticate.asciidoc[]