From 2a1b3250dbca9b81a6252e3a1c502fb3fbe0094a Mon Sep 17 00:00:00 2001 From: uboness Date: Sat, 27 Feb 2016 00:57:46 -0800 Subject: [PATCH] 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@742f6e00203af27e0517bbca4ebd97b37abf4fa0 --- .../shield/action/role/DeleteRoleRequest.java | 22 +- .../action/role/DeleteRoleRequestBuilder.java | 4 +- .../shield/action/role/GetRolesRequest.java | 17 +- .../action/role/GetRolesRequestBuilder.java | 4 +- .../shield/action/role/GetRolesResponse.java | 29 +- .../shield/action/role/PutRoleRequest.java | 48 +- .../action/role/PutRoleRequestBuilder.java | 15 +- .../role/TransportDeleteRoleAction.java | 2 +- .../action/role/TransportGetRolesAction.java | 12 +- .../action/role/TransportPutRoleAction.java | 4 +- .../shield/authz/RoleDescriptor.java | 430 +++++++++--------- .../authz/esnative/ESNativeRolesStore.java | 264 ++++++----- .../rest/action/role/RestGetRolesAction.java | 27 +- .../integration/ClearRolesCacheTests.java | 17 +- .../shield/authc/esnative/ESNativeTests.java | 29 +- .../shield/authz/RoleDescriptorTests.java | 127 ++---- .../org/elasticsearch/transport/handlers | 6 +- .../rest-api-spec/api/shield.put_role.json | 2 +- .../rest-api-spec/test/roles/10_basic.yaml | 9 +- .../test/roles/11_idx_arrays.yaml | 11 +- .../test/roles/20_get_missing.yaml | 12 + .../xpack/common/xcontent/XContentUtils.java | 2 +- 22 files changed, 527 insertions(+), 566 deletions(-) create mode 100644 elasticsearch/x-pack/shield/src/test/resources/rest-api-spec/test/roles/20_get_missing.yaml diff --git a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/action/role/DeleteRoleRequest.java b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/action/role/DeleteRoleRequest.java index 71c55d54b51..fc542fb5c16 100644 --- a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/action/role/DeleteRoleRequest.java +++ b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/action/role/DeleteRoleRequest.java @@ -19,41 +19,37 @@ import static org.elasticsearch.action.ValidateActions.addValidationError; */ public class DeleteRoleRequest extends ActionRequest { - private String role; + private String name; public DeleteRoleRequest() { } - public DeleteRoleRequest(String roleName) { - this.role = roleName; - } - @Override public ActionRequestValidationException validate() { ActionRequestValidationException validationException = null; - if (role == null) { - validationException = addValidationError("role is missing", validationException); + if (name == null) { + validationException = addValidationError("role name is missing", validationException); } return validationException; } - public void role(String role) { - this.role = role; + public void name(String name) { + this.name = name; } - public String role() { - return role; + public String name() { + return name; } @Override public void readFrom(StreamInput in) throws IOException { super.readFrom(in); - role = in.readString(); + name = in.readString(); } @Override public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); - out.writeString(role); + out.writeString(name); } } diff --git a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/action/role/DeleteRoleRequestBuilder.java b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/action/role/DeleteRoleRequestBuilder.java index 05dad3c2be9..4eec0b7a105 100644 --- a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/action/role/DeleteRoleRequestBuilder.java +++ b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/action/role/DeleteRoleRequestBuilder.java @@ -21,8 +21,8 @@ public class DeleteRoleRequestBuilder extends ActionRequestBuilder { - private String[] roles; + private String[] names = Strings.EMPTY_ARRAY; public GetRolesRequest() { - roles = Strings.EMPTY_ARRAY; } @Override public ActionRequestValidationException validate() { ActionRequestValidationException validationException = null; - if (roles == null) { + if (names == null) { validationException = addValidationError("role is missing", validationException); } return validationException; } - public void roles(String... roles) { - this.roles = roles; + public void names(String... names) { + this.names = names; } - public String[] roles() { - return roles; + public String[] names() { + return names; } @Override public void readFrom(StreamInput in) throws IOException { super.readFrom(in); - roles = in.readStringArray(); + names = in.readStringArray(); } @Override public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); - out.writeStringArray(roles); + out.writeStringArray(names); } } diff --git a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/action/role/GetRolesRequestBuilder.java b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/action/role/GetRolesRequestBuilder.java index 5889372f67f..83cfcf4a6eb 100644 --- a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/action/role/GetRolesRequestBuilder.java +++ b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/action/role/GetRolesRequestBuilder.java @@ -21,8 +21,8 @@ public class GetRolesRequestBuilder extends ActionRequestBuilder roles; - public GetRolesResponse() { - roles = Collections.emptyList(); - } + private RoleDescriptor[] roles; - public GetRolesResponse(RoleDescriptor role) { - this.roles = Collections.singletonList(role); - } - - public GetRolesResponse(List roles) { + public GetRolesResponse(RoleDescriptor... roles) { this.roles = roles; } - public List roles() { + public RoleDescriptor[] roles() { return roles; } - public boolean isExists() { - return roles != null && roles.size() > 0; + public boolean hasRoles() { + return roles.length > 0; } @Override public void readFrom(StreamInput in) throws IOException { super.readFrom(in); int size = in.readVInt(); - roles = new ArrayList<>(size); + roles = new RoleDescriptor[size]; for (int i = 0; i < size; i++) { - roles.add(RoleDescriptor.readFrom(in)); + roles[i] = RoleDescriptor.readFrom(in); } } @Override public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); - out.writeVInt(roles.size()); + out.writeVInt(roles.length); for (RoleDescriptor role : roles) { RoleDescriptor.writeTo(role, out); } diff --git a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/action/role/PutRoleRequest.java b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/action/role/PutRoleRequest.java index 2f04c19ad7d..339049fc781 100644 --- a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/action/role/PutRoleRequest.java +++ b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/action/role/PutRoleRequest.java @@ -8,11 +8,10 @@ package org.elasticsearch.shield.action.role; import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.common.xcontent.ToXContent; -import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.shield.authz.RoleDescriptor; 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 */ -public class PutRoleRequest extends ActionRequest implements ToXContent { +public class PutRoleRequest extends ActionRequest { private String name; - private String[] clusterPrivileges; + private String[] clusterPrivileges = Strings.EMPTY_ARRAY; private List indicesPrivileges = new ArrayList<>(); - private String[] runAs; - + private String[] runAs = Strings.EMPTY_ARRAY; + public PutRoleRequest() { } @@ -44,14 +43,6 @@ public class PutRoleRequest extends ActionRequest implements ToX 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) { this.name = name; } @@ -60,6 +51,10 @@ public class PutRoleRequest extends ActionRequest implements ToX 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) { this.indicesPrivileges.add(RoleDescriptor.IndicesPrivileges.builder() .indices(indices) @@ -89,38 +84,35 @@ public class PutRoleRequest extends ActionRequest implements ToX return runAs; } - private RoleDescriptor roleDescriptor() { - return new RoleDescriptor(name, clusterPrivileges, - indicesPrivileges.toArray(new RoleDescriptor.IndicesPrivileges[indicesPrivileges.size()]), runAs); - } - @Override public void readFrom(StreamInput in) throws IOException { super.readFrom(in); name = in.readString(); - clusterPrivileges = in.readOptionalStringArray(); + clusterPrivileges = in.readStringArray(); int indicesSize = in.readVInt(); indicesPrivileges = new ArrayList<>(indicesSize); 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 public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); out.writeString(name); - out.writeOptionalStringArray(clusterPrivileges); + out.writeStringArray(clusterPrivileges); out.writeVInt(indicesPrivileges.size()); for (RoleDescriptor.IndicesPrivileges index : indicesPrivileges) { index.writeTo(out); } - out.writeOptionalStringArray(runAs); + out.writeStringArray(runAs); } - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - return this.roleDescriptor().toXContent(builder, params); + RoleDescriptor roleDescriptor() { + return new RoleDescriptor(name, + clusterPrivileges, + indicesPrivileges.toArray(new RoleDescriptor.IndicesPrivileges[indicesPrivileges.size()]), + runAs); } -} +} \ No newline at end of file diff --git a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/action/role/PutRoleRequestBuilder.java b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/action/role/PutRoleRequestBuilder.java index 4f3dfdecc26..7c251556cc9 100644 --- a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/action/role/PutRoleRequestBuilder.java +++ b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/action/role/PutRoleRequestBuilder.java @@ -9,6 +9,7 @@ import org.elasticsearch.action.ActionRequestBuilder; import org.elasticsearch.client.ElasticsearchClient; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.shield.authz.RoleDescriptor; /** * Builder for requests to add a role to the administrative index @@ -24,7 +25,11 @@ public class PutRoleRequestBuilder extends ActionRequestBuilder listener) { try { - rolesStore.removeRole(request, new ActionListener() { + rolesStore.deleteRole(request, new ActionListener() { @Override public void onResponse(Boolean found) { listener.onResponse(new DeleteRoleResponse(found)); diff --git a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/action/role/TransportGetRolesAction.java b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/action/role/TransportGetRolesAction.java index 91265806263..4f2abde5692 100644 --- a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/action/role/TransportGetRolesAction.java +++ b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/action/role/TransportGetRolesAction.java @@ -33,9 +33,9 @@ public class TransportGetRolesAction extends HandledTransportAction listener) { - if (request.roles().length == 1) { - final String rolename = request.roles()[0]; + protected void doExecute(final GetRolesRequest request, final ActionListener listener) { + if (request.names().length == 1) { + final String rolename = request.names()[0]; // We can fetch a single role with a get, much easier rolesStore.getRoleDescriptor(rolename, new ActionListener() { @Override @@ -54,16 +54,16 @@ public class TransportGetRolesAction extends HandledTransportAction>() { + rolesStore.getRoleDescriptors(request.names(), new ActionListener>() { @Override public void onResponse(List roles) { - listener.onResponse(new GetRolesResponse(roles)); + listener.onResponse(new GetRolesResponse(roles.toArray(new RoleDescriptor[roles.size()]))); } @Override public void onFailure(Throwable t) { logger.error("failed to retrieve role [{}]", t, - Strings.arrayToDelimitedString(request.roles(), ",")); + Strings.arrayToDelimitedString(request.names(), ",")); } }); } diff --git a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/action/role/TransportPutRoleAction.java b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/action/role/TransportPutRoleAction.java index 3251c8333f9..adc112d18da 100644 --- a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/action/role/TransportPutRoleAction.java +++ b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/action/role/TransportPutRoleAction.java @@ -28,8 +28,8 @@ public class TransportPutRoleAction extends HandledTransportAction listener) { - rolesStore.addRole(request, new ActionListener() { + protected void doExecute(final PutRoleRequest request, final ActionListener listener) { + rolesStore.putRole(request.roleDescriptor(), new ActionListener() { @Override public void onResponse(Boolean created) { if (created) { diff --git a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authz/RoleDescriptor.java b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authz/RoleDescriptor.java index 69bf2df6614..3e90706cf4f 100644 --- a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authz/RoleDescriptor.java +++ b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authz/RoleDescriptor.java @@ -7,6 +7,8 @@ package org.elasticsearch.shield.authz; import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.ParseFieldMatcher; import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesArray; 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.XContentHelper; import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.xpack.common.xcontent.XContentUtils; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -33,12 +37,15 @@ public class RoleDescriptor implements ToXContent { private final IndicesPrivileges[] indicesPrivileges; private final String[] runAs; - public RoleDescriptor(String name, String[] clusterPrivileges, - IndicesPrivileges[] indicesPrivileges, String[] runAs) { + public RoleDescriptor(String name, + @Nullable String[] clusterPrivileges, + @Nullable IndicesPrivileges[] indicesPrivileges, + @Nullable String[] runAs) { + this.name = name; - this.clusterPrivileges = clusterPrivileges; - this.indicesPrivileges = indicesPrivileges; - this.runAs = runAs; + this.clusterPrivileges = clusterPrivileges != null ? clusterPrivileges : Strings.EMPTY_ARRAY; + this.indicesPrivileges = indicesPrivileges != null ? indicesPrivileges : IndicesPrivileges.NONE; + this.runAs = runAs != null ? runAs : Strings.EMPTY_ARRAY; } public String getName() { @@ -57,156 +64,6 @@ public class RoleDescriptor implements ToXContent { 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 privs = new ArrayList<>(); - List 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 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 parseIndices(XContentParser parser) throws Exception { - XContentParser.Token token; - List 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 = new ArrayList<>(); - List runAsUsers = new ArrayList<>(); - List 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 public String toString() { StringBuilder sb = new StringBuilder("Role["); @@ -222,30 +79,47 @@ public class RoleDescriptor implements ToXContent { } @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 { builder.startObject(); - builder.field("name", name); - builder.field("cluster", clusterPrivileges); + builder.field("cluster", (Object[]) clusterPrivileges); builder.field("indices", (Object[]) indicesPrivileges); if (runAs != null) { builder.field("run_as", runAs); } - builder.endObject(); - return builder; + return builder.endObject(); } public static RoleDescriptor readFrom(StreamInput in) throws IOException { String name = in.readString(); - String[] clusterPattern = in.readStringArray(); + String[] clusterPrivileges = in.readStringArray(); int size = in.readVInt(); - List indicesPrivileges = new ArrayList<>(size); + IndicesPrivileges[] indicesPrivileges = new IndicesPrivileges[size]; for (int i = 0; i < size; i++) { - IndicesPrivileges group = new IndicesPrivileges(); - group.readFrom(in); - indicesPrivileges.add(group); + indicesPrivileges[i] = IndicesPrivileges.createFrom(in); } 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 { @@ -258,38 +132,103 @@ public class RoleDescriptor implements ToXContent { out.writeStringArray(descriptor.runAs); } - public static class IndicesPrivilegesBuilder { - private String[] privileges; - private String[] indices; - private String[] fields; - private BytesReference query; - - IndicesPrivilegesBuilder() { + public static RoleDescriptor parse(String name, BytesReference source) throws Exception { + assert name != null; + try (XContentParser parser = XContentHelper.createParser(source)) { + XContentParser.Token token = parser.nextToken(); // advancing to the START_OBJECT token + if (token != XContentParser.Token.START_OBJECT) { + throw new ElasticsearchParseException("failed to parse role [{}]. expected an object but found [{}] instead", name, token); + } + 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) { - this.indices = indices; - return this; + private static RoleDescriptor.IndicesPrivileges[] parseIndices(String roleName, XContentParser parser) throws Exception { + if (parser.currentToken() != XContentParser.Token.START_ARRAY) { + 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 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) { - this.privileges = privileges; - return this; + private static RoleDescriptor.IndicesPrivileges parseIndex(String roleName, XContentParser parser) throws Exception { + XContentParser.Token token = parser.currentToken(); + 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); } - - public IndicesPrivilegesBuilder fields(@Nullable String[] fields) { - this.fields = fields; - return this; + String currentFieldName = null; + String[] names = null; + String query = null; + 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); + } } - - public IndicesPrivilegesBuilder query(@Nullable BytesReference query) { - this.query = query; - return this; + if (names == null) { + throw new ElasticsearchParseException("failed to parse indices privileges for role [{}]. missing required [{}] field", + roleName, Fields.NAMES.getPreferredName()); } - - public IndicesPrivileges build() { - return new IndicesPrivileges(privileges, indices, fields, query); + if (privileges == null) { + throw new ElasticsearchParseException("failed to parse indices privileges for role [{}]. missing required [{}] field", + 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 { - private String[] privileges; + private static final IndicesPrivileges[] NONE = new IndicesPrivileges[0]; + private String[] indices; + private String[] privileges; private String[] fields; private BytesReference query; private IndicesPrivileges() { } - IndicesPrivileges(String[] privileges, String[] indices, - @Nullable String[] fields, @Nullable BytesReference query) { - 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 static Builder builder() { + return new Builder(); } public String[] getIndices() { return this.indices; } + public String[] getPrivileges() { + return this.privileges; + } + @Nullable public String[] getFields() { return this.fields; @@ -339,8 +272,8 @@ public class RoleDescriptor implements ToXContent { @Override public String toString() { 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)); if (query != null) { sb.append("], query=").append(query.toUtf8()); @@ -349,9 +282,31 @@ public class RoleDescriptor implements ToXContent { 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 public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject(); // start + builder.startObject(); builder.array("names", indices); builder.array("privileges", privileges); if (fields != null) { @@ -360,11 +315,10 @@ public class RoleDescriptor implements ToXContent { if (query != null) { builder.field("query", query.toUtf8()); } - builder.endObject(); // end start - return builder; + return builder.endObject(); } - public static IndicesPrivileges readIndicesPrivileges(StreamInput in) throws IOException { + public static IndicesPrivileges createFrom(StreamInput in) throws IOException { IndicesPrivileges ip = new IndicesPrivileges(); ip.readFrom(in); return ip; @@ -372,9 +326,9 @@ public class RoleDescriptor implements ToXContent { @Override public void readFrom(StreamInput in) throws IOException { - this.privileges = in.readStringArray(); this.indices = in.readStringArray(); this.fields = in.readOptionalStringArray(); + this.privileges = in.readStringArray(); if (in.readBoolean()) { this.query = new BytesArray(in.readByteArray()); } @@ -382,9 +336,9 @@ public class RoleDescriptor implements ToXContent { @Override public void writeTo(StreamOutput out) throws IOException { - out.writeStringArray(privileges); out.writeStringArray(indices); out.writeOptionalStringArray(fields); + out.writeStringArray(privileges); if (query != null) { out.writeBoolean(true); out.writeByteArray(query.array()); @@ -392,5 +346,57 @@ public class RoleDescriptor implements ToXContent { 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"); } } diff --git a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authz/esnative/ESNativeRolesStore.java b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authz/esnative/ESNativeRolesStore.java index 6ea964fab91..ece071259e7 100644 --- a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authz/esnative/ESNativeRolesStore.java +++ b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authz/esnative/ESNativeRolesStore.java @@ -12,7 +12,6 @@ import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.delete.DeleteResponse; import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.get.GetResponse; -import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.index.IndexResponse; import org.elasticsearch.action.search.ClearScrollRequest; import org.elasticsearch.action.search.ClearScrollResponse; @@ -40,7 +39,6 @@ import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.SearchHit; import org.elasticsearch.shield.InternalClient; 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.ClearRolesCacheResponse; 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 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 clientProvider; private final ThreadPool threadPool; @@ -99,28 +106,66 @@ public class ESNativeRolesStore extends AbstractComponent implements RolesStore, this.threadPool = threadPool; } - @Nullable - private RoleDescriptor transformRole(GetResponse response) { - if (response.isExists() == false) { - return null; + public boolean canStart(ClusterState clusterState, boolean master) { + if (state() != ESNativeRolesStore.State.INITIALIZED) { + return false; } - 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 - private RoleDescriptor transformRole(BytesReference sourceBytes) { + public void start() { 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) { - logger.warn("unable to deserialize role from response", e); - return null; + 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); + } } } /** * Retrieve a list of roles, if rolesToGet is null or empty, fetch all roles */ - public void getRoleDescriptors(String[] rolesToGet, final ActionListener> listener) { + public void getRoleDescriptors(String[] names, final ActionListener> listener) { if (state() != State.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")); @@ -129,12 +174,13 @@ public class ESNativeRolesStore extends AbstractComponent implements RolesStore, try { final List roles = new ArrayList<>(); QueryBuilder query; - if (rolesToGet == null || rolesToGet.length == 0) { - query = QueryBuilders.boolQuery().filter(QueryBuilders.termQuery("_type", INDEX_ROLE_TYPE)); + if (names == null || names.length == 0) { + query = QueryBuilders.matchAllQuery(); } 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) + .setTypes(ROLE_DOC_TYPE) .setScroll(scrollKeepAlive) .setQuery(query) .setSize(scrollSize) @@ -149,7 +195,7 @@ public class ESNativeRolesStore extends AbstractComponent implements RolesStore, boolean hasHits = resp.getHits().getHits().length > 0; if (hasHits) { for (SearchHit hit : resp.getHits().getHits()) { - RoleDescriptor rd = transformRole(hit.getSourceRef()); + RoleDescriptor rd = transformRole(hit.getId(), hit.getSourceRef()); if (rd != null) { roles.add(rd); } @@ -203,30 +249,19 @@ public class ESNativeRolesStore extends AbstractComponent implements RolesStore, listener.onResponse(roleAndVersion == null ? null : roleAndVersion.getRoleDescriptor()); } - private void executeGetRoleRequest(String role, ActionListener 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 listener) { + public void deleteRole(final DeleteRoleRequest deleteRoleRequest, final ActionListener listener) { 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); } try { DeleteRequest request = client.prepareDelete(ShieldTemplateService.SECURITY_INDEX_NAME, - INDEX_ROLE_TYPE, deleteRoleRequest.role()).request(); + ROLE_DOC_TYPE, deleteRoleRequest.name()).request(); request.indicesOptions().ignoreUnavailable(); client.delete(request, new ActionListener() { @Override public void onResponse(DeleteResponse deleteResponse) { - clearRoleCache(deleteRoleRequest.role(), listener, deleteResponse.isFound()); + clearRoleCache(deleteRoleRequest.name(), listener, deleteResponse.isFound()); } @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 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() { + @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; final AtomicReference getRef = new AtomicReference<>(null); final CountDownLatch latch = new CountDownLatch(1); @@ -288,99 +360,15 @@ public class ESNativeRolesStore extends AbstractComponent implements RolesStore, return roleAndVersion; } - public void addRole(final PutRoleRequest putRoleRequest, final ActionListener listener) { - if (state() != State.STARTED) { - logger.trace("attempted to add role before service was started"); - listener.onResponse(false); - } + private void executeGetRoleRequest(String role, ActionListener listener) { try { - IndexRequest request = client.prepareIndex(ShieldTemplateService.SECURITY_INDEX_NAME, - INDEX_ROLE_TYPE, putRoleRequest.name()) - .setSource(putRoleRequest.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS)) - .request(); - client.index(request, new ActionListener() { - @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); - } - }); + GetRequest request = client.prepareGet(ShieldTemplateService.SECURITY_INDEX_NAME, ROLE_DOC_TYPE, role).request(); + request.indicesOptions().ignoreUnavailable(); + client.get(request, listener); } catch (Exception e) { - logger.error("unable to add role", e); + logger.error("unable to retrieve role", 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 @@ -442,13 +430,22 @@ public class ESNativeRolesStore extends AbstractComponent implements RolesStore, return state.get(); } - public enum State { - INITIALIZED, - STARTING, - STARTED, - STOPPING, - STOPPED, - FAILED + @Nullable + private RoleDescriptor transformRole(GetResponse response) { + if (response.isExists() == false) { + return null; + } + return transformRole(response.getId(), response.getSourceAsBytesRef()); + } + + @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 { @@ -475,7 +472,7 @@ public class ESNativeRolesStore extends AbstractComponent implements RolesStore, try { SearchRequest request = client.prepareSearch(ShieldTemplateService.SECURITY_INDEX_NAME) .setScroll(scrollKeepAlive) - .setQuery(QueryBuilders.typeQuery(INDEX_ROLE_TYPE)) + .setQuery(QueryBuilders.typeQuery(ROLE_DOC_TYPE)) .setSize(scrollSize) .setFetchSource(true) .setVersion(true) @@ -487,7 +484,7 @@ public class ESNativeRolesStore extends AbstractComponent implements RolesStore, if (isStopped()) { return; } - for (final SearchHit hit : response.getHits().getHits()) { + for (SearchHit hit : response.getHits().getHits()) { final String roleName = hit.getId(); final long version = hit.version(); existingRoles.remove(roleName); @@ -497,7 +494,7 @@ public class ESNativeRolesStore extends AbstractComponent implements RolesStore, @Override public RoleAndVersion apply(String roleName, RoleAndVersion existing) { if (version > existing.getVersion()) { - RoleDescriptor rd = transformRole(hit.getSourceRef()); + RoleDescriptor rd = transformRole(hit.getId(), hit.getSourceRef()); if (rd != null) { return new RoleAndVersion(rd, version); } @@ -541,6 +538,7 @@ public class ESNativeRolesStore extends AbstractComponent implements RolesStore, } private static class RoleAndVersion { + private final RoleDescriptor roleDescriptor; private final Role role; private final long version; diff --git a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/rest/action/role/RestGetRolesAction.java b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/rest/action/role/RestGetRolesAction.java index 7b29552af0e..dc288a2ddc7 100644 --- a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/rest/action/role/RestGetRolesAction.java +++ b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/rest/action/role/RestGetRolesAction.java @@ -9,7 +9,6 @@ import org.elasticsearch.client.Client; import org.elasticsearch.common.Strings; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.rest.BaseRestHandler; import org.elasticsearch.rest.BytesRestResponse; @@ -21,6 +20,7 @@ import org.elasticsearch.rest.RestStatus; import org.elasticsearch.rest.action.support.RestBuilderListener; import org.elasticsearch.shield.action.role.GetRolesResponse; import org.elasticsearch.shield.client.SecurityClient; +import org.elasticsearch.shield.authz.RoleDescriptor; /** * Rest endpoint to retrieve a Role from the shield index @@ -35,20 +35,25 @@ public class RestGetRolesAction extends BaseRestHandler { } @Override - protected void handleRequest(RestRequest request, final RestChannel channel, Client client) throws Exception { - String[] names = request.paramAsStringArrayOrEmptyIfAll("name"); - - new SecurityClient(client).prepareGetRoles(names).execute(new RestBuilderListener(channel) { + protected void handleRequest(RestRequest request, RestChannel channel, Client client) throws Exception { + final String[] roles = request.paramAsStringArray("name", Strings.EMPTY_ARRAY); + new SecurityClient(client).prepareGetRoles(roles).execute(new RestBuilderListener(channel) { @Override - public RestResponse buildResponse(GetRolesResponse getRolesResponse, XContentBuilder builder) throws Exception { + public RestResponse buildResponse(GetRolesResponse response, XContentBuilder builder) throws Exception { builder.startObject(); - builder.field("found", getRolesResponse.isExists()); - builder.startArray("roles"); - for (ToXContent role : getRolesResponse.roles()) { - role.toXContent(builder, ToXContent.EMPTY_PARAMS); + for (RoleDescriptor role : response.roles()) { + builder.field(role.getName(), role); } - builder.endArray(); 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); } }); diff --git a/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/integration/ClearRolesCacheTests.java b/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/integration/ClearRolesCacheTests.java index f4629fe0ef6..0626330c0a8 100644 --- a/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/integration/ClearRolesCacheTests.java +++ b/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/integration/ClearRolesCacheTests.java @@ -33,6 +33,7 @@ import org.junit.BeforeClass; import java.util.Arrays; import java.util.List; +import static org.hamcrest.Matchers.arrayWithSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.isOneOf; import static org.hamcrest.Matchers.notNullValue; @@ -135,7 +136,7 @@ public class ClearRolesCacheTests extends ShieldIntegTestCase { logger.debug("--> modifying roles {} to have run_as", toModify); for (String role : toModify) { 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 }) .get(); assertThat(response.isCreated(), is(false)); @@ -174,17 +175,17 @@ public class ClearRolesCacheTests extends ShieldIntegTestCase { SecurityClient securityClient = securityClient(client); final String role = randomFrom(roles); - List foundRoles = securityClient.prepareGetRoles().names(role).get().roles(); - assertThat(foundRoles.size(), is(1)); + RoleDescriptor[] foundRoles = securityClient.prepareGetRoles().names(role).get().roles(); + assertThat(foundRoles.length, is(1)); logger.debug("--> deleting role [{}]", role); - DeleteResponse response = client.prepareDelete(ShieldTemplateService.SECURITY_INDEX_NAME, - ESNativeRolesStore.INDEX_ROLE_TYPE, role).get(); + DeleteResponse response = client + .prepareDelete(ShieldTemplateService.SECURITY_INDEX_NAME, ESNativeRolesStore.ROLE_DOC_TYPE, role).get(); assertThat(response.isFound(), is(true)); assertBusy(new Runnable() { @Override 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) { logger.debug("--> getting role [{}]", role); GetRolesResponse roleResponse = securityClient.prepareGetRoles().names(role).get(); - assertThat(roleResponse.isExists(), is(true)); - final String[] runAs = roleResponse.roles().get(0).getRunAs(); + assertThat(roleResponse.hasRoles(), is(true)); + final String[] runAs = roleResponse.roles()[0].getRunAs(); if (toModify.contains(role)) { 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)); diff --git a/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/shield/authc/esnative/ESNativeTests.java b/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/shield/authc/esnative/ESNativeTests.java index 3439ea73387..fa6158660e5 100644 --- a/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/shield/authc/esnative/ESNativeTests.java +++ b/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/shield/authc/esnative/ESNativeTests.java @@ -55,7 +55,7 @@ public class ESNativeTests extends ShieldIntegTestCase { GetUsersResponse resp = c.prepareGetUsers("joe").get(); assertFalse("user should not exist", resp.hasUsers()); 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 { @@ -117,8 +117,8 @@ public class ESNativeTests extends ShieldIntegTestCase { ensureGreen(ShieldTemplateService.SECURITY_INDEX_NAME); logger.info("--> retrieving role"); GetRolesResponse resp = c.prepareGetRoles().names("test_role").get(); - assertTrue("role should exist", resp.isExists()); - RoleDescriptor testRole = resp.roles().get(0); + assertTrue("role should exist", resp.hasRoles()); + RoleDescriptor testRole = resp.roles()[0]; assertNotNull(testRole); c.preparePutRole("test_role2") @@ -139,20 +139,20 @@ public class ESNativeTests extends ShieldIntegTestCase { logger.info("--> retrieving all roles"); GetRolesResponse allRolesResp = c.prepareGetRoles().get(); - assertTrue("roles should exist", allRolesResp.isExists()); - assertEquals("should be 3 roles total", 3, allRolesResp.roles().size()); + assertTrue("roles should exist", allRolesResp.hasRoles()); + assertEquals("should be 3 roles total", 3, allRolesResp.roles().length); logger.info("--> retrieving all roles"); GetRolesResponse someRolesResp = c.prepareGetRoles().names("test_role", "test_role3").get(); - assertTrue("roles should exist", someRolesResp.isExists()); - assertEquals("should be 2 roles total", 2, someRolesResp.roles().size()); + assertTrue("roles should exist", someRolesResp.hasRoles()); + assertEquals("should be 2 roles total", 2, someRolesResp.roles().length); logger.info("--> deleting role"); DeleteRoleResponse delResp = c.prepareDeleteRole("test_role").get(); assertTrue(delResp.found()); logger.info("--> retrieving role"); 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 { @@ -160,8 +160,8 @@ public class ESNativeTests extends ShieldIntegTestCase { logger.error("--> creating role"); c.preparePutRole("test_role") .cluster("all") - .addIndices(new String[]{"*"}, new String[]{"read"}, - new String[]{"body", "title"}, new BytesArray("{\"match_all\": {}}")) + .addIndices(new String[] { "*" }, new String[] { "read" }, + new String[] { "body", "title" }, new BytesArray("{\"match_all\": {}}")) .get(); logger.error("--> creating user"); c.preparePutUser("joe", "s3krit".toCharArray(), "test_role").get(); @@ -284,19 +284,20 @@ public class ESNativeTests extends ShieldIntegTestCase { } } else { 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", - 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") .cluster("none") .addIndices(new String[]{"*"}, new String[]{"read"}, new String[]{"body", "title"}, new BytesArray("{\"match_all\": {}}")) .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", - Role.builder(getRolesResponse.roles().get(0)).build().cluster().check("cluster:admin/bar")); + Role.builder(getRolesResponse.roles()[0]).build().cluster().check("cluster:admin/bar")); } } diff --git a/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/shield/authz/RoleDescriptorTests.java b/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/shield/authz/RoleDescriptorTests.java index 82b3e0c1788..d7e157e3fe9 100644 --- a/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/shield/authz/RoleDescriptorTests.java +++ b/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/shield/authz/RoleDescriptorTests.java @@ -5,7 +5,6 @@ */ package org.elasticsearch.shield.authz; -import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.xcontent.ToXContent; @@ -19,111 +18,75 @@ public class RoleDescriptorTests extends ESTestCase { public void testIndexGroup() throws Exception { RoleDescriptor.IndicesPrivileges privs = RoleDescriptor.IndicesPrivileges.builder() - .indices(new String[]{"idx"}) - .privileges(new String[]{"priv"}) + .indices("idx") + .privileges("priv") .build(); XContentBuilder b = jsonBuilder(); privs.toXContent(b, ToXContent.EMPTY_PARAMS); 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.builder() - .indices(new String[] { "i1", "i2" }) - .privileges(new String[] { "read" }) - .fields(new String[] { "body", "title" }) - .query(new BytesArray("{\"query\": {\"match_all\": {}}}")) + .indices("i1", "i2") + .privileges("read") + .fields("body", "title") + .query("{\"query\": {\"match_all\": {}}}") .build() }; - RoleDescriptor d = 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], " + - "fields=[body,title], query={\"query\": {\"match_all\": {}}}],], runAs=[sudo]]", d.toString()); - 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()); + RoleDescriptor descriptor = new RoleDescriptor("test", new String[] { "all", "none" }, groups, new String[] { "sudo" }); + assertThat(descriptor.toString(), is("Role[name=test, cluster=[all,none], indicesPrivileges=[IndicesPrivileges[indices=[i1,i2], " + + "privileges=[read], fields=[body,title], query={\"query\": {\"match_all\": {}}}],], runAs=[sudo]]")); } - public void testRDParsing() throws Exception { - String q; - RoleDescriptor rd; - try { - q = "{}"; - rd = RoleDescriptor.source(null, new BytesArray(q)); - fail("should have failed"); - } catch (ElasticsearchParseException e) { - // expected - } + public void testToXContent() throws Exception { + RoleDescriptor.IndicesPrivileges[] groups = new RoleDescriptor.IndicesPrivileges[] { + RoleDescriptor.IndicesPrivileges.builder() + .indices("i1", "i2") + .privileges("read") + .fields("body", "title") + .query("{\"query\": {\"match_all\": {}}}") + .build() + }; + 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\"]}"; - rd = RoleDescriptor.source(null, new BytesArray(q)); + public void testParse() throws Exception { + + String q = "{\"cluster\":[\"a\", \"b\"]}"; + RoleDescriptor rd = RoleDescriptor.parse("test", new BytesArray(q)); assertEquals("test", rd.getName()); - assertArrayEquals(new String[]{"a", "b"}, rd.getClusterPrivileges()); + assertArrayEquals(new String[] { "a", "b" }, rd.getClusterPrivileges()); assertEquals(0, rd.getIndicesPrivileges().length); assertArrayEquals(Strings.EMPTY_ARRAY, rd.getRunAs()); - q = "{\"name\": \"test\", \"cluster\":[\"a\", \"b\"], \"run_as\": [\"m\", \"n\"]}"; - rd = RoleDescriptor.source(null, new BytesArray(q)); + q = "{\"cluster\":[\"a\", \"b\"], \"run_as\": [\"m\", \"n\"]}"; + rd = RoleDescriptor.parse("test", new BytesArray(q)); assertEquals("test", rd.getName()); - assertArrayEquals(new String[]{"a", "b"}, rd.getClusterPrivileges()); + assertArrayEquals(new String[] { "a", "b" }, rd.getClusterPrivileges()); 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\", " + - "\"privileges\": [\"p1\", \"p2\"]}, {\"names\": \"idx2\", \"privileges\": [\"p3\"], \"fields\": [\"f1\", \"f2\"]}, " + - "{\"names\": \"idx2\", \"privileges\": [\"p3\"], \"fields\": [\"f1\", \"f2\"], \"query\": \"{\\\"match_all\\\": {}}\"}]}"; - rd = RoleDescriptor.source(null, new BytesArray(q)); + q = "{\"cluster\":[\"a\", \"b\"], \"run_as\": [\"m\", \"n\"], \"indices\": [{\"names\": \"idx1\", \"privileges\": [\"p1\", " + + "\"p2\"]}, {\"names\": \"idx2\", \"privileges\": [\"p3\"], \"fields\": [\"f1\", \"f2\"]}, {\"names\": \"idx2\", " + + "\"privileges\": [\"p3\"], \"fields\": [\"f1\", \"f2\"], \"query\": \"{\\\"match_all\\\": {}}\"}]}"; + rd = RoleDescriptor.parse("test", new BytesArray(q)); assertEquals("test", rd.getName()); - assertArrayEquals(new String[]{"a", "b"}, rd.getClusterPrivileges()); + assertArrayEquals(new String[] { "a", "b" }, rd.getClusterPrivileges()); 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\"], " + - "\"privileges\": [\"p1\", \"p2\"]}]}"; - rd = RoleDescriptor.source(null, new BytesArray(q)); + q = "{\"cluster\":[\"a\", \"b\"], \"run_as\": [\"m\", \"n\"], \"indices\": [{\"names\": [\"idx1\",\"idx2\"], \"privileges\": " + + "[\"p1\", \"p2\"]}]}"; + rd = RoleDescriptor.parse("test", new BytesArray(q)); assertEquals("test", rd.getName()); - assertArrayEquals(new String[]{"a", "b"}, rd.getClusterPrivileges()); + assertArrayEquals(new String[] { "a", "b" }, rd.getClusterPrivileges()); assertEquals(1, rd.getIndicesPrivileges().length); - assertArrayEquals(new String[]{"idx1", "idx2"}, rd.getIndicesPrivileges()[0].getIndices()); - 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")); - } + assertArrayEquals(new String[] { "idx1", "idx2" }, rd.getIndicesPrivileges()[0].getIndices()); + assertArrayEquals(new String[] { "m", "n" }, rd.getRunAs()); } } diff --git a/elasticsearch/x-pack/shield/src/test/resources/org/elasticsearch/transport/handlers b/elasticsearch/x-pack/shield/src/test/resources/org/elasticsearch/transport/handlers index f3670357bad..3282cdb2d8e 100644 --- a/elasticsearch/x-pack/shield/src/test/resources/org/elasticsearch/transport/handlers +++ b/elasticsearch/x-pack/shield/src/test/resources/org/elasticsearch/transport/handlers @@ -12,12 +12,12 @@ cluster:admin/shield/realm/cache/clear cluster:admin/shield/realm/cache/clear[n] cluster:admin/shield/roles/cache/clear 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/delete 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/cache/clear[n] indices:admin/forcemerge[n] diff --git a/elasticsearch/x-pack/shield/src/test/resources/rest-api-spec/api/shield.put_role.json b/elasticsearch/x-pack/shield/src/test/resources/rest-api-spec/api/shield.put_role.json index 66a30fa6853..a49c4f0b9d5 100644 --- a/elasticsearch/x-pack/shield/src/test/resources/rest-api-spec/api/shield.put_role.json +++ b/elasticsearch/x-pack/shield/src/test/resources/rest-api-spec/api/shield.put_role.json @@ -8,7 +8,7 @@ "parts": { "name": { "type" : "string", - "description" : "Role Name", + "description" : "Role name", "required" : true } }, diff --git a/elasticsearch/x-pack/shield/src/test/resources/rest-api-spec/test/roles/10_basic.yaml b/elasticsearch/x-pack/shield/src/test/resources/rest-api-spec/test/roles/10_basic.yaml index c99e7cc2d09..c44d6ce99fa 100644 --- a/elasticsearch/x-pack/shield/src/test/resources/rest-api-spec/test/roles/10_basic.yaml +++ b/elasticsearch/x-pack/shield/src/test/resources/rest-api-spec/test/roles/10_basic.yaml @@ -12,7 +12,6 @@ name: "admin_role" body: > { - "name": "admin_role", "cluster": ["all"], "indices": [ { @@ -44,8 +43,6 @@ - do: shield.get_role: name: "admin_role" - - match: { found: true } - - match: { roles.0.name: "admin_role" } - - match: { roles.0.cluster.0: "all" } - - match: { roles.0.indices.0.names.0: "*" } - - match: { roles.0.indices.0.privileges.0: "all" } + - match: { admin_role.cluster.0: "all" } + - match: { admin_role.indices.0.names.0: "*" } + - match: { admin_role.indices.0.privileges.0: "all" } diff --git a/elasticsearch/x-pack/shield/src/test/resources/rest-api-spec/test/roles/11_idx_arrays.yaml b/elasticsearch/x-pack/shield/src/test/resources/rest-api-spec/test/roles/11_idx_arrays.yaml index 035d51f365e..75b48468e65 100644 --- a/elasticsearch/x-pack/shield/src/test/resources/rest-api-spec/test/roles/11_idx_arrays.yaml +++ b/elasticsearch/x-pack/shield/src/test/resources/rest-api-spec/test/roles/11_idx_arrays.yaml @@ -12,7 +12,6 @@ name: "admin_role2" body: > { - "name": "admin_role2", "cluster": ["all"], "indices": [ { @@ -65,9 +64,7 @@ - do: shield.get_role: name: "admin_role2" - - match: { found: true } - - match: { roles.0.name: "admin_role2" } - - match: { roles.0.cluster.0: "all" } - - match: { roles.0.indices.0.names.0: "foo" } - - match: { roles.0.indices.0.names.1: "bar" } - - match: { roles.0.indices.0.privileges.0: "all" } + - match: { admin_role2.cluster.0: "all" } + - match: { admin_role2.indices.0.names.0: "foo" } + - match: { admin_role2.indices.0.names.1: "bar" } + - match: { admin_role2.indices.0.privileges.0: "all" } diff --git a/elasticsearch/x-pack/shield/src/test/resources/rest-api-spec/test/roles/20_get_missing.yaml b/elasticsearch/x-pack/shield/src/test/resources/rest-api-spec/test/roles/20_get_missing.yaml new file mode 100644 index 00000000000..cbfa09b8d33 --- /dev/null +++ b/elasticsearch/x-pack/shield/src/test/resources/rest-api-spec/test/roles/20_get_missing.yaml @@ -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' ] diff --git a/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/common/xcontent/XContentUtils.java b/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/common/xcontent/XContentUtils.java index 2b71c802511..0ad83294e18 100644 --- a/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/common/xcontent/XContentUtils.java +++ b/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/common/xcontent/XContentUtils.java @@ -35,7 +35,7 @@ public class XContentUtils { } XContentParser.Token token = parser.nextToken(); 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()); } }