Introducing user full name, email and metadata.
- `full_name` and `email` are optional user fields - `metadata` is an optional arbitrary meta data that can be associated with the user - cleaned up the user actions - consistent naming (e.g. `PutUserAction` vs. `AddUserAction`) - moved source parsing from the `PutUserRequest` to the `PutUserRequestBuilder` - renamed`WatcherXContentUtils` to `XContentUtils` and moved it to sit under `o.e.xpack.commons.xcontent` Closes elastic/elasticsearch#412 Original commit: elastic/x-pack-elasticsearch@5460e3caf7
This commit is contained in:
parent
6d0d09468b
commit
18b08c82ca
|
@ -28,7 +28,7 @@ import org.elasticsearch.shield.action.role.TransportAddRoleAction;
|
|||
import org.elasticsearch.shield.action.role.TransportClearRolesCacheAction;
|
||||
import org.elasticsearch.shield.action.role.TransportDeleteRoleAction;
|
||||
import org.elasticsearch.shield.action.role.TransportGetRolesAction;
|
||||
import org.elasticsearch.shield.action.user.AddUserAction;
|
||||
import org.elasticsearch.shield.action.user.PutUserAction;
|
||||
import org.elasticsearch.shield.action.user.DeleteUserAction;
|
||||
import org.elasticsearch.shield.action.user.GetUsersAction;
|
||||
import org.elasticsearch.shield.action.user.TransportAddUserAction;
|
||||
|
@ -59,7 +59,7 @@ import org.elasticsearch.shield.rest.action.role.RestAddRoleAction;
|
|||
import org.elasticsearch.shield.rest.action.role.RestClearRolesCacheAction;
|
||||
import org.elasticsearch.shield.rest.action.role.RestDeleteRoleAction;
|
||||
import org.elasticsearch.shield.rest.action.role.RestGetRolesAction;
|
||||
import org.elasticsearch.shield.rest.action.user.RestAddUserAction;
|
||||
import org.elasticsearch.shield.rest.action.user.RestPutUserAction;
|
||||
import org.elasticsearch.shield.rest.action.user.RestDeleteUserAction;
|
||||
import org.elasticsearch.shield.rest.action.user.RestGetUsersAction;
|
||||
import org.elasticsearch.shield.ssl.SSLModule;
|
||||
|
@ -242,7 +242,7 @@ public class Shield {
|
|||
module.registerAction(ClearRealmCacheAction.INSTANCE, TransportClearRealmCacheAction.class);
|
||||
module.registerAction(ClearRolesCacheAction.INSTANCE, TransportClearRolesCacheAction.class);
|
||||
module.registerAction(GetUsersAction.INSTANCE, TransportGetUsersAction.class);
|
||||
module.registerAction(AddUserAction.INSTANCE, TransportAddUserAction.class);
|
||||
module.registerAction(PutUserAction.INSTANCE, TransportAddUserAction.class);
|
||||
module.registerAction(DeleteUserAction.INSTANCE, TransportDeleteUserAction.class);
|
||||
module.registerAction(GetRolesAction.INSTANCE, TransportGetRolesAction.class);
|
||||
module.registerAction(AddRoleAction.INSTANCE, TransportAddRoleAction.class);
|
||||
|
@ -269,7 +269,7 @@ public class Shield {
|
|||
module.registerRestHandler(RestClearRealmCacheAction.class);
|
||||
module.registerRestHandler(RestClearRolesCacheAction.class);
|
||||
module.registerRestHandler(RestGetUsersAction.class);
|
||||
module.registerRestHandler(RestAddUserAction.class);
|
||||
module.registerRestHandler(RestPutUserAction.class);
|
||||
module.registerRestHandler(RestDeleteUserAction.class);
|
||||
module.registerRestHandler(RestGetRolesAction.class);
|
||||
module.registerRestHandler(RestAddRoleAction.class);
|
||||
|
|
|
@ -6,14 +6,20 @@
|
|||
package org.elasticsearch.shield;
|
||||
|
||||
import org.elasticsearch.ElasticsearchSecurityException;
|
||||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.inject.internal.Nullable;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An authenticated user
|
||||
|
@ -23,19 +29,37 @@ public class User implements ToXContent {
|
|||
private final String username;
|
||||
private final String[] roles;
|
||||
private final User runAs;
|
||||
private final Map<String, Object> metadata;
|
||||
|
||||
private final @Nullable String fullName;
|
||||
private final @Nullable String email;
|
||||
|
||||
public User(String username, String... roles) {
|
||||
this.username = username;
|
||||
this.roles = roles == null ? Strings.EMPTY_ARRAY : roles;
|
||||
this.runAs = null;
|
||||
this(username, roles, null, null, null);
|
||||
}
|
||||
|
||||
public User(String username, String[] roles, User runAs) {
|
||||
this(username, roles, null, null, null, runAs);
|
||||
}
|
||||
|
||||
public User(String username, String[] roles, String fullName, String email, Map<String, Object> metadata) {
|
||||
this.username = username;
|
||||
this.roles = roles == null ? Strings.EMPTY_ARRAY : roles;
|
||||
assert (runAs == null || runAs.runAs() == null) : "the runAs user should not be a user that can run as";
|
||||
this.metadata = metadata != null ? Collections.unmodifiableMap(metadata) : Collections.emptyMap();
|
||||
this.fullName = fullName;
|
||||
this.email = email;
|
||||
this.runAs = null;
|
||||
}
|
||||
|
||||
public User(String username, String[] roles, String fullName, String email, Map<String, Object> metadata, User runAs) {
|
||||
this.username = username;
|
||||
this.roles = roles == null ? Strings.EMPTY_ARRAY : roles;
|
||||
this.metadata = metadata != null ? Collections.unmodifiableMap(metadata) : Collections.emptyMap();
|
||||
this.fullName = fullName;
|
||||
this.email = email;
|
||||
assert (runAs == null || runAs.runAs() == null) : "the run_as user should not be a user that can run as";
|
||||
if (runAs == SystemUser.INSTANCE) {
|
||||
throw new ElasticsearchSecurityException("the runAs user cannot be the internal system user");
|
||||
throw new ElasticsearchSecurityException("invalid run_as user");
|
||||
}
|
||||
this.runAs = runAs;
|
||||
}
|
||||
|
@ -57,6 +81,27 @@ public class User implements ToXContent {
|
|||
return this.roles;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The metadata that is associated with this user. Can never be {@code null}.
|
||||
*/
|
||||
public Map<String, Object> metadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The full name of this user. May be {@code null}.
|
||||
*/
|
||||
public String fullName() {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The email of this user. May be {@code null}.
|
||||
*/
|
||||
public String email() {
|
||||
return email;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The user that will be used for run as functionality. If run as
|
||||
* functionality is not being used, then <code>null</code> will be
|
||||
|
@ -70,13 +115,11 @@ public class User implements ToXContent {
|
|||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("User[username=").append(username);
|
||||
sb.append(",roles=[");
|
||||
if (roles != null) {
|
||||
for (String role : roles) {
|
||||
sb.append(role).append(",");
|
||||
}
|
||||
}
|
||||
sb.append("]");
|
||||
sb.append(",roles=[").append(Strings.arrayToCommaDelimitedString(roles)).append("]");
|
||||
sb.append(",fullName=").append(fullName);
|
||||
sb.append(",email=").append(email);
|
||||
sb.append(",metadata=");
|
||||
append(sb, metadata);
|
||||
if (runAs != null) {
|
||||
sb.append(",runAs=[").append(runAs.toString()).append("]");
|
||||
}
|
||||
|
@ -87,31 +130,39 @@ public class User implements ToXContent {
|
|||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null) return false;
|
||||
if (!(o instanceof User)) return false;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
User user = (User) o;
|
||||
if (!principal().equals(user.principal())) return false;
|
||||
if (!Arrays.equals(roles(), user.roles())) return false;
|
||||
if (runAs != null ? !runAs.equals(user.runAs) : user.runAs != null) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
if (!username.equals(user.username)) return false;
|
||||
// Probably incorrect - comparing Object[] arrays with Arrays.equals
|
||||
if (!Arrays.equals(roles, user.roles)) return false;
|
||||
if (runAs != null ? !runAs.equals(user.runAs) : user.runAs != null) return false;
|
||||
if (!metadata.equals(user.metadata)) return false;
|
||||
if (fullName != null ? !fullName.equals(user.fullName) : user.fullName != null) return false;
|
||||
return !(email != null ? !email.equals(user.email) : user.email != null);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = principal().hashCode();
|
||||
result = 31 * result + Arrays.hashCode(roles());
|
||||
int result = username.hashCode();
|
||||
result = 31 * result + Arrays.hashCode(roles);
|
||||
result = 31 * result + (runAs != null ? runAs.hashCode() : 0);
|
||||
result = 31 * result + metadata.hashCode();
|
||||
result = 31 * result + (fullName != null ? fullName.hashCode() : 0);
|
||||
result = 31 * result + (email != null ? email.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject();
|
||||
builder.field("username", principal());
|
||||
builder.array("roles", roles());
|
||||
builder.field(Fields.USERNAME.getPreferredName(), principal());
|
||||
builder.array(Fields.ROLES.getPreferredName(), roles());
|
||||
builder.field(Fields.FULL_NAME.getPreferredName(), fullName);
|
||||
builder.field(Fields.EMAIL.getPreferredName(), email);
|
||||
builder.field(Fields.METADATA.getPreferredName(), metadata);
|
||||
builder.endObject();
|
||||
return builder;
|
||||
}
|
||||
|
@ -130,12 +181,19 @@ public class User implements ToXContent {
|
|||
}
|
||||
String username = input.readString();
|
||||
String[] roles = input.readStringArray();
|
||||
Map<String, Object> metadata = input.readMap();
|
||||
String fullName = input.readOptionalString();
|
||||
String email = input.readOptionalString();
|
||||
if (input.readBoolean()) {
|
||||
String runAsUsername = input.readString();
|
||||
String[] runAsRoles = input.readStringArray();
|
||||
return new User(username, roles, new User(runAsUsername, runAsRoles));
|
||||
Map<String, Object> runAsMetadata = input.readMap();
|
||||
String runAsFullName = input.readOptionalString();
|
||||
String runAsEmail = input.readOptionalString();
|
||||
User runAs = new User(runAsUsername, runAsRoles, runAsFullName, runAsEmail, runAsMetadata);
|
||||
return new User(username, roles, fullName, email, metadata, runAs);
|
||||
}
|
||||
return new User(username, roles);
|
||||
return new User(username, roles, fullName, email, metadata);
|
||||
}
|
||||
|
||||
public static void writeTo(User user, StreamOutput output) throws IOException {
|
||||
|
@ -147,16 +205,67 @@ public class User implements ToXContent {
|
|||
output.writeString(XPackUser.NAME);
|
||||
} else {
|
||||
output.writeBoolean(false);
|
||||
output.writeString(user.principal());
|
||||
output.writeStringArray(user.roles());
|
||||
output.writeString(user.username);
|
||||
output.writeStringArray(user.roles);
|
||||
output.writeMap(user.metadata);
|
||||
output.writeOptionalString(user.fullName);
|
||||
output.writeOptionalString(user.email);
|
||||
if (user.runAs == null) {
|
||||
output.writeBoolean(false);
|
||||
} else {
|
||||
output.writeBoolean(true);
|
||||
output.writeString(user.runAs.principal());
|
||||
output.writeStringArray(user.runAs.roles());
|
||||
output.writeString(user.runAs.username);
|
||||
output.writeStringArray(user.runAs.roles);
|
||||
output.writeMap(user.runAs.metadata);
|
||||
output.writeOptionalString(user.runAs.fullName);
|
||||
output.writeOptionalString(user.runAs.email);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void append(StringBuilder sb, Object object) {
|
||||
if (object == null) {
|
||||
sb.append((Object) null);
|
||||
}
|
||||
if (object instanceof Map) {
|
||||
sb.append("{");
|
||||
for (Map.Entry<String, Object> entry : ((Map<String, Object>)object).entrySet()) {
|
||||
sb.append(entry.getKey()).append("=");
|
||||
append(sb, entry.getValue());
|
||||
}
|
||||
sb.append("}");
|
||||
|
||||
} else if (object instanceof Collection) {
|
||||
sb.append("[");
|
||||
boolean first = true;
|
||||
for (Object item : (Collection) object) {
|
||||
if (!first) {
|
||||
sb.append(",");
|
||||
}
|
||||
append(sb, item);
|
||||
first = false;
|
||||
}
|
||||
sb.append("]");
|
||||
} else if (object.getClass().isArray()) {
|
||||
sb.append("[");
|
||||
for (int i = 0; i < Array.getLength(object); i++) {
|
||||
if (i != 0) {
|
||||
sb.append(",");
|
||||
}
|
||||
append(sb, Array.get(object, i));
|
||||
}
|
||||
sb.append("]");
|
||||
} else {
|
||||
sb.append(object);
|
||||
}
|
||||
}
|
||||
|
||||
public interface Fields {
|
||||
ParseField USERNAME = new ParseField("username");
|
||||
ParseField PASSWORD = new ParseField("password");
|
||||
ParseField ROLES = new ParseField("roles");
|
||||
ParseField FULL_NAME = new ParseField("full_name");
|
||||
ParseField EMAIL = new ParseField("email");
|
||||
ParseField METADATA = new ParseField("metadata");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,130 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* 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.shield.action.user;
|
||||
|
||||
import org.elasticsearch.ElasticsearchParseException;
|
||||
import org.elasticsearch.action.ActionRequest;
|
||||
import org.elasticsearch.action.ActionRequestValidationException;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.xcontent.XContentHelper;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.shield.authc.support.CharArrays;
|
||||
import org.elasticsearch.shield.authc.support.Hasher;
|
||||
import org.elasticsearch.shield.authc.support.SecuredString;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.elasticsearch.action.ValidateActions.addValidationError;
|
||||
|
||||
/**
|
||||
* Request object to add a {@code User} to the shield administrative index
|
||||
*/
|
||||
public class AddUserRequest extends ActionRequest<AddUserRequest> {
|
||||
|
||||
private final Hasher hasher = Hasher.BCRYPT;
|
||||
|
||||
private String username;
|
||||
private String roles[];
|
||||
private char[] passwordHash;
|
||||
|
||||
public AddUserRequest() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionRequestValidationException validate() {
|
||||
ActionRequestValidationException validationException = null;
|
||||
if (username == null) {
|
||||
validationException = addValidationError("user is missing", validationException);
|
||||
}
|
||||
if (roles == null) {
|
||||
validationException = addValidationError("roles are missing", validationException);
|
||||
}
|
||||
if (passwordHash == null) {
|
||||
validationException = addValidationError("passwordHash is missing", validationException);
|
||||
}
|
||||
return validationException;
|
||||
}
|
||||
|
||||
public void username(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public void roles(String... roles) {
|
||||
this.roles = roles;
|
||||
}
|
||||
|
||||
public void passwordHash(char[] passwordHash) {
|
||||
this.passwordHash = passwordHash;
|
||||
}
|
||||
|
||||
public String username() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public String[] roles() {
|
||||
return roles;
|
||||
}
|
||||
|
||||
public char[] passwordHash() {
|
||||
return passwordHash;
|
||||
}
|
||||
|
||||
public AddUserRequest source(BytesReference source) throws Exception {
|
||||
List<String> parsedRoles = new ArrayList<>();
|
||||
try (XContentParser parser = XContentHelper.createParser(source)) {
|
||||
XContentParser.Token token;
|
||||
String currentFieldName = null;
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (token == XContentParser.Token.FIELD_NAME) {
|
||||
currentFieldName = parser.currentName();
|
||||
} else if (token.isValue()) {
|
||||
if ("username".equals(currentFieldName)) {
|
||||
username = parser.text();
|
||||
} else if ("password".equals(currentFieldName)) {
|
||||
// It's assumed the password is plaintext and needs to be hashed
|
||||
passwordHash = hasher.hash(new SecuredString(parser.text().toCharArray()));
|
||||
} else if ("roles".equals(currentFieldName)) {
|
||||
parsedRoles.add(parser.text());
|
||||
} else {
|
||||
throw new ElasticsearchParseException("unexpected field in add user request [{}]", currentFieldName);
|
||||
}
|
||||
} else if (token == XContentParser.Token.START_OBJECT) {
|
||||
// expected
|
||||
} else if (token == XContentParser.Token.START_ARRAY || token == XContentParser.Token.END_ARRAY) {
|
||||
if ("roles".equals(currentFieldName) == false) {
|
||||
throw new ElasticsearchParseException("unexpected array for field [{}]", currentFieldName);
|
||||
}
|
||||
} else {
|
||||
throw new ElasticsearchParseException("failed to parse add user request, got value with wrong type [{}]",
|
||||
currentFieldName);
|
||||
}
|
||||
}
|
||||
}
|
||||
roles = parsedRoles.toArray(Strings.EMPTY_ARRAY);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(StreamInput in) throws IOException {
|
||||
super.readFrom(in);
|
||||
username = in.readString();
|
||||
passwordHash = CharArrays.utf8BytesToChars(in.readByteArray());
|
||||
roles = in.readStringArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
super.writeTo(out);
|
||||
out.writeString(username);
|
||||
out.writeByteArray(CharArrays.toUtf8Bytes(passwordHash));
|
||||
out.writeStringArray(roles);
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* 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.shield.action.user;
|
||||
|
||||
import org.elasticsearch.action.ActionRequestBuilder;
|
||||
import org.elasticsearch.client.ElasticsearchClient;
|
||||
import org.elasticsearch.shield.authc.support.Hasher;
|
||||
import org.elasticsearch.shield.authc.support.SecuredString;
|
||||
|
||||
public class AddUserRequestBuilder extends ActionRequestBuilder<AddUserRequest, AddUserResponse, AddUserRequestBuilder> {
|
||||
|
||||
private final Hasher hasher = Hasher.BCRYPT;
|
||||
|
||||
public AddUserRequestBuilder(ElasticsearchClient client) {
|
||||
this(client, AddUserAction.INSTANCE);
|
||||
}
|
||||
|
||||
public AddUserRequestBuilder(ElasticsearchClient client, AddUserAction action) {
|
||||
super(client, action, new AddUserRequest());
|
||||
}
|
||||
|
||||
public AddUserRequestBuilder username(String username) {
|
||||
request.username(username);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AddUserRequestBuilder roles(String... roles) {
|
||||
request.roles(roles);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AddUserRequestBuilder password(String password) {
|
||||
request.passwordHash(hasher.hash(new SecuredString(password.toCharArray())));
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -9,14 +9,13 @@ import org.elasticsearch.action.Action;
|
|||
import org.elasticsearch.client.ElasticsearchClient;
|
||||
|
||||
/**
|
||||
* Action for deleting a user from the shield administrative index
|
||||
* Action for deleting a native user.
|
||||
*/
|
||||
public class DeleteUserAction extends Action<DeleteUserRequest, DeleteUserResponse, DeleteUserRequestBuilder> {
|
||||
|
||||
public static final DeleteUserAction INSTANCE = new DeleteUserAction();
|
||||
public static final String NAME = "cluster:admin/shield/user/delete";
|
||||
|
||||
|
||||
protected DeleteUserAction() {
|
||||
super(NAME);
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ import java.io.IOException;
|
|||
import static org.elasticsearch.action.ValidateActions.addValidationError;
|
||||
|
||||
/**
|
||||
* A request to delete a user from the shield administrative index by username
|
||||
* A request to delete a native user.
|
||||
*/
|
||||
public class DeleteUserRequest extends ActionRequest<DeleteUserRequest> {
|
||||
|
||||
|
@ -24,24 +24,24 @@ public class DeleteUserRequest extends ActionRequest<DeleteUserRequest> {
|
|||
public DeleteUserRequest() {
|
||||
}
|
||||
|
||||
public DeleteUserRequest(String user) {
|
||||
this.username = user;
|
||||
public DeleteUserRequest(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionRequestValidationException validate() {
|
||||
ActionRequestValidationException validationException = null;
|
||||
if (username == null) {
|
||||
validationException = addValidationError("user is missing", validationException);
|
||||
validationException = addValidationError("username is missing", validationException);
|
||||
}
|
||||
return validationException;
|
||||
}
|
||||
|
||||
public String user() {
|
||||
public String username() {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
public void user(String username) {
|
||||
public void username(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,8 +18,8 @@ public class DeleteUserRequestBuilder extends ActionRequestBuilder<DeleteUserReq
|
|||
super(client, action, new DeleteUserRequest());
|
||||
}
|
||||
|
||||
public DeleteUserRequestBuilder user(String username) {
|
||||
request.user(username);
|
||||
public DeleteUserRequestBuilder username(String username) {
|
||||
request.username(username);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,16 +14,14 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
|
|||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Response when deleting a user from the shield administrative index. Returns a
|
||||
* single boolean field for whether the user was found (and deleted) or not
|
||||
* found.
|
||||
* Response when deleting a native user. Returns a single boolean field for whether the user was
|
||||
* found (and deleted) or not found.
|
||||
*/
|
||||
public class DeleteUserResponse extends ActionResponse implements ToXContent {
|
||||
|
||||
private boolean found;
|
||||
|
||||
public DeleteUserResponse() {
|
||||
|
||||
}
|
||||
|
||||
public DeleteUserResponse(boolean found) {
|
||||
|
|
|
@ -16,7 +16,6 @@ public class GetUsersAction extends Action<GetUsersRequest, GetUsersResponse, Ge
|
|||
public static final GetUsersAction INSTANCE = new GetUsersAction();
|
||||
public static final String NAME = "cluster:admin/shield/user/get";
|
||||
|
||||
|
||||
protected GetUsersAction() {
|
||||
super(NAME);
|
||||
}
|
||||
|
|
|
@ -16,43 +16,43 @@ import java.io.IOException;
|
|||
import static org.elasticsearch.action.ValidateActions.addValidationError;
|
||||
|
||||
/**
|
||||
* Request to retrieve a user from the shield administrative index from a username
|
||||
* Request to retrieve a native user.
|
||||
*/
|
||||
public class GetUsersRequest extends ActionRequest<GetUsersRequest> {
|
||||
|
||||
private String[] users;
|
||||
private String[] usernames;
|
||||
|
||||
public GetUsersRequest() {
|
||||
users = Strings.EMPTY_ARRAY;
|
||||
usernames = Strings.EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionRequestValidationException validate() {
|
||||
ActionRequestValidationException validationException = null;
|
||||
if (users == null) {
|
||||
validationException = addValidationError("users cannot be null", validationException);
|
||||
if (usernames == null) {
|
||||
validationException = addValidationError("usernames cannot be null", validationException);
|
||||
}
|
||||
return validationException;
|
||||
}
|
||||
|
||||
public void users(String... usernames) {
|
||||
this.users = usernames;
|
||||
public void usernames(String... usernames) {
|
||||
this.usernames = usernames;
|
||||
}
|
||||
|
||||
public String[] users() {
|
||||
return users;
|
||||
public String[] usernames() {
|
||||
return usernames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(StreamInput in) throws IOException {
|
||||
super.readFrom(in);
|
||||
users = in.readStringArray();
|
||||
usernames = in.readStringArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
super.writeTo(out);
|
||||
out.writeStringArray(users);
|
||||
out.writeStringArray(usernames);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,8 +18,8 @@ public class GetUsersRequestBuilder extends ActionRequestBuilder<GetUsersRequest
|
|||
super(client, action, new GetUsersRequest());
|
||||
}
|
||||
|
||||
public GetUsersRequestBuilder users(String... usernames) {
|
||||
request.users(usernames);
|
||||
public GetUsersRequestBuilder usernames(String... usernames) {
|
||||
request.usernames(usernames);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,52 +11,53 @@ import org.elasticsearch.common.io.stream.StreamOutput;
|
|||
import org.elasticsearch.shield.User;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Response containing a User retrieved from the shield administrative index
|
||||
*/
|
||||
public class GetUsersResponse extends ActionResponse {
|
||||
private List<User> users;
|
||||
|
||||
public GetUsersResponse() {
|
||||
this.users = Collections.emptyList();
|
||||
}
|
||||
private User[] users;
|
||||
|
||||
public GetUsersResponse(User user) {
|
||||
this.users = Collections.singletonList(user);
|
||||
}
|
||||
|
||||
public GetUsersResponse(List<User> users) {
|
||||
public GetUsersResponse(User... users) {
|
||||
this.users = users;
|
||||
}
|
||||
|
||||
public List<User> users() {
|
||||
public GetUsersResponse(Collection<User> users) {
|
||||
this(users.toArray(new User[users.size()]));
|
||||
}
|
||||
|
||||
public User[] users() {
|
||||
return users;
|
||||
}
|
||||
|
||||
public boolean isExists() {
|
||||
return users != null && users.size() > 0;
|
||||
public boolean hasUsers() {
|
||||
return users != null && users.length > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(StreamInput in) throws IOException {
|
||||
super.readFrom(in);
|
||||
int size = in.readVInt();
|
||||
users = new ArrayList<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
users.add(User.readFrom(in));
|
||||
if (size < 0) {
|
||||
users = null;
|
||||
} else {
|
||||
users = new User[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
users[i] = User.readFrom(in);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
super.writeTo(out);
|
||||
out.writeVInt(users == null ? 0 : users.size());
|
||||
for (User u : users) {
|
||||
User.writeTo(u, out);
|
||||
out.writeVInt(users == null ? -1 : users.length);
|
||||
if (users != null) {
|
||||
for (User user : users) {
|
||||
User.writeTo(user, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,25 +9,24 @@ import org.elasticsearch.action.Action;
|
|||
import org.elasticsearch.client.ElasticsearchClient;
|
||||
|
||||
/**
|
||||
* Action for adding a user to the shield administrative index
|
||||
* Action for putting (adding/updating) a native user.
|
||||
*/
|
||||
public class AddUserAction extends Action<AddUserRequest, AddUserResponse, AddUserRequestBuilder> {
|
||||
public class PutUserAction extends Action<PutUserRequest, PutUserResponse, PutUserRequestBuilder> {
|
||||
|
||||
public static final AddUserAction INSTANCE = new AddUserAction();
|
||||
public static final String NAME = "cluster:admin/shield/user/add";
|
||||
public static final PutUserAction INSTANCE = new PutUserAction();
|
||||
public static final String NAME = "cluster:admin/shield/user/put";
|
||||
|
||||
|
||||
protected AddUserAction() {
|
||||
protected PutUserAction() {
|
||||
super(NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddUserRequestBuilder newRequestBuilder(ElasticsearchClient client) {
|
||||
return new AddUserRequestBuilder(client, this);
|
||||
public PutUserRequestBuilder newRequestBuilder(ElasticsearchClient client) {
|
||||
return new PutUserRequestBuilder(client, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddUserResponse newResponse() {
|
||||
return new AddUserResponse();
|
||||
public PutUserResponse newResponse() {
|
||||
return new PutUserResponse();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* 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.shield.action.user;
|
||||
|
||||
import org.elasticsearch.action.ActionRequest;
|
||||
import org.elasticsearch.action.ActionRequestValidationException;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.shield.authc.support.CharArrays;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.elasticsearch.action.ValidateActions.addValidationError;
|
||||
|
||||
/**
|
||||
* Request object to put a native user.
|
||||
*/
|
||||
public class PutUserRequest extends ActionRequest<PutUserRequest> {
|
||||
|
||||
private String username;
|
||||
private String[] roles;
|
||||
private String fullName;
|
||||
private String email;
|
||||
private Map<String, Object> metadata;
|
||||
private char[] passwordHash;
|
||||
|
||||
public PutUserRequest() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionRequestValidationException validate() {
|
||||
ActionRequestValidationException validationException = null;
|
||||
if (username == null) {
|
||||
validationException = addValidationError("user is missing", validationException);
|
||||
}
|
||||
if (roles == null) {
|
||||
validationException = addValidationError("roles are missing", validationException);
|
||||
}
|
||||
if (passwordHash == null) {
|
||||
validationException = addValidationError("passwordHash is missing", validationException);
|
||||
}
|
||||
return validationException;
|
||||
}
|
||||
|
||||
public void username(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public void roles(String... roles) {
|
||||
this.roles = roles;
|
||||
}
|
||||
|
||||
public void fullName(String fullName) {
|
||||
this.fullName = fullName;
|
||||
}
|
||||
|
||||
public void email(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public void metadata(Map<String, Object> metadata) {
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
public void passwordHash(char[] passwordHash) {
|
||||
this.passwordHash = passwordHash;
|
||||
}
|
||||
|
||||
public String username() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public String[] roles() {
|
||||
return roles;
|
||||
}
|
||||
|
||||
public String fullName() {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
public String email() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public Map<String, Object> metadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public char[] passwordHash() {
|
||||
return passwordHash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(StreamInput in) throws IOException {
|
||||
super.readFrom(in);
|
||||
username = in.readString();
|
||||
passwordHash = CharArrays.utf8BytesToChars(in.readByteArray());
|
||||
roles = in.readStringArray();
|
||||
fullName = in.readOptionalString();
|
||||
email = in.readOptionalString();
|
||||
metadata = in.readBoolean() ? in.readMap() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
super.writeTo(out);
|
||||
out.writeString(username);
|
||||
out.writeByteArray(CharArrays.toUtf8Bytes(passwordHash));
|
||||
out.writeStringArray(roles);
|
||||
out.writeOptionalString(fullName);
|
||||
out.writeOptionalString(email);
|
||||
if (metadata == null) {
|
||||
out.writeBoolean(false);
|
||||
} else {
|
||||
out.writeBoolean(true);
|
||||
out.writeMap(metadata);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* 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.shield.action.user;
|
||||
|
||||
import org.elasticsearch.ElasticsearchParseException;
|
||||
import org.elasticsearch.action.ActionRequestBuilder;
|
||||
import org.elasticsearch.client.ElasticsearchClient;
|
||||
import org.elasticsearch.common.ParseFieldMatcher;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.xcontent.XContentHelper;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.shield.User;
|
||||
import org.elasticsearch.shield.authc.support.Hasher;
|
||||
import org.elasticsearch.shield.authc.support.SecuredString;
|
||||
import org.elasticsearch.xpack.common.xcontent.XContentUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
public class PutUserRequestBuilder extends ActionRequestBuilder<PutUserRequest, PutUserResponse, PutUserRequestBuilder> {
|
||||
|
||||
private final Hasher hasher = Hasher.BCRYPT;
|
||||
|
||||
public PutUserRequestBuilder(ElasticsearchClient client) {
|
||||
this(client, PutUserAction.INSTANCE);
|
||||
}
|
||||
|
||||
public PutUserRequestBuilder(ElasticsearchClient client, PutUserAction action) {
|
||||
super(client, action, new PutUserRequest());
|
||||
}
|
||||
|
||||
public PutUserRequestBuilder username(String username) {
|
||||
request.username(username);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PutUserRequestBuilder roles(String... roles) {
|
||||
request.roles(roles);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PutUserRequestBuilder password(char[] password) {
|
||||
request.passwordHash(hasher.hash(new SecuredString(password)));
|
||||
return this;
|
||||
}
|
||||
|
||||
public PutUserRequestBuilder metadata(Map<String, Object> metadata) {
|
||||
request.metadata(metadata);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PutUserRequestBuilder fullName(String fullName) {
|
||||
request.fullName(fullName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PutUserRequestBuilder email(String email) {
|
||||
request.email(email);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PutUserRequestBuilder source(BytesReference source) throws IOException {
|
||||
try (XContentParser parser = XContentHelper.createParser(source)) {
|
||||
XContentUtils.verifyObject(parser);
|
||||
XContentParser.Token token;
|
||||
String currentFieldName = null;
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (token == XContentParser.Token.FIELD_NAME) {
|
||||
currentFieldName = parser.currentName();
|
||||
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, User.Fields.USERNAME)) {
|
||||
if (token == XContentParser.Token.VALUE_STRING) {
|
||||
username(parser.text());
|
||||
} else {
|
||||
throw new ElasticsearchParseException(
|
||||
"expected field [{}] to be of type string, but found [{}] instead", currentFieldName, token);
|
||||
}
|
||||
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, User.Fields.PASSWORD)) {
|
||||
if (token == XContentParser.Token.VALUE_STRING) {
|
||||
String password = parser.text();
|
||||
char[] passwordChars = password.toCharArray();
|
||||
password(passwordChars);
|
||||
password = null;
|
||||
Arrays.fill(passwordChars, (char) 0);
|
||||
} else {
|
||||
throw new ElasticsearchParseException(
|
||||
"expected field [{}] to be of type string, but found [{}] instead", currentFieldName, token);
|
||||
}
|
||||
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, User.Fields.ROLES)) {
|
||||
if (token == XContentParser.Token.VALUE_STRING) {
|
||||
roles(Strings.commaDelimitedListToStringArray(parser.text()));
|
||||
} else {
|
||||
roles(XContentUtils.readStringArray(parser, false));
|
||||
}
|
||||
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, User.Fields.FULL_NAME)) {
|
||||
if (token == XContentParser.Token.VALUE_STRING) {
|
||||
fullName(parser.text());
|
||||
} else {
|
||||
throw new ElasticsearchParseException(
|
||||
"expected field [{}] to be of type string, but found [{}] instead", currentFieldName, token);
|
||||
}
|
||||
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, User.Fields.EMAIL)) {
|
||||
if (token == XContentParser.Token.VALUE_STRING) {
|
||||
email(parser.text());
|
||||
} else {
|
||||
throw new ElasticsearchParseException(
|
||||
"expected field [{}] to be of type string, but found [{}] instead", currentFieldName, token);
|
||||
}
|
||||
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, User.Fields.METADATA)) {
|
||||
if (token == XContentParser.Token.START_OBJECT) {
|
||||
metadata(parser.map());
|
||||
} else {
|
||||
throw new ElasticsearchParseException(
|
||||
"expected field [{}] to be of type object, but found [{}] instead", currentFieldName, token);
|
||||
}
|
||||
} else {
|
||||
throw new ElasticsearchParseException("failed to parse add user request. unexpected field [{}]", currentFieldName);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -17,15 +17,14 @@ import java.io.IOException;
|
|||
* Response when adding a user to the shield administrative index. Returns a
|
||||
* single boolean field for whether the user was created or updated.
|
||||
*/
|
||||
public class AddUserResponse extends ActionResponse implements ToXContent {
|
||||
public class PutUserResponse extends ActionResponse implements ToXContent {
|
||||
|
||||
private boolean created;
|
||||
|
||||
public AddUserResponse() {
|
||||
|
||||
public PutUserResponse() {
|
||||
}
|
||||
|
||||
public AddUserResponse(boolean created) {
|
||||
public PutUserResponse(boolean created) {
|
||||
this.created = created;
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ import org.elasticsearch.shield.authc.esnative.ESNativeUsersStore;
|
|||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.TransportService;
|
||||
|
||||
public class TransportAddUserAction extends HandledTransportAction<AddUserRequest, AddUserResponse> {
|
||||
public class TransportAddUserAction extends HandledTransportAction<PutUserRequest, PutUserResponse> {
|
||||
|
||||
private final ESNativeUsersStore usersStore;
|
||||
|
||||
|
@ -23,13 +23,13 @@ public class TransportAddUserAction extends HandledTransportAction<AddUserReques
|
|||
public TransportAddUserAction(Settings settings, ThreadPool threadPool, ActionFilters actionFilters,
|
||||
IndexNameExpressionResolver indexNameExpressionResolver,
|
||||
ESNativeUsersStore usersStore, TransportService transportService) {
|
||||
super(settings, AddUserAction.NAME, threadPool, transportService, actionFilters, indexNameExpressionResolver, AddUserRequest::new);
|
||||
super(settings, PutUserAction.NAME, threadPool, transportService, actionFilters, indexNameExpressionResolver, PutUserRequest::new);
|
||||
this.usersStore = usersStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doExecute(final AddUserRequest request, final ActionListener<AddUserResponse> listener) {
|
||||
usersStore.addUser(request, new ActionListener<Boolean>() {
|
||||
protected void doExecute(final PutUserRequest request, final ActionListener<PutUserResponse> listener) {
|
||||
usersStore.putUser(request, new ActionListener<Boolean>() {
|
||||
@Override
|
||||
public void onResponse(Boolean created) {
|
||||
if (created) {
|
||||
|
@ -37,7 +37,7 @@ public class TransportAddUserAction extends HandledTransportAction<AddUserReques
|
|||
} else {
|
||||
logger.info("updated user [{}]", request.username());
|
||||
}
|
||||
listener.onResponse(new AddUserResponse(created));
|
||||
listener.onResponse(new PutUserResponse(created));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -31,7 +31,7 @@ public class TransportDeleteUserAction extends HandledTransportAction<DeleteUser
|
|||
@Override
|
||||
protected void doExecute(DeleteUserRequest request, final ActionListener<DeleteUserResponse> listener) {
|
||||
try {
|
||||
usersStore.removeUser(request, new ActionListener<Boolean>() {
|
||||
usersStore.deleteUser(request, new ActionListener<Boolean>() {
|
||||
@Override
|
||||
public void onResponse(Boolean found) {
|
||||
listener.onResponse(new DeleteUserResponse(found));
|
||||
|
|
|
@ -34,8 +34,8 @@ public class TransportGetUsersAction extends HandledTransportAction<GetUsersRequ
|
|||
|
||||
@Override
|
||||
protected void doExecute(final GetUsersRequest request, final ActionListener<GetUsersResponse> listener) {
|
||||
if (request.users().length == 1) {
|
||||
final String username = request.users()[0];
|
||||
if (request.usernames().length == 1) {
|
||||
final String username = request.usernames()[0];
|
||||
// We can fetch a single user with a get, much cheaper:
|
||||
usersStore.getUser(username, new ActionListener<User>() {
|
||||
@Override
|
||||
|
@ -54,7 +54,7 @@ public class TransportGetUsersAction extends HandledTransportAction<GetUsersRequ
|
|||
}
|
||||
});
|
||||
} else {
|
||||
usersStore.getUsers(request.users(), new ActionListener<List<User>>() {
|
||||
usersStore.getUsers(request.usernames(), new ActionListener<List<User>>() {
|
||||
@Override
|
||||
public void onResponse(List<User> users) {
|
||||
listener.onResponse(new GetUsersResponse(users));
|
||||
|
@ -63,7 +63,7 @@ public class TransportGetUsersAction extends HandledTransportAction<GetUsersRequ
|
|||
@Override
|
||||
public void onFailure(Throwable e) {
|
||||
logger.error("failed to retrieve user [{}]", e,
|
||||
Strings.arrayToDelimitedString(request.users(), ","));
|
||||
Strings.arrayToDelimitedString(request.usernames(), ","));
|
||||
listener.onFailure(e);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -49,9 +49,8 @@ import org.elasticsearch.shield.ShieldTemplateService;
|
|||
import org.elasticsearch.shield.User;
|
||||
import org.elasticsearch.shield.action.realm.ClearRealmCacheRequest;
|
||||
import org.elasticsearch.shield.action.realm.ClearRealmCacheResponse;
|
||||
import org.elasticsearch.shield.action.user.AddUserRequest;
|
||||
import org.elasticsearch.shield.action.user.DeleteUserRequest;
|
||||
import org.elasticsearch.shield.authc.AuthenticationService;
|
||||
import org.elasticsearch.shield.action.user.PutUserRequest;
|
||||
import org.elasticsearch.shield.authc.support.Hasher;
|
||||
import org.elasticsearch.shield.authc.support.SecuredString;
|
||||
import org.elasticsearch.shield.client.SecurityClient;
|
||||
|
@ -79,6 +78,15 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||
*/
|
||||
public class ESNativeUsersStore extends AbstractComponent implements ClusterStateListener {
|
||||
|
||||
public enum State {
|
||||
INITIALIZED,
|
||||
STARTING,
|
||||
STARTED,
|
||||
STOPPING,
|
||||
STOPPED,
|
||||
FAILED
|
||||
}
|
||||
|
||||
// TODO - perhaps separate indices for users/roles instead of types?
|
||||
public static final String INDEX_USER_TYPE = "user";
|
||||
|
||||
|
@ -93,36 +101,18 @@ public class ESNativeUsersStore extends AbstractComponent implements ClusterStat
|
|||
|
||||
private ScheduledFuture<?> versionChecker;
|
||||
private Client client;
|
||||
private AuthenticationService authService;
|
||||
private int scrollSize;
|
||||
private TimeValue scrollKeepAlive;
|
||||
|
||||
private volatile boolean shieldIndexExists = false;
|
||||
|
||||
@Inject
|
||||
public ESNativeUsersStore(Settings settings, Provider<InternalClient> clientProvider,
|
||||
Provider<AuthenticationService> authProvider, ThreadPool threadPool) {
|
||||
public ESNativeUsersStore(Settings settings, Provider<InternalClient> clientProvider, ThreadPool threadPool) {
|
||||
super(settings);
|
||||
this.clientProvider = clientProvider;
|
||||
this.threadPool = threadPool;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private UserAndPassword transformUser(Map<String, Object> sourceMap) {
|
||||
if (sourceMap == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
String username = (String) sourceMap.get(Fields.USERNAME);
|
||||
String password = (String) sourceMap.get(Fields.PASSWORD);
|
||||
String[] roles = ((List<String>) sourceMap.get(Fields.ROLES)).toArray(Strings.EMPTY_ARRAY);
|
||||
return new UserAndPassword(new User(username, roles), password.toCharArray());
|
||||
} catch (Exception e) {
|
||||
logger.error("error in the format of get response for user", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocking version of {@code getUser} that blocks until the User is returned
|
||||
*/
|
||||
|
@ -296,7 +286,7 @@ public class ESNativeUsersStore extends AbstractComponent implements ClusterStat
|
|||
}
|
||||
}
|
||||
|
||||
public void addUser(final AddUserRequest addUserRequest, final ActionListener<Boolean> listener) {
|
||||
public void putUser(final PutUserRequest putUserRequest, final ActionListener<Boolean> listener) {
|
||||
if (state() != State.STARTED) {
|
||||
listener.onFailure(new IllegalStateException("user cannot be added as native user service has not been started"));
|
||||
return;
|
||||
|
@ -304,10 +294,13 @@ public class ESNativeUsersStore extends AbstractComponent implements ClusterStat
|
|||
|
||||
try {
|
||||
IndexRequest request = client.prepareIndex(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME,
|
||||
INDEX_USER_TYPE, addUserRequest.username())
|
||||
.setSource("username", addUserRequest.username(),
|
||||
"password", String.valueOf(addUserRequest.passwordHash()),
|
||||
"roles", addUserRequest.roles())
|
||||
INDEX_USER_TYPE, putUserRequest.username())
|
||||
.setSource(User.Fields.USERNAME.getPreferredName(), putUserRequest.username(),
|
||||
User.Fields.PASSWORD.getPreferredName(), String.valueOf(putUserRequest.passwordHash()),
|
||||
User.Fields.ROLES.getPreferredName(), putUserRequest.roles(),
|
||||
User.Fields.FULL_NAME.getPreferredName(), putUserRequest.fullName(),
|
||||
User.Fields.EMAIL.getPreferredName(), putUserRequest.email(),
|
||||
User.Fields.METADATA.getPreferredName(), putUserRequest.metadata())
|
||||
.request();
|
||||
|
||||
client.index(request, new ActionListener<IndexResponse>() {
|
||||
|
@ -319,7 +312,7 @@ public class ESNativeUsersStore extends AbstractComponent implements ClusterStat
|
|||
return;
|
||||
}
|
||||
|
||||
clearRealmCache(addUserRequest.username(), listener, indexResponse.isCreated());
|
||||
clearRealmCache(putUserRequest.username(), listener, indexResponse.isCreated());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -333,7 +326,7 @@ public class ESNativeUsersStore extends AbstractComponent implements ClusterStat
|
|||
}
|
||||
}
|
||||
|
||||
public void removeUser(final DeleteUserRequest deleteUserRequest, final ActionListener<Boolean> listener) {
|
||||
public void deleteUser(final DeleteUserRequest deleteUserRequest, final ActionListener<Boolean> listener) {
|
||||
if (state() != State.STARTED) {
|
||||
listener.onFailure(new IllegalStateException("user cannot be deleted as native user service has not been started"));
|
||||
return;
|
||||
|
@ -341,12 +334,12 @@ public class ESNativeUsersStore extends AbstractComponent implements ClusterStat
|
|||
|
||||
try {
|
||||
DeleteRequest request = client.prepareDelete(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME,
|
||||
INDEX_USER_TYPE, deleteUserRequest.user()).request();
|
||||
INDEX_USER_TYPE, deleteUserRequest.username()).request();
|
||||
request.indicesOptions().ignoreUnavailable();
|
||||
client.delete(request, new ActionListener<DeleteResponse>() {
|
||||
@Override
|
||||
public void onResponse(DeleteResponse deleteResponse) {
|
||||
clearRealmCache(deleteUserRequest.user(), listener, deleteResponse.isFound());
|
||||
clearRealmCache(deleteUserRequest.username(), listener, deleteResponse.isFound());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -504,18 +497,27 @@ public class ESNativeUsersStore extends AbstractComponent implements ClusterStat
|
|||
this.versionMap.clear();
|
||||
this.listeners.clear();
|
||||
this.client = null;
|
||||
this.authService = null;
|
||||
this.shieldIndexExists = false;
|
||||
this.state.set(State.INITIALIZED);
|
||||
}
|
||||
|
||||
public enum State {
|
||||
INITIALIZED,
|
||||
STARTING,
|
||||
STARTED,
|
||||
STOPPING,
|
||||
STOPPED,
|
||||
FAILED
|
||||
@Nullable
|
||||
private UserAndPassword transformUser(Map<String, Object> sourceMap) {
|
||||
if (sourceMap == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
String username = (String) sourceMap.get(User.Fields.USERNAME.getPreferredName());
|
||||
String password = (String) sourceMap.get(User.Fields.PASSWORD.getPreferredName());
|
||||
String[] roles = ((List<String>) sourceMap.get(User.Fields.ROLES.getPreferredName())).toArray(Strings.EMPTY_ARRAY);
|
||||
String fullName = (String) sourceMap.get(User.Fields.FULL_NAME.getPreferredName());
|
||||
String email = (String) sourceMap.get(User.Fields.EMAIL.getPreferredName());
|
||||
Map<String, Object> metadata = (Map<String, Object>) sourceMap.get(User.Fields.METADATA.getPreferredName());
|
||||
return new UserAndPassword(new User(username, roles, fullName, email, metadata), password.toCharArray());
|
||||
} catch (Exception e) {
|
||||
logger.error("error in the format of get response for user", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class UserStorePoller extends AbstractRunnable {
|
||||
|
@ -649,12 +651,6 @@ public class ESNativeUsersStore extends AbstractComponent implements ClusterStat
|
|||
}
|
||||
}
|
||||
|
||||
public static class Fields {
|
||||
static String USERNAME = "username";
|
||||
static String PASSWORD = "password";
|
||||
static String ROLES = "roles";
|
||||
}
|
||||
|
||||
interface ChangeListener {
|
||||
|
||||
void onUsersChanged(List<String> username);
|
||||
|
|
|
@ -8,10 +8,19 @@ package org.elasticsearch.shield.client;
|
|||
import org.elasticsearch.action.ActionFuture;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.client.ElasticsearchClient;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.shield.action.realm.ClearRealmCacheAction;
|
||||
import org.elasticsearch.shield.action.realm.ClearRealmCacheRequest;
|
||||
import org.elasticsearch.shield.action.realm.ClearRealmCacheRequestBuilder;
|
||||
import org.elasticsearch.shield.action.realm.ClearRealmCacheResponse;
|
||||
import org.elasticsearch.shield.action.role.AddRoleAction;
|
||||
import org.elasticsearch.shield.action.role.AddRoleRequest;
|
||||
import org.elasticsearch.shield.action.role.AddRoleRequestBuilder;
|
||||
import org.elasticsearch.shield.action.role.AddRoleResponse;
|
||||
import org.elasticsearch.shield.action.role.ClearRolesCacheAction;
|
||||
import org.elasticsearch.shield.action.role.ClearRolesCacheRequest;
|
||||
import org.elasticsearch.shield.action.role.ClearRolesCacheRequestBuilder;
|
||||
import org.elasticsearch.shield.action.role.ClearRolesCacheResponse;
|
||||
import org.elasticsearch.shield.action.role.DeleteRoleAction;
|
||||
import org.elasticsearch.shield.action.role.DeleteRoleRequest;
|
||||
import org.elasticsearch.shield.action.role.DeleteRoleRequestBuilder;
|
||||
|
@ -20,10 +29,6 @@ import org.elasticsearch.shield.action.role.GetRolesAction;
|
|||
import org.elasticsearch.shield.action.role.GetRolesRequest;
|
||||
import org.elasticsearch.shield.action.role.GetRolesRequestBuilder;
|
||||
import org.elasticsearch.shield.action.role.GetRolesResponse;
|
||||
import org.elasticsearch.shield.action.user.AddUserAction;
|
||||
import org.elasticsearch.shield.action.user.AddUserRequest;
|
||||
import org.elasticsearch.shield.action.user.AddUserRequestBuilder;
|
||||
import org.elasticsearch.shield.action.user.AddUserResponse;
|
||||
import org.elasticsearch.shield.action.user.DeleteUserAction;
|
||||
import org.elasticsearch.shield.action.user.DeleteUserRequest;
|
||||
import org.elasticsearch.shield.action.user.DeleteUserRequestBuilder;
|
||||
|
@ -32,14 +37,12 @@ import org.elasticsearch.shield.action.user.GetUsersAction;
|
|||
import org.elasticsearch.shield.action.user.GetUsersRequest;
|
||||
import org.elasticsearch.shield.action.user.GetUsersRequestBuilder;
|
||||
import org.elasticsearch.shield.action.user.GetUsersResponse;
|
||||
import org.elasticsearch.shield.action.realm.ClearRealmCacheAction;
|
||||
import org.elasticsearch.shield.action.realm.ClearRealmCacheRequest;
|
||||
import org.elasticsearch.shield.action.realm.ClearRealmCacheRequestBuilder;
|
||||
import org.elasticsearch.shield.action.realm.ClearRealmCacheResponse;
|
||||
import org.elasticsearch.shield.action.role.ClearRolesCacheAction;
|
||||
import org.elasticsearch.shield.action.role.ClearRolesCacheRequest;
|
||||
import org.elasticsearch.shield.action.role.ClearRolesCacheRequestBuilder;
|
||||
import org.elasticsearch.shield.action.role.ClearRolesCacheResponse;
|
||||
import org.elasticsearch.shield.action.user.PutUserAction;
|
||||
import org.elasticsearch.shield.action.user.PutUserRequest;
|
||||
import org.elasticsearch.shield.action.user.PutUserRequestBuilder;
|
||||
import org.elasticsearch.shield.action.user.PutUserResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A wrapper to elasticsearch clients that exposes all Shield related APIs
|
||||
|
@ -114,9 +117,7 @@ public class SecurityClient {
|
|||
return client.execute(ClearRolesCacheAction.INSTANCE, request);
|
||||
}
|
||||
|
||||
/****************
|
||||
* admin things *
|
||||
****************/
|
||||
/** User Management */
|
||||
|
||||
public GetUsersRequestBuilder prepareGetUsers() {
|
||||
return new GetUsersRequestBuilder(client);
|
||||
|
@ -126,22 +127,28 @@ public class SecurityClient {
|
|||
client.execute(GetUsersAction.INSTANCE, request, listener);
|
||||
}
|
||||
|
||||
public DeleteUserRequestBuilder prepareDeleteUser() {
|
||||
return new DeleteUserRequestBuilder(client);
|
||||
public DeleteUserRequestBuilder prepareDeleteUser(String username) {
|
||||
return new DeleteUserRequestBuilder(client).username(username);
|
||||
}
|
||||
|
||||
public void deleteUser(DeleteUserRequest request, ActionListener<DeleteUserResponse> listener) {
|
||||
client.execute(DeleteUserAction.INSTANCE, request, listener);
|
||||
}
|
||||
|
||||
public AddUserRequestBuilder prepareAddUser() {
|
||||
return new AddUserRequestBuilder(client);
|
||||
public PutUserRequestBuilder preparePutUser(String username, BytesReference source) throws IOException {
|
||||
return new PutUserRequestBuilder(client).username(username).source(source);
|
||||
}
|
||||
|
||||
public void addUser(AddUserRequest request, ActionListener<AddUserResponse> listener) {
|
||||
client.execute(AddUserAction.INSTANCE, request, listener);
|
||||
public PutUserRequestBuilder preparePutUser(String username, char[] password, String... roles) {
|
||||
return new PutUserRequestBuilder(client).username(username).password(password).roles(roles);
|
||||
}
|
||||
|
||||
public void putUser(PutUserRequest request, ActionListener<PutUserResponse> listener) {
|
||||
client.execute(PutUserAction.INSTANCE, request, listener);
|
||||
}
|
||||
|
||||
/** Role Management */
|
||||
|
||||
public GetRolesRequestBuilder prepareGetRoles() {
|
||||
return new GetRolesRequestBuilder(client);
|
||||
}
|
||||
|
|
|
@ -34,10 +34,9 @@ public class RestDeleteUserAction extends BaseRestHandler {
|
|||
|
||||
@Override
|
||||
protected void handleRequest(RestRequest request, final RestChannel channel, Client client) throws Exception {
|
||||
String user = request.param("username");
|
||||
DeleteUserRequest delUserRequest = new DeleteUserRequest(user);
|
||||
String username = request.param("username");
|
||||
|
||||
new SecurityClient(client).deleteUser(delUserRequest, new RestBuilderListener<DeleteUserResponse>(channel) {
|
||||
new SecurityClient(client).prepareDeleteUser(username).execute(new RestBuilderListener<DeleteUserResponse>(channel) {
|
||||
@Override
|
||||
public RestResponse buildResponse(DeleteUserResponse response, XContentBuilder builder) throws Exception {
|
||||
return new BytesRestResponse(response.found() ? RestStatus.OK : RestStatus.NOT_FOUND,
|
||||
|
|
|
@ -37,13 +37,13 @@ public class RestGetUsersAction extends BaseRestHandler {
|
|||
|
||||
@Override
|
||||
protected void handleRequest(RestRequest request, final RestChannel channel, Client client) throws Exception {
|
||||
String[] users = Strings.splitStringByCommaToArray(request.param("username"));
|
||||
String[] usernames = request.paramAsStringArray("username", Strings.EMPTY_ARRAY);
|
||||
|
||||
new SecurityClient(client).prepareGetUsers().users(users).execute(new RestBuilderListener<GetUsersResponse>(channel) {
|
||||
new SecurityClient(client).prepareGetUsers().usernames(usernames).execute(new RestBuilderListener<GetUsersResponse>(channel) {
|
||||
@Override
|
||||
public RestResponse buildResponse(GetUsersResponse getUsersResponse, XContentBuilder builder) throws Exception {
|
||||
builder.startObject();
|
||||
builder.field("found", getUsersResponse.isExists());
|
||||
builder.field("found", getUsersResponse.hasUsers());
|
||||
builder.startArray("users");
|
||||
for (User user : getUsersResponse.users()) {
|
||||
user.toXContent(builder, ToXContent.EMPTY_PARAMS);
|
||||
|
|
|
@ -8,7 +8,6 @@ package org.elasticsearch.shield.rest.action.user;
|
|||
import org.elasticsearch.client.Client;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.rest.BaseRestHandler;
|
||||
import org.elasticsearch.rest.BytesRestResponse;
|
||||
|
@ -18,17 +17,16 @@ import org.elasticsearch.rest.RestRequest;
|
|||
import org.elasticsearch.rest.RestResponse;
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
import org.elasticsearch.rest.action.support.RestBuilderListener;
|
||||
import org.elasticsearch.shield.action.user.AddUserRequest;
|
||||
import org.elasticsearch.shield.action.user.AddUserResponse;
|
||||
import org.elasticsearch.shield.action.user.PutUserResponse;
|
||||
import org.elasticsearch.shield.client.SecurityClient;
|
||||
|
||||
/**
|
||||
* Rest endpoint to add a User to the shield index
|
||||
*/
|
||||
public class RestAddUserAction extends BaseRestHandler {
|
||||
public class RestPutUserAction extends BaseRestHandler {
|
||||
|
||||
@Inject
|
||||
public RestAddUserAction(Settings settings, RestController controller, Client client) {
|
||||
public RestPutUserAction(Settings settings, RestController controller, Client client) {
|
||||
super(settings, client);
|
||||
controller.registerHandler(RestRequest.Method.POST, "/_shield/user/{username}", this);
|
||||
controller.registerHandler(RestRequest.Method.PUT, "/_shield/user/{username}", this);
|
||||
|
@ -36,18 +34,15 @@ public class RestAddUserAction extends BaseRestHandler {
|
|||
|
||||
@Override
|
||||
protected void handleRequest(RestRequest request, final RestChannel channel, Client client) throws Exception {
|
||||
AddUserRequest addUserReq = new AddUserRequest();
|
||||
addUserReq.username(request.param("username"));
|
||||
addUserReq.source(request.content());
|
||||
|
||||
new SecurityClient(client).addUser(addUserReq, new RestBuilderListener<AddUserResponse>(channel) {
|
||||
@Override
|
||||
public RestResponse buildResponse(AddUserResponse addUserResponse, XContentBuilder builder) throws Exception {
|
||||
return new BytesRestResponse(RestStatus.OK,
|
||||
builder.startObject()
|
||||
.field("user", (ToXContent) addUserResponse)
|
||||
.endObject());
|
||||
}
|
||||
});
|
||||
new SecurityClient(client).preparePutUser(request.param("username"), request.content())
|
||||
.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());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,64 +1,101 @@
|
|||
{
|
||||
"template": ".shield",
|
||||
"order": 1000,
|
||||
"settings": {
|
||||
"number_of_shards": 1,
|
||||
"number_of_replicas": 0,
|
||||
"auto_expand_replicas": "0-all"
|
||||
"template" : ".shield",
|
||||
"order" : 1000,
|
||||
"settings" : {
|
||||
"number_of_shards" : 1,
|
||||
"number_of_replicas" : 0,
|
||||
"auto_expand_replicas" : "0-all",
|
||||
"analysis" : {
|
||||
"filter" : {
|
||||
"email" : {
|
||||
"type" : "pattern_capture",
|
||||
"preserve_original" : 1,
|
||||
"patterns" : [
|
||||
"([^@]+)",
|
||||
"(\\p{L}+)",
|
||||
"(\\d+)",
|
||||
"@(.+)"
|
||||
]
|
||||
}
|
||||
},
|
||||
"analyzer" : {
|
||||
"email" : {
|
||||
"tokenizer" : "uax_url_email",
|
||||
"filter" : [
|
||||
"email",
|
||||
"lowercase",
|
||||
"unique"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mappings": {
|
||||
"user": {
|
||||
"dynamic": "strict",
|
||||
"properties": {
|
||||
"username": {
|
||||
"type": "string",
|
||||
"index": "not_analyzed"
|
||||
"mappings" : {
|
||||
"user" : {
|
||||
"dynamic" : "strict",
|
||||
"properties" : {
|
||||
"username" : {
|
||||
"type" : "string",
|
||||
"index" : "not_analyzed"
|
||||
},
|
||||
"roles": {
|
||||
"type": "string",
|
||||
"index": "not_analyzed"
|
||||
"roles" : {
|
||||
"type" : "string",
|
||||
"index" : "not_analyzed"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"index": "no"
|
||||
"password" : {
|
||||
"type" : "string",
|
||||
"index" : "no"
|
||||
},
|
||||
"full_name" : {
|
||||
"type" : "string",
|
||||
"index" : "analyzed"
|
||||
},
|
||||
"email" : {
|
||||
"type" : "string",
|
||||
"index" : "analyzed",
|
||||
"analyzer" : "email"
|
||||
},
|
||||
"metadata" : {
|
||||
"type" : "object",
|
||||
"dynamic" : true
|
||||
}
|
||||
}
|
||||
},
|
||||
"role" : {
|
||||
"dynamic": "strict",
|
||||
"dynamic" : "strict",
|
||||
"properties" : {
|
||||
"cluster" : {
|
||||
"type" : "string",
|
||||
"index": "not_analyzed"
|
||||
"index" : "not_analyzed"
|
||||
},
|
||||
"indices" : {
|
||||
"type": "object",
|
||||
"type" : "object",
|
||||
"properties" : {
|
||||
"fields" : {
|
||||
"type" : "string",
|
||||
"index": "not_analyzed"
|
||||
"index" : "not_analyzed"
|
||||
},
|
||||
"names" : {
|
||||
"type" : "string",
|
||||
"index": "not_analyzed"
|
||||
"index" : "not_analyzed"
|
||||
},
|
||||
"privileges" : {
|
||||
"type" : "string",
|
||||
"index": "not_analyzed"
|
||||
"index" : "not_analyzed"
|
||||
},
|
||||
"query" : {
|
||||
"type" : "string",
|
||||
"index": "not_analyzed"
|
||||
"index" : "not_analyzed"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name" : {
|
||||
"type" : "string",
|
||||
"index": "not_analyzed"
|
||||
"index" : "not_analyzed"
|
||||
},
|
||||
"run_as" : {
|
||||
"type" : "string",
|
||||
"index": "not_analyzed"
|
||||
"index" : "not_analyzed"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,6 @@ public class ShieldF {
|
|||
settings.put("http.cors.allow-origin", "*");
|
||||
settings.put("script.inline", "true");
|
||||
settings.put("xpack.shield.enabled", "true");
|
||||
settings.put("security.manager.enabled", "false");
|
||||
// Disable Monitoring to prevent cluster activity
|
||||
settings.put("xpack.monitoring.enabled", "false");
|
||||
settings.put(IndexModule.INDEX_QUERY_CACHE_TYPE_SETTING.getKey(), Shield.OPT_OUT_QUERY_CACHE);
|
||||
|
|
|
@ -11,6 +11,7 @@ import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
|||
import org.elasticsearch.test.ESTestCase;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
@ -92,15 +93,17 @@ public class UserTests extends ESTestCase {
|
|||
generateRandomStringArray(16, 30, false), SystemUser.INSTANCE);
|
||||
fail("should not be able to create a runAs user with the system user");
|
||||
} catch (ElasticsearchSecurityException e) {
|
||||
assertThat(e.getMessage(), containsString("system"));
|
||||
assertThat(e.getMessage(), containsString("invalid run_as user"));
|
||||
}
|
||||
}
|
||||
|
||||
public void testUserToString() throws Exception {
|
||||
User sudo = new User("root", new String[]{"r3"});
|
||||
User u = new User("bob", new String[]{"r1", "r2"}, sudo);
|
||||
assertEquals("User[username=root,roles=[r3,]]", sudo.toString());
|
||||
assertEquals("User[username=bob,roles=[r1,r2,],runAs=[User[username=root,roles=[r3,]]]]",
|
||||
u.toString());
|
||||
User user = new User("u1", "r1");
|
||||
assertThat(user.toString(), is("User[username=u1,roles=[r1],fullName=null,email=null,metadata={}]"));
|
||||
user = new User("u1", new String[] { "r1", "r2" }, "user1", "user1@domain.com", Collections.singletonMap("key", "val"));
|
||||
assertThat(user.toString(), is("User[username=u1,roles=[r1,r2],fullName=user1,email=user1@domain.com,metadata={key=val}]"));
|
||||
user = new User("u1", new String[] {"r1", "r2"}, new User("u2", "r3"));
|
||||
assertThat(user.toString(), is("User[username=u1,roles=[r1,r2],fullName=null,email=null,metadata={},runAs=[User[username=u2," +
|
||||
"roles=[r3],fullName=null,email=null,metadata={}]]]"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ public class ESNativeTests extends ShieldIntegTestCase {
|
|||
|
||||
public void testDeletingNonexistingUserAndRole() throws Exception {
|
||||
SecurityClient c = securityClient();
|
||||
DeleteUserResponse resp = c.prepareDeleteUser().user("joe").get();
|
||||
DeleteUserResponse resp = c.prepareDeleteUser("joe").get();
|
||||
assertFalse("user shouldn't be found", resp.found());
|
||||
DeleteRoleResponse resp2 = c.prepareDeleteRole().role("role").get();
|
||||
assertFalse("role shouldn't be found", resp2.found());
|
||||
|
@ -52,8 +52,8 @@ public class ESNativeTests extends ShieldIntegTestCase {
|
|||
|
||||
public void testGettingUserThatDoesntExist() throws Exception {
|
||||
SecurityClient c = securityClient();
|
||||
GetUsersResponse resp = c.prepareGetUsers().users("joe").get();
|
||||
assertFalse("user should not exist", resp.isExists());
|
||||
GetUsersResponse resp = c.prepareGetUsers().usernames("joe").get();
|
||||
assertFalse("user should not exist", resp.hasUsers());
|
||||
GetRolesResponse resp2 = c.prepareGetRoles().roles("role").get();
|
||||
assertFalse("role should not exist", resp2.isExists());
|
||||
}
|
||||
|
@ -61,46 +61,34 @@ public class ESNativeTests extends ShieldIntegTestCase {
|
|||
public void testAddAndGetUser() throws Exception {
|
||||
SecurityClient c = securityClient();
|
||||
logger.error("--> creating user");
|
||||
c.prepareAddUser()
|
||||
.username("joe")
|
||||
.password("s3kirt")
|
||||
.roles("role1", "user")
|
||||
.get();
|
||||
c.preparePutUser("joe", "s3kirt".toCharArray(), "role1", "user").get();
|
||||
logger.error("--> waiting for .shield index");
|
||||
ensureGreen(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME);
|
||||
logger.info("--> retrieving user");
|
||||
GetUsersResponse resp = c.prepareGetUsers().users("joe").get();
|
||||
assertTrue("user should exist", resp.isExists());
|
||||
User joe = resp.users().get(0);
|
||||
GetUsersResponse resp = c.prepareGetUsers().usernames("joe").get();
|
||||
assertTrue("user should exist", resp.hasUsers());
|
||||
User joe = resp.users()[0];
|
||||
assertEquals(joe.principal(), "joe");
|
||||
assertArrayEquals(joe.roles(), new String[]{"role1", "user"});
|
||||
|
||||
logger.info("--> adding two more users");
|
||||
c.prepareAddUser()
|
||||
.username("joe2")
|
||||
.password("s3kirt2")
|
||||
.roles("role2", "user")
|
||||
.get();
|
||||
c.prepareAddUser()
|
||||
.username("joe3")
|
||||
.password("s3kirt3")
|
||||
.roles("role3", "user")
|
||||
.get();
|
||||
c.preparePutUser("joe2", "s3kirt2".toCharArray(), "role2", "user").get();
|
||||
c.preparePutUser("joe3", "s3kirt3".toCharArray(), "role3", "user").get();
|
||||
// Since getting multiple users relies on them being visible to search, perform a refresh
|
||||
refresh();
|
||||
GetUsersResponse allUsersResp = c.prepareGetUsers().get();
|
||||
assertTrue("users should exist", allUsersResp.isExists());
|
||||
assertEquals("should be 3 users total", 3, allUsersResp.users().size());
|
||||
assertTrue("users should exist", allUsersResp.hasUsers());
|
||||
assertEquals("should be 3 users total", 3, allUsersResp.users().length);
|
||||
List<String> names = new ArrayList<>(3);
|
||||
for (User u : allUsersResp.users()) {
|
||||
names.add(u.principal());
|
||||
}
|
||||
CollectionUtil.timSort(names);
|
||||
assertArrayEquals(new String[]{"joe", "joe2", "joe3"}, names.toArray(Strings.EMPTY_ARRAY));
|
||||
assertArrayEquals(new String[] { "joe", "joe2", "joe3" }, names.toArray(Strings.EMPTY_ARRAY));
|
||||
|
||||
GetUsersResponse someUsersResp = c.prepareGetUsers().users("joe", "joe3").get();
|
||||
assertTrue("users should exist", someUsersResp.isExists());
|
||||
assertEquals("should be 2 users returned", 2, someUsersResp.users().size());
|
||||
GetUsersResponse someUsersResp = c.prepareGetUsers().usernames("joe", "joe3").get();
|
||||
assertTrue("users should exist", someUsersResp.hasUsers());
|
||||
assertEquals("should be 2 users returned", 2, someUsersResp.users().length);
|
||||
names = new ArrayList<>(2);
|
||||
for (User u : someUsersResp.users()) {
|
||||
names.add(u.principal());
|
||||
|
@ -109,11 +97,11 @@ public class ESNativeTests extends ShieldIntegTestCase {
|
|||
assertArrayEquals(new String[]{"joe", "joe3"}, names.toArray(Strings.EMPTY_ARRAY));
|
||||
|
||||
logger.info("--> deleting user");
|
||||
DeleteUserResponse delResp = c.prepareDeleteUser().user("joe").get();
|
||||
DeleteUserResponse delResp = c.prepareDeleteUser("joe").get();
|
||||
assertTrue(delResp.found());
|
||||
logger.info("--> retrieving user");
|
||||
resp = c.prepareGetUsers().users("joe").get();
|
||||
assertFalse("user should not exist after being deleted", resp.isExists());
|
||||
resp = c.prepareGetUsers().usernames("joe").get();
|
||||
assertFalse("user should not exist after being deleted", resp.hasUsers());
|
||||
}
|
||||
|
||||
public void testAddAndGetRole() throws Exception {
|
||||
|
@ -180,17 +168,13 @@ public class ESNativeTests extends ShieldIntegTestCase {
|
|||
new String[]{"body", "title"}, new BytesArray("{\"match_all\": {}}"))
|
||||
.get();
|
||||
logger.error("--> creating user");
|
||||
c.prepareAddUser()
|
||||
.username("joe")
|
||||
.password("s3krit")
|
||||
.roles("test_role")
|
||||
.get();
|
||||
c.preparePutUser("joe", "s3krit".toCharArray(), "test_role").get();
|
||||
refresh();
|
||||
logger.error("--> waiting for .shield index");
|
||||
ensureGreen(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME);
|
||||
logger.info("--> retrieving user");
|
||||
GetUsersResponse resp = c.prepareGetUsers().users("joe").get();
|
||||
assertTrue("user should exist", resp.isExists());
|
||||
GetUsersResponse resp = c.prepareGetUsers().usernames("joe").get();
|
||||
assertTrue("user should exist", resp.hasUsers());
|
||||
|
||||
createIndex("idx");
|
||||
ensureGreen("idx");
|
||||
|
@ -206,18 +190,14 @@ public class ESNativeTests extends ShieldIntegTestCase {
|
|||
public void testUpdatingUserAndAuthentication() throws Exception {
|
||||
SecurityClient c = securityClient();
|
||||
logger.error("--> creating user");
|
||||
c.prepareAddUser()
|
||||
.username("joe")
|
||||
.password("s3krit")
|
||||
.roles(ShieldSettingsSource.DEFAULT_ROLE)
|
||||
.get();
|
||||
c.preparePutUser("joe", "s3krit".toCharArray(), ShieldSettingsSource.DEFAULT_ROLE).get();
|
||||
refresh();
|
||||
logger.error("--> waiting for .shield index");
|
||||
ensureGreen(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME);
|
||||
logger.info("--> retrieving user");
|
||||
GetUsersResponse resp = c.prepareGetUsers().users("joe").get();
|
||||
assertTrue("user should exist", resp.isExists());
|
||||
assertThat(resp.users().get(0).roles(), arrayContaining(ShieldSettingsSource.DEFAULT_ROLE));
|
||||
GetUsersResponse resp = c.prepareGetUsers().usernames("joe").get();
|
||||
assertTrue("user should exist", resp.hasUsers());
|
||||
assertThat(resp.users()[0].roles(), arrayContaining(ShieldSettingsSource.DEFAULT_ROLE));
|
||||
|
||||
createIndex("idx");
|
||||
ensureGreen("idx");
|
||||
|
@ -228,11 +208,7 @@ public class ESNativeTests extends ShieldIntegTestCase {
|
|||
|
||||
assertEquals(searchResp.getHits().getTotalHits(), 1L);
|
||||
|
||||
c.prepareAddUser()
|
||||
.username("joe")
|
||||
.password("s3krit2")
|
||||
.roles(ShieldSettingsSource.DEFAULT_ROLE)
|
||||
.get();
|
||||
c.preparePutUser("joe", "s3krit2".toCharArray(), ShieldSettingsSource.DEFAULT_ROLE).get();
|
||||
|
||||
try {
|
||||
client().filterWithHeader(Collections.singletonMap("Authorization", token)).prepareSearch("idx").get();
|
||||
|
@ -250,18 +226,14 @@ public class ESNativeTests extends ShieldIntegTestCase {
|
|||
public void testCreateDeleteAuthenticate() {
|
||||
SecurityClient c = securityClient();
|
||||
logger.error("--> creating user");
|
||||
c.prepareAddUser()
|
||||
.username("joe")
|
||||
.password("s3krit")
|
||||
.roles(ShieldSettingsSource.DEFAULT_ROLE)
|
||||
.get();
|
||||
c.preparePutUser("joe", "s3krit".toCharArray(), ShieldSettingsSource.DEFAULT_ROLE).get();
|
||||
refresh();
|
||||
logger.error("--> waiting for .shield index");
|
||||
ensureGreen(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME);
|
||||
logger.info("--> retrieving user");
|
||||
GetUsersResponse resp = c.prepareGetUsers().users("joe").get();
|
||||
assertTrue("user should exist", resp.isExists());
|
||||
assertThat(resp.users().get(0).roles(), arrayContaining(ShieldSettingsSource.DEFAULT_ROLE));
|
||||
GetUsersResponse resp = c.prepareGetUsers().usernames("joe").get();
|
||||
assertTrue("user should exist", resp.hasUsers());
|
||||
assertThat(resp.users()[0].roles(), arrayContaining(ShieldSettingsSource.DEFAULT_ROLE));
|
||||
|
||||
createIndex("idx");
|
||||
ensureGreen("idx");
|
||||
|
@ -272,7 +244,7 @@ public class ESNativeTests extends ShieldIntegTestCase {
|
|||
|
||||
assertEquals(searchResp.getHits().getTotalHits(), 1L);
|
||||
|
||||
DeleteUserResponse response = c.prepareDeleteUser().user("joe").get();
|
||||
DeleteUserResponse response = c.prepareDeleteUser("joe").get();
|
||||
assertThat(response.found(), is(true));
|
||||
try {
|
||||
client().filterWithHeader(Collections.singletonMap("Authorization", token)).prepareSearch("idx").get();
|
||||
|
@ -294,11 +266,7 @@ public class ESNativeTests extends ShieldIntegTestCase {
|
|||
new String[]{"body", "title"}, new BytesArray("{\"match_all\": {}}"))
|
||||
.get();
|
||||
logger.error("--> creating user");
|
||||
c.prepareAddUser()
|
||||
.username("joe")
|
||||
.password("s3krit")
|
||||
.roles("test_role")
|
||||
.get();
|
||||
c.preparePutUser("joe", "s3krit".toCharArray(), "test_role").get();
|
||||
refresh();
|
||||
logger.error("--> waiting for .shield index");
|
||||
ensureGreen(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME);
|
||||
|
@ -348,11 +316,7 @@ public class ESNativeTests extends ShieldIntegTestCase {
|
|||
.addIndices(new String[]{"*"}, new String[]{"read"},
|
||||
new String[]{"body", "title"}, new BytesArray("{\"match_all\": {}}"))
|
||||
.get();
|
||||
c.prepareAddUser()
|
||||
.username("joe")
|
||||
.password("s3krit")
|
||||
.roles("test_role")
|
||||
.get();
|
||||
c.preparePutUser("joe", "s3krit".toCharArray(), "test_role").get();
|
||||
refresh();
|
||||
logger.error("--> waiting for .shield index");
|
||||
ensureGreen(ShieldTemplateService.SHIELD_ADMIN_INDEX_NAME);
|
||||
|
|
|
@ -76,7 +76,7 @@ cluster:admin/plugin/license/delete
|
|||
cluster:admin/plugin/license/put
|
||||
cluster:admin/shield/realm/cache/clear
|
||||
cluster:admin/shield/roles/cache/clear
|
||||
cluster:admin/shield/user/add
|
||||
cluster:admin/shield/user/put
|
||||
cluster:admin/shield/user/delete
|
||||
cluster:admin/shield/user/get
|
||||
cluster:admin/shield/role/add
|
||||
|
|
|
@ -15,7 +15,7 @@ cluster:admin/shield/roles/cache/clear[n]
|
|||
cluster:admin/shield/role/add
|
||||
cluster:admin/shield/role/delete
|
||||
cluster:admin/shield/role/get
|
||||
cluster:admin/shield/user/add
|
||||
cluster:admin/shield/user/put
|
||||
cluster:admin/shield/user/delete
|
||||
cluster:admin/shield/user/get
|
||||
indices:admin/analyze[s]
|
||||
|
|
|
@ -12,9 +12,15 @@
|
|||
username: "joe"
|
||||
body: >
|
||||
{
|
||||
"username": "joe",
|
||||
"password": "s3krit",
|
||||
"roles" : [ "admin" ]
|
||||
"username" : "joe",
|
||||
"password" : "s3krit",
|
||||
"roles" : [ "admin" ],
|
||||
"full_name" : "Bazooka Joe",
|
||||
"email" : "joe@bazooka.gum",
|
||||
"metadata" : {
|
||||
"key1" : "val1",
|
||||
"key2" : "val2"
|
||||
}
|
||||
}
|
||||
- match: { user: { created: true } }
|
||||
|
||||
|
@ -30,3 +36,7 @@
|
|||
- match: { found: true }
|
||||
- match: { users.0.username: "joe" }
|
||||
- match: { users.0.roles.0: "admin" }
|
||||
- match: { users.0.full_name: "Bazooka Joe" }
|
||||
- match: { users.0.email: "joe@bazooka.gum" }
|
||||
- match: { users.0.metadata.key1: "val1" }
|
||||
- match: { users.0.metadata.key2: "val2" }
|
||||
|
|
|
@ -36,9 +36,15 @@
|
|||
username: "joe"
|
||||
body: >
|
||||
{
|
||||
"username": "joe",
|
||||
"password": "s3krit2",
|
||||
"roles" : [ "admin", "foo" ]
|
||||
"username" : "joe",
|
||||
"password" : "s3krit2",
|
||||
"roles" : [ "admin", "foo" ],
|
||||
"full_name" : "Bazooka Joe",
|
||||
"email" : "joe@bazooka.gum",
|
||||
"metadata" : {
|
||||
"key1" : "val1",
|
||||
"key2" : "val2"
|
||||
}
|
||||
}
|
||||
- match: { user: { created: false } }
|
||||
|
||||
|
@ -49,6 +55,10 @@
|
|||
- match: { users.0.username: "joe" }
|
||||
- match: { users.0.roles.0: "admin" }
|
||||
- match: { users.0.roles.1: "foo" }
|
||||
- match: { users.0.full_name: "Bazooka Joe" }
|
||||
- match: { users.0.email: "joe@bazooka.gum" }
|
||||
- match: { users.0.metadata.key1: "val1" }
|
||||
- match: { users.0.metadata.key2: "val2" }
|
||||
|
||||
- do:
|
||||
headers:
|
||||
|
|
|
@ -3,7 +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.watcher.support.xcontent;
|
||||
package org.elasticsearch.xpack.common.xcontent;
|
||||
|
||||
import org.elasticsearch.ElasticsearchParseException;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
|
@ -19,9 +19,24 @@ import java.util.List;
|
|||
/**
|
||||
*
|
||||
*/
|
||||
public class WatcherXContentUtils {
|
||||
public class XContentUtils {
|
||||
|
||||
private WatcherXContentUtils() {
|
||||
private XContentUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that we're currently on the start of an object, or that the next token is a start of an object.
|
||||
*
|
||||
* @throws ElasticsearchParseException if the current or the next token is a {@code START_OBJECT}
|
||||
*/
|
||||
public static void verifyObject(XContentParser parser) throws IOException, ElasticsearchParseException {
|
||||
if (parser.currentToken() == XContentParser.Token.START_OBJECT) {
|
||||
return;
|
||||
}
|
||||
XContentParser.Token token = parser.nextToken();
|
||||
if (token != XContentParser.Token.START_OBJECT) {
|
||||
throw new ElasticsearchParseException("expected a user object, but found token [{}]", parser.currentToken());
|
||||
}
|
||||
}
|
||||
|
||||
public static Tuple<XContentType, Object> convertToObject(BytesReference bytes) throws ElasticsearchParseException {
|
|
@ -11,7 +11,7 @@ import org.elasticsearch.common.ParseField;
|
|||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.watcher.condition.Condition;
|
||||
import org.elasticsearch.watcher.support.xcontent.WatcherXContentUtils;
|
||||
import org.elasticsearch.xpack.common.xcontent.XContentUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
@ -114,7 +114,7 @@ public class CompareCondition implements Condition {
|
|||
"operation [{}] must either be a numeric, string, boolean or null value, but found [{}] instead", TYPE,
|
||||
watchId, path, op.name().toLowerCase(Locale.ROOT), token);
|
||||
}
|
||||
value = WatcherXContentUtils.readValue(parser, token);
|
||||
value = XContentUtils.readValue(parser, token);
|
||||
token = parser.nextToken();
|
||||
if (token != XContentParser.Token.END_OBJECT) {
|
||||
throw new ElasticsearchParseException("could not parse [{}] condition for watch [{}]. expected end of path object, " +
|
||||
|
|
|
@ -13,7 +13,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
|
|||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.watcher.condition.Condition;
|
||||
import org.elasticsearch.watcher.condition.compare.LenientCompare;
|
||||
import org.elasticsearch.watcher.support.xcontent.WatcherXContentUtils;
|
||||
import org.elasticsearch.xpack.common.xcontent.XContentUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
@ -149,7 +149,7 @@ public class ArrayCompareCondition implements Condition {
|
|||
"boolean or null value, but found [{}] instead", TYPE, watchId, path,
|
||||
op.name().toLowerCase(Locale.ROOT), token);
|
||||
}
|
||||
value = WatcherXContentUtils.readValue(parser, token);
|
||||
value = XContentUtils.readValue(parser, token);
|
||||
haveValue = true;
|
||||
} else if (ParseFieldMatcher.STRICT.match(parser.currentName(), Field.QUANTIFIER)) {
|
||||
if (quantifier != null) {
|
||||
|
|
|
@ -15,6 +15,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
|
|||
import org.elasticsearch.common.xcontent.XContentHelper;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.xpack.common.xcontent.XContentUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
@ -117,7 +118,7 @@ public class XContentSource implements ToXContent {
|
|||
private Object data() {
|
||||
if (data == null) {
|
||||
try (XContentParser parser = parser()) {
|
||||
data = WatcherXContentUtils.readValue(parser, parser.nextToken());
|
||||
data = XContentUtils.readValue(parser, parser.nextToken());
|
||||
} catch (IOException ex) {
|
||||
throw new ElasticsearchException("failed to read value", ex);
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import org.elasticsearch.common.xcontent.XContentParser;
|
|||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.watcher.support.text.TextTemplate;
|
||||
import org.elasticsearch.watcher.support.xcontent.WatcherXContentUtils;
|
||||
import org.elasticsearch.xpack.common.xcontent.XContentUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
@ -68,9 +68,9 @@ public class HipChatMessageTests extends ESTestCase {
|
|||
} else if ("body".equals(currentFieldName)) {
|
||||
message = parser.text();
|
||||
} else if ("room".equals(currentFieldName)) {
|
||||
rooms = WatcherXContentUtils.readStringArray(parser, false);
|
||||
rooms = XContentUtils.readStringArray(parser, false);
|
||||
} else if ("user".equals(currentFieldName)) {
|
||||
users = WatcherXContentUtils.readStringArray(parser, false);
|
||||
users = XContentUtils.readStringArray(parser, false);
|
||||
} else if ("from".equals(currentFieldName)) {
|
||||
from = parser.text();
|
||||
} else if ("format".equals(currentFieldName)) {
|
||||
|
|
|
@ -10,6 +10,7 @@ import org.elasticsearch.common.settings.Settings;
|
|||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.test.junit.annotations.Network;
|
||||
import org.elasticsearch.watcher.support.http.auth.HttpAuthRegistry;
|
||||
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
|
@ -22,6 +23,7 @@ public class HttpConnectionTimeoutTests extends ESTestCase {
|
|||
// setting an unroutable IP to simulate a connection timeout
|
||||
private static final String UNROUTABLE_IP = "192.168.255.255";
|
||||
|
||||
@Network
|
||||
public void testDefaultTimeout() throws Exception {
|
||||
Environment environment = new Environment(Settings.builder().put("path.home", createTempDir()).build());
|
||||
HttpClient httpClient = new HttpClient(Settings.EMPTY, mock(HttpAuthRegistry.class), environment).start();
|
||||
|
@ -45,6 +47,7 @@ public class HttpConnectionTimeoutTests extends ESTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
@Network
|
||||
public void testDefaultTimeoutCustom() throws Exception {
|
||||
Environment environment = new Environment(Settings.builder().put("path.home", createTempDir()).build());
|
||||
HttpClient httpClient = new HttpClient(Settings.builder()
|
||||
|
@ -71,6 +74,7 @@ public class HttpConnectionTimeoutTests extends ESTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
@Network
|
||||
public void testTimeoutCustomPerRequest() throws Exception {
|
||||
Environment environment = new Environment(Settings.builder().put("path.home", createTempDir()).build());
|
||||
HttpClient httpClient = new HttpClient(Settings.builder()
|
||||
|
|
|
@ -5,12 +5,15 @@
|
|||
*/
|
||||
package org.elasticsearch.watcher.support.http;
|
||||
|
||||
import com.squareup.okhttp.mockwebserver.Dispatcher;
|
||||
import com.squareup.okhttp.mockwebserver.MockResponse;
|
||||
import com.squareup.okhttp.mockwebserver.MockWebServer;
|
||||
import com.squareup.okhttp.mockwebserver.RecordedRequest;
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.ElasticsearchTimeoutException;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.common.util.concurrent.CountDown;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.watcher.support.http.auth.HttpAuthRegistry;
|
||||
|
@ -18,6 +21,8 @@ import org.junit.After;
|
|||
import org.junit.Before;
|
||||
|
||||
import java.net.BindException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.hamcrest.Matchers.lessThan;
|
||||
|
@ -45,8 +50,9 @@ public class HttpReadTimeoutTests extends ESTestCase {
|
|||
}
|
||||
|
||||
@After
|
||||
public void after() throws Exception {
|
||||
public void cleanup() throws Exception {
|
||||
webServer.shutdown();
|
||||
|
||||
}
|
||||
|
||||
public void testDefaultTimeout() throws Exception {
|
||||
|
@ -83,11 +89,19 @@ public class HttpReadTimeoutTests extends ESTestCase {
|
|||
Environment environment = new Environment(Settings.builder().put("path.home", createTempDir()).build());
|
||||
|
||||
HttpClient httpClient = new HttpClient(Settings.builder()
|
||||
.put("watcher.http.default_read_timeout", "5s")
|
||||
.put("watcher.http.default_read_timeout", "3s")
|
||||
.build()
|
||||
, mock(HttpAuthRegistry.class), environment).start();
|
||||
|
||||
// we're not going to enqueue an response... so the server will just hang
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
webServer.setDispatcher(new Dispatcher() {
|
||||
@Override
|
||||
public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
|
||||
Thread.sleep(5000);
|
||||
latch.countDown();
|
||||
return new MockResponse().setStatus("200");
|
||||
}
|
||||
});
|
||||
|
||||
HttpRequest request = HttpRequest.builder("localhost", webPort)
|
||||
.method(HttpMethod.POST)
|
||||
|
@ -104,12 +118,14 @@ public class HttpReadTimeoutTests extends ESTestCase {
|
|||
TimeValue timeout = TimeValue.timeValueNanos(System.nanoTime() - start);
|
||||
logger.info("http connection timed out after {}", timeout.format());
|
||||
|
||||
// it's supposed to be 5, but we'll give it an error margin of 2 seconds
|
||||
assertThat(timeout.seconds(), greaterThan(3L));
|
||||
assertThat(timeout.seconds(), lessThan(7L));
|
||||
// it's supposed to be 3, but we'll give it an error margin of 2 seconds
|
||||
assertThat(timeout.seconds(), greaterThan(1L));
|
||||
assertThat(timeout.seconds(), lessThan(5L));
|
||||
}
|
||||
|
||||
// lets enqueue a response to relese the server.
|
||||
webServer.enqueue(new MockResponse());
|
||||
if (!latch.await(10, TimeUnit.SECONDS)) {
|
||||
// should never happen
|
||||
fail("waited too long for the response to be returned");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -121,7 +137,15 @@ public class HttpReadTimeoutTests extends ESTestCase {
|
|||
.build()
|
||||
, mock(HttpAuthRegistry.class), environment).start();
|
||||
|
||||
// we're not going to enqueue an response... so the server will just hang
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
webServer.setDispatcher(new Dispatcher() {
|
||||
@Override
|
||||
public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
|
||||
Thread.sleep(5000);
|
||||
latch.countDown();
|
||||
return new MockResponse().setStatus("200");
|
||||
}
|
||||
});
|
||||
|
||||
HttpRequest request = HttpRequest.builder("localhost", webPort)
|
||||
.readTimeout(TimeValue.timeValueSeconds(5))
|
||||
|
@ -142,9 +166,11 @@ public class HttpReadTimeoutTests extends ESTestCase {
|
|||
// it's supposed to be 5, but we'll give it an error margin of 2 seconds
|
||||
assertThat(timeout.seconds(), greaterThan(3L));
|
||||
assertThat(timeout.seconds(), lessThan(7L));
|
||||
}
|
||||
|
||||
// lets enqueue a response to relese the server.
|
||||
webServer.enqueue(new MockResponse());
|
||||
if (!latch.await(7, TimeUnit.SECONDS)) {
|
||||
// should never happen
|
||||
fail("waited too long for the response to be returned");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue