Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance Invalidate Token API #35388

Merged
merged 50 commits into from
Dec 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
71447cc
wip
jkakavas Nov 2, 2018
c9b1c45
Adds support for invalidate by realm or username
jkakavas Nov 6, 2018
dee9fce
Merge remote-tracking branch 'origin/master' into enhance-invalidate-api
jkakavas Nov 6, 2018
f1cbe27
Update TransportSamlInvalidateSessionActionTests
jkakavas Nov 6, 2018
118ed35
Handle cases where there are no tokens to invalidate
jkakavas Nov 7, 2018
a6e1f22
Remove * import
jkakavas Nov 7, 2018
485cff9
Add a couple more integ tests
jkakavas Nov 7, 2018
0647a28
Implement toXContent and revelant tests
jkakavas Nov 8, 2018
94abc3f
add rest tests
jkakavas Nov 8, 2018
a2227ea
Don't wrap result
jkakavas Nov 8, 2018
19c75c4
remove another unnecessary layer of XContent wrapping
jkakavas Nov 8, 2018
c3e67f6
Refactor and add invalidateAllTokens method
jkakavas Nov 8, 2018
8e4dec5
Change some hasText <-> isNullOrEmpty for readability and remove unne…
jkakavas Nov 8, 2018
1005e15
Correct parsing of invalidation request
jkakavas Nov 8, 2018
2a37cfc
Remove unused imports
jkakavas Nov 8, 2018
d6cf150
Don't handle VersionConflictEngineExceptions as we don't do version u…
jkakavas Nov 8, 2018
40feb13
duplicate traceLog for readability
jkakavas Nov 8, 2018
75c1f37
Merge remote-tracking branch 'origin/master' into enhance-invalidate-api
jkakavas Nov 28, 2018
4a76b77
address initial feedback
jkakavas Nov 29, 2018
43127b0
Finalize change
jkakavas Dec 2, 2018
0055ba1
Add empty new line
jkakavas Dec 3, 2018
d7c1556
Merge remote-tracking branch 'origin/master' into enhance-invalidate-api
jkakavas Dec 3, 2018
541bf64
Fix rest tests
jkakavas Dec 3, 2018
46fca25
Fix TransportSamlLogoutActionTests
jkakavas Dec 3, 2018
ac3dd86
Remove unused import
jkakavas Dec 3, 2018
be3cad3
fix tests so that arrays are not empty when required
jkakavas Dec 3, 2018
a2d8078
Address feedback
jkakavas Dec 5, 2018
8342818
Merge remote-tracking branch 'origin/master' into enhance-invalidate-api
jkakavas Dec 5, 2018
5c28353
Merge remote-tracking branch 'origin/master' into enhance-invalidate-api
jkakavas Dec 7, 2018
fd5ea5f
Reverts commits regarding HLRC
jkakavas Dec 7, 2018
e402d88
Address feedback
jkakavas Dec 7, 2018
dac7823
Merge remote-tracking branch 'origin/master' into enhance-invalidate-api
jkakavas Dec 7, 2018
9c8e90c
Reference HLRC PR in assumeFalse
jkakavas Dec 7, 2018
a8c9410
fix merge woes
jkakavas Dec 7, 2018
0f2f201
Address feedback
jkakavas Dec 10, 2018
6209deb
Merge remote-tracking branch 'origin/master' into enhance-invalidate-api
jkakavas Dec 10, 2018
2fb24d6
Fix serialization tests now that we do not implement equals
jkakavas Dec 11, 2018
2e0cf31
Merge remote-tracking branch 'origin/master' into enhance-invalidate-api
jkakavas Dec 14, 2018
e91e555
address feedback
jkakavas Dec 14, 2018
6ad0850
Missing previousResult param
jkakavas Dec 14, 2018
bbbd900
Restore rest tests
jkakavas Dec 14, 2018
bcac7f1
Merge remote-tracking branch 'origin/master' into enhance-invalidate-api
jkakavas Dec 14, 2018
bf9043a
restore previous invalidated token HLRC docs
jkakavas Dec 17, 2018
162ea66
Add created field so that it's included in the backport
jkakavas Dec 17, 2018
7356dd2
Merge remote-tracking branch 'origin/master' into enhance-invalidate-api
jkakavas Dec 17, 2018
75e62d6
Merge remote-tracking branch 'origin/master' into enhance-invalidate-api
jkakavas Dec 17, 2018
1c7c761
address final round of feedback
jkakavas Dec 17, 2018
858935e
Merge remote-tracking branch 'origin/master' into enhance-invalidate-api
jkakavas Dec 17, 2018
e0546b3
Merge remote-tracking branch 'origin/master' into enhance-invalidate-api
jkakavas Dec 17, 2018
ff775e6
Merge remote-tracking branch 'origin/master' into enhance-invalidate-api
jkakavas Dec 18, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1317,6 +1317,7 @@ public void onFailure(Exception e) {
}
}

@AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/pull/36362")
public void testInvalidateToken() throws Exception {
jkakavas marked this conversation as resolved.
Show resolved Hide resolved
RestHighLevelClient client = highLevelClient();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,4 @@ The returned +{response}+ contains a single property:
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests-file}[{api}-response]
--------------------------------------------------
--------------------------------------------------
90 changes: 80 additions & 10 deletions x-pack/docs/en/rest-api/security/invalidate-tokens.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
[[security-api-invalidate-token]]
=== Invalidate token API

Invalidates an access token or a refresh token.
Invalidates one or more access tokens or refresh tokens.

==== Request

Expand All @@ -19,21 +19,31 @@ can no longer be used. That time period is defined by the
The refresh tokens returned by the <<security-api-get-token,get token API>> are
only valid for 24 hours. They can also be used exactly once.

If you want to invalidate an access or refresh token immediately, use this invalidate token API.
If you want to invalidate one or more access or refresh tokens immediately, use this invalidate token API.


==== Request Body

The following parameters can be specified in the body of a DELETE request and
pertain to invalidating a token:
pertain to invalidating tokens:

`token` (optional)::
(string) An access token. This parameter cannot be used when `refresh_token` is used.
(string) An access token. This parameter cannot be used any of `refresh_token`, `realm_name` or
`username` are used.

`refresh_token` (optional)::
(string) A refresh token. This parameter cannot be used when `token` is used.
(string) A refresh token. This parameter cannot be used any of `refresh_token`, `realm_name` or
`username` are used.

NOTE: One of `token` or `refresh_token` parameters is required.
`realm_name` (optional)::
(string) The name of an authentication realm. This parameter cannot be used with either `refresh_token` or `token`.

`username` (optional)::
(string) The username of a user. This parameter cannot be used with either `refresh_token` or `token`

NOTE: While all parameters are optional, at least one of them is required. More specifically, either one of `token`
or `refresh_token` parameters is required. If none of these two are specified, then `realm_name` and/or `username`
need to be specified.

==== Examples

Expand All @@ -59,15 +69,75 @@ DELETE /_security/oauth2/token
--------------------------------------------------
// NOTCONSOLE

A successful call returns a JSON structure that indicates whether the token
has already been invalidated.
The following example invalidates all access tokens and refresh tokens for the `saml1` realm immediately:

[source,js]
--------------------------------------------------
DELETE /_xpack/security/oauth2/token
{
"realm_name" : "saml1"
}
--------------------------------------------------
// NOTCONSOLE

The following example invalidates all access tokens and refresh tokens for the user `myuser` in all realms immediately:

[source,js]
--------------------------------------------------
DELETE /_xpack/security/oauth2/token
{
"username" : "myuser"
}
--------------------------------------------------
// NOTCONSOLE

Finally, the following example invalidates all access tokens and refresh tokens for the user `myuser` in
the `saml1` realm immediately:

[source,js]
--------------------------------------------------
DELETE /_xpack/security/oauth2/token
{
"username" : "myuser",
"realm_name" : "saml1"
}
--------------------------------------------------
// NOTCONSOLE

A successful call returns a JSON structure that contains the number of tokens that were invalidated, the number
of tokens that had already been invalidated, and potentially a list of errors encountered while invalidating
specific tokens.

[source,js]
--------------------------------------------------
{
"created" : true <1>
"invalidated_tokens":9, <1>
"previously_invalidated_tokens":15, <2>
"error_count":2, <3>
"error_details":[ <4>
{
"type":"exception",
"reason":"Elasticsearch exception [type=exception, reason=foo]",
"caused_by":{
"type":"exception",
"reason":"Elasticsearch exception [type=illegal_argument_exception, reason=bar]"
}
},
{
"type":"exception",
"reason":"Elasticsearch exception [type=exception, reason=boo]",
"caused_by":{
"type":"exception",
"reason":"Elasticsearch exception [type=illegal_argument_exception, reason=far]"
}
}
]
}
--------------------------------------------------
// NOTCONSOLE

<1> When a token has already been invalidated, `created` is set to false.
<1> The number of the tokens that were invalidated as part of this request.
<2> The number of tokens that were already invalidated.
<3> The number of errors that were encountered when invalidating the tokens.
<4> Details about these errors. This field is not present in the response when
`error_count` is 0.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import org.elasticsearch.action.Action;

