HLRC support for query API key API (#76520)
This PR adds HLRC for the new Query API key API added with #75335 and #76144 Relates: #71023
This commit is contained in:
parent
c1a32447a7
commit
7bb1185806
|
@ -80,6 +80,8 @@ import org.elasticsearch.client.security.PutUserRequest;
|
|||
import org.elasticsearch.client.security.PutUserResponse;
|
||||
import org.elasticsearch.client.security.KibanaEnrollmentRequest;
|
||||
import org.elasticsearch.client.security.KibanaEnrollmentResponse;
|
||||
import org.elasticsearch.client.security.QueryApiKeyRequest;
|
||||
import org.elasticsearch.client.security.QueryApiKeyResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
@ -1054,7 +1056,7 @@ public final class SecurityClient {
|
|||
*
|
||||
* @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 {
|
||||
|
@ -1141,6 +1143,37 @@ public final class SecurityClient {
|
|||
CreateApiKeyResponse::fromXContent, listener, emptySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Query and retrieve API Key(s) information.<br>
|
||||
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-query-api-key.html">
|
||||
* the docs</a> 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.<br>
|
||||
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-query-api-key.html">
|
||||
* the docs</a> 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<QueryApiKeyResponse> listener) {
|
||||
return restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::queryApiKey, options,
|
||||
QueryApiKeyResponse::fromXContent, listener, emptySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a service account, or list of service accounts synchronously.
|
||||
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-service-accounts.html">
|
||||
|
|
|
@ -44,6 +44,7 @@ import org.elasticsearch.client.security.PutPrivilegesRequest;
|
|||
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 @@ final class SecurityRequestConverters {
|
|||
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");
|
||||
|
|
|
@ -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<FieldSortBuilder> 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<FieldSortBuilder> 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<FieldSortBuilder> 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<FieldSortBuilder> 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<ValidationException> 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;
|
||||
}
|
||||
}
|
|
@ -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<ApiKey> apiKeys;
|
||||
|
||||
public QueryApiKeyResponse(long total, List<ApiKey> apiKeys) {
|
||||
this.total = total;
|
||||
this.apiKeys = apiKeys;
|
||||
}
|
||||
|
||||
public long getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return apiKeys.size();
|
||||
}
|
||||
|
||||
public List<ApiKey> getApiKeys() {
|
||||
return apiKeys;
|
||||
}
|
||||
|
||||
public static QueryApiKeyResponse fromXContent(XContentParser parser) throws IOException {
|
||||
return PARSER.parse(parser, null);
|
||||
}
|
||||
|
||||
static final ConstructingObjectParser<QueryApiKeyResponse, Void> PARSER = new ConstructingObjectParser<>(
|
||||
"query_api_key_response",
|
||||
args -> {
|
||||
final long total = (long) args[0];
|
||||
final int count = (int) args[1];
|
||||
@SuppressWarnings("unchecked")
|
||||
final List<ApiKey> items = (List<ApiKey>) 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"));
|
||||
}
|
||||
}
|
|
@ -12,9 +12,12 @@ import org.elasticsearch.common.xcontent.ParseField;
|
|||
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<String, Object> metadata;
|
||||
@Nullable
|
||||
private final Object[] sortValues;
|
||||
|
||||
public ApiKey(String name, String id, Instant creation, Instant expiration, boolean invalidated, String username, String realm,
|
||||
Map<String, Object> 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<String, Object> 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 final class ApiKey {
|
|||
this.username = username;
|
||||
this.realm = realm;
|
||||
this.metadata = metadata;
|
||||
this.sortValues = sortValues;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
|
@ -98,9 +109,21 @@ public final class ApiKey {
|
|||
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 final class ApiKey {
|
|||
&& 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<ApiKey, Void> PARSER = new ConstructingObjectParser<>("api_key", args -> {
|
||||
final Object[] sortValues;
|
||||
if (args[8] == null) {
|
||||
sortValues = null;
|
||||
} else {
|
||||
final List<Object> arg8 = (List<Object>) args[8];
|
||||
sortValues = arg8.isEmpty() ? null : arg8.toArray();
|
||||
}
|
||||
return new ApiKey((String) args[0], (String) args[1], Instant.ofEpochMilli((Long) args[2]),
|
||||
(args[3] == null) ? null : Instant.ofEpochMilli((Long) args[3]), (Boolean) args[4], (String) args[5], (String) args[6],
|
||||
(Map<String, Object>) args[7]);
|
||||
(Map<String, Object>) args[7], sortValues);
|
||||
});
|
||||
static {
|
||||
PARSER.declareField(optionalConstructorArg(), (p, c) -> p.textOrNull(), new ParseField("name"),
|
||||
|
@ -141,6 +172,7 @@ public final class ApiKey {
|
|||
PARSER.declareString(constructorArg(), new ParseField("username"));
|
||||
PARSER.declareString(constructorArg(), new ParseField("realm"));
|
||||
PARSER.declareObject(optionalConstructorArg(), (p, c) -> p.map(), new ParseField("metadata"));
|
||||
PARSER.declareObjectArray(optionalConstructorArg(), (p, c) -> p.objectText(), new ParseField("_sort"));
|
||||
}
|
||||
|
||||
public static ApiKey fromXContent(XContentParser parser) throws IOException {
|
||||
|
@ -150,6 +182,6 @@ public final class ApiKey {
|
|||
@Override
|
||||
public String toString() {
|
||||
return "ApiKey [name=" + name + ", id=" + id + ", creation=" + creation + ", expiration=" + expiration + ", invalidated="
|
||||
+ invalidated + ", username=" + username + ", realm=" + realm + "]";
|
||||
+ invalidated + ", username=" + username + ", realm=" + realm + ", _sort=" + Arrays.toString(sortValues) + "]";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,8 @@ import org.elasticsearch.client.security.PutPrivilegesRequest;
|
|||
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.QueryApiKeyRequestTests;
|
||||
import org.elasticsearch.client.security.RefreshPolicy;
|
||||
import org.elasticsearch.client.security.support.expressiondsl.RoleMapperExpression;
|
||||
import org.elasticsearch.client.security.support.expressiondsl.expressions.AnyRoleMapperExpression;
|
||||
|
@ -504,6 +506,19 @@ public class SecurityRequestConvertersTests extends ESTestCase {
|
|||
assertToXContentBody(invalidateApiKeyRequest, request.getEntity());
|
||||
}
|
||||
|
||||
public void testQueryApiKey() throws IOException {
|
||||
final QueryApiKeyRequest queryApiKeyRequest = new QueryApiKeyRequest(
|
||||
QueryApiKeyRequestTests.randomQueryBuilder(),
|
||||
randomIntBetween(0, 100),
|
||||
randomIntBetween(0, 100),
|
||||
QueryApiKeyRequestTests.randomFieldSortBuilders(),
|
||||
QueryApiKeyRequestTests.randomSearchAfterBuilder());
|
||||
final Request request = SecurityRequestConverters.queryApiKey(queryApiKeyRequest);
|
||||
assertEquals(HttpGet.METHOD_NAME, request.getMethod());
|
||||
assertEquals("/_security/_query/api_key", request.getEndpoint());
|
||||
assertToXContentBody(queryApiKeyRequest, request.getEntity());
|
||||
}
|
||||
|
||||
public void testGetServiceAccounts() throws IOException {
|
||||
final String namespace = randomBoolean() ? randomAlphaOfLengthBetween(3, 8) : null;
|
||||
final String serviceName = namespace == null ? null : randomAlphaOfLengthBetween(3, 8);
|
||||
|
|
|
@ -84,6 +84,8 @@ import org.elasticsearch.client.security.PutRoleRequest;
|
|||
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 org.elasticsearch.client.security.RefreshPolicy;
|
||||
import org.elasticsearch.client.security.TemplateRoleName;
|
||||
import org.elasticsearch.client.security.support.ApiKey;
|
||||
|
@ -109,6 +111,10 @@ import org.elasticsearch.common.settings.Settings;
|
|||
import org.elasticsearch.core.TimeValue;
|
||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||
import org.elasticsearch.common.util.set.Sets;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.elasticsearch.search.searchafter.SearchAfterBuilder;
|
||||
import org.elasticsearch.search.sort.FieldSortBuilder;
|
||||
import org.elasticsearch.search.sort.SortOrder;
|
||||
import org.hamcrest.Matchers;
|
||||
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
|
@ -132,7 +138,9 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
|
@ -2557,6 +2565,131 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
|
|||
|
||||
}
|
||||
|
||||
public void testQueryApiKey() throws IOException, ExecutionException, InterruptedException, TimeoutException {
|
||||
RestHighLevelClient client = highLevelClient();
|
||||
final CreateApiKeyRequest createApiKeyRequest1 = new CreateApiKeyRequest("key-10000", List.of(),
|
||||
randomBoolean() ? TimeValue.timeValueHours(24) : null,
|
||||
RefreshPolicy.WAIT_UNTIL, Map.of("environment", "east-production"));
|
||||
final CreateApiKeyResponse createApiKeyResponse1 = client.security().createApiKey(createApiKeyRequest1, RequestOptions.DEFAULT);
|
||||
final CreateApiKeyRequest createApiKeyRequest2 = new CreateApiKeyRequest("key-20000", List.of(),
|
||||
randomBoolean() ? TimeValue.timeValueHours(24) : null,
|
||||
RefreshPolicy.WAIT_UNTIL, Map.of("environment", "east-staging"));
|
||||
final CreateApiKeyResponse createApiKeyResponse2 = client.security().createApiKey(createApiKeyRequest2, RequestOptions.DEFAULT);
|
||||
|
||||
{
|
||||
// tag::query-api-key-default-request
|
||||
QueryApiKeyRequest queryApiKeyRequest = new QueryApiKeyRequest();
|
||||
// end::query-api-key-default-request
|
||||
|
||||
// tag::query-api-key-execute
|
||||
QueryApiKeyResponse queryApiKeyResponse = client.security().queryApiKey(queryApiKeyRequest, RequestOptions.DEFAULT);
|
||||
// end::query-api-key-execute
|
||||
|
||||
assertThat(queryApiKeyResponse.getTotal(), equalTo(2L));
|
||||
assertThat(queryApiKeyResponse.getCount(), equalTo(2));
|
||||
assertThat(queryApiKeyResponse.getApiKeys().stream().map(ApiKey::getName).collect(Collectors.toUnmodifiableSet()),
|
||||
equalTo(Set.of("key-10000", "key-20000")));
|
||||
assertThat(queryApiKeyResponse.getApiKeys().stream().map(ApiKey::getId).collect(Collectors.toUnmodifiableSet()),
|
||||
equalTo(Set.of(createApiKeyResponse1.getId(), createApiKeyResponse2.getId())));
|
||||
}
|
||||
|
||||
{
|
||||
// tag::query-api-key-query-request
|
||||
QueryApiKeyRequest queryApiKeyRequest = new QueryApiKeyRequest().queryBuilder(
|
||||
QueryBuilders.boolQuery()
|
||||
.must(QueryBuilders.prefixQuery("metadata.environment", "east-"))
|
||||
.mustNot(QueryBuilders.termQuery("name", "key-20000")));
|
||||
// end::query-api-key-query-request
|
||||
|
||||
QueryApiKeyResponse queryApiKeyResponse = client.security().queryApiKey(queryApiKeyRequest, RequestOptions.DEFAULT);
|
||||
assertThat(queryApiKeyResponse.getTotal(), equalTo(1L));
|
||||
assertThat(queryApiKeyResponse.getCount(), equalTo(1));
|
||||
assertThat(queryApiKeyResponse.getApiKeys().get(0).getName(), equalTo(createApiKeyResponse1.getName()));
|
||||
assertThat(queryApiKeyResponse.getApiKeys().get(0).getId(), equalTo(createApiKeyResponse1.getId()));
|
||||
}
|
||||
|
||||
{
|
||||
// tag::query-api-key-from-size-sort-request
|
||||
QueryApiKeyRequest queryApiKeyRequest = new QueryApiKeyRequest()
|
||||
.from(1)
|
||||
.size(100)
|
||||
.fieldSortBuilders(List.of(new FieldSortBuilder("name").order(SortOrder.DESC)));
|
||||
// end::query-api-key-from-size-sort-request
|
||||
|
||||
QueryApiKeyResponse queryApiKeyResponse = client.security().queryApiKey(queryApiKeyRequest, RequestOptions.DEFAULT);
|
||||
|
||||
// tag::query-api-key-from-size-sort-response
|
||||
final long total = queryApiKeyResponse.getTotal(); // <1>
|
||||
final int count = queryApiKeyResponse.getCount(); // <2>
|
||||
final List<ApiKey> apiKeys = queryApiKeyResponse.getApiKeys(); // <3>
|
||||
final Object[] sortValues = apiKeys.get(apiKeys.size()-1).getSortValues(); // <4>
|
||||
// end::query-api-key-from-size-sort-response
|
||||
|
||||
assertThat(total, equalTo(2L));
|
||||
assertThat(count, equalTo(1));
|
||||
assertThat(apiKeys.get(0).getName(), equalTo(createApiKeyResponse1.getName()));
|
||||
assertThat(apiKeys.get(0).getId(), equalTo(createApiKeyResponse1.getId()));
|
||||
assertThat(sortValues.length, equalTo(1));
|
||||
assertThat(sortValues[0], equalTo(createApiKeyResponse1.getName()));
|
||||
}
|
||||
|
||||
{
|
||||
// tag::query-api-key-search-after-request
|
||||
QueryApiKeyRequest queryApiKeyRequest = new QueryApiKeyRequest()
|
||||
.fieldSortBuilders(List.of(new FieldSortBuilder("name")))
|
||||
.searchAfterBuilder(new SearchAfterBuilder().setSortValues(new String[] {"key-10000"}));
|
||||
// end::query-api-key-search-after-request
|
||||
|
||||
QueryApiKeyResponse queryApiKeyResponse = client.security().queryApiKey(queryApiKeyRequest, RequestOptions.DEFAULT);
|
||||
assertThat(queryApiKeyResponse.getTotal(), equalTo(2L));
|
||||
assertThat(queryApiKeyResponse.getCount(), equalTo(1));
|
||||
assertThat(queryApiKeyResponse.getApiKeys().get(0).getName(), equalTo(createApiKeyResponse2.getName()));
|
||||
assertThat(queryApiKeyResponse.getApiKeys().get(0).getId(), equalTo(createApiKeyResponse2.getId()));
|
||||
}
|
||||
|
||||
{
|
||||
QueryApiKeyRequest queryApiKeyRequest = new QueryApiKeyRequest();
|
||||
|
||||
ActionListener<QueryApiKeyResponse> listener;
|
||||
// tag::query-api-key-execute-listener
|
||||
listener = new ActionListener<QueryApiKeyResponse>() {
|
||||
@Override
|
||||
public void onResponse(QueryApiKeyResponse queryApiKeyResponse) {
|
||||
// <1>
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
// <2>
|
||||
}
|
||||
};
|
||||
// end::query-api-key-execute-listener
|
||||
|
||||
// Avoid unused variable warning
|
||||
assertNotNull(listener);
|
||||
|
||||
// Replace the empty listener by a blocking listener in test
|
||||
final PlainActionFuture<QueryApiKeyResponse> future = new PlainActionFuture<>();
|
||||
listener = future;
|
||||
|
||||
// tag::query-api-key-execute-async
|
||||
client.security().queryApiKeyAsync(queryApiKeyRequest, RequestOptions.DEFAULT, listener); // <1>
|
||||
// end::query-api-key-execute-async
|
||||
|
||||
final QueryApiKeyResponse queryApiKeyResponse = future.get(30, TimeUnit.SECONDS);
|
||||
assertNotNull(queryApiKeyResponse);
|
||||
|
||||
assertThat(queryApiKeyResponse.getTotal(), equalTo(2L));
|
||||
assertThat(queryApiKeyResponse.getCount(), equalTo(2));
|
||||
assertThat(queryApiKeyResponse.getApiKeys(), is(notNullValue()));
|
||||
assertThat(queryApiKeyResponse.getApiKeys().size(), is(2));
|
||||
assertThat(queryApiKeyResponse.getApiKeys().stream().map(ApiKey::getName).collect(Collectors.toUnmodifiableSet()),
|
||||
equalTo(Set.of("key-10000", "key-20000")));
|
||||
assertThat(queryApiKeyResponse.getApiKeys().stream().map(ApiKey::getId).collect(Collectors.toUnmodifiableSet()),
|
||||
equalTo(Set.of(createApiKeyResponse1.getId(), createApiKeyResponse2.getId())));
|
||||
}
|
||||
}
|
||||
|
||||
public void testGetServiceAccounts() throws IOException {
|
||||
RestHighLevelClient client = highLevelClient();
|
||||
{
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* 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.ValidationException;
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.elasticsearch.search.searchafter.SearchAfterBuilder;
|
||||
import org.elasticsearch.search.sort.FieldSortBuilder;
|
||||
import org.elasticsearch.search.sort.SortOrder;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.test.EqualsHashCodeTestUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
public class QueryApiKeyRequestTests extends ESTestCase {
|
||||
|
||||
public void testNewInstance() {
|
||||
final QueryBuilder queryBuilder = randomQueryBuilder();
|
||||
final int from = randomIntBetween(0, 100);
|
||||
final int size = randomIntBetween(0, 100);
|
||||
final List<FieldSortBuilder> fieldSortBuilders = randomFieldSortBuilders();
|
||||
final SearchAfterBuilder searchAfterBuilder = randomSearchAfterBuilder();
|
||||
final QueryApiKeyRequest request = new QueryApiKeyRequest(queryBuilder, from, size, fieldSortBuilders, searchAfterBuilder);
|
||||
|
||||
assertThat(request.getQueryBuilder(), equalTo(queryBuilder));
|
||||
assertThat(request.getFrom(), equalTo(from));
|
||||
assertThat(request.getSize(), equalTo(size));
|
||||
assertThat(request.getFieldSortBuilders(), equalTo(fieldSortBuilders));
|
||||
assertThat(request.getSearchAfterBuilder(), equalTo(searchAfterBuilder));
|
||||
}
|
||||
|
||||
public void testEqualsHashCode() {
|
||||
final QueryApiKeyRequest request = new QueryApiKeyRequest(randomQueryBuilder(),
|
||||
randomIntBetween(0, 100),
|
||||
randomIntBetween(0, 100),
|
||||
randomFieldSortBuilders(),
|
||||
randomSearchAfterBuilder());
|
||||
|
||||
EqualsHashCodeTestUtils.checkEqualsAndHashCode(request, original -> new QueryApiKeyRequest(original.getQueryBuilder(),
|
||||
original.getFrom(),
|
||||
original.getSize(),
|
||||
original.getFieldSortBuilders(),
|
||||
original.getSearchAfterBuilder()), this::mutateInstance);
|
||||
}
|
||||
|
||||
public void testValidation() {
|
||||
final QueryApiKeyRequest request1 = new QueryApiKeyRequest(null, randomIntBetween(0, 100), randomIntBetween(0, 100), null, null);
|
||||
final Optional<ValidationException> validationException1 = request1.validate();
|
||||
assertThat(validationException1.isEmpty(), is(true));
|
||||
|
||||
final QueryApiKeyRequest request2 = new QueryApiKeyRequest(null, randomIntBetween(-100, -1), randomIntBetween(0, 100), null, null);
|
||||
final Optional<ValidationException> validationException2 = request2.validate();
|
||||
assertThat(validationException2.orElseThrow().getMessage(), containsString("from must be non-negative"));
|
||||
|
||||
final QueryApiKeyRequest request3 = new QueryApiKeyRequest(null, randomIntBetween(0, 100), randomIntBetween(-100, -1), null, null);
|
||||
final Optional<ValidationException> validationException3 = request3.validate();
|
||||
assertThat(validationException3.orElseThrow().getMessage(), containsString("size must be non-negative"));
|
||||
}
|
||||
|
||||
private QueryApiKeyRequest mutateInstance(QueryApiKeyRequest request) {
|
||||
switch (randomIntBetween(0, 5)) {
|
||||
case 0:
|
||||
return new QueryApiKeyRequest(randomValueOtherThan(request.getQueryBuilder(), QueryApiKeyRequestTests::randomQueryBuilder),
|
||||
request.getFrom(),
|
||||
request.getSize(),
|
||||
request.getFieldSortBuilders(),
|
||||
request.getSearchAfterBuilder());
|
||||
case 1:
|
||||
return new QueryApiKeyRequest(request.getQueryBuilder(),
|
||||
request.getFrom() + 1,
|
||||
request.getSize(),
|
||||
request.getFieldSortBuilders(),
|
||||
request.getSearchAfterBuilder());
|
||||
case 2:
|
||||
return new QueryApiKeyRequest(request.getQueryBuilder(),
|
||||
request.getFrom(),
|
||||
request.getSize() + 1,
|
||||
request.getFieldSortBuilders(),
|
||||
request.getSearchAfterBuilder());
|
||||
case 3:
|
||||
return new QueryApiKeyRequest(request.getQueryBuilder(),
|
||||
request.getFrom(),
|
||||
request.getSize(),
|
||||
randomValueOtherThan(request.getFieldSortBuilders(), QueryApiKeyRequestTests::randomFieldSortBuilders),
|
||||
request.getSearchAfterBuilder());
|
||||
default:
|
||||
return new QueryApiKeyRequest(request.getQueryBuilder(),
|
||||
request.getFrom(),
|
||||
request.getSize(),
|
||||
request.getFieldSortBuilders(),
|
||||
randomValueOtherThan(request.getSearchAfterBuilder(), QueryApiKeyRequestTests::randomSearchAfterBuilder));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static QueryBuilder randomQueryBuilder() {
|
||||
switch (randomIntBetween(0, 5)) {
|
||||
case 0:
|
||||
return QueryBuilders.matchAllQuery();
|
||||
case 1:
|
||||
return QueryBuilders.termQuery(randomAlphaOfLengthBetween(3, 8),
|
||||
randomFrom(randomAlphaOfLength(8), randomInt(), randomLong(), randomDouble(), randomFloat()));
|
||||
case 2:
|
||||
return QueryBuilders.idsQuery().addIds(randomArray(1, 5, String[]::new, () -> randomAlphaOfLength(20)));
|
||||
case 3:
|
||||
return QueryBuilders.prefixQuery(randomAlphaOfLengthBetween(3, 8), randomAlphaOfLengthBetween(3, 8));
|
||||
case 4:
|
||||
return QueryBuilders.wildcardQuery(randomAlphaOfLengthBetween(3, 8),
|
||||
randomAlphaOfLengthBetween(0, 3) + "*" + randomAlphaOfLengthBetween(0, 3));
|
||||
case 5:
|
||||
return QueryBuilders.rangeQuery(randomAlphaOfLengthBetween(3, 8)).from(randomNonNegativeLong()).to(randomNonNegativeLong());
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<FieldSortBuilder> randomFieldSortBuilders() {
|
||||
if (randomBoolean()) {
|
||||
return randomList(1, 2, () -> new FieldSortBuilder(randomAlphaOfLengthBetween(3, 8)).order(randomFrom(SortOrder.values())));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static SearchAfterBuilder randomSearchAfterBuilder() {
|
||||
if (randomBoolean()) {
|
||||
return new SearchAfterBuilder().setSortValues(randomArray(1, 2, String[]::new, () -> randomAlphaOfLengthBetween(3, 8)));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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.AbstractResponseTestCase;
|
||||
import org.elasticsearch.client.security.support.ApiKey;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
public class QueryApiKeyResponseTests
|
||||
extends AbstractResponseTestCase<org.elasticsearch.xpack.core.security.action.apikey.QueryApiKeyResponse, QueryApiKeyResponse> {
|
||||
|
||||
@Override
|
||||
protected org.elasticsearch.xpack.core.security.action.apikey.QueryApiKeyResponse createServerTestInstance(XContentType xContentType) {
|
||||
final int count = randomIntBetween(0, 5);
|
||||
final int total = randomIntBetween(count, count + 5);
|
||||
final int nSortValues = randomIntBetween(0, 3);
|
||||
return new org.elasticsearch.xpack.core.security.action.apikey.QueryApiKeyResponse(total,
|
||||
IntStream.range(0, count)
|
||||
.mapToObj(i -> new org.elasticsearch.xpack.core.security.action.apikey.QueryApiKeyResponse.Item(
|
||||
randomApiKeyInfo(),
|
||||
randSortValues(nSortValues)))
|
||||
.collect(Collectors.toUnmodifiableList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected QueryApiKeyResponse doParseToClientInstance(XContentParser parser) throws IOException {
|
||||
return QueryApiKeyResponse.fromXContent(parser);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assertInstances(
|
||||
org.elasticsearch.xpack.core.security.action.apikey.QueryApiKeyResponse serverTestInstance, QueryApiKeyResponse clientInstance) {
|
||||
assertThat(serverTestInstance.getTotal(), equalTo(clientInstance.getTotal()));
|
||||
assertThat(serverTestInstance.getCount(), equalTo(clientInstance.getCount()));
|
||||
for (int i = 0; i < serverTestInstance.getItems().length; i++) {
|
||||
assertApiKeyInfo(serverTestInstance.getItems()[i], clientInstance.getApiKeys().get(i));
|
||||
}
|
||||
}
|
||||
|
||||
private void assertApiKeyInfo(
|
||||
org.elasticsearch.xpack.core.security.action.apikey.QueryApiKeyResponse.Item serverItem, ApiKey clientApiKeyInfo) {
|
||||
assertThat(serverItem.getApiKey().getId(), equalTo(clientApiKeyInfo.getId()));
|
||||
assertThat(serverItem.getApiKey().getName(), equalTo(clientApiKeyInfo.getName()));
|
||||
assertThat(serverItem.getApiKey().getUsername(), equalTo(clientApiKeyInfo.getUsername()));
|
||||
assertThat(serverItem.getApiKey().getRealm(), equalTo(clientApiKeyInfo.getRealm()));
|
||||
assertThat(serverItem.getApiKey().getCreation(), equalTo(clientApiKeyInfo.getCreation()));
|
||||
assertThat(serverItem.getApiKey().getExpiration(), equalTo(clientApiKeyInfo.getExpiration()));
|
||||
assertThat(serverItem.getApiKey().getMetadata(), equalTo(clientApiKeyInfo.getMetadata()));
|
||||
assertThat(serverItem.getSortValues(), equalTo(clientApiKeyInfo.getSortValues()));
|
||||
}
|
||||
|
||||
private org.elasticsearch.xpack.core.security.action.ApiKey randomApiKeyInfo() {
|
||||
final Instant creation = Instant.now();
|
||||
return new org.elasticsearch.xpack.core.security.action.ApiKey(randomAlphaOfLengthBetween(3, 8),
|
||||
randomAlphaOfLength(20),
|
||||
creation,
|
||||
randomFrom(creation.plus(randomLongBetween(1, 10), ChronoUnit.DAYS), null),
|
||||
randomBoolean(),
|
||||
randomAlphaOfLengthBetween(3, 8),
|
||||
randomAlphaOfLengthBetween(3, 8),
|
||||
CreateApiKeyRequestTests.randomMetadata()
|
||||
);
|
||||
}
|
||||
|
||||
private Object[] randSortValues(int nSortValues) {
|
||||
if (nSortValues > 0) {
|
||||
return randomArray(nSortValues, nSortValues, Object[]::new,
|
||||
() -> randomFrom(randomInt(Integer.MAX_VALUE), randomAlphaOfLength(8), randomBoolean()));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
--
|
||||
:api: query-api-key
|
||||
:request: QueryApiKeyRequest
|
||||
:response: QueryApiKeyResponse
|
||||
--
|
||||
[role="xpack"]
|
||||
[id="{upid}-{api}"]
|
||||
=== Query API Key information API
|
||||
|
||||
API Key(s) information can be queried and retrieved in a paginated
|
||||
fashion using this API.
|
||||
|
||||
[id="{upid}-{api}-request"]
|
||||
==== Query API Key Request
|
||||
The +{request}+ supports query and retrieving API key information using
|
||||
Elasticsearch's {ref}/query-dsl.html[Query DSL] with
|
||||
{ref}/paginate-search-results.html[pagination].
|
||||
It supports only a subset of available query types, including:
|
||||
|
||||
. {ref}/query-dsl-bool-query.html[Boolean query]
|
||||
|
||||
. {ref}/query-dsl-match-all-query.html[Match all query]
|
||||
|
||||
. {ref}/query-dsl-term-query.html[Term query]
|
||||
|
||||
. {ref}/query-dsl-terms-query.html[Terms query]
|
||||
|
||||
. {ref}/query-dsl-ids-query.html[IDs Query]
|
||||
|
||||
. {ref}/query-dsl-prefix-query.html[Prefix query]
|
||||
|
||||
. {ref}/query-dsl-wildcard-query.html[Wildcard query]
|
||||
|
||||
. {ref}/query-dsl-range-query.html[Range query]
|
||||
|
||||
===== Query for all API keys
|
||||
In its most basic form, the request selects all API keys that the user
|
||||
has access to.
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests-file}[query-api-key-default-request]
|
||||
--------------------------------------------------
|
||||
|
||||
===== Query API keys with Query DSL
|
||||
The following query selects API keys owned by the user and also satisfy following criteria:
|
||||
* The API key name must begin with the word `key`
|
||||
* The API key name must *not* be `key-20000`
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests-file}[query-api-key-query-request]
|
||||
--------------------------------------------------
|
||||
|
||||
===== Retrieve API keys with explicitly configured sort and paging
|
||||
The following request sort the API keys by their names in descending order.
|
||||
It also retrieves the API keys from index 1 (zero-based) and in a page size of 100.
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests-file}[query-api-key-from-size-sort-request]
|
||||
--------------------------------------------------
|
||||
|
||||
===== Deep pagination can be achieved with search after
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests-file}[query-api-key-search-after-request]
|
||||
--------------------------------------------------
|
||||
|
||||
include::../execution.asciidoc[]
|
||||
|
||||
[id="{upid}-{api}-response"]
|
||||
==== Query API Key information API Response
|
||||
|
||||
The returned +{response}+ contains the information regarding the API keys that were
|
||||
requested.
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests-file}[query-api-key-from-size-sort-response]
|
||||
--------------------------------------------------
|
||||
<1> Total number of API keys matched by the query
|
||||
<2> Number of API keys returned in this response
|
||||
<3> The list of API keys
|
||||
<4> If sorting is requested, each API key in the response contains its sort values.
|
|
@ -540,6 +540,7 @@ include::security/create-api-key.asciidoc[]
|
|||
include::security/grant-api-key.asciidoc[]
|
||||
include::security/get-api-key.asciidoc[]
|
||||
include::security/invalidate-api-key.asciidoc[]
|
||||
include::security/query-api-key.asciidoc[]
|
||||
include::security/get-service-accounts.asciidoc[]
|
||||
include::security/create-service-account-token.asciidoc[]
|
||||
include::security/delete-service-account-token.asciidoc[]
|
||||
|
|
|
@ -56,6 +56,10 @@ public final class QueryApiKeyResponse extends ActionResponse implements ToXCont
|
|||
return items;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return items.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject()
|
||||
|
|
Loading…
Reference in New Issue