Support prefix search for API key names (#59113) (#59520)

This PR adds minimum support for prefix search of API Key name. It only touches API key name and leave all other query parameters, e.g. realm name, username unchanged.
This commit is contained in:
Yang Wang 2020-07-14 22:06:20 +10:00 committed by GitHub
parent 7dcdaeae49
commit f651487d74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 57 additions and 11 deletions

View File

@ -30,12 +30,12 @@ The following parameters can be specified in the query parameters of a GET reque
pertain to retrieving api keys: pertain to retrieving api keys:
`id`:: `id`::
(Optional, string) An API key id. This parameter cannot be used with any of (Optional, string) An API key id. This parameter cannot be used with any of
`name`, `realm_name` or `username` are used. `name`, `realm_name` or `username` are used.
`name`:: `name`::
(Optional, string) An API key name. This parameter cannot be used with any of (Optional, string) An API key name. This parameter cannot be used with any of
`id`, `realm_name` or `username` are used. `id`, `realm_name` or `username` are used. It supports prefix search with wildcard.
`realm_name`:: `realm_name`::
(Optional, string) The name of an authentication realm. This parameter cannot be (Optional, string) The name of an authentication realm. This parameter cannot be
@ -47,7 +47,7 @@ either `id` or `name` or when `owner` flag is set to `true`.
`owner`:: `owner`::
(Optional, boolean) A boolean flag that can be used to query API keys owned (Optional, boolean) A boolean flag that can be used to query API keys owned
by the currently authenticated user. Defaults to false. by the currently authenticated user. Defaults to false.
The 'realm_name' or 'username' parameters cannot be specified when this The 'realm_name' or 'username' parameters cannot be specified when this
parameter is set to 'true' as they are assumed to be the currently authenticated ones. parameter is set to 'true' as they are assumed to be the currently authenticated ones.
@ -101,6 +101,14 @@ GET /_security/api_key?name=my-api-key
-------------------------------------------------- --------------------------------------------------
// TEST[continued] // TEST[continued]
API key name supports prefix search by using wildcard:
[source,console]
--------------------------------------------------
GET /_security/api_key?name=my-*
--------------------------------------------------
// TEST[continued]
The following example retrieves all API keys for the `native1` realm: The following example retrieves all API keys for the `native1` realm:
[source,console] [source,console]

View File

@ -830,8 +830,12 @@ public class ApiKeyService {
if (Strings.hasText(userName)) { if (Strings.hasText(userName)) {
boolQuery.filter(QueryBuilders.termQuery("creator.principal", userName)); boolQuery.filter(QueryBuilders.termQuery("creator.principal", userName));
} }
if (Strings.hasText(apiKeyName)) { if (Strings.hasText(apiKeyName) && "*".equals(apiKeyName) == false) {
boolQuery.filter(QueryBuilders.termQuery("name", apiKeyName)); if (apiKeyName.endsWith("*")) {
boolQuery.filter(QueryBuilders.prefixQuery("name", apiKeyName.substring(0, apiKeyName.length() - 1)));
} else {
boolQuery.filter(QueryBuilders.termQuery("name", apiKeyName));
}
} }
if (Strings.hasText(apiKeyId)) { if (Strings.hasText(apiKeyId)) {
boolQuery.filter(QueryBuilders.termQuery("_id", apiKeyId)); boolQuery.filter(QueryBuilders.termQuery("_id", apiKeyId));

View File

@ -546,14 +546,43 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
} }
public void testGetApiKeysForApiKeyName() throws InterruptedException, ExecutionException { public void testGetApiKeysForApiKeyName() throws InterruptedException, ExecutionException {
List<CreateApiKeyResponse> responses = createApiKeys(1, null); final Map<String, String> headers = Collections.singletonMap(
Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken "Authorization",
.basicAuthHeaderValue(SecuritySettingsSource.TEST_SUPERUSER, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); UsernamePasswordToken.basicAuthHeaderValue(
SecuritySettingsSource.TEST_SUPERUSER,
SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING));
final int noOfApiKeys = randomIntBetween(1, 3);
final List<CreateApiKeyResponse> createApiKeyResponses1 = createApiKeys(noOfApiKeys, null);
final List<CreateApiKeyResponse> createApiKeyResponses2 = createApiKeys(
headers, noOfApiKeys, "another-test-key-", null, "monitor");
Client client = client().filterWithHeader(headers);
SecurityClient securityClient = new SecurityClient(client); SecurityClient securityClient = new SecurityClient(client);
PlainActionFuture<GetApiKeyResponse> listener = new PlainActionFuture<>(); PlainActionFuture<GetApiKeyResponse> listener = new PlainActionFuture<>();
List<CreateApiKeyResponse> responses = randomFrom(createApiKeyResponses1, createApiKeyResponses2);
securityClient.getApiKey(GetApiKeyRequest.usingApiKeyName(responses.get(0).getName(), false), listener); securityClient.getApiKey(GetApiKeyRequest.usingApiKeyName(responses.get(0).getName(), false), listener);
GetApiKeyResponse response = listener.get(); verifyGetResponse(1, responses, listener.get(), Collections.singleton(responses.get(0).getId()), null);
verifyGetResponse(1, responses, response, Collections.singleton(responses.get(0).getId()), null);
PlainActionFuture<GetApiKeyResponse> listener2 = new PlainActionFuture<>();
securityClient.getApiKey(GetApiKeyRequest.usingApiKeyName("test-key*", false), listener2);
verifyGetResponse(noOfApiKeys, createApiKeyResponses1, listener2.get(),
createApiKeyResponses1.stream().map(CreateApiKeyResponse::getId).collect(Collectors.toSet()), null);
PlainActionFuture<GetApiKeyResponse> listener3 = new PlainActionFuture<>();
securityClient.getApiKey(GetApiKeyRequest.usingApiKeyName("*", false), listener3);
responses = Stream.concat(createApiKeyResponses1.stream(), createApiKeyResponses2.stream()).collect(Collectors.toList());
verifyGetResponse(2 * noOfApiKeys, responses, listener3.get(),
responses.stream().map(CreateApiKeyResponse::getId).collect(Collectors.toSet()), null);
PlainActionFuture<GetApiKeyResponse> listener4 = new PlainActionFuture<>();
securityClient.getApiKey(GetApiKeyRequest.usingApiKeyName("does-not-exist*", false), listener4);
verifyGetResponse(0, Collections.emptyList(), listener4.get(), Collections.emptySet(), null);
PlainActionFuture<GetApiKeyResponse> listener5 = new PlainActionFuture<>();
securityClient.getApiKey(GetApiKeyRequest.usingApiKeyName("another-test-key*", false), listener5);
verifyGetResponse(noOfApiKeys, createApiKeyResponses2, listener5.get(),
createApiKeyResponses2.stream().map(CreateApiKeyResponse::getId).collect(Collectors.toSet()), null);
} }
public void testGetApiKeysOwnedByCurrentAuthenticatedUser() throws InterruptedException, ExecutionException { public void testGetApiKeysOwnedByCurrentAuthenticatedUser() throws InterruptedException, ExecutionException {
@ -994,13 +1023,18 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
private List<CreateApiKeyResponse> createApiKeys(Map<String, String> headers, private List<CreateApiKeyResponse> createApiKeys(Map<String, String> headers,
int noOfApiKeys, TimeValue expiration, String... clusterPrivileges) { int noOfApiKeys, TimeValue expiration, String... clusterPrivileges) {
return createApiKeys(headers, noOfApiKeys, "test-key-", expiration, clusterPrivileges);
}
private List<CreateApiKeyResponse> createApiKeys(Map<String, String> headers,
int noOfApiKeys, String namePrefix, TimeValue expiration, String... clusterPrivileges) {
List<CreateApiKeyResponse> responses = new ArrayList<>(); List<CreateApiKeyResponse> responses = new ArrayList<>();
for (int i = 0; i < noOfApiKeys; i++) { for (int i = 0; i < noOfApiKeys; i++) {
final RoleDescriptor descriptor = new RoleDescriptor("role", clusterPrivileges, null, null); final RoleDescriptor descriptor = new RoleDescriptor("role", clusterPrivileges, null, null);
Client client = client().filterWithHeader(headers); Client client = client().filterWithHeader(headers);
SecurityClient securityClient = new SecurityClient(client); SecurityClient securityClient = new SecurityClient(client);
final CreateApiKeyResponse response = securityClient.prepareCreateApiKey() final CreateApiKeyResponse response = securityClient.prepareCreateApiKey()
.setName("test-key-" + randomAlphaOfLengthBetween(5, 9) + i).setExpiration(expiration) .setName(namePrefix + randomAlphaOfLengthBetween(5, 9) + i).setExpiration(expiration)
.setRoleDescriptors(Collections.singletonList(descriptor)).get(); .setRoleDescriptors(Collections.singletonList(descriptor)).get();
assertNotNull(response.getId()); assertNotNull(response.getId());
assertNotNull(response.getKey()); assertNotNull(response.getKey());