Skip to content

Commit 111a69d

Browse files
Support match for the Query API Key API (#104594)
This adds support for the `match` query type to the Query API key Information API. Note that since string values associated to API Keys are mapped as `keywords`, a `match` query with no analyzer parameter is effectively equivalent to a `term` query for such fields (e.g. `name`, `username`, `realm_name`). Relates: #101691
1 parent 0623eb0 commit 111a69d

File tree

5 files changed

+262
-30
lines changed

5 files changed

+262
-30
lines changed

docs/changelog/104594.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 104594
2+
summary: Support of `match` for the Query API Key API
3+
area: Authentication
4+
type: enhancement
5+
issues: []

docs/reference/rest-api/security/query-api-key.asciidoc

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,20 @@ You can specify the following parameters in the request body:
5252
(Optional, string) A <<query-dsl,query>> to filter which API keys to return.
5353
The query supports a subset of query types, including
5454
<<query-dsl-match-all-query,`match_all`>>, <<query-dsl-bool-query,`bool`>>,
55-
<<query-dsl-term-query,`term`>>, <<query-dsl-terms-query,`terms`>>, <<query-dsl-ids-query,`ids`>>,
56-
<<query-dsl-prefix-query,`prefix`>>, <<query-dsl-wildcard-query,`wildcard`>>, <<query-dsl-exists-query,`exists`>>,
57-
<<query-dsl-range-query,`range`>>, and <<query-dsl-simple-query-string-query,`simple query string`>>
55+
<<query-dsl-term-query,`term`>>, <<query-dsl-terms-query,`terms`>>,
56+
<<query-dsl-match-query,`match`>>, <<query-dsl-ids-query,`ids`>>,
57+
<<query-dsl-prefix-query,`prefix`>>, <<query-dsl-wildcard-query,`wildcard`>>,
58+
<<query-dsl-exists-query,`exists`>>, <<query-dsl-range-query,`range`>>,
59+
and <<query-dsl-simple-query-string-query,`simple query string`>>
5860
+
5961
You can query the following public values associated with an API key.
6062
+
63+
NOTE: The queryable string values associated with API keys are internally mapped as <<keyword,`keywords`>>.
64+
Consequently, if no <<analysis-analyzers,`analyzer`>> parameter is specified for a
65+
<<query-dsl-match-query,`match`>> query, then the provided match query string is interpreted as
66+
a single keyword value. Such a <<query-dsl-match-query,`match`>> query is hence equivalent to a
67+
<<query-dsl-term-query,`term`>> query.
68+
+
6169
.Valid values for `query`
6270
[%collapsible%open]
6371
====

x-pack/plugin/security/qa/security-basic/src/javaRestTest/java/org/elasticsearch/xpack/security/QueryApiKeyIT.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,21 @@ public void testQuery() throws IOException {
6363
apiKeys.forEach(k -> assertThat(k, not(hasKey("_sort"))));
6464
});
6565

66+
assertQuery(API_KEY_ADMIN_AUTH_HEADER, """
67+
{ "query": { "match": {"name": {"query": "my-ingest-key-1 my-org/alert-key-1", "analyzer": "whitespace"} } } }""", apiKeys -> {
68+
assertThat(apiKeys.size(), equalTo(2));
69+
assertThat(apiKeys.get(0).get("name"), oneOf("my-ingest-key-1", "my-org/alert-key-1"));
70+
assertThat(apiKeys.get(1).get("name"), oneOf("my-ingest-key-1", "my-org/alert-key-1"));
71+
apiKeys.forEach(k -> assertThat(k, not(hasKey("_sort"))));
72+
});
73+
6674
// An empty request body means search for all keys
6775
assertQuery(API_KEY_ADMIN_AUTH_HEADER, randomBoolean() ? "" : """
6876
{"query":{"match_all":{}}}""", apiKeys -> assertThat(apiKeys.size(), equalTo(6)));
6977

