Cleanup Security Roles
- Renamed `AddRoleAction/Request/Response` to `PutRoleAction/Request/Response` - also renamed the user/roles rest actions - Changed the returned format for `RestGetRoleAction`. Previously this endpoint returned an array of role descriptor. Now it returns an object where the role names serve as the keys for the role objects. This is aligned with other APIs in ES (e.g. index templates). - When `RestGetRoleAction` cannot find all the requested roles, it'll return an empty object and a 404 response status - Also cleaned up `RoleDescriptor` Original commit: elastic/x-pack-elasticsearch@742f6e0020
This commit is contained in:
parent
3ddbd77090
commit
2a1b3250db
|
@ -19,41 +19,37 @@ import static org.elasticsearch.action.ValidateActions.addValidationError;
|
||||||
*/
|
*/
|
||||||
public class DeleteRoleRequest extends ActionRequest<DeleteRoleRequest> {
|
public class DeleteRoleRequest extends ActionRequest<DeleteRoleRequest> {
|
||||||
|
|
||||||
private String role;
|
private String name;
|
||||||
|
|
||||||
public DeleteRoleRequest() {
|
public DeleteRoleRequest() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public DeleteRoleRequest(String roleName) {
|
|
||||||
this.role = roleName;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ActionRequestValidationException validate() {
|
public ActionRequestValidationException validate() {
|
||||||
ActionRequestValidationException validationException = null;
|
ActionRequestValidationException validationException = null;
|
||||||
if (role == null) {
|
if (name == null) {
|
||||||
validationException = addValidationError("role is missing", validationException);
|
validationException = addValidationError("role name is missing", validationException);
|
||||||
}
|
}
|
||||||
return validationException;
|
return validationException;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void role(String role) {
|
public void name(String name) {
|
||||||
this.role = role;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String role() {
|
public String name() {
|
||||||
return role;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
public void readFrom(StreamInput in) throws IOException {
|
||||||
super.readFrom(in);
|
super.readFrom(in);
|
||||||
role = in.readString();
|
name = in.readString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
super.writeTo(out);
|
super.writeTo(out);
|
||||||
out.writeString(role);
|
out.writeString(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,8 @@ public class DeleteRoleRequestBuilder extends ActionRequestBuilder<DeleteRoleReq
|
||||||
super(client, action, new DeleteRoleRequest());
|
super(client, action, new DeleteRoleRequest());
|
||||||
}
|
}
|
||||||
|
|
||||||
public DeleteRoleRequestBuilder name(String roleName) {
|
public DeleteRoleRequestBuilder name(String name) {
|
||||||
request.role(roleName);
|
request.name(name);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,38 +20,37 @@ import static org.elasticsearch.action.ValidateActions.addValidationError;
|
||||||
*/
|
*/
|
||||||
public class GetRolesRequest extends ActionRequest<GetRolesRequest> {
|
public class GetRolesRequest extends ActionRequest<GetRolesRequest> {
|
||||||
|
|
||||||
private String[] roles;
|
private String[] names = Strings.EMPTY_ARRAY;
|
||||||
|
|
||||||
public GetRolesRequest() {
|
public GetRolesRequest() {
|
||||||
roles = Strings.EMPTY_ARRAY;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ActionRequestValidationException validate() {
|
public ActionRequestValidationException validate() {
|
||||||
ActionRequestValidationException validationException = null;
|
ActionRequestValidationException validationException = null;
|
||||||
if (roles == null) {
|
if (names == null) {
|
||||||
validationException = addValidationError("role is missing", validationException);
|
validationException = addValidationError("role is missing", validationException);
|
||||||
}
|
}
|
||||||
return validationException;
|
return validationException;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void roles(String... roles) {
|
public void names(String... names) {
|
||||||
this.roles = roles;
|
this.names = names;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String[] roles() {
|
public String[] names() {
|
||||||
return roles;
|
return names;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
public void readFrom(StreamInput in) throws IOException {
|
||||||
super.readFrom(in);
|
super.readFrom(in);
|
||||||
roles = in.readStringArray();
|
names = in.readStringArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
super.writeTo(out);
|
super.writeTo(out);
|
||||||
out.writeStringArray(roles);
|
out.writeStringArray(names);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,8 @@ public class GetRolesRequestBuilder extends ActionRequestBuilder<GetRolesRequest
|
||||||
super(client, action, new GetRolesRequest());
|
super(client, action, new GetRolesRequest());
|
||||||
}
|
}
|
||||||
|
|
||||||
public GetRolesRequestBuilder names(String... roles) {
|
public GetRolesRequestBuilder names(String... names) {
|
||||||
request.roles(roles);
|
request.names(names);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,51 +11,40 @@ import org.elasticsearch.common.io.stream.StreamOutput;
|
||||||
import org.elasticsearch.shield.authz.RoleDescriptor;
|
import org.elasticsearch.shield.authz.RoleDescriptor;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Response when retrieving a role from the shield index. Does not contain a
|
* A response for the {@code Get Roles} API that holds the retrieved role descriptors.
|
||||||
* real {@code Role} object, only a {@code RoleDescriptor}.
|
|
||||||
*/
|
*/
|
||||||
public class GetRolesResponse extends ActionResponse {
|
public class GetRolesResponse extends ActionResponse {
|
||||||
private List<RoleDescriptor> roles;
|
|
||||||
|
|
||||||
public GetRolesResponse() {
|
private RoleDescriptor[] roles;
|
||||||
roles = Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public GetRolesResponse(RoleDescriptor role) {
|
public GetRolesResponse(RoleDescriptor... roles) {
|
||||||
this.roles = Collections.singletonList(role);
|
|
||||||
}
|
|
||||||
|
|
||||||
public GetRolesResponse(List<RoleDescriptor> roles) {
|
|
||||||
this.roles = roles;
|
this.roles = roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<RoleDescriptor> roles() {
|
public RoleDescriptor[] roles() {
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isExists() {
|
public boolean hasRoles() {
|
||||||
return roles != null && roles.size() > 0;
|
return roles.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
public void readFrom(StreamInput in) throws IOException {
|
||||||
super.readFrom(in);
|
super.readFrom(in);
|
||||||
int size = in.readVInt();
|
int size = in.readVInt();
|
||||||
roles = new ArrayList<>(size);
|
roles = new RoleDescriptor[size];
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
roles.add(RoleDescriptor.readFrom(in));
|
roles[i] = RoleDescriptor.readFrom(in);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
super.writeTo(out);
|
super.writeTo(out);
|
||||||
out.writeVInt(roles.size());
|
out.writeVInt(roles.length);
|
||||||
for (RoleDescriptor role : roles) {
|
for (RoleDescriptor role : roles) {
|
||||||
RoleDescriptor.writeTo(role, out);
|
RoleDescriptor.writeTo(role, out);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,10 @@ package org.elasticsearch.shield.action.role;
|
||||||
import org.elasticsearch.action.ActionRequest;
|
import org.elasticsearch.action.ActionRequest;
|
||||||
import org.elasticsearch.action.ActionRequestValidationException;
|
import org.elasticsearch.action.ActionRequestValidationException;
|
||||||
import org.elasticsearch.common.Nullable;
|
import org.elasticsearch.common.Nullable;
|
||||||
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.bytes.BytesReference;
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
|
||||||
import org.elasticsearch.shield.authz.RoleDescriptor;
|
import org.elasticsearch.shield.authz.RoleDescriptor;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -25,13 +24,13 @@ import static org.elasticsearch.action.ValidateActions.addValidationError;
|
||||||
/**
|
/**
|
||||||
* Request object for adding a role to the shield index
|
* Request object for adding a role to the shield index
|
||||||
*/
|
*/
|
||||||
public class PutRoleRequest extends ActionRequest<PutRoleRequest> implements ToXContent {
|
public class PutRoleRequest extends ActionRequest<PutRoleRequest> {
|
||||||
|
|
||||||
private String name;
|
private String name;
|
||||||
private String[] clusterPrivileges;
|
private String[] clusterPrivileges = Strings.EMPTY_ARRAY;
|
||||||
private List<RoleDescriptor.IndicesPrivileges> indicesPrivileges = new ArrayList<>();
|
private List<RoleDescriptor.IndicesPrivileges> indicesPrivileges = new ArrayList<>();
|
||||||
private String[] runAs;
|
private String[] runAs = Strings.EMPTY_ARRAY;
|
||||||
|
|
||||||
public PutRoleRequest() {
|
public PutRoleRequest() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,14 +43,6 @@ public class PutRoleRequest extends ActionRequest<PutRoleRequest> implements ToX
|
||||||
return validationException;
|
return validationException;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void source(String name, BytesReference source) throws Exception {
|
|
||||||
RoleDescriptor descriptor = RoleDescriptor.source(name, source);
|
|
||||||
this.name = descriptor.getName();
|
|
||||||
this.clusterPrivileges = descriptor.getClusterPrivileges();
|
|
||||||
this.indicesPrivileges = Arrays.asList(descriptor.getIndicesPrivileges());
|
|
||||||
this.runAs = descriptor.getRunAs();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void name(String name) {
|
public void name(String name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
@ -60,6 +51,10 @@ public class PutRoleRequest extends ActionRequest<PutRoleRequest> implements ToX
|
||||||
this.clusterPrivileges = clusterPrivileges;
|
this.clusterPrivileges = clusterPrivileges;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void addIndex(RoleDescriptor.IndicesPrivileges... privileges) {
|
||||||
|
this.indicesPrivileges.addAll(Arrays.asList(privileges));
|
||||||
|
}
|
||||||
|
|
||||||
public void addIndex(String[] indices, String[] privileges, @Nullable String[] fields, @Nullable BytesReference query) {
|
public void addIndex(String[] indices, String[] privileges, @Nullable String[] fields, @Nullable BytesReference query) {
|
||||||
this.indicesPrivileges.add(RoleDescriptor.IndicesPrivileges.builder()
|
this.indicesPrivileges.add(RoleDescriptor.IndicesPrivileges.builder()
|
||||||
.indices(indices)
|
.indices(indices)
|
||||||
|
@ -89,38 +84,35 @@ public class PutRoleRequest extends ActionRequest<PutRoleRequest> implements ToX
|
||||||
return runAs;
|
return runAs;
|
||||||
}
|
}
|
||||||
|
|
||||||
private RoleDescriptor roleDescriptor() {
|
|
||||||
return new RoleDescriptor(name, clusterPrivileges,
|
|
||||||
indicesPrivileges.toArray(new RoleDescriptor.IndicesPrivileges[indicesPrivileges.size()]), runAs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
public void readFrom(StreamInput in) throws IOException {
|
||||||
super.readFrom(in);
|
super.readFrom(in);
|
||||||
name = in.readString();
|
name = in.readString();
|
||||||
clusterPrivileges = in.readOptionalStringArray();
|
clusterPrivileges = in.readStringArray();
|
||||||
int indicesSize = in.readVInt();
|
int indicesSize = in.readVInt();
|
||||||
indicesPrivileges = new ArrayList<>(indicesSize);
|
indicesPrivileges = new ArrayList<>(indicesSize);
|
||||||
for (int i = 0; i < indicesSize; i++) {
|
for (int i = 0; i < indicesSize; i++) {
|
||||||
indicesPrivileges.add(RoleDescriptor.IndicesPrivileges.readIndicesPrivileges(in));
|
indicesPrivileges.add(RoleDescriptor.IndicesPrivileges.createFrom(in));
|
||||||
}
|
}
|
||||||
runAs = in.readOptionalStringArray();
|
runAs = in.readStringArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
super.writeTo(out);
|
super.writeTo(out);
|
||||||
out.writeString(name);
|
out.writeString(name);
|
||||||
out.writeOptionalStringArray(clusterPrivileges);
|
out.writeStringArray(clusterPrivileges);
|
||||||
out.writeVInt(indicesPrivileges.size());
|
out.writeVInt(indicesPrivileges.size());
|
||||||
for (RoleDescriptor.IndicesPrivileges index : indicesPrivileges) {
|
for (RoleDescriptor.IndicesPrivileges index : indicesPrivileges) {
|
||||||
index.writeTo(out);
|
index.writeTo(out);
|
||||||
}
|
}
|
||||||
out.writeOptionalStringArray(runAs);
|
out.writeStringArray(runAs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
RoleDescriptor roleDescriptor() {
|
||||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
return new RoleDescriptor(name,
|
||||||
return this.roleDescriptor().toXContent(builder, params);
|
clusterPrivileges,
|
||||||
|
indicesPrivileges.toArray(new RoleDescriptor.IndicesPrivileges[indicesPrivileges.size()]),
|
||||||
|
runAs);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -9,6 +9,7 @@ import org.elasticsearch.action.ActionRequestBuilder;
|
||||||
import org.elasticsearch.client.ElasticsearchClient;
|
import org.elasticsearch.client.ElasticsearchClient;
|
||||||
import org.elasticsearch.common.Nullable;
|
import org.elasticsearch.common.Nullable;
|
||||||
import org.elasticsearch.common.bytes.BytesReference;
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
|
import org.elasticsearch.shield.authz.RoleDescriptor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builder for requests to add a role to the administrative index
|
* Builder for requests to add a role to the administrative index
|
||||||
|
@ -24,7 +25,11 @@ public class PutRoleRequestBuilder extends ActionRequestBuilder<PutRoleRequest,
|
||||||
}
|
}
|
||||||
|
|
||||||
public PutRoleRequestBuilder source(String name, BytesReference source) throws Exception {
|
public PutRoleRequestBuilder source(String name, BytesReference source) throws Exception {
|
||||||
request.source(name, source);
|
RoleDescriptor descriptor = RoleDescriptor.parse(name, source);
|
||||||
|
request.name(descriptor.getName());
|
||||||
|
request.cluster(descriptor.getClusterPrivileges());
|
||||||
|
request.addIndex(descriptor.getIndicesPrivileges());
|
||||||
|
request.runAs(descriptor.getRunAs());
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,8 +38,8 @@ public class PutRoleRequestBuilder extends ActionRequestBuilder<PutRoleRequest,
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PutRoleRequestBuilder cluster(String... clusterPrivileges) {
|
public PutRoleRequestBuilder cluster(String... cluster) {
|
||||||
request.cluster(clusterPrivileges);
|
request.cluster(cluster);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,8 +48,8 @@ public class PutRoleRequestBuilder extends ActionRequestBuilder<PutRoleRequest,
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PutRoleRequestBuilder addIndices(String[] indices, String[] privileges, @Nullable String[] fields,
|
public PutRoleRequestBuilder addIndices(String[] indices, String[] privileges,
|
||||||
@Nullable BytesReference query) {
|
@Nullable String[] fields, @Nullable BytesReference query) {
|
||||||
request.addIndex(indices, privileges, fields, query);
|
request.addIndex(indices, privileges, fields, query);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ public class TransportDeleteRoleAction extends HandledTransportAction<DeleteRole
|
||||||
@Override
|
@Override
|
||||||
protected void doExecute(DeleteRoleRequest request, ActionListener<DeleteRoleResponse> listener) {
|
protected void doExecute(DeleteRoleRequest request, ActionListener<DeleteRoleResponse> listener) {
|
||||||
try {
|
try {
|
||||||
rolesStore.removeRole(request, new ActionListener<Boolean>() {
|
rolesStore.deleteRole(request, new ActionListener<Boolean>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(Boolean found) {
|
public void onResponse(Boolean found) {
|
||||||
listener.onResponse(new DeleteRoleResponse(found));
|
listener.onResponse(new DeleteRoleResponse(found));
|
||||||
|
|
|
@ -33,9 +33,9 @@ public class TransportGetRolesAction extends HandledTransportAction<GetRolesRequ
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doExecute(GetRolesRequest request, ActionListener<GetRolesResponse> listener) {
|
protected void doExecute(final GetRolesRequest request, final ActionListener<GetRolesResponse> listener) {
|
||||||
if (request.roles().length == 1) {
|
if (request.names().length == 1) {
|
||||||
final String rolename = request.roles()[0];
|
final String rolename = request.names()[0];
|
||||||
// We can fetch a single role with a get, much easier
|
// We can fetch a single role with a get, much easier
|
||||||
rolesStore.getRoleDescriptor(rolename, new ActionListener<RoleDescriptor>() {
|
rolesStore.getRoleDescriptor(rolename, new ActionListener<RoleDescriptor>() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -54,16 +54,16 @@ public class TransportGetRolesAction extends HandledTransportAction<GetRolesRequ
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
rolesStore.getRoleDescriptors(request.roles(), new ActionListener<List<RoleDescriptor>>() {
|
rolesStore.getRoleDescriptors(request.names(), new ActionListener<List<RoleDescriptor>>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(List<RoleDescriptor> roles) {
|
public void onResponse(List<RoleDescriptor> roles) {
|
||||||
listener.onResponse(new GetRolesResponse(roles));
|
listener.onResponse(new GetRolesResponse(roles.toArray(new RoleDescriptor[roles.size()])));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(Throwable t) {
|
public void onFailure(Throwable t) {
|
||||||
logger.error("failed to retrieve role [{}]", t,
|
logger.error("failed to retrieve role [{}]", t,
|
||||||
Strings.arrayToDelimitedString(request.roles(), ","));
|
Strings.arrayToDelimitedString(request.names(), ","));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,8 +28,8 @@ public class TransportPutRoleAction extends HandledTransportAction<PutRoleReques
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doExecute(PutRoleRequest request, ActionListener<PutRoleResponse> listener) {
|
protected void doExecute(final PutRoleRequest request, final ActionListener<PutRoleResponse> listener) {
|
||||||
rolesStore.addRole(request, new ActionListener<Boolean>() {
|
rolesStore.putRole(request.roleDescriptor(), new ActionListener<Boolean>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(Boolean created) {
|
public void onResponse(Boolean created) {
|
||||||
if (created) {
|
if (created) {
|
||||||
|
|
|
@ -7,6 +7,8 @@ package org.elasticsearch.shield.authz;
|
||||||
|
|
||||||
import org.elasticsearch.ElasticsearchParseException;
|
import org.elasticsearch.ElasticsearchParseException;
|
||||||
import org.elasticsearch.common.Nullable;
|
import org.elasticsearch.common.Nullable;
|
||||||
|
import org.elasticsearch.common.ParseField;
|
||||||
|
import org.elasticsearch.common.ParseFieldMatcher;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.bytes.BytesArray;
|
import org.elasticsearch.common.bytes.BytesArray;
|
||||||
import org.elasticsearch.common.bytes.BytesReference;
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
|
@ -17,9 +19,11 @@ import org.elasticsearch.common.xcontent.ToXContent;
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
import org.elasticsearch.common.xcontent.XContentHelper;
|
import org.elasticsearch.common.xcontent.XContentHelper;
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
|
import org.elasticsearch.xpack.common.xcontent.XContentUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,12 +37,15 @@ public class RoleDescriptor implements ToXContent {
|
||||||
private final IndicesPrivileges[] indicesPrivileges;
|
private final IndicesPrivileges[] indicesPrivileges;
|
||||||
private final String[] runAs;
|
private final String[] runAs;
|
||||||
|
|
||||||
public RoleDescriptor(String name, String[] clusterPrivileges,
|
public RoleDescriptor(String name,
|
||||||
IndicesPrivileges[] indicesPrivileges, String[] runAs) {
|
@Nullable String[] clusterPrivileges,
|
||||||
|
@Nullable IndicesPrivileges[] indicesPrivileges,
|
||||||
|
@Nullable String[] runAs) {
|
||||||
|
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.clusterPrivileges = clusterPrivileges;
|
this.clusterPrivileges = clusterPrivileges != null ? clusterPrivileges : Strings.EMPTY_ARRAY;
|
||||||
this.indicesPrivileges = indicesPrivileges;
|
this.indicesPrivileges = indicesPrivileges != null ? indicesPrivileges : IndicesPrivileges.NONE;
|
||||||
this.runAs = runAs;
|
this.runAs = runAs != null ? runAs : Strings.EMPTY_ARRAY;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
|
@ -57,156 +64,6 @@ public class RoleDescriptor implements ToXContent {
|
||||||
return this.runAs;
|
return this.runAs;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void validateIndexName(String idxName) throws ElasticsearchParseException {
|
|
||||||
if (idxName == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (idxName.indexOf(",") != -1) {
|
|
||||||
throw new ElasticsearchParseException("index name [" + idxName + "] may not contain ','");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static RoleDescriptor.IndicesPrivileges parseIndex(XContentParser parser) throws Exception {
|
|
||||||
XContentParser.Token token;
|
|
||||||
String currentFieldName = null;
|
|
||||||
String[] idxNames = null;
|
|
||||||
String query = null;
|
|
||||||
List<String> privs = new ArrayList<>();
|
|
||||||
List<String> fields = new ArrayList<>();
|
|
||||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
|
||||||
if (token == XContentParser.Token.FIELD_NAME) {
|
|
||||||
currentFieldName = parser.currentName();
|
|
||||||
} else if (token.isValue()) {
|
|
||||||
if ("names".equals(currentFieldName)) {
|
|
||||||
String idxName = parser.text();
|
|
||||||
validateIndexName(idxName);
|
|
||||||
idxNames = new String[]{idxName};
|
|
||||||
} else if ("query".equals(currentFieldName)) {
|
|
||||||
query = parser.text();
|
|
||||||
} else {
|
|
||||||
throw new ElasticsearchParseException("unexpected field in add role request [{}]", currentFieldName);
|
|
||||||
}
|
|
||||||
} else if (token == XContentParser.Token.START_OBJECT) {
|
|
||||||
// expected
|
|
||||||
} else if (token == XContentParser.Token.START_ARRAY && "names".equals(currentFieldName)) {
|
|
||||||
List<String> idxNameList = new ArrayList<>();
|
|
||||||
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
|
|
||||||
if (token.isValue()) {
|
|
||||||
String idxName = parser.text();
|
|
||||||
validateIndexName(idxName);
|
|
||||||
idxNameList.add(idxName);
|
|
||||||
} else {
|
|
||||||
throw new ElasticsearchParseException("unexpected object while parsing index names [{}]", token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
idxNames = idxNameList.toArray(Strings.EMPTY_ARRAY);
|
|
||||||
} else if (token == XContentParser.Token.START_ARRAY && "privileges".equals(currentFieldName)) {
|
|
||||||
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
|
|
||||||
if (token.isValue()) {
|
|
||||||
privs.add(parser.text());
|
|
||||||
} else {
|
|
||||||
throw new ElasticsearchParseException("unexpected object while parsing index privileges [{}]", token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (token == XContentParser.Token.START_ARRAY && "fields".equals(currentFieldName)) {
|
|
||||||
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
|
|
||||||
if (token.isValue()) {
|
|
||||||
fields.add(parser.text());
|
|
||||||
} else {
|
|
||||||
throw new ElasticsearchParseException("unexpected object while parsing index fields [{}]", token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new ElasticsearchParseException("failed to parse add role request indices, got value with wrong type [{}]",
|
|
||||||
currentFieldName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (idxNames == null || idxNames.length == 0) {
|
|
||||||
throw new ElasticsearchParseException("'name' is a required field for index permissions");
|
|
||||||
}
|
|
||||||
if (privs.isEmpty()) {
|
|
||||||
throw new ElasticsearchParseException("'privileges' is a required field for index permissions");
|
|
||||||
}
|
|
||||||
return RoleDescriptor.IndicesPrivileges.builder()
|
|
||||||
.indices(idxNames)
|
|
||||||
.privileges(privs.toArray(Strings.EMPTY_ARRAY))
|
|
||||||
.fields(fields.isEmpty() ? null : fields.toArray(Strings.EMPTY_ARRAY))
|
|
||||||
.query(query == null ? null : new BytesArray(query))
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<RoleDescriptor.IndicesPrivileges> parseIndices(XContentParser parser) throws Exception {
|
|
||||||
XContentParser.Token token;
|
|
||||||
List<RoleDescriptor.IndicesPrivileges> tempIndices = new ArrayList<>();
|
|
||||||
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
|
|
||||||
if (token == XContentParser.Token.START_OBJECT) {
|
|
||||||
tempIndices.add(parseIndex(parser));
|
|
||||||
} else {
|
|
||||||
throw new ElasticsearchParseException("unexpected type parsing index sub object [{}]", token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tempIndices;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RoleDescriptor source(String name, BytesReference source) throws Exception {
|
|
||||||
try (XContentParser parser = XContentHelper.createParser(source)) {
|
|
||||||
XContentParser.Token token;
|
|
||||||
String currentFieldName = null;
|
|
||||||
String roleName = name;
|
|
||||||
List<IndicesPrivileges> indicesPrivileges = new ArrayList<>();
|
|
||||||
List<String> runAsUsers = new ArrayList<>();
|
|
||||||
List<String> clusterPrivileges = new ArrayList<>();
|
|
||||||
parser.nextToken(); // remove object wrapping
|
|
||||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
|
||||||
if (token == XContentParser.Token.FIELD_NAME) {
|
|
||||||
currentFieldName = parser.currentName();
|
|
||||||
} else if (token.isValue()) {
|
|
||||||
if ("name".equals(currentFieldName)) {
|
|
||||||
if (roleName == null) {
|
|
||||||
// if the role name is not given, we'll parse it from the source
|
|
||||||
roleName = parser.text();
|
|
||||||
} else if (roleName.equals(parser.text()) == false) {
|
|
||||||
// if the given role name is not the same as the parsed role name, we have inconstency and we need to
|
|
||||||
// throw an error
|
|
||||||
throw new ElasticsearchParseException("expected role name [{}] but found [{}] instead",
|
|
||||||
roleName, parser.text());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new ElasticsearchParseException("unexpected field in add role request [{}]", currentFieldName);
|
|
||||||
}
|
|
||||||
} else if (token == XContentParser.Token.START_ARRAY && "indices".equals(currentFieldName)) {
|
|
||||||
indicesPrivileges = parseIndices(parser);
|
|
||||||
} else if (token == XContentParser.Token.START_ARRAY && "run_as".equals(currentFieldName)) {
|
|
||||||
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
|
|
||||||
if (token.isValue()) {
|
|
||||||
runAsUsers.add(parser.text());
|
|
||||||
} else {
|
|
||||||
throw new ElasticsearchParseException("unexpected value parsing run_as users [{}]", token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (token == XContentParser.Token.START_ARRAY && "cluster".equals(currentFieldName)) {
|
|
||||||
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
|
|
||||||
if (token.isValue()) {
|
|
||||||
clusterPrivileges.add(parser.text());
|
|
||||||
} else {
|
|
||||||
throw new ElasticsearchParseException("unexpected value parsing cluster privileges [{}]", token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new ElasticsearchParseException("failed to parse add role request, got value with wrong type [{}]",
|
|
||||||
currentFieldName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (roleName == null) {
|
|
||||||
throw new ElasticsearchParseException("field [name] required for role description");
|
|
||||||
}
|
|
||||||
return new RoleDescriptor(roleName, clusterPrivileges.toArray(new String[clusterPrivileges.size()]),
|
|
||||||
indicesPrivileges.toArray(new IndicesPrivileges[indicesPrivileges.size()]),
|
|
||||||
runAsUsers.toArray(new String[runAsUsers.size()]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder sb = new StringBuilder("Role[");
|
StringBuilder sb = new StringBuilder("Role[");
|
||||||
|
@ -222,30 +79,47 @@ public class RoleDescriptor implements ToXContent {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
RoleDescriptor that = (RoleDescriptor) o;
|
||||||
|
|
||||||
|
if (!name.equals(that.name)) return false;
|
||||||
|
if (!Arrays.equals(clusterPrivileges, that.clusterPrivileges)) return false;
|
||||||
|
if (!Arrays.equals(indicesPrivileges, that.indicesPrivileges)) return false;
|
||||||
|
return Arrays.equals(runAs, that.runAs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = name.hashCode();
|
||||||
|
result = 31 * result + Arrays.hashCode(clusterPrivileges);
|
||||||
|
result = 31 * result + Arrays.hashCode(indicesPrivileges);
|
||||||
|
result = 31 * result + Arrays.hashCode(runAs);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||||
builder.startObject();
|
builder.startObject();
|
||||||
builder.field("name", name);
|
builder.field("cluster", (Object[]) clusterPrivileges);
|
||||||
builder.field("cluster", clusterPrivileges);
|
|
||||||
builder.field("indices", (Object[]) indicesPrivileges);
|
builder.field("indices", (Object[]) indicesPrivileges);
|
||||||
if (runAs != null) {
|
if (runAs != null) {
|
||||||
builder.field("run_as", runAs);
|
builder.field("run_as", runAs);
|
||||||
}
|
}
|
||||||
builder.endObject();
|
return builder.endObject();
|
||||||
return builder;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RoleDescriptor readFrom(StreamInput in) throws IOException {
|
public static RoleDescriptor readFrom(StreamInput in) throws IOException {
|
||||||
String name = in.readString();
|
String name = in.readString();
|
||||||
String[] clusterPattern = in.readStringArray();
|
String[] clusterPrivileges = in.readStringArray();
|
||||||
int size = in.readVInt();
|
int size = in.readVInt();
|
||||||
List<IndicesPrivileges> indicesPrivileges = new ArrayList<>(size);
|
IndicesPrivileges[] indicesPrivileges = new IndicesPrivileges[size];
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
IndicesPrivileges group = new IndicesPrivileges();
|
indicesPrivileges[i] = IndicesPrivileges.createFrom(in);
|
||||||
group.readFrom(in);
|
|
||||||
indicesPrivileges.add(group);
|
|
||||||
}
|
}
|
||||||
String[] runAs = in.readStringArray();
|
String[] runAs = in.readStringArray();
|
||||||
return new RoleDescriptor(name, clusterPattern, indicesPrivileges.toArray(new IndicesPrivileges[indicesPrivileges.size()]), runAs);
|
return new RoleDescriptor(name, clusterPrivileges, indicesPrivileges, runAs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void writeTo(RoleDescriptor descriptor, StreamOutput out) throws IOException {
|
public static void writeTo(RoleDescriptor descriptor, StreamOutput out) throws IOException {
|
||||||
|
@ -258,38 +132,103 @@ public class RoleDescriptor implements ToXContent {
|
||||||
out.writeStringArray(descriptor.runAs);
|
out.writeStringArray(descriptor.runAs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class IndicesPrivilegesBuilder {
|
public static RoleDescriptor parse(String name, BytesReference source) throws Exception {
|
||||||
private String[] privileges;
|
assert name != null;
|
||||||
private String[] indices;
|
try (XContentParser parser = XContentHelper.createParser(source)) {
|
||||||
private String[] fields;
|
XContentParser.Token token = parser.nextToken(); // advancing to the START_OBJECT token
|
||||||
private BytesReference query;
|
if (token != XContentParser.Token.START_OBJECT) {
|
||||||
|
throw new ElasticsearchParseException("failed to parse role [{}]. expected an object but found [{}] instead", name, token);
|
||||||
IndicesPrivilegesBuilder() {
|
}
|
||||||
|
String currentFieldName = null;
|
||||||
|
IndicesPrivileges[] indicesPrivileges = null;
|
||||||
|
String[] clusterPrivileges = null;
|
||||||
|
String[] runAsUsers = null;
|
||||||
|
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||||
|
if (token == XContentParser.Token.FIELD_NAME) {
|
||||||
|
currentFieldName = parser.currentName();
|
||||||
|
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, Fields.INDICES)) {
|
||||||
|
indicesPrivileges = parseIndices(name, parser);
|
||||||
|
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, Fields.RUN_AS)) {
|
||||||
|
runAsUsers = XContentUtils.readStringArray(parser, true);
|
||||||
|
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, Fields.CLUSTER)) {
|
||||||
|
clusterPrivileges = XContentUtils.readStringArray(parser, true);
|
||||||
|
} else {
|
||||||
|
throw new ElasticsearchParseException("failed to parse role [{}]. unexpected field [{}]", name, currentFieldName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new RoleDescriptor(name, clusterPrivileges, indicesPrivileges, runAsUsers);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public IndicesPrivilegesBuilder indices(String[] indices) {
|
private static RoleDescriptor.IndicesPrivileges[] parseIndices(String roleName, XContentParser parser) throws Exception {
|
||||||
this.indices = indices;
|
if (parser.currentToken() != XContentParser.Token.START_ARRAY) {
|
||||||
return this;
|
throw new ElasticsearchParseException("failed to parse indices privileges for role [{}]. expected field [{}] value " +
|
||||||
|
"to be an array, but found [{}] instead", roleName, parser.currentName(), parser.currentToken());
|
||||||
}
|
}
|
||||||
|
List<RoleDescriptor.IndicesPrivileges> privileges = new ArrayList<>();
|
||||||
|
XContentParser.Token token;
|
||||||
|
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
|
||||||
|
privileges.add(parseIndex(roleName, parser));
|
||||||
|
}
|
||||||
|
return privileges.toArray(new IndicesPrivileges[privileges.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
public IndicesPrivilegesBuilder privileges(String[] privileges) {
|
private static RoleDescriptor.IndicesPrivileges parseIndex(String roleName, XContentParser parser) throws Exception {
|
||||||
this.privileges = privileges;
|
XContentParser.Token token = parser.currentToken();
|
||||||
return this;
|
if (token != XContentParser.Token.START_OBJECT) {
|
||||||
|
throw new ElasticsearchParseException("failed to parse indices privileges for role [{}]. expected field [{}] value to " +
|
||||||
|
"be an array of objects, but found an array element of type [{}]", roleName, parser.currentName(), token);
|
||||||
}
|
}
|
||||||
|
String currentFieldName = null;
|
||||||
public IndicesPrivilegesBuilder fields(@Nullable String[] fields) {
|
String[] names = null;
|
||||||
this.fields = fields;
|
String query = null;
|
||||||
return this;
|
String[] privileges = null;
|
||||||
|
String[] fields = null;
|
||||||
|
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||||
|
if (token == XContentParser.Token.FIELD_NAME) {
|
||||||
|
currentFieldName = parser.currentName();
|
||||||
|
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, Fields.NAMES)) {
|
||||||
|
if (token == XContentParser.Token.VALUE_STRING) {
|
||||||
|
names = new String[] { parser.text() };
|
||||||
|
} else if (token == XContentParser.Token.START_ARRAY) {
|
||||||
|
names = XContentUtils.readStringArray(parser, false);
|
||||||
|
if (names.length == 0) {
|
||||||
|
throw new ElasticsearchParseException("failed to parse indices privileges for role [{}]. [{}] cannot be an empty " +
|
||||||
|
"array", roleName, currentFieldName);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new ElasticsearchParseException("failed to parse indices privileges for role [{}]. expected field [{}] " +
|
||||||
|
"value to be a string or an array of strings, but found [{}] instead", roleName, currentFieldName, token);
|
||||||
|
}
|
||||||
|
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, Fields.QUERY)) {
|
||||||
|
query = parser.textOrNull();
|
||||||
|
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, Fields.PRIVILEGES)) {
|
||||||
|
privileges = XContentUtils.readStringArray(parser, false);
|
||||||
|
if (names.length == 0) {
|
||||||
|
throw new ElasticsearchParseException("failed to parse indices privileges for role [{}]. [{}] cannot be an empty " +
|
||||||
|
"array", roleName, currentFieldName);
|
||||||
|
}
|
||||||
|
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, Fields.FIELDS)) {
|
||||||
|
fields = XContentUtils.readStringArray(parser, true);
|
||||||
|
} else {
|
||||||
|
throw new ElasticsearchParseException("failed to parse indices privileges for role [{}]. unexpected field [{}]",
|
||||||
|
roleName, currentFieldName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (names == null) {
|
||||||
public IndicesPrivilegesBuilder query(@Nullable BytesReference query) {
|
throw new ElasticsearchParseException("failed to parse indices privileges for role [{}]. missing required [{}] field",
|
||||||
this.query = query;
|
roleName, Fields.NAMES.getPreferredName());
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
if (privileges == null) {
|
||||||
public IndicesPrivileges build() {
|
throw new ElasticsearchParseException("failed to parse indices privileges for role [{}]. missing required [{}] field",
|
||||||
return new IndicesPrivileges(privileges, indices, fields, query);
|
roleName, Fields.PRIVILEGES.getPreferredName());
|
||||||
}
|
}
|
||||||
|
return RoleDescriptor.IndicesPrivileges.builder()
|
||||||
|
.indices(names)
|
||||||
|
.privileges(privileges)
|
||||||
|
.fields(fields)
|
||||||
|
.query(query)
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -298,34 +237,28 @@ public class RoleDescriptor implements ToXContent {
|
||||||
*/
|
*/
|
||||||
public static class IndicesPrivileges implements ToXContent, Streamable {
|
public static class IndicesPrivileges implements ToXContent, Streamable {
|
||||||
|
|
||||||
private String[] privileges;
|
private static final IndicesPrivileges[] NONE = new IndicesPrivileges[0];
|
||||||
|
|
||||||
private String[] indices;
|
private String[] indices;
|
||||||
|
private String[] privileges;
|
||||||
private String[] fields;
|
private String[] fields;
|
||||||
private BytesReference query;
|
private BytesReference query;
|
||||||
|
|
||||||
private IndicesPrivileges() {
|
private IndicesPrivileges() {
|
||||||
}
|
}
|
||||||
|
|
||||||
IndicesPrivileges(String[] privileges, String[] indices,
|
public static Builder builder() {
|
||||||
@Nullable String[] fields, @Nullable BytesReference query) {
|
return new Builder();
|
||||||
this.privileges = privileges;
|
|
||||||
this.indices = indices;
|
|
||||||
this.fields = fields;
|
|
||||||
this.query = query;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IndicesPrivilegesBuilder builder() {
|
|
||||||
return new IndicesPrivilegesBuilder();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String[] getPrivileges() {
|
|
||||||
return this.privileges;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String[] getIndices() {
|
public String[] getIndices() {
|
||||||
return this.indices;
|
return this.indices;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String[] getPrivileges() {
|
||||||
|
return this.privileges;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public String[] getFields() {
|
public String[] getFields() {
|
||||||
return this.fields;
|
return this.fields;
|
||||||
|
@ -339,8 +272,8 @@ public class RoleDescriptor implements ToXContent {
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder sb = new StringBuilder("IndicesPrivileges[");
|
StringBuilder sb = new StringBuilder("IndicesPrivileges[");
|
||||||
sb.append("privileges=[").append(Strings.arrayToCommaDelimitedString(privileges));
|
sb.append("indices=[").append(Strings.arrayToCommaDelimitedString(indices));
|
||||||
sb.append("], indices=[").append(Strings.arrayToCommaDelimitedString(indices));
|
sb.append("], privileges=[").append(Strings.arrayToCommaDelimitedString(privileges));
|
||||||
sb.append("], fields=[").append(Strings.arrayToCommaDelimitedString(fields));
|
sb.append("], fields=[").append(Strings.arrayToCommaDelimitedString(fields));
|
||||||
if (query != null) {
|
if (query != null) {
|
||||||
sb.append("], query=").append(query.toUtf8());
|
sb.append("], query=").append(query.toUtf8());
|
||||||
|
@ -349,9 +282,31 @@ public class RoleDescriptor implements ToXContent {
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
IndicesPrivileges that = (IndicesPrivileges) o;
|
||||||
|
|
||||||
|
if (!Arrays.equals(indices, that.indices)) return false;
|
||||||
|
if (!Arrays.equals(privileges, that.privileges)) return false;
|
||||||
|
if (!Arrays.equals(fields, that.fields)) return false;
|
||||||
|
return !(query != null ? !query.equals(that.query) : that.query != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = Arrays.hashCode(indices);
|
||||||
|
result = 31 * result + Arrays.hashCode(privileges);
|
||||||
|
result = 31 * result + Arrays.hashCode(fields);
|
||||||
|
result = 31 * result + (query != null ? query.hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||||
builder.startObject(); // start
|
builder.startObject();
|
||||||
builder.array("names", indices);
|
builder.array("names", indices);
|
||||||
builder.array("privileges", privileges);
|
builder.array("privileges", privileges);
|
||||||
if (fields != null) {
|
if (fields != null) {
|
||||||
|
@ -360,11 +315,10 @@ public class RoleDescriptor implements ToXContent {
|
||||||
if (query != null) {
|
if (query != null) {
|
||||||
builder.field("query", query.toUtf8());
|
builder.field("query", query.toUtf8());
|
||||||
}
|
}
|
||||||
builder.endObject(); // end start
|
return builder.endObject();
|
||||||
return builder;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IndicesPrivileges readIndicesPrivileges(StreamInput in) throws IOException {
|
public static IndicesPrivileges createFrom(StreamInput in) throws IOException {
|
||||||
IndicesPrivileges ip = new IndicesPrivileges();
|
IndicesPrivileges ip = new IndicesPrivileges();
|
||||||
ip.readFrom(in);
|
ip.readFrom(in);
|
||||||
return ip;
|
return ip;
|
||||||
|
@ -372,9 +326,9 @@ public class RoleDescriptor implements ToXContent {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
public void readFrom(StreamInput in) throws IOException {
|
||||||
this.privileges = in.readStringArray();
|
|
||||||
this.indices = in.readStringArray();
|
this.indices = in.readStringArray();
|
||||||
this.fields = in.readOptionalStringArray();
|
this.fields = in.readOptionalStringArray();
|
||||||
|
this.privileges = in.readStringArray();
|
||||||
if (in.readBoolean()) {
|
if (in.readBoolean()) {
|
||||||
this.query = new BytesArray(in.readByteArray());
|
this.query = new BytesArray(in.readByteArray());
|
||||||
}
|
}
|
||||||
|
@ -382,9 +336,9 @@ public class RoleDescriptor implements ToXContent {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
out.writeStringArray(privileges);
|
|
||||||
out.writeStringArray(indices);
|
out.writeStringArray(indices);
|
||||||
out.writeOptionalStringArray(fields);
|
out.writeOptionalStringArray(fields);
|
||||||
|
out.writeStringArray(privileges);
|
||||||
if (query != null) {
|
if (query != null) {
|
||||||
out.writeBoolean(true);
|
out.writeBoolean(true);
|
||||||
out.writeByteArray(query.array());
|
out.writeByteArray(query.array());
|
||||||
|
@ -392,5 +346,57 @@ public class RoleDescriptor implements ToXContent {
|
||||||
out.writeBoolean(false);
|
out.writeBoolean(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
|
||||||
|
private IndicesPrivileges indicesPrivileges = new IndicesPrivileges();
|
||||||
|
|
||||||
|
private Builder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder indices(String... indices) {
|
||||||
|
indicesPrivileges.indices = indices;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder privileges(String... privileges) {
|
||||||
|
indicesPrivileges.privileges = privileges;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder fields(@Nullable String... fields) {
|
||||||
|
indicesPrivileges.fields = fields;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder query(@Nullable String query) {
|
||||||
|
return query(query == null ? null : new BytesArray(query));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder query(@Nullable BytesReference query) {
|
||||||
|
indicesPrivileges.query = query;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IndicesPrivileges build() {
|
||||||
|
if (indicesPrivileges.indices == null || indicesPrivileges.indices.length == 0) {
|
||||||
|
throw new IllegalArgumentException("indices privileges must refer to at least one index name or index name pattern");
|
||||||
|
}
|
||||||
|
if (indicesPrivileges.privileges == null || indicesPrivileges.privileges.length == 0) {
|
||||||
|
throw new IllegalArgumentException("indices privileges must define at least one privilege");
|
||||||
|
}
|
||||||
|
return indicesPrivileges;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Fields {
|
||||||
|
ParseField CLUSTER = new ParseField("cluster");
|
||||||
|
ParseField INDICES = new ParseField("indices");
|
||||||
|
ParseField RUN_AS = new ParseField("run_as");
|
||||||
|
ParseField NAMES = new ParseField("names");
|
||||||
|
ParseField QUERY = new ParseField("query");
|
||||||
|
ParseField PRIVILEGES = new ParseField("privileges");
|
||||||
|
ParseField FIELDS = new ParseField("fields");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,6 @@ import org.elasticsearch.action.delete.DeleteRequest;
|
||||||
import org.elasticsearch.action.delete.DeleteResponse;
|
import org.elasticsearch.action.delete.DeleteResponse;
|
||||||
import org.elasticsearch.action.get.GetRequest;
|
import org.elasticsearch.action.get.GetRequest;
|
||||||
import org.elasticsearch.action.get.GetResponse;
|
import org.elasticsearch.action.get.GetResponse;
|
||||||
import org.elasticsearch.action.index.IndexRequest;
|
|
||||||
import org.elasticsearch.action.index.IndexResponse;
|
import org.elasticsearch.action.index.IndexResponse;
|
||||||
import org.elasticsearch.action.search.ClearScrollRequest;
|
import org.elasticsearch.action.search.ClearScrollRequest;
|
||||||
import org.elasticsearch.action.search.ClearScrollResponse;
|
import org.elasticsearch.action.search.ClearScrollResponse;
|
||||||
|
@ -40,7 +39,6 @@ import org.elasticsearch.index.query.QueryBuilders;
|
||||||
import org.elasticsearch.search.SearchHit;
|
import org.elasticsearch.search.SearchHit;
|
||||||
import org.elasticsearch.shield.InternalClient;
|
import org.elasticsearch.shield.InternalClient;
|
||||||
import org.elasticsearch.shield.ShieldTemplateService;
|
import org.elasticsearch.shield.ShieldTemplateService;
|
||||||
import org.elasticsearch.shield.action.role.PutRoleRequest;
|
|
||||||
import org.elasticsearch.shield.action.role.ClearRolesCacheRequest;
|
import org.elasticsearch.shield.action.role.ClearRolesCacheRequest;
|
||||||
import org.elasticsearch.shield.action.role.ClearRolesCacheResponse;
|
import org.elasticsearch.shield.action.role.ClearRolesCacheResponse;
|
||||||
import org.elasticsearch.shield.action.role.DeleteRoleRequest;
|
import org.elasticsearch.shield.action.role.DeleteRoleRequest;
|
||||||
|
@ -76,7 +74,16 @@ import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
|
||||||
*/
|
*/
|
||||||
public class ESNativeRolesStore extends AbstractComponent implements RolesStore, ClusterStateListener {
|
public class ESNativeRolesStore extends AbstractComponent implements RolesStore, ClusterStateListener {
|
||||||
|
|
||||||
public static final String INDEX_ROLE_TYPE = "role";
|
public enum State {
|
||||||
|
INITIALIZED,
|
||||||
|
STARTING,
|
||||||
|
STARTED,
|
||||||
|
STOPPING,
|
||||||
|
STOPPED,
|
||||||
|
FAILED
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String ROLE_DOC_TYPE = "role";
|
||||||
|
|
||||||
private final Provider<InternalClient> clientProvider;
|
private final Provider<InternalClient> clientProvider;
|
||||||
private final ThreadPool threadPool;
|
private final ThreadPool threadPool;
|
||||||
|
@ -99,28 +106,66 @@ public class ESNativeRolesStore extends AbstractComponent implements RolesStore,
|
||||||
this.threadPool = threadPool;
|
this.threadPool = threadPool;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
public boolean canStart(ClusterState clusterState, boolean master) {
|
||||||
private RoleDescriptor transformRole(GetResponse response) {
|
if (state() != ESNativeRolesStore.State.INITIALIZED) {
|
||||||
if (response.isExists() == false) {
|
return false;
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
return transformRole(response.getSourceAsBytesRef());
|
|
||||||
|
if (clusterState.blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)) {
|
||||||
|
// wait until the gateway has recovered from disk, otherwise we
|
||||||
|
// think may not have the .shield index but they it may not have
|
||||||
|
// been restored from the cluster state on disk yet
|
||||||
|
logger.debug("native roles store waiting until gateway has recovered from disk");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clusterState.metaData().templates().get(ShieldTemplateService.SECURITY_TEMPLATE_NAME) == null) {
|
||||||
|
logger.debug("native roles template [{}] does not exist, so service cannot start",
|
||||||
|
ShieldTemplateService.SECURITY_TEMPLATE_NAME);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Okay to start...
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
public void start() {
|
||||||
private RoleDescriptor transformRole(BytesReference sourceBytes) {
|
|
||||||
try {
|
try {
|
||||||
return RoleDescriptor.source(null, sourceBytes);
|
if (state.compareAndSet(State.INITIALIZED, State.STARTING)) {
|
||||||
|
this.client = clientProvider.get();
|
||||||
|
this.securityClient = new SecurityClient(client);
|
||||||
|
this.scrollSize = settings.getAsInt("shield.authc.native.scroll.size", 1000);
|
||||||
|
this.scrollKeepAlive = settings.getAsTime("shield.authc.native.scroll.keep_alive", TimeValue.timeValueSeconds(10L));
|
||||||
|
TimeValue pollInterval = settings.getAsTime("shield.authc.native.reload.interval", TimeValue.timeValueSeconds(30L));
|
||||||
|
RolesStorePoller poller = new RolesStorePoller();
|
||||||
|
try {
|
||||||
|
poller.doRun();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("failed to perform initial poll of roles index [{}]. scheduling again in [{}]", e,
|
||||||
|
ShieldTemplateService.SECURITY_INDEX_NAME, pollInterval);
|
||||||
|
}
|
||||||
|
versionChecker = threadPool.scheduleWithFixedDelay(poller, pollInterval);
|
||||||
|
state.set(State.STARTED);
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn("unable to deserialize role from response", e);
|
logger.error("failed to start ESNativeRolesStore", e);
|
||||||
return null;
|
state.set(State.FAILED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
if (state.compareAndSet(State.STARTED, State.STOPPING)) {
|
||||||
|
try {
|
||||||
|
FutureUtils.cancel(versionChecker);
|
||||||
|
} finally {
|
||||||
|
state.set(State.STOPPED);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve a list of roles, if rolesToGet is null or empty, fetch all roles
|
* Retrieve a list of roles, if rolesToGet is null or empty, fetch all roles
|
||||||
*/
|
*/
|
||||||
public void getRoleDescriptors(String[] rolesToGet, final ActionListener<List<RoleDescriptor>> listener) {
|
public void getRoleDescriptors(String[] names, final ActionListener<List<RoleDescriptor>> listener) {
|
||||||
if (state() != State.STARTED) {
|
if (state() != State.STARTED) {
|
||||||
logger.trace("attempted to get roles before service was started");
|
logger.trace("attempted to get roles before service was started");
|
||||||
listener.onFailure(new IllegalStateException("roles cannot be retrieved as native role service has not been started"));
|
listener.onFailure(new IllegalStateException("roles cannot be retrieved as native role service has not been started"));
|
||||||
|
@ -129,12 +174,13 @@ public class ESNativeRolesStore extends AbstractComponent implements RolesStore,
|
||||||
try {
|
try {
|
||||||
final List<RoleDescriptor> roles = new ArrayList<>();
|
final List<RoleDescriptor> roles = new ArrayList<>();
|
||||||
QueryBuilder query;
|
QueryBuilder query;
|
||||||
if (rolesToGet == null || rolesToGet.length == 0) {
|
if (names == null || names.length == 0) {
|
||||||
query = QueryBuilders.boolQuery().filter(QueryBuilders.termQuery("_type", INDEX_ROLE_TYPE));
|
query = QueryBuilders.matchAllQuery();
|
||||||
} else {
|
} else {
|
||||||
query = QueryBuilders.boolQuery().filter(QueryBuilders.idsQuery(INDEX_ROLE_TYPE).addIds(rolesToGet));
|
query = QueryBuilders.boolQuery().filter(QueryBuilders.idsQuery(ROLE_DOC_TYPE).addIds(names));
|
||||||
}
|
}
|
||||||
SearchRequest request = client.prepareSearch(ShieldTemplateService.SECURITY_INDEX_NAME)
|
SearchRequest request = client.prepareSearch(ShieldTemplateService.SECURITY_INDEX_NAME)
|
||||||
|
.setTypes(ROLE_DOC_TYPE)
|
||||||
.setScroll(scrollKeepAlive)
|
.setScroll(scrollKeepAlive)
|
||||||
.setQuery(query)
|
.setQuery(query)
|
||||||
.setSize(scrollSize)
|
.setSize(scrollSize)
|
||||||
|
@ -149,7 +195,7 @@ public class ESNativeRolesStore extends AbstractComponent implements RolesStore,
|
||||||
boolean hasHits = resp.getHits().getHits().length > 0;
|
boolean hasHits = resp.getHits().getHits().length > 0;
|
||||||
if (hasHits) {
|
if (hasHits) {
|
||||||
for (SearchHit hit : resp.getHits().getHits()) {
|
for (SearchHit hit : resp.getHits().getHits()) {
|
||||||
RoleDescriptor rd = transformRole(hit.getSourceRef());
|
RoleDescriptor rd = transformRole(hit.getId(), hit.getSourceRef());
|
||||||
if (rd != null) {
|
if (rd != null) {
|
||||||
roles.add(rd);
|
roles.add(rd);
|
||||||
}
|
}
|
||||||
|
@ -203,30 +249,19 @@ public class ESNativeRolesStore extends AbstractComponent implements RolesStore,
|
||||||
listener.onResponse(roleAndVersion == null ? null : roleAndVersion.getRoleDescriptor());
|
listener.onResponse(roleAndVersion == null ? null : roleAndVersion.getRoleDescriptor());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void executeGetRoleRequest(String role, ActionListener<GetResponse> listener) {
|
public void deleteRole(final DeleteRoleRequest deleteRoleRequest, final ActionListener<Boolean> listener) {
|
||||||
try {
|
|
||||||
GetRequest request = client.prepareGet(ShieldTemplateService.SECURITY_INDEX_NAME, INDEX_ROLE_TYPE, role).request();
|
|
||||||
request.indicesOptions().ignoreUnavailable();
|
|
||||||
client.get(request, listener);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("unable to retrieve role", e);
|
|
||||||
listener.onFailure(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeRole(final DeleteRoleRequest deleteRoleRequest, final ActionListener<Boolean> listener) {
|
|
||||||
if (state() != State.STARTED) {
|
if (state() != State.STARTED) {
|
||||||
logger.trace("attempted to delete role [{}] before service was started", deleteRoleRequest.role());
|
logger.trace("attempted to delete role [{}] before service was started", deleteRoleRequest.name());
|
||||||
listener.onResponse(false);
|
listener.onResponse(false);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
DeleteRequest request = client.prepareDelete(ShieldTemplateService.SECURITY_INDEX_NAME,
|
DeleteRequest request = client.prepareDelete(ShieldTemplateService.SECURITY_INDEX_NAME,
|
||||||
INDEX_ROLE_TYPE, deleteRoleRequest.role()).request();
|
ROLE_DOC_TYPE, deleteRoleRequest.name()).request();
|
||||||
request.indicesOptions().ignoreUnavailable();
|
request.indicesOptions().ignoreUnavailable();
|
||||||
client.delete(request, new ActionListener<DeleteResponse>() {
|
client.delete(request, new ActionListener<DeleteResponse>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(DeleteResponse deleteResponse) {
|
public void onResponse(DeleteResponse deleteResponse) {
|
||||||
clearRoleCache(deleteRoleRequest.role(), listener, deleteResponse.isFound());
|
clearRoleCache(deleteRoleRequest.name(), listener, deleteResponse.isFound());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -241,7 +276,44 @@ public class ESNativeRolesStore extends AbstractComponent implements RolesStore,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private RoleAndVersion getRoleAndVersion(String roleId) {
|
public void putRole(final RoleDescriptor role, final ActionListener<Boolean> listener) {
|
||||||
|
if (state() != State.STARTED) {
|
||||||
|
logger.trace("attempted to put role before service was started");
|
||||||
|
listener.onResponse(false);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
client.prepareIndex(ShieldTemplateService.SECURITY_INDEX_NAME, ROLE_DOC_TYPE, role.getName())
|
||||||
|
.setSource(role.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS))
|
||||||
|
.execute(new ActionListener<IndexResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(IndexResponse indexResponse) {
|
||||||
|
if (indexResponse.isCreated()) {
|
||||||
|
listener.onResponse(indexResponse.isCreated());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clearRoleCache(role.getName(), listener, indexResponse.isCreated());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable e) {
|
||||||
|
logger.error("failed to put role to the index", e);
|
||||||
|
listener.onFailure(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("unable to put role", e);
|
||||||
|
listener.onFailure(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Role role(String roleName) {
|
||||||
|
RoleAndVersion roleAndVersion = getRoleAndVersion(roleName);
|
||||||
|
return roleAndVersion == null ? null : roleAndVersion.getRole();
|
||||||
|
}
|
||||||
|
|
||||||
|
private RoleAndVersion getRoleAndVersion(final String roleId) {
|
||||||
RoleAndVersion roleAndVersion = null;
|
RoleAndVersion roleAndVersion = null;
|
||||||
final AtomicReference<GetResponse> getRef = new AtomicReference<>(null);
|
final AtomicReference<GetResponse> getRef = new AtomicReference<>(null);
|
||||||
final CountDownLatch latch = new CountDownLatch(1);
|
final CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
@ -288,99 +360,15 @@ public class ESNativeRolesStore extends AbstractComponent implements RolesStore,
|
||||||
return roleAndVersion;
|
return roleAndVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addRole(final PutRoleRequest putRoleRequest, final ActionListener<Boolean> listener) {
|
private void executeGetRoleRequest(String role, ActionListener<GetResponse> listener) {
|
||||||
if (state() != State.STARTED) {
|
|
||||||
logger.trace("attempted to add role before service was started");
|
|
||||||
listener.onResponse(false);
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
IndexRequest request = client.prepareIndex(ShieldTemplateService.SECURITY_INDEX_NAME,
|
GetRequest request = client.prepareGet(ShieldTemplateService.SECURITY_INDEX_NAME, ROLE_DOC_TYPE, role).request();
|
||||||
INDEX_ROLE_TYPE, putRoleRequest.name())
|
request.indicesOptions().ignoreUnavailable();
|
||||||
.setSource(putRoleRequest.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS))
|
client.get(request, listener);
|
||||||
.request();
|
|
||||||
client.index(request, new ActionListener<IndexResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(IndexResponse indexResponse) {
|
|
||||||
if (indexResponse.isCreated()) {
|
|
||||||
listener.onResponse(indexResponse.isCreated());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
clearRoleCache(putRoleRequest.name(), listener, indexResponse.isCreated());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Throwable e) {
|
|
||||||
logger.error("failed to add role to the index", e);
|
|
||||||
listener.onFailure(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("unable to add role", e);
|
logger.error("unable to retrieve role", e);
|
||||||
listener.onFailure(e);
|
listener.onFailure(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Role role(String roleName) {
|
|
||||||
RoleAndVersion roleAndVersion = getRoleAndVersion(roleName);
|
|
||||||
return roleAndVersion == null ? null : roleAndVersion.getRole();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean canStart(ClusterState clusterState, boolean master) {
|
|
||||||
if (state() != ESNativeRolesStore.State.INITIALIZED) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clusterState.blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)) {
|
|
||||||
// wait until the gateway has recovered from disk, otherwise we
|
|
||||||
// think may not have the .security index but they it may not have
|
|
||||||
// been restored from the cluster state on disk yet
|
|
||||||
logger.debug("native roles store waiting until gateway has recovered from disk");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clusterState.metaData().templates().get(ShieldTemplateService.SECURITY_TEMPLATE_NAME) == null) {
|
|
||||||
logger.debug("native roles template [{}] does not exist, so service cannot start",
|
|
||||||
ShieldTemplateService.SECURITY_TEMPLATE_NAME);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Okay to start...
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start() {
|
|
||||||
try {
|
|
||||||
if (state.compareAndSet(State.INITIALIZED, State.STARTING)) {
|
|
||||||
this.client = clientProvider.get();
|
|
||||||
this.securityClient = new SecurityClient(client);
|
|
||||||
this.scrollSize = settings.getAsInt("shield.authc.native.scroll.size", 1000);
|
|
||||||
this.scrollKeepAlive = settings.getAsTime("shield.authc.native.scroll.keep_alive", TimeValue.timeValueSeconds(10L));
|
|
||||||
TimeValue pollInterval = settings.getAsTime("shield.authc.native.reload.interval", TimeValue.timeValueSeconds(30L));
|
|
||||||
RolesStorePoller poller = new RolesStorePoller();
|
|
||||||
try {
|
|
||||||
poller.doRun();
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.warn("failed to perform initial poll of roles index [{}]. scheduling again in [{}]", e,
|
|
||||||
ShieldTemplateService.SECURITY_INDEX_NAME, pollInterval);
|
|
||||||
}
|
|
||||||
versionChecker = threadPool.scheduleWithFixedDelay(poller, pollInterval);
|
|
||||||
state.set(State.STARTED);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("failed to start ESNativeRolesStore", e);
|
|
||||||
state.set(State.FAILED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stop() {
|
|
||||||
if (state.compareAndSet(State.STARTED, State.STOPPING)) {
|
|
||||||
try {
|
|
||||||
FutureUtils.cancel(versionChecker);
|
|
||||||
} finally {
|
|
||||||
state.set(State.STOPPED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME hack for testing
|
// FIXME hack for testing
|
||||||
|
@ -442,13 +430,22 @@ public class ESNativeRolesStore extends AbstractComponent implements RolesStore,
|
||||||
return state.get();
|
return state.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum State {
|
@Nullable
|
||||||
INITIALIZED,
|
private RoleDescriptor transformRole(GetResponse response) {
|
||||||
STARTING,
|
if (response.isExists() == false) {
|
||||||
STARTED,
|
return null;
|
||||||
STOPPING,
|
}
|
||||||
STOPPED,
|
return transformRole(response.getId(), response.getSourceAsBytesRef());
|
||||||
FAILED
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private RoleDescriptor transformRole(String name, BytesReference sourceBytes) {
|
||||||
|
try {
|
||||||
|
return RoleDescriptor.parse(name, sourceBytes);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("unable to deserialize role from response", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class RolesStorePoller extends AbstractRunnable {
|
private class RolesStorePoller extends AbstractRunnable {
|
||||||
|
@ -475,7 +472,7 @@ public class ESNativeRolesStore extends AbstractComponent implements RolesStore,
|
||||||
try {
|
try {
|
||||||
SearchRequest request = client.prepareSearch(ShieldTemplateService.SECURITY_INDEX_NAME)
|
SearchRequest request = client.prepareSearch(ShieldTemplateService.SECURITY_INDEX_NAME)
|
||||||
.setScroll(scrollKeepAlive)
|
.setScroll(scrollKeepAlive)
|
||||||
.setQuery(QueryBuilders.typeQuery(INDEX_ROLE_TYPE))
|
.setQuery(QueryBuilders.typeQuery(ROLE_DOC_TYPE))
|
||||||
.setSize(scrollSize)
|
.setSize(scrollSize)
|
||||||
.setFetchSource(true)
|
.setFetchSource(true)
|
||||||
.setVersion(true)
|
.setVersion(true)
|
||||||
|
@ -487,7 +484,7 @@ public class ESNativeRolesStore extends AbstractComponent implements RolesStore,
|
||||||
if (isStopped()) {
|
if (isStopped()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (final SearchHit hit : response.getHits().getHits()) {
|
for (SearchHit hit : response.getHits().getHits()) {
|
||||||
final String roleName = hit.getId();
|
final String roleName = hit.getId();
|
||||||
final long version = hit.version();
|
final long version = hit.version();
|
||||||
existingRoles.remove(roleName);
|
existingRoles.remove(roleName);
|
||||||
|
@ -497,7 +494,7 @@ public class ESNativeRolesStore extends AbstractComponent implements RolesStore,
|
||||||
@Override
|
@Override
|
||||||
public RoleAndVersion apply(String roleName, RoleAndVersion existing) {
|
public RoleAndVersion apply(String roleName, RoleAndVersion existing) {
|
||||||
if (version > existing.getVersion()) {
|
if (version > existing.getVersion()) {
|
||||||
RoleDescriptor rd = transformRole(hit.getSourceRef());
|
RoleDescriptor rd = transformRole(hit.getId(), hit.getSourceRef());
|
||||||
if (rd != null) {
|
if (rd != null) {
|
||||||
return new RoleAndVersion(rd, version);
|
return new RoleAndVersion(rd, version);
|
||||||
}
|
}
|
||||||
|
@ -541,6 +538,7 @@ public class ESNativeRolesStore extends AbstractComponent implements RolesStore,
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class RoleAndVersion {
|
private static class RoleAndVersion {
|
||||||
|
|
||||||
private final RoleDescriptor roleDescriptor;
|
private final RoleDescriptor roleDescriptor;
|
||||||
private final Role role;
|
private final Role role;
|
||||||
private final long version;
|
private final long version;
|
||||||
|
|
|
@ -9,7 +9,6 @@ import org.elasticsearch.client.Client;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.inject.Inject;
|
import org.elasticsearch.common.inject.Inject;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
import org.elasticsearch.rest.BaseRestHandler;
|
import org.elasticsearch.rest.BaseRestHandler;
|
||||||
import org.elasticsearch.rest.BytesRestResponse;
|
import org.elasticsearch.rest.BytesRestResponse;
|
||||||
|
@ -21,6 +20,7 @@ import org.elasticsearch.rest.RestStatus;
|
||||||
import org.elasticsearch.rest.action.support.RestBuilderListener;
|
import org.elasticsearch.rest.action.support.RestBuilderListener;
|
||||||
import org.elasticsearch.shield.action.role.GetRolesResponse;
|
import org.elasticsearch.shield.action.role.GetRolesResponse;
|
||||||
import org.elasticsearch.shield.client.SecurityClient;
|
import org.elasticsearch.shield.client.SecurityClient;
|
||||||
|
import org.elasticsearch.shield.authz.RoleDescriptor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rest endpoint to retrieve a Role from the shield index
|
* Rest endpoint to retrieve a Role from the shield index
|
||||||
|
@ -35,20 +35,25 @@ public class RestGetRolesAction extends BaseRestHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handleRequest(RestRequest request, final RestChannel channel, Client client) throws Exception {
|
protected void handleRequest(RestRequest request, RestChannel channel, Client client) throws Exception {
|
||||||
String[] names = request.paramAsStringArrayOrEmptyIfAll("name");
|
final String[] roles = request.paramAsStringArray("name", Strings.EMPTY_ARRAY);
|
||||||
|
new SecurityClient(client).prepareGetRoles(roles).execute(new RestBuilderListener<GetRolesResponse>(channel) {
|
||||||
new SecurityClient(client).prepareGetRoles(names).execute(new RestBuilderListener<GetRolesResponse>(channel) {
|
|
||||||
@Override
|
@Override
|
||||||
public RestResponse buildResponse(GetRolesResponse getRolesResponse, XContentBuilder builder) throws Exception {
|
public RestResponse buildResponse(GetRolesResponse response, XContentBuilder builder) throws Exception {
|
||||||
builder.startObject();
|
builder.startObject();
|
||||||
builder.field("found", getRolesResponse.isExists());
|
for (RoleDescriptor role : response.roles()) {
|
||||||
builder.startArray("roles");
|
builder.field(role.getName(), role);
|
||||||
for (ToXContent role : getRolesResponse.roles()) {
|
|
||||||
role.toXContent(builder, ToXContent.EMPTY_PARAMS);
|
|
||||||
}
|
}
|
||||||
builder.endArray();
|
|
||||||
builder.endObject();
|
builder.endObject();
|
||||||
|
|
||||||
|
// if the user asked for specific roles, but none of them were found
|
||||||
|
// we'll return an empty result and 404 status code
|
||||||
|
if (roles.length != 0 && response.roles().length == 0) {
|
||||||
|
return new BytesRestResponse(RestStatus.NOT_FOUND, builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
// either the user asked for all roles, or at least one of the roles
|
||||||
|
// the user asked for was found
|
||||||
return new BytesRestResponse(RestStatus.OK, builder);
|
return new BytesRestResponse(RestStatus.OK, builder);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -33,6 +33,7 @@ import org.junit.BeforeClass;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.arrayWithSize;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
import static org.hamcrest.Matchers.isOneOf;
|
import static org.hamcrest.Matchers.isOneOf;
|
||||||
import static org.hamcrest.Matchers.notNullValue;
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
|
@ -135,7 +136,7 @@ public class ClearRolesCacheTests extends ShieldIntegTestCase {
|
||||||
logger.debug("--> modifying roles {} to have run_as", toModify);
|
logger.debug("--> modifying roles {} to have run_as", toModify);
|
||||||
for (String role : toModify) {
|
for (String role : toModify) {
|
||||||
UpdateResponse response = client.prepareUpdate().setId(role).setIndex(ShieldTemplateService.SECURITY_INDEX_NAME)
|
UpdateResponse response = client.prepareUpdate().setId(role).setIndex(ShieldTemplateService.SECURITY_INDEX_NAME)
|
||||||
.setType(ESNativeRolesStore.INDEX_ROLE_TYPE)
|
.setType(ESNativeRolesStore.ROLE_DOC_TYPE)
|
||||||
.setDoc("run_as", new String[] { role })
|
.setDoc("run_as", new String[] { role })
|
||||||
.get();
|
.get();
|
||||||
assertThat(response.isCreated(), is(false));
|
assertThat(response.isCreated(), is(false));
|
||||||
|
@ -174,17 +175,17 @@ public class ClearRolesCacheTests extends ShieldIntegTestCase {
|
||||||
SecurityClient securityClient = securityClient(client);
|
SecurityClient securityClient = securityClient(client);
|
||||||
|
|
||||||
final String role = randomFrom(roles);
|
final String role = randomFrom(roles);
|
||||||
List<RoleDescriptor> foundRoles = securityClient.prepareGetRoles().names(role).get().roles();
|
RoleDescriptor[] foundRoles = securityClient.prepareGetRoles().names(role).get().roles();
|
||||||
assertThat(foundRoles.size(), is(1));
|
assertThat(foundRoles.length, is(1));
|
||||||
logger.debug("--> deleting role [{}]", role);
|
logger.debug("--> deleting role [{}]", role);
|
||||||
DeleteResponse response = client.prepareDelete(ShieldTemplateService.SECURITY_INDEX_NAME,
|
DeleteResponse response = client
|
||||||
ESNativeRolesStore.INDEX_ROLE_TYPE, role).get();
|
.prepareDelete(ShieldTemplateService.SECURITY_INDEX_NAME, ESNativeRolesStore.ROLE_DOC_TYPE, role).get();
|
||||||
assertThat(response.isFound(), is(true));
|
assertThat(response.isFound(), is(true));
|
||||||
|
|
||||||
assertBusy(new Runnable() {
|
assertBusy(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
assertThat(securityClient.prepareGetRoles().names(role).get().roles().isEmpty(), is(true));
|
assertThat(securityClient.prepareGetRoles().names(role).get().roles(), arrayWithSize(0));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -193,8 +194,8 @@ public class ClearRolesCacheTests extends ShieldIntegTestCase {
|
||||||
for (String role : roles) {
|
for (String role : roles) {
|
||||||
logger.debug("--> getting role [{}]", role);
|
logger.debug("--> getting role [{}]", role);
|
||||||
GetRolesResponse roleResponse = securityClient.prepareGetRoles().names(role).get();
|
GetRolesResponse roleResponse = securityClient.prepareGetRoles().names(role).get();
|
||||||
assertThat(roleResponse.isExists(), is(true));
|
assertThat(roleResponse.hasRoles(), is(true));
|
||||||
final String[] runAs = roleResponse.roles().get(0).getRunAs();
|
final String[] runAs = roleResponse.roles()[0].getRunAs();
|
||||||
if (toModify.contains(role)) {
|
if (toModify.contains(role)) {
|
||||||
assertThat("role [" + role + "] should be modified and have run as", runAs == null || runAs.length == 0, is(false));
|
assertThat("role [" + role + "] should be modified and have run as", runAs == null || runAs.length == 0, is(false));
|
||||||
assertThat(Arrays.asList(runAs).contains(role), is(true));
|
assertThat(Arrays.asList(runAs).contains(role), is(true));
|
||||||
|
|
|
@ -55,7 +55,7 @@ public class ESNativeTests extends ShieldIntegTestCase {
|
||||||
GetUsersResponse resp = c.prepareGetUsers("joe").get();
|
GetUsersResponse resp = c.prepareGetUsers("joe").get();
|
||||||
assertFalse("user should not exist", resp.hasUsers());
|
assertFalse("user should not exist", resp.hasUsers());
|
||||||
GetRolesResponse resp2 = c.prepareGetRoles().names("role").get();
|
GetRolesResponse resp2 = c.prepareGetRoles().names("role").get();
|
||||||
assertFalse("role should not exist", resp2.isExists());
|
assertFalse("role should not exist", resp2.hasRoles());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testAddAndGetUser() throws Exception {
|
public void testAddAndGetUser() throws Exception {
|
||||||
|
@ -117,8 +117,8 @@ public class ESNativeTests extends ShieldIntegTestCase {
|
||||||
ensureGreen(ShieldTemplateService.SECURITY_INDEX_NAME);
|
ensureGreen(ShieldTemplateService.SECURITY_INDEX_NAME);
|
||||||
logger.info("--> retrieving role");
|
logger.info("--> retrieving role");
|
||||||
GetRolesResponse resp = c.prepareGetRoles().names("test_role").get();
|
GetRolesResponse resp = c.prepareGetRoles().names("test_role").get();
|
||||||
assertTrue("role should exist", resp.isExists());
|
assertTrue("role should exist", resp.hasRoles());
|
||||||
RoleDescriptor testRole = resp.roles().get(0);
|
RoleDescriptor testRole = resp.roles()[0];
|
||||||
assertNotNull(testRole);
|
assertNotNull(testRole);
|
||||||
|
|
||||||
c.preparePutRole("test_role2")
|
c.preparePutRole("test_role2")
|
||||||
|
@ -139,20 +139,20 @@ public class ESNativeTests extends ShieldIntegTestCase {
|
||||||
|
|
||||||
logger.info("--> retrieving all roles");
|
logger.info("--> retrieving all roles");
|
||||||
GetRolesResponse allRolesResp = c.prepareGetRoles().get();
|
GetRolesResponse allRolesResp = c.prepareGetRoles().get();
|
||||||
assertTrue("roles should exist", allRolesResp.isExists());
|
assertTrue("roles should exist", allRolesResp.hasRoles());
|
||||||
assertEquals("should be 3 roles total", 3, allRolesResp.roles().size());
|
assertEquals("should be 3 roles total", 3, allRolesResp.roles().length);
|
||||||
|
|
||||||
logger.info("--> retrieving all roles");
|
logger.info("--> retrieving all roles");
|
||||||
GetRolesResponse someRolesResp = c.prepareGetRoles().names("test_role", "test_role3").get();
|
GetRolesResponse someRolesResp = c.prepareGetRoles().names("test_role", "test_role3").get();
|
||||||
assertTrue("roles should exist", someRolesResp.isExists());
|
assertTrue("roles should exist", someRolesResp.hasRoles());
|
||||||
assertEquals("should be 2 roles total", 2, someRolesResp.roles().size());
|
assertEquals("should be 2 roles total", 2, someRolesResp.roles().length);
|
||||||
|
|
||||||
logger.info("--> deleting role");
|
logger.info("--> deleting role");
|
||||||
DeleteRoleResponse delResp = c.prepareDeleteRole("test_role").get();
|
DeleteRoleResponse delResp = c.prepareDeleteRole("test_role").get();
|
||||||
assertTrue(delResp.found());
|
assertTrue(delResp.found());
|
||||||
logger.info("--> retrieving role");
|
logger.info("--> retrieving role");
|
||||||
GetRolesResponse resp2 = c.prepareGetRoles().names("test_role").get();
|
GetRolesResponse resp2 = c.prepareGetRoles().names("test_role").get();
|
||||||
assertFalse("role should not exist after being deleted", resp2.isExists());
|
assertFalse("role should not exist after being deleted", resp2.hasRoles());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testAddUserAndRoleThenAuth() throws Exception {
|
public void testAddUserAndRoleThenAuth() throws Exception {
|
||||||
|
@ -160,8 +160,8 @@ public class ESNativeTests extends ShieldIntegTestCase {
|
||||||
logger.error("--> creating role");
|
logger.error("--> creating role");
|
||||||
c.preparePutRole("test_role")
|
c.preparePutRole("test_role")
|
||||||
.cluster("all")
|
.cluster("all")
|
||||||
.addIndices(new String[]{"*"}, new String[]{"read"},
|
.addIndices(new String[] { "*" }, new String[] { "read" },
|
||||||
new String[]{"body", "title"}, new BytesArray("{\"match_all\": {}}"))
|
new String[] { "body", "title" }, new BytesArray("{\"match_all\": {}}"))
|
||||||
.get();
|
.get();
|
||||||
logger.error("--> creating user");
|
logger.error("--> creating user");
|
||||||
c.preparePutUser("joe", "s3krit".toCharArray(), "test_role").get();
|
c.preparePutUser("joe", "s3krit".toCharArray(), "test_role").get();
|
||||||
|
@ -284,19 +284,20 @@ public class ESNativeTests extends ShieldIntegTestCase {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
GetRolesResponse getRolesResponse = c.prepareGetRoles().names("test_role").get();
|
GetRolesResponse getRolesResponse = c.prepareGetRoles().names("test_role").get();
|
||||||
assertTrue("test_role does not exist!", getRolesResponse.isExists());
|
assertTrue("test_role does not exist!", getRolesResponse.hasRoles());
|
||||||
assertTrue("any cluster permission should be authorized",
|
assertTrue("any cluster permission should be authorized",
|
||||||
Role.builder(getRolesResponse.roles().get(0)).build().cluster().check("cluster:admin/foo"));
|
Role.builder(getRolesResponse.roles()[0]).build().cluster().check("cluster:admin/foo"));
|
||||||
|
|
||||||
c.preparePutRole("test_role")
|
c.preparePutRole("test_role")
|
||||||
.cluster("none")
|
.cluster("none")
|
||||||
.addIndices(new String[]{"*"}, new String[]{"read"},
|
.addIndices(new String[]{"*"}, new String[]{"read"},
|
||||||
new String[]{"body", "title"}, new BytesArray("{\"match_all\": {}}"))
|
new String[]{"body", "title"}, new BytesArray("{\"match_all\": {}}"))
|
||||||
.get();
|
.get();
|
||||||
getRolesResponse = c.prepareGetRoles().names("test_role").get();
|
getRolesResponse = c.prepareGetRoles().names("test_role").get();
|
||||||
assertTrue("test_role does not exist!", getRolesResponse.isExists());
|
assertTrue("test_role does not exist!", getRolesResponse.hasRoles());
|
||||||
|
|
||||||
assertFalse("no cluster permission should be authorized",
|
assertFalse("no cluster permission should be authorized",
|
||||||
Role.builder(getRolesResponse.roles().get(0)).build().cluster().check("cluster:admin/bar"));
|
Role.builder(getRolesResponse.roles()[0]).build().cluster().check("cluster:admin/bar"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.shield.authz;
|
package org.elasticsearch.shield.authz;
|
||||||
|
|
||||||
import org.elasticsearch.ElasticsearchParseException;
|
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.bytes.BytesArray;
|
import org.elasticsearch.common.bytes.BytesArray;
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
import org.elasticsearch.common.xcontent.ToXContent;
|
||||||
|
@ -19,111 +18,75 @@ public class RoleDescriptorTests extends ESTestCase {
|
||||||
|
|
||||||
public void testIndexGroup() throws Exception {
|
public void testIndexGroup() throws Exception {
|
||||||
RoleDescriptor.IndicesPrivileges privs = RoleDescriptor.IndicesPrivileges.builder()
|
RoleDescriptor.IndicesPrivileges privs = RoleDescriptor.IndicesPrivileges.builder()
|
||||||
.indices(new String[]{"idx"})
|
.indices("idx")
|
||||||
.privileges(new String[]{"priv"})
|
.privileges("priv")
|
||||||
.build();
|
.build();
|
||||||
XContentBuilder b = jsonBuilder();
|
XContentBuilder b = jsonBuilder();
|
||||||
privs.toXContent(b, ToXContent.EMPTY_PARAMS);
|
privs.toXContent(b, ToXContent.EMPTY_PARAMS);
|
||||||
assertEquals("{\"names\":[\"idx\"],\"privileges\":[\"priv\"]}", b.string());
|
assertEquals("{\"names\":[\"idx\"],\"privileges\":[\"priv\"]}", b.string());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testRDJson() throws Exception {
|
public void testToString() throws Exception {
|
||||||
RoleDescriptor.IndicesPrivileges[] groups = new RoleDescriptor.IndicesPrivileges[] {
|
RoleDescriptor.IndicesPrivileges[] groups = new RoleDescriptor.IndicesPrivileges[] {
|
||||||
RoleDescriptor.IndicesPrivileges.builder()
|
RoleDescriptor.IndicesPrivileges.builder()
|
||||||
.indices(new String[] { "i1", "i2" })
|
.indices("i1", "i2")
|
||||||
.privileges(new String[] { "read" })
|
.privileges("read")
|
||||||
.fields(new String[] { "body", "title" })
|
.fields("body", "title")
|
||||||
.query(new BytesArray("{\"query\": {\"match_all\": {}}}"))
|
.query("{\"query\": {\"match_all\": {}}}")
|
||||||
.build()
|
.build()
|
||||||
};
|
};
|
||||||
RoleDescriptor d = new RoleDescriptor("test", new String[]{"all", "none"}, groups, new String[]{"sudo"});
|
RoleDescriptor descriptor = new RoleDescriptor("test", new String[] { "all", "none" }, groups, new String[] { "sudo" });
|
||||||
assertEquals("Role[name=test, cluster=[all,none], indicesPrivileges=[IndicesPrivileges[privileges=[read], indices=[i1,i2], " +
|
assertThat(descriptor.toString(), is("Role[name=test, cluster=[all,none], indicesPrivileges=[IndicesPrivileges[indices=[i1,i2], " +
|
||||||
"fields=[body,title], query={\"query\": {\"match_all\": {}}}],], runAs=[sudo]]", d.toString());
|
"privileges=[read], fields=[body,title], query={\"query\": {\"match_all\": {}}}],], runAs=[sudo]]"));
|
||||||
XContentBuilder builder = jsonBuilder();
|
|
||||||
d.toXContent(builder, ToXContent.EMPTY_PARAMS);
|
|
||||||
assertEquals("{\"name\":\"test\",\"cluster\":[\"all\",\"none\"],\"indices\":[{\"names\":[\"i1\",\"i2\"]," +
|
|
||||||
"\"privileges\":[\"read\"],\"fields\":[\"body\",\"title\"],\"query\":\"{\\\"query\\\": {\\\"match_all\\\": {}}}\"}]," +
|
|
||||||
"\"run_as\":[\"sudo\"]}",
|
|
||||||
builder.string());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testRDParsing() throws Exception {
|
public void testToXContent() throws Exception {
|
||||||
String q;
|
RoleDescriptor.IndicesPrivileges[] groups = new RoleDescriptor.IndicesPrivileges[] {
|
||||||
RoleDescriptor rd;
|
RoleDescriptor.IndicesPrivileges.builder()
|
||||||
try {
|
.indices("i1", "i2")
|
||||||
q = "{}";
|
.privileges("read")
|
||||||
rd = RoleDescriptor.source(null, new BytesArray(q));
|
.fields("body", "title")
|
||||||
fail("should have failed");
|
.query("{\"query\": {\"match_all\": {}}}")
|
||||||
} catch (ElasticsearchParseException e) {
|
.build()
|
||||||
// expected
|
};
|
||||||
}
|
RoleDescriptor descriptor = new RoleDescriptor("test", new String[] { "all", "none" }, groups, new String[] { "sudo" });
|
||||||
|
XContentBuilder builder = descriptor.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS);
|
||||||
|
RoleDescriptor parsed = RoleDescriptor.parse("test", builder.bytes());
|
||||||
|
assertThat(parsed, is(descriptor));
|
||||||
|
}
|
||||||
|
|
||||||
q = "{\"name\": \"test\", \"cluster\":[\"a\", \"b\"]}";
|
public void testParse() throws Exception {
|
||||||
rd = RoleDescriptor.source(null, new BytesArray(q));
|
|
||||||
|
String q = "{\"cluster\":[\"a\", \"b\"]}";
|
||||||
|
RoleDescriptor rd = RoleDescriptor.parse("test", new BytesArray(q));
|
||||||
assertEquals("test", rd.getName());
|
assertEquals("test", rd.getName());
|
||||||
assertArrayEquals(new String[]{"a", "b"}, rd.getClusterPrivileges());
|
assertArrayEquals(new String[] { "a", "b" }, rd.getClusterPrivileges());
|
||||||
assertEquals(0, rd.getIndicesPrivileges().length);
|
assertEquals(0, rd.getIndicesPrivileges().length);
|
||||||
assertArrayEquals(Strings.EMPTY_ARRAY, rd.getRunAs());
|
assertArrayEquals(Strings.EMPTY_ARRAY, rd.getRunAs());
|
||||||
|
|
||||||
q = "{\"name\": \"test\", \"cluster\":[\"a\", \"b\"], \"run_as\": [\"m\", \"n\"]}";
|
q = "{\"cluster\":[\"a\", \"b\"], \"run_as\": [\"m\", \"n\"]}";
|
||||||
rd = RoleDescriptor.source(null, new BytesArray(q));
|
rd = RoleDescriptor.parse("test", new BytesArray(q));
|
||||||
assertEquals("test", rd.getName());
|
assertEquals("test", rd.getName());
|
||||||
assertArrayEquals(new String[]{"a", "b"}, rd.getClusterPrivileges());
|
assertArrayEquals(new String[] { "a", "b" }, rd.getClusterPrivileges());
|
||||||
assertEquals(0, rd.getIndicesPrivileges().length);
|
assertEquals(0, rd.getIndicesPrivileges().length);
|
||||||
assertArrayEquals(new String[]{"m", "n"}, rd.getRunAs());
|
assertArrayEquals(new String[] { "m", "n" }, rd.getRunAs());
|
||||||
|
|
||||||
q = "{\"name\": \"test\", \"cluster\":[\"a\", \"b\"], \"run_as\": [\"m\", \"n\"], \"indices\": [{\"names\": \"idx1\", " +
|
q = "{\"cluster\":[\"a\", \"b\"], \"run_as\": [\"m\", \"n\"], \"indices\": [{\"names\": \"idx1\", \"privileges\": [\"p1\", " +
|
||||||
"\"privileges\": [\"p1\", \"p2\"]}, {\"names\": \"idx2\", \"privileges\": [\"p3\"], \"fields\": [\"f1\", \"f2\"]}, " +
|
"\"p2\"]}, {\"names\": \"idx2\", \"privileges\": [\"p3\"], \"fields\": [\"f1\", \"f2\"]}, {\"names\": \"idx2\", " +
|
||||||
"{\"names\": \"idx2\", \"privileges\": [\"p3\"], \"fields\": [\"f1\", \"f2\"], \"query\": \"{\\\"match_all\\\": {}}\"}]}";
|
"\"privileges\": [\"p3\"], \"fields\": [\"f1\", \"f2\"], \"query\": \"{\\\"match_all\\\": {}}\"}]}";
|
||||||
rd = RoleDescriptor.source(null, new BytesArray(q));
|
rd = RoleDescriptor.parse("test", new BytesArray(q));
|
||||||
assertEquals("test", rd.getName());
|
assertEquals("test", rd.getName());
|
||||||
assertArrayEquals(new String[]{"a", "b"}, rd.getClusterPrivileges());
|
assertArrayEquals(new String[] { "a", "b" }, rd.getClusterPrivileges());
|
||||||
assertEquals(3, rd.getIndicesPrivileges().length);
|
assertEquals(3, rd.getIndicesPrivileges().length);
|
||||||
assertArrayEquals(new String[]{"m", "n"}, rd.getRunAs());
|
assertArrayEquals(new String[] { "m", "n" }, rd.getRunAs());
|
||||||
|
|
||||||
q = "{\"name\": \"test\", \"cluster\":[\"a\", \"b\"], \"run_as\": [\"m\", \"n\"], \"indices\": [{\"names\": [\"idx1\",\"idx2\"], " +
|
q = "{\"cluster\":[\"a\", \"b\"], \"run_as\": [\"m\", \"n\"], \"indices\": [{\"names\": [\"idx1\",\"idx2\"], \"privileges\": " +
|
||||||
"\"privileges\": [\"p1\", \"p2\"]}]}";
|
"[\"p1\", \"p2\"]}]}";
|
||||||
rd = RoleDescriptor.source(null, new BytesArray(q));
|
rd = RoleDescriptor.parse("test", new BytesArray(q));
|
||||||
assertEquals("test", rd.getName());
|
assertEquals("test", rd.getName());
|
||||||
assertArrayEquals(new String[]{"a", "b"}, rd.getClusterPrivileges());
|
assertArrayEquals(new String[] { "a", "b" }, rd.getClusterPrivileges());
|
||||||
assertEquals(1, rd.getIndicesPrivileges().length);
|
assertEquals(1, rd.getIndicesPrivileges().length);
|
||||||
assertArrayEquals(new String[]{"idx1", "idx2"}, rd.getIndicesPrivileges()[0].getIndices());
|
assertArrayEquals(new String[] { "idx1", "idx2" }, rd.getIndicesPrivileges()[0].getIndices());
|
||||||
assertArrayEquals(new String[]{"m", "n"}, rd.getRunAs());
|
assertArrayEquals(new String[] { "m", "n" }, rd.getRunAs());
|
||||||
|
|
||||||
try {
|
|
||||||
q = "{\"name\": \"test\", \"cluster\":[\"a\", \"b\"], \"run_as\": [\"m\", \"n\"], \"indices\": [{\"names\": \"idx1,idx2\", " +
|
|
||||||
"\"privileges\": [\"p1\", \"p2\"]}]}";
|
|
||||||
rd = RoleDescriptor.source(null, new BytesArray(q));
|
|
||||||
fail("should have thrown a parse exception");
|
|
||||||
} catch (ElasticsearchParseException epe) {
|
|
||||||
assertTrue(epe.getMessage(),
|
|
||||||
epe.getMessage().contains("index name [idx1,idx2] may not contain ','"));
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Same, but an array of names
|
|
||||||
q = "{\"name\": \"test\", \"cluster\":[\"a\", \"b\"], \"run_as\": [\"m\", \"n\"], \"indices\": [{\"names\": [\"idx1,idx2\"], " +
|
|
||||||
"\"privileges\": [\"p1\", \"p2\"]}]}";
|
|
||||||
rd = RoleDescriptor.source(null, new BytesArray(q));
|
|
||||||
fail("should have thrown a parse exception");
|
|
||||||
} catch (ElasticsearchParseException epe) {
|
|
||||||
assertTrue(epe.getMessage(),
|
|
||||||
epe.getMessage().contains("index name [idx1,idx2] may not contain ','"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test given name matches the parsed name
|
|
||||||
q = "{\"name\": \"foo\"}";
|
|
||||||
rd = RoleDescriptor.source("foo", new BytesArray(q));
|
|
||||||
assertThat("foo", is(rd.getName()));
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Test mismatch between given name and parsed name
|
|
||||||
q = "{\"name\": \"foo\"}";
|
|
||||||
rd = RoleDescriptor.source("bar", new BytesArray(q));
|
|
||||||
fail("should have thrown a parse exception");
|
|
||||||
} catch (ElasticsearchParseException epe) {
|
|
||||||
assertTrue(epe.getMessage(),
|
|
||||||
epe.getMessage().contains("expected role name [bar] but found [foo] instead"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,12 +12,12 @@ cluster:admin/shield/realm/cache/clear
|
||||||
cluster:admin/shield/realm/cache/clear[n]
|
cluster:admin/shield/realm/cache/clear[n]
|
||||||
cluster:admin/shield/roles/cache/clear
|
cluster:admin/shield/roles/cache/clear
|
||||||
cluster:admin/shield/roles/cache/clear[n]
|
cluster:admin/shield/roles/cache/clear[n]
|
||||||
cluster:admin/shield/user/put
|
|
||||||
cluster:admin/shield/user/delete
|
|
||||||
cluster:admin/shield/user/get
|
|
||||||
cluster:admin/shield/role/put
|
cluster:admin/shield/role/put
|
||||||
cluster:admin/shield/role/delete
|
cluster:admin/shield/role/delete
|
||||||
cluster:admin/shield/role/get
|
cluster:admin/shield/role/get
|
||||||
|
cluster:admin/shield/user/put
|
||||||
|
cluster:admin/shield/user/delete
|
||||||
|
cluster:admin/shield/user/get
|
||||||
indices:admin/analyze[s]
|
indices:admin/analyze[s]
|
||||||
indices:admin/cache/clear[n]
|
indices:admin/cache/clear[n]
|
||||||
indices:admin/forcemerge[n]
|
indices:admin/forcemerge[n]
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
"parts": {
|
"parts": {
|
||||||
"name": {
|
"name": {
|
||||||
"type" : "string",
|
"type" : "string",
|
||||||
"description" : "Role Name",
|
"description" : "Role name",
|
||||||
"required" : true
|
"required" : true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
name: "admin_role"
|
name: "admin_role"
|
||||||
body: >
|
body: >
|
||||||
{
|
{
|
||||||
"name": "admin_role",
|
|
||||||
"cluster": ["all"],
|
"cluster": ["all"],
|
||||||
"indices": [
|
"indices": [
|
||||||
{
|
{
|
||||||
|
@ -44,8 +43,6 @@
|
||||||
- do:
|
- do:
|
||||||
shield.get_role:
|
shield.get_role:
|
||||||
name: "admin_role"
|
name: "admin_role"
|
||||||
- match: { found: true }
|
- match: { admin_role.cluster.0: "all" }
|
||||||
- match: { roles.0.name: "admin_role" }
|
- match: { admin_role.indices.0.names.0: "*" }
|
||||||
- match: { roles.0.cluster.0: "all" }
|
- match: { admin_role.indices.0.privileges.0: "all" }
|
||||||
- match: { roles.0.indices.0.names.0: "*" }
|
|
||||||
- match: { roles.0.indices.0.privileges.0: "all" }
|
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
name: "admin_role2"
|
name: "admin_role2"
|
||||||
body: >
|
body: >
|
||||||
{
|
{
|
||||||
"name": "admin_role2",
|
|
||||||
"cluster": ["all"],
|
"cluster": ["all"],
|
||||||
"indices": [
|
"indices": [
|
||||||
{
|
{
|
||||||
|
@ -65,9 +64,7 @@
|
||||||
- do:
|
- do:
|
||||||
shield.get_role:
|
shield.get_role:
|
||||||
name: "admin_role2"
|
name: "admin_role2"
|
||||||
- match: { found: true }
|
- match: { admin_role2.cluster.0: "all" }
|
||||||
- match: { roles.0.name: "admin_role2" }
|
- match: { admin_role2.indices.0.names.0: "foo" }
|
||||||
- match: { roles.0.cluster.0: "all" }
|
- match: { admin_role2.indices.0.names.1: "bar" }
|
||||||
- match: { roles.0.indices.0.names.0: "foo" }
|
- match: { admin_role2.indices.0.privileges.0: "all" }
|
||||||
- match: { roles.0.indices.0.names.1: "bar" }
|
|
||||||
- match: { roles.0.indices.0.privileges.0: "all" }
|
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
"Get missing role":
|
||||||
|
- do:
|
||||||
|
catch: missing
|
||||||
|
shield.get_role:
|
||||||
|
name: 'foo'
|
||||||
|
|
||||||
|
---
|
||||||
|
"Get missing (multiple) roles":
|
||||||
|
- do:
|
||||||
|
catch: missing
|
||||||
|
shield.get_role:
|
||||||
|
name: [ 'foo', 'bar' ]
|
|
@ -35,7 +35,7 @@ public class XContentUtils {
|
||||||
}
|
}
|
||||||
XContentParser.Token token = parser.nextToken();
|
XContentParser.Token token = parser.nextToken();
|
||||||
if (token != XContentParser.Token.START_OBJECT) {
|
if (token != XContentParser.Token.START_OBJECT) {
|
||||||
throw new ElasticsearchParseException("expected a user object, but found token [{}]", parser.currentToken());
|
throw new ElasticsearchParseException("expected an object, but found token [{}]", parser.currentToken());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue