Browse Source

Refactor GetApiKeyResponse and QueryApiKeyResponse to return List instead of arrays (#106409)

The plan is that GetApiKeyResponse and QueryApiKeyResponse
will be receiving collections of api key info, optional profile uids, and optional
sort values, that they will "zip" into a single list of records.
Array vs List is not important in this context, but we use collections in the
internal services and action handlers, so it is more consistent to also use them
in the responses.
Albert Zaharovits 1 year ago
parent
commit
963a5b876d

+ 48 - 20
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/apikey/GetApiKeyResponse.java

@@ -29,25 +29,25 @@ import static org.elasticsearch.xcontent.ConstructingObjectParser.optionalConstr
  */
 public final class GetApiKeyResponse extends ActionResponse implements ToXContentObject {
 
-    private final ApiKey[] foundApiKeysInfo;
+    public static final GetApiKeyResponse EMPTY = new GetApiKeyResponse(List.of());
 
-    public GetApiKeyResponse(Collection<ApiKey> foundApiKeysInfo) {
-        Objects.requireNonNull(foundApiKeysInfo, "found_api_keys_info must be provided");
-        this.foundApiKeysInfo = foundApiKeysInfo.toArray(new ApiKey[0]);
-    }
+    private final List<Item> foundApiKeyInfoList;
 
-    public static GetApiKeyResponse emptyResponse() {
-        return new GetApiKeyResponse(List.of());
+    public GetApiKeyResponse(Collection<ApiKey> foundApiKeyInfos) {
+        Objects.requireNonNull(foundApiKeyInfos, "found_api_keys_info must be provided");
+        this.foundApiKeyInfoList = foundApiKeyInfos.stream().map(Item::new).toList();
     }
 
-    public ApiKey[] getApiKeyInfos() {
-        return foundApiKeysInfo;
+    public List<Item> getApiKeyInfoList() {
+        return foundApiKeyInfoList;
     }
 
     @Override
     public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
-        builder.startObject().array("api_keys", (Object[]) foundApiKeysInfo);
-        return builder.endObject();
+        builder.startObject();
+        builder.field("api_keys", foundApiKeyInfoList);
+        builder.endObject();
+        return builder;
     }
 
     @Override
@@ -55,21 +55,49 @@ public final class GetApiKeyResponse extends ActionResponse implements ToXConten
         TransportAction.localOnly();
     }
 
-    @SuppressWarnings("unchecked")
-    static final ConstructingObjectParser<GetApiKeyResponse, Void> PARSER = new ConstructingObjectParser<>("get_api_key_response", args -> {
-        return (args[0] == null) ? GetApiKeyResponse.emptyResponse() : new GetApiKeyResponse((List<ApiKey>) args[0]);
-    });
-    static {
-        PARSER.declareObjectArray(optionalConstructorArg(), (p, c) -> ApiKey.fromXContent(p), new ParseField("api_keys"));
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        GetApiKeyResponse that = (GetApiKeyResponse) o;
+        return Objects.equals(foundApiKeyInfoList, that.foundApiKeyInfoList);
     }
 
-    public static GetApiKeyResponse fromXContent(XContentParser parser) throws IOException {
-        return PARSER.parse(parser, null);
+    @Override
+    public int hashCode() {
+        return Objects.hash(foundApiKeyInfoList);
     }
 
     @Override
     public String toString() {
-        return "GetApiKeyResponse [foundApiKeysInfo=" + foundApiKeysInfo + "]";
+        return "GetApiKeyResponse{foundApiKeysInfo=" + foundApiKeyInfoList + "}";
     }
 
+    public record Item(ApiKey apiKeyInfo) implements ToXContentObject {
+        @Override
+        public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+            builder.startObject();
+            apiKeyInfo.innerToXContent(builder, params);
+            builder.endObject();
+            return builder;
+        }
+
+        @Override
+        public String toString() {
+            return "Item{apiKeyInfo=" + apiKeyInfo + "}";
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    static final ConstructingObjectParser<GetApiKeyResponse, Void> PARSER = new ConstructingObjectParser<>(
+        "get_api_key_response",
+        args -> (args[0] == null) ? GetApiKeyResponse.EMPTY : new GetApiKeyResponse((List<ApiKey>) args[0])
+    );
+    static {
+        PARSER.declareObjectArray(optionalConstructorArg(), (p, c) -> ApiKey.fromXContent(p), new ParseField("api_keys"));
+    }
+
+    public static GetApiKeyResponse fromXContent(XContentParser parser) throws IOException {
+        return PARSER.parse(parser, null);
+    }
 }

+ 19 - 51
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/apikey/QueryApiKeyResponse.java

@@ -16,6 +16,7 @@ import org.elasticsearch.xcontent.ToXContentObject;
 import org.elasticsearch.xcontent.XContentBuilder;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
@@ -27,31 +28,25 @@ import java.util.Objects;
  */
 public final class QueryApiKeyResponse extends ActionResponse implements ToXContentObject {
 
+    public static final QueryApiKeyResponse EMPTY = new QueryApiKeyResponse(0, List.of(), null);
+
     private final long total;
-    private final Item[] items;
+    private final List<Item> foundApiKeyInfoList;
     private final @Nullable InternalAggregations aggregations;
 
     public QueryApiKeyResponse(long total, Collection<Item> items, @Nullable InternalAggregations aggregations) {
         this.total = total;
         Objects.requireNonNull(items, "items must be provided");
-        this.items = items.toArray(new Item[0]);
+        this.foundApiKeyInfoList = items instanceof List<Item> ? (List<Item>) items : new ArrayList<>(items);
         this.aggregations = aggregations;
     }
 
-    public static QueryApiKeyResponse emptyResponse() {
-        return new QueryApiKeyResponse(0, List.of(), null);
-    }
-
     public long getTotal() {
         return total;
     }
 
-    public Item[] getItems() {
-        return items;
-    }
-
-    public int getCount() {
-        return items.length;
+    public List<Item> getApiKeyInfoList() {
+        return foundApiKeyInfoList;
     }
 
     public InternalAggregations getAggregations() {
@@ -60,11 +55,13 @@ public final class QueryApiKeyResponse extends ActionResponse implements ToXCont
 
     @Override
     public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
-        builder.startObject().field("total", total).field("count", items.length).array("api_keys", (Object[]) items);
+        builder.startObject();
+        builder.field("total", total).field("count", foundApiKeyInfoList.size()).field("api_keys", foundApiKeyInfoList);
         if (aggregations != null) {
             aggregations.toXContent(builder, params);
         }
-        return builder.endObject();
+        builder.endObject();
+        return builder;
     }
 
     @Override
@@ -77,44 +74,30 @@ public final class QueryApiKeyResponse extends ActionResponse implements ToXCont
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         QueryApiKeyResponse that = (QueryApiKeyResponse) o;
-        return total == that.total && Arrays.equals(items, that.items) && Objects.equals(aggregations, that.aggregations);
+        return total == that.total
+            && Objects.equals(foundApiKeyInfoList, that.foundApiKeyInfoList)
+            && Objects.equals(aggregations, that.aggregations);
     }
 
     @Override
     public int hashCode() {
         int result = Objects.hash(total);
-        result = 31 * result + Arrays.hashCode(items);
+        result = 31 * result + Objects.hash(foundApiKeyInfoList);
         result = 31 * result + Objects.hash(aggregations);
         return result;
     }
 
     @Override
     public String toString() {
-        return "QueryApiKeyResponse{total=" + total + ", items=" + Arrays.toString(items) + ", aggs=" + aggregations + "}";
+        return "QueryApiKeyResponse{total=" + total + ", items=" + foundApiKeyInfoList + ", aggs=" + aggregations + "}";
     }
 
-    public static class Item implements ToXContentObject {
-        private final ApiKey apiKey;
-        @Nullable
-        private final Object[] sortValues;
-
-        public Item(ApiKey apiKey, @Nullable Object[] sortValues) {
-            this.apiKey = apiKey;
-            this.sortValues = sortValues;
-        }
-
-        public ApiKey getApiKey() {
-            return apiKey;
-        }
-
-        public Object[] getSortValues() {
-            return sortValues;
-        }
+    public record Item(ApiKey apiKeyInfo, @Nullable Object[] sortValues) implements ToXContentObject {
 
         @Override
         public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
             builder.startObject();
-            apiKey.innerToXContent(builder, params);
+            apiKeyInfo.innerToXContent(builder, params);
             if (sortValues != null && sortValues.length > 0) {
                 builder.array("_sort", sortValues);
             }
@@ -122,24 +105,9 @@ public final class QueryApiKeyResponse extends ActionResponse implements ToXCont
             return builder;
         }
 
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            Item item = (Item) o;
-            return Objects.equals(apiKey, item.apiKey) && Arrays.equals(sortValues, item.sortValues);
-        }
-
-        @Override
-        public int hashCode() {
-            int result = Objects.hash(apiKey);
-            result = 31 * result + Arrays.hashCode(sortValues);
-            return result;
-        }
-
         @Override
         public String toString() {
-            return "Item{" + "apiKey=" + apiKey + ", sortValues=" + Arrays.toString(sortValues) + '}';
+            return "Item{apiKeyInfo=" + apiKeyInfo + ", sortValues=" + Arrays.toString(sortValues) + '}';
         }
     }
 }

+ 5 - 2
x-pack/plugin/security/qa/security-trial/src/javaRestTest/java/org/elasticsearch/xpack/security/apikey/ApiKeyRestIT.java

@@ -1824,9 +1824,12 @@ public class ApiKeyRestIT extends SecurityOnTrialLicenseRestTestCase {
         assertOK(response);
         try (XContentParser parser = responseAsParser(response)) {
             final var apiKeyResponse = GetApiKeyResponse.fromXContent(parser);
-            assertThat(apiKeyResponse.getApiKeyInfos().length, equalTo(1));
+            assertThat(apiKeyResponse.getApiKeyInfoList().size(), equalTo(1));
             // ApiKey metadata is set to empty Map if null
-            assertThat(apiKeyResponse.getApiKeyInfos()[0].getMetadata(), equalTo(expectedMetadata == null ? Map.of() : expectedMetadata));
+            assertThat(
+                apiKeyResponse.getApiKeyInfoList().get(0).apiKeyInfo().getMetadata(),
+                equalTo(expectedMetadata == null ? Map.of() : expectedMetadata)
+            );
         }
     }
 

+ 19 - 17
x-pack/plugin/security/qa/security-trial/src/javaRestTest/java/org/elasticsearch/xpack/security/apikey/GetApiKeysRestIT.java

@@ -27,18 +27,17 @@ import org.elasticsearch.xpack.security.SecurityOnTrialLicenseRestTestCase;
 import org.junit.Before;
 
 import java.io.IOException;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.stream.Collectors;
 
 import javax.annotation.Nullable;
 
 import static org.hamcrest.Matchers.containsInAnyOrder;
-import static org.hamcrest.Matchers.emptyArray;
+import static org.hamcrest.Matchers.emptyIterable;
 import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.iterableWithSize;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.hamcrest.Matchers.nullValue;
 
@@ -103,7 +102,7 @@ public class GetApiKeysRestIT extends SecurityOnTrialLicenseRestTestCase {
 
         // We get an empty result when no API keys active
         getSecurityClient().invalidateApiKeys(apiKeyId1);
-        assertThat(getApiKeysWithRequestParams(Map.of("active_only", "true")).getApiKeyInfos(), emptyArray());
+        assertThat(getApiKeysWithRequestParams(Map.of("active_only", "true")).getApiKeyInfoList(), emptyIterable());
 
         {
             // Using together with id parameter, returns 404 for inactive key
@@ -166,12 +165,12 @@ public class GetApiKeysRestIT extends SecurityOnTrialLicenseRestTestCase {
             manageApiKeyUserApiKeyId
         );
         assertThat(
-            getApiKeysWithRequestParams(Map.of("active_only", "true", "username", MANAGE_OWN_API_KEY_USER)).getApiKeyInfos(),
-            emptyArray()
+            getApiKeysWithRequestParams(Map.of("active_only", "true", "username", MANAGE_OWN_API_KEY_USER)).getApiKeyInfoList(),
+            emptyIterable()
         );
         assertThat(
-            getApiKeysWithRequestParams(MANAGE_OWN_API_KEY_USER, Map.of("active_only", "true", "owner", "true")).getApiKeyInfos(),
-            emptyArray()
+            getApiKeysWithRequestParams(MANAGE_OWN_API_KEY_USER, Map.of("active_only", "true", "owner", "true")).getApiKeyInfoList(),
+            emptyIterable()
         );
 
         // No more active API keys
@@ -180,15 +179,15 @@ public class GetApiKeysRestIT extends SecurityOnTrialLicenseRestTestCase {
         assertThat(
             getApiKeysWithRequestParams(
                 Map.of("active_only", "true", "username", randomFrom(MANAGE_SECURITY_USER, MANAGE_OWN_API_KEY_USER))
-            ).getApiKeyInfos(),
-            emptyArray()
+            ).getApiKeyInfoList(),
+            emptyIterable()
         );
         assertThat(
             getApiKeysWithRequestParams(
                 randomFrom(MANAGE_SECURITY_USER, MANAGE_OWN_API_KEY_USER),
                 Map.of("active_only", "true", "owner", "true")
-            ).getApiKeyInfos(),
-            emptyArray()
+            ).getApiKeyInfoList(),
+            emptyIterable()
         );
         // With flag set to false, we get both inactive keys
         assertResponseContainsApiKeyIds(
@@ -205,8 +204,8 @@ public class GetApiKeysRestIT extends SecurityOnTrialLicenseRestTestCase {
         setUserForRequest(request, MANAGE_SECURITY_USER);
         GetApiKeyResponse getApiKeyResponse = GetApiKeyResponse.fromXContent(getParser(client().performRequest(request)));
 
-        assertThat(getApiKeyResponse.getApiKeyInfos().length, equalTo(1));
-        ApiKey apiKey = getApiKeyResponse.getApiKeyInfos()[0];
+        assertThat(getApiKeyResponse.getApiKeyInfoList(), iterableWithSize(1));
+        ApiKey apiKey = getApiKeyResponse.getApiKeyInfoList().get(0).apiKeyInfo();
         assertThat(apiKey.isInvalidated(), equalTo(false));
         assertThat(apiKey.getInvalidation(), nullValue());
         assertThat(apiKey.getId(), equalTo(apiKeyId0));
@@ -226,8 +225,8 @@ public class GetApiKeysRestIT extends SecurityOnTrialLicenseRestTestCase {
         setUserForRequest(request, MANAGE_SECURITY_USER);
         getApiKeyResponse = GetApiKeyResponse.fromXContent(getParser(client().performRequest(request)));
 
-        assertThat(getApiKeyResponse.getApiKeyInfos().length, equalTo(1));
-        apiKey = getApiKeyResponse.getApiKeyInfos()[0];
+        assertThat(getApiKeyResponse.getApiKeyInfoList(), iterableWithSize(1));
+        apiKey = getApiKeyResponse.getApiKeyInfoList().get(0).apiKeyInfo();
         assertThat(apiKey.isInvalidated(), equalTo(true));
         assertThat(apiKey.getInvalidation(), notNullValue());
         assertThat(apiKey.getId(), equalTo(apiKeyId0));
@@ -245,7 +244,10 @@ public class GetApiKeysRestIT extends SecurityOnTrialLicenseRestTestCase {
     }
 
     private static void assertResponseContainsApiKeyIds(GetApiKeyResponse response, String... ids) {
-        assertThat(Arrays.stream(response.getApiKeyInfos()).map(ApiKey::getId).collect(Collectors.toList()), containsInAnyOrder(ids));
+        assertThat(
+            response.getApiKeyInfoList().stream().map(GetApiKeyResponse.Item::apiKeyInfo).map(ApiKey::getId).toList(),
+            containsInAnyOrder(ids)
+        );
     }
 
     private static XContentParser getParser(Response response) throws IOException {

+ 68 - 55
x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authc/ApiKeyIntegTests.java

@@ -137,7 +137,6 @@ import static org.elasticsearch.xpack.security.Security.SECURITY_CRYPTO_THREAD_P
 import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SECURITY_MAIN_ALIAS;
 import static org.hamcrest.Matchers.anEmptyMap;
 import static org.hamcrest.Matchers.anyOf;
-import static org.hamcrest.Matchers.arrayWithSize;
 import static org.hamcrest.Matchers.contains;
 import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.containsString;
@@ -149,6 +148,7 @@ import static org.hamcrest.Matchers.hasKey;
 import static org.hamcrest.Matchers.in;
 import static org.hamcrest.Matchers.instanceOf;
 import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.iterableWithSize;
 import static org.hamcrest.Matchers.not;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.hamcrest.Matchers.nullValue;
@@ -399,7 +399,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
     public void testInvalidateApiKeyWillClearApiKeyCache() throws IOException, ExecutionException, InterruptedException {
         final List<ApiKeyService> services = Arrays.stream(internalCluster().getNodeNames())
             .map(n -> internalCluster().getInstance(ApiKeyService.class, n))
-            .collect(Collectors.toList());
+            .toList();
 
         // Create two API keys and authenticate with them
         Tuple<String, String> apiKey1 = createApiKeyAndAuthenticateWithIt();
@@ -471,7 +471,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
             refreshSecurityIndex();
 
             // Get API keys to make sure remover didn't remove any yet
-            assertThat(getAllApiKeyInfo(client, false).length, equalTo(3));
+            assertThat(getAllApiKeyInfo(client, false).size(), equalTo(3));
 
             // Invalidate another key
             listener = new PlainActionFuture<>();
@@ -481,7 +481,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
             refreshSecurityIndex();
 
             // Get API keys to make sure remover didn't remove any yet (shouldn't be removed because of the long DELETE_INTERVAL)
-            assertThat(getAllApiKeyInfo(client, false).length, equalTo(3));
+            assertThat(getAllApiKeyInfo(client, false).size(), equalTo(3));
 
             // Update DELETE_INTERVAL to every 0 ms
             builder = Settings.builder();
@@ -499,7 +499,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
             // Make sure all keys except the last invalidated one are deleted
             // There is a (tiny) risk that the remover runs after the invalidation and therefore deletes the key that was just
             // invalidated, so 0 or 1 keys can be returned from the get api
-            assertThat(getAllApiKeyInfo(client, false).length, in(Set.of(0, 1)));
+            assertThat(getAllApiKeyInfo(client, false).size(), in(Set.of(0, 1)));
         } finally {
             final Settings.Builder builder = Settings.builder();
             builder.putNull(ApiKeyService.DELETE_INTERVAL.getKey());
@@ -516,7 +516,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
         assertThat(invalidateResponse.getInvalidatedApiKeys().size(), equalTo(noOfApiKeys));
         assertThat(
             invalidateResponse.getInvalidatedApiKeys(),
-            containsInAnyOrder(responses.stream().map(r -> r.getId()).collect(Collectors.toList()).toArray(Strings.EMPTY_ARRAY))
+            containsInAnyOrder(responses.stream().map(CreateApiKeyResponse::getId).toArray(String[]::new))
         );
         assertThat(invalidateResponse.getPreviouslyInvalidatedApiKeys().size(), equalTo(0));
         assertThat(invalidateResponse.getErrors().size(), equalTo(0));
@@ -588,7 +588,11 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
         // The first API key with 1ms expiration should already be deleted
         Set<String> expectedKeyIds = Sets.newHashSet(nonExpiringKey.getId(), createdApiKeys.get(0).getId(), createdApiKeys.get(1).getId());
         boolean apiKeyInvalidatedButNotYetDeletedByExpiredApiKeysRemover = false;
-        for (ApiKey apiKey : getApiKeyResponseListener.get().getApiKeyInfos()) {
+        for (ApiKey apiKey : getApiKeyResponseListener.get()
+            .getApiKeyInfoList()
+            .stream()
+            .map(GetApiKeyResponse.Item::apiKeyInfo)
+            .toList()) {
             assertThat(apiKey.getId(), is(in(expectedKeyIds)));
             if (apiKey.getId().equals(nonExpiringKey.getId())) {
                 assertThat(apiKey.isInvalidated(), is(false));
@@ -603,7 +607,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
             }
         }
         assertThat(
-            getApiKeyResponseListener.get().getApiKeyInfos().length,
+            getApiKeyResponseListener.get().getApiKeyInfoList().size(),
             is((apiKeyInvalidatedButNotYetDeletedByExpiredApiKeysRemover) ? 3 : 2)
         );
 
@@ -633,7 +637,11 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
         );
         expectedKeyIds = Sets.newHashSet(nonExpiringKey.getId(), createdApiKeys.get(1).getId());
         apiKeyInvalidatedButNotYetDeletedByExpiredApiKeysRemover = false;
-        for (ApiKey apiKey : getApiKeyResponseListener.get().getApiKeyInfos()) {
+        for (ApiKey apiKey : getApiKeyResponseListener.get()
+            .getApiKeyInfoList()
+            .stream()
+            .map(GetApiKeyResponse.Item::apiKeyInfo)
+            .toList()) {
             assertThat(apiKey.getId(), is(in(expectedKeyIds)));
             if (apiKey.getId().equals(nonExpiringKey.getId())) {
                 assertThat(apiKey.isInvalidated(), is(false));
@@ -645,7 +653,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
             }
         }
         assertThat(
-            getApiKeyResponseListener.get().getApiKeyInfos().length,
+            getApiKeyResponseListener.get().getApiKeyInfoList().size(),
             is((apiKeyInvalidatedButNotYetDeletedByExpiredApiKeysRemover) ? 2 : 1)
         );
     }
@@ -684,7 +692,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
             GetApiKeyRequest.builder().apiKeyName(namePrefix + "*").build(),
             getApiKeyResponseListener
         );
-        assertThat(getApiKeyResponseListener.get().getApiKeyInfos().length, is(noOfKeys));
+        assertThat(getApiKeyResponseListener.get().getApiKeyInfoList().size(), is(noOfKeys));
 
         // Expire the 1st key such that it cannot be deleted by the remover
         // hack doc to modify the expiration time
@@ -783,7 +791,11 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
             createdApiKeys.get(7).getId(),
             createdApiKeys.get(8).getId()
         );
-        for (ApiKey apiKey : getApiKeyResponseListener.get().getApiKeyInfos()) {
+        for (ApiKey apiKey : getApiKeyResponseListener.get()
+            .getApiKeyInfoList()
+            .stream()
+            .map(GetApiKeyResponse.Item::apiKeyInfo)
+            .toList()) {
             assertThat(apiKey.getId(), is(in(expectedKeyIds)));
             if (apiKey.getId().equals(createdApiKeys.get(0).getId())) {
                 // has been expired, not invalidated
@@ -805,7 +817,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
                 fail("unexpected API key " + apiKey);
             }
         }
-        assertThat(getApiKeyResponseListener.get().getApiKeyInfos().length, is(4));
+        assertThat(getApiKeyResponseListener.get().getApiKeyInfoList().size(), is(4));
     }
 
     private void refreshSecurityIndex() throws Exception {
@@ -842,7 +854,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
             tuple.v2(),
             List.of(DEFAULT_API_KEY_ROLE_DESCRIPTOR),
             withLimitedBy ? List.of(ES_TEST_ROOT_ROLE_DESCRIPTOR) : null,
-            response.getApiKeyInfos(),
+            response.getApiKeyInfoList(),
             Collections.singleton(responses.get(0).getId()),
             Collections.singletonList(responses.get(1).getId())
         );
@@ -888,7 +900,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
             tuple.v2(),
             List.of(DEFAULT_API_KEY_ROLE_DESCRIPTOR),
             withLimitedBy ? List.of(ES_TEST_ROOT_ROLE_DESCRIPTOR) : null,
-            response.getApiKeyInfos(),
+            response.getApiKeyInfoList(),
             expectedValidKeyIds,
             invalidatedApiKeyIds
         );
@@ -913,7 +925,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
             tuple.v2(),
             List.of(DEFAULT_API_KEY_ROLE_DESCRIPTOR),
             withLimitedBy ? List.of(ES_TEST_ROOT_ROLE_DESCRIPTOR) : null,
-            response.getApiKeyInfos(),
+            response.getApiKeyInfoList(),
             responses.stream().map(o -> o.getId()).collect(Collectors.toSet()),
             null
         );
@@ -937,7 +949,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
             tuple.v2(),
             List.of(DEFAULT_API_KEY_ROLE_DESCRIPTOR),
             withLimitedBy ? List.of(ES_TEST_ROOT_ROLE_DESCRIPTOR) : null,
-            response.getApiKeyInfos(),
+            response.getApiKeyInfoList(),
             Collections.singleton(responses.get(0).getId()),
             null
         );
@@ -961,7 +973,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
             tuple.v2(),
             List.of(DEFAULT_API_KEY_ROLE_DESCRIPTOR),
             withLimitedBy ? List.of(ES_TEST_ROOT_ROLE_DESCRIPTOR) : null,
-            response.getApiKeyInfos(),
+            response.getApiKeyInfoList(),
             Collections.singleton(responses.get(0).getId()),
             null
         );
@@ -1003,7 +1015,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
             metadatas,
             List.of(DEFAULT_API_KEY_ROLE_DESCRIPTOR),
             withLimitedBy ? List.of(ES_TEST_ROOT_ROLE_DESCRIPTOR) : null,
-            listener.get().getApiKeyInfos(),
+            listener.get().getApiKeyInfoList(),
             Collections.singleton(responses.get(0).getId()),
             null
         );
@@ -1020,7 +1032,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
             tuple1.v2(),
             List.of(DEFAULT_API_KEY_ROLE_DESCRIPTOR),
             withLimitedBy ? List.of(ES_TEST_ROOT_ROLE_DESCRIPTOR) : null,
-            listener2.get().getApiKeyInfos(),
+            listener2.get().getApiKeyInfoList(),
             createApiKeyResponses1.stream().map(CreateApiKeyResponse::getId).collect(Collectors.toSet()),
             null
         );
@@ -1043,7 +1055,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
             metadatas,
             List.of(DEFAULT_API_KEY_ROLE_DESCRIPTOR),
             withLimitedBy ? List.of(ES_TEST_ROOT_ROLE_DESCRIPTOR) : null,
-            listener3.get().getApiKeyInfos(),
+            listener3.get().getApiKeyInfoList(),
             responses.stream().map(CreateApiKeyResponse::getId).collect(Collectors.toSet()),
             null
         );
@@ -1060,7 +1072,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
             null,
             List.of(),
             List.of(),
-            listener4.get().getApiKeyInfos(),
+            listener4.get().getApiKeyInfoList(),
             Collections.emptySet(),
             null
         );
@@ -1077,7 +1089,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
             tuple2.v2(),
             List.of(DEFAULT_API_KEY_ROLE_DESCRIPTOR),
             withLimitedBy ? List.of(ES_TEST_ROOT_ROLE_DESCRIPTOR) : null,
-            listener5.get().getApiKeyInfos(),
+            listener5.get().getApiKeyInfoList(),
             createApiKeyResponses2.stream().map(CreateApiKeyResponse::getId).collect(Collectors.toSet()),
             null
         );
@@ -1121,7 +1133,6 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
             GetApiKeyRequest.builder().ownedByAuthenticatedUser().withLimitedBy(withLimitedBy).build(),
             listener
         );
