Skip to content

Commit f57e429

Browse files
committed
HLRC: Add delete user action (#35294)
* HLRC: Add delete user action It adds delete user action to the high level rest client. Relates #29827
1 parent 794d5b9 commit f57e429

File tree

12 files changed

+407
-3
lines changed

12 files changed

+407
-3
lines changed

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

+29
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
import org.elasticsearch.client.security.DeleteRoleMappingResponse;
3636
import org.elasticsearch.client.security.DeleteRoleRequest;
3737
import org.elasticsearch.client.security.DeleteRoleResponse;
38+
import org.elasticsearch.client.security.DeleteUserRequest;
39+
import org.elasticsearch.client.security.DeleteUserResponse;
3840
import org.elasticsearch.client.security.DisableUserRequest;
3941
import org.elasticsearch.client.security.EmptyResponse;
4042
import org.elasticsearch.client.security.EnableUserRequest;
@@ -102,6 +104,33 @@ public void putUserAsync(PutUserRequest request, RequestOptions options, ActionL
102104
PutUserResponse::fromXContent, listener, emptySet());
103105
}
104106

107+
/**
108+
* Removes user from the native realm synchronously.
109+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-delete-user.html">
110+
* the docs</a> for more.
111+
* @param request the request with the user to delete
112+
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
113+
* @return the response from the delete user call
114+
* @throws IOException in case there is a problem sending the request or parsing back the response
115+
*/
116+
public DeleteUserResponse deleteUser(DeleteUserRequest request, RequestOptions options) throws IOException {
117+
return restHighLevelClient.performRequestAndParseEntity(request, SecurityRequestConverters::deleteUser, options,
118+
DeleteUserResponse::fromXContent, singleton(404));
119+
}
120+
121+
/**
122+
* Asynchronously deletes a user in the native realm.
123+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-delete-user.html">
124+
* the docs</a> for more.
125+
* @param request the request with the user to delete
126+
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
127+
* @param listener the listener to be notified upon request completion
128+
*/
129+
public void deleteUserAsync(DeleteUserRequest request, RequestOptions options, ActionListener<DeleteUserResponse> listener) {
130+
restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::deleteUser, options,
131+
DeleteUserResponse::fromXContent, listener, singleton(404));
132+
}
133+
105134
/**
106135
* Create/Update a role mapping.
107136
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-put-role-mapping.html">

client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityRequestConverters.java

+12
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.elasticsearch.client.security.GetPrivilegesRequest;
3232
import org.elasticsearch.client.security.DeleteRoleMappingRequest;
3333
import org.elasticsearch.client.security.DeleteRoleRequest;
34+
import org.elasticsearch.client.security.DeleteUserRequest;
3435
import org.elasticsearch.client.security.InvalidateTokenRequest;
3536
import org.elasticsearch.client.security.GetRolesRequest;
3637
import org.elasticsearch.client.security.PutRoleMappingRequest;
@@ -76,6 +77,17 @@ static Request putUser(PutUserRequest putUserRequest) throws IOException {
7677
return request;
7778
}
7879

80+
static Request deleteUser(DeleteUserRequest deleteUserRequest) {
81+
String endpoint = new RequestConverters.EndpointBuilder()
82+
.addPathPartAsIs("_xpack","security", "user")
83+
.addPathPart(deleteUserRequest.getName())
84+
.build();
85+
Request request = new Request(HttpDelete.METHOD_NAME, endpoint);
86+
RequestConverters.Params params = new RequestConverters.Params(request);
87+
params.withRefreshPolicy(deleteUserRequest.getRefreshPolicy());
88+
return request;
89+
}
90+
7991
static Request putRoleMapping(final PutRoleMappingRequest putRoleMappingRequest) throws IOException {
8092
final String endpoint = new RequestConverters.EndpointBuilder()
8193
.addPathPartAsIs("_xpack/security/role_mapping")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.client.security;
21+
22+
import org.elasticsearch.client.Validatable;
23+
24+
import java.util.Objects;
25+
26+
/**
27+
* A request to delete a user from the native realm.
28+
*/
29+
public final class DeleteUserRequest implements Validatable {
30+
31+
private final String name;
32+
private final RefreshPolicy refreshPolicy;
33+
34+
public DeleteUserRequest(String name) {
35+
this(name, RefreshPolicy.IMMEDIATE);
36+
}
37+
38+
public DeleteUserRequest(String name, RefreshPolicy refreshPolicy) {
39+
this.name = Objects.requireNonNull(name, "user name is required");
40+
this.refreshPolicy = Objects.requireNonNull(refreshPolicy, "refresh policy is required");
41+
}
42+
43+
public String getName() {
44+
return name;
45+
}
46+
47+
public RefreshPolicy getRefreshPolicy() {
48+
return refreshPolicy;
49+
}
50+
51+
@Override
52+
public int hashCode() {
53+
return Objects.hash(name, refreshPolicy);
54+
}
55+
56+
@Override
57+
public boolean equals(Object obj) {
58+
if (this == obj) {
59+
return true;
60+
}
61+
if (obj == null) {
62+
return false;
63+
}
64+
if (getClass() != obj.getClass()) {
65+
return false;
66+
}
67+
final DeleteUserRequest other = (DeleteUserRequest) obj;
68+
69+
return (refreshPolicy == other.refreshPolicy) && Objects.equals(name, other.name);
70+
}
71+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.client.security;
21+
22+
import org.elasticsearch.client.core.AcknowledgedResponse;
23+
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
24+
import org.elasticsearch.common.xcontent.XContentParser;
25+
26+
import java.io.IOException;
27+
28+
/**
29+
* Response for a user being deleted from the native realm
30+
*/
31+
public final class DeleteUserResponse extends AcknowledgedResponse {
32+
33+
private static final String PARSE_FIELD_NAME = "found";
34+
35+
private static final ConstructingObjectParser<DeleteUserResponse, Void> PARSER = AcknowledgedResponse
36+
.generateParser("delete_user_response", DeleteUserResponse::new, PARSE_FIELD_NAME);
37+
38+
public DeleteUserResponse(boolean acknowledged) {
39+
super(acknowledged);
40+
}
41+
42+
public static DeleteUserResponse fromXContent(final XContentParser parser) throws IOException {
43+
return PARSER.parse(parser, null);
44+
}
45+
46+
@Override
47+
protected String getFieldName() {
48+
return PARSE_FIELD_NAME;
49+
}
50+
}

client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityIT.java

+13-3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import org.apache.http.client.methods.HttpDelete;
2323
import org.elasticsearch.ElasticsearchStatusException;
2424
import org.elasticsearch.client.security.AuthenticateResponse;
25+
import org.elasticsearch.client.security.DeleteUserRequest;
26+
import org.elasticsearch.client.security.DeleteUserResponse;
2527
import org.elasticsearch.client.security.PutUserRequest;
2628
import org.elasticsearch.client.security.PutUserResponse;
2729
import org.elasticsearch.client.security.RefreshPolicy;
@@ -74,14 +76,22 @@ public void testAuthenticate() throws Exception {
7476
assertThat(authenticateResponse.enabled(), is(true));
7577

7678
// delete user
77-
final Request deleteUserRequest = new Request(HttpDelete.METHOD_NAME,
78-
"/_xpack/security/user/" + putUserRequest.getUser().getUsername());
79-
highLevelClient().getLowLevelClient().performRequest(deleteUserRequest);
79+
final DeleteUserRequest deleteUserRequest =
80+
new DeleteUserRequest(putUserRequest.getUser().getUsername(), putUserRequest.getRefreshPolicy());
81+
82+
final DeleteUserResponse deleteUserResponse =
83+
execute(deleteUserRequest, securityClient::deleteUser, securityClient::deleteUserAsync);
84+
assertThat(deleteUserResponse.isAcknowledged(), is(true));
8085

8186
// authentication no longer works
8287
ElasticsearchStatusException e = expectThrows(ElasticsearchStatusException.class, () -> execute(securityClient::authenticate,
8388
securityClient::authenticateAsync, authorizationRequestOptions(basicAuthHeader)));
8489
assertThat(e.getMessage(), containsString("unable to authenticate user [" + putUserRequest.getUser().getUsername() + "]"));
90+
91+
// delete non-existing user
92+
final DeleteUserResponse deleteUserResponse2 =
93+
execute(deleteUserRequest, securityClient::deleteUser, securityClient::deleteUserAsync);
94+
assertThat(deleteUserResponse2.isAcknowledged(), is(false));
8595
}
8696

8797
private static User randomUser() {

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

+13
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.elasticsearch.client.security.DeletePrivilegesRequest;
2828
import org.elasticsearch.client.security.DeleteRoleMappingRequest;
2929
import org.elasticsearch.client.security.DeleteRoleRequest;
30+
import org.elasticsearch.client.security.DeleteUserRequest;
3031
import org.elasticsearch.client.security.DisableUserRequest;
3132
import org.elasticsearch.client.security.EnableUserRequest;
3233
import org.elasticsearch.client.security.GetPrivilegesRequest;
@@ -80,6 +81,18 @@ public void testPutUser() throws IOException {
8081
assertToXContentBody(putUserRequest, request.getEntity());
8182
}
8283

84+
public void testDeleteUser() {
85+
final String name = randomAlphaOfLengthBetween(4, 12);
86+
final RefreshPolicy refreshPolicy = randomFrom(RefreshPolicy.values());
87+
final Map<String, String> expectedParams = getExpectedParamsFromRefreshPolicy(refreshPolicy);
88+
DeleteUserRequest deleteUserRequest = new DeleteUserRequest(name, refreshPolicy);
89+
Request request = SecurityRequestConverters.deleteUser(deleteUserRequest);
90+
assertEquals(HttpDelete.METHOD_NAME, request.getMethod());
91+
assertEquals("/_xpack/security/user/" + name, request.getEndpoint());
92+
assertEquals(expectedParams, request.getParameters());
93+
assertNull(request.getEntity());
94+
}
95+
8396
public void testPutRoleMapping() throws IOException {
8497
final String username = randomAlphaOfLengthBetween(4, 7);
8598
final String rolename = randomAlphaOfLengthBetween(4, 7);

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

+63
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
import org.elasticsearch.client.security.DeleteRoleMappingResponse;
4545
import org.elasticsearch.client.security.DeleteRoleRequest;
4646
import org.elasticsearch.client.security.DeleteRoleResponse;
47+
import org.elasticsearch.client.security.DeleteUserRequest;
48+
import org.elasticsearch.client.security.DeleteUserResponse;
4749
import org.elasticsearch.client.security.DisableUserRequest;
4850
import org.elasticsearch.client.security.EmptyResponse;
4951
import org.elasticsearch.client.security.EnableUserRequest;
@@ -150,6 +152,67 @@ public void onFailure(Exception e) {
150152
}
151153
}
152154

155+
public void testDeleteUser() throws Exception {
156+
RestHighLevelClient client = highLevelClient();
157+
addUser(client, "testUser", "testPassword");
158+
159+
{
160+
// tag::delete-user-request
161+
DeleteUserRequest deleteUserRequest = new DeleteUserRequest(
162+
"testUser"); // <1>
163+
// end::delete-user-request
164+
165+
// tag::delete-user-execute
166+
DeleteUserResponse deleteUserResponse = client.security().deleteUser(deleteUserRequest, RequestOptions.DEFAULT);
167+
// end::delete-user-execute
168+
169+
// tag::delete-user-response
170+
boolean found = deleteUserResponse.isAcknowledged(); // <1>
171+
// end::delete-user-response
172+
assertTrue(found);
173+
174+
// check if deleting the already deleted user again will give us a different response
175+
deleteUserResponse = client.security().deleteUser(deleteUserRequest, RequestOptions.DEFAULT);
176+
assertFalse(deleteUserResponse.isAcknowledged());
177+
}
178+
179+
{
180+
DeleteUserRequest deleteUserRequest = new DeleteUserRequest("testUser", RefreshPolicy.IMMEDIATE);
181+
182+
ActionListener<DeleteUserResponse> listener;
183+
//tag::delete-user-execute-listener
184+
listener = new ActionListener<DeleteUserResponse>() {
185+
@Override
186+
public void onResponse(DeleteUserResponse deleteUserResponse) {
187+
// <1>
188+
}
189+
190+
@Override
191+
public void onFailure(Exception e) {
192+
// <2>
193+
}
194+
};
195+
//end::delete-user-execute-listener
196+
197+
// Replace the empty listener by a blocking listener in test
198+
final CountDownLatch latch = new CountDownLatch(1);
199+
listener = new LatchedActionListener<>(listener, latch);
200+
201+
//tag::delete-user-execute-async
202+
client.security().deleteUserAsync(deleteUserRequest, RequestOptions.DEFAULT, listener); // <1>
203+
//end::delete-user-execute-async
204+
205+
assertTrue(latch.await(30L, TimeUnit.SECONDS));
206+
}
207+
}
208+
209+
private void addUser(RestHighLevelClient client, String userName, String password) throws IOException {
210+
User user = new User(userName, Collections.singletonList(userName));
211+
PutUserRequest request = new PutUserRequest(user, password.toCharArray(), true, RefreshPolicy.NONE);
212+
PutUserResponse response = client.security().putUser(request, RequestOptions.DEFAULT);
213+
assertTrue(response.isCreated());
214+
}
215+
153216
public void testPutRoleMapping() throws Exception {
154217
final RestHighLevelClient client = highLevelClient();
155218

client/rest-high-level/src/test/java/org/elasticsearch/client/security/DeleteRoleResponseTests.java

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
* specific language governing permissions and limitations
1717
* under the License.
1818
*/
19+
1920
package org.elasticsearch.client.security;
2021

2122
import org.elasticsearch.common.bytes.BytesReference;

0 commit comments

Comments
 (0)