Invalidate Token API enhancements - HLRC (#36362)

* Adds Invalidate Token API enhancements to HLRC

Relates: #35388
This commit is contained in:
Ioannis Kakavas 2018-12-18 16:12:43 +02:00 committed by GitHub
parent f884b2b1cd
commit 78f9af19c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 356 additions and 57 deletions

View File

@ -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 final class InvalidateTokenRequest implements Validatable, ToXContentObje
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 final class InvalidateTokenRequest implements Validatable, ToXContentObje
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 final class InvalidateTokenRequest implements Validatable, ToXContentObje
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);
}
}

View File

@ -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 {
private final boolean created;
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");
public InvalidateTokenResponse(boolean created) {
private final boolean created;
private final int invalidatedTokens;
private final int previouslyInvalidatedTokens;
private List<ElasticsearchException> errors;
@SuppressWarnings("unchecked")
private static final ConstructingObjectParser<InvalidateTokenResponse, Void> 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<ElasticsearchException>) 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, int invalidatedTokens, int previouslyInvalidatedTokens,
@Nullable List<ElasticsearchException> 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<ElasticsearchException> 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<InvalidateTokenResponse, Void> 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);
}
}

View File

@ -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;
@ -1324,19 +1325,52 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
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);
@ -1348,15 +1382,54 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
// end::invalidate-token-execute
// tag::invalidate-token-response
boolean isCreated = invalidateTokenResponse.isCreated();
final List<ElasticsearchException> 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<InvalidateTokenResponse> listener;
//tag::invalidate-token-execute-listener
@ -1386,8 +1459,10 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
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));
}
}

View File

@ -49,17 +49,66 @@ public class InvalidateTokenRequestTests extends ESTestCase {
));
}
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<InvalidateTokenRequest> 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,

View File

@ -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 org.hamcrest.Matchers;
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"));
}
}
}

View File

@ -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.
`created`:: Whether the invalidation record was newly created (`true`),
or if the token had already been invalidated (`false`).
`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.
`errors`:: Available using `getErrors` contains possible errors that were encountered while
attempting to invalidate specific tokens.
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------