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
This commit is contained in:
Jay Modi 2018-09-05 10:56:30 -06:00 committed by GitHub
parent 7eef7f441b
commit ea52277a1e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 622 additions and 38 deletions

View File

@ -77,6 +77,27 @@ forbiddenApisMain {
signaturesFiles += files('src/main/resources/forbidden/rest-high-level-signatures.txt')
}
integTestRunner {
systemProperty 'tests.rest.cluster.username', System.getProperty('tests.rest.cluster.username', 'test_user')
systemProperty 'tests.rest.cluster.password', System.getProperty('tests.rest.cluster.password', 'test-password')
}
integTestCluster {
setting 'xpack.license.self_generated.type', 'trial'
setting 'xpack.security.enabled', 'true'
setupCommand 'setupDummyUser',
'bin/elasticsearch-users',
'useradd', System.getProperty('tests.rest.cluster.username', 'test_user'),
'-p', System.getProperty('tests.rest.cluster.password', 'test-password'),
'-r', 'superuser'
waitCondition = { node, ant ->
File tmpFile = new File(node.cwd, 'wait.success')
ant.get(src: "http://${node.httpUri()}/_cluster/health?wait_for_nodes=>=${numNodes}&wait_for_status=yellow",
dest: tmpFile.toString(),
username: System.getProperty('tests.rest.cluster.username', 'test_user'),
password: System.getProperty('tests.rest.cluster.password', 'test-password'),
ignoreerrors: true,
retries: 10)
return tmpFile.exists()
}
}

View File

