Skip to content

Commit

Permalink
HLRC: Add delete user action (elastic#35294)
Browse files Browse the repository at this point in the history
* HLRC: Add delete user action

It adds delete user action to the high level rest client.

Relates elastic#29827
  • Loading branch information
iverase authored Nov 29, 2018
1 parent e0a678f commit 93ed8b7
Show file tree
Hide file tree
Showing 12 changed files with 407 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
import org.elasticsearch.client.security.DeleteRoleMappingResponse;
import org.elasticsearch.client.security.DeleteRoleRequest;
import org.elasticsearch.client.security.DeleteRoleResponse;
import org.elasticsearch.client.security.DeleteUserRequest;
import org.elasticsearch.client.security.DeleteUserResponse;
import org.elasticsearch.client.security.DisableUserRequest;
import org.elasticsearch.client.security.EmptyResponse;
import org.elasticsearch.client.security.EnableUserRequest;
Expand Down Expand Up @@ -102,6 +104,33 @@ public void putUserAsync(PutUserRequest request, RequestOptions options, ActionL
PutUserResponse::fromXContent, listener, emptySet());
}

/**
* Removes user from the native realm synchronously.
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-delete-user.html">
* the docs</a> for more.
* @param request the request with the user 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 user call
* @throws IOException in case there is a problem sending the request or parsing back the response
*/
public DeleteUserResponse deleteUser(DeleteUserRequest request, RequestOptions options) throws IOException {
return restHighLevelClient.performRequestAndParseEntity(request, SecurityRequestConverters::deleteUser, options,
DeleteUserResponse::fromXContent, singleton(404));
}

/**
* Asynchronously deletes a user in the native realm.
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-delete-user.html">
* the docs</a> for more.
* @param request the request with the user 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 deleteUserAsync(DeleteUserRequest request, RequestOptions options, ActionListener<DeleteUserResponse> listener) {
restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::deleteUser, options,
DeleteUserResponse::fromXContent, listener, singleton(404));
}

/**
* Create/Update a role mapping.
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-put-role-mapping.html">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.elasticsearch.client.security.GetPrivilegesRequest;
import org.elasticsearch.client.security.DeleteRoleMappingRequest;
import org.elasticsearch.client.security.DeleteRoleRequest;
import org.elasticsearch.client.security.DeleteUserRequest;
import org.elasticsearch.client.security.InvalidateTokenRequest;
import org.elasticsearch.client.security.GetRolesRequest;
import org.elasticsearch.client.security.PutRoleMappingRequest;
Expand Down Expand Up @@ -76,6 +77,17 @@ static Request putUser(PutUserRequest putUserRequest) throws IOException {
return request;
}

static Request deleteUser(DeleteUserRequest deleteUserRequest) {
String endpoint = new RequestConverters.EndpointBuilder()
.addPathPartAsIs("_xpack","security", "user")
.addPathPart(deleteUserRequest.getName())
.build();
Request request = new Request(HttpDelete.METHOD_NAME, endpoint);
RequestConverters.Params params = new RequestConverters.Params(request);
params.withRefreshPolicy(deleteUserRequest.getRefreshPolicy());
return request;
}

static Request putRoleMapping(final PutRoleMappingRequest putRoleMappingRequest) throws IOException {
final String endpoint = new RequestConverters.EndpointBuilder()
.addPathPartAsIs("_xpack/security/role_mapping")
Expand Down
Original file line number Diff line number Diff line change
@@ -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.Validatable;

import java.util.Objects;

/**
* A request to delete a user from the native realm.
*/
public final class DeleteUserRequest implements Validatable {

private final String name;
private final RefreshPolicy refreshPolicy;

public DeleteUserRequest(String name) {
this(name, RefreshPolicy.IMMEDIATE);
}

public DeleteUserRequest(String name, RefreshPolicy refreshPolicy) {
this.name = Objects.requireNonNull(name, "user name is required");
this.refreshPolicy = Objects.requireNonNull(refreshPolicy, "refresh policy is required");
}

public String getName() {
return name;
}

public RefreshPolicy getRefreshPolicy() {
return refreshPolicy;
}

@Override
public int hashCode() {
return Objects.hash(name, refreshPolicy);
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final DeleteUserRequest other = (DeleteUserRequest) obj;

return (refreshPolicy == other.refreshPolicy) && Objects.equals(name, other.name);
}
}
Original file line number Diff line number Diff line change
@@ -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.core.AcknowledgedResponse;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.XContentParser;

import java.io.IOException;

