diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/FieldSubsetReader.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/FieldSubsetReader.java index ea454c5b0ef..03b1a24dd00 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/FieldSubsetReader.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/FieldSubsetReader.java @@ -56,30 +56,33 @@ public final class FieldSubsetReader extends FilterLeafReader { * and so on. * @param in reader to filter * @param fieldNames fields to filter. + * @param negate {@code true} if this should be a negative set, meaning set of field names that is denied. */ - public static DirectoryReader wrap(DirectoryReader in, Set fieldNames) throws IOException { - return new FieldSubsetDirectoryReader(in, fieldNames); + public static DirectoryReader wrap(DirectoryReader in, Set fieldNames, boolean negate) throws IOException { + return new FieldSubsetDirectoryReader(in, fieldNames, negate); } // wraps subreaders with fieldsubsetreaders. static class FieldSubsetDirectoryReader extends FilterDirectoryReader { private final Set fieldNames; + private final boolean negate; - FieldSubsetDirectoryReader(DirectoryReader in, final Set fieldNames) throws IOException { + FieldSubsetDirectoryReader(DirectoryReader in, Set fieldNames, boolean negate) throws IOException { super(in, new FilterDirectoryReader.SubReaderWrapper() { @Override public LeafReader wrap(LeafReader reader) { - return new FieldSubsetReader(reader, fieldNames); + return new FieldSubsetReader(reader, fieldNames, negate); } }); this.fieldNames = fieldNames; + this.negate = negate; verifyNoOtherFieldSubsetDirectoryReaderIsWrapped(in); } @Override protected DirectoryReader doWrapDirectoryReader(DirectoryReader in) throws IOException { - return new FieldSubsetDirectoryReader(in, fieldNames); + return new FieldSubsetDirectoryReader(in, fieldNames, negate); } public Set getFieldNames() { @@ -111,17 +114,23 @@ public final class FieldSubsetReader extends FilterLeafReader { /** * Wrap a single segment, exposing a subset of its fields. + * @param fields set of field names that should be allowed + * @param negate {@code true} if this should be a negative set, meaning set of field names that is denied. */ - FieldSubsetReader(LeafReader in, Set fieldNames) { + FieldSubsetReader(LeafReader in, Set fields, boolean negate) { super(in); + // look at what fields the reader has, and preprocess a subset of them that are allowed ArrayList filteredInfos = new ArrayList<>(); for (FieldInfo fi : in.getFieldInfos()) { - if (fieldNames.contains(fi.name)) { + if (fields.contains(fi.name) ^ negate) { filteredInfos.add(fi); } } fieldInfos = new FieldInfos(filteredInfos.toArray(new FieldInfo[filteredInfos.size()])); - this.fieldNames = fieldNames.toArray(new String[fieldNames.size()]); + fieldNames = new String[filteredInfos.size()]; + for (int i = 0; i < fieldNames.length; i++) { + fieldNames[i] = filteredInfos.get(i).name; + } } /** returns true if this field is allowed. */ diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/SecurityIndexSearcherWrapper.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/SecurityIndexSearcherWrapper.java index 6245e44c688..2e133336eea 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/SecurityIndexSearcherWrapper.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/SecurityIndexSearcherWrapper.java @@ -144,7 +144,8 @@ public class SecurityIndexSearcherWrapper extends IndexSearcherWrapper { allowedFields.addAll(mapperService.simpleMatchToIndexNames(field)); } resolveParentChildJoinFields(allowedFields); - reader = FieldSubsetReader.wrap(reader, allowedFields); + // TODO: support 'denied' fields (pass true as the 3rd parameter in this case) + reader = FieldSubsetReader.wrap(reader, allowedFields, false); } return reader; diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/FieldDataCacheWithFieldSubsetReaderTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/FieldDataCacheWithFieldSubsetReaderTests.java index 7ed7130950d..8bea84a0f63 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/FieldDataCacheWithFieldSubsetReaderTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/FieldDataCacheWithFieldSubsetReaderTests.java @@ -97,7 +97,7 @@ public class FieldDataCacheWithFieldSubsetReaderTests extends ESTestCase { assertThat(atomic.getOrdinalsValues().getValueCount(), equalTo(numDocs)); assertThat(indexFieldDataCache.topLevelBuilds, equalTo(1)); - DirectoryReader ir = FieldSubsetReader.wrap(this.ir, Collections.emptySet()); + DirectoryReader ir = FieldSubsetReader.wrap(this.ir, Collections.emptySet(), false); global = sortedSetDVOrdinalsIndexFieldData.loadGlobal(ir); atomic = global.load(ir.leaves().get(0)); assertThat(atomic.getOrdinalsValues().getValueCount(), equalTo(0L)); @@ -110,7 +110,7 @@ public class FieldDataCacheWithFieldSubsetReaderTests extends ESTestCase { assertThat(atomic.getOrdinalsValues().getValueCount(), greaterThanOrEqualTo(1L)); } - DirectoryReader ir = FieldSubsetReader.wrap(this.ir, Collections.emptySet()); + DirectoryReader ir = FieldSubsetReader.wrap(this.ir, Collections.emptySet(), false); for (LeafReaderContext context : ir.leaves()) { AtomicOrdinalsFieldData atomic = sortedSetDVOrdinalsIndexFieldData.load(context); assertThat(atomic.getOrdinalsValues().getValueCount(), equalTo(0L)); @@ -126,7 +126,7 @@ public class FieldDataCacheWithFieldSubsetReaderTests extends ESTestCase { assertThat(atomic.getOrdinalsValues().getValueCount(), equalTo(numDocs)); assertThat(indexFieldDataCache.topLevelBuilds, equalTo(1)); - DirectoryReader ir = FieldSubsetReader.wrap(this.ir, Collections.emptySet()); + DirectoryReader ir = FieldSubsetReader.wrap(this.ir, Collections.emptySet(), false); global = pagedBytesIndexFieldData.loadGlobal(ir); atomic = global.load(ir.leaves().get(0)); assertThat(atomic.getOrdinalsValues().getValueCount(), equalTo(0L)); @@ -141,7 +141,7 @@ public class FieldDataCacheWithFieldSubsetReaderTests extends ESTestCase { } assertThat(indexFieldDataCache.leafLevelBuilds, equalTo(ir.leaves().size())); - DirectoryReader ir = FieldSubsetReader.wrap(this.ir, Collections.emptySet()); + DirectoryReader ir = FieldSubsetReader.wrap(this.ir, Collections.emptySet(), false); for (LeafReaderContext context : ir.leaves()) { AtomicOrdinalsFieldData atomic = pagedBytesIndexFieldData.load(context); assertThat(atomic.getOrdinalsValues().getValueCount(), equalTo(0L)); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/FieldSubsetReaderTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/FieldSubsetReaderTests.java index 58a015abc26..82e0a93b309 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/FieldSubsetReaderTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/FieldSubsetReaderTests.java @@ -70,7 +70,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -102,7 +102,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -189,7 +189,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); // see only one field Document d2 = ir.document(0); @@ -216,7 +216,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); // see only one field Document d2 = ir.document(0); @@ -243,7 +243,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); // see only one field Document d2 = ir.document(0); @@ -270,7 +270,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); // see only one field Document d2 = ir.document(0); @@ -297,7 +297,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); // see only one field Document d2 = ir.document(0); @@ -324,7 +324,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); // see only one field Document d2 = ir.document(0); @@ -353,7 +353,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); // see only one field Fields vectors = ir.getTermVectors(0); @@ -383,7 +383,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -410,7 +410,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -442,7 +442,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -474,7 +474,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -506,7 +506,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -542,7 +542,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -577,7 +577,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -610,7 +610,7 @@ public class FieldSubsetReaderTests extends ESTestCase { Set fields = new HashSet<>(); fields.add("fieldA"); fields.add(SourceFieldMapper.NAME); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); // see only one field Document d2 = ir.document(0); @@ -641,7 +641,7 @@ public class FieldSubsetReaderTests extends ESTestCase { Set fields = new HashSet<>(); fields.add("fieldA"); fields.add(FieldNamesFieldMapper.NAME); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -690,7 +690,7 @@ public class FieldSubsetReaderTests extends ESTestCase { fields.add("fieldA"); fields.add("fieldC"); fields.add(FieldNamesFieldMapper.NAME); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); // see only two fields LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -738,7 +738,7 @@ public class FieldSubsetReaderTests extends ESTestCase { fields.add("fieldA"); fields.add("fieldC"); fields.add(FieldNamesFieldMapper.NAME); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -774,7 +774,7 @@ public class FieldSubsetReaderTests extends ESTestCase { Set fields = new HashSet<>(); fields.add("fieldA"); fields.add(FieldNamesFieldMapper.NAME); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -803,7 +803,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("id"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); assertEquals(2, ir.numDocs()); assertEquals(1, ir.leaves().size()); @@ -838,7 +838,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldB"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); // sees no fields assertNull(ir.getTermVectors(0)); @@ -858,7 +858,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); // see no fields LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -888,9 +888,9 @@ public class FieldSubsetReaderTests extends ESTestCase { iw.close(); DirectoryReader directoryReader = DirectoryReader.open(dir); - directoryReader = FieldSubsetReader.wrap(directoryReader, Collections.emptySet()); + directoryReader = FieldSubsetReader.wrap(directoryReader, Collections.emptySet(), false); try { - FieldSubsetReader.wrap(directoryReader, Collections.emptySet()); + FieldSubsetReader.wrap(directoryReader, Collections.emptySet(), false); fail("shouldn't be able to wrap FieldSubsetDirectoryReader twice"); } catch (IllegalArgumentException e) { assertThat(e.getMessage(), equalTo("Can't wrap [class org.elasticsearch.xpack.security.authz.accesscontrol" + @@ -899,4 +899,36 @@ public class FieldSubsetReaderTests extends ESTestCase { directoryReader.close(); dir.close(); } + + /** + * test filtering two string fields, with negated set + */ + public void testNegative() throws Exception { + Directory dir = newDirectory(); + IndexWriterConfig iwc = new IndexWriterConfig(null); + IndexWriter iw = new IndexWriter(dir, iwc); + + // add document with 2 fields + Document doc = new Document(); + doc.add(new StringField("fieldA", "test", Field.Store.NO)); + doc.add(new StringField("fieldB", "test", Field.Store.NO)); + iw.addDocument(doc); + + // open reader + Set fields = Collections.singleton("fieldB"); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, true); + + // see only one field + LeafReader segmentReader = ir.leaves().get(0).reader(); + Set seenFields = new HashSet<>(); + for (String field : segmentReader.fields()) { + seenFields.add(field); + } + assertEquals(Collections.singleton("fieldA"), seenFields); + assertNotNull(segmentReader.terms("fieldA")); + assertNull(segmentReader.terms("fieldB")); + + TestUtil.checkReader(ir); + IOUtils.close(ir, iw, dir); + } }