Browse Source

Add Delete Privileges API to HLRC (#35454)

This commit adds the Delete Privileges API to the high level REST
client.

Related to #29827
Tanguy Leroux 7 years ago
parent
commit
5b7446bb5f

+ 33 - 3
client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java

@@ -29,6 +29,8 @@ import org.elasticsearch.client.security.ClearRolesCacheRequest;
 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 final class SecurityClient {
      * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-authenticate.html">
      * the docs</a> 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 final class SecurityClient {
      * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-authenticate.html">
      * the docs</a> 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<AuthenticateResponse> listener) {
         restHighLevelClient.performRequestAsyncAndParseEntity(AuthenticateRequest.INSTANCE, AuthenticateRequest::getRequest, options,
@@ -473,4 +475,32 @@ public final class SecurityClient {
             InvalidateTokenResponse::fromXContent, listener, emptySet());
     }
 
+    /**
+     * Removes application privilege(s)
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-delete-privilege.html">
+     * the docs</a> 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 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-delete-privilege.html">
+     * the docs</a> 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<DeletePrivilegesResponse> listener) {
+        restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::deletePrivileges, options,
+            DeletePrivilegesResponse::fromXContent, listener, singleton(404));
+    }
+
 }

+ 17 - 4
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 @@ final class SecurityRequestConverters {
         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;
+    }
 }

+ 76 - 0
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;
+    }
+}

+ 91 - 0
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<String> privileges;
+
+    DeletePrivilegesResponse(String application, List<String> 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<String> 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);
+    }
+}

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

@@ -24,6 +24,7 @@ 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.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 class SecurityRequestConvertersTests extends ESTestCase {
         assertEquals(0, request.getParameters().size());
         assertToXContentBody(createTokenRequest, request.getEntity());
     }
+
+    public void testDeletePrivileges() {
+        final String application = randomAlphaOfLengthBetween(1, 12);
+        final List<String> privileges = randomSubsetOf(randomIntBetween(1, 3), "read", "write", "all");
+        final RefreshPolicy refreshPolicy = randomFrom(RefreshPolicy.values());
+        final Map<String, String> 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());
+    }
 }

+ 80 - 2
client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java

@@ -28,6 +28,7 @@ import org.elasticsearch.action.support.PlainActionFuture;
 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.ClearRolesCacheRequest;
 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.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.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 class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
             // 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<DeletePrivilegesResponse> listener;
+            //tag::delete-privileges-execute-listener
+            listener = new ActionListener<DeletePrivilegesResponse>() {
+                @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));
+        }
+    }
 }

+ 37 - 0
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

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

@@ -353,12 +353,14 @@ The Java High Level REST Client supports the following Security APIs:
 * <<java-rest-high-security-delete-role-mapping>>
 * <<java-rest-high-security-create-token>>
 * <<{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[]