Skip to content

Commit 00c9533

Browse files
authored
Deprecate the id field for the InvalidateApiKey API (#66317)
This PR deprecates the usage of the id field in the payload for the InvalidateApiKey API. The ids field introduced in #63224 is now the recommended way for performing (bulk) API key invalidation.
1 parent 15f5758 commit 00c9533

File tree

12 files changed

+202
-115
lines changed

12 files changed

+202
-115
lines changed

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

Lines changed: 72 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
import org.elasticsearch.common.xcontent.XContentBuilder;
2727

2828
import java.io.IOException;
29+
import java.util.Arrays;
30+
import java.util.List;
31+
import java.util.stream.IntStream;
2932

3033
/**
3134
* Request for invalidating API key(s) so that it can no longer be used
@@ -34,38 +37,62 @@ public final class InvalidateApiKeyRequest implements Validatable, ToXContentObj
3437

3538
private final String realmName;
3639
private final String userName;
37-
private final String id;
40+
private final List<String> ids;
3841
private final String name;
3942
private final boolean ownedByAuthenticatedUser;
4043

4144
// pkg scope for testing
45+
@Deprecated
4246
InvalidateApiKeyRequest(@Nullable String realmName, @Nullable String userName, @Nullable String apiKeyId,
4347
@Nullable String apiKeyName, boolean ownedByAuthenticatedUser) {
44-
if (Strings.hasText(realmName) == false && Strings.hasText(userName) == false && Strings.hasText(apiKeyId) == false
45-
&& Strings.hasText(apiKeyName) == false && ownedByAuthenticatedUser == false) {
46-
throwValidationError("One of [api key id, api key name, username, realm name] must be specified if [owner] flag is false");
48+
this(realmName, userName, apiKeyName, ownedByAuthenticatedUser, apiKeyIdToIds(apiKeyId));
49+
}
50+
51+
InvalidateApiKeyRequest(@Nullable String realmName, @Nullable String userName,
52+
@Nullable String apiKeyName, boolean ownedByAuthenticatedUser, @Nullable List<String> apiKeyIds) {
53+
validateApiKeyIds(apiKeyIds);
54+
if (Strings.hasText(realmName) == false && Strings.hasText(userName) == false && apiKeyIds == null
55+
&& Strings.hasText(apiKeyName) == false && ownedByAuthenticatedUser == false) {
56+
throwValidationError("One of [api key id(s), api key name, username, realm name] must be specified if [owner] flag is false");
4757
}
48-
if (Strings.hasText(apiKeyId) || Strings.hasText(apiKeyName)) {
58+
if (apiKeyIds != null || Strings.hasText(apiKeyName)) {
4959
if (Strings.hasText(realmName) || Strings.hasText(userName)) {
5060
throwValidationError(
51-
"username or realm name must not be specified when the api key id or api key name is specified");
61+
"username or realm name must not be specified when the api key id(s) or api key name is specified");
5262
}
5363
}
5464
if (ownedByAuthenticatedUser) {
5565
if (Strings.hasText(realmName) || Strings.hasText(userName)) {
5666
throwValidationError("neither username nor realm-name may be specified when invalidating owned API keys");
5767
}
5868
}
59-
if (Strings.hasText(apiKeyId) && Strings.hasText(apiKeyName)) {
60-
throwValidationError("only one of [api key id, api key name] can be specified");
69+
if (apiKeyIds != null && Strings.hasText(apiKeyName)) {
70+
throwValidationError("only one of [api key id(s), api key name] can be specified");
6171
}
6272
this.realmName = realmName;
6373
this.userName = userName;
64-
this.id = apiKeyId;
74+
this.ids = apiKeyIds == null ? null : List.copyOf(apiKeyIds);
6575
this.name = apiKeyName;
6676
this.ownedByAuthenticatedUser = ownedByAuthenticatedUser;
6777
}
6878

79+
private void validateApiKeyIds(@Nullable List<String> apiKeyIds) {
80+
if (apiKeyIds != null) {
81+
if (apiKeyIds.size() == 0) {
82+
throwValidationError("Argument [apiKeyIds] cannot be an empty array");
83+
} else {
84+
final int[] idxOfBlankIds = IntStream.range(0, apiKeyIds.size())
85+
.filter(i -> Strings.hasText(apiKeyIds.get(i)) == false).toArray();
86+
if (idxOfBlankIds.length > 0) {
87+
throwValidationError("Argument [apiKeyIds] must not contain blank id, but got blank "
88+
+ (idxOfBlankIds.length == 1 ? "id" : "ids") + " at index "
89+
+ (idxOfBlankIds.length == 1 ? "position" : "positions") + ": "
90+
+ Arrays.toString(idxOfBlankIds));
91+
}
92+
}
93+
}
94+
}
95+
6996
private void throwValidationError(String message) {
7097
throw new IllegalArgumentException(message);
7198
}
@@ -78,8 +105,20 @@ public String getUserName() {
78105
return userName;
79106
}
80107

108+
@Deprecated
81109
public String getId() {
82-
return id;
110+
if (ids == null) {
111+
return null;
112+
} else if (ids.size() == 1) {
113+
return ids.get(0);
114+
} else {
115+
throw new IllegalArgumentException("Cannot get a single api key id when multiple ids have been set ["
116+
+ Strings.collectionToCommaDelimitedString(ids) + "]");
117+
}
118+
}
119+
120+
public List<String> getIds() {
121+
return ids;
83122
}
84123

85124
public String getName() {
@@ -96,7 +135,7 @@ public boolean ownedByAuthenticatedUser() {
96135
* @return {@link InvalidateApiKeyRequest}
97136
*/
98137
public static InvalidateApiKeyRequest usingRealmName(String realmName) {
99-
return new InvalidateApiKeyRequest(realmName, null, null, null, false);
138+
return new InvalidateApiKeyRequest(realmName, null, null, false, null);
100139
}
101140

102141
/**
@@ -105,7 +144,7 @@ public static InvalidateApiKeyRequest usingRealmName(String realmName) {
105144
* @return {@link InvalidateApiKeyRequest}
106145
*/
107146
public static InvalidateApiKeyRequest usingUserName(String userName) {
108-
return new InvalidateApiKeyRequest(null, userName, null, null, false);
147+
return new InvalidateApiKeyRequest(null, userName, null, false, null);
109148
}
110149

111150
/**
@@ -115,7 +154,7 @@ public static InvalidateApiKeyRequest usingUserName(String userName) {
115154
* @return {@link InvalidateApiKeyRequest}
116155
*/
117156
public static InvalidateApiKeyRequest usingRealmAndUserName(String realmName, String userName) {
118-
return new InvalidateApiKeyRequest(realmName, userName, null, null, false);
157+
return new InvalidateApiKeyRequest(realmName, userName, null, false, null);
119158
}
120159

121160
/**
@@ -126,7 +165,18 @@ public static InvalidateApiKeyRequest usingRealmAndUserName(String realmName, St
126165
* @return {@link InvalidateApiKeyRequest}
127166
*/
128167
public static InvalidateApiKeyRequest usingApiKeyId(String apiKeyId, boolean ownedByAuthenticatedUser) {
129-
return new InvalidateApiKeyRequest(null, null, apiKeyId, null, ownedByAuthenticatedUser);
168+
return new InvalidateApiKeyRequest(null, null, null, ownedByAuthenticatedUser, apiKeyIdToIds(apiKeyId));
169+
}
170+
171+
/**
172+
* Creates invalidate API key request for given api key ids
173+
* @param apiKeyIds api key ids
174+
* @param ownedByAuthenticatedUser set {@code true} if the request is only for the API keys owned by current authenticated user else
175+
* {@code false}
176+
* @return {@link InvalidateApiKeyRequest}
177+
*/
178+
public static InvalidateApiKeyRequest usingApiKeyIds(List<String> apiKeyIds, boolean ownedByAuthenticatedUser) {
179+
return new InvalidateApiKeyRequest(null, null, null, ownedByAuthenticatedUser, apiKeyIds);
130180
}
131181

132182
/**
@@ -137,14 +187,14 @@ public static InvalidateApiKeyRequest usingApiKeyId(String apiKeyId, boolean own
137187
* @return {@link InvalidateApiKeyRequest}
138188
*/
139189
public static InvalidateApiKeyRequest usingApiKeyName(String apiKeyName, boolean ownedByAuthenticatedUser) {
140-
return new InvalidateApiKeyRequest(null, null, null, apiKeyName, ownedByAuthenticatedUser);
190+
return new InvalidateApiKeyRequest(null, null, apiKeyName, ownedByAuthenticatedUser, null);
141191
}
142192

143193
/**
144194
* Creates invalidate api key request to invalidate api keys owned by the current authenticated user.
145195
*/
146196
public static InvalidateApiKeyRequest forOwnedApiKeys() {
147-
return new InvalidateApiKeyRequest(null, null, null, null, true);
197+
return new InvalidateApiKeyRequest(null, null, null, true, null);
148198
}
149199

150200
@Override
@@ -156,13 +206,17 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
156206
if (userName != null) {
157207
builder.field("username", userName);
158208
}
159-
if (id != null) {
160-
builder.field("id", id);
209+
if (ids != null) {
210+
builder.field("ids", ids);
161211
}
162212
if (name != null) {
163213
builder.field("name", name);
164214
}
165215
builder.field("owner", ownedByAuthenticatedUser);
166216
return builder.endObject();
167217
}
218+
219+
static List<String> apiKeyIdToIds(@Nullable String apiKeyId) {
220+
return Strings.hasText(apiKeyId) ? List.of(apiKeyId) : null;
221+
}
168222
}

