Add Delete Privileges API to HLRC (#35454)

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

Related to #29827
This commit is contained in:
Tanguy Leroux 2018-11-14 14:04:30 +01:00 committed by GitHub
parent e7b7d52a6a
commit 5b7446bb5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 352 additions and 9 deletions

View File

@ -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));
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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));
}
}
}

View File

@ -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

View File

@ -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[]