-        GetApiKeyResponse response = listener.get();
         verifyApiKeyInfos(
             userWithManageApiKeyRole,
             noOfApiKeysForUserWithManageApiKeyRole,
@@ -1129,7 +1140,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
             tuple.v2(),
             List.of(DEFAULT_API_KEY_ROLE_DESCRIPTOR),
             expectedLimitedByRoleDescriptors,
-            response.getApiKeyInfos(),
+            listener.get().getApiKeyInfoList().stream().map(GetApiKeyResponse.Item::apiKeyInfo).toList(),
             userWithManageApiKeyRoleApiKeys.stream().map(o -> o.getId()).collect(Collectors.toSet()),
             null
         );
@@ -1155,7 +1166,6 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
             GetApiKeyRequest.builder().ownedByAuthenticatedUser().withLimitedBy(withLimitedBy).build(),
             listener
         );
-        GetApiKeyResponse response = listener.get();
         verifyApiKeyInfos(
             "user_with_manage_own_api_key_role",
             noOfApiKeysForUserWithManageApiKeyRole,
@@ -1165,7 +1175,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
             withLimitedBy
                 ? List.of(new RoleDescriptor("manage_own_api_key_role", new String[] { "manage_own_api_key" }, null, null))
                 : null,
-            response.getApiKeyInfos(),
+            listener.get().getApiKeyInfoList().stream().map(GetApiKeyResponse.Item::apiKeyInfo).toList(),
             userWithManageOwnApiKeyRoleApiKeys.stream().map(o -> o.getId()).collect(Collectors.toSet()),
             null
         );
@@ -1191,7 +1201,6 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
             GetApiKeyRequest.builder().realmName("file").userName("user_with_manage_own_api_key_role").withLimitedBy(withLimitedBy).build(),
             listener
         );
-        GetApiKeyResponse response = listener.get();
         verifyApiKeyInfos(
             "user_with_manage_own_api_key_role",
             noOfApiKeysForUserWithManageApiKeyRole,
@@ -1201,7 +1210,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
             withLimitedBy
                 ? List.of(new RoleDescriptor("manage_own_api_key_role", new String[] { "manage_own_api_key" }, null, null))
                 : null,
-            response.getApiKeyInfos(),
+            listener.get().getApiKeyInfoList().stream().map(GetApiKeyResponse.Item::apiKeyInfo).toList(),
             userWithManageOwnApiKeyRoleApiKeys.stream().map(o -> o.getId()).collect(Collectors.toSet()),
             null
         );
@@ -1451,7 +1460,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
             tuple.v2(),
             List.of(new RoleDescriptor(DEFAULT_API_KEY_ROLE_DESCRIPTOR.getName(), Strings.EMPTY_ARRAY, null, null)),
             null,
-            response.getApiKeyInfos(),
+            response.getApiKeyInfoList(),
             Collections.singleton(responses.get(0).getId()),
             null
         );