/**
* Action for invalidating a given token
* Action for invalidating one or more tokens
*/
public final class InvalidateTokenAction extends Action<InvalidateTokenResponse> {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
Expand All @@ -22,31 +23,81 @@
public final class InvalidateTokenRequest extends ActionRequest {

public enum Type {
ACCESS_TOKEN,
REFRESH_TOKEN
ACCESS_TOKEN("token"),
REFRESH_TOKEN("refresh_token");

private final String value;

Type(String value) {
this.value = value;
}

public String getValue() {
return value;
}

public static Type fromString(String tokenType) {
if (tokenType != null) {
for (Type type : values()) {
if (type.getValue().equals(tokenType)) {
return type;
}
}
}
return null;
}
}

private String tokenString;
private Type tokenType;
private String realmName;
private String userName;

public InvalidateTokenRequest() {}

/**
* @param tokenString the string representation of the token
* @param tokenString the string representation of the token to be invalidated
* @param tokenType the type of the token to be invalidated
* @param realmName the name of the realm for which all tokens will be invalidated
* @param userName the principal of the user for which all tokens will be invalidated
*/
public InvalidateTokenRequest(String tokenString, Type type) {
public InvalidateTokenRequest(@Nullable String tokenString, @Nullable String tokenType,
@Nullable String realmName, @Nullable String userName) {
this.tokenString = tokenString;
this.tokenType = type;
this.tokenType = Type.fromString(tokenType);
this.realmName = realmName;
this.userName = userName;
}

/**
* @param tokenString the string representation of the token to be invalidated
* @param tokenType the type of the token to be invalidated
*/
public InvalidateTokenRequest(String tokenString, String tokenType) {
this.tokenString = tokenString;
this.tokenType = Type.fromString(tokenType);
this.realmName = null;
this.userName = null;
}

@Override
public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = null;
if (Strings.isNullOrEmpty(tokenString)) {
validationException = addValidationError("token string must be provided", null);
}
if (tokenType == null) {
validationException = addValidationError("token type must be provided", validationException);
if (Strings.hasText(realmName) || Strings.hasText(userName)) {
if (Strings.hasText(tokenString)) {
validationException =
addValidationError("token string must not be provided when realm name or username is specified", null);
}
if (tokenType != null) {
validationException =
addValidationError("token type must not be provided when realm name or username is specified", validationException);
}
} else if (Strings.isNullOrEmpty(tokenString)) {
validationException =
addValidationError("token string must be provided when not specifying a realm name or a username", null);
} else if (tokenType == null) {
validationException =
addValidationError("token type must be provided when a token string is specified", null);
}
return validationException;
}
Expand All @@ -67,26 +118,76 @@ void setTokenType(Type tokenType) {
this.tokenType = tokenType;
}

public String getRealmName() {
return realmName;
}

public void setRealmName(String realmName) {
this.realmName = realmName;
}

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeString(tokenString);
if (out.getVersion().before(Version.V_7_0_0)) {
if (Strings.isNullOrEmpty(tokenString)) {
throw new IllegalArgumentException("token is required for versions < v6.6.0");
}
out.writeString(tokenString);
jkakavas marked this conversation as resolved.
Show resolved Hide resolved
} else {
out.writeOptionalString(tokenString);
}
if (out.getVersion().onOrAfter(Version.V_6_2_0)) {
out.writeVInt(tokenType.ordinal());
if (out.getVersion().before(Version.V_7_0_0)) {
if (tokenType == null) {
throw new IllegalArgumentException("token type is not optional for versions > v6.2.0 and < v6.6.0");
}
out.writeVInt(tokenType.ordinal());
} else {
out.writeOptionalVInt(tokenType == null ? null : tokenType.ordinal());
}
} else if (tokenType == Type.REFRESH_TOKEN) {
throw new IllegalArgumentException("refresh token invalidation cannot be serialized with version [" + out.getVersion() +
"]");
throw new IllegalArgumentException("refresh token invalidation cannot be serialized with version [" + out.getVersion() + "]");
}
if (out.getVersion().onOrAfter(Version.V_7_0_0)) {
out.writeOptionalString(realmName);
out.writeOptionalString(userName);
} else if (realmName != null || userName != null) {
throw new IllegalArgumentException(
"realm or user token invalidation cannot be serialized with version [" + out.getVersion() + "]");
}
}

@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
tokenString = in.readString();
if (in.getVersion().before(Version.V_7_0_0)) {
tokenString = in.readString();
} else {
tokenString = in.readOptionalString();
}
if (in.getVersion().onOrAfter(Version.V_6_2_0)) {
tokenType = Type.values()[in.readVInt()];
if (in.getVersion().before(Version.V_7_0_0)) {
int type = in.readVInt();
tokenType = Type.values()[type];
} else {
Integer type = in.readOptionalVInt();
tokenType = type == null ? null : Type.values()[type];
}
} else {
tokenType = Type.ACCESS_TOKEN;
}
if (in.getVersion().onOrAfter(Version.V_7_0_0)) {
realmName = in.readOptionalString();
userName = in.readOptionalString();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,20 @@ public InvalidateTokenRequestBuilder setType(InvalidateTokenRequest.Type type) {
request.setTokenType(type);
return this;
}

/**
* Sets the name of the realm for which all tokens should be invalidated
*/
public InvalidateTokenRequestBuilder setRealmName(String realmName) {
request.setRealmName(realmName);
return this;
}

/**
* Sets the username for which all tokens should be invalidated
*/
public InvalidateTokenRequestBuilder setUserName(String username) {
request.setUserName(username);
return this;
}
}
Loading