/**
* Response for a user being deleted from the native realm
*/
public final class DeleteUserResponse extends AcknowledgedResponse {

private static final String PARSE_FIELD_NAME = "found";

private static final ConstructingObjectParser<DeleteUserResponse, Void> PARSER = AcknowledgedResponse
.generateParser("delete_user_response", DeleteUserResponse::new, PARSE_FIELD_NAME);

public DeleteUserResponse(boolean acknowledged) {
super(acknowledged);
}

public static DeleteUserResponse fromXContent(final XContentParser parser) throws IOException {
return PARSER.parse(parser, null);
}

@Override
protected String getFieldName() {
return PARSE_FIELD_NAME;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import org.apache.http.client.methods.HttpDelete;
import org.elasticsearch.ElasticsearchStatusException;
import org.elasticsearch.client.security.AuthenticateResponse;
import org.elasticsearch.client.security.DeleteUserRequest;
import org.elasticsearch.client.security.DeleteUserResponse;
import org.elasticsearch.client.security.PutUserRequest;
import org.elasticsearch.client.security.PutUserResponse;
import org.elasticsearch.client.security.RefreshPolicy;
Expand Down Expand Up @@ -74,14 +76,22 @@ public void testAuthenticate() throws Exception {
assertThat(authenticateResponse.enabled(), is(true));

// delete user
final Request deleteUserRequest = new Request(HttpDelete.METHOD_NAME,
"/_xpack/security/user/" + putUserRequest.getUser().getUsername());
highLevelClient().getLowLevelClient().performRequest(deleteUserRequest);
final DeleteUserRequest deleteUserRequest =
new DeleteUserRequest(putUserRequest.getUser().getUsername(), putUserRequest.getRefreshPolicy());

final DeleteUserResponse deleteUserResponse =
execute(deleteUserRequest, securityClient::deleteUser, securityClient::deleteUserAsync);
assertThat(deleteUserResponse.isAcknowledged(), is(true));

// authentication no longer works
ElasticsearchStatusException e = expectThrows(ElasticsearchStatusException.class, () -> execute(securityClient::authenticate,
securityClient::authenticateAsync, authorizationRequestOptions(basicAuthHeader)));
assertThat(e.getMessage(), containsString("unable to authenticate user [" + putUserRequest.getUser().getUsername() + "]"));

// delete non-existing user
final DeleteUserResponse deleteUserResponse2 =
execute(deleteUserRequest, securityClient::deleteUser, securityClient::deleteUserAsync);
assertThat(deleteUserResponse2.isAcknowledged(), is(false));
}

private static User randomUser() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.elasticsearch.client.security.DeletePrivilegesRequest;
import org.elasticsearch.client.security.DeleteRoleMappingRequest;
import org.elasticsearch.client.security.DeleteRoleRequest;
import org.elasticsearch.client.security.DeleteUserRequest;
import org.elasticsearch.client.security.DisableUserRequest;
import org.elasticsearch.client.security.EnableUserRequest;
import org.elasticsearch.client.security.GetPrivilegesRequest;
Expand Down Expand Up @@ -80,6 +81,18 @@ public void testPutUser() throws IOException {
assertToXContentBody(putUserRequest, request.getEntity());
}

public void testDeleteUser() {
final String name = randomAlphaOfLengthBetween(4, 12);
final RefreshPolicy refreshPolicy = randomFrom(RefreshPolicy.values());
final Map<String, String> expectedParams = getExpectedParamsFromRefreshPolicy(refreshPolicy);
DeleteUserRequest deleteUserRequest = new DeleteUserRequest(name, refreshPolicy);
Request request = SecurityRequestConverters.deleteUser(deleteUserRequest);
assertEquals(HttpDelete.METHOD_NAME, request.getMethod());
assertEquals("/_xpack/security/user/" + name, request.getEndpoint());
assertEquals(expectedParams, request.getParameters());
assertNull(request.getEntity());
}

public void testPutRoleMapping() throws IOException {
final String username = randomAlphaOfLengthBetween(4, 7);
final String rolename = randomAlphaOfLengthBetween(4, 7);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
import org.elasticsearch.client.security.DeleteRoleMappingResponse;
import org.elasticsearch.client.security.DeleteRoleRequest;
import org.elasticsearch.client.security.DeleteRoleResponse;
import org.elasticsearch.client.security.DeleteUserRequest;
import org.elasticsearch.client.security.DeleteUserResponse;
import org.elasticsearch.client.security.DisableUserRequest;
import org.elasticsearch.client.security.EmptyResponse;
import org.elasticsearch.client.security.EnableUserRequest;
Expand Down Expand Up @@ -185,6 +187,67 @@ public void onFailure(Exception e) {
}
}

public void testDeleteUser() throws Exception {
RestHighLevelClient client = highLevelClient();
addUser(client, "testUser", "testPassword");

{
// tag::delete-user-request
DeleteUserRequest deleteUserRequest = new DeleteUserRequest(
"testUser"); // <1>
// end::delete-user-request

// tag::delete-user-execute
DeleteUserResponse deleteUserResponse = client.security().deleteUser(deleteUserRequest, RequestOptions.DEFAULT);
// end::delete-user-execute

// tag::delete-user-response
boolean found = deleteUserResponse.isAcknowledged(); // <1>
// end::delete-user-response
assertTrue(found);

// check if deleting the already deleted user again will give us a different response
deleteUserResponse = client.security().deleteUser(deleteUserRequest, RequestOptions.DEFAULT);
assertFalse(deleteUserResponse.isAcknowledged());
}

{
DeleteUserRequest deleteUserRequest = new DeleteUserRequest("testUser", RefreshPolicy.IMMEDIATE);

ActionListener<DeleteUserResponse> listener;
//tag::delete-user-execute-listener
listener = new ActionListener<DeleteUserResponse>() {
@Override
public void onResponse(DeleteUserResponse deleteUserResponse) {
// <1>
}

@Override
public void onFailure(Exception e) {
// <2>
}
};
//end::delete-user-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-user-execute-async
client.security().deleteUserAsync(deleteUserRequest, RequestOptions.DEFAULT, listener); // <1>
//end::delete-user-execute-async

assertTrue(latch.await(30L, TimeUnit.SECONDS));
}
}

private void addUser(RestHighLevelClient client, String userName, String password) throws IOException {
User user = new User(userName, Collections.singletonList(userName));
PutUserRequest request = new PutUserRequest(user, password.toCharArray(), true, RefreshPolicy.NONE);
PutUserResponse response = client.security().putUser(request, RequestOptions.DEFAULT);
assertTrue(response.isCreated());
}

public void testPutRoleMapping() throws Exception {
final RestHighLevelClient client = highLevelClient();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/

package org.elasticsearch.client.security;

import org.elasticsearch.common.bytes.BytesReference;
Expand Down
Loading

0 comments on commit 93ed8b7

Please sign in to comment.