@@ -1503,12 +1512,13 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
 
         // Can view itself without limited-by
         verifyApiKeyInfos(
+            ES_TEST_ROOT_USER,
             1,
             responses1,
             tuple1.v2(),
             List.of(new RoleDescriptor(DEFAULT_API_KEY_ROLE_DESCRIPTOR.getName(), new String[] { "manage_own_api_key" }, null, null)),
             null,
-            new ApiKey[] { getApiKeyInfo(client1, apiKeyId1, false, randomBoolean()) },
+            List.of(getApiKeyInfo(client1, apiKeyId1, false, randomBoolean())),
             Collections.singleton(apiKeyId1),
             null
         );
@@ -1538,24 +1548,26 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
 
         // View its own limited-by
         verifyApiKeyInfos(
+            ES_TEST_ROOT_USER,
             1,
             responses3,
             tuple3.v2(),
             List.of(new RoleDescriptor(DEFAULT_API_KEY_ROLE_DESCRIPTOR.getName(), new String[] { "manage_api_key" }, null, null)),
             List.of(ES_TEST_ROOT_ROLE_DESCRIPTOR),
-            new ApiKey[] { getApiKeyInfo(client3, apiKeyId3, true, randomBoolean()) },
+            List.of(getApiKeyInfo(client3, apiKeyId3, true, randomBoolean())),
             Collections.singleton(apiKeyId3),
             null
         );
 
         // View other key's limited-by
         verifyApiKeyInfos(
+            ES_TEST_ROOT_USER,
             1,
             responses1,
             tuple1.v2(),
             List.of(new RoleDescriptor(DEFAULT_API_KEY_ROLE_DESCRIPTOR.getName(), new String[] { "manage_own_api_key" }, null, null)),
             List.of(ES_TEST_ROOT_ROLE_DESCRIPTOR),
-            new ApiKey[] { getApiKeyInfo(client3, apiKeyId1, true, randomBoolean()) },
+            List.of(getApiKeyInfo(client3, apiKeyId1, true, randomBoolean())),
             Collections.singleton(apiKeyId1),
             null
         );
@@ -1721,8 +1733,8 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
             GetApiKeyRequest.builder().apiKeyId(key100Response.getId()).withLimitedBy().build(),
             future
         );
-        assertThat(future.actionGet().getApiKeyInfos().length, equalTo(1));
-        final RoleDescriptorsIntersection limitedBy = future.actionGet().getApiKeyInfos()[0].getLimitedBy();
+        assertThat(future.actionGet().getApiKeyInfoList().size(), equalTo(1));
+        RoleDescriptorsIntersection limitedBy = future.actionGet().getApiKeyInfoList().get(0).apiKeyInfo().getLimitedBy();
         assertThat(limitedBy.roleDescriptorsList().size(), equalTo(1));
         assertThat(limitedBy.roleDescriptorsList().iterator().next(), emptyIterable());
 
@@ -1761,8 +1773,8 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
             GetApiKeyAction.INSTANCE,
             GetApiKeyRequest.builder().apiKeyId(response2.getId()).ownedByAuthenticatedUser(true).build()
         ).actionGet();
-        assertThat(getApiKeyResponse.getApiKeyInfos(), arrayWithSize(1));
-        final ApiKey apiKeyInfo = getApiKeyResponse.getApiKeyInfos()[0];
+        assertThat(getApiKeyResponse.getApiKeyInfoList(), iterableWithSize(1));
+        ApiKey apiKeyInfo = getApiKeyResponse.getApiKeyInfoList().get(0).apiKeyInfo();
         assertThat(apiKeyInfo.getId(), equalTo(response2.getId()));
         assertThat(apiKeyInfo.getUsername(), equalTo(ES_TEST_ROOT_USER));
         assertThat(apiKeyInfo.getRealm(), equalTo("file"));
@@ -2885,8 +2897,8 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
                 future
             );
             final GetApiKeyResponse getApiKeyResponse = future.actionGet();
-            assertThat(getApiKeyResponse.getApiKeyInfos(), arrayWithSize(1));
-            return getApiKeyResponse.getApiKeyInfos()[0];
+            assertThat(getApiKeyResponse.getApiKeyInfoList(), iterableWithSize(1));
+            return getApiKeyResponse.getApiKeyInfoList().get(0).apiKeyInfo();
         } else {
             final PlainActionFuture<QueryApiKeyResponse> future = new PlainActionFuture<>();
             client.execute(
@@ -2895,17 +2907,17 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
                 future
             );
             final QueryApiKeyResponse queryApiKeyResponse = future.actionGet();
-            assertThat(queryApiKeyResponse.getItems(), arrayWithSize(1));
-            return queryApiKeyResponse.getItems()[0].getApiKey();
+            assertThat(queryApiKeyResponse.getApiKeyInfoList(), iterableWithSize(1));
+            return queryApiKeyResponse.getApiKeyInfoList().get(0).apiKeyInfo();
         }
     }
 
-    private ApiKey[] getAllApiKeyInfo(Client client, boolean withLimitedBy) {
+    private List<ApiKey> getAllApiKeyInfo(Client client, boolean withLimitedBy) {
         if (randomBoolean()) {
             final PlainActionFuture<GetApiKeyResponse> future = new PlainActionFuture<>();
             client.execute(GetApiKeyAction.INSTANCE, GetApiKeyRequest.builder().withLimitedBy(withLimitedBy).build(), future);
             final GetApiKeyResponse getApiKeyResponse = future.actionGet();
-            return getApiKeyResponse.getApiKeyInfos();
+            return getApiKeyResponse.getApiKeyInfoList().stream().map(GetApiKeyResponse.Item::apiKeyInfo).toList();
         } else {
             final PlainActionFuture<QueryApiKeyResponse> future = new PlainActionFuture<>();
             client.execute(
@@ -2914,7 +2926,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
                 future
             );
             final QueryApiKeyResponse queryApiKeyResponse = future.actionGet();
-            return Arrays.stream(queryApiKeyResponse.getItems()).map(QueryApiKeyResponse.Item::getApiKey).toArray(ApiKey[]::new);
+            return queryApiKeyResponse.getApiKeyInfoList().stream().map(QueryApiKeyResponse.Item::apiKeyInfo).toList();
         }
     }
 
@@ -2967,7 +2979,8 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
             0,
             client.execute(GetApiKeyAction.INSTANCE, GetApiKeyRequest.builder().apiKeyName(keyName).ownedByAuthenticatedUser(false).build())
                 .get()
-                .getApiKeyInfos().length
+                .getApiKeyInfoList()
+                .size()
         );
     }
 
@@ -2977,7 +2990,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
         List<Map<String, Object>> metadatas,
         List<RoleDescriptor> expectedRoleDescriptors,
         List<RoleDescriptor> expectedLimitedByRoleDescriptors,
-        ApiKey[] apiKeyInfos,
+        List<GetApiKeyResponse.Item> apiKeyInfos,
         Set<String> validApiKeyIds,
         List<String> invalidatedApiKeyIds
     ) {
@@ -2988,7 +3001,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
             metadatas,
             expectedRoleDescriptors,
             expectedLimitedByRoleDescriptors,
-            apiKeyInfos,
+            apiKeyInfos.stream().map(GetApiKeyResponse.Item::apiKeyInfo).toList(),
             validApiKeyIds,
             invalidatedApiKeyIds
         );
@@ -3001,7 +3014,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
         List<Map<String, Object>> metadatas,
         List<RoleDescriptor> expectedRoleDescriptors,
         List<RoleDescriptor> expectedLimitedByRoleDescriptors,
