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()); + } +}