@ -217,6 +217,7 @@ public class RestHighLevelClient implements Closeable {
private final LicenseClient licenseClient = new LicenseClient(this);
private final MigrationClient migrationClient = new MigrationClient(this);
private final MachineLearningClient machineLearningClient = new MachineLearningClient(this);
private final SecurityClient securityClient = new SecurityClient(this);
/**
* Creates a {@link RestHighLevelClient} given the low level {@link RestClientBuilder} that allows to build the
@ -376,6 +377,20 @@ public class RestHighLevelClient implements Closeable {
return machineLearningClient;
}
/**
* Provides methods for accessing the Elastic Licensed Security APIs that
* are shipped with the Elastic Stack distribution of Elasticsearch. All of
* these APIs will 404 if run against the OSS distribution of Elasticsearch.
* <p>
* See the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api.html">
* Security APIs on elastic.co</a> for more information.
*
* @return the client wrapper for making Security API calls
*/
public SecurityClient security() {
return securityClient;
}
/**
* Executes a bulk request using the Bulk API.
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html">Bulk API on elastic.co</a>

View File

@ -0,0 +1,69 @@
/*
* 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;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.client.security.PutUserRequest;
import org.elasticsearch.client.security.PutUserResponse;
import java.io.IOException;
import static java.util.Collections.emptySet;
/**
* A wrapper for the {@link RestHighLevelClient} that provides methods for accessing the Security APIs.
* <p>
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api.html">Security APIs on elastic.co</a>
*/
public final class SecurityClient {
private final RestHighLevelClient restHighLevelClient;
SecurityClient(RestHighLevelClient restHighLevelClient) {
this.restHighLevelClient = restHighLevelClient;
}
/**
* Create/update a user in the native realm synchronously.
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-users.html">
* the docs</a> for more.
* @param request the request with the user's information
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
* @return the response from the put user call
* @throws IOException in case there is a problem sending the request or parsing back the response
*/
public PutUserResponse putUser(PutUserRequest request, RequestOptions options) throws IOException {
return restHighLevelClient.performRequestAndParseEntity(request, SecurityRequestConverters::putUser, options,
PutUserResponse::fromXContent, emptySet());
}
/**
* Asynchronously create/update a user in the native realm.
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-users.html">
* the docs</a> for more.
* @param request the request with the user's information
* @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 putUserAsync(PutUserRequest request, RequestOptions options, ActionListener<PutUserResponse> listener) {
restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::putUser, options,
PutUserResponse::fromXContent, listener, emptySet());
}
}

View File

@ -0,0 +1,45 @@
/*
* 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;
import org.apache.http.client.methods.HttpPut;
import org.elasticsearch.client.security.PutUserRequest;
import java.io.IOException;
import static org.elasticsearch.client.RequestConverters.REQUEST_BODY_CONTENT_TYPE;
import static org.elasticsearch.client.RequestConverters.createEntity;
public final class SecurityRequestConverters {
private SecurityRequestConverters() {}
static Request putUser(PutUserRequest putUserRequest) throws IOException {
String endpoint = new RequestConverters.EndpointBuilder()
.addPathPartAsIs("_xpack/security/user")
.addPathPart(putUserRequest.getUsername())
.build();
Request request = new Request(HttpPut.METHOD_NAME, endpoint);
request.setEntity(createEntity(putUserRequest, REQUEST_BODY_CONTENT_TYPE));
RequestConverters.Params params = new RequestConverters.Params(request);
params.withRefreshPolicy(putUserRequest.getRefreshPolicy());
return request;
}
}

View File

@ -0,0 +1,156 @@
/*
* 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.client.ValidationException;
import org.elasticsearch.common.CharArrays;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.Closeable;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
/**
* Request object to create or update a user in the native realm.
*/
public final class PutUserRequest implements Validatable, Closeable, ToXContentObject {
private final String username;
private final List<String> roles;
private final String fullName;
private final String email;
private final Map<String, Object> metadata;
private final char[] password;
private final boolean enabled;
private final RefreshPolicy refreshPolicy;
public PutUserRequest(String username, char[] password, List<String> roles, String fullName, String email, boolean enabled,
Map<String, Object> metadata, RefreshPolicy refreshPolicy) {
this.username = Objects.requireNonNull(username, "username is required");
this.password = password;
this.roles = Collections.unmodifiableList(Objects.requireNonNull(roles, "roles must be specified"));
this.fullName = fullName;
this.email = email;
this.enabled = enabled;
this.metadata = metadata == null ? Collections.emptyMap() : Collections.unmodifiableMap(metadata);
this.refreshPolicy = refreshPolicy == null ? RefreshPolicy.getDefault() : refreshPolicy;
}
public String getUsername() {
return username;
}
public List<String> getRoles() {
return roles;
}
public String getFullName() {
return fullName;
}
public String getEmail() {
return email;
}
public Map<String, Object> getMetadata() {
return metadata;
}
public char[] getPassword() {
return password;
}
public boolean isEnabled() {
return enabled;
}
public RefreshPolicy getRefreshPolicy() {
return refreshPolicy;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PutUserRequest that = (PutUserRequest) o;
return enabled == that.enabled &&
Objects.equals(username, that.username) &&
Objects.equals(roles, that.roles) &&
Objects.equals(fullName, that.fullName) &&
Objects.equals(email, that.email) &&
Objects.equals(metadata, that.metadata) &&
Arrays.equals(password, that.password) &&
refreshPolicy == that.refreshPolicy;
}
@Override
public int hashCode() {
int result = Objects.hash(username, roles, fullName, email, metadata, enabled, refreshPolicy);
result = 31 * result + Arrays.hashCode(password);
return result;
}
@Override
public void close() {
if (password != null) {
Arrays.fill(password, (char) 0);
}
}
@Override
public Optional<ValidationException> validate() {
if (metadata != null && metadata.keySet().stream().anyMatch(s -> s.startsWith("_"))) {
ValidationException validationException = new ValidationException();
validationException.addValidationError("metadata keys may not start with [_]");
return Optional.of(validationException);
}
return Optional.empty();
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field("username", username);
if (password != null) {
byte[] charBytes = CharArrays.toUtf8Bytes(password);
builder.field("password").utf8Value(charBytes, 0, charBytes.length);
}
if (roles != null) {
builder.field("roles", roles);
}
if (fullName != null) {
builder.field("full_name", fullName);
}
if (email != null) {
builder.field("email", email);
}
if (metadata != null) {
builder.field("metadata", metadata);
}
return builder.endObject();
}
}

View File

@ -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.common.ParseField;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
import java.util.Objects;
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
/**
* Response when adding a user to the native realm. Returns a
* single boolean field for whether the user was created or updated.
*/
public final class PutUserResponse {
private final boolean created;
public PutUserResponse(boolean created) {
this.created = created;
}
public boolean isCreated() {
return created;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PutUserResponse that = (PutUserResponse) o;
return created == that.created;
}
@Override
public int hashCode() {
return Objects.hash(created);
}
private static final ConstructingObjectParser<PutUserResponse, Void> PARSER = new ConstructingObjectParser<>("put_user_response",
true, args -> new PutUserResponse((boolean) args[0]));
static {
PARSER.declareBoolean(constructorArg(), new ParseField("created"));
PARSER.declareObject((a,b) -> {}, (parser, context) -> null, new ParseField("user")); // ignore the user field!
}
public static PutUserResponse fromXContent(XContentParser parser) throws IOException {
return PARSER.parse(parser, null);
}
}

View File

@ -25,6 +25,7 @@ import org.elasticsearch.action.ingest.PutPipelineRequest;
import org.elasticsearch.action.support.PlainActionFuture;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.ingest.Pipeline;
@ -33,7 +34,10 @@ import org.junit.AfterClass;
import org.junit.Before;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Collections;
import java.util.Objects;
public abstract class ESRestHighLevelClientTestCase extends ESRestTestCase {
@ -137,4 +141,15 @@ public abstract class ESRestHighLevelClientTestCase extends ESRestTestCase {
assertTrue(execute(
request, highLevelClient().cluster()::putSettings, highLevelClient().cluster()::putSettingsAsync).isAcknowledged());
}
@Override
protected Settings restClientSettings() {
final String user = Objects.requireNonNull(System.getProperty("tests.rest.cluster.username"));
final String pass = Objects.requireNonNull(System.getProperty("tests.rest.cluster.password"));
final String token = "Basic " + Base64.getEncoder().encodeToString((user + ":" + pass).getBytes(StandardCharsets.UTF_8));
return Settings.builder()
.put(super.restClientSettings())
.put(ThreadContext.PREFIX + ".Authorization", token)
.build();
}
}

View File

@ -2438,7 +2438,7 @@ public class RequestConvertersTests extends ESTestCase {
assertThat(request.getEntity(), nullValue());
}
private static void assertToXContentBody(ToXContent expectedBody, HttpEntity actualEntity) throws IOException {
static void assertToXContentBody(ToXContent expectedBody, HttpEntity actualEntity) throws IOException {
BytesReference expectedBytes = XContentHelper.toXContent(expectedBody, REQUEST_BODY_CONTENT_TYPE, false);
assertEquals(XContentType.JSON.mediaTypeWithoutParameters(), actualEntity.getContentType().getValue());
assertEquals(expectedBytes, new BytesArray(EntityUtils.toByteArray(actualEntity)));

View File

@ -757,7 +757,8 @@ public class RestHighLevelClientTests extends ESTestCase {
apiName.startsWith("machine_learning.") == false &&
apiName.startsWith("watcher.") == false &&
apiName.startsWith("graph.") == false &&
apiName.startsWith("migration.") == false) {
apiName.startsWith("migration.") == false &&
apiName.startsWith("security.") == false) {
apiNotFound.add(apiName);
}
}

View File

@ -0,0 +1,70 @@
/*
* 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;
import org.apache.http.client.methods.HttpPut;
import org.elasticsearch.client.security.PutUserRequest;
import org.elasticsearch.client.security.RefreshPolicy;
import org.elasticsearch.test.ESTestCase;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.elasticsearch.client.RequestConvertersTests.assertToXContentBody;
public class SecurityRequestConvertersTests extends ESTestCase {
public void testPutUser() throws IOException {
final String username = randomAlphaOfLengthBetween(4, 12);
final char[] password = randomBoolean() ? randomAlphaOfLengthBetween(8, 12).toCharArray() : null;
final List<String> roles = Arrays.asList(generateRandomStringArray(randomIntBetween(2, 8), randomIntBetween(8, 16), false, true));
final String email = randomBoolean() ? null : randomAlphaOfLengthBetween(12, 24);
final String fullName = randomBoolean() ? null : randomAlphaOfLengthBetween(7, 14);
final boolean enabled = randomBoolean();
final Map<String, Object> metadata;
if (randomBoolean()) {
metadata = new HashMap<>();
for (int i = 0; i < randomIntBetween(0, 10); i++) {
metadata.put(String.valueOf(i), randomAlphaOfLengthBetween(1, 12));
}
} else {
metadata = null;
}
final RefreshPolicy refreshPolicy = randomFrom(RefreshPolicy.values());
final Map<String, String> expectedParams;
if (refreshPolicy != RefreshPolicy.NONE) {
expectedParams = Collections.singletonMap("refresh", refreshPolicy.getValue());
} else {
expectedParams = Collections.emptyMap();
}
PutUserRequest putUserRequest = new PutUserRequest(username, password, roles, fullName, email, enabled, metadata, refreshPolicy);
Request request = SecurityRequestConverters.putUser(putUserRequest);
assertEquals(HttpPut.METHOD_NAME, request.getMethod());
assertEquals("/_xpack/security/user/" + putUserRequest.getUsername(), request.getEndpoint());
assertEquals(expectedParams, request.getParameters());
assertToXContentBody(putUserRequest, request.getEntity());
}
}

View File

@ -0,0 +1,84 @@
/*
* 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.documentation;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.LatchedActionListener;
import org.elasticsearch.client.ESRestHighLevelClientTestCase;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.security.PutUserRequest;
import org.elasticsearch.client.security.PutUserResponse;
import org.elasticsearch.client.security.RefreshPolicy;
import java.util.Collections;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
public void testPutUser() throws Exception {
RestHighLevelClient client = highLevelClient();
{
//tag::x-pack-put-user-execute
char[] password = new char[] { 'p', 'a', 's', 's', 'w', 'o', 'r', 'd' };
PutUserRequest request =
new PutUserRequest("example", password, Collections.singletonList("superuser"), null, null, true, null, RefreshPolicy.NONE);
PutUserResponse response = client.security().putUser(request, RequestOptions.DEFAULT);
//end::x-pack-put-user-execute
//tag::x-pack-put-user-response
boolean isCreated = response.isCreated(); // <1>
//end::x-pack-put-user-response
assertTrue(isCreated);
}
{
char[] password = new char[] { 'p', 'a', 's', 's', 'w', 'o', 'r', 'd' };
PutUserRequest request = new PutUserRequest("example2", password, Collections.singletonList("superuser"), null, null, true,
null, RefreshPolicy.NONE);
// tag::x-pack-put-user-execute-listener
ActionListener<PutUserResponse> listener = new ActionListener<PutUserResponse>() {
@Override
public void onResponse(PutUserResponse response) {
// <1>
}
@Override
public void onFailure(Exception e) {
// <2>
}
};
// end::x-pack-put-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::x-pack-put-user-execute-async
client.security().putUserAsync(request, RequestOptions.DEFAULT, listener); // <1>
// end::x-pack-put-user-execute-async
assertTrue(latch.await(30L, TimeUnit.SECONDS));
}
}
}

View File

@ -0,0 +1,52 @@
[[java-rest-high-x-pack-security-put-user]]
=== X-Pack Put User API
[[java-rest-high-x-pack-security-put-user-execution]]
==== Execution
Creating and updating a user can be performed using the `security().putUser()`
method:
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/SecurityDocumentationIT.java[x-pack-put-user-execute]
--------------------------------------------------
[[java-rest-high-x-pack-security-put-user-response]]
==== Response
The returned `PutUserResponse` contains a single field, `created`. This field
serves as an indication if a user was created or if an existing entry was updated.
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/SecurityDocumentationIT.java[x-pack-put-user-response]
--------------------------------------------------
<1> `created` is a boolean indicating whether the user was created or updated
[[java-rest-high-x-pack-security-put-user-async]]
==== Asynchronous Execution
This request can be executed asynchronously:
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/SecurityDocumentationIT.java[x-pack-put-user-execute-async]
--------------------------------------------------
<1> The `PutUserResponse` to execute and the `ActionListener` to use when
the execution completes
The asynchronous method does not block and returns immediately. Once the request
has completed the `ActionListener` is called back using the `onResponse` method
if the execution successfully completed or using the `onFailure` method if
it failed.
A typical listener for a `PutUserResponse` looks like:
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/SecurityDocumentationIT.java[x-pack-put-user-execute-listener]
--------------------------------------------------
<1> Called when the execution is successfully completed. The response is
provided as an argument
<2> Called in case of failure. The raised exception is provided as an argument

View File

@ -91,8 +91,9 @@ created or updated.
--------------------------------------------------
{
"user": {
"created" : true <1>
}
"created" : true
},
"created": true <1>
}
--------------------------------------------------
// TESTRESPONSE

View File

@ -151,4 +151,4 @@ public class PutRoleMappingRequest extends ActionRequest
enabled
);
}
}
}

View File

@ -3,6 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.core.security.action.user;
import org.elasticsearch.action.ActionRequest;
@ -31,7 +32,6 @@ public class PutUserRequest extends ActionRequest implements UserRequest, WriteR
private String email;
private Map<String, Object> metadata;
private char[] passwordHash;
private char[] password;
private boolean enabled = true;
private RefreshPolicy refreshPolicy = RefreshPolicy.IMMEDIATE;
@ -50,9 +50,6 @@ public class PutUserRequest extends ActionRequest implements UserRequest, WriteR
if (metadata != null && metadata.keySet().stream().anyMatch(s -> s.startsWith("_"))) {
validationException = addValidationError("metadata keys may not start with [_]", validationException);
}
if (password != null && passwordHash != null) {
validationException = addValidationError("only one of [password, passwordHash] can be provided", validationException);
}
// we do not check for a password hash here since it is possible that the user exists and we don't want to update the password
return validationException;
}
@ -85,10 +82,6 @@ public class PutUserRequest extends ActionRequest implements UserRequest, WriteR
this.enabled = enabled;
}
public void password(@Nullable char[] password) {
this.password = password;
}
/**
* Should this request trigger a refresh ({@linkplain RefreshPolicy#IMMEDIATE}, the default), wait for a refresh (
* {@linkplain RefreshPolicy#WAIT_UNTIL}), or proceed ignore refreshes entirely ({@linkplain RefreshPolicy#NONE}).
@ -138,11 +131,6 @@ public class PutUserRequest extends ActionRequest implements UserRequest, WriteR
return new String[] { username };
}
@Nullable
public char[] password() {
return password;
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
@ -161,9 +149,6 @@ public class PutUserRequest extends ActionRequest implements UserRequest, WriteR
super.writeTo(out);
out.writeString(username);
writeCharArrayToStream(out, passwordHash);
if (password != null) {
throw new IllegalStateException("password cannot be serialized. it is only used for HL rest");
}
out.writeStringArray(roles);
out.writeOptionalString(fullName);
out.writeOptionalString(email);

View File

@ -3,12 +3,13 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.core.security.action.user;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.ToXContentFragment;
import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
@ -17,7 +18,7 @@ import java.io.IOException;
* Response when adding a user to the security index. Returns a
* single boolean field for whether the user was created or updated.
*/
public class PutUserResponse extends ActionResponse implements ToXContentObject {
public class PutUserResponse extends ActionResponse implements ToXContentFragment {
private boolean created;
@ -32,12 +33,6 @@ public class PutUserResponse extends ActionResponse implements ToXContentObject
return created;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject().field("created", created).endObject();
return builder;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
@ -49,4 +44,9 @@ public class PutUserResponse extends ActionResponse implements ToXContentObject
super.readFrom(in);
this.created = in.readBoolean();
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.field("created", created);
}
}

View File

@ -6,7 +6,6 @@
package org.elasticsearch.xpack.core.security.user;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.core.security.user.User;
import java.util.Collections;

View File

@ -93,10 +93,6 @@ public class TransportPutUserAction extends HandledTransportAction<PutUserReques
}
}
}
if (request.password() != null) {
validationException = addValidationError("password should never be passed to the transport action", validationException);
}
return validationException;
}
}

View File

@ -58,10 +58,13 @@ public class RestPutUserAction extends SecurityBaseRestHandler implements RestRe
return channel -> requestBuilder.execute(new RestBuilderListener<PutUserResponse>(channel) {
@Override
public RestResponse buildResponse(PutUserResponse putUserResponse, XContentBuilder builder) throws Exception {
return new BytesRestResponse(RestStatus.OK,
builder.startObject()
.field("user", putUserResponse)
.endObject());
builder.startObject()
.startObject("user"); // TODO in 7.0 remove wrapping of response in the user object and just return the object
putUserResponse.toXContent(builder, request);
builder.endObject();
putUserResponse.toXContent(builder, request);
return new BytesRestResponse(RestStatus.OK, builder.endObject());
}
});
}

View File

@ -3,6 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.action.user;
import org.elasticsearch.action.ActionRequestValidationException;