Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4b8c300
HLRest: add xpack put user API
jaymode Jul 24, 2018
3d9c92f
Merge branch 'master' into hl_rest_put_user
jaymode Jul 24, 2018
0d484b5
Merge branch 'master' into hl_rest_put_user
jaymode Jul 25, 2018
c322f08
Merge branch 'master' into hl_rest_put_user
jaymode Aug 16, 2018
fc093c7
Merge branch 'master' into hl_rest_put_user
jaymode Aug 16, 2018
771b074
revert changes to CharArrays
jaymode Aug 16, 2018
577de8e
fixes after updating
jaymode Aug 16, 2018
488a0b9
change put user response so we can remove wrapping in 7.0
jaymode Aug 16, 2018
2d5d16b
revert unnecessary changes
jaymode Aug 16, 2018
d743360
Merge branch 'master' into hl_rest_put_user
jaymode Aug 24, 2018
c19ed15
move requests back to core
jaymode Aug 24, 2018
16c57f5
remove some changes to server objects
jaymode Aug 24, 2018
b799b37
more moves back
jaymode Aug 24, 2018
93d109c
new style req/resp
jaymode Aug 24, 2018
f41436d
remove hasher validation
jaymode Aug 24, 2018
ec13763
Merge branch 'master' into hl_rest_put_user
jaymode Aug 24, 2018
568d53e
Merge branch 'master' into hl_rest_put_user
jaymode Aug 27, 2018
400626a
fix compile
jaymode Aug 27, 2018
f1df362
Merge branch 'master' into hl_rest_put_user
jaymode Aug 28, 2018
4d2b9e3
Merge branch 'master' into hl_rest_put_user
jaymode Aug 28, 2018
008bf6a
HLRC: add client side RefreshPolicy
jaymode Aug 28, 2018
741321a
add refresh policy back to request
jaymode Aug 28, 2018
213bd47
fix response
jaymode Aug 28, 2018
f4bf017
Merge branch 'master' into hl_rest_put_user
jaymode Aug 28, 2018
7d6341c
Merge branch 'master' into hl_rest_put_user
jaymode Aug 29, 2018
a225363
Merge branch 'master' into hl_rest_put_user
jaymode Aug 30, 2018
373f89a
update pkg
jaymode Aug 30, 2018
eeb5ab5
fix compile
jaymode Aug 30, 2018
0e93800
Merge branch 'master' into hl_rest_put_user
jaymode Aug 30, 2018
9d29380
really this time
jaymode Aug 30, 2018
04f5dee
Merge branch 'master' into hl_rest_put_user
jaymode Sep 4, 2018
899dd8b
fix copy paste error
jaymode Sep 4, 2018
0a65cf1
add space
jaymode Sep 4, 2018
e109dea
address feedback from baz
jaymode Sep 4, 2018
8633a9c
put user body has username
jaymode Sep 4, 2018
937a48a
Merge branch 'master' into hl_rest_put_user
jaymode Sep 5, 2018
16014ff
add assert
jaymode Sep 5, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions client/rest-high-level/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -376,6 +377,20 @@ public MachineLearningClient machineLearning() {
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>
Expand Down
Original file line number Diff line number Diff line change
@@ -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());
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe organize under package org.elasticsearch.client.security.user? As we will keep adding a lot of such classes in the same package for different APIs like role mapping etc.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you think its too much to put them all in the same package, I wont enforce keeping them in one package. I certainly do not think the number of classes that currently exist in all of the action.* classes is too many for a single package here. But I dont think we need to enforce it one way or the other.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need more packages. I think our goal should be to keep the number of packages to a minimum and at this point I do not see justification for it.


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);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jaymode
enabled is missing, so this HL REST API will only be putting enabled=true users.
I have actually pulled out enabled out of the User object on the client side (which I am arduously peddling #33552 (comment)) but the PutUserRequest, containing the User object, has to have an enabled flag that it puts in the XContent.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Adding a test in SecurityIT introduced by #33552 is one way to test for this)

return builder.endObject();
}
}
Loading