Skip to content

Commit 312dd24

Browse files
Alias filters
1 parent ecabe86 commit 312dd24

File tree

8 files changed

+151
-22
lines changed

8 files changed

+151
-22
lines changed

x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/lookup/QueryList.java

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.elasticsearch.index.mapper.MappedFieldType;
3535
import org.elasticsearch.index.mapper.RangeFieldMapper;
3636
import org.elasticsearch.index.query.SearchExecutionContext;
37+
import org.elasticsearch.search.internal.AliasFilter;
3738

3839
import java.io.IOException;
3940
import java.io.UncheckedIOException;
@@ -46,6 +47,7 @@
4647
*/
4748
public abstract class QueryList {
4849
protected final SearchExecutionContext searchExecutionContext;
50+
protected final AliasFilter aliasFilter;
4951
protected final MappedFieldType field;
5052
protected final Block block;
5153
@Nullable
@@ -54,10 +56,12 @@ public abstract class QueryList {
5456
protected QueryList(
5557
MappedFieldType field,
5658
SearchExecutionContext searchExecutionContext,
59+
AliasFilter aliasFilter,
5760
Block block,
5861
OnlySingleValueParams onlySingleValueParams
5962
) {
6063
this.searchExecutionContext = searchExecutionContext;
64+
this.aliasFilter = aliasFilter;
6165
this.field = field;
6266
this.block = block;
6367
this.onlySingleValueParams = onlySingleValueParams;
@@ -74,7 +78,7 @@ int getPositionCount() {
7478
* Returns a copy of this query list that only returns queries for single-valued positions.
7579
* That is, it returns `null` queries for either multivalued or null positions.
7680
* <p>
77-
* Whenever a multi-value position is encountered, whether in the input block or in the queried index, a warning is emitted.
81+
* Whenever a multi-value position is encountered, whether in the input block or in the queried index, a warning is emitted.
7882
* </p>
7983
*/
8084
public abstract QueryList onlySingleValues(Warnings warnings, String multiValueWarningMessage);
@@ -93,6 +97,17 @@ final Query getQuery(int position) {
9397

9498
Query query = doGetQuery(position, firstValueIndex, valueCount);
9599

100+
if (aliasFilter != null && aliasFilter != AliasFilter.EMPTY) {
101+
BooleanQuery.Builder builder = new BooleanQuery.Builder();
102+
builder.add(query, BooleanClause.Occur.FILTER);
103+
try {
104+
builder.add(aliasFilter.getQueryBuilder().toQuery(searchExecutionContext), BooleanClause.Occur.FILTER);
105+
query = builder.build();
106+
} catch (IOException e) {
107+
throw new RuntimeException(e);// TODO
108+
}
109+
}
110+
96111
if (onlySingleValueParams != null) {
97112
query = wrapSingleValueQuery(query);
98113
}
@@ -138,7 +153,12 @@ private Query wrapSingleValueQuery(Query query) {
138153
* using only the {@link ElementType} of the {@link Block} to determine the
139154
* query.
140155
*/
141-
public static QueryList rawTermQueryList(MappedFieldType field, SearchExecutionContext searchExecutionContext, Block block) {
156+
public static QueryList rawTermQueryList(
157+
MappedFieldType field,
158+
SearchExecutionContext searchExecutionContext,
159+
AliasFilter aliasFilter,
160+
Block block
161+
) {
142162
IntFunction<Object> blockToJavaObject = switch (block.elementType()) {
143163
case BOOLEAN -> {
144164
BooleanBlock booleanBlock = (BooleanBlock) block;
@@ -170,17 +190,22 @@ public static QueryList rawTermQueryList(MappedFieldType field, SearchExecutionC
170190
case AGGREGATE_METRIC_DOUBLE -> throw new IllegalArgumentException("can't read values from [aggregate metric double] block");
171191
case UNKNOWN -> throw new IllegalArgumentException("can't read values from [" + block + "]");
172192
};
173-
return new TermQueryList(field, searchExecutionContext, block, null, blockToJavaObject);
193+
return new TermQueryList(field, searchExecutionContext, aliasFilter, block, null, blockToJavaObject);
174194
}
175195

176196
/**
177197
* Returns a list of term queries for the given field and the input block of
178198
* {@code ip} field values.
179199
*/
180-
public static QueryList ipTermQueryList(MappedFieldType field, SearchExecutionContext searchExecutionContext, BytesRefBlock block) {
200+
public static QueryList ipTermQueryList(
201+
MappedFieldType field,
202+
SearchExecutionContext searchExecutionContext,
203+
AliasFilter aliasFilter,
204+
BytesRefBlock block
205+
) {
181206
BytesRef scratch = new BytesRef();
182207
byte[] ipBytes = new byte[InetAddressPoint.BYTES];
183-
return new TermQueryList(field, searchExecutionContext, block, null, offset -> {
208+
return new TermQueryList(field, searchExecutionContext, aliasFilter, block, null, offset -> {
184209
final var bytes = block.getBytesRef(offset, scratch);
185210
if (ipBytes.length != bytes.length) {
186211
// Lucene only support 16-byte IP addresses, even IPv4 is encoded in 16 bytes
@@ -195,10 +220,16 @@ public static QueryList ipTermQueryList(MappedFieldType field, SearchExecutionCo
195220
* Returns a list of term queries for the given field and the input block of
196221
* {@code date} field values.
197222
*/
198-
public static QueryList dateTermQueryList(MappedFieldType field, SearchExecutionContext searchExecutionContext, LongBlock block) {
223+
public static QueryList dateTermQueryList(
224+
MappedFieldType field,
225+
SearchExecutionContext searchExecutionContext,
226+
AliasFilter aliasFilter,
227+
LongBlock block
228+
) {
199229
return new TermQueryList(
200230
field,
201231
searchExecutionContext,
232+
aliasFilter,
202233
block,
203234
null,
204235
field instanceof RangeFieldMapper.RangeFieldType rangeFieldType
@@ -210,8 +241,13 @@ public static QueryList dateTermQueryList(MappedFieldType field, SearchExecution
210241
/**
211242
* Returns a list of geo_shape queries for the given field and the input block.
212243
*/
213-
public static QueryList geoShapeQueryList(MappedFieldType field, SearchExecutionContext searchExecutionContext, Block block) {
214-
return new GeoShapeQueryList(field, searchExecutionContext, block, null);
244+
public static QueryList geoShapeQueryList(
245+
MappedFieldType field,
246+
SearchExecutionContext searchExecutionContext,
247+
AliasFilter aliasFilter,
248+
Block block
249+
) {
250+
return new GeoShapeQueryList(field, searchExecutionContext, aliasFilter, block, null);
215251
}
216252

217253
private static class TermQueryList extends QueryList {
@@ -220,11 +256,12 @@ private static class TermQueryList extends QueryList {
220256
private TermQueryList(
221257
MappedFieldType field,
222258
SearchExecutionContext searchExecutionContext,
259+
AliasFilter aliasFilter,
223260
Block block,
224261
OnlySingleValueParams onlySingleValueParams,
225262
IntFunction<Object> blockValueReader
226263
) {
227-
super(field, searchExecutionContext, block, onlySingleValueParams);
264+
super(field, searchExecutionContext, aliasFilter, block, onlySingleValueParams);
228265
this.blockValueReader = blockValueReader;
229266
}
230267

@@ -233,6 +270,7 @@ public TermQueryList onlySingleValues(Warnings warnings, String multiValueWarnin
233270
return new TermQueryList(
234271
field,
235272
searchExecutionContext,
273+
aliasFilter,
236274
block,
237275
new OnlySingleValueParams(warnings, multiValueWarningMessage),
238276
blockValueReader
@@ -264,10 +302,11 @@ private static class GeoShapeQueryList extends QueryList {
264302
private GeoShapeQueryList(
265303
MappedFieldType field,
266304
SearchExecutionContext searchExecutionContext,
305+
AliasFilter aliasFilter,
267306
Block block,
268307
OnlySingleValueParams onlySingleValueParams
269308
) {
270-
super(field, searchExecutionContext, block, onlySingleValueParams);
309+
super(field, searchExecutionContext, aliasFilter, block, onlySingleValueParams);
271310

272311
this.blockValueReader = blockToGeometry(block);
273312
this.shapeQuery = shapeQuery();
@@ -278,6 +317,7 @@ public GeoShapeQueryList onlySingleValues(Warnings warnings, String multiValueWa
278317
return new GeoShapeQueryList(
279318
field,
280319
searchExecutionContext,
320+
aliasFilter,
281321
block,
282322
new OnlySingleValueParams(warnings, multiValueWarningMessage)
283323
);

x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/lookup/EnrichQuerySourceOperatorTests.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.elasticsearch.index.mapper.MappedFieldType;
4242
import org.elasticsearch.index.query.SearchExecutionContext;
4343
import org.elasticsearch.indices.breaker.NoneCircuitBreakerService;
44+
import org.elasticsearch.search.internal.AliasFilter;
4445
import org.elasticsearch.test.ESTestCase;
4546
import org.junit.After;
4647
import org.junit.Before;
@@ -83,7 +84,7 @@ public void testQueries() throws Exception {
8384
var inputTerms = makeTermsBlock(List.of(List.of("b2"), List.of("c1", "a2"), List.of("z2"), List.of(), List.of("a3"), List.of()))
8485
) {
8586
MappedFieldType uidField = new KeywordFieldMapper.KeywordFieldType("uid");
86-
QueryList queryList = QueryList.rawTermQueryList(uidField, directoryData.searchExecutionContext, inputTerms);
87+
QueryList queryList = QueryList.rawTermQueryList(uidField, directoryData.searchExecutionContext, AliasFilter.EMPTY, inputTerms);
8788
assertThat(queryList.getPositionCount(), equalTo(6));
8889
assertThat(queryList.getQuery(0), equalTo(new TermQuery(new Term("uid", new BytesRef("b2")))));
8990
assertThat(queryList.getQuery(1), equalTo(new TermInSetQuery("uid", List.of(new BytesRef("c1"), new BytesRef("a2")))));
@@ -153,7 +154,12 @@ public void testRandomMatchQueries() throws Exception {
153154
}).toList();
154155

155156
try (var directoryData = makeDirectoryWith(directoryTermsList); var inputTerms = makeTermsBlock(inputTermsList)) {
156-
var queryList = QueryList.rawTermQueryList(directoryData.field, directoryData.searchExecutionContext, inputTerms);
157+
var queryList = QueryList.rawTermQueryList(
158+
directoryData.field,
159+
directoryData.searchExecutionContext,
160+
AliasFilter.EMPTY,
161+
inputTerms
162+
);
157163
int maxPageSize = between(1, 256);
158164
EnrichQuerySourceOperator queryOperator = new EnrichQuerySourceOperator(
159165
blockFactory,
@@ -190,8 +196,12 @@ public void testQueries_OnlySingleValues() throws Exception {
190196
List.of(List.of("b2"), List.of("c1", "a2"), List.of("z2"), List.of(), List.of("a3"), List.of("a3", "a2", "z2", "xx"))
191197
)
192198
) {
193-
QueryList queryList = QueryList.rawTermQueryList(directoryData.field, directoryData.searchExecutionContext, inputTerms)
194-
.onlySingleValues(warnings(), "multi-value found");
199+
QueryList queryList = QueryList.rawTermQueryList(
200+
directoryData.field,
201+
directoryData.searchExecutionContext,
202+
AliasFilter.EMPTY,
203+
inputTerms
204+
).onlySingleValues(warnings(), "multi-value found");
195205
// pos -> terms -> docs
196206
// -----------------------------
197207
// 0 -> [b2] -> []

x-pack/plugin/esql/qa/security/src/javaRestTest/java/org/elasticsearch/xpack/esql/EsqlSecurityIT.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -637,7 +637,7 @@ public void testLookupJoinIndexAllowed() throws Exception {
637637
);
638638
assertThat(respMap.get("values"), equalTo(List.of(List.of(40.0, "sales"))));
639639

640-
// user has permission on the alias, but can't read the key (doc level security)
640+
// user has permission on the alias, but can't read the key (doc level security at role level)
641641
resp = runESQLCommand(
642642
"metadata1_alias_read2",
643643
"ROW x = 32.0 | EVAL value = x | LOOKUP JOIN lookup-second-alias ON value | KEEP x, org"
@@ -654,6 +654,31 @@ public void testLookupJoinIndexAllowed() throws Exception {
654654
assertThat(row.size(), is(2));
655655
assertThat(row.get(0), is(32.0));
656656
assertThat(row.get(1), is(nullValue()));
657+
658+
// user has permission on the alias, the alias has a filter that doesn't allow to see the value
659+
resp = runESQLCommand("alias_user1", "ROW x = 12.0 | EVAL value = x | LOOKUP JOIN lookup-first-alias ON value | KEEP x, org");
660+
assertOK(resp);
661+
respMap = entityAsMap(resp);
662+
assertThat(
663+
respMap.get("columns"),
664+
equalTo(List.of(Map.of("name", "x", "type", "double"), Map.of("name", "org", "type", "keyword")))
665+
);
666+
values = (List<?>) respMap.get("values");
667+
assertThat(values.size(), is(1));
668+
row = (List<?>) values.get(0);
669+
assertThat(row.size(), is(2));
670+
assertThat(row.get(0), is(12.0));
671+
assertThat(row.get(1), is(nullValue()));
672+
673+
// user has permission on the alias, the alias has a filter that allows to see the value
674+
resp = runESQLCommand("alias_user1", "ROW x = 31.0 | EVAL value = x | LOOKUP JOIN lookup-first-alias ON value | KEEP x, org");
675+
assertOK(resp);
676+
respMap = entityAsMap(resp);
677+
assertThat(
678+
respMap.get("columns"),
679+
equalTo(List.of(Map.of("name", "x", "type", "double"), Map.of("name", "org", "type", "keyword")))
680+
);
681+
assertThat(respMap.get("values"), equalTo(List.of(List.of(31.0, "sales"))));
657682
}
658683

659684
@SuppressWarnings("unchecked")

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/enrich/AbstractLookupService.java

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
import org.elasticsearch.action.support.ChannelActionListener;
1515
import org.elasticsearch.action.support.IndicesOptions;
1616
import org.elasticsearch.cluster.ClusterState;
17+
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
18+
import org.elasticsearch.cluster.metadata.ProjectMetadata;
1719
import org.elasticsearch.cluster.node.DiscoveryNode;
1820
import org.elasticsearch.cluster.routing.ShardIterator;
1921
import org.elasticsearch.cluster.routing.ShardRouting;
@@ -52,6 +54,7 @@
5254
import org.elasticsearch.index.mapper.MappedFieldType;
5355
import org.elasticsearch.index.query.SearchExecutionContext;
5456
import org.elasticsearch.index.shard.ShardId;
57+
import org.elasticsearch.indices.IndicesService;
5558
import org.elasticsearch.search.SearchService;
5659
import org.elasticsearch.search.internal.AliasFilter;
5760
import org.elasticsearch.search.internal.SearchContext;
@@ -122,8 +125,10 @@
122125
public abstract class AbstractLookupService<R extends AbstractLookupService.Request, T extends AbstractLookupService.TransportRequest> {
123126
private final String actionName;
124127
protected final ClusterService clusterService;
128+
protected final IndicesService indicesService;
125129
private final LookupShardContextFactory lookupShardContextFactory;
126130
protected final TransportService transportService;
131+
IndexNameExpressionResolver indexNameExpressionResolver;
127132
protected final Executor executor;
128133
private final BigArrays bigArrays;
129134
private final BlockFactory blockFactory;
@@ -141,17 +146,21 @@ public abstract class AbstractLookupService<R extends AbstractLookupService.Requ
141146
AbstractLookupService(
142147
String actionName,
143148
ClusterService clusterService,
149+
IndicesService indicesService,
144150
LookupShardContextFactory lookupShardContextFactory,
145151
TransportService transportService,
152+
IndexNameExpressionResolver indexNameExpressionResolver,
146153
BigArrays bigArrays,
147154
BlockFactory blockFactory,
148155
boolean mergePages,
149156
CheckedBiFunction<StreamInput, BlockFactory, T, IOException> readRequest
150157
) {
151158
this.actionName = actionName;
152159
this.clusterService = clusterService;
160+
this.indicesService = indicesService;
153161
this.lookupShardContextFactory = lookupShardContextFactory;
154162
this.transportService = transportService;
163+
this.indexNameExpressionResolver = indexNameExpressionResolver;
155164
this.executor = transportService.getThreadPool().executor(ThreadPool.Names.SEARCH);
156165
this.bigArrays = bigArrays;
157166
this.blockFactory = blockFactory;
@@ -181,6 +190,7 @@ public ThreadContext getThreadContext() {
181190
protected abstract QueryList queryList(
182191
T request,
183192
SearchExecutionContext context,
193+
AliasFilter aliasFilter,
184194
Block inputBlock,
185195
@Nullable DataType inputDataType,
186196
Warnings warnings
@@ -199,13 +209,14 @@ protected abstract QueryList queryList(
199209
protected static QueryList termQueryList(
200210
MappedFieldType field,
201211
SearchExecutionContext searchExecutionContext,
212+
AliasFilter aliasFilter,
202213
Block block,
203214
@Nullable DataType inputDataType
204215
) {
205216
return switch (inputDataType) {
206-
case IP -> QueryList.ipTermQueryList(field, searchExecutionContext, (BytesRefBlock) block);
207-
case DATETIME -> QueryList.dateTermQueryList(field, searchExecutionContext, (LongBlock) block);
208-
case null, default -> QueryList.rawTermQueryList(field, searchExecutionContext, block);
217+
case IP -> QueryList.ipTermQueryList(field, searchExecutionContext, aliasFilter, (BytesRefBlock) block);
218+
case DATETIME -> QueryList.dateTermQueryList(field, searchExecutionContext, aliasFilter, (LongBlock) block);
219+
case null, default -> QueryList.rawTermQueryList(field, searchExecutionContext, aliasFilter, block);
209220
};
210221
}
211222

@@ -265,6 +276,14 @@ private void doLookup(T request, CancellableTask task, ActionListener<List<Page>
265276
final List<Releasable> releasables = new ArrayList<>(6);
266277
boolean started = false;
267278
try {
279+
280+
ProjectMetadata projMeta = clusterService.state().metadata().getProject();
281+
AliasFilter aliasFilter = indicesService.buildAliasFilter(
282+
clusterService.state().projectState(),
283+
request.shardId.getIndex().getName(),
284+
indexNameExpressionResolver.resolveExpressions(projMeta, request.indexPattern)
285+
);
286+
268287
LookupShardContext shardContext = lookupShardContextFactory.create(request.shardId);
269288
releasables.add(shardContext.release);
270289
final LocalCircuitBreaker localBreaker = new LocalCircuitBreaker(
@@ -310,7 +329,14 @@ private void doLookup(T request, CancellableTask task, ActionListener<List<Page>
310329
request.source.source().getColumnNumber(),
311330
request.source.text()
312331
);
313-
QueryList queryList = queryList(request, shardContext.executionContext, inputBlock, request.inputDataType, warnings);
332+
QueryList queryList = queryList(
333+
request,
334+
shardContext.executionContext,
335+
aliasFilter,
336+
inputBlock,
337+
request.inputDataType,
338+
warnings
339+
);
314340
var queryOperator = new EnrichQuerySourceOperator(
315341
driverContext.blockFactory(),
316342
EnrichQuerySourceOperator.DEFAULT_MAX_PAGE_SIZE,

0 commit comments

Comments
 (0)