Skip to content

Commit

Permalink
HLRC support for query API key API (#76520)
Browse files Browse the repository at this point in the history
This PR adds HLRC for the new Query API key API added with #75335 and #76144

Relates: #71023
  • Loading branch information
ywangd authored Aug 17, 2021
1 parent c1a3244 commit 7bb1185
Show file tree
Hide file tree
Showing 12 changed files with 774 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@
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;

Expand Down Expand Up @@ -1054,7 +1056,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 {
Expand Down Expand Up @@ -1141,6 +1143,37 @@ public Cancellable grantApiKeyAsync(final GrantApiKeyRequest request, final Requ
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">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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");
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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"));
}
}
Loading

0 comments on commit 7bb1185

Please sign in to comment.