2424import org .apache .logging .log4j .LogManager ;
2525import org .apache .logging .log4j .Logger ;
2626
27- import org .opensearch .client .node .NodeClient ;
2827import org .opensearch .cluster .metadata .IndexNameExpressionResolver ;
2928import org .opensearch .cluster .service .ClusterService ;
3029import org .opensearch .common .settings .Settings ;
4847import org .opensearch .security .ssl .transport .PrincipalExtractor ;
4948import org .opensearch .security .support .ConfigConstants ;
5049import org .opensearch .threadpool .ThreadPool ;
50+ import org .opensearch .transport .client .node .NodeClient ;
5151
5252import static org .opensearch .rest .RestRequest .Method .DELETE ;
5353import static org .opensearch .rest .RestRequest .Method .GET ;
5454import static org .opensearch .rest .RestRequest .Method .POST ;
5555import static org .opensearch .security .action .apitokens .ApiToken .ALLOWED_ACTIONS_FIELD ;
5656import static org .opensearch .security .action .apitokens .ApiToken .CLUSTER_PERMISSIONS_FIELD ;
57- import static org .opensearch .security .action .apitokens .ApiToken .CREATION_TIME_FIELD ;
5857import static org .opensearch .security .action .apitokens .ApiToken .EXPIRATION_FIELD ;
5958import static org .opensearch .security .action .apitokens .ApiToken .INDEX_PATTERN_FIELD ;
6059import static org .opensearch .security .action .apitokens .ApiToken .INDEX_PERMISSIONS_FIELD ;
60+ import static org .opensearch .security .action .apitokens .ApiToken .ISSUED_AT_FIELD ;
6161import static org .opensearch .security .action .apitokens .ApiToken .NAME_FIELD ;
6262import static org .opensearch .security .dlic .rest .support .Utils .addRoutesPrefix ;
6363import static org .opensearch .security .support .ConfigConstants .SECURITY_RESTAPI_ADMIN_ENABLED ;
@@ -146,30 +146,32 @@ RestChannelConsumer doPrepareRequest(RestRequest request, NodeClient client) {
146146
147147 private RestChannelConsumer handleGet (RestRequest request , NodeClient client ) {
148148 return channel -> {
149- final XContentBuilder builder = channel .newBuilder ();
150- BytesRestResponse response ;
151- try {
152- Map <String , ApiToken > tokens = apiTokenRepository .getApiTokens ();
153-
154- builder .startArray ();
155- for (ApiToken token : tokens .values ()) {
156- builder .startObject ();
157- builder .field (NAME_FIELD , token .getName ());
158- builder .field (CREATION_TIME_FIELD , token .getCreationTime ().toEpochMilli ());
159- builder .field (EXPIRATION_FIELD , token .getExpiration ());
160- builder .field (CLUSTER_PERMISSIONS_FIELD , token .getClusterPermissions ());
161- builder .field (INDEX_PERMISSIONS_FIELD , token .getIndexPermissions ());
162- builder .endObject ();
149+ apiTokenRepository .getApiTokens (ActionListener .wrap (tokens -> {
150+ try {
151+ XContentBuilder builder = channel .newBuilder ();
152+ builder .startArray ();
153+ for (ApiToken token : tokens .values ()) {
154+ builder .startObject ();
155+ builder .field (NAME_FIELD , token .getName ());
156+ builder .field (ISSUED_AT_FIELD , token .getCreationTime ().toEpochMilli ());
157+ builder .field (EXPIRATION_FIELD , token .getExpiration ());
158+ builder .field (CLUSTER_PERMISSIONS_FIELD , token .getClusterPermissions ());
159+ builder .field (INDEX_PERMISSIONS_FIELD , token .getIndexPermissions ());
160+ builder .endObject ();
161+ }
162+ builder .endArray ();
163+
164+ BytesRestResponse response = new BytesRestResponse (RestStatus .OK , builder );
165+ builder .close ();
166+ channel .sendResponse (response );
167+ } catch (final Exception exception ) {
168+ sendErrorResponse (channel , RestStatus .INTERNAL_SERVER_ERROR , exception .getMessage ());
163169 }
164- builder .endArray ();
170+ }, exception -> {
171+ sendErrorResponse (channel , RestStatus .INTERNAL_SERVER_ERROR , exception .getMessage ());
172+
173+ }));
165174
166- response = new BytesRestResponse (RestStatus .OK , builder );
167- } catch (final Exception exception ) {
168- builder .startObject ().field ("error" , exception .getMessage ()).endObject ();
169- response = new BytesRestResponse (RestStatus .INTERNAL_SERVER_ERROR , builder );
170- }
171- builder .close ();
172- channel .sendResponse (response );
173175 };
174176 }
175177
@@ -181,43 +183,76 @@ private RestChannelConsumer handlePost(RestRequest request, NodeClient client) {
181183
182184 List <String > clusterPermissions = extractClusterPermissions (requestBody );
183185 List <ApiToken .IndexPermission > indexPermissions = extractIndexPermissions (requestBody );
184-
185- String token = apiTokenRepository .createApiToken (
186- (String ) requestBody .get (NAME_FIELD ),
187- clusterPermissions ,
188- indexPermissions ,
189- (Long ) requestBody .getOrDefault (EXPIRATION_FIELD , Instant .now ().toEpochMilli () + TimeUnit .DAYS .toMillis (30 ))
186+ String name = (String ) requestBody .get (NAME_FIELD );
187+ long expiration = (Long ) requestBody .getOrDefault (
188+ EXPIRATION_FIELD ,
189+ Instant .now ().toEpochMilli () + TimeUnit .DAYS .toMillis (30 )
190190 );
191191
192- // Then trigger the update action
193- ApiTokenUpdateRequest updateRequest = new ApiTokenUpdateRequest ();
194- client .execute (ApiTokenUpdateAction .INSTANCE , updateRequest , new ActionListener <ApiTokenUpdateResponse >() {
195- @ Override
196- public void onResponse (ApiTokenUpdateResponse updateResponse ) {
197- try {
192+ // First check token count
193+ apiTokenRepository .getTokenCount (ActionListener .wrap (tokenCount -> {
194+ if (tokenCount >= 100 ) {
195+ sendErrorResponse (
196+ channel ,
197+ RestStatus .TOO_MANY_REQUESTS ,
198+ "Maximum limit of 100 API tokens reached. Please delete existing tokens before creating new ones."
199+ );
200+ return ;
201+ }
202+
203+ apiTokenRepository .createApiToken (
204+ name ,
205+ clusterPermissions ,
206+ indexPermissions ,
207+ expiration ,
208+ wrapWithCacheRefresh (ActionListener .wrap (token -> {
198209 XContentBuilder builder = channel .newBuilder ();
199210 builder .startObject ();
200- builder .field ("Api Token: " , token );
211+ builder .field ("token " , token );
201212 builder .endObject ();
202-
203- BytesRestResponse response = new BytesRestResponse (RestStatus .OK , builder );
204- channel .sendResponse (response );
205- } catch (IOException e ) {
206- sendErrorResponse (channel , RestStatus .INTERNAL_SERVER_ERROR , "Failed to send response after token creation" );
207- }
208- }
209-
210- @ Override
211- public void onFailure (Exception e ) {
212- sendErrorResponse (channel , RestStatus .INTERNAL_SERVER_ERROR , "Failed to propagate token creation" );
213- }
214- });
215- } catch (final Exception exception ) {
216- sendErrorResponse (channel , RestStatus .INTERNAL_SERVER_ERROR , exception .getMessage ());
213+ channel .sendResponse (new BytesRestResponse (RestStatus .OK , builder ));
214+ builder .close ();
215+
216+ },
217+ createException -> sendErrorResponse (
218+ channel ,
219+ RestStatus .INTERNAL_SERVER_ERROR ,
220+ "Failed to create token: " + createException .getMessage ()
221+ )
222+ ), client )
223+ );
224+ },
225+ countException -> sendErrorResponse (
226+ channel ,
227+ RestStatus .INTERNAL_SERVER_ERROR ,
228+ "Failed to get token count: " + countException .getMessage ()
229+ )
230+ ));
231+
232+ } catch (Exception e ) {
233+ sendErrorResponse (channel , RestStatus .BAD_REQUEST , "Invalid request: " + e .getMessage ());
217234 }
218235 };
219236 }
220237
238+ private <T > ActionListener <T > wrapWithCacheRefresh (ActionListener <T > listener , NodeClient client ) {
239+ return ActionListener .wrap (response -> {
240+ try {
241+ ApiTokenUpdateRequest updateRequest = new ApiTokenUpdateRequest ();
242+ client .execute (
243+ ApiTokenUpdateAction .INSTANCE ,
244+ updateRequest ,
245+ ActionListener .wrap (
246+ updateResponse -> listener .onResponse (response ),
247+ exception -> listener .onFailure (new ApiTokenException ("Failed to refresh cache" , exception ))
248+ )
249+ );
250+ } catch (Exception e ) {
251+ listener .onFailure (new ApiTokenException ("Failed to refresh cache after operation" , e ));
252+ }
253+ }, listener ::onFailure );
254+ }
255+
221256 /**
222257 * Extracts cluster permissions from the request body
223258 */
@@ -303,37 +338,30 @@ private RestChannelConsumer handleDelete(RestRequest request, NodeClient client)
303338 final Map <String , Object > requestBody = request .contentOrSourceParamParser ().map ();
304339
305340 validateRequestParameters (requestBody );
306- apiTokenRepository .deleteApiToken ((String ) requestBody .get (NAME_FIELD ));
307-
308- ApiTokenUpdateRequest updateRequest = new ApiTokenUpdateRequest ();
309- client .execute (ApiTokenUpdateAction .INSTANCE , updateRequest , new ActionListener <ApiTokenUpdateResponse >() {
310- @ Override
311- public void onResponse (ApiTokenUpdateResponse updateResponse ) {
312- try {
313- XContentBuilder builder = channel .newBuilder ();
314- builder .startObject ();
315- builder .field ("message" , "token " + requestBody .get (NAME_FIELD ) + " deleted successfully." );
316- builder .endObject ();
317-
318- BytesRestResponse response = new BytesRestResponse (RestStatus .OK , builder );
319- channel .sendResponse (response );
320- } catch (Exception e ) {
321- sendErrorResponse (channel , RestStatus .INTERNAL_SERVER_ERROR , "Failed to send response after token update" );
322- }
323- }
324-
325- @ Override
326- public void onFailure (Exception e ) {
327- sendErrorResponse (channel , RestStatus .INTERNAL_SERVER_ERROR , "Failed to propagate token deletion" );
328- }
329- });
330- } catch (final ApiTokenException exception ) {
331- sendErrorResponse (channel , RestStatus .NOT_FOUND , exception .getMessage ());
341+ apiTokenRepository .deleteApiToken (
342+ (String ) requestBody .get (NAME_FIELD ),
343+ wrapWithCacheRefresh (ActionListener .wrap (ignored -> {
344+ XContentBuilder builder = channel .newBuilder ();
345+ builder .startObject ();
346+ builder .field ("message" , "Token " + requestBody .get (NAME_FIELD ) + " deleted successfully." );
347+ builder .endObject ();
348+ channel .sendResponse (new BytesRestResponse (RestStatus .OK , builder ));
349+ },
350+ deleteException -> sendErrorResponse (
351+ channel ,
352+ RestStatus .INTERNAL_SERVER_ERROR ,
353+ "Failed to delete token: " + deleteException .getMessage ()
354+ )
355+ ), client )
356+ );
332357 } catch (final Exception exception ) {
333- sendErrorResponse (channel , RestStatus .INTERNAL_SERVER_ERROR , exception .getMessage ());
358+ RestStatus status = RestStatus .INTERNAL_SERVER_ERROR ;
359+ if (exception instanceof ApiTokenException ) {
360+ status = RestStatus .NOT_FOUND ;
361+ }
362+ sendErrorResponse (channel , status , exception .getMessage ());
334363 }
335364 };
336-
337365 }
338366
339367 private void sendErrorResponse (RestChannel channel , RestStatus status , String errorMessage ) {
0 commit comments