client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2204,6 +2204,24 @@ public void testInvalidateApiKey() throws Exception {
22042204
assertThat(previouslyInvalidatedApiKeyIds.size(), equalTo(0));
22052205
}
22062206

2207+
{
2208+
// tag::invalidate-api-key-ids-request
2209+
InvalidateApiKeyRequest invalidateApiKeyRequest = InvalidateApiKeyRequest.usingApiKeyIds(
2210+
Arrays.asList("kI3QZHYBnpSXoDRq1XzR", "ko3SZHYBnpSXoDRqk3zm"), false);
2211+
// end::invalidate-api-key-ids-request
2212+
2213+
InvalidateApiKeyResponse invalidateApiKeyResponse = client.security().invalidateApiKey(invalidateApiKeyRequest,
2214+
RequestOptions.DEFAULT);
2215+
2216+
final List<ElasticsearchException> errors = invalidateApiKeyResponse.getErrors();
2217+
final List<String> invalidatedApiKeyIds = invalidateApiKeyResponse.getInvalidatedApiKeys();
2218+
final List<String> previouslyInvalidatedApiKeyIds = invalidateApiKeyResponse.getPreviouslyInvalidatedApiKeys();
2219+
2220+
assertTrue(errors.isEmpty());
2221+
assertThat(invalidatedApiKeyIds.size(), equalTo(0));
2222+
assertThat(previouslyInvalidatedApiKeyIds.size(), equalTo(0));
2223+
}
2224+
22072225
{
22082226
createApiKeyRequest = new CreateApiKeyRequest("k2", roles, expiration, refreshPolicy);
22092227
CreateApiKeyResponse createApiKeyResponse2 = client.security().createApiKey(createApiKeyRequest, RequestOptions.DEFAULT);

client/rest-high-level/src/test/java/org/elasticsearch/client/security/InvalidateApiKeyRequestTests.java

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,24 @@
2424

2525
import java.io.IOException;
2626
import java.util.Optional;
27+
import java.util.stream.Collectors;
28+
import java.util.stream.IntStream;
2729

30+
import static org.elasticsearch.client.security.InvalidateApiKeyRequest.apiKeyIdToIds;
2831
import static org.hamcrest.Matchers.equalTo;
2932
import static org.hamcrest.Matchers.is;
3033

3134
public class InvalidateApiKeyRequestTests extends ESTestCase {
3235

3336
public void testRequestValidation() {
34-
InvalidateApiKeyRequest request = InvalidateApiKeyRequest.usingApiKeyId(randomAlphaOfLength(5), randomBoolean());
37+
InvalidateApiKeyRequest request;
38+
if (randomBoolean()) {
39+
request = InvalidateApiKeyRequest.usingApiKeyId(randomAlphaOfLength(5), randomBoolean());
40+
} else {
41+
request = InvalidateApiKeyRequest.usingApiKeyIds(
42+
IntStream.range(1, randomIntBetween(1, 5)).mapToObj(ignored -> randomAlphaOfLength(5)).collect(Collectors.toList()),
43+
randomBoolean());
44+
}
3545
Optional<ValidationException> ve = request.validate();
3646
assertThat(ve.isPresent(), is(false));
3747
request = InvalidateApiKeyRequest.usingApiKeyName(randomAlphaOfLength(5), randomBoolean());
@@ -61,19 +71,19 @@ public void testRequestValidationFailureScenarios() throws IOException {
6171
{ "realm", randomNullOrEmptyString(), randomNullOrEmptyString(), randomNullOrEmptyString(), "true" },
6272
{ randomNullOrEmptyString(), "user", randomNullOrEmptyString(), randomNullOrEmptyString(), "true" } };
6373
String[] expectedErrorMessages = new String[] {
64-
"One of [api key id, api key name, username, realm name] must be specified if [owner] flag is false",
65-
"username or realm name must not be specified when the api key id or api key name is specified",
66-
"username or realm name must not be specified when the api key id or api key name is specified",
67-
"username or realm name must not be specified when the api key id or api key name is specified",
68-
"only one of [api key id, api key name] can be specified",
74+
"One of [api key id(s), api key name, username, realm name] must be specified if [owner] flag is false",
75+
"username or realm name must not be specified when the api key id(s) or api key name is specified",
76+
"username or realm name must not be specified when the api key id(s) or api key name is specified",
77+
"username or realm name must not be specified when the api key id(s) or api key name is specified",
78+
"only one of [api key id(s), api key name] can be specified",
6979
"neither username nor realm-name may be specified when invalidating owned API keys",
7080
"neither username nor realm-name may be specified when invalidating owned API keys" };
7181

7282
for (int i = 0; i < inputs.length; i++) {
7383
final int caseNo = i;
7484
IllegalArgumentException ve = expectThrows(IllegalArgumentException.class,
75-
() -> new InvalidateApiKeyRequest(inputs[caseNo][0], inputs[caseNo][1], inputs[caseNo][2], inputs[caseNo][3],
76-
Boolean.valueOf(inputs[caseNo][4])));
85+
() -> new InvalidateApiKeyRequest(inputs[caseNo][0], inputs[caseNo][1], inputs[caseNo][3],
86+
Boolean.valueOf(inputs[caseNo][4]), apiKeyIdToIds(inputs[caseNo][2])));
7787
assertNotNull(ve);
7888
assertThat(ve.getMessage(), equalTo(expectedErrorMessages[caseNo]));
7989
}

docs/java-rest/high-level/security/invalidate-api-key.asciidoc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ The +{request}+ supports invalidating
2323

2424
. A specific key or all API keys owned by the current authenticated user
2525

26-
===== Specific API key by API key id
26+
===== Specific API keys by a list of API key ids
2727
["source","java",subs="attributes,callouts,macros"]
2828
--------------------------------------------------
29-
include-tagged::{doc-tests-file}[invalidate-api-key-id-request]
29+
include-tagged::{doc-tests-file}[invalidate-api-key-ids-request]
3030
--------------------------------------------------
3131

3232
===== Specific API key by API key name
@@ -80,4 +80,4 @@ invalidated.
8080
["source","java",subs="attributes,callouts,macros"]
8181
--------------------------------------------------
8282
include-tagged::{doc-tests-file}[{api}-response]
83-
--------------------------------------------------
83+
--------------------------------------------------

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

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ The following parameters can be specified in the body of a DELETE request and
3131
pertain to invalidating api keys:
3232

3333
`id`::
34+
deprecated:[7.12.0, "Use ids instead"]
3435
(Optional, string) An API key id. This parameter cannot be used when any of
3536
`ids`, `name`, `realm_name` or `username` are used.
3637

@@ -56,7 +57,7 @@ by the currently authenticated user. Defaults to false.
5657
The 'realm_name' or 'username' parameters cannot be specified when this
5758
parameter is set to 'true' as they are assumed to be the currently authenticated ones.
5859

59-
NOTE: At least one of "id", "name", "username" and "realm_name" must be specified
60+
NOTE: At least one of "id", "ids", "name", "username" and "realm_name" must be specified
6061
if "owner" is "false" (default).
6162

6263
[[security-api-invalidate-api-key-response-body]]
@@ -94,30 +95,19 @@ API key information. For example:
9495
// TESTRESPONSE[s/VuaCfGcBCdbkQm-e5aOx/$body.id/]
9596
// TESTRESPONSE[s/ui2lp2axTNmsyakw9tvNnw/$body.api_key/]
9697

97-
The following example invalidates the API key identified by specified `id`
98+
The following example invalidates the API key identified by specified `ids`
9899
immediately:
99100

100101
[source,console]
101102
--------------------------------------------------
102103
DELETE /_security/api_key
103104
{
104-
"id" : "VuaCfGcBCdbkQm-e5aOx"
105+
"ids" : [ "VuaCfGcBCdbkQm-e5aOx" ]
105106
}
106107
--------------------------------------------------
107108
// TEST[s/VuaCfGcBCdbkQm-e5aOx/$body.id/]
108109
// TEST[continued]
109110

110-
The above request can also be performed using the `ids` field which takes
111-
a list of API keys for bulk invalidation:
112-
113-
[source,console]
114-
--------------------------------------------------
115-
DELETE /_security/api_key
116-
{
117-
"ids" : [ "VuaCfGcBCdbkQm-e5aOx" ]
118-
}
119-
--------------------------------------------------
120-
121111
The following example invalidates the API key identified by specified `name`
122112
immediately:
123113

@@ -151,14 +141,14 @@ DELETE /_security/api_key
151141
}
152142
--------------------------------------------------
153143

154-
The following example invalidates the API key identified by the specified `id` if
144+
The following example invalidates the API key identified by the specified `ids` if
155145
it is owned by the currently authenticated user immediately:
156146

157147
[source,console]
158148
--------------------------------------------------
159149
DELETE /_security/api_key
160150
{
161-
"id" : "VuaCfGcBCdbkQm-e5aOx",
151+
"ids" : ["VuaCfGcBCdbkQm-e5aOx"],
162152
"owner" : "true"
163153
}
164154
--------------------------------------------------

0 commit comments

Comments
 (0)