diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/FieldSubsetReader.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/FieldSubsetReader.java index 908f58c5f9147..2cd18b0a4a2a2 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/FieldSubsetReader.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/FieldSubsetReader.java @@ -54,6 +54,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.function.Function; /** * A {@link FilterLeafReader} that exposes only a subset @@ -68,36 +69,42 @@ public final class FieldSubsetReader extends SequentialStoredFieldsLeafReader { * Note that for convenience, the returned reader * can be used normally (e.g. passed to {@link DirectoryReader#openIfChanged(DirectoryReader)}) * and so on. - * @param in reader to filter - * @param filter fields to filter. + * + * @param in reader to filter + * @param filter fields to filter. + * @param getParentField */ - public static DirectoryReader wrap(DirectoryReader in, CharacterRunAutomaton filter) throws IOException { - return new FieldSubsetDirectoryReader(in, filter); + public static DirectoryReader wrap(DirectoryReader in, CharacterRunAutomaton filter, Function getParentField) + throws IOException { + return new FieldSubsetDirectoryReader(in, filter, getParentField); } // wraps subreaders with fieldsubsetreaders. static class FieldSubsetDirectoryReader extends FilterDirectoryReader { private final CharacterRunAutomaton filter; + private final Function getParentField; - FieldSubsetDirectoryReader(DirectoryReader in, final CharacterRunAutomaton filter) throws IOException { + FieldSubsetDirectoryReader(DirectoryReader in, final CharacterRunAutomaton filter, Function getParentField) + throws IOException { super(in, new FilterDirectoryReader.SubReaderWrapper() { @Override public LeafReader wrap(LeafReader reader) { try { - return new FieldSubsetReader(reader, filter); + return new FieldSubsetReader(reader, filter, getParentField); } catch (IOException e) { throw new UncheckedIOException(e); } } }); this.filter = filter; + this.getParentField = getParentField; verifyNoOtherFieldSubsetDirectoryReaderIsWrapped(in); } @Override protected DirectoryReader doWrapDirectoryReader(DirectoryReader in) throws IOException { - return new FieldSubsetDirectoryReader(in, filter); + return new FieldSubsetDirectoryReader(in, filter, getParentField); } /** Return the automaton that is used to filter fields. */ @@ -125,6 +132,7 @@ public CacheHelper getReaderCacheHelper() { /** List of filtered fields */ private final FieldInfos fieldInfos; + private final Function getParentField; /** An automaton that only accepts authorized fields. */ private final CharacterRunAutomaton filter; /** {@link Terms} cache with filtered stats for the {@link FieldNamesFieldMapper} field. */ @@ -133,15 +141,20 @@ public CacheHelper getReaderCacheHelper() { /** * Wrap a single segment, exposing a subset of its fields. */ - FieldSubsetReader(LeafReader in, CharacterRunAutomaton filter) throws IOException { + FieldSubsetReader(LeafReader in, CharacterRunAutomaton filter, Function getParentField) throws IOException { super(in); ArrayList filteredInfos = new ArrayList<>(); for (FieldInfo fi : in.getFieldInfos()) { if (filter.run(fi.name)) { + String parentField = getParentField.apply(fi.name); + if (parentField != null && filter.run(parentField) == false) { + continue; + } filteredInfos.add(fi); } } fieldInfos = new FieldInfos(filteredInfos.toArray(new FieldInfo[filteredInfos.size()])); + this.getParentField = getParentField; this.filter = filter; } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/SecurityIndexReaderWrapper.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/SecurityIndexReaderWrapper.java index 71ba14b02667a..7beb481ee52c1 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/SecurityIndexReaderWrapper.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/SecurityIndexReaderWrapper.java @@ -96,7 +96,9 @@ public DirectoryReader apply(final DirectoryReader reader) { } } - return permissions.getFieldPermissions().filter(wrappedReader); + var mappingLookup = searchExecutionContextProvider.apply(shardId).getMappingLookup(); + Function getParentField = mappingLookup::parentField; + return permissions.getFieldPermissions().filter(wrappedReader, getParentField); } catch (IOException e) { logger.error("Unable to apply field level security"); throw ExceptionsHelper.convertToElastic(e); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/FieldPermissions.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/FieldPermissions.java index c46f1350776b1..8c497f9d5061a 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/FieldPermissions.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/FieldPermissions.java @@ -32,6 +32,7 @@ import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.function.Function; import java.util.stream.Collectors; /** @@ -243,11 +244,11 @@ public boolean hasFieldLevelSecurity() { } /** Return a wrapped reader that only exposes allowed fields. */ - public DirectoryReader filter(DirectoryReader reader) throws IOException { + public DirectoryReader filter(DirectoryReader reader, Function getParentField) throws IOException { if (hasFieldLevelSecurity() == false) { return reader; } - return FieldSubsetReader.wrap(reader, permittedFieldsAutomaton); + return FieldSubsetReader.wrap(reader, permittedFieldsAutomaton, getParentField); } Automaton getIncludeAutomaton() { diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/FieldSubsetReaderTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/FieldSubsetReaderTests.java index 86f3e718c90b1..3ad7e0d4d1d63 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/FieldSubsetReaderTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/FieldSubsetReaderTests.java @@ -109,7 +109,11 @@ public void testIndexed() throws Exception { iw.addDocument(doc); // open reader - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(Automata.makeString("fieldA"))); + DirectoryReader ir = FieldSubsetReader.wrap( + DirectoryReader.open(iw), + new CharacterRunAutomaton(Automata.makeString("fieldA")), + (field) -> null + ); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -140,7 +144,11 @@ public void testPoints() throws Exception { iw.addDocument(doc); // open reader - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(Automata.makeString("fieldA"))); + DirectoryReader ir = FieldSubsetReader.wrap( + DirectoryReader.open(iw), + new CharacterRunAutomaton(Automata.makeString("fieldA")), + (field) -> null + ); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -199,7 +207,11 @@ public void testKnnVectors() throws Exception { doc.add(new KnnFloatVectorField("fieldB", new float[] { 3.0f, 2.0f, 1.0f })); iw.addDocument(doc); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(Automata.makeString("fieldA"))); + DirectoryReader ir = FieldSubsetReader.wrap( + DirectoryReader.open(iw), + new CharacterRunAutomaton(Automata.makeString("fieldA")), + (field) -> null + ); LeafReader leafReader = ir.leaves().get(0).reader(); // Check that fieldA behaves as normal @@ -234,7 +246,11 @@ public void testKnnByteVectors() throws Exception { doc.add(new KnnByteVectorField("fieldB", new byte[] { 3, 2, 1 })); iw.addDocument(doc); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(Automata.makeString("fieldA"))); + DirectoryReader ir = FieldSubsetReader.wrap( + DirectoryReader.open(iw), + new CharacterRunAutomaton(Automata.makeString("fieldA")), + (field) -> null + ); LeafReader leafReader = ir.leaves().get(0).reader(); // Check that fieldA behaves as normal @@ -274,7 +290,11 @@ public void testStoredFieldsString() throws Exception { iw.addDocument(doc); // open reader - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(Automata.makeString("fieldA"))); + DirectoryReader ir = FieldSubsetReader.wrap( + DirectoryReader.open(iw), + new CharacterRunAutomaton(Automata.makeString("fieldA")), + (field) -> null + ); // see only one field { @@ -301,7 +321,11 @@ public void testStoredFieldsBinary() throws Exception { iw.addDocument(doc); // open reader - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(Automata.makeString("fieldA"))); + DirectoryReader ir = FieldSubsetReader.wrap( + DirectoryReader.open(iw), + new CharacterRunAutomaton(Automata.makeString("fieldA")), + (field) -> null + ); // see only one field { @@ -328,7 +352,11 @@ public void testStoredFieldsInt() throws Exception { iw.addDocument(doc); // open reader - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(Automata.makeString("fieldA"))); + DirectoryReader ir = FieldSubsetReader.wrap( + DirectoryReader.open(iw), + new CharacterRunAutomaton(Automata.makeString("fieldA")), + (field) -> null + ); // see only one field { @@ -355,7 +383,11 @@ public void testStoredFieldsLong() throws Exception { iw.addDocument(doc); // open reader - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(Automata.makeString("fieldA"))); + DirectoryReader ir = FieldSubsetReader.wrap( + DirectoryReader.open(iw), + new CharacterRunAutomaton(Automata.makeString("fieldA")), + (field) -> null + ); // see only one field { @@ -382,7 +414,11 @@ public void testStoredFieldsFloat() throws Exception { iw.addDocument(doc); // open reader - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(Automata.makeString("fieldA"))); + DirectoryReader ir = FieldSubsetReader.wrap( + DirectoryReader.open(iw), + new CharacterRunAutomaton(Automata.makeString("fieldA")), + (field) -> null + ); // see only one field { @@ -409,7 +445,11 @@ public void testStoredFieldsDouble() throws Exception { iw.addDocument(doc); // open reader - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(Automata.makeString("fieldA"))); + DirectoryReader ir = FieldSubsetReader.wrap( + DirectoryReader.open(iw), + new CharacterRunAutomaton(Automata.makeString("fieldA")), + (field) -> null + ); // see only one field { @@ -438,7 +478,11 @@ public void testVectors() throws Exception { iw.addDocument(doc); // open reader - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(Automata.makeString("fieldA"))); + DirectoryReader ir = FieldSubsetReader.wrap( + DirectoryReader.open(iw), + new CharacterRunAutomaton(Automata.makeString("fieldA")), + (field) -> null + ); // see only one field Fields vectors = ir.termVectors().get(0); @@ -467,7 +511,11 @@ public void testNorms() throws Exception { iw.addDocument(doc); // open reader - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(Automata.makeString("fieldA"))); + DirectoryReader ir = FieldSubsetReader.wrap( + DirectoryReader.open(iw), + new CharacterRunAutomaton(Automata.makeString("fieldA")), + (field) -> null + ); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -493,7 +541,11 @@ public void testNumericDocValues() throws Exception { iw.addDocument(doc); // open reader - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(Automata.makeString("fieldA"))); + DirectoryReader ir = FieldSubsetReader.wrap( + DirectoryReader.open(iw), + new CharacterRunAutomaton(Automata.makeString("fieldA")), + (field) -> null + ); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -522,7 +574,11 @@ public void testBinaryDocValues() throws Exception { iw.addDocument(doc); // open reader - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(Automata.makeString("fieldA"))); + DirectoryReader ir = FieldSubsetReader.wrap( + DirectoryReader.open(iw), + new CharacterRunAutomaton(Automata.makeString("fieldA")), + (field) -> null + ); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -551,7 +607,11 @@ public void testSortedDocValues() throws Exception { iw.addDocument(doc); // open reader - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(Automata.makeString("fieldA"))); + DirectoryReader ir = FieldSubsetReader.wrap( + DirectoryReader.open(iw), + new CharacterRunAutomaton(Automata.makeString("fieldA")), + (field) -> null + ); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -580,7 +640,11 @@ public void testSortedSetDocValues() throws Exception { iw.addDocument(doc); // open reader - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(Automata.makeString("fieldA"))); + DirectoryReader ir = FieldSubsetReader.wrap( + DirectoryReader.open(iw), + new CharacterRunAutomaton(Automata.makeString("fieldA")), + (field) -> null + ); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -610,7 +674,11 @@ public void testSortedNumericDocValues() throws Exception { iw.addDocument(doc); // open reader - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(Automata.makeString("fieldA"))); + DirectoryReader ir = FieldSubsetReader.wrap( + DirectoryReader.open(iw), + new CharacterRunAutomaton(Automata.makeString("fieldA")), + (field) -> null + ); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -640,7 +708,11 @@ public void testFieldInfos() throws Exception { iw.addDocument(doc); // open reader - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(Automata.makeString("fieldA"))); + DirectoryReader ir = FieldSubsetReader.wrap( + DirectoryReader.open(iw), + new CharacterRunAutomaton(Automata.makeString("fieldA")), + (field) -> null + ); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -671,7 +743,7 @@ public void testSourceFilteringIntegration() throws Exception { // open reader Automaton automaton = Automatons.patterns(Arrays.asList("fieldA", SourceFieldMapper.NAME)); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(automaton)); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(automaton), (field) -> null); // see only one field { @@ -713,7 +785,8 @@ public void testIgnoredSourceFilteringIntegration() throws Exception { try ( DirectoryReader indexReader = FieldSubsetReader.wrap( wrapInMockESDirectoryReader(DirectoryReader.open(directory)), - new CharacterRunAutomaton(automaton) + new CharacterRunAutomaton(automaton), + (field) -> null ) ) { String syntheticSource = syntheticSource(mapper, indexReader, doc.docs().size() - 1); @@ -730,7 +803,8 @@ public void testIgnoredSourceFilteringIntegration() throws Exception { try ( DirectoryReader indexReader = FieldSubsetReader.wrap( wrapInMockESDirectoryReader(DirectoryReader.open(directory)), - new CharacterRunAutomaton(automaton) + new CharacterRunAutomaton(automaton), + (field) -> null ) ) { String syntheticSource = syntheticSource(mapper, indexReader, doc.docs().size() - 1); @@ -744,7 +818,8 @@ public void testIgnoredSourceFilteringIntegration() throws Exception { try ( DirectoryReader indexReader = FieldSubsetReader.wrap( wrapInMockESDirectoryReader(DirectoryReader.open(directory)), - new CharacterRunAutomaton(automaton) + new CharacterRunAutomaton(automaton), + (field) -> null ) ) { String syntheticSource = syntheticSource(mapper, indexReader, doc.docs().size() - 1); @@ -762,7 +837,8 @@ public void testIgnoredSourceFilteringIntegration() throws Exception { try ( DirectoryReader indexReader = FieldSubsetReader.wrap( wrapInMockESDirectoryReader(DirectoryReader.open(directory)), - new CharacterRunAutomaton(automaton) + new CharacterRunAutomaton(automaton), + (field) -> null ) ) { String syntheticSource = syntheticSource(mapper, indexReader, doc.docs().size() - 1); @@ -776,7 +852,8 @@ public void testIgnoredSourceFilteringIntegration() throws Exception { try ( DirectoryReader indexReader = FieldSubsetReader.wrap( wrapInMockESDirectoryReader(DirectoryReader.open(directory)), - new CharacterRunAutomaton(automaton) + new CharacterRunAutomaton(automaton), + (field) -> null ) ) { String syntheticSource = syntheticSource(mapper, indexReader, doc.docs().size() - 1); @@ -794,7 +871,8 @@ public void testIgnoredSourceFilteringIntegration() throws Exception { try ( DirectoryReader indexReader = FieldSubsetReader.wrap( wrapInMockESDirectoryReader(DirectoryReader.open(directory)), - new CharacterRunAutomaton(automaton) + new CharacterRunAutomaton(automaton), + (field) -> null ) ) { String syntheticSource = syntheticSource(mapper, indexReader, doc.docs().size() - 1); @@ -967,7 +1045,7 @@ public void testFieldNames() throws Exception { Set fields = new HashSet<>(); fields.add("fieldA"); Automaton automaton = Automatons.patterns(Arrays.asList("fieldA", FieldNamesFieldMapper.NAME)); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(automaton)); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(automaton), (field) -> null); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -1025,7 +1103,7 @@ public void testFieldNamesThreeFields() throws Exception { // open reader Automaton automaton = Automatons.patterns(Arrays.asList("fieldA", "fieldC", FieldNamesFieldMapper.NAME)); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(automaton)); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(automaton), (field) -> null); // see only two fields LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -1070,7 +1148,7 @@ public void testFieldNamesMissing() throws Exception { // open reader Automaton automaton = Automatons.patterns(Arrays.asList("fieldA", "fieldC", FieldNamesFieldMapper.NAME)); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(automaton)); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(automaton), (field) -> null); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -1104,7 +1182,7 @@ public void testFieldNamesOldIndex() throws Exception { // open reader Automaton automaton = Automatons.patterns(Arrays.asList("fieldA", SourceFieldMapper.NAME)); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(automaton)); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(automaton), (field) -> null); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -1132,7 +1210,11 @@ public void testCoreCacheKey() throws Exception { iw.addDocument(doc); // open reader - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(Automata.makeString("id"))); + DirectoryReader ir = FieldSubsetReader.wrap( + DirectoryReader.open(iw), + new CharacterRunAutomaton(Automata.makeString("id")), + (field) -> null + ); assertEquals(2, ir.numDocs()); assertEquals(1, ir.leaves().size()); @@ -1166,7 +1248,11 @@ public void testFilterAwayAllVectors() throws Exception { iw.addDocument(doc); // open reader - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(Automata.makeString("fieldB"))); + DirectoryReader ir = FieldSubsetReader.wrap( + DirectoryReader.open(iw), + new CharacterRunAutomaton(Automata.makeString("fieldB")), + (field) -> null + ); // sees no fields assertNull(ir.termVectors().get(0)); @@ -1185,7 +1271,11 @@ public void testEmpty() throws Exception { iw.addDocument(new Document()); // open reader - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(Automata.makeString("fieldA"))); + DirectoryReader ir = FieldSubsetReader.wrap( + DirectoryReader.open(iw), + new CharacterRunAutomaton(Automata.makeString("fieldA")), + (field) -> null + ); // see no fields LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -1216,11 +1306,12 @@ public void testWrapTwice() throws Exception { final DirectoryReader directoryReader = FieldSubsetReader.wrap( DirectoryReader.open(dir), - new CharacterRunAutomaton(Automata.makeString("fieldA")) + new CharacterRunAutomaton(Automata.makeString("fieldA")), + (field) -> null ); IllegalArgumentException e = expectThrows( IllegalArgumentException.class, - () -> FieldSubsetReader.wrap(directoryReader, new CharacterRunAutomaton(Automata.makeString("fieldA"))) + () -> FieldSubsetReader.wrap(directoryReader, new CharacterRunAutomaton(Automata.makeString("fieldA")), (field) -> null) ); assertThat( e.getMessage(), @@ -1406,7 +1497,7 @@ public void testProducesStoredFieldsReader() throws Exception { // open reader Automaton automaton = Automatons.patterns(Arrays.asList("fieldA", SourceFieldMapper.NAME)); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(automaton)); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), new CharacterRunAutomaton(automaton), (field) -> null); TestUtil.checkReader(ir); assertThat(ir.leaves().size(), Matchers.greaterThanOrEqualTo(1)); diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/SecurityIndexReaderWrapperUnitTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/SecurityIndexReaderWrapperUnitTests.java index 104f6f2847ab0..3a6dca3bce918 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/SecurityIndexReaderWrapperUnitTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/SecurityIndexReaderWrapperUnitTests.java @@ -15,8 +15,11 @@ import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.index.Index; import org.elasticsearch.index.mapper.FieldNamesFieldMapper; +import org.elasticsearch.index.mapper.Mapping; +import org.elasticsearch.index.mapper.MappingLookup; import org.elasticsearch.index.mapper.SeqNoFieldMapper; import org.elasticsearch.index.mapper.SourceFieldMapper; +import org.elasticsearch.index.query.SearchExecutionContext; import org.elasticsearch.index.shard.IndexShard; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.indices.IndicesModule; @@ -34,6 +37,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; +import java.util.function.Function; import static java.util.Collections.singletonMap; import static org.elasticsearch.xpack.core.security.SecurityField.DOCUMENT_LEVEL_SECURITY_FEATURE; @@ -86,7 +90,11 @@ public void tearDown() throws Exception { } public void testDefaultMetaFields() throws Exception { - securityIndexReaderWrapper = new SecurityIndexReaderWrapper(null, null, securityContext, licenseState, scriptService) { + SearchExecutionContext context = mock(SearchExecutionContext.class); + MappingLookup mappingLookup = MappingLookup.fromMapping(Mapping.EMPTY); + when(context.getMappingLookup()).thenReturn(mappingLookup); + Function provider = shardId -> context; + securityIndexReaderWrapper = new SecurityIndexReaderWrapper(provider, null, securityContext, licenseState, scriptService) { @Override protected IndicesAccessControl getIndicesAccessControl() { IndicesAccessControl.IndexAccessControl indexAccessControl = new IndicesAccessControl.IndexAccessControl( diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/FieldDataCacheWithFieldSubsetReaderTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/FieldDataCacheWithFieldSubsetReaderTests.java index 4d46cba676049..920d743e6d0a4 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/FieldDataCacheWithFieldSubsetReaderTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/FieldDataCacheWithFieldSubsetReaderTests.java @@ -116,7 +116,7 @@ public void testSortedSetDVOrdinalsIndexFieldData_global() throws Exception { assertThat(atomic.getOrdinalsValues().getValueCount(), equalTo(numDocs)); assertThat(indexFieldDataCache.topLevelBuilds, equalTo(1)); - DirectoryReader ir = FieldSubsetReader.wrap(this.ir, new CharacterRunAutomaton(Automata.makeEmpty())); + DirectoryReader ir = FieldSubsetReader.wrap(this.ir, new CharacterRunAutomaton(Automata.makeEmpty()), (field) -> null); global = sortedSetOrdinalsIndexFieldData.loadGlobal(ir); atomic = global.load(ir.leaves().get(0)); assertThat(atomic.getOrdinalsValues().getValueCount(), equalTo(0L)); @@ -129,7 +129,7 @@ public void testSortedSetDVOrdinalsIndexFieldData_segment() throws Exception { assertThat(atomic.getOrdinalsValues().getValueCount(), greaterThanOrEqualTo(1L)); } - DirectoryReader ir = FieldSubsetReader.wrap(this.ir, new CharacterRunAutomaton(Automata.makeEmpty())); + DirectoryReader ir = FieldSubsetReader.wrap(this.ir, new CharacterRunAutomaton(Automata.makeEmpty()), (field) -> null); for (LeafReaderContext context : ir.leaves()) { LeafOrdinalsFieldData atomic = sortedSetOrdinalsIndexFieldData.load(context); assertThat(atomic.getOrdinalsValues().getValueCount(), equalTo(0L)); @@ -145,7 +145,7 @@ public void testPagedBytesIndexFieldData_global() throws Exception { assertThat(atomic.getOrdinalsValues().getValueCount(), equalTo(numDocs)); assertThat(indexFieldDataCache.topLevelBuilds, equalTo(1)); - DirectoryReader ir = FieldSubsetReader.wrap(this.ir, new CharacterRunAutomaton(Automata.makeEmpty())); + DirectoryReader ir = FieldSubsetReader.wrap(this.ir, new CharacterRunAutomaton(Automata.makeEmpty()), (field) -> null); global = pagedBytesIndexFieldData.loadGlobal(ir); atomic = global.load(ir.leaves().get(0)); assertThat(atomic.getOrdinalsValues().getValueCount(), equalTo(0L)); @@ -160,7 +160,7 @@ public void testPagedBytesIndexFieldData_segment() throws Exception { } assertThat(indexFieldDataCache.leafLevelBuilds, equalTo(ir.leaves().size())); - DirectoryReader ir = FieldSubsetReader.wrap(this.ir, new CharacterRunAutomaton(Automata.makeEmpty())); + DirectoryReader ir = FieldSubsetReader.wrap(this.ir, new CharacterRunAutomaton(Automata.makeEmpty()), (field) -> null); for (LeafReaderContext context : ir.leaves()) { LeafOrdinalsFieldData atomic = pagedBytesIndexFieldData.load(context); assertThat(atomic.getOrdinalsValues().getValueCount(), equalTo(0L)); diff --git a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/security/authz_api_keys/30_field_level_security_synthetic_source.yml b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/security/authz_api_keys/30_field_level_security_synthetic_source.yml index 301cb01acd2d3..20fb280ebc492 100644 --- a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/security/authz_api_keys/30_field_level_security_synthetic_source.yml +++ b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/security/authz_api_keys/30_field_level_security_synthetic_source.yml @@ -140,6 +140,82 @@ Filter fields in object: - match: { hits.hits.0._source.object.public: interest } - is_false: "_source.object.secret" +--- +Filter fields multi fields: + - do: + indices.create: + index: index_fls + body: + settings: + index: + mapping.source.mode: synthetic + mappings: + properties: + message: + type: text + fields: + keyword: + type: keyword + + - do: + bulk: + index: index_fls + refresh: true + body: + - '{"create": { }}' + - '{"message": "2025-07-01T09:11 3328971 [Note] Access denied for user root@10.12.6.38 (using password: YES)"}' + - match: { errors: false } + + - do: + security.create_api_key: + body: + name: "test-fls" + expiration: "1d" + role_descriptors: + index_access: + indices: + - names: [ "index_fls" ] + privileges: [ "read", "monitor" ] + field_security: + grant: [ "*" ] + except: [ "message" ] + - match: { name: "test-fls" } + - is_true: id + - set: + id: api_key_id + encoded: credentials + + # With superuser... + - do: + search: + index: index_fls + - match: { hits.hits.0._source.message: "2025-07-01T09:11 3328971 [Note] Access denied for user root@10.12.6.38 (using password: YES)" } + + - do: + search: + index: index_fls + body: + fields: [message.keyword] + - match: { hits.hits.0._source.message: "2025-07-01T09:11 3328971 [Note] Access denied for user root@10.12.6.38 (using password: YES)" } + - match: { hits.hits.0.fields.message\.keyword.0: "2025-07-01T09:11 3328971 [Note] Access denied for user root@10.12.6.38 (using password: YES)" } + + # With FLS API Key + - do: + headers: + Authorization: "ApiKey ${credentials}" + search: + index: index_fls + - is_false: "hits.hits.0._source.message" + + - do: + headers: + Authorization: "ApiKey ${credentials}" + search: + index: index_fls + body: + fields: [ message.keyword ] + - is_false: hits.hits.0._source.message + - is_false: hits.hits.0.fields.message\.keyword.0 --- Fields under a disabled object - uses _ignored_source: