diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java
index 7d053a69e8915..5ae98c8847285 100644
--- a/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java
@@ -76,6 +76,8 @@
import org.elasticsearch.client.security.PutRoleResponse;
import org.elasticsearch.client.security.PutUserRequest;
import org.elasticsearch.client.security.PutUserResponse;
+import org.elasticsearch.client.security.QueryApiKeyRequest;
+import org.elasticsearch.client.security.QueryApiKeyResponse;
import java.io.IOException;
@@ -1050,7 +1052,7 @@ public Cancellable createApiKeyAsync(final CreateApiKeyRequest request, final Re
*
* @param request the request to retrieve API key(s)
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
- * @return the response from the create API key call
+ * @return the response from the get API key call
* @throws IOException in case there is a problem sending the request or parsing back the response
*/
public GetApiKeyResponse getApiKey(final GetApiKeyRequest request, final RequestOptions options) throws IOException {
@@ -1137,6 +1139,37 @@ public Cancellable grantApiKeyAsync(final GrantApiKeyRequest request, final Requ
CreateApiKeyResponse::fromXContent, listener, emptySet());
}
+ /**
+ * Query and retrieve API Key(s) information.
+ * See
+ * the docs for more.
+ *
+ * @param request the request to query and retrieve API key(s)
+ * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+ * @return the response from the query API key call
+ * @throws IOException in case there is a problem sending the request or parsing back the response
+ */
+ public QueryApiKeyResponse queryApiKey(final QueryApiKeyRequest request, final RequestOptions options) throws IOException {
+ return restHighLevelClient.performRequestAndParseEntity(request, SecurityRequestConverters::queryApiKey, options,
+ QueryApiKeyResponse::fromXContent, emptySet());
+ }
+
+ /**
+ * Asynchronously query and retrieve API Key(s) information.
+ * See
+ * the docs for more.
+ *
+ * @param request the request to query and retrieve API key(s)
+ * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+ * @param listener the listener to be notified upon request completion
+ * @return cancellable that may be used to cancel the request
+ */
+ public Cancellable queryApiKeyAsync(final QueryApiKeyRequest request, final RequestOptions options,
+ final ActionListener listener) {
+ return restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::queryApiKey, options,
+ QueryApiKeyResponse::fromXContent, listener, emptySet());
+ }
+
/**
* Get a service account, or list of service accounts synchronously.
* See
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityRequestConverters.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityRequestConverters.java
index a5c1e8a37f2dd..624ff74d802ab 100644
--- a/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityRequestConverters.java
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityRequestConverters.java
@@ -44,6 +44,7 @@
import org.elasticsearch.client.security.PutRoleMappingRequest;
import org.elasticsearch.client.security.PutRoleRequest;
import org.elasticsearch.client.security.PutUserRequest;
+import org.elasticsearch.client.security.QueryApiKeyRequest;
import org.elasticsearch.client.security.SetUserEnabledRequest;
import org.elasticsearch.common.Strings;
@@ -346,6 +347,12 @@ static Request invalidateApiKey(final InvalidateApiKeyRequest invalidateApiKeyRe
return request;
}
+ static Request queryApiKey(final QueryApiKeyRequest queryApiKeyRequest) throws IOException {
+ final Request request = new Request(HttpGet.METHOD_NAME, "/_security/_query/api_key");
+ request.setEntity(createEntity(queryApiKeyRequest, REQUEST_BODY_CONTENT_TYPE));
+ return request;
+ }
+
static Request getServiceAccounts(final GetServiceAccountsRequest getServiceAccountsRequest) {
final RequestConverters.EndpointBuilder endpointBuilder = new RequestConverters.EndpointBuilder()
.addPathPartAsIs("_security/service");
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/QueryApiKeyRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/QueryApiKeyRequest.java
new file mode 100644
index 0000000000000..03b51388408cb
--- /dev/null
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/QueryApiKeyRequest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.client.security;
+
+import org.elasticsearch.client.Validatable;
+import org.elasticsearch.client.ValidationException;
+import org.elasticsearch.common.xcontent.ToXContentObject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.core.Nullable;
+import org.elasticsearch.index.query.QueryBuilder;
+import org.elasticsearch.search.searchafter.SearchAfterBuilder;
+import org.elasticsearch.search.sort.FieldSortBuilder;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+public final class QueryApiKeyRequest implements Validatable, ToXContentObject {
+
+ @Nullable
+ private QueryBuilder queryBuilder;
+ private Integer from;
+ private Integer size;
+ @Nullable
+ private List fieldSortBuilders;
+ @Nullable
+ private SearchAfterBuilder searchAfterBuilder;
+
+ public QueryApiKeyRequest() {
+ this(null, null, null, null, null);
+ }
+
+ public QueryApiKeyRequest(
+ @Nullable QueryBuilder queryBuilder,
+ @Nullable Integer from,
+ @Nullable Integer size,
+ @Nullable List fieldSortBuilders,
+ @Nullable SearchAfterBuilder searchAfterBuilder) {
+ this.queryBuilder = queryBuilder;
+ this.from = from;
+ this.size = size;
+ this.fieldSortBuilders = fieldSortBuilders;
+ this.searchAfterBuilder = searchAfterBuilder;
+ }
+
+ public QueryBuilder getQueryBuilder() {
+ return queryBuilder;
+ }
+
+ public int getFrom() {
+ return from;
+ }
+
+ public int getSize() {
+ return size;
+ }
+
+ public List getFieldSortBuilders() {
+ return fieldSortBuilders;
+ }
+
+ public SearchAfterBuilder getSearchAfterBuilder() {
+ return searchAfterBuilder;
+ }
+
+ public QueryApiKeyRequest queryBuilder(QueryBuilder queryBuilder) {
+ this.queryBuilder = queryBuilder;
+ return this;
+ }
+
+ public QueryApiKeyRequest from(int from) {
+ this.from = from;
+ return this;
+ }
+
+ public QueryApiKeyRequest size(int size) {
+ this.size = size;
+ return this;
+ }
+
+ public QueryApiKeyRequest fieldSortBuilders(List fieldSortBuilders) {
+ this.fieldSortBuilders = fieldSortBuilders;
+ return this;
+ }
+
+ public QueryApiKeyRequest searchAfterBuilder(SearchAfterBuilder searchAfterBuilder) {
+ this.searchAfterBuilder = searchAfterBuilder;
+ return this;
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ builder.startObject();
+ if (queryBuilder != null) {
+ builder.field("query");
+ queryBuilder.toXContent(builder, params);
+ }
+ if (from != null) {
+ builder.field("from", from);
+ }
+ if (size != null) {
+ builder.field("size", size);
+ }
+ if (fieldSortBuilders != null && false == fieldSortBuilders.isEmpty()) {
+ builder.field("sort", fieldSortBuilders);
+ }
+ if (searchAfterBuilder != null) {
+ builder.array(SearchAfterBuilder.SEARCH_AFTER.getPreferredName(), searchAfterBuilder.getSortValues());
+ }
+ return builder.endObject();
+ }
+
+ @Override
+ public Optional validate() {
+ ValidationException validationException = null;
+ if (from != null && from < 0) {
+ validationException = addValidationError(validationException, "from must be non-negative");
+ }
+ if (size != null && size < 0) {
+ validationException = addValidationError(validationException, "size must be non-negative");
+ }
+ return validationException == null ? Optional.empty() : Optional.of(validationException);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ QueryApiKeyRequest that = (QueryApiKeyRequest) o;
+ return Objects.equals(queryBuilder, that.queryBuilder) && Objects.equals(from, that.from) && Objects.equals(
+ size,
+ that.size) && Objects.equals(fieldSortBuilders, that.fieldSortBuilders) && Objects.equals(
+ searchAfterBuilder,
+ that.searchAfterBuilder);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(queryBuilder, from, size, fieldSortBuilders, searchAfterBuilder);
+ }
+
+ private ValidationException addValidationError(ValidationException validationException, String message) {
+ if (validationException == null) {
+ validationException = new ValidationException();
+ }
+ validationException.addValidationError(message);
+ return validationException;
+ }
+}
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/QueryApiKeyResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/QueryApiKeyResponse.java
new file mode 100644
index 0000000000000..811fc62976400
--- /dev/null
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/QueryApiKeyResponse.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.client.security;
+
+import org.elasticsearch.client.security.support.ApiKey;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.ParseField;
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+import java.util.List;
+
+import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
+import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg;
+
+public final class QueryApiKeyResponse {
+
+ private final long total;
+ private final List apiKeys;
+
+ public QueryApiKeyResponse(long total, List apiKeys) {
+ this.total = total;
+ this.apiKeys = apiKeys;
+ }
+
+ public long getTotal() {
+ return total;
+ }
+
+ public int getCount() {
+ return apiKeys.size();
+ }
+
+ public List getApiKeys() {
+ return apiKeys;
+ }
+
+ public static QueryApiKeyResponse fromXContent(XContentParser parser) throws IOException {
+ return PARSER.parse(parser, null);
+ }
+
+ static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>(
+ "query_api_key_response",
+ args -> {
+ final long total = (long) args[0];
+ final int count = (int) args[1];
+ @SuppressWarnings("unchecked")
+ final List items = (List) args[2];
+ if (count != items.size()) {
+ throw new IllegalArgumentException("count [" + count + "] is not equal to number of items ["
+ + items.size() + "]");
+ }
+ return new QueryApiKeyResponse(total, items);
+ }
+ );
+
+ static {
+ PARSER.declareLong(constructorArg(), new ParseField("total"));
+ PARSER.declareInt(constructorArg(), new ParseField("count"));
+ PARSER.declareObjectArray(optionalConstructorArg(), (p, c) -> ApiKey.fromXContent(p), new ParseField("api_keys"));
+ }
+}
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/support/ApiKey.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/support/ApiKey.java
index 8989c92e20f36..65fa26edd3f23 100644
--- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/support/ApiKey.java
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/support/ApiKey.java
@@ -12,9 +12,12 @@
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.ObjectParser;
import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.core.Nullable;
import java.io.IOException;
import java.time.Instant;
+import java.util.Arrays;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -34,9 +37,16 @@ public final class ApiKey {
private final String username;
private final String realm;
private final Map metadata;
+ @Nullable
+ private final Object[] sortValues;
public ApiKey(String name, String id, Instant creation, Instant expiration, boolean invalidated, String username, String realm,
Map metadata) {
+ this(name, id, creation, expiration, invalidated, username, realm, metadata, null);
+ }
+
+ public ApiKey(String name, String id, Instant creation, Instant expiration, boolean invalidated, String username, String realm,
+ Map metadata, @Nullable Object[] sortValues) {
this.name = name;
this.id = id;
// As we do not yet support the nanosecond precision when we serialize to JSON,
@@ -48,6 +58,7 @@ public ApiKey(String name, String id, Instant creation, Instant expiration, bool
this.username = username;
this.realm = realm;
this.metadata = metadata;
+ this.sortValues = sortValues;
}
public String getId() {
@@ -98,9 +109,21 @@ public Map getMetadata() {
return metadata;
}
+ /**
+ * API keys can be retrieved with either {@link org.elasticsearch.client.security.GetApiKeyRequest}
+ * or {@link org.elasticsearch.client.security.QueryApiKeyRequest}. When sorting is specified for
+ * QueryApiKeyRequest, the sort values for each key is returned along with each API key.
+ *
+ * @return Sort values for this API key if it is retrieved with QueryApiKeyRequest and sorting is
+ * required. Otherwise, it is null.
+ */
+ public Object[] getSortValues() {
+ return sortValues;
+ }
+
@Override
public int hashCode() {
- return Objects.hash(name, id, creation, expiration, invalidated, username, realm, metadata);
+ return Objects.hash(name, id, creation, expiration, invalidated, username, realm, metadata, Arrays.hashCode(sortValues));
}
@Override
@@ -122,14 +145,22 @@ public boolean equals(Object obj) {
&& Objects.equals(invalidated, other.invalidated)
&& Objects.equals(username, other.username)
&& Objects.equals(realm, other.realm)
- && Objects.equals(metadata, other.metadata);
+ && Objects.equals(metadata, other.metadata)
+ && Arrays.equals(sortValues, other.sortValues);
}
@SuppressWarnings("unchecked")
static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>("api_key", args -> {
+ final Object[] sortValues;
+ if (args[8] == null) {
+ sortValues = null;
+ } else {
+ final List