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 23 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 @@ -35,6 +35,8 @@ 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)) {
jkakavas marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -46,6 +48,32 @@ public final class InvalidateTokenRequest implements Validatable, ToXContentObje
}
this.accessToken = accessToken;
this.refreshToken = refreshToken;
this.realmName = null;
this.username = 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");
jkakavas marked this conversation as resolved.
Show resolved Hide resolved
}
} 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) {
Expand All @@ -62,6 +90,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;
}
Expand All @@ -70,6 +112,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();
Expand All @@ -79,24 +129,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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,56 +19,44 @@

package org.elasticsearch.client.security;

import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.client.security.support.TokensInvalidationResult;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentParserUtils;

import java.io.IOException;
import java.util.Objects;

import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;

/**
* Response when invalidating an OAuth2 token. Returns a
* single boolean field for whether the invalidation record was created or updated.
*/
public final class InvalidateTokenResponse {
private final TokensInvalidationResult result;
jkakavas marked this conversation as resolved.
Show resolved Hide resolved

private final boolean created;

public InvalidateTokenResponse(boolean created) {
this.created = created;
public InvalidateTokenResponse(TokensInvalidationResult result) {
this.result = result;
}

public boolean isCreated() {
return created;
public TokensInvalidationResult getResult() {
return result;
}

@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 Objects.equals(result, that.result);
}

@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(result);
}

public static InvalidateTokenResponse fromXContent(XContentParser parser) throws IOException {
return PARSER.parse(parser, null);
XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation);
TokensInvalidationResult result = TokensInvalidationResult.fromXContent(parser);
return new InvalidateTokenResponse(result);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.elasticsearch.client.security.support;


import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;

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;

public class TokensInvalidationResult implements ToXContentObject {

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_SIZE = new ParseField("error_size");
public static final ParseField ERRORS = new ParseField("error_details");

private final int invalidatedTokens;
private final int previouslyInvalidatedTokens;
private List<ElasticsearchException> errors;

public TokensInvalidationResult(int invalidatedTokens, int previouslyInvalidatedTokens, @Nullable List<ElasticsearchException> errors) {
this.invalidatedTokens = invalidatedTokens;
this.previouslyInvalidatedTokens = previouslyInvalidatedTokens;
if (null == errors) {
this.errors = Collections.emptyList();
} else {
this.errors = errors;
}
}

public int getInvalidatedTokens() {
return invalidatedTokens;
}

public int getPreviouslyInvalidatedTokens() {
return previouslyInvalidatedTokens;
}

public List<ElasticsearchException> getErrors() {
return errors;
}

@SuppressWarnings("unchecked")
public static final ConstructingObjectParser<TokensInvalidationResult, Void> PARSER = new ConstructingObjectParser<>(
"tokens_invalidation_result", true,
// we parse but do not use the size of errors as we implicitly have this in the size of the Exceptions list
args -> new TokensInvalidationResult((int) args[0], (int) args[1], (List<ElasticsearchException>) args[3]));

static {
PARSER.declareInt(constructorArg(), INVALIDATED_TOKENS);
PARSER.declareInt(constructorArg(), PREVIOUSLY_INVALIDATED_TOKENS);
PARSER.declareInt(constructorArg(), ERROR_SIZE);
PARSER.declareObjectArray(optionalConstructorArg(), (p, c) -> ElasticsearchException.fromXContent(p), ERRORS);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TokensInvalidationResult that = (TokensInvalidationResult) o;
return invalidatedTokens == that.invalidatedTokens &&
previouslyInvalidatedTokens == that.previouslyInvalidatedTokens &&
Objects.equals(errors, that.errors);
}

@Override
public int hashCode() {
return Objects.hash(invalidatedTokens, previouslyInvalidatedTokens, errors);
}

public static TokensInvalidationResult fromXContent(XContentParser parser) throws IOException {
return PARSER.parse(parser, null);
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject()
.field("invalidated_tokens", invalidatedTokens)
.field("previously_invalidated_tokens", previouslyInvalidatedTokens)
.field("error_size", errors.size());
if (errors.isEmpty() == false) {
builder.field("error_details");
builder.startArray();
for (ElasticsearchException e : errors) {
builder.startObject();
ElasticsearchException.generateThrowableXContent(builder, params, e);
builder.endObject();
}
builder.endArray();
}
return builder.endObject();
}

public String toString() {
try {
final XContentBuilder builder = XContentFactory.jsonBuilder();
toXContent(builder, ToXContent.EMPTY_PARAMS);
return Strings.toString(builder);
} catch (IOException e) {
return "";
jkakavas marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
Loading