Skip to content

Commit 8b19e22

Browse files
authored
[9.2] Handle ._original and._ignore_malformed stored fields correctly with fls (#137527)
* [9.2] Handle ._original and._ignore_malformed stored fields correctly with fls Backporting #137442 to 9.2 branch. * fixed compile error
1 parent 89440b7 commit 8b19e22

File tree

11 files changed

+469
-57
lines changed

11 files changed

+469
-57
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;
@@ -571,7 +572,7 @@ public KeywordFieldType(
571572
this.isSyntheticSource = isSyntheticSource;
572573
this.indexSortConfig = builder.indexSortConfig;
573574
this.hasDocValuesSkipper = DocValuesSkipIndexType.NONE.equals(fieldType.docValuesSkipIndexType()) == false;
574-
this.originalName = isSyntheticSource ? name + "._original" : null;
575+
this.originalName = isSyntheticSource ? name + FALLBACK_FIELD_NAME_SUFFIX : null;
575576
}
576577

577578
public KeywordFieldType(String name) {

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

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@
4141
import org.elasticsearch.common.xcontent.XContentHelper;
4242
import org.elasticsearch.core.Tuple;
4343
import org.elasticsearch.index.mapper.FieldNamesFieldMapper;
44+
import org.elasticsearch.index.mapper.IgnoreMalformedStoredValues;
4445
import org.elasticsearch.index.mapper.IgnoredSourceFieldMapper;
46+
import org.elasticsearch.index.mapper.KeywordFieldMapper;
4547
import org.elasticsearch.index.mapper.SourceFieldMapper;
4648
import org.elasticsearch.transport.Transports;
4749
import org.elasticsearch.xcontent.XContentBuilder;
@@ -55,6 +57,7 @@
5557
import java.util.List;
5658
import java.util.Map;
5759
import java.util.Optional;
60+
import java.util.function.Function;
5861

5962
/**
6063
* A {@link FilterLeafReader} that exposes only a subset
@@ -69,46 +72,52 @@ public final class FieldSubsetReader extends SequentialStoredFieldsLeafReader {
6972
* Note that for convenience, the returned reader
7073
* can be used normally (e.g. passed to {@link DirectoryReader#openIfChanged(DirectoryReader)})
7174
* and so on.
72-
* @param in reader to filter
73-
* @param filter fields to filter.
75+
*
76+
* @param in reader to filter
77+
* @param filter fields to filter.
78+
* @param isMapped whether a field is mapped or not.
7479
*/
7580
public static DirectoryReader wrap(
7681
DirectoryReader in,
7782
CharacterRunAutomaton filter,
78-
IgnoredSourceFieldMapper.IgnoredSourceFormat ignoredSourceFormat
83+
IgnoredSourceFieldMapper.IgnoredSourceFormat ignoredSourceFormat,
84+
Function<String, Boolean> isMapped
7985
) throws IOException {
80-
return new FieldSubsetDirectoryReader(in, filter, ignoredSourceFormat);
86+
return new FieldSubsetDirectoryReader(in, filter, ignoredSourceFormat, isMapped);
8187
}
8288

8389
// wraps subreaders with fieldsubsetreaders.
8490
static class FieldSubsetDirectoryReader extends FilterDirectoryReader {
8591

8692
private final CharacterRunAutomaton filter;
8793
private final IgnoredSourceFieldMapper.IgnoredSourceFormat ignoredSourceFormat;
94+
private final Function<String, Boolean> isMapped;
8895

8996
FieldSubsetDirectoryReader(
9097
DirectoryReader in,
9198
final CharacterRunAutomaton filter,
92-
final IgnoredSourceFieldMapper.IgnoredSourceFormat ignoredSourceFormat
99+
final IgnoredSourceFieldMapper.IgnoredSourceFormat ignoredSourceFormat,
100+
Function<String, Boolean> isMapped
93101
) throws IOException {
94102
super(in, new FilterDirectoryReader.SubReaderWrapper() {
95103
@Override
96104
public LeafReader wrap(LeafReader reader) {
97105
try {
98-
return new FieldSubsetReader(reader, filter, ignoredSourceFormat);
106+
return new FieldSubsetReader(reader, filter, ignoredSourceFormat, isMapped);
99107
} catch (IOException e) {
100108
throw new UncheckedIOException(e);
101109
}
102110
}
103111
});
104112
this.filter = filter;
105113
this.ignoredSourceFormat = ignoredSourceFormat;
114+
this.isMapped = isMapped;
106115
verifyNoOtherFieldSubsetDirectoryReaderIsWrapped(in);
107116
}
108117

109118
@Override
110119
protected DirectoryReader doWrapDirectoryReader(DirectoryReader in) throws IOException {
111-
return new FieldSubsetDirectoryReader(in, filter, ignoredSourceFormat);
120+
return new FieldSubsetDirectoryReader(in, filter, ignoredSourceFormat, isMapped);
112121
}
113122

114123
/** Return the automaton that is used to filter fields. */
@@ -145,12 +154,25 @@ public CacheHelper getReaderCacheHelper() {
145154
/**
146155
* Wrap a single segment, exposing a subset of its fields.
147156
*/
148-
FieldSubsetReader(LeafReader in, CharacterRunAutomaton filter, IgnoredSourceFieldMapper.IgnoredSourceFormat ignoredSourceFormat)
149-
throws IOException {
157+
FieldSubsetReader(
158+
LeafReader in,
159+
CharacterRunAutomaton filter,
160+
IgnoredSourceFieldMapper.IgnoredSourceFormat ignoredSourceFormat,
161+
Function<String, Boolean> isMapped
162+
) throws IOException {
150163
super(in);
151164
ArrayList<FieldInfo> filteredInfos = new ArrayList<>();
152165
for (FieldInfo fi : in.getFieldInfos()) {
153-
if (filter.run(fi.name)) {
166+
String name = fi.name;
167+
if (fi.getName().endsWith(KeywordFieldMapper.FALLBACK_FIELD_NAME_SUFFIX) && isMapped.apply(fi.getName()) == false) {
168+
name = fi.getName().substring(0, fi.getName().length() - KeywordFieldMapper.FALLBACK_FIELD_NAME_SUFFIX.length());
169+
}
170+
if (fi.getName().endsWith(IgnoreMalformedStoredValues.IGNORE_MALFORMED_FIELD_NAME_SUFFIX)
171+
&& isMapped.apply(fi.getName()) == false) {
172+
name = fi.getName()
173+
.substring(0, fi.getName().length() - IgnoreMalformedStoredValues.IGNORE_MALFORMED_FIELD_NAME_SUFFIX.length());
174+
}
175+
if (filter.run(name)) {
154176
filteredInfos.add(fi);
155177
}
156178
}

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,11 @@ public DirectoryReader apply(final DirectoryReader reader) {
9696
}
9797
}
9898

99-
var indexVersionCreated = searchExecutionContextProvider.apply(shardId).indexVersionCreated();
99+
var searchContext = searchExecutionContextProvider.apply(shardId);
100+
var indexVersionCreated = searchContext.indexVersionCreated();
101+
Function<String, Boolean> isMapped = searchContext::isFieldMapped;
100102

101-
return permissions.getFieldPermissions().filter(wrappedReader, indexVersionCreated);
103+
return permissions.getFieldPermissions().filter(wrappedReader, indexVersionCreated, isMapped);
102104
} catch (IOException e) {
103105
logger.error("Unable to apply field level security");
104106
throw ExceptionsHelper.convertToElastic(e);

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import java.util.List;
3535
import java.util.Objects;
3636
import java.util.Set;
37+
import java.util.function.Function;
3738
import java.util.stream.Collectors;
3839

3940
/**
@@ -245,11 +246,17 @@ public boolean hasFieldLevelSecurity() {
245246
}
246247

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

255262
Automaton getIncludeAutomaton() {

0 commit comments

Comments
 (0)