Skip to content

Commit 6ae04a5

Browse files
authored
API key name should always be required for creation (#59836)
The name is now required when creating or granting API keys.
1 parent c9f2124 commit 6ae04a5

File tree

5 files changed

+47
-13
lines changed

5 files changed

+47
-13
lines changed

client/rest-high-level/src/main/java/org/elasticsearch/client/security/CreateApiKeyRequest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public final class CreateApiKeyRequest implements Validatable, ToXContentObject
4646
* @param roles list of {@link Role}s
4747
* @param expiration to specify expiration for the API key
4848
*/
49-
public CreateApiKeyRequest(@Nullable String name, List<Role> roles, @Nullable TimeValue expiration,
49+
public CreateApiKeyRequest(String name, List<Role> roles, @Nullable TimeValue expiration,
5050
@Nullable final RefreshPolicy refreshPolicy) {
5151
this.name = name;
5252
this.roles = Objects.requireNonNull(roles, "roles may not be null");

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/CreateApiKeyRequest.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.elasticsearch.action.ActionRequestValidationException;
1212
import org.elasticsearch.action.support.WriteRequest;
1313
import org.elasticsearch.common.Nullable;
14+
import org.elasticsearch.common.Strings;
1415
import org.elasticsearch.common.io.stream.StreamInput;
1516
import org.elasticsearch.common.io.stream.StreamOutput;
1617
import org.elasticsearch.common.unit.TimeValue;
@@ -43,7 +44,7 @@ public CreateApiKeyRequest() {}
4344
* @param roleDescriptors list of {@link RoleDescriptor}s
4445
* @param expiration to specify expiration for the API key
4546
*/
46-
public CreateApiKeyRequest(@Nullable String name, @Nullable List<RoleDescriptor> roleDescriptors, @Nullable TimeValue expiration) {
47+
public CreateApiKeyRequest(String name, @Nullable List<RoleDescriptor> roleDescriptors, @Nullable TimeValue expiration) {
4748
this.name = name;
4849
this.roleDescriptors = (roleDescriptors == null) ? List.of() : List.copyOf(roleDescriptors);
4950
this.expiration = expiration;
@@ -65,7 +66,7 @@ public String getName() {
6566
return name;
6667
}
6768

68-
public void setName(@Nullable String name) {
69+
public void setName(String name) {
6970
this.name = name;
7071
}
7172

@@ -96,15 +97,17 @@ public void setRefreshPolicy(WriteRequest.RefreshPolicy refreshPolicy) {
9697
@Override
9798
public ActionRequestValidationException validate() {
9899
ActionRequestValidationException validationException = null;
99-
if (name != null) {
100+
if (Strings.isNullOrEmpty(name)) {
101+
validationException = addValidationError("api key name is required", validationException);
102+
} else {
100103
if (name.length() > 256) {
101-
validationException = addValidationError("name may not be more than 256 characters long", validationException);
104+
validationException = addValidationError("api key name may not be more than 256 characters long", validationException);
102105
}
103106
if (name.equals(name.trim()) == false) {
104-
validationException = addValidationError("name may not begin or end with whitespace", validationException);
107+
validationException = addValidationError("api key name may not begin or end with whitespace", validationException);
105108
}
106109
if (name.startsWith("_")) {
107-
validationException = addValidationError("name may not begin with an underscore", validationException);
110+
validationException = addValidationError("api key name may not begin with an underscore", validationException);
108111
}
109112
}
110113
return validationException;

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/CreateApiKeyRequestTests.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ public void testNameValidation() {
2828
CreateApiKeyRequest request = new CreateApiKeyRequest();
2929

3030
ActionRequestValidationException ve = request.validate();
31-
assertNull(ve);
31+
assertThat(ve.validationErrors().size(), is(1));
32+
assertThat(ve.validationErrors().get(0), containsString("api key name is required"));
3233

3334
request.setName(name);
3435
ve = request.validate();
@@ -38,25 +39,25 @@ public void testNameValidation() {
3839
ve = request.validate();
3940
assertNotNull(ve);
4041
assertThat(ve.validationErrors().size(), is(1));
41-
assertThat(ve.validationErrors().get(0), containsString("name may not be more than 256 characters long"));
42+
assertThat(ve.validationErrors().get(0), containsString("api key name may not be more than 256 characters long"));
4243

4344
request.setName(" leading space");
4445
ve = request.validate();
4546
assertNotNull(ve);
4647
assertThat(ve.validationErrors().size(), is(1));
47-
assertThat(ve.validationErrors().get(0), containsString("name may not begin or end with whitespace"));
48+
assertThat(ve.validationErrors().get(0), containsString("api key name may not begin or end with whitespace"));
4849

4950
request.setName("trailing space ");
5051
ve = request.validate();
5152
assertNotNull(ve);
5253
assertThat(ve.validationErrors().size(), is(1));
53-
assertThat(ve.validationErrors().get(0), containsString("name may not begin or end with whitespace"));
54+
assertThat(ve.validationErrors().get(0), containsString("api key name may not begin or end with whitespace"));
5455

5556
request.setName(" leading and trailing space ");
5657
ve = request.validate();
5758
assertNotNull(ve);
5859
assertThat(ve.validationErrors().size(), is(1));
59-
assertThat(ve.validationErrors().get(0), containsString("name may not begin or end with whitespace"));
60+
assertThat(ve.validationErrors().get(0), containsString("api key name may not begin or end with whitespace"));
6061

6162
request.setName("inner space");
6263
ve = request.validate();
@@ -66,7 +67,7 @@ public void testNameValidation() {
6667
ve = request.validate();
6768
assertNotNull(ve);
6869
assertThat(ve.validationErrors().size(), is(1));
69-
assertThat(ve.validationErrors().get(0), containsString("name may not begin with an underscore"));
70+
assertThat(ve.validationErrors().get(0), containsString("api key name may not begin with an underscore"));
7071
}
7172

7273
public void testSerialization() throws IOException {

x-pack/plugin/security/qa/security-trial/src/test/java/org/elasticsearch/xpack/security/apikey/ApiKeyRestIT.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.elasticsearch.client.Request;
1010
import org.elasticsearch.client.RequestOptions;
1111
import org.elasticsearch.client.Response;
12+
import org.elasticsearch.client.ResponseException;
1213
import org.elasticsearch.client.security.support.ApiKey;
1314
import org.elasticsearch.common.collect.Tuple;
1415
import org.elasticsearch.common.settings.SecureString;
@@ -26,6 +27,7 @@
2627
import java.util.Map;
2728
import java.util.Set;
2829

30+
import static org.hamcrest.Matchers.containsString;
2931
import static org.hamcrest.Matchers.equalTo;
3032
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
3133
import static org.hamcrest.Matchers.instanceOf;
@@ -115,4 +117,22 @@ public void testGrantApiKeyForOtherUserWithAccessToken() throws IOException {
115117
assertThat(apiKey.getExpiration(), greaterThanOrEqualTo(minExpiry));
116118
assertThat(apiKey.getExpiration(), lessThanOrEqualTo(maxExpiry));
117119
}
120+
121+
public void testGrantApiKeyWithoutApiKeyNameWillFail() throws IOException {
122+
Request request = new Request("POST", "_security/api_key/grant");
123+
request.setOptions(RequestOptions.DEFAULT.toBuilder().addHeader("Authorization",
124+
UsernamePasswordToken.basicAuthHeaderValue(SYSTEM_USER, SYSTEM_USER_PASSWORD)));
125+
final Map<String, Object> requestBody = Map.ofEntries(
126+
Map.entry("grant_type", "password"),
127+
Map.entry("username", END_USER),
128+
Map.entry("password", END_USER_PASSWORD.toString())
129+
);
130+
request.setJsonEntity(XContentTestUtils.convertToXContent(requestBody, XContentType.JSON).utf8ToString());
131+
132+
final ResponseException e =
133+
expectThrows(ResponseException.class, () -> client().performRequest(request));
134+
135+
assertEquals(400, e.getResponse().getStatusLine().getStatusCode());
136+
assertThat(e.getMessage(), containsString("api key name is required"));
137+
}
118138
}

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyIntegTests.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
package org.elasticsearch.xpack.security.authc;
88

99
import org.elasticsearch.ElasticsearchSecurityException;
10+
import org.elasticsearch.action.ActionRequestValidationException;
1011
import org.elasticsearch.action.DocWriteResponse;
1112
import org.elasticsearch.action.admin.cluster.node.info.NodeInfo;
1213
import org.elasticsearch.action.admin.indices.refresh.RefreshAction;
@@ -221,6 +222,15 @@ public void testMultipleApiKeysCanHaveSameName() {
221222
}
222223
}
223224

225+
public void testCreateApiKeyWithoutNameWillFail() {
226+
Client client = client().filterWithHeader(Collections.singletonMap("Authorization",
227+
UsernamePasswordToken.basicAuthHeaderValue(SecuritySettingsSource.TEST_SUPERUSER,
228+
SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)));
229+
final ActionRequestValidationException e =
230+
expectThrows(ActionRequestValidationException.class, () -> new CreateApiKeyRequestBuilder(client).get());
231+
assertThat(e.getMessage(), containsString("api key name is required"));
232+
}
233+
224234
public void testInvalidateApiKeysForRealm() throws InterruptedException, ExecutionException {
225235
int noOfApiKeys = randomIntBetween(3, 5);
226236
List<CreateApiKeyResponse> responses = createApiKeys(noOfApiKeys, null);

0 commit comments

Comments
 (0)