Skip to content
5 changes: 5 additions & 0 deletions docs/changelog/137442.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 137442
summary: Handle ._original stored fields with fls
area: "Authorization"
type: bug
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
* {@code _source}.
*/
public abstract class IgnoreMalformedStoredValues {

public static final String IGNORE_MALFORMED_FIELD_NAME_SUFFIX = "._ignore_malformed";

/**
* Creates a stored field that stores malformed data to be used in synthetic source.
* Name of the stored field is original name of the field with added conventional suffix.
Expand Down Expand Up @@ -143,6 +146,6 @@ public void reset() {
}

public static String name(String fieldName) {
return fieldName + "._ignore_malformed";
return fieldName + IGNORE_MALFORMED_FIELD_NAME_SUFFIX;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
*/
public abstract class TextFamilyFieldType extends StringFieldType {

public static final String FALLBACK_FIELD_NAME_SUFFIX = "._original";
private final boolean isSyntheticSourceEnabled;
private final boolean isWithinMultiField;

Expand Down Expand Up @@ -51,7 +52,7 @@ public boolean isWithinMultiField() {
* stored for whatever reason.
*/
public String syntheticSourceFallbackFieldName() {
return name() + "._original";
return name() + FALLBACK_FIELD_NAME_SUFFIX;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.core.Tuple;
import org.elasticsearch.index.mapper.FieldNamesFieldMapper;
import org.elasticsearch.index.mapper.IgnoreMalformedStoredValues;
import org.elasticsearch.index.mapper.IgnoredSourceFieldMapper;
import org.elasticsearch.index.mapper.SourceFieldMapper;
import org.elasticsearch.index.mapper.TextFamilyFieldType;
import org.elasticsearch.transport.Transports;
import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.XContentType;
Expand All @@ -55,6 +57,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
Expand All @@ -69,46 +72,52 @@ 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 isMapped whether a field is mapped or not.
*/
public static DirectoryReader wrap(
DirectoryReader in,
CharacterRunAutomaton filter,
IgnoredSourceFieldMapper.IgnoredSourceFormat ignoredSourceFormat
IgnoredSourceFieldMapper.IgnoredSourceFormat ignoredSourceFormat,
Function<String, Boolean> isMapped
) throws IOException {
return new FieldSubsetDirectoryReader(in, filter, ignoredSourceFormat);
return new FieldSubsetDirectoryReader(in, filter, ignoredSourceFormat, isMapped);
}

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

private final CharacterRunAutomaton filter;
private final IgnoredSourceFieldMapper.IgnoredSourceFormat ignoredSourceFormat;
private final Function<String, Boolean> isMapped;

FieldSubsetDirectoryReader(
DirectoryReader in,
final CharacterRunAutomaton filter,
final IgnoredSourceFieldMapper.IgnoredSourceFormat ignoredSourceFormat
final IgnoredSourceFieldMapper.IgnoredSourceFormat ignoredSourceFormat,
Function<String, Boolean> isMapped
) throws IOException {
super(in, new FilterDirectoryReader.SubReaderWrapper() {
@Override
public LeafReader wrap(LeafReader reader) {
try {
return new FieldSubsetReader(reader, filter, ignoredSourceFormat);
return new FieldSubsetReader(reader, filter, ignoredSourceFormat, isMapped);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
});
this.filter = filter;
this.ignoredSourceFormat = ignoredSourceFormat;
this.isMapped = isMapped;
verifyNoOtherFieldSubsetDirectoryReaderIsWrapped(in);
}

@Override
protected DirectoryReader doWrapDirectoryReader(DirectoryReader in) throws IOException {
return new FieldSubsetDirectoryReader(in, filter, ignoredSourceFormat);
return new FieldSubsetDirectoryReader(in, filter, ignoredSourceFormat, isMapped);
}

/** Return the automaton that is used to filter fields. */
Expand Down Expand Up @@ -145,12 +154,25 @@ public CacheHelper getReaderCacheHelper() {
/**
* Wrap a single segment, exposing a subset of its fields.
*/
FieldSubsetReader(LeafReader in, CharacterRunAutomaton filter, IgnoredSourceFieldMapper.IgnoredSourceFormat ignoredSourceFormat)
throws IOException {
FieldSubsetReader(
LeafReader in,
CharacterRunAutomaton filter,
IgnoredSourceFieldMapper.IgnoredSourceFormat ignoredSourceFormat,
Function<String, Boolean> isMapped
) throws IOException {
super(in);
ArrayList<FieldInfo> filteredInfos = new ArrayList<>();
for (FieldInfo fi : in.getFieldInfos()) {
if (filter.run(fi.name)) {
String name = fi.name;
if (fi.getName().endsWith(TextFamilyFieldType.FALLBACK_FIELD_NAME_SUFFIX) && isMapped.apply(fi.getName()) == false) {
name = fi.getName().substring(0, fi.getName().length() - TextFamilyFieldType.FALLBACK_FIELD_NAME_SUFFIX.length());
}
if (fi.getName().endsWith(IgnoreMalformedStoredValues.IGNORE_MALFORMED_FIELD_NAME_SUFFIX)
&& isMapped.apply(fi.getName()) == false) {
name = fi.getName()
.substring(0, fi.getName().length() - IgnoreMalformedStoredValues.IGNORE_MALFORMED_FIELD_NAME_SUFFIX.length());
}
if (filter.run(name)) {
filteredInfos.add(fi);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,11 @@ public DirectoryReader apply(final DirectoryReader reader) {
}
}

var indexVersionCreated = searchExecutionContextProvider.apply(shardId).indexVersionCreated();
var searchContext = searchExecutionContextProvider.apply(shardId);
var indexVersionCreated = searchContext.indexVersionCreated();
Function<String, Boolean> isMapped = searchContext::isFieldMapped;

return permissions.getFieldPermissions().filter(wrappedReader, indexVersionCreated);
return permissions.getFieldPermissions().filter(wrappedReader, indexVersionCreated, isMapped);
} catch (IOException e) {
logger.error("Unable to apply field level security");
throw ExceptionsHelper.convertToElastic(e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
Expand Down Expand Up @@ -245,11 +246,17 @@ public boolean hasFieldLevelSecurity() {
}

/** Return a wrapped reader that only exposes allowed fields. */
public DirectoryReader filter(DirectoryReader reader, IndexVersion indexVersionCreated) throws IOException {
public DirectoryReader filter(DirectoryReader reader, IndexVersion indexVersionCreated, Function<String, Boolean> isMapped)
throws IOException {
if (hasFieldLevelSecurity() == false) {
return reader;
}
return FieldSubsetReader.wrap(reader, permittedFieldsAutomaton, IgnoredSourceFieldMapper.ignoredSourceFormat(indexVersionCreated));
return FieldSubsetReader.wrap(
reader,
permittedFieldsAutomaton,
IgnoredSourceFieldMapper.ignoredSourceFormat(indexVersionCreated),
isMapped
);
}

Automaton getIncludeAutomaton() {
Expand Down
Loading