From a114e0b351484a180d21f16293d9759062959838 Mon Sep 17 00:00:00 2001 From: Ioannis Kakavas Date: Tue, 18 Dec 2018 16:12:43 +0200 Subject: [PATCH] Invalidate Token API enhancements - HLRC (#36362) * Adds Invalidate Token API enhancements to HLRC Relates: #35388 --- .../security/InvalidateTokenRequest.java | 75 ++++++++++++--- .../security/InvalidateTokenResponse.java | 87 ++++++++++++++---- .../SecurityDocumentationIT.java | 91 +++++++++++++++++-- .../security/InvalidateTokenRequestTests.java | 59 +++++++++++- .../InvalidateTokenResponseTests.java | 53 ++++++++++- .../security/invalidate-token.asciidoc | 46 ++++++++-- 6 files changed, 355 insertions(+), 56 deletions(-) diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/InvalidateTokenRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/InvalidateTokenRequest.java index 4e767335ffdbd..92aaf1f1a10e6 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/InvalidateTokenRequest.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/InvalidateTokenRequest.java @@ -35,17 +35,36 @@ public final class InvalidateTokenRequest implements Validatable, ToXContentObje private final String accessToken; private final String refreshToken; + private final String realmName; + private final String username; InvalidateTokenRequest(@Nullable String accessToken, @Nullable String refreshToken) { - if (Strings.isNullOrEmpty(accessToken)) { - if (Strings.isNullOrEmpty(refreshToken)) { - throw new IllegalArgumentException("Either access-token or refresh-token is required"); + this(accessToken, refreshToken, null, null); + } + + public InvalidateTokenRequest(@Nullable String accessToken, @Nullable String refreshToken, + @Nullable String realmName, @Nullable String username) { + if (Strings.hasText(realmName) || Strings.hasText(username)) { + if (Strings.hasText(accessToken)) { + throw new IllegalArgumentException("access token is not allowed when realm name or username are specified"); + } + if (refreshToken != null) { + throw new IllegalArgumentException("refresh token is not allowed when realm name or username are specified"); + } + } else { + if (Strings.isNullOrEmpty(accessToken)) { + if (Strings.isNullOrEmpty(refreshToken)) { + throw new IllegalArgumentException("Either access token or refresh token is required when neither realm name or " + + "username are specified"); + } + } else if (Strings.isNullOrEmpty(refreshToken) == false) { + throw new IllegalArgumentException("Cannot supply both access token and refresh token"); } - } else if (Strings.isNullOrEmpty(refreshToken) == false) { - throw new IllegalArgumentException("Cannot supply both access-token and refresh-token"); } this.accessToken = accessToken; this.refreshToken = refreshToken; + this.realmName = realmName; + this.username = username; } public static InvalidateTokenRequest accessToken(String accessToken) { @@ -62,6 +81,20 @@ public static InvalidateTokenRequest refreshToken(String refreshToken) { return new InvalidateTokenRequest(null, refreshToken); } + public static InvalidateTokenRequest realmTokens(String realmName) { + if (Strings.isNullOrEmpty(realmName)) { + throw new IllegalArgumentException("realm name is required"); + } + return new InvalidateTokenRequest(null, null, realmName, null); + } + + public static InvalidateTokenRequest userTokens(String username) { + if (Strings.isNullOrEmpty(username)) { + throw new IllegalArgumentException("username is required"); + } + return new InvalidateTokenRequest(null, null, null, username); + } + public String getAccessToken() { return accessToken; } @@ -70,6 +103,14 @@ public String getRefreshToken() { return refreshToken; } + public String getRealmName() { + return realmName; + } + + public String getUsername() { + return username; + } + @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(); @@ -79,24 +120,28 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws if (refreshToken != null) { builder.field("refresh_token", refreshToken); } + if (realmName != null) { + builder.field("realm_name", realmName); + } + if (username != null) { + builder.field("username", username); + } return builder.endObject(); } @Override public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - final InvalidateTokenRequest that = (InvalidateTokenRequest) o; - return Objects.equals(this.accessToken, that.accessToken) && - Objects.equals(this.refreshToken, that.refreshToken); + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + InvalidateTokenRequest that = (InvalidateTokenRequest) o; + return Objects.equals(accessToken, that.accessToken) && + Objects.equals(refreshToken, that.refreshToken) && + Objects.equals(realmName, that.realmName) && + Objects.equals(username, that.username); } @Override public int hashCode() { - return Objects.hash(accessToken, refreshToken); + return Objects.hash(accessToken, refreshToken, realmName, username); } } diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/InvalidateTokenResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/InvalidateTokenResponse.java index 876faa485c236..8efc527d2142d 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/InvalidateTokenResponse.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/InvalidateTokenResponse.java @@ -19,56 +19,107 @@ package org.elasticsearch.client.security; +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.common.Nullable; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.xcontent.ConstructingObjectParser; import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentParserUtils; import java.io.IOException; +import java.util.Collections; +import java.util.List; import java.util.Objects; import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg; /** - * Response when invalidating an OAuth2 token. Returns a - * single boolean field for whether the invalidation record was created or updated. + * Response when invalidating one or multiple OAuth2 access tokens and refresh tokens. Returns + * information concerning how many tokens were invalidated, how many of the tokens that + * were attempted to be invalidated were already invalid, and if there were any errors + * encountered. */ public final class InvalidateTokenResponse { + public static final ParseField CREATED = new ParseField("created"); + public static final ParseField INVALIDATED_TOKENS = new ParseField("invalidated_tokens"); + public static final ParseField PREVIOUSLY_INVALIDATED_TOKENS = new ParseField("previously_invalidated_tokens"); + public static final ParseField ERROR_COUNT = new ParseField("error_count"); + public static final ParseField ERRORS = new ParseField("error_details"); + private final boolean created; + private final int invalidatedTokens; + private final int previouslyInvalidatedTokens; + private List errors; + + @SuppressWarnings("unchecked") + private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( + "tokens_invalidation_result", true, + // we parse but do not use the count of errors as we implicitly have this in the size of the Exceptions list + args -> new InvalidateTokenResponse((boolean) args[0], (int) args[1], (int) args[2], (List) args[4])); + + static { + PARSER.declareBoolean(constructorArg(), CREATED); + PARSER.declareInt(constructorArg(), INVALIDATED_TOKENS); + PARSER.declareInt(constructorArg(), PREVIOUSLY_INVALIDATED_TOKENS); + PARSER.declareInt(constructorArg(), ERROR_COUNT); + PARSER.declareObjectArray(optionalConstructorArg(), (p, c) -> ElasticsearchException.fromXContent(p), ERRORS); + } - public InvalidateTokenResponse(boolean created) { + public InvalidateTokenResponse(boolean created, int invalidatedTokens, int previouslyInvalidatedTokens, + @Nullable List errors) { this.created = created; + this.invalidatedTokens = invalidatedTokens; + this.previouslyInvalidatedTokens = previouslyInvalidatedTokens; + if (null == errors) { + this.errors = Collections.emptyList(); + } else { + this.errors = Collections.unmodifiableList(errors); + } } public boolean isCreated() { return created; } + public int getInvalidatedTokens() { + return invalidatedTokens; + } + + public int getPreviouslyInvalidatedTokens() { + return previouslyInvalidatedTokens; + } + + public List getErrors() { + return errors; + } + + public int getErrorsCount() { + return errors == null ? 0 : errors.size(); + } + @Override public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; InvalidateTokenResponse that = (InvalidateTokenResponse) o; - return created == that.created; + return created == that.created && + invalidatedTokens == that.invalidatedTokens && + previouslyInvalidatedTokens == that.previouslyInvalidatedTokens && + Objects.equals(errors, that.errors); } @Override public int hashCode() { - return Objects.hash(created); - } - - private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( - "invalidate_token_response", true, args -> new InvalidateTokenResponse((boolean) args[0])); - - static { - PARSER.declareBoolean(constructorArg(), new ParseField("created")); + return Objects.hash(created, invalidatedTokens, previouslyInvalidatedTokens, errors); } public static InvalidateTokenResponse fromXContent(XContentParser parser) throws IOException { + if (parser.currentToken() == null) { + parser.nextToken(); + } + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation); return PARSER.parse(parser, null); } } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java index 2604e9988d9ff..334ed864f5e84 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java @@ -19,6 +19,7 @@ package org.elasticsearch.client.documentation; +import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchStatusException; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.LatchedActionListener; @@ -1323,19 +1324,52 @@ public void testInvalidateToken() throws Exception { String accessToken; String refreshToken; { - // Setup user + // Setup users final char[] password = "password".toCharArray(); - User invalidate_token_user = new User("invalidate_token", Collections.singletonList("kibana_user")); - PutUserRequest putUserRequest = new PutUserRequest(invalidate_token_user, password, true, RefreshPolicy.IMMEDIATE); + User user = new User("user", Collections.singletonList("kibana_user")); + PutUserRequest putUserRequest = new PutUserRequest(user, password, true, RefreshPolicy.IMMEDIATE); PutUserResponse putUserResponse = client.security().putUser(putUserRequest, RequestOptions.DEFAULT); assertTrue(putUserResponse.isCreated()); + User this_user = new User("this_user", Collections.singletonList("kibana_user")); + PutUserRequest putThisUserRequest = new PutUserRequest(this_user, password, true, RefreshPolicy.IMMEDIATE); + PutUserResponse putThisUserResponse = client.security().putUser(putThisUserRequest, RequestOptions.DEFAULT); + assertTrue(putThisUserResponse.isCreated()); + + User that_user = new User("that_user", Collections.singletonList("kibana_user")); + PutUserRequest putThatUserRequest = new PutUserRequest(that_user, password, true, RefreshPolicy.IMMEDIATE); + PutUserResponse putThatUserResponse = client.security().putUser(putThatUserRequest, RequestOptions.DEFAULT); + assertTrue(putThatUserResponse.isCreated()); + + User other_user = new User("other_user", Collections.singletonList("kibana_user")); + PutUserRequest putOtherUserRequest = new PutUserRequest(other_user, password, true, RefreshPolicy.IMMEDIATE); + PutUserResponse putOtherUserResponse = client.security().putUser(putOtherUserRequest, RequestOptions.DEFAULT); + assertTrue(putOtherUserResponse.isCreated()); + + User extra_user = new User("extra_user", Collections.singletonList("kibana_user")); + PutUserRequest putExtraUserRequest = new PutUserRequest(extra_user, password, true, RefreshPolicy.IMMEDIATE); + PutUserResponse putExtraUserResponse = client.security().putUser(putExtraUserRequest, RequestOptions.DEFAULT); + assertTrue(putExtraUserResponse.isCreated()); + // Create tokens - final CreateTokenRequest createTokenRequest = CreateTokenRequest.passwordGrant("invalidate_token", password); + final CreateTokenRequest createTokenRequest = CreateTokenRequest.passwordGrant("user", password); final CreateTokenResponse tokenResponse = client.security().createToken(createTokenRequest, RequestOptions.DEFAULT); accessToken = tokenResponse.getAccessToken(); refreshToken = tokenResponse.getRefreshToken(); + final CreateTokenRequest createThisTokenRequest = CreateTokenRequest.passwordGrant("this_user", password); + final CreateTokenResponse thisTokenResponse = client.security().createToken(createThisTokenRequest, RequestOptions.DEFAULT); + assertNotNull(thisTokenResponse); + final CreateTokenRequest createThatTokenRequest = CreateTokenRequest.passwordGrant("that_user", password); + final CreateTokenResponse thatTokenResponse = client.security().createToken(createThatTokenRequest, RequestOptions.DEFAULT); + assertNotNull(thatTokenResponse); + final CreateTokenRequest createOtherTokenRequest = CreateTokenRequest.passwordGrant("other_user", password); + final CreateTokenResponse otherTokenResponse = client.security().createToken(createOtherTokenRequest, RequestOptions.DEFAULT); + assertNotNull(otherTokenResponse); + final CreateTokenRequest createExtraTokenRequest = CreateTokenRequest.passwordGrant("extra_user", password); + final CreateTokenResponse extraTokenResponse = client.security().createToken(createExtraTokenRequest, RequestOptions.DEFAULT); + assertNotNull(extraTokenResponse); } + { // tag::invalidate-access-token-request InvalidateTokenRequest invalidateTokenRequest = InvalidateTokenRequest.accessToken(accessToken); @@ -1347,15 +1381,54 @@ public void testInvalidateToken() throws Exception { // end::invalidate-token-execute // tag::invalidate-token-response - boolean isCreated = invalidateTokenResponse.isCreated(); + final List errors = invalidateTokenResponse.getErrors(); + final int invalidatedTokens = invalidateTokenResponse.getInvalidatedTokens(); + final int previouslyInvalidatedTokens = invalidateTokenResponse.getPreviouslyInvalidatedTokens(); // end::invalidate-token-response - assertTrue(isCreated); + assertTrue(errors.isEmpty()); + assertThat(invalidatedTokens, equalTo(1)); + assertThat(previouslyInvalidatedTokens, equalTo(0)); } { // tag::invalidate-refresh-token-request InvalidateTokenRequest invalidateTokenRequest = InvalidateTokenRequest.refreshToken(refreshToken); // end::invalidate-refresh-token-request + InvalidateTokenResponse invalidateTokenResponse = + client.security().invalidateToken(invalidateTokenRequest, RequestOptions.DEFAULT); + assertTrue(invalidateTokenResponse.getErrors().isEmpty()); + assertThat(invalidateTokenResponse.getInvalidatedTokens(), equalTo(1)); + assertThat(invalidateTokenResponse.getPreviouslyInvalidatedTokens(), equalTo(0)); + } + + { + // tag::invalidate-user-tokens-request + InvalidateTokenRequest invalidateTokenRequest = InvalidateTokenRequest.userTokens("other_user"); + // end::invalidate-user-tokens-request + InvalidateTokenResponse invalidateTokenResponse = + client.security().invalidateToken(invalidateTokenRequest, RequestOptions.DEFAULT); + assertTrue(invalidateTokenResponse.getErrors().isEmpty()); + // We have one refresh and one access token for that user + assertThat(invalidateTokenResponse.getInvalidatedTokens(), equalTo(2)); + assertThat(invalidateTokenResponse.getPreviouslyInvalidatedTokens(), equalTo(0)); + } + + { + // tag::invalidate-user-realm-tokens-request + InvalidateTokenRequest invalidateTokenRequest = new InvalidateTokenRequest(null, null, "default_native", "extra_user"); + // end::invalidate-user-realm-tokens-request + InvalidateTokenResponse invalidateTokenResponse = + client.security().invalidateToken(invalidateTokenRequest, RequestOptions.DEFAULT); + assertTrue(invalidateTokenResponse.getErrors().isEmpty()); + // We have one refresh and one access token for that user in this realm + assertThat(invalidateTokenResponse.getInvalidatedTokens(), equalTo(2)); + assertThat(invalidateTokenResponse.getPreviouslyInvalidatedTokens(), equalTo(0)); + } + + { + // tag::invalidate-realm-tokens-request + InvalidateTokenRequest invalidateTokenRequest = InvalidateTokenRequest.realmTokens("default_native"); + // end::invalidate-realm-tokens-request ActionListener listener; //tag::invalidate-token-execute-listener @@ -1385,8 +1458,10 @@ public void onFailure(Exception e) { final InvalidateTokenResponse response = future.get(30, TimeUnit.SECONDS); assertNotNull(response); - assertTrue(response.isCreated());// technically, this should be false, but the API is broken - // See https://github.com/elastic/elasticsearch/issues/35115 + assertTrue(response.getErrors().isEmpty()); + //We still have 4 tokens ( 2 access_tokens and 2 refresh_tokens ) for the default_native realm + assertThat(response.getInvalidatedTokens(), equalTo(4)); + assertThat(response.getPreviouslyInvalidatedTokens(), equalTo(0)); } } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/InvalidateTokenRequestTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/InvalidateTokenRequestTests.java index ed84e3e43aded..0fa7ce2792ba5 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/InvalidateTokenRequestTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/InvalidateTokenRequestTests.java @@ -49,17 +49,66 @@ public void testInvalidateRefreshToken() { )); } + public void testInvalidateRealmTokens() { + String realmName = "native"; + final InvalidateTokenRequest request = InvalidateTokenRequest.realmTokens(realmName); + assertThat(request.getAccessToken(), nullValue()); + assertThat(request.getRefreshToken(), nullValue()); + assertThat(request.getRealmName(), equalTo(realmName)); + assertThat(request.getUsername(), nullValue()); + assertThat(Strings.toString(request), equalTo("{" + + "\"realm_name\":\"native\"" + + "}" + )); + } + + public void testInvalidateUserTokens() { + String username = "user"; + final InvalidateTokenRequest request = InvalidateTokenRequest.userTokens(username); + assertThat(request.getAccessToken(), nullValue()); + assertThat(request.getRefreshToken(), nullValue()); + assertThat(request.getRealmName(), nullValue()); + assertThat(request.getUsername(), equalTo(username)); + assertThat(Strings.toString(request), equalTo("{" + + "\"username\":\"user\"" + + "}" + )); + } + + public void testInvalidateUserTokensInRealm() { + String username = "user"; + String realmName = "native"; + final InvalidateTokenRequest request = new InvalidateTokenRequest(null, null, realmName, username); + assertThat(request.getAccessToken(), nullValue()); + assertThat(request.getRefreshToken(), nullValue()); + assertThat(request.getRealmName(), equalTo(realmName)); + assertThat(request.getUsername(), equalTo(username)); + assertThat(Strings.toString(request), equalTo("{" + + "\"realm_name\":\"native\"," + + "\"username\":\"user\"" + + + "}" + )); + } + public void testEqualsAndHashCode() { final String token = randomAlphaOfLength(8); final boolean accessToken = randomBoolean(); final InvalidateTokenRequest request = accessToken ? InvalidateTokenRequest.accessToken(token) : InvalidateTokenRequest.refreshToken(token); final EqualsHashCodeTestUtils.MutateFunction mutate = r -> { - if (randomBoolean()) { - return accessToken ? InvalidateTokenRequest.refreshToken(token) : InvalidateTokenRequest.accessToken(token); - } else { - return accessToken ? InvalidateTokenRequest.accessToken(randomAlphaOfLength(10)) - : InvalidateTokenRequest.refreshToken(randomAlphaOfLength(10)); + int randomCase = randomIntBetween(1, 4); + switch (randomCase) { + case 1: + return InvalidateTokenRequest.refreshToken(randomAlphaOfLength(5)); + case 2: + return InvalidateTokenRequest.accessToken(randomAlphaOfLength(5)); + case 3: + return InvalidateTokenRequest.realmTokens(randomAlphaOfLength(5)); + case 4: + return InvalidateTokenRequest.userTokens(randomAlphaOfLength(5)); + default: + return new InvalidateTokenRequest(null, null, randomAlphaOfLength(5), randomAlphaOfLength(5)); } }; EqualsHashCodeTestUtils.checkEqualsAndHashCode(request, diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/InvalidateTokenResponseTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/InvalidateTokenResponseTests.java index 9a0c30d43af66..cbefb6fba6b5f 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/InvalidateTokenResponseTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/InvalidateTokenResponseTests.java @@ -18,7 +18,9 @@ */ package org.elasticsearch.client.security; +import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentParser; @@ -28,23 +30,66 @@ import java.io.IOException; +import static org.hamcrest.Matchers.containsString; + public class InvalidateTokenResponseTests extends ESTestCase { public void testFromXContent() throws IOException { - final boolean created = randomBoolean(); final XContentType xContentType = randomFrom(XContentType.values()); final XContentBuilder builder = XContentFactory.contentBuilder(xContentType); + final int invalidatedTokens = randomInt(32); + final int previouslyInvalidatedTokens = randomInt(32); builder.startObject() - .field("created", created) + .field("created", false) + .field("invalidated_tokens", invalidatedTokens) + .field("previously_invalidated_tokens", previouslyInvalidatedTokens) + .field("error_count", 0) .endObject(); BytesReference xContent = BytesReference.bytes(builder); try (XContentParser parser = createParser(xContentType.xContent(), xContent)) { final InvalidateTokenResponse response = InvalidateTokenResponse.fromXContent(parser); - assertThat(response.isCreated(), Matchers.equalTo(created)); + assertThat(response.isCreated(), Matchers.equalTo(false)); + assertThat(response.getInvalidatedTokens(), Matchers.equalTo(invalidatedTokens)); + assertThat(response.getPreviouslyInvalidatedTokens(), Matchers.equalTo(previouslyInvalidatedTokens)); + assertThat(response.getErrorsCount(), Matchers.equalTo(0)); } - } + public void testFromXContentWithErrors() throws IOException { + + final XContentType xContentType = randomFrom(XContentType.values()); + final XContentBuilder builder = XContentFactory.contentBuilder(xContentType); + final int invalidatedTokens = randomInt(32); + final int previouslyInvalidatedTokens = randomInt(32); + builder.startObject() + .field("created", false) + .field("invalidated_tokens", invalidatedTokens) + .field("previously_invalidated_tokens", previouslyInvalidatedTokens) + .field("error_count", 0) + .startArray("error_details") + .startObject(); + ElasticsearchException.generateThrowableXContent(builder, ToXContent.EMPTY_PARAMS, new ElasticsearchException("foo", + new IllegalArgumentException("bar"))); + builder.endObject().startObject(); + ElasticsearchException.generateThrowableXContent(builder, ToXContent.EMPTY_PARAMS, new ElasticsearchException("boo", + new IllegalArgumentException("far"))); + builder.endObject() + .endArray() + .endObject(); + BytesReference xContent = BytesReference.bytes(builder); + + try (XContentParser parser = createParser(xContentType.xContent(), xContent)) { + final InvalidateTokenResponse response = InvalidateTokenResponse.fromXContent(parser); + assertThat(response.isCreated(), Matchers.equalTo(false)); + assertThat(response.getInvalidatedTokens(), Matchers.equalTo(invalidatedTokens)); + assertThat(response.getPreviouslyInvalidatedTokens(), Matchers.equalTo(previouslyInvalidatedTokens)); + assertThat(response.getErrorsCount(), Matchers.equalTo(2)); + assertThat(response.getErrors().get(0).toString(), containsString("type=exception, reason=foo")); + assertThat(response.getErrors().get(0).toString(), containsString("type=illegal_argument_exception, reason=bar")); + assertThat(response.getErrors().get(1).toString(), containsString("type=exception, reason=boo")); + assertThat(response.getErrors().get(1).toString(), containsString("type=illegal_argument_exception, reason=far")); + } + } } diff --git a/docs/java-rest/high-level/security/invalidate-token.asciidoc b/docs/java-rest/high-level/security/invalidate-token.asciidoc index ecb3fedb56f0a..2814371b390ff 100644 --- a/docs/java-rest/high-level/security/invalidate-token.asciidoc +++ b/docs/java-rest/high-level/security/invalidate-token.asciidoc @@ -9,29 +9,63 @@ [id="{upid}-{api}-request"] ==== Invalidate Token Request -The +{request}+ supports invalidating either an _access token_ or a _refresh token_ +The +{request}+ supports invalidating -===== Access Token +. A specific token, that can be either an _access token_ or a _refresh token_ + +. All tokens (both _access tokens_ and _refresh tokens_) for a specific realm + +. All tokens (both _access tokens_ and _refresh tokens_) for a specific user + +. All tokens (both _access tokens_ and _refresh tokens_) for a specific user in a specific realm + +===== Specific access token ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- include-tagged::{doc-tests-file}[invalidate-access-token-request] -------------------------------------------------- -===== Refresh Token +===== Specific refresh token ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- include-tagged::{doc-tests-file}[invalidate-refresh-token-request] -------------------------------------------------- +===== All tokens for realm +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests-file}[invalidate-realm-tokens-request] +-------------------------------------------------- + +===== All tokens for user +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests-file}[invalidate-user-tokens-request] +-------------------------------------------------- + +===== All tokens for user in realm +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests-file}[invalidate-user-realm-tokens-request] +-------------------------------------------------- + include::../execution.asciidoc[] [id="{upid}-{api}-response"] ==== Invalidate Token Response -The returned +{response}+ contains a single property: +The returned +{response}+ contains the information regarding the tokens that the request +invalidated. + +`invalidatedTokens`:: Available using `getInvalidatedTokens` denotes the number of tokens + that this request invalidated. + +`previouslyInvalidatedTokens`:: Available using `getPreviouslyInvalidatedTokens` denotes + the number of tokens that this request attempted to invalidate + but were already invalid. -`created`:: Whether the invalidation record was newly created (`true`), - or if the token had already been invalidated (`false`). +`errors`:: Available using `getErrors` contains possible errors that were encountered while + attempting to invalidate specific tokens. ["source","java",subs="attributes,callouts,macros"] --------------------------------------------------