Skip to content

Commit

Permalink
Implement simple functionality for get and delete APIs as well
Browse files Browse the repository at this point in the history
Signed-off-by: Derek Ho <dxho@amazon.com>
  • Loading branch information
derek-ho committed Nov 20, 2024
1 parent 0b1da6d commit 77e18d9
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,25 @@
import org.opensearch.rest.RestRequest;
import org.opensearch.threadpool.ThreadPool;

import static org.opensearch.rest.RestRequest.Method.DELETE;
import static org.opensearch.rest.RestRequest.Method.GET;
import static org.opensearch.rest.RestRequest.Method.POST;
import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix;

public class ApiTokenAction extends BaseRestHandler {

private ClusterService clusterService;
private ThreadPool threadpool;
private ApiTokenRepository apiTokenRepository;
private final ApiTokenRepository apiTokenRepository;

public static final String NAME_JSON_PROPERTY = "name";

private static final List<RestHandler.Route> ROUTES = addRoutesPrefix(ImmutableList.of(new RestHandler.Route(POST, "/apitokens")));
private static final List<RestHandler.Route> ROUTES = addRoutesPrefix(
ImmutableList.of(
new RestHandler.Route(POST, "/apitokens"),
new RestHandler.Route(DELETE, "/apitokens"),
new RestHandler.Route(GET, "/apitokens")
)
);

public ApiTokenAction(ClusterService clusterService, ThreadPool threadPool, Client client) {
this.clusterService = clusterService;
this.threadpool = threadPool;
this.apiTokenRepository = new ApiTokenRepository(client, clusterService);
}

Expand All @@ -59,15 +62,49 @@ public List<RestHandler.Route> routes() {

@Override
protected RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException {
// TODO: Authorize this API properly
switch (request.method()) {
case POST:
return handlePost(request, client);
case DELETE:
return handleDelete(request, client);
case GET:
return handleGet(request, client);
default:
throw new IllegalArgumentException(request.method() + " not supported");
}
}

private RestChannelConsumer handleGet(RestRequest request, NodeClient client) {
return channel -> {
final XContentBuilder builder = channel.newBuilder();
BytesRestResponse response;
try {
List<Map<String, Object>> token = apiTokenRepository.getApiTokens();

builder.startArray();
for (int i = 0; i < token.toArray().length; i++) {
// TODO: refactor this to the helper function
builder.startObject();
builder.field("name", token.get(i).get("description"));
builder.field("creation_time", token.get(i).get("creation_time"));
builder.endObject();
}
builder.endArray();

response = new BytesRestResponse(RestStatus.OK, builder);
} catch (final Exception exception) {
builder.startObject().field("error", "An unexpected error occurred. Please check the input and try again.").endObject();
response = new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR, builder);
}
builder.close();
channel.sendResponse(response);
};

}

private RestChannelConsumer handlePost(RestRequest request, NodeClient client) {
// TODO: Enforce unique token description
return channel -> {
final XContentBuilder builder = channel.newBuilder();
BytesRestResponse response;
Expand All @@ -92,6 +129,34 @@ private RestChannelConsumer handlePost(RestRequest request, NodeClient client) {

}

private RestChannelConsumer handleDelete(RestRequest request, NodeClient client) {
return channel -> {
final XContentBuilder builder = channel.newBuilder();
BytesRestResponse response;
try {
final Map<String, Object> requestBody = request.contentOrSourceParamParser().map();

validateRequestParameters(requestBody);
apiTokenRepository.deleteApiToken((String) requestBody.get(NAME_JSON_PROPERTY));

builder.startObject();
builder.field("message", "token " + requestBody.get(NAME_JSON_PROPERTY) + " deleted successfully.");
builder.endObject();

response = new BytesRestResponse(RestStatus.OK, builder);
} catch (final ApiTokenException exception) {
builder.startObject().field("error", exception.getMessage()).endObject();
response = new BytesRestResponse(RestStatus.NOT_FOUND, builder);
} catch (final Exception exception) {
builder.startObject().field("error", "An unexpected error occurred. Please check the input and try again.").endObject();
response = new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR, builder);
}
builder.close();
channel.sendResponse(response);
};

}

private void validateRequestParameters(Map<String, Object> requestBody) {
if (!requestBody.containsKey(NAME_JSON_PROPERTY)) {
throw new IllegalArgumentException("Name parameter is required and cannot be empty.");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/

package org.opensearch.security.action.apitokens;

import org.opensearch.OpenSearchException;

public class ApiTokenException extends OpenSearchException {
public ApiTokenException(String message) {
super(message);
}

public ApiTokenException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,19 @@
package org.opensearch.security.action.apitokens;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import com.google.common.collect.ImmutableMap;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import org.opensearch.action.admin.indices.create.CreateIndexRequest;
import org.opensearch.action.get.GetRequest;
import org.opensearch.action.index.IndexRequest;
import org.opensearch.action.index.IndexResponse;
import org.opensearch.action.search.SearchRequest;
import org.opensearch.action.search.SearchResponse;
import org.opensearch.client.Client;
import org.opensearch.cluster.service.ClusterService;
import org.opensearch.common.util.concurrent.ThreadContext;
Expand All @@ -30,6 +33,11 @@
import org.opensearch.core.action.ActionListener;
import org.opensearch.core.xcontent.ToXContent;
import org.opensearch.core.xcontent.XContentBuilder;
import org.opensearch.index.query.QueryBuilders;
import org.opensearch.index.reindex.BulkByScrollResponse;
import org.opensearch.index.reindex.DeleteByQueryAction;
import org.opensearch.index.reindex.DeleteByQueryRequest;
import org.opensearch.search.SearchHit;
import org.opensearch.security.support.ConfigConstants;

public class ApiTokenIndexHandler {
Expand Down Expand Up @@ -67,11 +75,36 @@ public String indexToken(ApiToken token) {

}

public Object getTokens() {
public void deleteToken(String name) throws ApiTokenException {
try (final ThreadContext.StoredContext ctx = client.threadPool().getThreadContext().stashContext()) {
DeleteByQueryRequest request = new DeleteByQueryRequest(ConfigConstants.OPENSEARCH_API_TOKENS_INDEX).setQuery(
QueryBuilders.matchQuery("description", name)
).setRefresh(true); // This will refresh the index after deletion

return client.get(new GetRequest(ConfigConstants.OPENSEARCH_API_TOKENS_INDEX));
BulkByScrollResponse response = client.execute(DeleteByQueryAction.INSTANCE, request).actionGet();

long deletedDocs = response.getDeleted();

if (deletedDocs == 0) {
throw new ApiTokenException("No token found with name " + name);
}
LOGGER.info("Deleted " + deletedDocs + " documents");
}
}

public List<Map<String, Object>> getApiTokens() {
try (final ThreadContext.StoredContext ctx = client.threadPool().getThreadContext().stashContext()) {

SearchRequest searchRequest = new SearchRequest(ConfigConstants.OPENSEARCH_API_TOKENS_INDEX);

SearchResponse response = client.search(searchRequest).actionGet();

List<Map<String, Object>> tokens = new ArrayList<>();
for (SearchHit hit : response.getHits().getHits()) {
tokens.add(hit.getSourceAsMap());
}

return tokens;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
package org.opensearch.security.action.apitokens;

import java.util.List;
import java.util.Map;

import org.opensearch.client.Client;
import org.opensearch.cluster.service.ClusterService;
Expand All @@ -25,8 +26,17 @@ public ApiTokenRepository(Client client, ClusterService clusterService) {

public String createApiToken(String name) {
apiTokenIndexHandler.createApiTokenIndexIfAbsent();
String token = apiTokenIndexHandler.indexToken(new ApiToken(name, "test-token", List.of()));
return token;
return apiTokenIndexHandler.indexToken(new ApiToken(name, "test-token", List.of()));
}

public void deleteApiToken(String name) throws ApiTokenException {
apiTokenIndexHandler.createApiTokenIndexIfAbsent();
apiTokenIndexHandler.deleteToken(name);
}

public List<Map<String, Object>> getApiTokens() {
apiTokenIndexHandler.createApiTokenIndexIfAbsent();
return apiTokenIndexHandler.getApiTokens();
}

}

0 comments on commit 77e18d9

Please sign in to comment.