From de72f4aeee1a0c37eebbf4b090ffeb474c73732b Mon Sep 17 00:00:00 2001 From: jaymode Date: Mon, 29 Feb 2016 08:58:02 -0500 Subject: [PATCH] security: change DLS behavior to OR queries together This commit changes the behavior of combining multiple document level security queries from an AND operation to an OR operation. Additionally, the behavior is also changed when evaluating the combination of roles that have document level security and roles that do not have document level security. Previously when the permissions for these roles were combined, the queries from the roles with document level security were still being applied, even though the user had access to all the documents. This change now grants the user access to all documents in this scenario and the same applies for field level security. Closes elastic/elasticsearch#1074 Original commit: elastic/x-pack-elasticsearch@291107ec27957a4900f75766857d9409bd5c7597 --- .../accesscontrol/IndicesAccessControl.java | 5 +- .../ShieldIndexSearcherWrapper.java | 11 +- .../DocumentAndFieldLevelSecurityTests.java | 30 +- .../DocumentLevelSecurityTests.java | 254 +++++++++-- .../integration/FieldLevelSecurityTests.java | 418 ++++++++++++++++-- .../IndicesAccessControlTests.java | 108 +++++ 6 files changed, 759 insertions(+), 67 deletions(-) create mode 100644 elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/shield/authz/accesscontrol/IndicesAccessControlTests.java diff --git a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authz/accesscontrol/IndicesAccessControl.java b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authz/accesscontrol/IndicesAccessControl.java index 1bec72f80af..0749705757e 100644 --- a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authz/accesscontrol/IndicesAccessControl.java +++ b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authz/accesscontrol/IndicesAccessControl.java @@ -94,8 +94,9 @@ public class IndicesAccessControl { // this code is a bit of a pita, but right now we can't just initialize an empty set, // because an empty Set means no permissions on fields and // null means no field level security + // Also, if one grants no access to fields and the other grants all access, merging should result in all access... Set fields = null; - if (this.fields != null || other.getFields() != null) { + if (this.fields != null && other.getFields() != null) { fields = new HashSet<>(); if (this.fields != null) { fields.addAll(this.fields); @@ -106,7 +107,7 @@ public class IndicesAccessControl { fields = unmodifiableSet(fields); } Set queries = null; - if (this.queries != null || other.getQueries() != null) { + if (this.queries != null && other.getQueries() != null) { queries = new HashSet<>(); if (this.queries != null) { queries.addAll(this.queries); diff --git a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authz/accesscontrol/ShieldIndexSearcherWrapper.java b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authz/accesscontrol/ShieldIndexSearcherWrapper.java index 8cc2410a40b..b7fc1e6b511 100644 --- a/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authz/accesscontrol/ShieldIndexSearcherWrapper.java +++ b/elasticsearch/x-pack/shield/src/main/java/org/elasticsearch/shield/authz/accesscontrol/ShieldIndexSearcherWrapper.java @@ -12,6 +12,7 @@ import org.apache.lucene.search.BulkScorer; import org.apache.lucene.search.CollectionTerminatedException; import org.apache.lucene.search.Collector; import org.apache.lucene.search.ConjunctionDISI; +import org.apache.lucene.search.ConstantScoreQuery; import org.apache.lucene.search.DocIdSetIterator; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.LeafCollector; @@ -52,7 +53,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import static org.apache.lucene.search.BooleanClause.Occur.FILTER; +import static org.apache.lucene.search.BooleanClause.Occur.SHOULD; /** * An {@link IndexSearcherWrapper} implementation that is used for field and document level security. @@ -116,13 +117,15 @@ public class ShieldIndexSearcherWrapper extends IndexSearcherWrapper { } if (permissions.getQueries() != null) { - BooleanQuery.Builder roleQuery = new BooleanQuery.Builder(); + BooleanQuery.Builder filter = new BooleanQuery.Builder(); for (BytesReference bytesReference : permissions.getQueries()) { QueryShardContext queryShardContext = copyQueryShardContext(this.queryShardContext); ParsedQuery parsedQuery = queryShardContext.parse(bytesReference); - roleQuery.add(parsedQuery.query(), FILTER); + filter.add(parsedQuery.query(), SHOULD); } - reader = DocumentSubsetReader.wrap(reader, bitsetFilterCache, roleQuery.build()); + // at least one of the queries should match + filter.setMinimumNumberShouldMatch(1); + reader = DocumentSubsetReader.wrap(reader, bitsetFilterCache, new ConstantScoreQuery(filter.build())); } if (permissions.getFields() != null) { diff --git a/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/integration/DocumentAndFieldLevelSecurityTests.java b/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/integration/DocumentAndFieldLevelSecurityTests.java index c0b24daf45b..bb685de20bf 100644 --- a/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/integration/DocumentAndFieldLevelSecurityTests.java +++ b/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/integration/DocumentAndFieldLevelSecurityTests.java @@ -35,15 +35,16 @@ public class DocumentAndFieldLevelSecurityTests extends ShieldIntegTestCase { return super.configUsers() + "user1:" + USERS_PASSWD_HASHED + "\n" + "user2:" + USERS_PASSWD_HASHED + "\n" + - "user3:" + USERS_PASSWD_HASHED + "\n" ; + "user3:" + USERS_PASSWD_HASHED + "\n" + + "user4:" + USERS_PASSWD_HASHED + "\n"; } @Override protected String configUsersRoles() { return super.configUsersRoles() + - "role1:user1\n" + - "role2:user2\n" + - "role3:user3\n"; + "role1:user1,user4\n" + + "role2:user2,user4\n" + + "role3:user3,user4\n"; } @Override @@ -107,6 +108,14 @@ public class DocumentAndFieldLevelSecurityTests extends ShieldIntegTestCase { assertSearchHits(response, "2"); assertThat(response.getHits().getAt(0).getSource().size(), equalTo(1)); assertThat(response.getHits().getAt(0).getSource().get("field2").toString(), equalTo("value2")); + + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user4", USERS_PASSWD))) + .prepareSearch("test") + .get(); + assertHitCount(response, 2); + assertSearchHits(response, "1", "2"); + assertThat(response.getHits().getAt(0).getSource().get("field1").toString(), equalTo("value1")); + assertThat(response.getHits().getAt(1).getSource().get("field2").toString(), equalTo("value2")); } public void testQueryCache() throws Exception { @@ -149,6 +158,19 @@ public class DocumentAndFieldLevelSecurityTests extends ShieldIntegTestCase { assertHitCount(response, 1); assertThat(response.getHits().getAt(0).getId(), equalTo("2")); assertThat(response.getHits().getAt(0).sourceAsMap().size(), equalTo(0)); + + // user4 has all roles + response = client().filterWithHeader( + Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user4", USERS_PASSWD))) + .prepareSearch("test") + .get(); + assertHitCount(response, 2); + assertThat(response.getHits().getAt(0).getId(), equalTo("1")); + assertThat(response.getHits().getAt(0).sourceAsMap().size(), equalTo(1)); + assertThat(response.getHits().getAt(0).sourceAsMap().get("field1"), equalTo("value1")); + assertThat(response.getHits().getAt(1).getId(), equalTo("2")); + assertThat(response.getHits().getAt(1).sourceAsMap().size(), equalTo(1)); + assertThat(response.getHits().getAt(1).sourceAsMap().get("field2"), equalTo("value2")); } } diff --git a/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/integration/DocumentLevelSecurityTests.java b/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/integration/DocumentLevelSecurityTests.java index ea658189b7a..38b6475eeaa 100644 --- a/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/integration/DocumentLevelSecurityTests.java +++ b/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/integration/DocumentLevelSecurityTests.java @@ -16,6 +16,7 @@ import org.elasticsearch.action.termvectors.TermVectorsRequest; import org.elasticsearch.action.termvectors.TermVectorsResponse; import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.indices.IndicesRequestCache; import org.elasticsearch.rest.RestStatus; @@ -56,15 +57,17 @@ public class DocumentLevelSecurityTests extends ShieldIntegTestCase { protected String configUsers() { return super.configUsers() + "user1:" + USERS_PASSWD_HASHED + "\n" + - "user2:" + USERS_PASSWD_HASHED + "\n" ; + "user2:" + USERS_PASSWD_HASHED + "\n" + + "user3:" + USERS_PASSWD_HASHED + "\n" ; } @Override protected String configUsersRoles() { return super.configUsersRoles() + - "role1:user1\n" + - "role2:user2\n"; + "role1:user1,user3\n" + + "role2:user2,user3\n"; } + @Override protected String configRoles() { return super.configRoles() + @@ -94,7 +97,7 @@ public class DocumentLevelSecurityTests extends ShieldIntegTestCase { public void testSimpleQuery() throws Exception { assertAcked(client().admin().indices().prepareCreate("test") - .addMapping("type1", "field1", "type=string", "field2", "type=string") + .addMapping("type1", "field1", "type=string", "field2", "type=string", "field3", "type=string") ); client().prepareIndex("test", "type1", "1").setSource("field1", "value1") .setRefresh(true) @@ -102,6 +105,9 @@ public class DocumentLevelSecurityTests extends ShieldIntegTestCase { client().prepareIndex("test", "type1", "2").setSource("field2", "value2") .setRefresh(true) .get(); + client().prepareIndex("test", "type1", "3").setSource("field3", "value3") + .setRefresh(true) + .get(); SearchResponse response = client() .filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user1", USERS_PASSWD))) @@ -110,24 +116,36 @@ public class DocumentLevelSecurityTests extends ShieldIntegTestCase { .get(); assertHitCount(response, 1); assertSearchHits(response, "1"); + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user2", USERS_PASSWD))) .prepareSearch("test") .setQuery(randomBoolean() ? QueryBuilders.termQuery("field2", "value2") : QueryBuilders.matchAllQuery()) .get(); assertHitCount(response, 1); assertSearchHits(response, "2"); + + QueryBuilder combined = QueryBuilders.boolQuery() + .should(QueryBuilders.termQuery("field2", "value2")) + .should(QueryBuilders.termQuery("field1", "value1")) + .minimumNumberShouldMatch(1); + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user3", USERS_PASSWD))) + .prepareSearch("test") + .setQuery(randomBoolean() ? combined : QueryBuilders.matchAllQuery()) + .get(); + assertHitCount(response, 2); + assertSearchHits(response, "1", "2"); } public void testGetApi() throws Exception { assertAcked(client().admin().indices().prepareCreate("test") - .addMapping("type1", "field1", "type=string", "field2", "type=string") + .addMapping("type1", "field1", "type=string", "field2", "type=string", "field3", "type=string") ); - client().prepareIndex("test", "type1", "1").setSource("field1", "value1") - .get(); - client().prepareIndex("test", "type1", "2").setSource("field2", "value2") - .get(); + client().prepareIndex("test", "type1", "1").setSource("field1", "value1").get(); + client().prepareIndex("test", "type1", "2").setSource("field2", "value2").get(); + client().prepareIndex("test", "type1", "3").setSource("field3", "value3").get(); + // test documents users can see Boolean realtime = randomFrom(true, false, null); GetResponse response = client() .filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user1", USERS_PASSWD))) @@ -137,6 +155,7 @@ public class DocumentLevelSecurityTests extends ShieldIntegTestCase { .get(); assertThat(response.isExists(), is(true)); assertThat(response.getId(), equalTo("1")); + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user2", USERS_PASSWD))) .prepareGet("test", "type1", "2") .setRealtime(realtime) @@ -145,6 +164,23 @@ public class DocumentLevelSecurityTests extends ShieldIntegTestCase { assertThat(response.isExists(), is(true)); assertThat(response.getId(), equalTo("2")); + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user3", USERS_PASSWD))) + .prepareGet("test", "type1", "1") + .setRealtime(realtime) + .setRefresh(true) + .get(); + assertThat(response.isExists(), is(true)); + assertThat(response.getId(), equalTo("1")); + + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user3", USERS_PASSWD))) + .prepareGet("test", "type1", "2") + .setRealtime(realtime) + .setRefresh(true) + .get(); + assertThat(response.isExists(), is(true)); + assertThat(response.getId(), equalTo("2")); + + // test documents user cannot see response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user2", USERS_PASSWD))) .prepareGet("test", "type1", "1") .setRealtime(realtime) @@ -157,17 +193,22 @@ public class DocumentLevelSecurityTests extends ShieldIntegTestCase { .setRefresh(true) .get(); assertThat(response.isExists(), is(false)); + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user3", USERS_PASSWD))) + .prepareGet("test", "type1", "3") + .setRealtime(realtime) + .setRefresh(true) + .get(); + assertThat(response.isExists(), is(false)); } public void testMGetApi() throws Exception { assertAcked(client().admin().indices().prepareCreate("test") - .addMapping("type1", "field1", "type=string", "field2", "type=string") + .addMapping("type1", "field1", "type=string", "field2", "type=string", "field3", "type=string") ); - client().prepareIndex("test", "type1", "1").setSource("field1", "value1") - .get(); - client().prepareIndex("test", "type1", "2").setSource("field2", "value2") - .get(); + client().prepareIndex("test", "type1", "1").setSource("field1", "value1").get(); + client().prepareIndex("test", "type1", "2").setSource("field2", "value2").get(); + client().prepareIndex("test", "type1", "3").setSource("field3", "value3").get(); Boolean realtime = randomFrom(true, false, null); MultiGetResponse response = client() @@ -191,6 +232,20 @@ public class DocumentLevelSecurityTests extends ShieldIntegTestCase { assertThat(response.getResponses()[0].getResponse().isExists(), is(true)); assertThat(response.getResponses()[0].getResponse().getId(), equalTo("2")); + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user3", USERS_PASSWD))) + .prepareMultiGet() + .add("test", "type1", "1") + .add("test", "type1", "2") + .setRealtime(realtime) + .setRefresh(true) + .get(); + assertThat(response.getResponses()[0].isFailed(), is(false)); + assertThat(response.getResponses()[0].getResponse().isExists(), is(true)); + assertThat(response.getResponses()[0].getResponse().getId(), equalTo("1")); + assertThat(response.getResponses()[1].isFailed(), is(false)); + assertThat(response.getResponses()[1].getResponse().isExists(), is(true)); + assertThat(response.getResponses()[1].getResponse().getId(), equalTo("2")); + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user2", USERS_PASSWD))) .prepareMultiGet() .add("test", "type1", "1") @@ -208,12 +263,22 @@ public class DocumentLevelSecurityTests extends ShieldIntegTestCase { .get(); assertThat(response.getResponses()[0].isFailed(), is(false)); assertThat(response.getResponses()[0].getResponse().isExists(), is(false)); + + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user3", USERS_PASSWD))) + .prepareMultiGet() + .add("test", "type1", "3") + .setRealtime(realtime) + .setRefresh(true) + .get(); + assertThat(response.getResponses()[0].isFailed(), is(false)); + assertThat(response.getResponses()[0].getResponse().isExists(), is(false)); } public void testTVApi() throws Exception { assertAcked(client().admin().indices().prepareCreate("test") .addMapping("type1", "field1", "type=string,term_vector=with_positions_offsets_payloads", - "field2", "type=string,term_vector=with_positions_offsets_payloads") + "field2", "type=string,term_vector=with_positions_offsets_payloads", + "field3", "type=string,term_vector=with_positions_offsets_payloads") ); client().prepareIndex("test", "type1", "1").setSource("field1", "value1") .setRefresh(true) @@ -221,6 +286,9 @@ public class DocumentLevelSecurityTests extends ShieldIntegTestCase { client().prepareIndex("test", "type1", "2").setSource("field2", "value2") .setRefresh(true) .get(); + client().prepareIndex("test", "type1", "3").setSource("field3", "value3") + .setRefresh(true) + .get(); Boolean realtime = randomFrom(true, false, null); TermVectorsResponse response = client() @@ -238,6 +306,20 @@ public class DocumentLevelSecurityTests extends ShieldIntegTestCase { assertThat(response.isExists(), is(true)); assertThat(response.getId(), is("2")); + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user3", USERS_PASSWD))) + .prepareTermVectors("test", "type1", "1") + .setRealtime(realtime) + .get(); + assertThat(response.isExists(), is(true)); + assertThat(response.getId(), is("1")); + + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user3", USERS_PASSWD))) + .prepareTermVectors("test", "type1", "2") + .setRealtime(realtime) + .get(); + assertThat(response.isExists(), is(true)); + assertThat(response.getId(), is("2")); + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user2", USERS_PASSWD))) .prepareTermVectors("test", "type1", "1") .setRealtime(realtime) @@ -249,12 +331,19 @@ public class DocumentLevelSecurityTests extends ShieldIntegTestCase { .setRealtime(realtime) .get(); assertThat(response.isExists(), is(false)); + + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user3", USERS_PASSWD))) + .prepareTermVectors("test", "type1", "3") + .setRealtime(realtime) + .get(); + assertThat(response.isExists(), is(false)); } public void testMTVApi() throws Exception { assertAcked(client().admin().indices().prepareCreate("test") .addMapping("type1", "field1", "type=string,term_vector=with_positions_offsets_payloads", - "field2", "type=string,term_vector=with_positions_offsets_payloads") + "field2", "type=string,term_vector=with_positions_offsets_payloads", + "field3", "type=string,term_vector=with_positions_offsets_payloads") ); client().prepareIndex("test", "type1", "1").setSource("field1", "value1") .setRefresh(true) @@ -262,6 +351,9 @@ public class DocumentLevelSecurityTests extends ShieldIntegTestCase { client().prepareIndex("test", "type1", "2").setSource("field2", "value2") .setRefresh(true) .get(); + client().prepareIndex("test", "type1", "3").setSource("field3", "value3") + .setRefresh(true) + .get(); Boolean realtime = randomFrom(true, false, null); MultiTermVectorsResponse response = client() @@ -281,6 +373,17 @@ public class DocumentLevelSecurityTests extends ShieldIntegTestCase { assertThat(response.getResponses()[0].getResponse().isExists(), is(true)); assertThat(response.getResponses()[0].getResponse().getId(), is("2")); + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user3", USERS_PASSWD))) + .prepareMultiTermVectors() + .add(new TermVectorsRequest("test", "type1", "1").realtime(realtime)) + .add(new TermVectorsRequest("test", "type1", "2").realtime(realtime)) + .get(); + assertThat(response.getResponses().length, equalTo(2)); + assertThat(response.getResponses()[0].getResponse().isExists(), is(true)); + assertThat(response.getResponses()[0].getResponse().getId(), is("1")); + assertThat(response.getResponses()[1].getResponse().isExists(), is(true)); + assertThat(response.getResponses()[1].getResponse().getId(), is("2")); + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user2", USERS_PASSWD))) .prepareMultiTermVectors() .add(new TermVectorsRequest("test", "type1", "1").realtime(realtime)) @@ -294,11 +397,18 @@ public class DocumentLevelSecurityTests extends ShieldIntegTestCase { .get(); assertThat(response.getResponses().length, equalTo(1)); assertThat(response.getResponses()[0].getResponse().isExists(), is(false)); + + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user3", USERS_PASSWD))) + .prepareMultiTermVectors() + .add(new TermVectorsRequest("test", "type1", "3").realtime(realtime)) + .get(); + assertThat(response.getResponses().length, equalTo(1)); + assertThat(response.getResponses()[0].getResponse().isExists(), is(false)); } public void testGlobalAggregation() throws Exception { assertAcked(client().admin().indices().prepareCreate("test") - .addMapping("type1", "field1", "type=string", "field2", "type=string") + .addMapping("type1", "field1", "type=string", "field2", "type=string", "field3", "type=string") ); client().prepareIndex("test", "type1", "1").setSource("field1", "value1") .setRefresh(true) @@ -306,15 +416,18 @@ public class DocumentLevelSecurityTests extends ShieldIntegTestCase { client().prepareIndex("test", "type1", "2").setSource("field2", "value2") .setRefresh(true) .get(); + client().prepareIndex("test", "type1", "3").setSource("field3", "value3") + .setRefresh(true) + .get(); SearchResponse response = client().prepareSearch("test") .addAggregation(AggregationBuilders.global("global").subAggregation(AggregationBuilders.terms("field2").field("field2"))) .get(); - assertHitCount(response, 2); - assertSearchHits(response, "1", "2"); + assertHitCount(response, 3); + assertSearchHits(response, "1", "2", "3"); Global globalAgg = response.getAggregations().get("global"); - assertThat(globalAgg.getDocCount(), equalTo(2L)); + assertThat(globalAgg.getDocCount(), equalTo(3L)); Terms termsAgg = globalAgg.getAggregations().get("field2"); assertThat(termsAgg.getBuckets().get(0).getKeyAsString(), equalTo("value2")); assertThat(termsAgg.getBuckets().get(0).getDocCount(), equalTo(1L)); @@ -330,6 +443,30 @@ public class DocumentLevelSecurityTests extends ShieldIntegTestCase { assertThat(globalAgg.getDocCount(), equalTo(1L)); termsAgg = globalAgg.getAggregations().get("field2"); assertThat(termsAgg.getBuckets().size(), equalTo(0)); + + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user2", USERS_PASSWD))) + .prepareSearch("test") + .addAggregation(AggregationBuilders.global("global").subAggregation(AggregationBuilders.terms("field2").field("field2"))) + .get(); + assertHitCount(response, 1); + assertSearchHits(response, "2"); + + globalAgg = response.getAggregations().get("global"); + assertThat(globalAgg.getDocCount(), equalTo(1L)); + termsAgg = globalAgg.getAggregations().get("field2"); + assertThat(termsAgg.getBuckets().size(), equalTo(1)); + + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user3", USERS_PASSWD))) + .prepareSearch("test") + .addAggregation(AggregationBuilders.global("global").subAggregation(AggregationBuilders.terms("field2").field("field2"))) + .get(); + assertHitCount(response, 2); + assertSearchHits(response, "1", "2"); + + globalAgg = response.getAggregations().get("global"); + assertThat(globalAgg.getDocCount(), equalTo(2L)); + termsAgg = globalAgg.getAggregations().get("field2"); + assertThat(termsAgg.getBuckets().size(), equalTo(1)); } public void testChildrenAggregation() throws Exception { @@ -372,34 +509,49 @@ public class DocumentLevelSecurityTests extends ShieldIntegTestCase { assertThat(children.getDocCount(), equalTo(0L)); termsAgg = children.getAggregations().get("field3"); assertThat(termsAgg.getBuckets().size(), equalTo(0)); + + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user3", USERS_PASSWD))) + .prepareSearch("test") + .setTypes("type1") + .addAggregation(AggregationBuilders.children("children", "type2") + .subAggregation(AggregationBuilders.terms("field3").field("field3"))) + .get(); + assertHitCount(response, 1); + assertSearchHits(response, "1"); + + children = response.getAggregations().get("children"); + assertThat(children.getDocCount(), equalTo(0L)); + termsAgg = children.getAggregations().get("field3"); + assertThat(termsAgg.getBuckets().size(), equalTo(0)); } public void testParentChild() { assertAcked(prepareCreate("test") .addMapping("parent") - .addMapping("child", "_parent", "type=parent", "field1", "type=string", "field2", "type=string")); + .addMapping("child", "_parent", "type=parent", "field1", "type=string", "field2", "type=string", "field3", "type=string")); ensureGreen(); // index simple data client().prepareIndex("test", "parent", "p1").setSource("field1", "value1").get(); client().prepareIndex("test", "child", "c1").setSource("field2", "value2").setParent("p1").get(); client().prepareIndex("test", "child", "c2").setSource("field2", "value2").setParent("p1").get(); + client().prepareIndex("test", "child", "c3").setSource("field3", "value3").setParent("p1").get(); refresh(); SearchResponse searchResponse = client().prepareSearch("test") .setQuery(hasChildQuery("child", matchAllQuery())) .get(); assertHitCount(searchResponse, 1L); - assertThat(searchResponse.getHits().totalHits(), equalTo(1L)); assertThat(searchResponse.getHits().getAt(0).id(), equalTo("p1")); searchResponse = client().prepareSearch("test") .setQuery(hasParentQuery("parent", matchAllQuery())) .addSort("_id", SortOrder.ASC) .get(); - assertHitCount(searchResponse, 2L); + assertHitCount(searchResponse, 3L); assertThat(searchResponse.getHits().getAt(0).id(), equalTo("c1")); assertThat(searchResponse.getHits().getAt(1).id(), equalTo("c2")); + assertThat(searchResponse.getHits().getAt(2).id(), equalTo("c3")); // Both user1 and user2 can't see field1 and field2, no parent/child query should yield results: searchResponse = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user1", USERS_PASSWD))) @@ -425,11 +577,27 @@ public class DocumentLevelSecurityTests extends ShieldIntegTestCase { .setQuery(hasParentQuery("parent", matchAllQuery())) .get(); assertHitCount(searchResponse, 0L); + + // user 3 can see them but not c3 + searchResponse = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user3", USERS_PASSWD))) + .prepareSearch("test") + .setQuery(hasChildQuery("child", matchAllQuery())) + .get(); + assertHitCount(searchResponse, 1L); + assertThat(searchResponse.getHits().getAt(0).id(), equalTo("p1")); + + searchResponse = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user3", USERS_PASSWD))) + .prepareSearch("test") + .setQuery(hasParentQuery("parent", matchAllQuery())) + .get(); + assertHitCount(searchResponse, 2L); + assertThat(searchResponse.getHits().getAt(0).id(), equalTo("c1")); + assertThat(searchResponse.getHits().getAt(1).id(), equalTo("c2")); } public void testPercolateApi() { assertAcked(client().admin().indices().prepareCreate("test") - .addMapping(".percolator", "field1", "type=string", "field2", "type=string") + .addMapping(".percolator", "field1", "type=string", "field2", "type=string", "field3", "type=string") ); client().prepareIndex("test", ".percolator", "1") .setSource("{\"query\" : { \"match_all\" : {} }, \"field1\" : \"value1\"}") @@ -446,6 +614,23 @@ public class DocumentLevelSecurityTests extends ShieldIntegTestCase { assertThat(response.getCount(), equalTo(1L)); assertThat(response.getMatches()[0].getId().string(), equalTo("1")); + response = client() + .filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user2", USERS_PASSWD))) + .preparePercolate() + .setDocumentType("type") + .setPercolateDoc(new PercolateSourceBuilder.DocBuilder().setDoc("{}")) + .get(); + assertThat(response.getCount(), equalTo(0L)); + + response = client() + .filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user3", USERS_PASSWD))) + .preparePercolate() + .setDocumentType("type") + .setPercolateDoc(new PercolateSourceBuilder.DocBuilder().setDoc("{}")) + .get(); + assertThat(response.getCount(), equalTo(1L)); + assertThat(response.getMatches()[0].getId().string(), equalTo("1")); + // Percolator with a query on a document that the current user can see. Percolator will have one query to evaluate, so there is a // match: response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user1", USERS_PASSWD))) @@ -467,6 +652,15 @@ public class DocumentLevelSecurityTests extends ShieldIntegTestCase { .get(); assertThat(response.getCount(), equalTo(0L)); + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user3", USERS_PASSWD))) + .preparePercolate() + .setDocumentType("type") + .setPercolateQuery(termQuery("field1", "value1")) + .setPercolateDoc(new PercolateSourceBuilder.DocBuilder().setDoc("{}")) + .get(); + assertThat(response.getCount(), equalTo(1L)); + assertThat(response.getMatches()[0].getId().string(), equalTo("1")); + assertAcked(client().admin().indices().prepareClose("test")); assertAcked(client().admin().indices().prepareOpen("test")); ensureGreen("test"); @@ -484,12 +678,14 @@ public class DocumentLevelSecurityTests extends ShieldIntegTestCase { public void testRequestCache() throws Exception { assertAcked(client().admin().indices().prepareCreate("test") .setSettings(Settings.builder().put(IndicesRequestCache.INDEX_CACHE_REQUEST_ENABLED_SETTING.getKey(), true)) - .addMapping("type1", "field1", "type=string", "field2", "type=string") + .addMapping("type1", "field1", "type=string", "field2", "type=string", "field3", "type=string") ); client().prepareIndex("test", "type1", "1").setSource("field1", "value1") .get(); client().prepareIndex("test", "type1", "2").setSource("field2", "value2") .get(); + client().prepareIndex("test", "type1", "3").setSource("field3", "value3") + .get(); refresh(); int max = scaledRandomIntBetween(4, 32); @@ -512,6 +708,14 @@ public class DocumentLevelSecurityTests extends ShieldIntegTestCase { .get(); assertNoFailures(response); assertHitCount(response, 0); + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user3", USERS_PASSWD))) + .prepareSearch("test") + .setSize(0) + .setQuery(termQuery("field1", "value1")) + .setRequestCache(requestCache) + .get(); + assertNoFailures(response); + assertHitCount(response, 1); } } diff --git a/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/integration/FieldLevelSecurityTests.java b/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/integration/FieldLevelSecurityTests.java index 6059b49eeb3..5ab9c61aaf5 100644 --- a/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/integration/FieldLevelSecurityTests.java +++ b/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/integration/FieldLevelSecurityTests.java @@ -45,7 +45,7 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; -// The random usage of meta fields such as _timestamp add noice to the test, so disable random index templates: +// The random usage of meta fields such as _timestamp add noise to the test, so disable random index templates: @ESIntegTestCase.ClusterScope(randomDynamicTemplates = false) public class FieldLevelSecurityTests extends ShieldIntegTestCase { @@ -60,18 +60,20 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase { "user3:" + USERS_PASSWD_HASHED + "\n" + "user4:" + USERS_PASSWD_HASHED + "\n" + "user5:" + USERS_PASSWD_HASHED + "\n" + - "user6:" + USERS_PASSWD_HASHED + "\n"; + "user6:" + USERS_PASSWD_HASHED + "\n" + + "user7:" + USERS_PASSWD_HASHED + "\n" + + "user8:" + USERS_PASSWD_HASHED + "\n"; } @Override protected String configUsersRoles() { return super.configUsersRoles() + - "role1:user1\n" + - "role2:user2\n" + - "role3:user3\n" + - "role4:user4\n" + - "role5:user5\n" + - "role5:user6\n"; + "role1:user1,user7,user8\n" + + "role2:user2,user7,user8\n" + + "role3:user3,user7\n" + + "role4:user4,user7\n" + + "role5:user5,user7\n" + + "role6:user6\n"; } @Override protected String configRoles() { @@ -109,6 +111,7 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase { "role6:\n" + " cluster: all\n" + " indices:\n" + + " '*':\n" + " privileges: ALL\n" + " fields: 'field*'\n"; } @@ -123,9 +126,9 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase { public void testQuery() throws Exception { assertAcked(client().admin().indices().prepareCreate("test") - .addMapping("type1", "field1", "type=string", "field2", "type=string") + .addMapping("type1", "field1", "type=string", "field2", "type=string", "field3", "type=string") ); - client().prepareIndex("test", "type1", "1").setSource("field1", "value1", "field2", "value2") + client().prepareIndex("test", "type1", "1").setSource("field1", "value1", "field2", "value2", "field3", "value3") .setRefresh(true) .get(); @@ -160,6 +163,18 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase { .setQuery(matchQuery("field1", "value1")) .get(); assertHitCount(response, 1); + // user7 has roles with field level security configured and without field level security + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user7", USERS_PASSWD))) + .prepareSearch("test") + .setQuery(matchQuery("field1", "value1")) + .get(); + assertHitCount(response, 1); + // user8 has roles with field level security configured for field1 and field2 + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user8", USERS_PASSWD))) + .prepareSearch("test") + .setQuery(matchQuery("field1", "value1")) + .get(); + assertHitCount(response, 1); // user1 has no access to field1, so the query should not match with the document: response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user1", USERS_PASSWD))) @@ -191,14 +206,70 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase { .setQuery(matchQuery("field2", "value2")) .get(); assertHitCount(response, 1); + // user7 has role with field level security and without field level security + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user5", USERS_PASSWD))) + .prepareSearch("test") + .setQuery(matchQuery("field2", "value2")) + .get(); + assertHitCount(response, 1); + // user8 has roles with field level security configured for field1 and field2 + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user8", USERS_PASSWD))) + .prepareSearch("test") + .setQuery(matchQuery("field2", "value2")) + .get(); + assertHitCount(response, 1); + + // user1 has access to field3, so the query should not match with the document: + response = client() + .filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user1", USERS_PASSWD))) + .prepareSearch("test") + .setQuery(matchQuery("field3", "value3")) + .get(); + assertHitCount(response, 0); + // user2 has no access to field3, so the query should not match with the document: + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user2", USERS_PASSWD))) + .prepareSearch("test") + .setQuery(matchQuery("field3", "value3")) + .get(); + assertHitCount(response, 0); + // user3 has access to field1 and field2 but not field3, so the query should not match with the document: + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user3", USERS_PASSWD))) + .prepareSearch("test") + .setQuery(matchQuery("field3", "value3")) + .get(); + assertHitCount(response, 0); + // user4 has access to no fields, so the query should not match with the document: + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user4", USERS_PASSWD))) + .prepareSearch("test") + .setQuery(matchQuery("field3", "value3")) + .get(); + assertHitCount(response, 0); + // user5 has no field level security configured, so the query should match with the document: + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user5", USERS_PASSWD))) + .prepareSearch("test") + .setQuery(matchQuery("field3", "value3")) + .get(); + assertHitCount(response, 1); + // user7 has roles with field level security and without field level security + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user5", USERS_PASSWD))) + .prepareSearch("test") + .setQuery(matchQuery("field3", "value3")) + .get(); + assertHitCount(response, 1); + // user8 has roles with field level security configured for field1 and field2 + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user8", USERS_PASSWD))) + .prepareSearch("test") + .setQuery(matchQuery("field3", "value3")) + .get(); + assertHitCount(response, 0); } public void testGetApi() throws Exception { assertAcked(client().admin().indices().prepareCreate("test") - .addMapping("type1", "field1", "type=string", "field2", "type=string") + .addMapping("type1", "field1", "type=string", "field2", "type=string", "field3", "type=string") ); - client().prepareIndex("test", "type1", "1").setSource("field1", "value1", "field2", "value2") + client().prepareIndex("test", "type1", "1").setSource("field1", "value1", "field2", "value2", "field3", "value3") .get(); Boolean realtime = randomFrom(true, false, null); @@ -250,6 +321,42 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase { .setRefresh(true) .get(); assertThat(response.isExists(), is(true)); + assertThat(response.getSource().size(), equalTo(3)); + assertThat(response.getSource().get("field1").toString(), equalTo("value1")); + assertThat(response.getSource().get("field2").toString(), equalTo("value2")); + assertThat(response.getSource().get("field3").toString(), equalTo("value3")); + + // user6 has access to field* + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user6", USERS_PASSWD))) + .prepareGet("test", "type1", "1") + .setRealtime(realtime) + .setRefresh(true) + .get(); + assertThat(response.isExists(), is(true)); + assertThat(response.getSource().size(), equalTo(3)); + assertThat(response.getSource().get("field1").toString(), equalTo("value1")); + assertThat(response.getSource().get("field2").toString(), equalTo("value2")); + assertThat(response.getSource().get("field3").toString(), equalTo("value3")); + + // user7 has roles with field level security and without field level security + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user7", USERS_PASSWD))) + .prepareGet("test", "type1", "1") + .setRealtime(realtime) + .setRefresh(true) + .get(); + assertThat(response.isExists(), is(true)); + assertThat(response.getSource().size(), equalTo(3)); + assertThat(response.getSource().get("field1").toString(), equalTo("value1")); + assertThat(response.getSource().get("field2").toString(), equalTo("value2")); + assertThat(response.getSource().get("field3").toString(), equalTo("value3")); + + // user8 has roles with field level security with access to field1 and field2 + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user8", USERS_PASSWD))) + .prepareGet("test", "type1", "1") + .setRealtime(realtime) + .setRefresh(true) + .get(); + assertThat(response.isExists(), is(true)); assertThat(response.getSource().size(), equalTo(2)); assertThat(response.getSource().get("field1").toString(), equalTo("value1")); assertThat(response.getSource().get("field2").toString(), equalTo("value2")); @@ -257,10 +364,9 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase { public void testMGetApi() throws Exception { assertAcked(client().admin().indices().prepareCreate("test") - .addMapping("type1", "field1", "type=string", "field2", "type=string") + .addMapping("type1", "field1", "type=string", "field2", "type=string", "field3", "type=string") ); - client().prepareIndex("test", "type1", "1").setSource("field1", "value1", "field2", "value2") - .get(); + client().prepareIndex("test", "type1", "1").setSource("field1", "value1", "field2", "value2", "field3", "value3").get(); Boolean realtime = randomFrom(true, false, null); // user1 is granted access to field1 only: @@ -321,6 +427,48 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase { .get(); assertThat(response.getResponses()[0].isFailed(), is(false)); assertThat(response.getResponses()[0].getResponse().isExists(), is(true)); + assertThat(response.getResponses()[0].getResponse().getSource().size(), equalTo(3)); + assertThat(response.getResponses()[0].getResponse().getSource().get("field1").toString(), equalTo("value1")); + assertThat(response.getResponses()[0].getResponse().getSource().get("field2").toString(), equalTo("value2")); + assertThat(response.getResponses()[0].getResponse().getSource().get("field3").toString(), equalTo("value3")); + + // user6 has access to field* + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user6", USERS_PASSWD))) + .prepareMultiGet() + .add("test", "type1", "1") + .setRealtime(realtime) + .setRefresh(true) + .get(); + assertThat(response.getResponses()[0].isFailed(), is(false)); + assertThat(response.getResponses()[0].getResponse().isExists(), is(true)); + assertThat(response.getResponses()[0].getResponse().getSource().size(), equalTo(3)); + assertThat(response.getResponses()[0].getResponse().getSource().get("field1").toString(), equalTo("value1")); + assertThat(response.getResponses()[0].getResponse().getSource().get("field2").toString(), equalTo("value2")); + assertThat(response.getResponses()[0].getResponse().getSource().get("field3").toString(), equalTo("value3")); + + // user7 has roles with field level security and without field level security + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user7", USERS_PASSWD))) + .prepareMultiGet() + .add("test", "type1", "1") + .setRealtime(realtime) + .setRefresh(true) + .get(); + assertThat(response.getResponses()[0].isFailed(), is(false)); + assertThat(response.getResponses()[0].getResponse().isExists(), is(true)); + assertThat(response.getResponses()[0].getResponse().getSource().size(), equalTo(3)); + assertThat(response.getResponses()[0].getResponse().getSource().get("field1").toString(), equalTo("value1")); + assertThat(response.getResponses()[0].getResponse().getSource().get("field2").toString(), equalTo("value2")); + assertThat(response.getResponses()[0].getResponse().getSource().get("field3").toString(), equalTo("value3")); + + // user8 has roles with field level security with access to field1 and field2 + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user8", USERS_PASSWD))) + .prepareMultiGet() + .add("test", "type1", "1") + .setRealtime(realtime) + .setRefresh(true) + .get(); + assertThat(response.getResponses()[0].isFailed(), is(false)); + assertThat(response.getResponses()[0].getResponse().isExists(), is(true)); assertThat(response.getResponses()[0].getResponse().getSource().size(), equalTo(2)); assertThat(response.getResponses()[0].getResponse().getSource().get("field1").toString(), equalTo("value1")); assertThat(response.getResponses()[0].getResponse().getSource().get("field2").toString(), equalTo("value2")); @@ -328,9 +476,9 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase { public void testFieldStatsApi() throws Exception { assertAcked(client().admin().indices().prepareCreate("test") - .addMapping("type1", "field1", "type=string", "field2", "type=string") + .addMapping("type1", "field1", "type=string", "field2", "type=string", "field3", "type=string") ); - client().prepareIndex("test", "type1", "1").setSource("field1", "value1", "field2", "value2") + client().prepareIndex("test", "type1", "1").setSource("field1", "value1", "field2", "value2", "field3", "value3") .setRefresh(true) .get(); @@ -338,7 +486,7 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase { FieldStatsResponse response = client() .filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user1", USERS_PASSWD))) .prepareFieldStats() - .setFields("field1", "field2") + .setFields("field1", "field2", "field3") .get(); assertThat(response.getAllFieldStats().size(), equalTo(1)); assertThat(response.getAllFieldStats().get("field1").getDocCount(), equalTo(1L)); @@ -346,7 +494,7 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase { // user2 is granted access to field2 only: response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user2", USERS_PASSWD))) .prepareFieldStats() - .setFields("field1", "field2") + .setFields("field1", "field2", "field3") .get(); assertThat(response.getAllFieldStats().size(), equalTo(1)); assertThat(response.getAllFieldStats().get("field2").getDocCount(), equalTo(1L)); @@ -354,7 +502,7 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase { // user3 is granted access to field1 and field2: response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user3", USERS_PASSWD))) .prepareFieldStats() - .setFields("field1", "field2") + .setFields("field1", "field2", "field3") .get(); assertThat(response.getAllFieldStats().size(), equalTo(2)); assertThat(response.getAllFieldStats().get("field1").getDocCount(), equalTo(1L)); @@ -370,7 +518,37 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase { // user5 has no field level security configured: response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user5", USERS_PASSWD))) .prepareFieldStats() - .setFields("field1", "field2") + .setFields("field1", "field2", "field3") + .get(); + assertThat(response.getAllFieldStats().size(), equalTo(3)); + assertThat(response.getAllFieldStats().get("field1").getDocCount(), equalTo(1L)); + assertThat(response.getAllFieldStats().get("field2").getDocCount(), equalTo(1L)); + assertThat(response.getAllFieldStats().get("field3").getDocCount(), equalTo(1L)); + + // user6 has field level security configured for field*: + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user6", USERS_PASSWD))) + .prepareFieldStats() + .setFields("field1", "field2", "field3") + .get(); + assertThat(response.getAllFieldStats().size(), equalTo(3)); + assertThat(response.getAllFieldStats().get("field1").getDocCount(), equalTo(1L)); + assertThat(response.getAllFieldStats().get("field2").getDocCount(), equalTo(1L)); + assertThat(response.getAllFieldStats().get("field3").getDocCount(), equalTo(1L)); + + // user7 has no field level security configured (roles with and without field level security): + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user7", USERS_PASSWD))) + .prepareFieldStats() + .setFields("field1", "field2", "field3") + .get(); + assertThat(response.getAllFieldStats().size(), equalTo(3)); + assertThat(response.getAllFieldStats().get("field1").getDocCount(), equalTo(1L)); + assertThat(response.getAllFieldStats().get("field2").getDocCount(), equalTo(1L)); + assertThat(response.getAllFieldStats().get("field3").getDocCount(), equalTo(1L)); + + // user8 has field level security configured for field1 and field2 (multiple roles): + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user8", USERS_PASSWD))) + .prepareFieldStats() + .setFields("field1", "field2", "field3") .get(); assertThat(response.getAllFieldStats().size(), equalTo(2)); assertThat(response.getAllFieldStats().get("field1").getDocCount(), equalTo(1L)); @@ -380,9 +558,9 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase { public void testQueryCache() throws Exception { assertAcked(client().admin().indices().prepareCreate("test") .setSettings(Settings.builder().put(IndexModule.INDEX_QUERY_CACHE_EVERYTHING_SETTING.getKey(), true)) - .addMapping("type1", "field1", "type=string", "field2", "type=string") + .addMapping("type1", "field1", "type=string", "field2", "type=string", "field3", "type=string") ); - client().prepareIndex("test", "type1", "1").setSource("field1", "value1", "field2", "value2") + client().prepareIndex("test", "type1", "1").setSource("field1", "value1", "field2", "value2", "field3", "value3") .setRefresh(true) .get(); @@ -394,18 +572,31 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase { .setQuery(constantScoreQuery(termQuery("field1", "value1"))) .get(); assertHitCount(response, 1); + assertThat(response.getHits().getAt(0).getSource().size(), is(1)); + assertThat(response.getHits().getAt(0).getSource().get("field1"), is("value1")); response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user2", USERS_PASSWD))) .prepareSearch("test") .setQuery(constantScoreQuery(termQuery("field1", "value1"))) .get(); assertHitCount(response, 0); + String multipleFieldsUser = randomFrom("user5", "user6", "user7"); + response = client() + .filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue(multipleFieldsUser, USERS_PASSWD))) + .prepareSearch("test") + .setQuery(constantScoreQuery(termQuery("field1", "value1"))) + .get(); + assertHitCount(response, 1); + assertThat(response.getHits().getAt(0).getSource().size(), is(3)); + assertThat(response.getHits().getAt(0).getSource().get("field1"), is("value1")); + assertThat(response.getHits().getAt(0).getSource().get("field2"), is("value2")); + assertThat(response.getHits().getAt(0).getSource().get("field3"), is("value3")); } } public void testRequestCache() throws Exception { assertAcked(client().admin().indices().prepareCreate("test") .setSettings(Settings.builder().put(IndicesRequestCache.INDEX_CACHE_REQUEST_ENABLED_SETTING.getKey(), true)) - .addMapping("type1", "field1", "type=string", "field2", "type=string") + .addMapping("type1", "field1", "type=string", "field2", "type=string", "field3", "type=string") ); client().prepareIndex("test", "type1", "1").setSource("field1", "value1", "field2", "value2") .setRefresh(true) @@ -431,14 +622,25 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase { .get(); assertNoFailures(response); assertHitCount(response, 0); + String multipleFieldsUser = randomFrom("user5", "user6", "user7"); + response = client() + .filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue(multipleFieldsUser, USERS_PASSWD))) + .prepareSearch("test") + .setSize(0) + .setQuery(termQuery("field1", "value1")) + .setRequestCache(requestCache) + .get(); + assertNoFailures(response); + assertHitCount(response, 1); } } public void testFields() throws Exception { assertAcked(client().admin().indices().prepareCreate("test") - .addMapping("type1", "field1", "type=string,store=true", "field2", "type=string,store=true") + .addMapping("type1", "field1", "type=string,store=true", "field2", "type=string,store=true", + "field3", "type=string,store=true") ); - client().prepareIndex("test", "type1", "1").setSource("field1", "value1", "field2", "value2") + client().prepareIndex("test", "type1", "1").setSource("field1", "value1", "field2", "value2", "field3", "value3") .setRefresh(true) .get(); @@ -448,6 +650,7 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase { .prepareSearch("test") .addField("field1") .addField("field2") + .addField("field3") .get(); assertThat(response.getHits().getAt(0).fields().size(), equalTo(1)); assertThat(response.getHits().getAt(0).fields().get("field1").getValue(), equalTo("value1")); @@ -457,6 +660,7 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase { .prepareSearch("test") .addField("field1") .addField("field2") + .addField("field3") .get(); assertThat(response.getHits().getAt(0).fields().size(), equalTo(1)); assertThat(response.getHits().getAt(0).fields().get("field2").getValue(), equalTo("value2")); @@ -466,6 +670,7 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase { .prepareSearch("test") .addField("field1") .addField("field2") + .addField("field3") .get(); assertThat(response.getHits().getAt(0).fields().size(), equalTo(2)); assertThat(response.getHits().getAt(0).fields().get("field1").getValue(), equalTo("value1")); @@ -476,6 +681,7 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase { .prepareSearch("test") .addField("field1") .addField("field2") + .addField("field3") .get(); assertThat(response.getHits().getAt(0).fields().size(), equalTo(0)); @@ -484,6 +690,43 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase { .prepareSearch("test") .addField("field1") .addField("field2") + .addField("field3") + .get(); + assertThat(response.getHits().getAt(0).fields().size(), equalTo(3)); + assertThat(response.getHits().getAt(0).fields().get("field1").getValue(), equalTo("value1")); + assertThat(response.getHits().getAt(0).fields().get("field2").getValue(), equalTo("value2")); + assertThat(response.getHits().getAt(0).fields().get("field3").getValue(), equalTo("value3")); + + // user6 has field level security configured with access to field*: + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user6", USERS_PASSWD))) + .prepareSearch("test") + .addField("field1") + .addField("field2") + .addField("field3") + .get(); + assertThat(response.getHits().getAt(0).fields().size(), equalTo(3)); + assertThat(response.getHits().getAt(0).fields().get("field1").getValue(), equalTo("value1")); + assertThat(response.getHits().getAt(0).fields().get("field2").getValue(), equalTo("value2")); + assertThat(response.getHits().getAt(0).fields().get("field3").getValue(), equalTo("value3")); + + // user7 has access to all fields due to a mix of roles without field level security and with: + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user7", USERS_PASSWD))) + .prepareSearch("test") + .addField("field1") + .addField("field2") + .addField("field3") + .get(); + assertThat(response.getHits().getAt(0).fields().size(), equalTo(3)); + assertThat(response.getHits().getAt(0).fields().get("field1").getValue(), equalTo("value1")); + assertThat(response.getHits().getAt(0).fields().get("field2").getValue(), equalTo("value2")); + assertThat(response.getHits().getAt(0).fields().get("field3").getValue(), equalTo("value3")); + + // user8 has field level security configured with access to field1 and field2: + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user8", USERS_PASSWD))) + .prepareSearch("test") + .addField("field1") + .addField("field2") + .addField("field3") .get(); assertThat(response.getHits().getAt(0).fields().size(), equalTo(2)); assertThat(response.getHits().getAt(0).fields().get("field1").getValue(), equalTo("value1")); @@ -492,9 +735,9 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase { public void testSource() throws Exception { assertAcked(client().admin().indices().prepareCreate("test") - .addMapping("type1", "field1", "type=string", "field2", "type=string") + .addMapping("type1", "field1", "type=string", "field2", "type=string", "field3", "type=string") ); - client().prepareIndex("test", "type1", "1").setSource("field1", "value1", "field2", "value2") + client().prepareIndex("test", "type1", "1").setSource("field1", "value1", "field2", "value2", "field3", "value3") .setRefresh(true) .get(); @@ -531,6 +774,33 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase { response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user5", USERS_PASSWD))) .prepareSearch("test") .get(); + assertThat(response.getHits().getAt(0).sourceAsMap().size(), equalTo(3)); + assertThat(response.getHits().getAt(0).sourceAsMap().get("field1").toString(), equalTo("value1")); + assertThat(response.getHits().getAt(0).sourceAsMap().get("field2").toString(), equalTo("value2")); + assertThat(response.getHits().getAt(0).sourceAsMap().get("field3").toString(), equalTo("value3")); + + // user6 has field level security configured with access to field*: + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user6", USERS_PASSWD))) + .prepareSearch("test") + .get(); + assertThat(response.getHits().getAt(0).sourceAsMap().size(), equalTo(3)); + assertThat(response.getHits().getAt(0).sourceAsMap().get("field1").toString(), equalTo("value1")); + assertThat(response.getHits().getAt(0).sourceAsMap().get("field2").toString(), equalTo("value2")); + assertThat(response.getHits().getAt(0).sourceAsMap().get("field3").toString(), equalTo("value3")); + + // user7 has access to all fields + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user7", USERS_PASSWD))) + .prepareSearch("test") + .get(); + assertThat(response.getHits().getAt(0).sourceAsMap().size(), equalTo(3)); + assertThat(response.getHits().getAt(0).sourceAsMap().get("field1").toString(), equalTo("value1")); + assertThat(response.getHits().getAt(0).sourceAsMap().get("field2").toString(), equalTo("value2")); + assertThat(response.getHits().getAt(0).sourceAsMap().get("field3").toString(), equalTo("value3")); + + // user8 has field level security configured with access to field1 and field2: + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user8", USERS_PASSWD))) + .prepareSearch("test") + .get(); assertThat(response.getHits().getAt(0).sourceAsMap().size(), equalTo(2)); assertThat(response.getHits().getAt(0).sourceAsMap().get("field1").toString(), equalTo("value1")); assertThat(response.getHits().getAt(0).sourceAsMap().get("field2").toString(), equalTo("value2")); @@ -616,9 +886,10 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase { public void testTVApi() throws Exception { assertAcked(client().admin().indices().prepareCreate("test") .addMapping("type1", "field1", "type=string,term_vector=with_positions_offsets_payloads", - "field2", "type=string,term_vector=with_positions_offsets_payloads") + "field2", "type=string,term_vector=with_positions_offsets_payloads", + "field3", "type=string,term_vector=with_positions_offsets_payloads") ); - client().prepareIndex("test", "type1", "1").setSource("field1", "value1", "field2", "value2") + client().prepareIndex("test", "type1", "1").setSource("field1", "value1", "field2", "value2", "field3", "value3") .setRefresh(true) .get(); @@ -655,14 +926,54 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase { .get(); assertThat(response.isExists(), is(true)); assertThat(response.getFields().size(), equalTo(0)); + + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user5", USERS_PASSWD))) + .prepareTermVectors("test", "type1", "1") + .setRealtime(realtime) + .get(); + assertThat(response.isExists(), is(true)); + assertThat(response.getFields().size(), equalTo(3)); + assertThat(response.getFields().terms("field1").size(), equalTo(1L)); + assertThat(response.getFields().terms("field2").size(), equalTo(1L)); + assertThat(response.getFields().terms("field3").size(), equalTo(1L)); + + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user6", USERS_PASSWD))) + .prepareTermVectors("test", "type1", "1") + .setRealtime(realtime) + .get(); + assertThat(response.isExists(), is(true)); + assertThat(response.getFields().size(), equalTo(3)); + assertThat(response.getFields().terms("field1").size(), equalTo(1L)); + assertThat(response.getFields().terms("field2").size(), equalTo(1L)); + assertThat(response.getFields().terms("field3").size(), equalTo(1L)); + + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user7", USERS_PASSWD))) + .prepareTermVectors("test", "type1", "1") + .setRealtime(realtime) + .get(); + assertThat(response.isExists(), is(true)); + assertThat(response.getFields().size(), equalTo(3)); + assertThat(response.getFields().terms("field1").size(), equalTo(1L)); + assertThat(response.getFields().terms("field2").size(), equalTo(1L)); + assertThat(response.getFields().terms("field3").size(), equalTo(1L)); + + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user8", USERS_PASSWD))) + .prepareTermVectors("test", "type1", "1") + .setRealtime(realtime) + .get(); + assertThat(response.isExists(), is(true)); + assertThat(response.getFields().size(), equalTo(2)); + assertThat(response.getFields().terms("field1").size(), equalTo(1L)); + assertThat(response.getFields().terms("field2").size(), equalTo(1L)); } public void testMTVApi() throws Exception { assertAcked(client().admin().indices().prepareCreate("test") .addMapping("type1", "field1", "type=string,term_vector=with_positions_offsets_payloads", - "field2", "type=string,term_vector=with_positions_offsets_payloads") + "field2", "type=string,term_vector=with_positions_offsets_payloads", + "field3", "type=string,term_vector=with_positions_offsets_payloads") ); - client().prepareIndex("test", "type1", "1").setSource("field1", "value1", "field2", "value2") + client().prepareIndex("test", "type1", "1").setSource("field1", "value1", "field2", "value2", "field3", "value3") .setRefresh(true) .get(); @@ -703,6 +1014,49 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase { assertThat(response.getResponses().length, equalTo(1)); assertThat(response.getResponses()[0].getResponse().isExists(), is(true)); assertThat(response.getResponses()[0].getResponse().getFields().size(), equalTo(0)); + + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user5", USERS_PASSWD))) + .prepareMultiTermVectors() + .add(new TermVectorsRequest("test", "type1", "1").realtime(realtime)) + .get(); + assertThat(response.getResponses().length, equalTo(1)); + assertThat(response.getResponses()[0].getResponse().isExists(), is(true)); + assertThat(response.getResponses()[0].getResponse().getFields().size(), equalTo(3)); + assertThat(response.getResponses()[0].getResponse().getFields().terms("field1").size(), equalTo(1L)); + assertThat(response.getResponses()[0].getResponse().getFields().terms("field2").size(), equalTo(1L)); + assertThat(response.getResponses()[0].getResponse().getFields().terms("field3").size(), equalTo(1L)); + + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user6", USERS_PASSWD))) + .prepareMultiTermVectors() + .add(new TermVectorsRequest("test", "type1", "1").realtime(realtime)) + .get(); + assertThat(response.getResponses().length, equalTo(1)); + assertThat(response.getResponses()[0].getResponse().isExists(), is(true)); + assertThat(response.getResponses()[0].getResponse().getFields().size(), equalTo(3)); + assertThat(response.getResponses()[0].getResponse().getFields().terms("field1").size(), equalTo(1L)); + assertThat(response.getResponses()[0].getResponse().getFields().terms("field2").size(), equalTo(1L)); + assertThat(response.getResponses()[0].getResponse().getFields().terms("field3").size(), equalTo(1L)); + + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user7", USERS_PASSWD))) + .prepareMultiTermVectors() + .add(new TermVectorsRequest("test", "type1", "1").realtime(realtime)) + .get(); + assertThat(response.getResponses().length, equalTo(1)); + assertThat(response.getResponses()[0].getResponse().isExists(), is(true)); + assertThat(response.getResponses()[0].getResponse().getFields().size(), equalTo(3)); + assertThat(response.getResponses()[0].getResponse().getFields().terms("field1").size(), equalTo(1L)); + assertThat(response.getResponses()[0].getResponse().getFields().terms("field2").size(), equalTo(1L)); + assertThat(response.getResponses()[0].getResponse().getFields().terms("field3").size(), equalTo(1L)); + + response = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user8", USERS_PASSWD))) + .prepareMultiTermVectors() + .add(new TermVectorsRequest("test", "type1", "1").realtime(realtime)) + .get(); + assertThat(response.getResponses().length, equalTo(1)); + assertThat(response.getResponses()[0].getResponse().isExists(), is(true)); + assertThat(response.getResponses()[0].getResponse().getFields().size(), equalTo(2)); + assertThat(response.getResponses()[0].getResponse().getFields().terms("field1").size(), equalTo(1L)); + assertThat(response.getResponses()[0].getResponse().getFields().terms("field2").size(), equalTo(1L)); } public void testPercolateApi() { diff --git a/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/shield/authz/accesscontrol/IndicesAccessControlTests.java b/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/shield/authz/accesscontrol/IndicesAccessControlTests.java new file mode 100644 index 00000000000..35410ac80c5 --- /dev/null +++ b/elasticsearch/x-pack/shield/src/test/java/org/elasticsearch/shield/authz/accesscontrol/IndicesAccessControlTests.java @@ -0,0 +1,108 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.shield.authz.accesscontrol; + +import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.util.set.Sets; +import org.elasticsearch.test.ESTestCase; + +import java.util.Collections; +import org.elasticsearch.shield.authz.accesscontrol.IndicesAccessControl.IndexAccessControl; + +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; + +/** + * Unit tests for {@link IndicesAccessControl} + */ +public class IndicesAccessControlTests extends ESTestCase { + + public void testEmptyIndicesAccessControl() { + IndicesAccessControl indicesAccessControl = new IndicesAccessControl(true, Collections.emptyMap()); + assertThat(indicesAccessControl.isGranted(), is(true)); + assertThat(indicesAccessControl.getIndexPermissions(randomAsciiOfLengthBetween(3,20)), nullValue()); + } + + public void testMergeFields() { + IndexAccessControl indexAccessControl = new IndexAccessControl(true, Sets.newHashSet("a", "c"), null); + IndexAccessControl other = new IndexAccessControl(true, Sets.newHashSet("b"), null); + + IndexAccessControl merge1 = indexAccessControl.merge(other); + assertThat(merge1.getFields(), containsInAnyOrder("a", "b", "c")); + assertThat(merge1.isGranted(), is(true)); + assertThat(merge1.getQueries(), nullValue()); + + IndexAccessControl merge2 = other.merge(indexAccessControl); + assertThat(merge2.getFields(), containsInAnyOrder("a", "b", "c")); + assertThat(merge2.isGranted(), is(true)); + assertThat(merge2.getQueries(), nullValue()); + } + + public void testMergeEmptyAndNullFields() { + IndexAccessControl indexAccessControl = new IndexAccessControl(true, Collections.emptySet(), null); + IndexAccessControl other = new IndexAccessControl(true, null, null); + + IndexAccessControl merge1 = indexAccessControl.merge(other); + assertThat(merge1.getFields(), nullValue()); + assertThat(merge1.isGranted(), is(true)); + assertThat(merge1.getQueries(), nullValue()); + + IndexAccessControl merge2 = other.merge(indexAccessControl); + assertThat(merge2.getFields(), nullValue()); + assertThat(merge2.isGranted(), is(true)); + assertThat(merge2.getQueries(), nullValue()); + } + + public void testMergeNullFields() { + IndexAccessControl indexAccessControl = new IndexAccessControl(true, Sets.newHashSet("a", "b"), null); + IndexAccessControl other = new IndexAccessControl(true, null, null); + + IndexAccessControl merge1 = indexAccessControl.merge(other); + assertThat(merge1.getFields(), nullValue()); + assertThat(merge1.isGranted(), is(true)); + assertThat(merge1.getQueries(), nullValue()); + + IndexAccessControl merge2 = other.merge(indexAccessControl); + assertThat(merge2.getFields(), nullValue()); + assertThat(merge2.isGranted(), is(true)); + assertThat(merge2.getQueries(), nullValue()); + } + + public void testMergeQueries() { + BytesReference query1 = new BytesArray(new byte[] { 0x1 }); + BytesReference query2 = new BytesArray(new byte[] { 0x2 }); + IndexAccessControl indexAccessControl = new IndexAccessControl(true, null, Collections.singleton(query1)); + IndexAccessControl other = new IndexAccessControl(true, null, Collections.singleton(query2)); + + IndexAccessControl merge1 = indexAccessControl.merge(other); + assertThat(merge1.getFields(), nullValue()); + assertThat(merge1.isGranted(), is(true)); + assertThat(merge1.getQueries(), containsInAnyOrder(query1, query2)); + + IndexAccessControl merge2 = other.merge(indexAccessControl); + assertThat(merge2.getFields(), nullValue()); + assertThat(merge2.isGranted(), is(true)); + assertThat(merge1.getQueries(), containsInAnyOrder(query1, query2)); + } + + public void testMergeNullQuery() { + BytesReference query1 = new BytesArray(new byte[] { 0x1 }); + IndexAccessControl indexAccessControl = new IndexAccessControl(true, null, Collections.singleton(query1)); + IndexAccessControl other = new IndexAccessControl(true, null, null); + + IndexAccessControl merge1 = indexAccessControl.merge(other); + assertThat(merge1.getFields(), nullValue()); + assertThat(merge1.isGranted(), is(true)); + assertThat(merge1.getQueries(), nullValue()); + + IndexAccessControl merge2 = other.merge(indexAccessControl); + assertThat(merge2.getFields(), nullValue()); + assertThat(merge2.isGranted(), is(true)); + assertThat(merge1.getQueries(), nullValue()); + } +}