Skip to content

Commit 3b87615

Browse files
authored
Make role descriptors optional when creating API keys (#43481)
This commit changes the `role_descriptors` field from required to optional when creating API key. The default behavior in .NET ES client is to omit properties with `null` value requiring additional workarounds. The behavior for the API does not change. Field names (`id`, `name`) in the invalidate api keys API documentation have been corrected where they were wrong. Closes #42053
1 parent 5fa36da commit 3b87615

File tree

6 files changed

+48
-23
lines changed

6 files changed

+48
-23
lines changed

x-pack/docs/en/rest-api/security/create-api-keys.asciidoc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ The following parameters can be specified in the body of a POST or PUT request:
3737
`name`::
3838
(string) Specifies the name for this API key.
3939

40-
`role_descriptors` (required)::
40+
`role_descriptors` (optional)::
4141
(array-of-role-descriptor) An array of role descriptors for this API key. This
42-
parameter is required but can be an empty array, which applies the permissions
43-
of the authenticated user. If you supply role descriptors, they must be a subset
44-
of the authenticated user's permissions. The structure of role descriptor is the
42+
parameter is optional. When it is not specified or is an empty array, then the API key will have
43+
the permissions of the authenticated user. If you supply role descriptors, they must
44+
be a subset of the authenticated user's permissions. The structure of role descriptor is the
4545
same as the request for create role API. For more details, see
4646
<<security-api-roles,role management APIs>>.
4747

x-pack/docs/en/rest-api/security/invalidate-api-keys.asciidoc

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ pertain to invalidating api keys:
3131

3232
`realm_name` (optional)::
3333
(string) The name of an authentication realm. This parameter cannot be used with
34-
either `api_key_id` or `api_key_name`.
34+
either `id` or `name`.
3535

3636
`username` (optional)::
3737
(string) The username of a user. This parameter cannot be used with either
38-
`api_key_id` or `api_key_name`.
38+
`id` or `name`.
3939

4040
NOTE: While all parameters are optional, at least one of them is required.
4141

@@ -47,8 +47,7 @@ If you create an API key as follows:
4747
------------------------------------------------------------
4848
POST /_security/api_key
4949
{
50-
"name": "my-api-key",
51-
"role_descriptors": {}
50+
"name": "my-api-key"
5251
}
5352
------------------------------------------------------------
5453
// CONSOLE

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,21 +43,21 @@ public CreateApiKeyRequest() {}
4343
* @param roleDescriptors list of {@link RoleDescriptor}s
4444
* @param expiration to specify expiration for the API key
4545
*/
46-
public CreateApiKeyRequest(String name, List<RoleDescriptor> roleDescriptors, @Nullable TimeValue expiration) {
46+
public CreateApiKeyRequest(String name, @Nullable List<RoleDescriptor> roleDescriptors, @Nullable TimeValue expiration) {
4747
if (Strings.hasText(name)) {
4848
this.name = name;
4949
} else {
5050
throw new IllegalArgumentException("name must not be null or empty");
5151
}
52-
this.roleDescriptors = Objects.requireNonNull(roleDescriptors, "role descriptors may not be null");
52+
this.roleDescriptors = (roleDescriptors == null) ? List.of() : List.copyOf(roleDescriptors);
5353
this.expiration = expiration;
5454
}
5555

5656
public CreateApiKeyRequest(StreamInput in) throws IOException {
5757
super(in);
5858
this.name = in.readString();
5959
this.expiration = in.readOptionalTimeValue();
60-
this.roleDescriptors = Collections.unmodifiableList(in.readList(RoleDescriptor::new));
60+
this.roleDescriptors = List.copyOf(in.readList(RoleDescriptor::new));
6161
this.refreshPolicy = WriteRequest.RefreshPolicy.readFrom(in);
6262
}
6363

@@ -77,16 +77,16 @@ public TimeValue getExpiration() {
7777
return expiration;
7878
}
7979

80-
public void setExpiration(TimeValue expiration) {
80+
public void setExpiration(@Nullable TimeValue expiration) {
8181
this.expiration = expiration;
8282
}
8383

8484
public List<RoleDescriptor> getRoleDescriptors() {
8585
return roleDescriptors;
8686
}
8787

88-
public void setRoleDescriptors(List<RoleDescriptor> roleDescriptors) {
89-
this.roleDescriptors = Collections.unmodifiableList(Objects.requireNonNull(roleDescriptors, "role descriptors may not be null"));
88+
public void setRoleDescriptors(@Nullable List<RoleDescriptor> roleDescriptors) {
89+
this.roleDescriptors = (roleDescriptors == null) ? List.of() : List.copyOf(roleDescriptors);
9090
}
9191

9292
public WriteRequest.RefreshPolicy getRefreshPolicy() {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public final class CreateApiKeyRequestBuilder extends ActionRequestBuilder<Creat
3939

4040
static {
4141
PARSER.declareString(constructorArg(), new ParseField("name"));
42-
PARSER.declareNamedObjects(constructorArg(), (p, c, n) -> {
42+
PARSER.declareNamedObjects(optionalConstructorArg(), (p, c, n) -> {
4343
p.nextToken();
4444
return RoleDescriptor.parse(n, p, false);
4545
}, new ParseField("role_descriptors"));

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,22 @@ public void testParserAndCreateApiRequestBuilder() throws IOException {
5959
assertThat(request.getExpiration(), equalTo(TimeValue.parseTimeValue("1d", "expiration")));
6060
}
6161
}
62+
63+
public void testParserAndCreateApiRequestBuilderWithNullOrEmptyRoleDescriptors() throws IOException {
64+
boolean withExpiration = randomBoolean();
65+
boolean noRoleDescriptorsField = randomBoolean();
66+
final String json = "{ \"name\" : \"my-api-key\""
67+
+ ((withExpiration) ? ", \"expiration\": \"1d\"" : "")
68+
+ ((noRoleDescriptorsField) ? "" : ", \"role_descriptors\": {}")
69+
+ "}";
70+
final BytesArray source = new BytesArray(json);
71+
final NodeClient mockClient = mock(NodeClient.class);
72+
final CreateApiKeyRequest request = new CreateApiKeyRequestBuilder(mockClient).source(source, XContentType.JSON).request();
73+
final List<RoleDescriptor> actualRoleDescriptors = request.getRoleDescriptors();
74+
assertThat(request.getName(), equalTo("my-api-key"));
75+
assertThat(actualRoleDescriptors.size(), is(0));
76+
if (withExpiration) {
77+
assertThat(request.getExpiration(), equalTo(TimeValue.parseTimeValue("1d", "expiration")));
78+
}
79+
}
6280
}

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

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,16 @@ public void testSerialization() throws IOException {
8282
final TimeValue expiration = randomBoolean() ? null :
8383
TimeValue.parseTimeValue(randomTimeValue(), "test serialization of create api key");
8484
final WriteRequest.RefreshPolicy refreshPolicy = randomFrom(WriteRequest.RefreshPolicy.values());
85-
final int numDescriptors = randomIntBetween(0, 4);
86-
final List<RoleDescriptor> descriptorList = new ArrayList<>();
87-
for (int i = 0; i < numDescriptors; i++) {
88-
descriptorList.add(new RoleDescriptor("role_" + i, new String[] { "all" }, null, null));
85+
boolean nullOrEmptyRoleDescriptors = randomBoolean();
86+
final List<RoleDescriptor> descriptorList;
87+
if (nullOrEmptyRoleDescriptors) {
88+
descriptorList = randomBoolean() ? null : List.of();
89+
} else {
90+
final int numDescriptors = randomIntBetween(1, 4);
91+
descriptorList = new ArrayList<>();
92+
for (int i = 0; i < numDescriptors; i++) {
93+
descriptorList.add(new RoleDescriptor("role_" + i, new String[] { "all" }, null, null));
94+
}
8995
}
9096

9197
final CreateApiKeyRequest request = new CreateApiKeyRequest();
@@ -95,9 +101,7 @@ public void testSerialization() throws IOException {
95101
if (refreshPolicy != request.getRefreshPolicy() || randomBoolean()) {
96102
request.setRefreshPolicy(refreshPolicy);
97103
}
98-
if (descriptorList.isEmpty() == false || randomBoolean()) {
99-
request.setRoleDescriptors(descriptorList);
100-
}
104+
request.setRoleDescriptors(descriptorList);
101105

102106
try (BytesStreamOutput out = new BytesStreamOutput()) {
103107
request.writeTo(out);
@@ -106,7 +110,11 @@ public void testSerialization() throws IOException {
106110
assertEquals(name, serialized.getName());
107111
assertEquals(expiration, serialized.getExpiration());
108112
assertEquals(refreshPolicy, serialized.getRefreshPolicy());
109-
assertEquals(descriptorList, serialized.getRoleDescriptors());
113+
if (nullOrEmptyRoleDescriptors) {
114+
assertThat(serialized.getRoleDescriptors().isEmpty(), is(true));
115+
} else {
116+
assertEquals(descriptorList, serialized.getRoleDescriptors());
117+
}
110118
}
111119
}
112120
}

0 commit comments

Comments
 (0)