-        ApiKey[] apiKeyInfos,
+        List<ApiKey> apiKeyInfos,
         Set<String> validApiKeyIds,
         List<String> invalidatedApiKeyIds
     ) {
@@ -3025,16 +3038,16 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
         List<Map<String, Object>> metadatas,
         List<RoleDescriptor> expectedRoleDescriptors,
         Function<String, List<RoleDescriptor>> expectedLimitedByRoleDescriptorsLookup,
-        ApiKey[] apiKeyInfos,
+        List<ApiKey> apiKeyInfos,
         Set<String> validApiKeyIds,
         List<String> invalidatedApiKeyIds
     ) {
-        assertThat(apiKeyInfos.length, equalTo(expectedNumberOfApiKeys));
+        assertThat(apiKeyInfos.size(), equalTo(expectedNumberOfApiKeys));
         List<String> expectedIds = responses.stream()
             .filter(o -> validApiKeyIds.contains(o.getId()))
             .map(o -> o.getId())
             .collect(Collectors.toList());
-        List<String> actualIds = Arrays.stream(apiKeyInfos)
+        List<String> actualIds = apiKeyInfos.stream()
             .filter(o -> o.isInvalidated() == false)
             .map(o -> o.getId())
             .collect(Collectors.toList());
@@ -3043,19 +3056,19 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
             .filter(o -> validApiKeyIds.contains(o.getId()))
             .map(o -> o.getName())
             .collect(Collectors.toList());
-        List<String> actualNames = Arrays.stream(apiKeyInfos)
+        List<String> actualNames = apiKeyInfos.stream()
             .filter(o -> o.isInvalidated() == false)
             .map(o -> o.getName())
             .collect(Collectors.toList());
         assertThat(actualNames, containsInAnyOrder(expectedNames.toArray(Strings.EMPTY_ARRAY)));
         Set<String> expectedUsernames = (validApiKeyIds.isEmpty()) ? Collections.emptySet() : Set.of(user);
-        Set<String> actualUsernames = Arrays.stream(apiKeyInfos)
+        Set<String> actualUsernames = apiKeyInfos.stream()
             .filter(o -> o.isInvalidated() == false)
             .map(o -> o.getUsername())
             .collect(Collectors.toSet());
         assertThat(actualUsernames, containsInAnyOrder(expectedUsernames.toArray(Strings.EMPTY_ARRAY)));
         if (invalidatedApiKeyIds != null) {
-            List<String> actualInvalidatedApiKeyIds = Arrays.stream(apiKeyInfos)
+            List<String> actualInvalidatedApiKeyIds = apiKeyInfos.stream()
                 .filter(o -> o.isInvalidated())
                 .map(o -> o.getId())
                 .collect(Collectors.toList());
@@ -3073,7 +3086,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
                 assertThat(apiKey.getMetadata(), equalTo(metadata == null ? Map.of() : metadata));
             }
         }
