From 0c896f4ee18e4558cfd91669a925916eeeba45aa Mon Sep 17 00:00:00 2001 From: Tim Vernum Date: Wed, 31 Oct 2018 15:44:24 +1100 Subject: [PATCH 1/4] HLRC: Add InvalidateToken security API This change adds the Invalidate Token API (DELETE /_xpack/security/oauth2/token) to the Elasticsearch High Level Rest Client. Relates: #29827 --- .../elasticsearch/client/SecurityClient.java | 33 +++++++ .../client/SecurityRequestConverters.java | 7 ++ .../security/InvalidateTokenRequest.java | 95 +++++++++++++++++++ .../security/InvalidateTokenResponse.java | 74 +++++++++++++++ .../SecurityDocumentationIT.java | 74 +++++++++++++++ .../security/InvalidateTokenRequestTests.java | 68 +++++++++++++ .../InvalidateTokenResponseTests.java | 50 ++++++++++ .../security/invalidate-token.asciidoc | 72 ++++++++++++++ .../high-level/supported-apis.asciidoc | 4 +- 9 files changed, 476 insertions(+), 1 deletion(-) create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/security/InvalidateTokenRequest.java create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/security/InvalidateTokenResponse.java create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/security/InvalidateTokenRequestTests.java create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/security/InvalidateTokenResponseTests.java create mode 100644 docs/java-rest/high-level/security/invalidate-token.asciidoc diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java index e604814a3bce5..12608e69da745 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java @@ -36,6 +36,8 @@ import org.elasticsearch.client.security.GetRoleMappingsResponse; import org.elasticsearch.client.security.GetSslCertificatesRequest; import org.elasticsearch.client.security.GetSslCertificatesResponse; +import org.elasticsearch.client.security.InvalidateTokenRequest; +import org.elasticsearch.client.security.InvalidateTokenResponse; import org.elasticsearch.client.security.PutRoleMappingRequest; import org.elasticsearch.client.security.PutRoleMappingResponse; import org.elasticsearch.client.security.PutUserRequest; @@ -380,4 +382,35 @@ public void createTokenAsync(CreateTokenRequest request, RequestOptions options, restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::createToken, options, CreateTokenResponse::fromXContent, listener, emptySet()); } + + /** + * Invalidates an OAuth2 token. + * See + * the docs for more. + * + * @param request the request to invalidate the token + * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @return the response from the create token call + * @throws IOException in case there is a problem sending the request or parsing back the response + */ + public InvalidateTokenResponse invalidateToken(InvalidateTokenRequest request, RequestOptions options) throws IOException { + return restHighLevelClient.performRequestAndParseEntity(request, SecurityRequestConverters::invalidateToken, options, + InvalidateTokenResponse::fromXContent, emptySet()); + } + + /** + * Asynchronously invalidates an OAuth2 token. + * See + * the docs for more. + * + * @param request the request to invalidate the token + * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @param listener the listener to be notified upon request completion + */ + public void invalidateTokenAsync(InvalidateTokenRequest request, RequestOptions options, + ActionListener listener) { + restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::invalidateToken, options, + InvalidateTokenResponse::fromXContent, listener, emptySet()); + } + } diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityRequestConverters.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityRequestConverters.java index 2feb15704333e..c8e3fe2b04dfb 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityRequestConverters.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityRequestConverters.java @@ -27,6 +27,7 @@ import org.elasticsearch.client.security.CreateTokenRequest; import org.elasticsearch.client.security.DeleteRoleMappingRequest; import org.elasticsearch.client.security.DeleteRoleRequest; +import org.elasticsearch.client.security.InvalidateTokenRequest; import org.elasticsearch.client.security.PutRoleMappingRequest; import org.elasticsearch.client.security.DisableUserRequest; import org.elasticsearch.client.security.EnableUserRequest; @@ -147,4 +148,10 @@ static Request createToken(CreateTokenRequest createTokenRequest) throws IOExcep request.setEntity(createEntity(createTokenRequest, REQUEST_BODY_CONTENT_TYPE)); return request; } + + static Request invalidateToken(InvalidateTokenRequest invalidateTokenRequest) throws IOException { + Request request = new Request(HttpDelete.METHOD_NAME, "/_xpack/security/oauth2/token"); + request.setEntity(createEntity(invalidateTokenRequest, REQUEST_BODY_CONTENT_TYPE)); + return request; + } } 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 new file mode 100644 index 0000000000000..733001ce5edf6 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/InvalidateTokenRequest.java @@ -0,0 +1,95 @@ +/* + * 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; + +import org.elasticsearch.client.Validatable; +import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; + +import java.io.IOException; +import java.util.Objects; + +/** + * Request to invalidate a OAuth2 token within the Elasticsearch cluster. + */ +public final class InvalidateTokenRequest implements Validatable, ToXContentObject { + + private final String accessToken; + private final String refreshToken; + + InvalidateTokenRequest(@Nullable String accessToken, @Nullable String refreshToken) { + this.accessToken = accessToken; + this.refreshToken = refreshToken; + } + + public static InvalidateTokenRequest accessToken(String accessToken) { + if (Strings.isNullOrEmpty(accessToken)) { + throw new IllegalArgumentException("token is required"); + } + return new InvalidateTokenRequest(accessToken, null); + } + + public static InvalidateTokenRequest refreshToken(String refreshToken) { + if (Strings.isNullOrEmpty(refreshToken)) { + throw new IllegalArgumentException("refresh_token is required"); + } + return new InvalidateTokenRequest(null, refreshToken); + } + + public String getAccessToken() { + return accessToken; + } + + public String getRefreshToken() { + return refreshToken; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + if (accessToken != null) { + builder.field("token", accessToken); + } + if (refreshToken != null) { + builder.field("refresh_token", refreshToken); + } + 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); + } + + @Override + public int hashCode() { + return Objects.hash(accessToken, refreshToken); + } +} 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 new file mode 100644 index 0000000000000..876faa485c236 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/InvalidateTokenResponse.java @@ -0,0 +1,74 @@ +/* + * 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; + +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.XContentParser; + +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 boolean created; + + public InvalidateTokenResponse(boolean created) { + this.created = created; + } + + public boolean isCreated() { + return created; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + InvalidateTokenResponse that = (InvalidateTokenResponse) o; + return created == that.created; + } + + @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")); + } + + public static InvalidateTokenResponse fromXContent(XContentParser parser) throws IOException { + 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 7c37c7ef50a7a..188aabe89fa81 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 @@ -45,6 +45,8 @@ import org.elasticsearch.client.security.GetRoleMappingsRequest; import org.elasticsearch.client.security.GetRoleMappingsResponse; import org.elasticsearch.client.security.GetSslCertificatesResponse; +import org.elasticsearch.client.security.InvalidateTokenRequest; +import org.elasticsearch.client.security.InvalidateTokenResponse; import org.elasticsearch.client.security.PutRoleMappingRequest; import org.elasticsearch.client.security.PutRoleMappingResponse; import org.elasticsearch.client.security.PutUserRequest; @@ -744,6 +746,78 @@ public void onFailure(Exception e) { // "client-credentials" grants aren't refreshable assertNull(future.get().getRefreshToken()); } + } + + public void testInvalidateToken() throws Exception { + RestHighLevelClient client = highLevelClient(); + + String accessToken; + String refreshToken; + { + // Setup user + final char[] password = "password".toCharArray(); + PutUserRequest putUserRequest = new PutUserRequest("invalidate_token", password, + Collections.singletonList("kibana_user"), null, null, true, null, RefreshPolicy.IMMEDIATE); + PutUserResponse putUserResponse = client.security().putUser(putUserRequest, RequestOptions.DEFAULT); + assertTrue(putUserResponse.isCreated()); + // Create tokens + final CreateTokenRequest createTokenRequest = CreateTokenRequest.passwordGrant("invalidate_token", password); + final CreateTokenResponse tokenResponse = client.security().createToken(createTokenRequest, RequestOptions.DEFAULT); + accessToken = tokenResponse.getAccessToken(); + refreshToken = tokenResponse.getRefreshToken(); + } + { + // tag::invalidate-access-token-request + InvalidateTokenRequest invalidateTokenRequest = InvalidateTokenRequest.accessToken(accessToken); + // end::invalidate-access-token-request + + // tag::invalidate-token-execute + InvalidateTokenResponse invalidateTokenResponse = + client.security().invalidateToken(invalidateTokenRequest, RequestOptions.DEFAULT); + // end::invalidate-token-execute + + // tag::invalidate-token-response + boolean isCreated = invalidateTokenResponse.isCreated(); + // end::invalidate-token-response + assertTrue(isCreated); + } + + { + // tag::invalidate-refresh-token-request + InvalidateTokenRequest invalidateTokenRequest = InvalidateTokenRequest.refreshToken(refreshToken); + // end::invalidate-refresh-token-request + + ActionListener listener; + //tag::invalidate-token-execute-listener + listener = new ActionListener() { + @Override + public void onResponse(InvalidateTokenResponse invalidateTokenResponse) { + // <1> + } + + @Override + public void onFailure(Exception e) { + // <2> + } + }; + //end::invalidate-token-execute-listener + + // Avoid unused variable warning + assertNotNull(listener); + + // Replace the empty listener by a blocking listener in test + final PlainActionFuture future = new PlainActionFuture<>(); + listener = future; + + //tag::invalidate-token-execute-async + client.security().invalidateTokenAsync(invalidateTokenRequest, RequestOptions.DEFAULT, listener); // <1> + //end::invalidate-token-execute-async + + 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/TBD + } } } 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 new file mode 100644 index 0000000000000..ed84e3e43aded --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/InvalidateTokenRequestTests.java @@ -0,0 +1,68 @@ +/* + * 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; + +import org.elasticsearch.common.Strings; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.EqualsHashCodeTestUtils; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.nullValue; + +public class InvalidateTokenRequestTests extends ESTestCase { + + public void testInvalidateAccessToken() { + String token = "Tf01rrAymdUjxMY4VlG3gV3gsFFUWxVVPrztX+4uhe0="; + final InvalidateTokenRequest request = InvalidateTokenRequest.accessToken(token); + assertThat(request.getAccessToken(), equalTo(token)); + assertThat(request.getRefreshToken(), nullValue()); + assertThat(Strings.toString(request), equalTo("{" + + "\"token\":\"Tf01rrAymdUjxMY4VlG3gV3gsFFUWxVVPrztX+4uhe0=\"" + + "}" + )); + } + + public void testInvalidateRefreshToken() { + String token = "4rE0YPT/oHODS83TbTtYmuh8"; + final InvalidateTokenRequest request = InvalidateTokenRequest.refreshToken(token); + assertThat(request.getAccessToken(), nullValue()); + assertThat(request.getRefreshToken(), equalTo(token)); + assertThat(Strings.toString(request), equalTo("{" + + "\"refresh_token\":\"4rE0YPT/oHODS83TbTtYmuh8\"" + + "}" + )); + } + + 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)); + } + }; + EqualsHashCodeTestUtils.checkEqualsAndHashCode(request, + r -> new InvalidateTokenRequest(r.getAccessToken(), r.getRefreshToken()), mutate); + } +} 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 new file mode 100644 index 0000000000000..9a0c30d43af66 --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/InvalidateTokenResponseTests.java @@ -0,0 +1,50 @@ +/* + * 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; + +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.test.ESTestCase; +import org.hamcrest.Matchers; + +import java.io.IOException; + +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); + builder.startObject() + .field("created", created) + .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)); + } + + } + +} diff --git a/docs/java-rest/high-level/security/invalidate-token.asciidoc b/docs/java-rest/high-level/security/invalidate-token.asciidoc new file mode 100644 index 0000000000000..9cdb066b7d7a6 --- /dev/null +++ b/docs/java-rest/high-level/security/invalidate-token.asciidoc @@ -0,0 +1,72 @@ +[[java-rest-high-security-invalidate-token]] +=== Invalidate Token API + +[[java-rest-high-security-invalidate-token-request]] +==== Request +The `InvalidateTokenRequest` supports invalidating either an _access token_ or a _refresh token_ + +===== Access Token + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/SecurityDocumentationIT.java[invalidate-access-token-request] +-------------------------------------------------- + +===== Refresh Token +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/SecurityDocumentationIT.java[invalidate-refresh-token-request] +-------------------------------------------------- + +[[java-rest-high-security-invalidate-token-execution]] +==== Execution + +Invalidating a OAuth2 security token can be performed by passing the appropriate request to the + `security().invalidateToken()` method: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/SecurityDocumentationIT.java[invalidate-token-execute] +-------------------------------------------------- + +[[java-rest-high-security-invalidate-token-response]] +==== Response + +The returned `InvalidateTokenResponse` contains a single property: + +`created`:: Whether the invalidation record was newly created (`true`), + or if the token had already been invalidated (`false`). + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/SecurityDocumentationIT.java[invalidate-token-response] +-------------------------------------------------- + +[[java-rest-high-security-invalidate-token-async]] +==== Asynchronous Execution + +This request can be executed asynchronously using the `security().invalidateTokenAsync()` +method: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/SecurityDocumentationIT.java[invalidate-token-execute-async] +-------------------------------------------------- +<1> The `InvalidateTokenRequest` to execute and the `ActionListener` to use when +the execution completes + +The asynchronous method does not block and returns immediately. Once the request +has completed the `ActionListener` is called back using the `onResponse` method +if the execution successfully completed or using the `onFailure` method if +it failed. + +A typical listener for a `InvalidateTokenResponse` looks like: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/SecurityDocumentationIT.java[invalidate-token-execute-listener] +-------------------------------------------------- +<1> Called when the execution is successfully completed. The response is +provided as an argument +<2> Called in case of failure. The raised exception is provided as an argument + diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc index b7f4cba952083..72f734c116c0a 100644 --- a/docs/java-rest/high-level/supported-apis.asciidoc +++ b/docs/java-rest/high-level/supported-apis.asciidoc @@ -332,6 +332,7 @@ The Java High Level REST Client supports the following Security APIs: * <> * <> * <> +* <> include::security/put-user.asciidoc[] include::security/enable-user.asciidoc[] @@ -344,6 +345,7 @@ include::security/put-role-mapping.asciidoc[] include::security/get-role-mappings.asciidoc[] include::security/delete-role-mapping.asciidoc[] include::security/create-token.asciidoc[] +include::security/invalidate-token.asciidoc[] == Watcher APIs @@ -386,4 +388,4 @@ don't leak into the rest of the documentation. :response!: :doc-tests-file!: :upid!: --- \ No newline at end of file +-- From 89d4a7b82dded3ad72fa7dcc3e4aff7ac017ff0e Mon Sep 17 00:00:00 2001 From: Tim Vernum Date: Wed, 31 Oct 2018 16:11:41 +1100 Subject: [PATCH 2/4] Fix some URLs --- .../elasticsearch/client/SecurityClient.java | 4 +- .../SecurityDocumentationIT.java | 40 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java index 12608e69da745..d586dc1762d4d 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java @@ -385,7 +385,7 @@ public void createTokenAsync(CreateTokenRequest request, RequestOptions options, /** * Invalidates an OAuth2 token. - * See + * See * the docs for more. * * @param request the request to invalidate the token @@ -400,7 +400,7 @@ public InvalidateTokenResponse invalidateToken(InvalidateTokenRequest request, R /** * Asynchronously invalidates an OAuth2 token. - * See + * See * the docs for more. * * @param request the request to invalidate the token 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 188aabe89fa81..513c790a134e0 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 @@ -69,13 +69,13 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.not; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.isIn; +import static org.hamcrest.Matchers.not; public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase { @@ -133,11 +133,11 @@ public void testPutRoleMapping() throws Exception { { // tag::put-role-mapping-execute final RoleMapperExpression rules = AnyRoleMapperExpression.builder() - .addExpression(FieldRoleMapperExpression.ofUsername("*")) - .addExpression(FieldRoleMapperExpression.ofGroups("cn=admins,dc=example,dc=com")) - .build(); + .addExpression(FieldRoleMapperExpression.ofUsername("*")) + .addExpression(FieldRoleMapperExpression.ofGroups("cn=admins,dc=example,dc=com")) + .build(); final PutRoleMappingRequest request = new PutRoleMappingRequest("mapping-example", true, Collections.singletonList("superuser"), - rules, null, RefreshPolicy.NONE); + rules, null, RefreshPolicy.NONE); final PutRoleMappingResponse response = client.security().putRoleMapping(request, RequestOptions.DEFAULT); // end::put-role-mapping-execute // tag::put-role-mapping-response @@ -148,11 +148,11 @@ public void testPutRoleMapping() throws Exception { { final RoleMapperExpression rules = AnyRoleMapperExpression.builder() - .addExpression(FieldRoleMapperExpression.ofUsername("*")) - .addExpression(FieldRoleMapperExpression.ofGroups("cn=admins,dc=example,dc=com")) - .build(); + .addExpression(FieldRoleMapperExpression.ofUsername("*")) + .addExpression(FieldRoleMapperExpression.ofGroups("cn=admins,dc=example,dc=com")) + .build(); final PutRoleMappingRequest request = new PutRoleMappingRequest("mapping-example", true, Collections.singletonList("superuser"), - rules, null, RefreshPolicy.NONE); + rules, null, RefreshPolicy.NONE); // tag::put-role-mapping-execute-listener ActionListener listener = new ActionListener() { @Override @@ -183,21 +183,21 @@ public void testGetRoleMappings() throws Exception { final RestHighLevelClient client = highLevelClient(); final RoleMapperExpression rules1 = AnyRoleMapperExpression.builder().addExpression(FieldRoleMapperExpression.ofUsername("*")) - .addExpression(FieldRoleMapperExpression.ofGroups("cn=admins,dc=example,dc=com")).build(); + .addExpression(FieldRoleMapperExpression.ofGroups("cn=admins,dc=example,dc=com")).build(); final PutRoleMappingRequest putRoleMappingRequest1 = new PutRoleMappingRequest("mapping-example-1", true, Collections.singletonList( - "superuser"), rules1, null, RefreshPolicy.NONE); + "superuser"), rules1, null, RefreshPolicy.NONE); final PutRoleMappingResponse putRoleMappingResponse1 = client.security().putRoleMapping(putRoleMappingRequest1, - RequestOptions.DEFAULT); + RequestOptions.DEFAULT); boolean isCreated1 = putRoleMappingResponse1.isCreated(); assertTrue(isCreated1); final RoleMapperExpression rules2 = AnyRoleMapperExpression.builder().addExpression(FieldRoleMapperExpression.ofGroups( - "cn=admins,dc=example,dc=com")).build(); + "cn=admins,dc=example,dc=com")).build(); final Map metadata2 = new HashMap<>(); metadata2.put("k1", "v1"); final PutRoleMappingRequest putRoleMappingRequest2 = new PutRoleMappingRequest("mapping-example-2", true, Collections.singletonList( - "monitoring"), rules2, metadata2, RefreshPolicy.NONE); + "monitoring"), rules2, metadata2, RefreshPolicy.NONE); final PutRoleMappingResponse putRoleMappingResponse2 = client.security().putRoleMapping(putRoleMappingRequest2, - RequestOptions.DEFAULT); + RequestOptions.DEFAULT); boolean isCreated2 = putRoleMappingResponse2.isCreated(); assertTrue(isCreated2); @@ -228,7 +228,7 @@ public void testGetRoleMappings() throws Exception { assertThat(mappings.size(), is(2)); for (ExpressionRoleMapping roleMapping : mappings) { assertThat(roleMapping.isEnabled(), is(true)); - assertThat(roleMapping.getName(), isIn(new String[] { "mapping-example-1", "mapping-example-2" })); + assertThat(roleMapping.getName(), isIn(new String[]{"mapping-example-1", "mapping-example-2"})); if (roleMapping.getName().equals("mapping-example-1")) { assertThat(roleMapping.getMetadata(), equalTo(Collections.emptyMap())); assertThat(roleMapping.getExpression(), equalTo(rules1)); @@ -251,7 +251,7 @@ public void testGetRoleMappings() throws Exception { assertThat(mappings.size(), is(2)); for (ExpressionRoleMapping roleMapping : mappings) { assertThat(roleMapping.isEnabled(), is(true)); - assertThat(roleMapping.getName(), isIn(new String[] { "mapping-example-1", "mapping-example-2" })); + assertThat(roleMapping.getName(), isIn(new String[]{"mapping-example-1", "mapping-example-2"})); if (roleMapping.getName().equals("mapping-example-1")) { assertThat(roleMapping.getMetadata(), equalTo(Collections.emptyMap())); assertThat(roleMapping.getExpression(), equalTo(rules1)); @@ -559,7 +559,7 @@ public void testDeleteRoleMapping() throws Exception { // Create role mappings final RoleMapperExpression rules = FieldRoleMapperExpression.ofUsername("*"); final PutRoleMappingRequest request = new PutRoleMappingRequest("mapping-example", true, Collections.singletonList("superuser"), - rules, null, RefreshPolicy.NONE); + rules, null, RefreshPolicy.NONE); final PutRoleMappingResponse response = client.security().putRoleMapping(request, RequestOptions.DEFAULT); boolean isCreated = response.isCreated(); assertTrue(isCreated); @@ -817,7 +817,7 @@ 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/TBD + // See https://github.com/elastic/elasticsearch/issues/35115 } } } From d5326edbbffbbfd9681877679d0b9b39b0366648 Mon Sep 17 00:00:00 2001 From: Tim Vernum Date: Thu, 1 Nov 2018 12:53:17 +1100 Subject: [PATCH 3/4] Address feedback --- .../security/InvalidateTokenRequest.java | 7 ++ .../security/invalidate-token.asciidoc | 67 +++++-------------- .../high-level/supported-apis.asciidoc | 2 +- 3 files changed, 25 insertions(+), 51 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 733001ce5edf6..ab4311c47efe6 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 @@ -37,6 +37,13 @@ public final class InvalidateTokenRequest implements Validatable, ToXContentObje private final String refreshToken; 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"); + } + } else if (Strings.isNullOrEmpty(refreshToken) == false) { + throw new IllegalArgumentException("Cannot supplt both access-token and refresh-token"); + } this.accessToken = accessToken; this.refreshToken = refreshToken; } diff --git a/docs/java-rest/high-level/security/invalidate-token.asciidoc b/docs/java-rest/high-level/security/invalidate-token.asciidoc index 9cdb066b7d7a6..ecb3fedb56f0a 100644 --- a/docs/java-rest/high-level/security/invalidate-token.asciidoc +++ b/docs/java-rest/high-level/security/invalidate-token.asciidoc @@ -1,72 +1,39 @@ -[[java-rest-high-security-invalidate-token]] +-- +:api: invalidate-token +:request: InvalidateTokenRequest +:response: InvalidateTokenResponse +-- + +[id="{upid}-{api}"] === Invalidate Token API -[[java-rest-high-security-invalidate-token-request]] -==== Request -The `InvalidateTokenRequest` supports invalidating either an _access token_ or a _refresh token_ +[id="{upid}-{api}-request"] +==== Invalidate Token Request +The +{request}+ supports invalidating either an _access token_ or a _refresh token_ ===== Access Token - ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- -include-tagged::{doc-tests}/SecurityDocumentationIT.java[invalidate-access-token-request] +include-tagged::{doc-tests-file}[invalidate-access-token-request] -------------------------------------------------- ===== Refresh Token ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- -include-tagged::{doc-tests}/SecurityDocumentationIT.java[invalidate-refresh-token-request] +include-tagged::{doc-tests-file}[invalidate-refresh-token-request] -------------------------------------------------- -[[java-rest-high-security-invalidate-token-execution]] -==== Execution - -Invalidating a OAuth2 security token can be performed by passing the appropriate request to the - `security().invalidateToken()` method: - -["source","java",subs="attributes,callouts,macros"] --------------------------------------------------- -include-tagged::{doc-tests}/SecurityDocumentationIT.java[invalidate-token-execute] --------------------------------------------------- +include::../execution.asciidoc[] -[[java-rest-high-security-invalidate-token-response]] -==== Response +[id="{upid}-{api}-response"] +==== Invalidate Token Response -The returned `InvalidateTokenResponse` contains a single property: +The returned +{response}+ contains a single property: `created`:: Whether the invalidation record was newly created (`true`), or if the token had already been invalidated (`false`). ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- -include-tagged::{doc-tests}/SecurityDocumentationIT.java[invalidate-token-response] --------------------------------------------------- - -[[java-rest-high-security-invalidate-token-async]] -==== Asynchronous Execution - -This request can be executed asynchronously using the `security().invalidateTokenAsync()` -method: - -["source","java",subs="attributes,callouts,macros"] --------------------------------------------------- -include-tagged::{doc-tests}/SecurityDocumentationIT.java[invalidate-token-execute-async] --------------------------------------------------- -<1> The `InvalidateTokenRequest` to execute and the `ActionListener` to use when -the execution completes - -The asynchronous method does not block and returns immediately. Once the request -has completed the `ActionListener` is called back using the `onResponse` method -if the execution successfully completed or using the `onFailure` method if -it failed. - -A typical listener for a `InvalidateTokenResponse` looks like: - -["source","java",subs="attributes,callouts,macros"] --------------------------------------------------- -include-tagged::{doc-tests}/SecurityDocumentationIT.java[invalidate-token-execute-listener] +include-tagged::{doc-tests-file}[{api}-response] -------------------------------------------------- -<1> Called when the execution is successfully completed. The response is -provided as an argument -<2> Called in case of failure. The raised exception is provided as an argument - diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc index b5707ea8b17fb..0f46c5dcd98d9 100644 --- a/docs/java-rest/high-level/supported-apis.asciidoc +++ b/docs/java-rest/high-level/supported-apis.asciidoc @@ -333,7 +333,7 @@ The Java High Level REST Client supports the following Security APIs: * <> * <> * <> -* <> +* <<{upid}-invalidate-token>> include::security/put-user.asciidoc[] include::security/enable-user.asciidoc[] From 83f3e108ffff607890525de6007b37da3eba846f Mon Sep 17 00:00:00 2001 From: Tim Vernum Date: Thu, 1 Nov 2018 13:11:12 +1100 Subject: [PATCH 4/4] Fix typo in error message --- .../elasticsearch/client/security/InvalidateTokenRequest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ab4311c47efe6..4e767335ffdbd 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 @@ -42,7 +42,7 @@ public final class InvalidateTokenRequest implements Validatable, ToXContentObje throw new IllegalArgumentException("Either access-token or refresh-token is required"); } } else if (Strings.isNullOrEmpty(refreshToken) == false) { - throw new IllegalArgumentException("Cannot supplt both access-token and refresh-token"); + throw new IllegalArgumentException("Cannot supply both access-token and refresh-token"); } this.accessToken = accessToken; this.refreshToken = refreshToken;