78+
assertQuery(API_KEY_ADMIN_AUTH_HEADER, randomBoolean() ? "" : """
79+
{ "query": { "match": {"type": "rest"} } }""", apiKeys -> assertThat(apiKeys.size(), equalTo(6)));
80+
7081
assertQuery(
7182
API_KEY_ADMIN_AUTH_HEADER,
7283
"""

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/ApiKeyBoolQueryBuilder.java

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.elasticsearch.index.query.IdsQueryBuilder;
1515
import org.elasticsearch.index.query.MatchAllQueryBuilder;
1616
import org.elasticsearch.index.query.MatchNoneQueryBuilder;
17+
import org.elasticsearch.index.query.MatchQueryBuilder;
1718
import org.elasticsearch.index.query.PrefixQueryBuilder;
1819
import org.elasticsearch.index.query.QueryBuilder;
1920
import org.elasticsearch.index.query.QueryBuilders;
@@ -111,7 +112,8 @@ private static QueryBuilder doProcess(QueryBuilder qb, Consumer<String> fieldNam
111112
if (qb instanceof final BoolQueryBuilder query) {
112113
final BoolQueryBuilder newQuery = QueryBuilders.boolQuery()
113114
.minimumShouldMatch(query.minimumShouldMatch())
114-
.adjustPureNegative(query.adjustPureNegative());
115+
.adjustPureNegative(query.adjustPureNegative())
116+
.boost(query.boost());
115117
query.must().stream().map(q -> ApiKeyBoolQueryBuilder.doProcess(q, fieldNameVisitor)).forEach(newQuery::must);
116118
query.should().stream().map(q -> ApiKeyBoolQueryBuilder.doProcess(q, fieldNameVisitor)).forEach(newQuery::should);
117119
query.mustNot().stream().map(q -> ApiKeyBoolQueryBuilder.doProcess(q, fieldNameVisitor)).forEach(newQuery::mustNot);
@@ -124,28 +126,63 @@ private static QueryBuilder doProcess(QueryBuilder qb, Consumer<String> fieldNam
124126
} else if (qb instanceof final TermQueryBuilder query) {
125127
final String translatedFieldName = ApiKeyFieldNameTranslators.translate(query.fieldName());
126128
fieldNameVisitor.accept(translatedFieldName);
127-
return QueryBuilders.termQuery(translatedFieldName, query.value()).caseInsensitive(query.caseInsensitive());
129+
return QueryBuilders.termQuery(translatedFieldName, query.value())
130+
.caseInsensitive(query.caseInsensitive())
131+
.boost(query.boost());
128132
} else if (qb instanceof final ExistsQueryBuilder query) {
129133
final String translatedFieldName = ApiKeyFieldNameTranslators.translate(query.fieldName());
130134
fieldNameVisitor.accept(translatedFieldName);
131-
return QueryBuilders.existsQuery(translatedFieldName);
135+
return QueryBuilders.existsQuery(translatedFieldName).boost(query.boost());
132136
} else if (qb instanceof final TermsQueryBuilder query) {
133137
if (query.termsLookup() != null) {
134138
throw new IllegalArgumentException("terms query with terms lookup is not supported for API Key query");
135139
}
136140
final String translatedFieldName = ApiKeyFieldNameTranslators.translate(query.fieldName());
137141
fieldNameVisitor.accept(translatedFieldName);
138-
return QueryBuilders.termsQuery(translatedFieldName, query.getValues());
142+
return QueryBuilders.termsQuery(translatedFieldName, query.getValues()).boost(query.boost());
139143
} else if (qb instanceof final PrefixQueryBuilder query) {
140144
final String translatedFieldName = ApiKeyFieldNameTranslators.translate(query.fieldName());
141145
fieldNameVisitor.accept(translatedFieldName);
142-
return QueryBuilders.prefixQuery(translatedFieldName, query.value()).caseInsensitive(query.caseInsensitive());
146+
return QueryBuilders.prefixQuery(translatedFieldName, query.value())
147+
.caseInsensitive(query.caseInsensitive())
148+
.rewrite(query.rewrite())
149+
.boost(query.boost());
143150
} else if (qb instanceof final WildcardQueryBuilder query) {
144151
final String translatedFieldName = ApiKeyFieldNameTranslators.translate(query.fieldName());
145152
fieldNameVisitor.accept(translatedFieldName);
146153
return QueryBuilders.wildcardQuery(translatedFieldName, query.value())
147154
.caseInsensitive(query.caseInsensitive())
148-
.rewrite(query.rewrite());
155+
.rewrite(query.rewrite())
156+
.boost(query.boost());
157+
} else if (qb instanceof final MatchQueryBuilder query) {
158+
final String translatedFieldName = ApiKeyFieldNameTranslators.translate(query.fieldName());
159+
fieldNameVisitor.accept(translatedFieldName);
160+
final MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery(translatedFieldName, query.value());
161+
if (query.operator() != null) {
162+
matchQueryBuilder.operator(query.operator());
163+
}
164+
if (query.analyzer() != null) {
165+
matchQueryBuilder.analyzer(query.analyzer());
166+
}
167+
if (query.fuzziness() != null) {
168+
matchQueryBuilder.fuzziness(query.fuzziness());
169+
}
170+
if (query.minimumShouldMatch() != null) {
171+
matchQueryBuilder.minimumShouldMatch(query.minimumShouldMatch());
172+
}
173+
if (query.fuzzyRewrite() != null) {
174+
matchQueryBuilder.fuzzyRewrite(query.fuzzyRewrite());
175+
}
176+
if (query.zeroTermsQuery() != null) {
177+
matchQueryBuilder.zeroTermsQuery(query.zeroTermsQuery());
178+
}
179+
matchQueryBuilder.prefixLength(query.prefixLength())
180+
.maxExpansions(query.maxExpansions())
181+
.fuzzyTranspositions(query.fuzzyTranspositions())
182+
.lenient(query.lenient())
183+
.autoGenerateSynonymsPhraseQuery(query.autoGenerateSynonymsPhraseQuery())
184+
.boost(query.boost());
185+
return matchQueryBuilder;
149186
} else if (qb instanceof final RangeQueryBuilder query) {
150187
if (query.relation() != null) {
151188
throw new IllegalArgumentException("range query with relation is not supported for API Key query");

0 commit comments

Comments
 (0)