Skip to content

Commit 52cae60

Browse files
committed
HLRest: add put user API (#32332)
This commit adds a security client to the high level rest client, which includes an implementation for the put user api. As part of these changes, a new request and response class have been added that are specific to the high level rest client. One change here is that the response was previously wrapped inside a user object. The plan is to remove this wrapping and this PR adds an unwrapped response outside of the user object so we can remove the user object later on. See #29827
1 parent 205d60c commit 52cae60

File tree

20 files changed

+622
-38
lines changed

20 files changed

+622
-38
lines changed

client/rest-high-level/build.gradle

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,27 @@ forbiddenApisMain {
7777
signaturesFiles += files('src/main/resources/forbidden/rest-high-level-signatures.txt')
7878
}
7979

80+
integTestRunner {
81+
systemProperty 'tests.rest.cluster.username', System.getProperty('tests.rest.cluster.username', 'test_user')
82+
systemProperty 'tests.rest.cluster.password', System.getProperty('tests.rest.cluster.password', 'test-password')
83+
}
84+
8085
integTestCluster {
8186
setting 'xpack.license.self_generated.type', 'trial'
87+
setting 'xpack.security.enabled', 'true'
88+
setupCommand 'setupDummyUser',
89+
'bin/elasticsearch-users',
90+
'useradd', System.getProperty('tests.rest.cluster.username', 'test_user'),
91+
'-p', System.getProperty('tests.rest.cluster.password', 'test-password'),
92+
'-r', 'superuser'
93+
waitCondition = { node, ant ->
94+
File tmpFile = new File(node.cwd, 'wait.success')
95+
ant.get(src: "http://${node.httpUri()}/_cluster/health?wait_for_nodes=>=${numNodes}&wait_for_status=yellow",
96+
dest: tmpFile.toString(),
97+
username: System.getProperty('tests.rest.cluster.username', 'test_user'),
98+
password: System.getProperty('tests.rest.cluster.password', 'test-password'),
99+
ignoreerrors: true,
100+
retries: 10)
101+
return tmpFile.exists()
102+
}
82103
}

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ public class RestHighLevelClient implements Closeable {
215215
private final LicenseClient licenseClient = new LicenseClient(this);
216216
private final MigrationClient migrationClient = new MigrationClient(this);
217217
private final MachineLearningClient machineLearningClient = new MachineLearningClient(this);
218+
private final SecurityClient securityClient = new SecurityClient(this);
218219

219220
/**
220221
* Creates a {@link RestHighLevelClient} given the low level {@link RestClientBuilder} that allows to build the
@@ -374,6 +375,20 @@ public MachineLearningClient machineLearning() {
374375
return machineLearningClient;
375376
}
376377

378+
/**
379+
* Provides methods for accessing the Elastic Licensed Security APIs that
380+
* are shipped with the Elastic Stack distribution of Elasticsearch. All of
381+
* these APIs will 404 if run against the OSS distribution of Elasticsearch.
382+
* <p>
383+
* See the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api.html">
384+
* Security APIs on elastic.co</a> for more information.
385+
*
386+
* @return the client wrapper for making Security API calls
387+
*/
388+
public SecurityClient security() {
389+
return securityClient;
390+
}
391+
377392
/**
378393
* Executes a bulk request using the Bulk API.
379394
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html">Bulk API on elastic.co</a>
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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;
21+
22+
import org.elasticsearch.action.ActionListener;
23+
import org.elasticsearch.client.security.PutUserRequest;
24+
import org.elasticsearch.client.security.PutUserResponse;
25+
26+
import java.io.IOException;
27+
28+
import static java.util.Collections.emptySet;
29+
30+
/**
31+
* A wrapper for the {@link RestHighLevelClient} that provides methods for accessing the Security APIs.
32+
* <p>
33+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api.html">Security APIs on elastic.co</a>
34+
*/
35+
public final class SecurityClient {
36+
37+
private final RestHighLevelClient restHighLevelClient;
38+
39+
SecurityClient(RestHighLevelClient restHighLevelClient) {
40+
this.restHighLevelClient = restHighLevelClient;
41+
}
42+
43+
/**
44+
* Create/update a user in the native realm synchronously.
45+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-users.html">
46+
* the docs</a> for more.
47+
* @param request the request with the user's information
48+
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
49+
* @return the response from the put user call
50+
* @throws IOException in case there is a problem sending the request or parsing back the response
51+
*/
52+
public PutUserResponse putUser(PutUserRequest request, RequestOptions options) throws IOException {
53+
return restHighLevelClient.performRequestAndParseEntity(request, SecurityRequestConverters::putUser, options,
54+
PutUserResponse::fromXContent, emptySet());
55+
}
56+
57+
/**
58+
* Asynchronously create/update a user in the native realm.
59+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-users.html">
60+
* the docs</a> for more.
61+
* @param request the request with the user's information
62+
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
63+
* @param listener the listener to be notified upon request completion
64+
*/
65+
public void putUserAsync(PutUserRequest request, RequestOptions options, ActionListener<PutUserResponse> listener) {
66+
restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::putUser, options,
67+
PutUserResponse::fromXContent, listener, emptySet());
68+
}
69+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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;
21+
22+
import org.apache.http.client.methods.HttpPut;
23+
import org.elasticsearch.client.security.PutUserRequest;
24+
25+
import java.io.IOException;
26+
27+
import static org.elasticsearch.client.RequestConverters.REQUEST_BODY_CONTENT_TYPE;
28+
import static org.elasticsearch.client.RequestConverters.createEntity;
29+
30+
public final class SecurityRequestConverters {
31+
32+
private SecurityRequestConverters() {}
33+
34+
static Request putUser(PutUserRequest putUserRequest) throws IOException {
35+
String endpoint = new RequestConverters.EndpointBuilder()
36+
.addPathPartAsIs("_xpack/security/user")
37+
.addPathPart(putUserRequest.getUsername())
38+
.build();
39+
Request request = new Request(HttpPut.METHOD_NAME, endpoint);
40+
request.setEntity(createEntity(putUserRequest, REQUEST_BODY_CONTENT_TYPE));
41+
RequestConverters.Params params = new RequestConverters.Params(request);
42+
params.withRefreshPolicy(putUserRequest.getRefreshPolicy());
43+
return request;
44+
}
45+
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
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+
import org.elasticsearch.client.ValidationException;
24+
import org.elasticsearch.common.CharArrays;
25+
import org.elasticsearch.common.xcontent.ToXContentObject;
26+
import org.elasticsearch.common.xcontent.XContentBuilder;
27+
28+
import java.io.Closeable;
29+
import java.io.IOException;
30+
import java.util.Arrays;
31+
import java.util.Collections;
32+
import java.util.List;
33+
import java.util.Map;
34+
import java.util.Objects;
35+
import java.util.Optional;
36+
37+
/**
38+
* Request object to create or update a user in the native realm.
39+
*/
40+
public final class PutUserRequest implements Validatable, Closeable, ToXContentObject {
41+
42+
private final String username;
43+
private final List<String> roles;
44+
private final String fullName;
45+
private final String email;
46+
private final Map<String, Object> metadata;
47+
private final char[] password;
48+
private final boolean enabled;
49+
private final RefreshPolicy refreshPolicy;
50+
51+
public PutUserRequest(String username, char[] password, List<String> roles, String fullName, String email, boolean enabled,
52+
Map<String, Object> metadata, RefreshPolicy refreshPolicy) {
53+
this.username = Objects.requireNonNull(username, "username is required");
54+
this.password = password;
55+
this.roles = Collections.unmodifiableList(Objects.requireNonNull(roles, "roles must be specified"));
56+
this.fullName = fullName;
57+
this.email = email;
58+
this.enabled = enabled;
59+
this.metadata = metadata == null ? Collections.emptyMap() : Collections.unmodifiableMap(metadata);
60+
this.refreshPolicy = refreshPolicy == null ? RefreshPolicy.getDefault() : refreshPolicy;
61+
}
62+
63+
public String getUsername() {
64+
return username;
65+
}
66+
67+
public List<String> getRoles() {
68+
return roles;
69+
}
70+
71+
public String getFullName() {
72+
return fullName;
73+
}
74+
75+
public String getEmail() {
76+
return email;
77+
}
78+
79+
public Map<String, Object> getMetadata() {
80+
return metadata;
81+
}
82+
83+
public char[] getPassword() {
84+
return password;
85+
}
86+
87+
public boolean isEnabled() {
88+
return enabled;
89+
}
90+
91+
public RefreshPolicy getRefreshPolicy() {
92+
return refreshPolicy;
93+
}
94+
95+
@Override
96+
public boolean equals(Object o) {
97+
if (this == o) return true;
98+
if (o == null || getClass() != o.getClass()) return false;
99+
PutUserRequest that = (PutUserRequest) o;
100+
return enabled == that.enabled &&
101+
Objects.equals(username, that.username) &&
102+
Objects.equals(roles, that.roles) &&
103+
Objects.equals(fullName, that.fullName) &&
104+
Objects.equals(email, that.email) &&
105+
Objects.equals(metadata, that.metadata) &&
106+
Arrays.equals(password, that.password) &&
107+
refreshPolicy == that.refreshPolicy;
108+
}
109+
110+
@Override
111+
public int hashCode() {
112+
int result = Objects.hash(username, roles, fullName, email, metadata, enabled, refreshPolicy);
113+
result = 31 * result + Arrays.hashCode(password);
114+
return result;
115+
}
116+
117+
@Override
118+
public void close() {
119+
if (password != null) {
120+
Arrays.fill(password, (char) 0);
121+
}
122+
}
123+
124+
@Override
125+
public Optional<ValidationException> validate() {
126+
if (metadata != null && metadata.keySet().stream().anyMatch(s -> s.startsWith("_"))) {
127+
ValidationException validationException = new ValidationException();
128+
validationException.addValidationError("metadata keys may not start with [_]");
129+
return Optional.of(validationException);
130+
}
131+
return Optional.empty();
132+
}
133+
134+
@Override
135+
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
136+
builder.startObject();
137+
builder.field("username", username);
138+
if (password != null) {
139+
byte[] charBytes = CharArrays.toUtf8Bytes(password);
140+
builder.field("password").utf8Value(charBytes, 0, charBytes.length);
141+
}
142+
if (roles != null) {
143+
builder.field("roles", roles);
144+
}
145+
if (fullName != null) {
146+
builder.field("full_name", fullName);
147+
}
148+
if (email != null) {
149+
builder.field("email", email);
150+
}
151+
if (metadata != null) {
152+
builder.field("metadata", metadata);
153+
}
154+
return builder.endObject();
155+
}
156+
}

0 commit comments

Comments
 (0)