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 70f8c5723e1..2f04c19ad7d 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,7 +8,6 @@ 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; @@ -18,7 +17,7 @@ import org.elasticsearch.shield.authz.RoleDescriptor; import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; +import java.util.Arrays; import java.util.List; import static org.elasticsearch.action.ValidateActions.addValidationError; @@ -29,19 +28,13 @@ import static org.elasticsearch.action.ValidateActions.addValidationError; public class PutRoleRequest extends ActionRequest implements ToXContent { private String name; - private List clusterPriv; - // List of index names to privileges - private List indices = new ArrayList<>(); - private List runAs = new ArrayList<>(); - private RoleDescriptor roleDescriptor; + private String[] clusterPrivileges; + private List indicesPrivileges = new ArrayList<>(); + private String[] runAs; public PutRoleRequest() { } - public PutRoleRequest(BytesReference source) throws Exception { - this.roleDescriptor = RoleDescriptor.source(source); - } - @Override public ActionRequestValidationException validate() { ActionRequestValidationException validationException = null; @@ -51,20 +44,24 @@ 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; } - public void cluster(String clusterPrivilege) { - this.clusterPriv = Collections.singletonList(clusterPrivilege); - } - - public void cluster(List clusterPrivileges) { - this.clusterPriv = clusterPrivileges; + public void cluster(String... clusterPrivileges) { + this.clusterPrivileges = clusterPrivileges; } public void addIndex(String[] indices, String[] privileges, @Nullable String[] fields, @Nullable BytesReference query) { - this.indices.add(RoleDescriptor.IndicesPrivileges.builder() + this.indicesPrivileges.add(RoleDescriptor.IndicesPrivileges.builder() .indices(indices) .privileges(privileges) .fields(fields) @@ -72,7 +69,7 @@ public class PutRoleRequest extends ActionRequest implements ToX .build()); } - public void runAs(List usernames) { + public void runAs(String... usernames) { this.runAs = usernames; } @@ -80,72 +77,46 @@ public class PutRoleRequest extends ActionRequest implements ToX return name; } - public List cluster() { - return clusterPriv; + public String[] cluster() { + return clusterPrivileges; } - public List indices() { - return indices; + public RoleDescriptor.IndicesPrivileges[] indices() { + return indicesPrivileges.toArray(new RoleDescriptor.IndicesPrivileges[indicesPrivileges.size()]); } - public List runAs() { + public String[] runAs() { return runAs; } private RoleDescriptor roleDescriptor() { - if (this.roleDescriptor != null) { - return this.roleDescriptor; - } - this.roleDescriptor = new RoleDescriptor(name, this.clusterPriv.toArray(Strings.EMPTY_ARRAY), - this.indices, this.runAs.toArray(Strings.EMPTY_ARRAY)); - return this.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(); - int clusterSize = in.readVInt(); - List tempCluster = new ArrayList<>(clusterSize); - for (int i = 0; i < clusterSize; i++) { - tempCluster.add(in.readString()); - } - clusterPriv = tempCluster; + clusterPrivileges = in.readOptionalStringArray(); int indicesSize = in.readVInt(); - indices = new ArrayList<>(indicesSize); + indicesPrivileges = new ArrayList<>(indicesSize); for (int i = 0; i < indicesSize; i++) { - indices.add(RoleDescriptor.IndicesPrivileges.readIndicesPrivileges(in)); - } - if (in.readBoolean()) { - int runAsSize = in.readVInt(); - runAs = new ArrayList<>(runAsSize); - for (int i = 0; i < runAsSize; i++) { - runAs.add(in.readString()); - } + indicesPrivileges.add(RoleDescriptor.IndicesPrivileges.readIndicesPrivileges(in)); } + runAs = in.readOptionalStringArray(); } @Override public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); out.writeString(name); - out.writeVInt(clusterPriv.size()); - for (String cluster : clusterPriv) { - out.writeString(cluster); - } - out.writeVInt(indices.size()); - for (RoleDescriptor.IndicesPrivileges index : indices) { + out.writeOptionalStringArray(clusterPrivileges); + out.writeVInt(indicesPrivileges.size()); + for (RoleDescriptor.IndicesPrivileges index : indicesPrivileges) { index.writeTo(out); } - if (runAs.isEmpty() == false) { - out.writeBoolean(true); - out.writeVInt(runAs.size()); - for (String runAsUser : runAs) { - out.writeString(runAsUser); - } - } else { - out.writeBoolean(false); - } + out.writeOptionalStringArray(runAs); } @Override 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 d2fcb9232d3..4f3dfdecc26 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 @@ -10,8 +10,6 @@ import org.elasticsearch.client.ElasticsearchClient; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.bytes.BytesReference; -import java.util.Arrays; - /** * Builder for requests to add a role to the administrative index */ @@ -25,18 +23,23 @@ public class PutRoleRequestBuilder extends ActionRequestBuilder indicesPrivileges; + private final String[] clusterPrivileges; + private final IndicesPrivileges[] indicesPrivileges; private final String[] runAs; - public RoleDescriptor(String name, String[] clusterPattern, - List indicesPrivileges, String[] runAs) { + public RoleDescriptor(String name, String[] clusterPrivileges, + IndicesPrivileges[] indicesPrivileges, String[] runAs) { this.name = name; - this.clusterPattern = clusterPattern; + this.clusterPrivileges = clusterPrivileges; this.indicesPrivileges = indicesPrivileges; this.runAs = runAs; } @@ -45,11 +45,11 @@ public class RoleDescriptor implements ToXContent { return this.name; } - public String[] getClusterPattern() { - return this.clusterPattern; + public String[] getClusterPrivileges() { + return this.clusterPrivileges; } - public List getIndicesPrivileges() { + public IndicesPrivileges[] getIndicesPrivileges() { return this.indicesPrivileges; } @@ -149,21 +149,28 @@ public class RoleDescriptor implements ToXContent { return tempIndices; } - public static RoleDescriptor source(BytesReference source) throws Exception { + public static RoleDescriptor source(String name, BytesReference source) throws Exception { try (XContentParser parser = XContentHelper.createParser(source)) { XContentParser.Token token; String currentFieldName = null; - String roleName = null; + String roleName = name; List indicesPrivileges = new ArrayList<>(); List runAsUsers = new ArrayList<>(); - List tempClusterPriv = 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)) { - roleName = parser.text(); + 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); } @@ -180,7 +187,7 @@ public class RoleDescriptor implements ToXContent { } else if (token == XContentParser.Token.START_ARRAY && "cluster".equals(currentFieldName)) { while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { if (token.isValue()) { - tempClusterPriv.add(parser.text()); + clusterPrivileges.add(parser.text()); } else { throw new ElasticsearchParseException("unexpected value parsing cluster privileges [{}]", token); } @@ -193,8 +200,9 @@ public class RoleDescriptor implements ToXContent { if (roleName == null) { throw new ElasticsearchParseException("field [name] required for role description"); } - return new RoleDescriptor(roleName, tempClusterPriv.toArray(Strings.EMPTY_ARRAY), - indicesPrivileges, runAsUsers.toArray(Strings.EMPTY_ARRAY)); + return new RoleDescriptor(roleName, clusterPrivileges.toArray(new String[clusterPrivileges.size()]), + indicesPrivileges.toArray(new IndicesPrivileges[indicesPrivileges.size()]), + runAsUsers.toArray(new String[runAsUsers.size()])); } } @@ -202,7 +210,7 @@ public class RoleDescriptor implements ToXContent { public String toString() { StringBuilder sb = new StringBuilder("Role["); sb.append("name=").append(name); - sb.append(", cluster=[").append(Strings.arrayToCommaDelimitedString(clusterPattern)); + sb.append(", cluster=[").append(Strings.arrayToCommaDelimitedString(clusterPrivileges)); sb.append("], indicesPrivileges=["); for (IndicesPrivileges group : indicesPrivileges) { sb.append(group.toString()).append(","); @@ -216,8 +224,8 @@ public class RoleDescriptor implements ToXContent { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(); builder.field("name", name); - builder.field("cluster", clusterPattern); - builder.field("indices", indicesPrivileges); + builder.field("cluster", clusterPrivileges); + builder.field("indices", (Object[]) indicesPrivileges); if (runAs != null) { builder.field("run_as", runAs); } @@ -236,17 +244,17 @@ public class RoleDescriptor implements ToXContent { indicesPrivileges.add(group); } String[] runAs = in.readStringArray(); - return new RoleDescriptor(name, clusterPattern, indicesPrivileges, runAs); + return new RoleDescriptor(name, clusterPattern, indicesPrivileges.toArray(new IndicesPrivileges[indicesPrivileges.size()]), runAs); } public static void writeTo(RoleDescriptor descriptor, StreamOutput out) throws IOException { - out.writeString(descriptor.getName()); - out.writeStringArray(descriptor.getClusterPattern()); - out.writeVInt(descriptor.getIndicesPrivileges().size()); - for (IndicesPrivileges group : descriptor.getIndicesPrivileges()) { + out.writeString(descriptor.name); + out.writeStringArray(descriptor.clusterPrivileges); + out.writeVInt(descriptor.indicesPrivileges.length); + for (IndicesPrivileges group : descriptor.indicesPrivileges) { group.writeTo(out); } - out.writeStringArray(descriptor.getRunAs()); + out.writeStringArray(descriptor.runAs); } public static class IndicesPrivilegesBuilder { 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 d9eda305deb..27edf613a15 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 @@ -110,7 +110,7 @@ public class ESNativeRolesStore extends AbstractComponent implements RolesStore, @Nullable private RoleDescriptor transformRole(BytesReference sourceBytes) { try { - return RoleDescriptor.source(sourceBytes); + return RoleDescriptor.source(null, sourceBytes); } catch (Exception e) { logger.warn("unable to deserialize role from response", e); return null; diff --git a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authz/permission/Role.java b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authz/permission/Role.java index 50e42aaf2f4..5858a9d9284 100644 --- a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authz/permission/Role.java +++ b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authz/permission/Role.java @@ -68,7 +68,7 @@ public class Role extends GlobalPermission { private Builder(RoleDescriptor rd) { this.name = rd.getName(); - this.cluster(ClusterPrivilege.get((new Privilege.Name(rd.getClusterPattern())))); + this.cluster(ClusterPrivilege.get((new Privilege.Name(rd.getClusterPrivileges())))); for (RoleDescriptor.IndicesPrivileges iGroup : rd.getIndicesPrivileges()) { this.add(iGroup.getFields() == null ? null : Arrays.asList(iGroup.getFields()), iGroup.getQuery(), diff --git a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/client/SecurityClient.java b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/client/SecurityClient.java index d07f5fd0228..5b270d7b6c7 100644 --- a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/client/SecurityClient.java +++ b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/client/SecurityClient.java @@ -169,6 +169,10 @@ public class SecurityClient { return new PutRoleRequestBuilder(client).name(name); } + public PutRoleRequestBuilder preparePutRole(String name, BytesReference source) throws Exception { + return new PutRoleRequestBuilder(client).source(name, source); + } + public void putRole(PutRoleRequest request, ActionListener listener) { client.execute(PutRoleAction.INSTANCE, request, listener); } diff --git a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/rest/action/role/RestPutRoleAction.java b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/rest/action/role/RestPutRoleAction.java index 34d1daa1a0b..3d662ef19d0 100644 --- a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/rest/action/role/RestPutRoleAction.java +++ b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/rest/action/role/RestPutRoleAction.java @@ -34,14 +34,15 @@ public class RestPutRoleAction extends BaseRestHandler { @Override protected void handleRequest(RestRequest request, final RestChannel channel, Client client) throws Exception { - new SecurityClient(client).preparePutRole(request.param("name")).execute(new RestBuilderListener(channel) { - @Override - public RestResponse buildResponse(PutRoleResponse putRoleResponse, XContentBuilder builder) throws Exception { - return new BytesRestResponse(RestStatus.OK, - builder.startObject() - .field("role", putRoleResponse) - .endObject()); - } - }); + new SecurityClient(client).preparePutRole(request.param("name"), request.content()).execute( + new RestBuilderListener(channel) { + @Override + public RestResponse buildResponse(PutRoleResponse putRoleResponse, XContentBuilder builder) throws Exception { + return new BytesRestResponse(RestStatus.OK, + builder.startObject() + .field("role", putRoleResponse) + .endObject()); + } + }); } } 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 6d4a923303f..82b3e0c1788 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 @@ -12,10 +12,8 @@ import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.test.ESTestCase; -import java.util.ArrayList; -import java.util.List; - import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; +import static org.hamcrest.core.Is.is; public class RoleDescriptorTests extends ESTestCase { @@ -30,13 +28,14 @@ public class RoleDescriptorTests extends ESTestCase { } public void testRDJson() throws Exception { - List groups = new ArrayList<>(); - groups.add(RoleDescriptor.IndicesPrivileges.builder() - .indices(new String[]{"i1", "i2"}) - .privileges(new String[]{"read"}) - .fields(new String[]{"body", "title"}) - .query(new BytesArray("{\"query\": {\"match_all\": {}}}")) - .build()); + 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\": {}}}")) + .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()); @@ -53,48 +52,48 @@ public class RoleDescriptorTests extends ESTestCase { RoleDescriptor rd; try { q = "{}"; - rd = RoleDescriptor.source(new BytesArray(q)); + rd = RoleDescriptor.source(null, new BytesArray(q)); fail("should have failed"); } catch (ElasticsearchParseException e) { // expected } q = "{\"name\": \"test\", \"cluster\":[\"a\", \"b\"]}"; - rd = RoleDescriptor.source(new BytesArray(q)); + rd = RoleDescriptor.source(null, new BytesArray(q)); assertEquals("test", rd.getName()); - assertArrayEquals(new String[]{"a", "b"}, rd.getClusterPattern()); - assertEquals(0, rd.getIndicesPrivileges().size()); + 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(new BytesArray(q)); + rd = RoleDescriptor.source(null, new BytesArray(q)); assertEquals("test", rd.getName()); - assertArrayEquals(new String[]{"a", "b"}, rd.getClusterPattern()); - assertEquals(0, rd.getIndicesPrivileges().size()); + assertArrayEquals(new String[]{"a", "b"}, rd.getClusterPrivileges()); + assertEquals(0, rd.getIndicesPrivileges().length); 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(new BytesArray(q)); + rd = RoleDescriptor.source(null, new BytesArray(q)); assertEquals("test", rd.getName()); - assertArrayEquals(new String[]{"a", "b"}, rd.getClusterPattern()); - assertEquals(3, rd.getIndicesPrivileges().size()); + assertArrayEquals(new String[]{"a", "b"}, rd.getClusterPrivileges()); + assertEquals(3, rd.getIndicesPrivileges().length); 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(new BytesArray(q)); + rd = RoleDescriptor.source(null, new BytesArray(q)); assertEquals("test", rd.getName()); - assertArrayEquals(new String[]{"a", "b"}, rd.getClusterPattern()); - assertEquals(1, rd.getIndicesPrivileges().size()); - assertArrayEquals(new String[]{"idx1", "idx2"}, rd.getIndicesPrivileges().get(0).getIndices()); + 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(new BytesArray(q)); + rd = RoleDescriptor.source(null, new BytesArray(q)); fail("should have thrown a parse exception"); } catch (ElasticsearchParseException epe) { assertTrue(epe.getMessage(), @@ -105,11 +104,26 @@ public class RoleDescriptorTests extends ESTestCase { // 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(new BytesArray(q)); + 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")); + } } }