-        Arrays.stream(apiKeyInfos).forEach(apiKeyInfo -> {
+        apiKeyInfos.stream().forEach(apiKeyInfo -> {
             assertThat(apiKeyInfo.getRoleDescriptors(), containsInAnyOrder(expectedRoleDescriptors.toArray(RoleDescriptor[]::new)));
             final List<RoleDescriptor> expectedLimitedByRoleDescriptors = expectedLimitedByRoleDescriptorsLookup.apply(
                 apiKeyInfo.getUsername()

+ 15 - 15
x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authc/apikey/ApiKeySingleNodeTests.java

@@ -92,7 +92,6 @@ import static org.elasticsearch.test.SecuritySettingsSourceField.TEST_PASSWORD;
 import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue;
 import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SECURITY_MAIN_ALIAS;
 import static org.hamcrest.Matchers.anEmptyMap;
-import static org.hamcrest.Matchers.arrayWithSize;
 import static org.hamcrest.Matchers.contains;
 import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.Matchers.emptyArray;
@@ -101,6 +100,7 @@ import static org.hamcrest.Matchers.greaterThanOrEqualTo;
 import static org.hamcrest.Matchers.hasKey;
 import static org.hamcrest.Matchers.instanceOf;
 import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.iterableWithSize;
 import static org.hamcrest.Matchers.lessThanOrEqualTo;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.hamcrest.Matchers.nullValue;
@@ -148,10 +148,10 @@ public class ApiKeySingleNodeTests extends SecuritySingleNodeTestCase {
                 .filter(QueryBuilders.rangeQuery("expiration").from(Instant.now().toEpochMilli()))
         );
         final QueryApiKeyResponse queryApiKeyResponse = client().execute(QueryApiKeyAction.INSTANCE, queryApiKeyRequest).actionGet();
-        assertThat(queryApiKeyResponse.getItems().length, equalTo(1));
-        assertThat(queryApiKeyResponse.getItems()[0].getApiKey().getId(), equalTo(id2));
-        assertThat(queryApiKeyResponse.getItems()[0].getApiKey().getName(), equalTo("long-lived"));
-        assertThat(queryApiKeyResponse.getItems()[0].getSortValues(), emptyArray());
+        assertThat(queryApiKeyResponse.getApiKeyInfoList(), iterableWithSize(1));
+        assertThat(queryApiKeyResponse.getApiKeyInfoList().get(0).apiKeyInfo().getId(), equalTo(id2));
+        assertThat(queryApiKeyResponse.getApiKeyInfoList().get(0).apiKeyInfo().getName(), equalTo("long-lived"));
+        assertThat(queryApiKeyResponse.getApiKeyInfoList().get(0).sortValues(), emptyArray());
     }
 
     public void testCreatingApiKeyWithNoAccess() {
@@ -286,8 +286,8 @@ public class ApiKeySingleNodeTests extends SecuritySingleNodeTestCase {
             GetApiKeyAction.INSTANCE,
             GetApiKeyRequest.builder().apiKeyId(apiKeyId).ownedByAuthenticatedUser(randomBoolean()).build()
         ).actionGet();
-        assertThat(getApiKeyResponse.getApiKeyInfos().length, equalTo(1));
-        assertThat(getApiKeyResponse.getApiKeyInfos()[0].getId(), equalTo(apiKeyId));
+        assertThat(getApiKeyResponse.getApiKeyInfoList(), iterableWithSize(1));
+        assertThat(getApiKeyResponse.getApiKeyInfoList().get(0).apiKeyInfo().getId(), is(apiKeyId));
 
         // Cannot get any other keys
         final ElasticsearchSecurityException e = expectThrows(
@@ -613,8 +613,8 @@ public class ApiKeySingleNodeTests extends SecuritySingleNodeTestCase {
             GetApiKeyAction.INSTANCE,
             GetApiKeyRequest.builder().apiKeyId(apiKeyId).withLimitedBy(randomBoolean()).build()
         ).actionGet();
-        assertThat(getApiKeyResponse.getApiKeyInfos(), arrayWithSize(1));
-        final ApiKey getApiKeyInfo = getApiKeyResponse.getApiKeyInfos()[0];
+        assertThat(getApiKeyResponse.getApiKeyInfoList(), iterableWithSize(1));
+        ApiKey getApiKeyInfo = getApiKeyResponse.getApiKeyInfoList().get(0).apiKeyInfo();
         assertThat(getApiKeyInfo.getType(), is(ApiKey.Type.CROSS_CLUSTER));
         assertThat(getApiKeyInfo.getRoleDescriptors(), contains(expectedRoleDescriptor));
         assertThat(getApiKeyInfo.getLimitedBy(), nullValue());
@@ -634,8 +634,8 @@ public class ApiKeySingleNodeTests extends SecuritySingleNodeTestCase {
             randomBoolean()
         );
         final QueryApiKeyResponse queryApiKeyResponse = client().execute(QueryApiKeyAction.INSTANCE, queryApiKeyRequest).actionGet();
-        assertThat(queryApiKeyResponse.getItems(), arrayWithSize(1));
-        final ApiKey queryApiKeyInfo = queryApiKeyResponse.getItems()[0].getApiKey();
+        assertThat(queryApiKeyResponse.getApiKeyInfoList(), iterableWithSize(1));
+        ApiKey queryApiKeyInfo = queryApiKeyResponse.getApiKeyInfoList().get(0).apiKeyInfo();
         assertThat(queryApiKeyInfo.getType(), is(ApiKey.Type.CROSS_CLUSTER));
         assertThat(queryApiKeyInfo.getRoleDescriptors(), contains(expectedRoleDescriptor));
         assertThat(queryApiKeyInfo.getLimitedBy(), nullValue());
@@ -669,8 +669,8 @@ public class ApiKeySingleNodeTests extends SecuritySingleNodeTestCase {
             GetApiKeyAction.INSTANCE,
             GetApiKeyRequest.builder().apiKeyId(apiKeyId).withLimitedBy(randomBoolean()).build()
         ).actionGet();
-        assertThat(getApiKeyResponse.getApiKeyInfos(), arrayWithSize(1));
-        final ApiKey getApiKeyInfo = getApiKeyResponse.getApiKeyInfos()[0];
+        assertThat(getApiKeyResponse.getApiKeyInfoList(), iterableWithSize(1));
+        ApiKey getApiKeyInfo = getApiKeyResponse.getApiKeyInfoList().get(0).apiKeyInfo();
         assertThat(getApiKeyInfo.getType(), is(ApiKey.Type.CROSS_CLUSTER));
         assertThat(getApiKeyInfo.getRoleDescriptors(), contains(originalRoleDescriptor));
         assertThat(getApiKeyInfo.getLimitedBy(), nullValue());
@@ -740,8 +740,8 @@ public class ApiKeySingleNodeTests extends SecuritySingleNodeTestCase {
             randomBoolean()
         );
         final QueryApiKeyResponse queryApiKeyResponse = client().execute(QueryApiKeyAction.INSTANCE, queryApiKeyRequest).actionGet();
-        assertThat(queryApiKeyResponse.getItems(), arrayWithSize(1));
-        final ApiKey queryApiKeyInfo = queryApiKeyResponse.getItems()[0].getApiKey();
+        assertThat(queryApiKeyResponse.getApiKeyInfoList(), iterableWithSize(1));
+        final ApiKey queryApiKeyInfo = queryApiKeyResponse.getApiKeyInfoList().get(0).apiKeyInfo();
         assertThat(queryApiKeyInfo.getType(), is(ApiKey.Type.CROSS_CLUSTER));
         assertThat(
             queryApiKeyInfo.getRoleDescriptors(),

+ 3 - 3
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java

@@ -1927,7 +1927,7 @@ public class ApiKeyService {
                         Arrays.toString(apiKeyIds),
                         activeOnly
                     );
-                    listener.onResponse(GetApiKeyResponse.emptyResponse());
+                    listener.onResponse(GetApiKeyResponse.EMPTY);
                 } else {
                     listener.onResponse(new GetApiKeyResponse(apiKeyInfos));
                 }
@@ -1940,7 +1940,7 @@ public class ApiKeyService {
         final SecurityIndexManager frozenSecurityIndex = securityIndex.defensiveCopy();
         if (frozenSecurityIndex.indexExists() == false) {
             logger.debug("security index does not exist");
-            listener.onResponse(QueryApiKeyResponse.emptyResponse());
+            listener.onResponse(QueryApiKeyResponse.EMPTY);
         } else if (frozenSecurityIndex.isAvailable(SEARCH_SHARDS) == false) {
             listener.onFailure(frozenSecurityIndex.getUnavailableReason(SEARCH_SHARDS));
         } else {
@@ -1955,7 +1955,7 @@ public class ApiKeyService {
                         final long total = searchResponse.getHits().getTotalHits().value;
                         if (total == 0) {
                             logger.debug("No api keys found for query [{}]", searchRequest.source().query());
-                            listener.onResponse(QueryApiKeyResponse.emptyResponse());
+                            listener.onResponse(QueryApiKeyResponse.EMPTY);
                             return;
                         }
                         final List<QueryApiKeyResponse.Item> apiKeyItem = Arrays.stream(searchResponse.getHits().getHits())

+ 1 - 1
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/apikey/RestGetApiKeyAction.java

@@ -66,7 +66,7 @@ public final class RestGetApiKeyAction extends ApiKeyBaseRestHandler {
                 getApiKeyResponse.toXContent(builder, channel.request());
 
                 // return HTTP status 404 if no API key found for API key id
-                if (Strings.hasText(apiKeyId) && getApiKeyResponse.getApiKeyInfos().length == 0) {
+                if (Strings.hasText(apiKeyId) && getApiKeyResponse.getApiKeyInfoList().isEmpty()) {
                     return new RestResponse(RestStatus.NOT_FOUND, builder);
                 }
                 return new RestResponse(RestStatus.OK, builder);

+ 19 - 15
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyServiceTests.java

@@ -181,6 +181,7 @@ import static org.hamcrest.Matchers.hasEntry;
 import static org.hamcrest.Matchers.hasSize;
 import static org.hamcrest.Matchers.instanceOf;
 import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.iterableWithSize;
 import static org.hamcrest.Matchers.lessThanOrEqualTo;
 import static org.hamcrest.Matchers.not;
 import static org.hamcrest.Matchers.notNullValue;
@@ -326,7 +327,7 @@ public class ApiKeyServiceTests extends ESTestCase {
         verify(searchRequestBuilder).setFetchSource(eq(true));
         assertThat(searchRequest.get().source().query(), is(boolQuery));
         GetApiKeyResponse getApiKeyResponse = getApiKeyResponsePlainActionFuture.get();
-        assertThat(getApiKeyResponse.getApiKeyInfos(), emptyArray());
+        assertThat(getApiKeyResponse.getApiKeyInfoList(), emptyIterable());
     }
 
     @SuppressWarnings("unchecked")
@@ -407,28 +408,31 @@ public class ApiKeyServiceTests extends ESTestCase {
                 getApiKeyResponsePlainActionFuture
             );
             GetApiKeyResponse getApiKeyResponse = getApiKeyResponsePlainActionFuture.get();
-            assertThat(getApiKeyResponse.getApiKeyInfos().length, is(2));
-            assertThat(getApiKeyResponse.getApiKeyInfos()[0].getRealm(), is(realm1));
-            assertThat(getApiKeyResponse.getApiKeyInfos()[0].getRealmType(), is(realm1Type));
-            assertThat(getApiKeyResponse.getApiKeyInfos()[0].getRealmIdentifier(), is(new RealmConfig.RealmIdentifier(realm1Type, realm1)));
-            assertThat(getApiKeyResponse.getApiKeyInfos()[1].getRealm(), is(realm2));
-            assertThat(getApiKeyResponse.getApiKeyInfos()[1].getRealmType(), nullValue());
-            assertThat(getApiKeyResponse.getApiKeyInfos()[1].getRealmIdentifier(), nullValue());
+            assertThat(getApiKeyResponse.getApiKeyInfoList(), iterableWithSize(2));
+            assertThat(getApiKeyResponse.getApiKeyInfoList().get(0).apiKeyInfo().getRealm(), is(realm1));
+            assertThat(getApiKeyResponse.getApiKeyInfoList().get(0).apiKeyInfo().getRealmType(), is(realm1Type));
+            assertThat(
+                getApiKeyResponse.getApiKeyInfoList().get(0).apiKeyInfo().getRealmIdentifier(),
+                is(new RealmConfig.RealmIdentifier(realm1Type, realm1))
+            );
+            assertThat(getApiKeyResponse.getApiKeyInfoList().get(1).apiKeyInfo().getRealm(), is(realm2));
+            assertThat(getApiKeyResponse.getApiKeyInfoList().get(1).apiKeyInfo().getRealmType(), nullValue());
+            assertThat(getApiKeyResponse.getApiKeyInfoList().get(1).apiKeyInfo().getRealmIdentifier(), nullValue());
         }
         {
             PlainActionFuture<QueryApiKeyResponse> queryApiKeyResponsePlainActionFuture = new PlainActionFuture<>();
             service.queryApiKeys(new SearchRequest(".security"), false, queryApiKeyResponsePlainActionFuture);
             QueryApiKeyResponse queryApiKeyResponse = queryApiKeyResponsePlainActionFuture.get();
-            assertThat(queryApiKeyResponse.getItems().length, is(2));
-            assertThat(queryApiKeyResponse.getItems()[0].getApiKey().getRealm(), is(realm1));
-            assertThat(queryApiKeyResponse.getItems()[0].getApiKey().getRealmType(), is(realm1Type));
+            assertThat(queryApiKeyResponse.getApiKeyInfoList(), iterableWithSize(2));
+            assertThat(queryApiKeyResponse.getApiKeyInfoList().get(0).apiKeyInfo().getRealm(), is(realm1));
+            assertThat(queryApiKeyResponse.getApiKeyInfoList().get(0).apiKeyInfo().getRealmType(), is(realm1Type));
             assertThat(
-                queryApiKeyResponse.getItems()[0].getApiKey().getRealmIdentifier(),
+                queryApiKeyResponse.getApiKeyInfoList().get(0).apiKeyInfo().getRealmIdentifier(),
                 is(new RealmConfig.RealmIdentifier(realm1Type, realm1))
             );
-            assertThat(queryApiKeyResponse.getItems()[1].getApiKey().getRealm(), is(realm2));
-            assertThat(queryApiKeyResponse.getItems()[1].getApiKey().getRealmType(), nullValue());
-            assertThat(queryApiKeyResponse.getItems()[1].getApiKey().getRealmIdentifier(), nullValue());
+            assertThat(queryApiKeyResponse.getApiKeyInfoList().get(1).apiKeyInfo().getRealm(), is(realm2));
+            assertThat(queryApiKeyResponse.getApiKeyInfoList().get(1).apiKeyInfo().getRealmType(), nullValue());
+            assertThat(queryApiKeyResponse.getApiKeyInfoList().get(1).apiKeyInfo().getRealmIdentifier(), nullValue());
         }
     }
 

+ 24 - 23
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/apikey/RestGetApiKeyActionTests.java

@@ -44,7 +44,8 @@ import java.util.Map;
 
 import static org.elasticsearch.xpack.core.security.authz.RoleDescriptorTests.randomCrossClusterAccessRoleDescriptor;
 import static org.elasticsearch.xpack.core.security.authz.RoleDescriptorTests.randomUniquelyNamedRoleDescriptors;
-import static org.hamcrest.Matchers.arrayContaining;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.emptyIterable;
 import static org.hamcrest.Matchers.is;
 import static org.mockito.Mockito.mock;
 
@@ -144,7 +145,7 @@ public class RestGetApiKeyActionTests extends ESTestCase {
                     || getApiKeyRequest.getRealmName() != null && getApiKeyRequest.getRealmName().equals("realm-1")
                     || getApiKeyRequest.getUserName() != null && getApiKeyRequest.getUserName().equals("user-x")) {
                     if (replyEmptyResponse) {
-                        listener.onResponse((Response) GetApiKeyResponse.emptyResponse());
+                        listener.onResponse((Response) GetApiKeyResponse.EMPTY);
                     } else {
                         listener.onResponse((Response) getApiKeyResponseExpected);
                     }
@@ -162,25 +163,27 @@ public class RestGetApiKeyActionTests extends ESTestCase {
         assertThat(restResponse.status(), (replyEmptyResponse && params.get("id") != null) ? is(RestStatus.NOT_FOUND) : is(RestStatus.OK));
         final GetApiKeyResponse actual = GetApiKeyResponse.fromXContent(createParser(XContentType.JSON.xContent(), restResponse.content()));
         if (replyEmptyResponse) {
-            assertThat(actual.getApiKeyInfos().length, is(0));
+            assertThat(actual.getApiKeyInfoList(), emptyIterable());
         } else {
             assertThat(
-                actual.getApiKeyInfos(),
-                arrayContaining(
-                    new ApiKey(
-                        "api-key-name-1",
-                        "api-key-id-1",
-                        type,
-                        creation,
-                        expiration,
-                        false,
-                        null,
-                        "user-x",
-                        "realm-1",
-                        "realm-type-1",
-                        metadata,
-                        roleDescriptors,
-                        limitedByRoleDescriptors
+                actual.getApiKeyInfoList(),
+                contains(
+                    new GetApiKeyResponse.Item(
+                        new ApiKey(
+                            "api-key-name-1",
+                            "api-key-id-1",
+                            type,
+                            creation,
+                            expiration,
+                            false,
+                            null,
+                            "user-x",
+                            "realm-1",
+                            "realm-type-1",
+                            metadata,
+                            roleDescriptors,
+                            limitedByRoleDescriptors
+                        )
                     )
                 )
             );
@@ -286,11 +289,9 @@ public class RestGetApiKeyActionTests extends ESTestCase {
         assertThat(restResponse.status(), is(RestStatus.OK));
         final GetApiKeyResponse actual = GetApiKeyResponse.fromXContent(createParser(XContentType.JSON.xContent(), restResponse.content()));
         if (isGetRequestForOwnedKeysOnly) {
-            assertThat(actual.getApiKeyInfos().length, is(1));
-            assertThat(actual.getApiKeyInfos(), arrayContaining(apiKey1));
+            assertThat(actual.getApiKeyInfoList().stream().map(GetApiKeyResponse.Item::apiKeyInfo).toList(), contains(apiKey1));
         } else {
-            assertThat(actual.getApiKeyInfos().length, is(2));
-            assertThat(actual.getApiKeyInfos(), arrayContaining(apiKey1, apiKey2));
+            assertThat(actual.getApiKeyInfoList().stream().map(GetApiKeyResponse.Item::apiKeyInfo).toList(), contains(apiKey1, apiKey2));
         }
     }
 }

+ 2 - 2
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/apikey/RestQueryApiKeyActionTests.java

@@ -126,7 +126,7 @@ public class RestQueryApiKeyActionTests extends ESTestCase {
                 final QueryBuilder shouldQueryBuilder = boolQueryBuilder.should().get(0);
                 assertThat(shouldQueryBuilder.getClass(), is(PrefixQueryBuilder.class));
                 assertThat(((PrefixQueryBuilder) shouldQueryBuilder).fieldName(), equalTo("metadata.environ"));
-                listener.onResponse((Response) QueryApiKeyResponse.emptyResponse());
+                listener.onResponse((Response) QueryApiKeyResponse.EMPTY);
             }
         };
         final RestQueryApiKeyAction restQueryApiKeyAction = new RestQueryApiKeyAction(Settings.EMPTY, mockLicenseState);
@@ -190,7 +190,7 @@ public class RestQueryApiKeyActionTests extends ESTestCase {
                     equalTo(new SearchAfterBuilder().setSortValues(new String[] { "key-2048", "2021-07-01T00:00:59.000Z" }))
                 );
 
-                listener.onResponse((Response) QueryApiKeyResponse.emptyResponse());
+                listener.onResponse((Response) QueryApiKeyResponse.EMPTY);
             }
         };