Skip to content

Commit b3df23e

Browse files
[Backport 2.x] Remove static metaFields list and get version-specific values from core (#4412)
Signed-off-by: Craig Perkins <[email protected]> Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 600f4e2 commit b3df23e

File tree

5 files changed

+123
-19
lines changed

5 files changed

+123
-19
lines changed

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,7 @@ dependencies {
724724
exclude(group: 'org.hamcrest', module: 'hamcrest')
725725
}
726726
integrationTestImplementation 'com.unboundid:unboundid-ldapsdk:4.0.14'
727+
integrationTestImplementation "org.opensearch.plugin:mapper-size:${opensearch_version}"
727728
integrationTestImplementation "org.apache.httpcomponents:httpclient-cache:4.5.14"
728729
integrationTestImplementation "org.apache.httpcomponents:httpclient:4.5.14"
729730
integrationTestImplementation "org.apache.httpcomponents:fluent-hc:4.5.14"

src/integrationTest/java/org/opensearch/security/FlsAndFieldMaskingTests.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.junit.runner.RunWith;
2828

2929
import org.opensearch.action.admin.indices.alias.IndicesAliasesRequest;
30+
import org.opensearch.action.admin.indices.create.CreateIndexRequest;
3031
import org.opensearch.action.fieldcaps.FieldCapabilitiesRequest;
3132
import org.opensearch.action.fieldcaps.FieldCapabilitiesResponse;
3233
import org.opensearch.action.get.GetRequest;
@@ -43,10 +44,15 @@
4344
import org.opensearch.action.search.SearchScrollRequest;
4445
import org.opensearch.client.Client;
4546
import org.opensearch.client.RestHighLevelClient;
47+
import org.opensearch.index.mapper.SourceFieldMapper;
48+
import org.opensearch.index.mapper.size.SizeFieldMapper;
49+
import org.opensearch.index.query.MatchAllQueryBuilder;
4650
import org.opensearch.index.query.QueryBuilder;
4751
import org.opensearch.index.query.QueryBuilders;
52+
import org.opensearch.plugin.mapper.MapperSizePlugin;
4853
import org.opensearch.search.aggregations.Aggregation;
4954
import org.opensearch.search.aggregations.metrics.ParsedAvg;
55+
import org.opensearch.search.builder.SearchSourceBuilder;
5056
import org.opensearch.search.sort.SortOrder;
5157
import org.opensearch.test.framework.TestSecurityConfig;
5258
import org.opensearch.test.framework.cluster.ClusterManager;
@@ -55,6 +61,7 @@
5561

5662
import static org.hamcrest.MatcherAssert.assertThat;
5763
import static org.hamcrest.Matchers.allOf;
64+
import static org.hamcrest.Matchers.containsString;
5865
import static org.hamcrest.Matchers.everyItem;
5966
import static org.hamcrest.Matchers.greaterThan;
6067
import static org.hamcrest.Matchers.instanceOf;
@@ -94,6 +101,7 @@
94101
import static org.opensearch.test.framework.matcher.SearchResponseMatchers.isSuccessfulSearchResponse;
95102
import static org.opensearch.test.framework.matcher.SearchResponseMatchers.numberOfTotalHitsIsEqualTo;
96103
import static org.opensearch.test.framework.matcher.SearchResponseMatchers.searchHitContainsFieldWithValue;
104+
import static org.opensearch.test.framework.matcher.SearchResponseMatchers.searchHitDoesContainField;
97105
import static org.opensearch.test.framework.matcher.SearchResponseMatchers.searchHitDoesNotContainField;
98106
import static org.opensearch.test.framework.matcher.SearchResponseMatchers.searchHitsContainDocumentWithId;
99107

@@ -212,6 +220,7 @@ public class FlsAndFieldMaskingTests {
212220
.nodeSettings(
213221
Map.of("plugins.security.restapi.roles_enabled", List.of("user_" + ADMIN_USER.getName() + "__" + ALL_ACCESS.getName()))
214222
)
223+
.plugin(MapperSizePlugin.class)
215224
.authc(AUTHC_HTTPBASIC_INTERNAL)
216225
.users(
217226
ADMIN_USER,
@@ -426,6 +435,10 @@ public void flsEnabledFieldsAreHiddenForNormalUsers() throws IOException {
426435

427436
private static List<String> createIndexWithDocs(String indexName, Song... songs) {
428437
try (Client client = cluster.getInternalNodeClient()) {
438+
client.admin()
439+
.indices()
440+
.create(new CreateIndexRequest(indexName).mapping(Map.of("_size", Map.of("enabled", true))))
441+
.actionGet();
429442
return Stream.of(songs).map(song -> {
430443
IndexResponse response = client.index(new IndexRequest(indexName).setRefreshPolicy(IMMEDIATE).source(song.asMap()))
431444
.actionGet();
@@ -469,6 +482,14 @@ private static void assertSearchHitsDoNotContainField(SearchResponse response, S
469482
.forEach(index -> assertThat(response, searchHitDoesNotContainField(index, excludedField)));
470483
}
471484

485+
private static void assertSearchHitsDoContainField(SearchResponse response, String includedField) {
486+
assertThat(response, isSuccessfulSearchResponse());
487+
assertThat(response.getHits().getHits().length, greaterThan(0));
488+
IntStream.range(0, response.getHits().getHits().length)
489+
.boxed()
490+
.forEach(index -> assertThat(response, searchHitDoesContainField(index, includedField)));
491+
}
492+
472493
@Test
473494
public void searchForDocuments() throws IOException {
474495
// FIELD MASKING
@@ -811,4 +832,28 @@ public void getFieldCapabilities() throws IOException {
811832
}
812833
}
813834

835+
@Test
836+
public void flsWithIncludesRulesIncludesFieldMappersFromPlugins() throws IOException {
837+
String indexName = "fls_includes_index";
838+
TestSecurityConfig.Role userRole = new TestSecurityConfig.Role("fls_include_stars_reader").clusterPermissions(
839+
"cluster_composite_ops_ro"
840+
).indexPermissions("read").fls(FIELD_STARS).on("*");
841+
TestSecurityConfig.User user = createUserWithRole("fls_includes_user", userRole);
842+
List<String> docIds = createIndexWithDocs(indexName, SONGS[0], SONGS[1]);
843+
844+
try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(user)) {
845+
SearchRequest searchRequest = new SearchRequest(indexName);
846+
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
847+
MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
848+
searchSourceBuilder.storedFields(List.of(SizeFieldMapper.NAME, SourceFieldMapper.NAME));
849+
searchSourceBuilder.query(matchAllQueryBuilder);
850+
searchRequest.source(searchSourceBuilder);
851+
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, DEFAULT);
852+
853+
assertSearchHitsDoContainField(searchResponse, FIELD_STARS);
854+
assertThat(searchResponse.toString(), containsString(SizeFieldMapper.NAME));
855+
assertSearchHitsDoNotContainField(searchResponse, FIELD_ARTIST);
856+
}
857+
}
858+
814859
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*
5+
* The OpenSearch Contributors require contributions made to
6+
* this file be licensed under the Apache-2.0 license or a
7+
* compatible open source license.
8+
*
9+
*/
10+
package org.opensearch.test.framework.matcher;
11+
12+
import java.util.Map;
13+
14+
import org.hamcrest.Description;
15+
import org.hamcrest.TypeSafeDiagnosingMatcher;
16+
17+
import org.opensearch.action.search.SearchResponse;
18+
import org.opensearch.search.SearchHit;
19+
20+
import static java.util.Objects.requireNonNull;
21+
import static org.opensearch.test.framework.matcher.SearchResponseMatchers.readTotalHits;
22+
23+
class SearchHitDoesContainFieldMatcher extends TypeSafeDiagnosingMatcher<SearchResponse> {
24+
25+
private final int hitIndex;
26+
27+
private final String fieldName;
28+
29+
public SearchHitDoesContainFieldMatcher(int hitIndex, String fieldName) {
30+
this.hitIndex = hitIndex;
31+
this.fieldName = requireNonNull(fieldName, "Field name is required.");
32+
}
33+
34+
@Override
35+
protected boolean matchesSafely(SearchResponse searchResponse, Description mismatchDescription) {
36+
Long numberOfHits = readTotalHits(searchResponse);
37+
if (numberOfHits == null) {
38+
mismatchDescription.appendText("Total number of hits is unknown.");
39+
return false;
40+
}
41+
if (hitIndex >= numberOfHits) {
42+
mismatchDescription.appendText("Search result contain only ").appendValue(numberOfHits).appendText(" hits");
43+
return false;
44+
}
45+
SearchHit searchHit = searchResponse.getHits().getAt(hitIndex);
46+
Map<String, Object> source = searchHit.getSourceAsMap();
47+
if (source == null) {
48+
mismatchDescription.appendText("Source document is null, is fetch source option set to true?");
49+
return false;
50+
}
51+
if (!source.containsKey(fieldName)) {
52+
mismatchDescription.appendText(" document does not contain field ").appendValue(fieldName);
53+
return false;
54+
}
55+
return true;
56+
}
57+
58+
@Override
59+
public void describeTo(Description description) {
60+
description.appendText("search hit with index ").appendValue(hitIndex).appendText(" does contain field ").appendValue(fieldName);
61+
}
62+
}

src/integrationTest/java/org/opensearch/test/framework/matcher/SearchResponseMatchers.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ public static Matcher<SearchResponse> searchHitDoesNotContainField(int hitIndex,
4444
return new SearchHitDoesNotContainFieldMatcher(hitIndex, fieldName);
4545
}
4646

47+
public static Matcher<SearchResponse> searchHitDoesContainField(int hitIndex, String fieldName) {
48+
return new SearchHitDoesContainFieldMatcher(hitIndex, fieldName);
49+
}
50+
4751
public static Matcher<SearchResponse> searchHitsContainDocumentWithId(int hitIndex, String indexName, String documentId) {
4852
return new SearchHitsContainDocumentWithIdMatcher(hitIndex, indexName, documentId);
4953
}

src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
package org.opensearch.security.configuration;
1313

1414
import java.io.IOException;
15+
import java.util.Arrays;
16+
import java.util.Collections;
1517
import java.util.HashSet;
1618
import java.util.Map;
1719
import java.util.Set;
@@ -26,7 +28,7 @@
2628
import org.opensearch.common.settings.Settings;
2729
import org.opensearch.core.index.shard.ShardId;
2830
import org.opensearch.index.IndexService;
29-
import org.opensearch.index.mapper.IgnoredFieldMapper;
31+
import org.opensearch.index.mapper.SeqNoFieldMapper;
3032
import org.opensearch.index.query.QueryShardContext;
3133
import org.opensearch.index.shard.ShardUtils;
3234
import org.opensearch.security.auditlog.AuditLog;
@@ -38,24 +40,9 @@
3840

3941
public class SecurityFlsDlsIndexSearcherWrapper extends SecurityIndexSearcherWrapper {
4042

41-
// TODO: the list is outdated. It is necessary to change how meta fields are handled in the near future.
42-
// We may consider using MapperService.isMetadataField() instead of relying on the static set or
43-
// (if it is too costly or does not meet requirements) use IndicesModule.getBuiltInMetadataFields()
44-
// for OpenSearch version specific Set of meta fields
45-
private static final Set<String> metaFields = Sets.newHashSet(
46-
"_source",
47-
"_version",
48-
"_field_names",
49-
"_seq_no",
50-
"_primary_term",
51-
"_id",
52-
IgnoredFieldMapper.NAME,
53-
"_index",
54-
"_routing",
55-
"_size",
56-
"_timestamp",
57-
"_ttl",
58-
"_type"
43+
private final Set<String> metaFields;
44+
public static final Set<String> META_FIELDS_BEFORE_7DOT8 = Collections.unmodifiableSet(
45+
new HashSet<>(Arrays.asList("_timestamp", "_ttl", "_type"))
5946
);
6047
private final ClusterService clusterService;
6148
private final IndexService indexService;
@@ -75,6 +62,11 @@ public SecurityFlsDlsIndexSearcherWrapper(
7562
final Salt salt
7663
) {
7764
super(indexService, settings, adminDNs, evaluator);
65+
Set<String> metadataFieldsCopy = new HashSet<>(indexService.mapperService().getMetadataFields());
66+
SeqNoFieldMapper.SequenceIDFields sequenceIDFields = SeqNoFieldMapper.SequenceIDFields.emptySeqID();
67+
metadataFieldsCopy.add(sequenceIDFields.primaryTerm.name());
68+
metadataFieldsCopy.addAll(META_FIELDS_BEFORE_7DOT8);
69+
metaFields = metadataFieldsCopy;
7870
ciol.setIs(indexService);
7971
this.clusterService = clusterService;
8072
this.indexService = indexService;

0 commit comments

Comments
 (0)