Skip to content

Commit 31d6234

Browse files
martijnvgelasticsearchmachine
andauthored
[9.1] Handle ._original and._ignore_malformed stored fields correctly with flu (#137541)
* [9.1] Handle ._original and._ignore_malformed stored fields correctly with fls Backporting #137442 to 9.1 branch. * fixed compile error * [CI] Auto commit changes from spotless * fixed mocking --------- Co-authored-by: elasticsearchmachine <[email protected]>
1 parent 5da3673 commit 31d6234

File tree

12 files changed

+491
-56
lines changed

12 files changed

+491
-56
lines changed

docs/changelog/137442.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 137442
2+
summary: Handle ._original stored fields with fls
3+
area: "Authorization"
4+
type: bug
5+
issues: []

modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/MatchOnlyTextFieldMapper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ public MatchOnlyTextFieldType(
208208
super(name, true, false, false, tsi, meta);
209209
this.indexAnalyzer = Objects.requireNonNull(indexAnalyzer);
210210
this.textFieldType = new TextFieldType(name, isSyntheticSource, syntheticSourceDelegate);
211-
this.originalName = isSyntheticSource ? name + "._original" : null;
211+
this.originalName = isSyntheticSource ? name + KeywordFieldMapper.FALLBACK_FIELD_NAME_SUFFIX : null;
212212
this.withinMultiField = withinMultiField;
213213
this.storedFieldInBinaryFormat = storedFieldInBinaryFormat;
214214
}

server/src/main/java/org/elasticsearch/index/mapper/IgnoreMalformedStoredValues.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
* {@code _source}.
2727
*/
2828
public abstract class IgnoreMalformedStoredValues {
29+
30+
public static final String IGNORE_MALFORMED_FIELD_NAME_SUFFIX = "._ignore_malformed";
31+
2932
/**
3033
* Creates a stored field that stores malformed data to be used in synthetic source.
3134
* Name of the stored field is original name of the field with added conventional suffix.
@@ -143,6 +146,6 @@ public void reset() {
143146
}
144147

145148
public static String name(String fieldName) {
146-
return fieldName + "._ignore_malformed";
149+
return fieldName + IGNORE_MALFORMED_FIELD_NAME_SUFFIX;
147150
}
148151
}

server/src/main/java/org/elasticsearch/index/mapper/KeywordFieldMapper.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ public final class KeywordFieldMapper extends FieldMapper {
103103

104104
public static final String CONTENT_TYPE = "keyword";
105105
private static final String HOST_NAME = "host.name";
106+
public static final String FALLBACK_FIELD_NAME_SUFFIX = "._original";
106107

107108
public static class Defaults {
108109
public static final FieldType FIELD_TYPE;
@@ -537,7 +538,7 @@ public KeywordFieldType(
537538
this.isSyntheticSource = isSyntheticSource;
538539
this.indexSortConfig = builder.indexSortConfig;
539540
this.hasDocValuesSkipper = DocValuesSkipIndexType.NONE.equals(fieldType.docValuesSkipIndexType()) == false;
540-
this.originalName = isSyntheticSource ? name + "._original" : null;
541+
this.originalName = isSyntheticSource ? name + FALLBACK_FIELD_NAME_SUFFIX : null;
541542
}
542543

543544
public KeywordFieldType(String name) {

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/FieldSubsetReader.java

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@
4040
import org.elasticsearch.common.xcontent.XContentHelper;
4141
import org.elasticsearch.core.Tuple;
4242
import org.elasticsearch.index.mapper.FieldNamesFieldMapper;
43+
import org.elasticsearch.index.mapper.IgnoreMalformedStoredValues;
4344
import org.elasticsearch.index.mapper.IgnoredSourceFieldMapper;
45+
import org.elasticsearch.index.mapper.KeywordFieldMapper;
4446
import org.elasticsearch.index.mapper.SourceFieldMapper;
4547
import org.elasticsearch.transport.Transports;
4648
import org.elasticsearch.xcontent.XContentBuilder;
@@ -54,6 +56,7 @@
5456
import java.util.List;
5557
import java.util.Map;
5658
import java.util.Optional;
59+
import java.util.function.Function;
5760

5861
/**
5962
* A {@link FilterLeafReader} that exposes only a subset
@@ -68,36 +71,42 @@ public final class FieldSubsetReader extends SequentialStoredFieldsLeafReader {
6871
* Note that for convenience, the returned reader
6972
* can be used normally (e.g. passed to {@link DirectoryReader#openIfChanged(DirectoryReader)})
7073
* and so on.
71-
* @param in reader to filter
72-
* @param filter fields to filter.
74+
*
75+
* @param in reader to filter
76+
* @param filter fields to filter.
77+
* @param isMapped whether a field is mapped or not.
7378
*/
74-
public static DirectoryReader wrap(DirectoryReader in, CharacterRunAutomaton filter) throws IOException {
75-
return new FieldSubsetDirectoryReader(in, filter);
79+
public static DirectoryReader wrap(DirectoryReader in, CharacterRunAutomaton filter, Function<String, Boolean> isMapped)
80+
throws IOException {
81+
return new FieldSubsetDirectoryReader(in, filter, isMapped);
7682
}
7783

7884
// wraps subreaders with fieldsubsetreaders.
7985
static class FieldSubsetDirectoryReader extends FilterDirectoryReader {
8086

8187
private final CharacterRunAutomaton filter;
88+
private final Function<String, Boolean> isMapped;
8289

83-
FieldSubsetDirectoryReader(DirectoryReader in, final CharacterRunAutomaton filter) throws IOException {
90+
FieldSubsetDirectoryReader(DirectoryReader in, final CharacterRunAutomaton filter, Function<String, Boolean> isMapped)
91+
throws IOException {
8492
super(in, new FilterDirectoryReader.SubReaderWrapper() {
8593
@Override
8694
public LeafReader wrap(LeafReader reader) {
8795
try {
88-
return new FieldSubsetReader(reader, filter);
96+
return new FieldSubsetReader(reader, filter, isMapped);
8997
} catch (IOException e) {
9098
throw new UncheckedIOException(e);
9199
}
92100
}
93101
});
94102
this.filter = filter;
103+
this.isMapped = isMapped;
95104
verifyNoOtherFieldSubsetDirectoryReaderIsWrapped(in);
96105
}
97106

98107
@Override
99108
protected DirectoryReader doWrapDirectoryReader(DirectoryReader in) throws IOException {
100-
return new FieldSubsetDirectoryReader(in, filter);
109+
return new FieldSubsetDirectoryReader(in, filter, isMapped);
101110
}
102111

103112
/** Return the automaton that is used to filter fields. */
@@ -133,11 +142,20 @@ public CacheHelper getReaderCacheHelper() {
133142
/**
134143
* Wrap a single segment, exposing a subset of its fields.
135144
*/
136-
FieldSubsetReader(LeafReader in, CharacterRunAutomaton filter) throws IOException {
145+
FieldSubsetReader(LeafReader in, CharacterRunAutomaton filter, Function<String, Boolean> isMapped) throws IOException {
137146
super(in);
138147
ArrayList<FieldInfo> filteredInfos = new ArrayList<>();
139148
for (FieldInfo fi : in.getFieldInfos()) {
140-
if (filter.run(fi.name)) {
149+
String name = fi.name;
150+
if (fi.getName().endsWith(KeywordFieldMapper.FALLBACK_FIELD_NAME_SUFFIX) && isMapped.apply(fi.getName()) == false) {
151+
name = fi.getName().substring(0, fi.getName().length() - KeywordFieldMapper.FALLBACK_FIELD_NAME_SUFFIX.length());
152+
}
153+
if (fi.getName().endsWith(IgnoreMalformedStoredValues.IGNORE_MALFORMED_FIELD_NAME_SUFFIX)
154+
&& isMapped.apply(fi.getName()) == false) {
155+
name = fi.getName()
156+
.substring(0, fi.getName().length() - IgnoreMalformedStoredValues.IGNORE_MALFORMED_FIELD_NAME_SUFFIX.length());
157+
}
158+
if (filter.run(name)) {
141159
filteredInfos.add(fi);
142160
}
143161
}

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/SecurityIndexReaderWrapper.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,10 @@ public DirectoryReader apply(final DirectoryReader reader) {
9696
}
9797
}
9898

99-
return permissions.getFieldPermissions().filter(wrappedReader);
99+
var searchContext = searchExecutionContextProvider.apply(shardId);
100+
Function<String, Boolean> isMapped = searchContext::isFieldMapped;
101+
102+
return permissions.getFieldPermissions().filter(wrappedReader, isMapped);
100103
} catch (IOException e) {
101104
logger.error("Unable to apply field level security");
102105
throw ExceptionsHelper.convertToElastic(e);

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/FieldPermissions.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.util.List;
3333
import java.util.Objects;
3434
import java.util.Set;
35+
import java.util.function.Function;
3536
import java.util.stream.Collectors;
3637

3738
/**
@@ -243,11 +244,11 @@ public boolean hasFieldLevelSecurity() {
243244
}
244245

245246
/** Return a wrapped reader that only exposes allowed fields. */
246-
public DirectoryReader filter(DirectoryReader reader) throws IOException {
247+
public DirectoryReader filter(DirectoryReader reader, Function<String, Boolean> isMapped) throws IOException {
247248
if (hasFieldLevelSecurity() == false) {
248249
return reader;
249250
}
250-
return FieldSubsetReader.wrap(reader, permittedFieldsAutomaton);
251+
return FieldSubsetReader.wrap(reader, permittedFieldsAutomaton, isMapped);
251252
}
252253

253254
Automaton getIncludeAutomaton() {

0 commit comments

Comments
 (0)