Skip to content

Commit 5373376

Browse files
committed
[8.19] Handle ._original and._ignore_malformed stored fields correctly with fls
Backporting elastic#137442 to 8.19 branch.
1 parent d652165 commit 5373376

File tree

11 files changed

+549
-55
lines changed

11 files changed

+549
-55
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
@@ -202,7 +202,7 @@ public MatchOnlyTextFieldType(
202202
super(name, true, false, false, tsi, meta);
203203
this.indexAnalyzer = Objects.requireNonNull(indexAnalyzer);
204204
this.textFieldType = new TextFieldType(name, isSyntheticSource, syntheticSourceDelegate);
205-
this.originalName = isSyntheticSource ? name + "._original" : null;
205+
this.originalName = isSyntheticSource ? name + KeywordFieldMapper.FALLBACK_FIELD_NAME_SUFFIX : null;
206206
this.withinMultiField = withinMultiField;
207207
this.storedFieldInBinaryFormat = storedFieldInBinaryFormat;
208208
}

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
@@ -104,6 +104,7 @@ public final class KeywordFieldMapper extends FieldMapper {
104104

105105
static final NodeFeature KEYWORD_DIMENSION_IGNORE_ABOVE = new NodeFeature("mapper.keyword_dimension_ignore_above", true);
106106
static final NodeFeature KEYWORD_NORMALIZER_SYNTHETIC_SOURCE = new NodeFeature("mapper.keyword_normalizer_synthetic_source", true);
107+
public static final String FALLBACK_FIELD_NAME_SUFFIX = "._original";
107108

108109
public static class Defaults {
109110
public static final FieldType FIELD_TYPE;
@@ -472,7 +473,7 @@ public KeywordFieldType(
472473
this.scriptValues = builder.scriptValues();
473474
this.isDimension = builder.dimension.getValue();
474475
this.isSyntheticSource = isSyntheticSource;
475-
this.originalName = isSyntheticSource ? name + "._original" : null;
476+
this.originalName = isSyntheticSource ? name + FALLBACK_FIELD_NAME_SUFFIX : null;
476477
}
477478

478479
public KeywordFieldType(String name) {

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

Lines changed: 37 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,48 @@ 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(
80+
DirectoryReader in,
81+
CharacterRunAutomaton filter,
82+
Function<String, Boolean> isMapped
83+
) throws IOException {
84+
return new FieldSubsetDirectoryReader(in, filter, isMapped);
7685
}
7786

7887
// wraps subreaders with fieldsubsetreaders.
7988
static class FieldSubsetDirectoryReader extends FilterDirectoryReader {
8089

8190
private final CharacterRunAutomaton filter;
91+
private final Function<String, Boolean> isMapped;
8292

83-
FieldSubsetDirectoryReader(DirectoryReader in, final CharacterRunAutomaton filter) throws IOException {
93+
FieldSubsetDirectoryReader(
94+
DirectoryReader in,
95+
final CharacterRunAutomaton filter,
96+
Function<String, Boolean> isMapped
97+
) throws IOException {
8498
super(in, new FilterDirectoryReader.SubReaderWrapper() {
8599
@Override
86100
public LeafReader wrap(LeafReader reader) {
87101
try {
88-
return new FieldSubsetReader(reader, filter);
102+
return new FieldSubsetReader(reader, filter, isMapped);
89103
} catch (IOException e) {
90104
throw new UncheckedIOException(e);
91105
}
92106
}
93107
});
94108
this.filter = filter;
109+
this.isMapped = isMapped;
95110
verifyNoOtherFieldSubsetDirectoryReaderIsWrapped(in);
96111
}
97112

98113
@Override
99114
protected DirectoryReader doWrapDirectoryReader(DirectoryReader in) throws IOException {
100-
return new FieldSubsetDirectoryReader(in, filter);
115+
return new FieldSubsetDirectoryReader(in, filter, isMapped);
101116
}
102117

103118
/** Return the automaton that is used to filter fields. */
@@ -133,11 +148,24 @@ public CacheHelper getReaderCacheHelper() {
133148
/**
134149
* Wrap a single segment, exposing a subset of its fields.
135150
*/
136-
FieldSubsetReader(LeafReader in, CharacterRunAutomaton filter) throws IOException {
151+
FieldSubsetReader(
152+
LeafReader in,
153+
CharacterRunAutomaton filter,
154+
Function<String, Boolean> isMapped
155+
) throws IOException {
137156
super(in);
138157
ArrayList<FieldInfo> filteredInfos = new ArrayList<>();
139158
for (FieldInfo fi : in.getFieldInfos()) {
140-
if (filter.run(fi.name)) {
159+
String name = fi.name;
160+
if (fi.getName().endsWith(KeywordFieldMapper.FALLBACK_FIELD_NAME_SUFFIX) && isMapped.apply(fi.getName()) == false) {
161+
name = fi.getName().substring(0, fi.getName().length() - KeywordFieldMapper.FALLBACK_FIELD_NAME_SUFFIX.length());
162+
}
163+
if (fi.getName().endsWith(IgnoreMalformedStoredValues.IGNORE_MALFORMED_FIELD_NAME_SUFFIX)
164+
&& isMapped.apply(fi.getName()) == false) {
165+
name = fi.getName()
166+
.substring(0, fi.getName().length() - IgnoreMalformedStoredValues.IGNORE_MALFORMED_FIELD_NAME_SUFFIX.length());
167+
}
168+
if (filter.run(name)) {
141169
filteredInfos.add(fi);
142170
}
143171
}

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: 8 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
import static org.apache.lucene.util.automaton.Operations.subsetOf;
@@ -245,11 +246,16 @@ public boolean hasFieldLevelSecurity() {
245246
}
246247

247248
/** Return a wrapped reader that only exposes allowed fields. */
248-
public DirectoryReader filter(DirectoryReader reader) throws IOException {
249+
public DirectoryReader filter(DirectoryReader reader, Function<String, Boolean> isMapped)
250+
throws IOException {
249251
if (hasFieldLevelSecurity() == false) {
250252
return reader;
251253
}
252-
return FieldSubsetReader.wrap(reader, permittedFieldsAutomaton);
254+
return FieldSubsetReader.wrap(
255+
reader,
256+
permittedFieldsAutomaton,
257+
isMapped
258+
);
253259
}
254260

255261
Automaton getIncludeAutomaton() {

0 commit comments

Comments
 (0)