From 9446b1963f2431556d78c9b3186fe7d40815cb2e Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Fri, 7 Sep 2018 19:21:04 +0300 Subject: [PATCH 01/18] No tests!!! No. --- .../client/EmptyBodyRequest.java | 34 ++++ .../elasticsearch/client/SecurityClient.java | 14 +- .../client/security/AuthenticateRequest.java | 39 ++++ .../client/security/AuthenticateResponse.java | 59 ++++++ .../elasticsearch/client/security/User.java | 174 ++++++++++++++++++ 5 files changed, 318 insertions(+), 2 deletions(-) create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/EmptyBodyRequest.java create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateRequest.java create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/security/User.java diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/EmptyBodyRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/EmptyBodyRequest.java new file mode 100644 index 0000000000000..0ed72b476378f --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/EmptyBodyRequest.java @@ -0,0 +1,34 @@ +/* + * 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; + +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; + +import java.io.IOException; + +public class EmptyBodyRequest implements Validatable, ToXContentObject { + + @Override + public final XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + return builder.startObject().endObject(); + } + +} 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 19f56fbd1ec93..53a124c4f8b67 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 @@ -20,11 +20,11 @@ package org.elasticsearch.client; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.client.security.AuthenticateRequest; +import org.elasticsearch.client.security.AuthenticateResponse; import org.elasticsearch.client.security.PutUserRequest; import org.elasticsearch.client.security.PutUserResponse; - import java.io.IOException; - import static java.util.Collections.emptySet; /** @@ -66,4 +66,14 @@ public void putUserAsync(PutUserRequest request, RequestOptions options, ActionL restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::putUser, options, PutUserResponse::fromXContent, listener, emptySet()); } + + public AuthenticateResponse authenticate(RequestOptions options) throws IOException { + return restHighLevelClient.performRequestAndParseEntity(AuthenticateRequest.INSTANCE, AuthenticateRequest::getRequest, options, + AuthenticateResponse::fromXContent, emptySet()); + } + + public void authenticateAsync(RequestOptions options, ActionListener listener) { + restHighLevelClient.performRequestAsyncAndParseEntity(AuthenticateRequest.INSTANCE, AuthenticateRequest::getRequest, options, + AuthenticateResponse::fromXContent, listener, emptySet()); + } } diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateRequest.java new file mode 100644 index 0000000000000..9a6f0674a3854 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateRequest.java @@ -0,0 +1,39 @@ +/* + * 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.apache.http.client.methods.HttpGet; +import org.elasticsearch.client.EmptyBodyRequest; +import org.elasticsearch.client.Request; + +public class AuthenticateRequest extends EmptyBodyRequest { + + public static final AuthenticateRequest INSTANCE = new AuthenticateRequest(); + private final Request request; + + private AuthenticateRequest() { + request = new Request(HttpGet.METHOD_NAME, "/_xpack/security/_authenticate"); + } + + public Request getRequest() { + return request; + } + +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java new file mode 100644 index 0000000000000..d6a87dd385bf8 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java @@ -0,0 +1,59 @@ +/* + * 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.xcontent.XContentParser; + +import java.io.IOException; +import java.util.Objects; + +public class AuthenticateResponse { + + private final User user; + + public AuthenticateResponse(User user) { + this.user = user; + } + + public User getUser() { + return user; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final AuthenticateResponse that = (AuthenticateResponse) o; + return user.equals(that.user); + } + + @Override + public int hashCode() { + return Objects.hash(user); + } + + public static AuthenticateResponse fromXContent(XContentParser parser) throws IOException { + return new AuthenticateResponse(User.fromXContent(parser)); + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/User.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/User.java new file mode 100644 index 0000000000000..ec322bc305bc1 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/User.java @@ -0,0 +1,174 @@ +/* + * 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.Nullable; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.XContentParser; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg; +import static org.elasticsearch.common.xcontent.ObjectParser.fromList; + +public class User { + + static final ParseField USERNAME = new ParseField("username"); + static final ParseField ROLES = new ParseField("roles"); + static final ParseField FULL_NAME = new ParseField("full_name"); + static final ParseField EMAIL = new ParseField("email"); + static final ParseField METADATA = new ParseField("metadata"); + static final ParseField ENABLED = new ParseField("enabled"); + + @SuppressWarnings("unchecked") + private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>("client_security_user", + a -> new User((String) a[0], (String[]) a[1], (String) a[2], (String) a[3], (Map) a[4], (Boolean) a[5])); + static { + PARSER.declareString(constructorArg(), USERNAME); + PARSER.declareStringArray(fromList(String.class, optionalConstructorArg()), ROLES); + PARSER.declareStringOrNull(optionalConstructorArg(), FULL_NAME); + PARSER.declareStringOrNull(optionalConstructorArg(), EMAIL); + PARSER.>declareObject(optionalConstructorArg(), (parser, c) -> parser.map(), METADATA); + PARSER.declareBoolean(constructorArg(), ENABLED); + } + + private final String username; + private final String[] roles; + private final Map metadata; + private final boolean enabled; + + @Nullable private final String fullName; + @Nullable private final String email; + + private User(String username, @Nullable String[] roles, @Nullable String fullName, @Nullable String email, + @Nullable Map metadata, Boolean enabled) { + this.username = username; + this.roles = roles == null ? Strings.EMPTY_ARRAY : roles; + this.fullName = fullName; + this.email = email; + this.metadata = metadata != null ? Collections.unmodifiableMap(metadata) : Collections.emptyMap(); + this.enabled = enabled; + } + + /** + * @return The principal of this user - effectively serving as the + * unique identity of of the user. + */ + public String principal() { + return this.username; + } + + /** + * @return The roles this user is associated with. The roles are + * identified by their unique names and each represents as + * set of permissions + */ + public String[] roles() { + return this.roles; + } + + /** + * @return The metadata that is associated with this user. Can never be {@code null}. + */ + public Map metadata() { + return metadata; + } + + /** + * @return The full name of this user. May be {@code null}. + */ + public @Nullable String fullName() { + return fullName; + } + + /** + * @return The email of this user. May be {@code null}. + */ + public @Nullable String email() { + return email; + } + + /** + * @return whether the user is enabled or not + */ + public boolean enabled() { + return enabled; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("User[username=").append(username); + sb.append(",roles=[").append(Strings.arrayToCommaDelimitedString(roles)).append("]"); + sb.append(",fullName=").append(fullName); + sb.append(",email=").append(email); + sb.append(",metadata="); + sb.append(metadata); + sb.append("]"); + return sb.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o instanceof User == false) { + return false; + } + + final User user = (User) o; + + if (!username.equals(user.username)) { + return false; + } + if (!Arrays.equals(roles, user.roles)) { + return false; + } + if (!metadata.equals(user.metadata)) { + return false; + } + if (fullName != null ? !fullName.equals(user.fullName) : user.fullName != null) { + return false; + } + return !(email != null ? !email.equals(user.email) : user.email != null); + } + + @Override + public int hashCode() { + int result = username.hashCode(); + result = 31 * result + Arrays.hashCode(roles); + result = 31 * result + metadata.hashCode(); + result = 31 * result + (fullName != null ? fullName.hashCode() : 0); + result = 31 * result + (email != null ? email.hashCode() : 0); + return result; + } + + public static User fromXContent(XContentParser parser) throws IOException { + return PARSER.parse(parser, null); + } + +} From 52fe8babea64bee72d86123a605d45e237a2c88a Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Sun, 9 Sep 2018 18:46:52 +0300 Subject: [PATCH 02/18] Authenticate is done?? --- .../elasticsearch/client/SecurityClient.java | 37 +++++++++--- .../client/security/EmptyResponse.java | 2 +- .../SecurityDocumentationIT.java | 53 ++++++++++++++++++ .../high-level/security/authenticate.asciidoc | 56 +++++++++++++++++++ 4 files changed, 139 insertions(+), 9 deletions(-) create mode 100644 docs/java-rest/high-level/security/authenticate.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 5c67b0583e459..5e8ea808225ee 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 @@ -71,14 +71,6 @@ public void putUserAsync(PutUserRequest request, RequestOptions options, ActionL PutUserResponse::fromXContent, listener, emptySet()); } - public AuthenticateResponse authenticate(RequestOptions options) throws IOException { - return restHighLevelClient.performRequestAndParseEntity(AuthenticateRequest.INSTANCE, AuthenticateRequest::getRequest, options, - AuthenticateResponse::fromXContent, emptySet()); - } - - public void authenticateAsync(RequestOptions options, ActionListener listener) { - restHighLevelClient.performRequestAsyncAndParseEntity(AuthenticateRequest.INSTANCE, AuthenticateRequest::getRequest, options, - AuthenticateResponse::fromXContent, listener, emptySet()); /** * Enable a native realm or built-in user synchronously. * See @@ -134,4 +126,33 @@ public void disableUserAsync(DisableUserRequest request, RequestOptions options, restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::disableUser, options, EmptyResponse::fromXContent, listener, emptySet()); } + + /** + * Authenticate the current user (pertaining to request headers) + * and return all the information about the authenticated user. + * See + * the docs for more. + * + * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @return the responsee from the authenticate user call + */ + public AuthenticateResponse authenticate(RequestOptions options) throws IOException { + return restHighLevelClient.performRequestAndParseEntity(AuthenticateRequest.INSTANCE, AuthenticateRequest::getRequest, options, + AuthenticateResponse::fromXContent, emptySet()); + } + + /** + * Authenticate the current user (pertaining to request headers) asynchronously + * and return all the information about the authenticated user. + * See + * the docs for more. + * + * @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 authenticateAsync(RequestOptions options, ActionListener listener) { + restHighLevelClient.performRequestAsyncAndParseEntity(AuthenticateRequest.INSTANCE, AuthenticateRequest::getRequest, options, + AuthenticateResponse::fromXContent, listener, emptySet()); + } + } diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/EmptyResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/EmptyResponse.java index 62fea88e52356..004f1b2657c9a 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/EmptyResponse.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/EmptyResponse.java @@ -25,7 +25,7 @@ import java.io.IOException; /** - * Response for a request which simply returns an empty object. + * Response for a request which simply returns an empty object if successful. */ public final class EmptyResponse { 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 103b031fc0e03..f6b6a734b55d6 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 @@ -24,17 +24,23 @@ import org.elasticsearch.client.ESRestHighLevelClientTestCase; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.client.security.AuthenticateResponse; import org.elasticsearch.client.security.DisableUserRequest; import org.elasticsearch.client.security.EnableUserRequest; import org.elasticsearch.client.security.PutUserRequest; import org.elasticsearch.client.security.PutUserResponse; import org.elasticsearch.client.security.RefreshPolicy; +import org.elasticsearch.client.security.User; import org.elasticsearch.client.security.EmptyResponse; import java.util.Collections; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.nullValue; + public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase { public void testPutUser() throws Exception { @@ -173,4 +179,51 @@ public void onFailure(Exception e) { assertTrue(latch.await(30L, TimeUnit.SECONDS)); } } + + public void testAuthenticate() throws Exception { + RestHighLevelClient client = highLevelClient(); + + { + //tag::authenticate-execute + AuthenticateResponse response = client.security().authenticate(RequestOptions.DEFAULT); + //end::authenticate-execute + + //tag::authenticate-response + User user = response.getUser(); // <1> + //end::authenticate-response + + assertThat(user.principal(), is("test_user")); + assertThat(user.roles(), equalTo(new String[] {"superuser"})); + assertThat(user.fullName(), nullValue()); + assertThat(user.email(), nullValue()); + assertThat(user.metadata().isEmpty(), is(true)); + assertThat(user.enabled(), is(true)); + } + + { + // tag::authenticate-execute-listener + ActionListener listener = new ActionListener() { + @Override + public void onResponse(AuthenticateResponse response) { + // <1> + } + + @Override + public void onFailure(Exception e) { + // <2> + } + }; + // end::authenticate-execute-listener + + // Replace the empty listener by a blocking listener in test + final CountDownLatch latch = new CountDownLatch(1); + listener = new LatchedActionListener<>(listener, latch); + + // tag::authenticate-execute-async + client.security().authenticateAsync(RequestOptions.DEFAULT, listener); // <1> + // end::authenticate-execute-async + + assertTrue(latch.await(30L, TimeUnit.SECONDS)); + } + } } diff --git a/docs/java-rest/high-level/security/authenticate.asciidoc b/docs/java-rest/high-level/security/authenticate.asciidoc new file mode 100644 index 0000000000000..630020c008ba6 --- /dev/null +++ b/docs/java-rest/high-level/security/authenticate.asciidoc @@ -0,0 +1,56 @@ +[[java-rest-high-security-authenticate]] +=== Authenticate API + +[[java-rest-high-security-authenticate-execution]] +==== Execution + +To authenticate and retrieve information about a user can be performed +using the `security().authenticate()` method: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/SecurityDocumentationIT.java[authenticate-execute] +-------------------------------------------------- + +This method does not require a request object. + +[[java-rest-high-security-authenticate-response]] +==== Response + +The returned `AuthenticateResponse` contains a single field, `user`. This field +, accessed with `getUser`, contains all the information about this +authenticated user. + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/SecurityDocumentationIT.java[authenticate-response] +-------------------------------------------------- +<1> `getUser` retrieves the `User` instance containing the information. + +[[java-rest-high-security-authenticate-async]] +==== Asynchronous Execution + +This request can also be executed asynchronously: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/SecurityDocumentationIT.java[authenticate-execute-async] +-------------------------------------------------- +<1> The `ActionListener` to use when the execution completes. This method does +not require a request object. + +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 completed successfully or using the `onFailure` method if +it failed. + + A typical listener for a `AuthenticateResponse` looks like: + + ["source","java",subs="attributes,callouts,macros"] + -------------------------------------------------- + include-tagged::{doc-tests}/SecurityDocumentationIT.java[authenticate-execute-listener] + -------------------------------------------------- + <1> Called when the execution completed successfully. The response is + provided as an argument. + <2> Called in case of a failure. The exception is provided as an argument. + From fdde4c342bd47bd0b6d7e643fde037390266f66a Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Sun, 9 Sep 2018 20:20:15 +0300 Subject: [PATCH 03/18] RestHighLevelClient tests --- .../org/elasticsearch/client/SecurityClient.java | 1 + .../client/security/EmptyResponse.java | 2 +- .../client/RestHighLevelClientTests.java | 16 +++++++++++----- 3 files changed, 13 insertions(+), 6 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 5e8ea808225ee..8f614e0921a96 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 @@ -29,6 +29,7 @@ import org.elasticsearch.client.security.EmptyResponse; import java.io.IOException; + import static java.util.Collections.emptySet; /** diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/EmptyResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/EmptyResponse.java index 004f1b2657c9a..62fea88e52356 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/EmptyResponse.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/EmptyResponse.java @@ -25,7 +25,7 @@ import java.io.IOException; /** - * Response for a request which simply returns an empty object if successful. + * Response for a request which simply returns an empty object. */ public final class EmptyResponse { diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java index b6562cd44cd55..5b4c26585340f 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java @@ -722,10 +722,16 @@ public void testApiNamingConventions() throws Exception { methods.containsKey(apiName.substring(0, apiName.length() - 6))); assertThat(method.getReturnType(), equalTo(Void.TYPE)); assertEquals(0, method.getExceptionTypes().length); - assertEquals(3, method.getParameterTypes().length); - assertThat(method.getParameterTypes()[0].getSimpleName(), endsWith("Request")); - assertThat(method.getParameterTypes()[1], equalTo(RequestOptions.class)); - assertThat(method.getParameterTypes()[2], equalTo(ActionListener.class)); + if (apiName.equals("security.authenticate_async")) { + assertEquals(2, method.getParameterTypes().length); + assertThat(method.getParameterTypes()[0], equalTo(RequestOptions.class)); + assertThat(method.getParameterTypes()[1], equalTo(ActionListener.class)); + } else { + assertEquals(3, method.getParameterTypes().length); + assertThat(method.getParameterTypes()[0].getSimpleName(), endsWith("Request")); + assertThat(method.getParameterTypes()[1], equalTo(RequestOptions.class)); + assertThat(method.getParameterTypes()[2], equalTo(ActionListener.class)); + } } else { //A few methods return a boolean rather than a response object if (apiName.equals("ping") || apiName.contains("exist")) { @@ -736,7 +742,7 @@ public void testApiNamingConventions() throws Exception { assertEquals(1, method.getExceptionTypes().length); //a few methods don't accept a request object as argument - if (apiName.equals("ping") || apiName.equals("info")) { + if (apiName.equals("ping") || apiName.equals("info") || apiName.equals("security.authenticate")) { assertEquals(1, method.getParameterTypes().length); assertThat(method.getParameterTypes()[0], equalTo(RequestOptions.class)); } else { From 090eef6c79caef061001921d039c89c48d470ac2 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Sun, 9 Sep 2018 22:10:55 +0300 Subject: [PATCH 04/18] LOL --- .../java/org/elasticsearch/client/security/User.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/User.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/User.java index ec322bc305bc1..77c26a6d7a9dd 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/User.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/User.java @@ -29,11 +29,14 @@ import java.util.Arrays; import java.util.Collections; import java.util.Map; +import java.util.List; import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg; -import static org.elasticsearch.common.xcontent.ObjectParser.fromList; +/** + * An authenticated user + */ public class User { static final ParseField USERNAME = new ParseField("username"); @@ -45,10 +48,11 @@ public class User { @SuppressWarnings("unchecked") private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>("client_security_user", - a -> new User((String) a[0], (String[]) a[1], (String) a[2], (String) a[3], (Map) a[4], (Boolean) a[5])); + a -> new User((String) a[0], ((List) a[1]).toArray(new String[0]), (String) a[2], (String) a[3], + (Map) a[4], (Boolean) a[5])); static { PARSER.declareString(constructorArg(), USERNAME); - PARSER.declareStringArray(fromList(String.class, optionalConstructorArg()), ROLES); + PARSER.declareStringArray(optionalConstructorArg(), ROLES); PARSER.declareStringOrNull(optionalConstructorArg(), FULL_NAME); PARSER.declareStringOrNull(optionalConstructorArg(), EMAIL); PARSER.>declareObject(optionalConstructorArg(), (parser, c) -> parser.map(), METADATA); From d2977ce5f61fe25597540f1e385740bb10b7be8d Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Fri, 19 Oct 2018 18:49:12 +0300 Subject: [PATCH 05/18] Addressed some feedback --- .../client/EmptyBodyRequest.java | 34 ------------------- .../elasticsearch/client/SecurityClient.java | 6 ++-- .../client/security/AuthenticateRequest.java | 17 ++++++++-- .../client/security/AuthenticateResponse.java | 7 +++- .../elasticsearch/client/security/User.java | 2 +- 5 files changed, 24 insertions(+), 42 deletions(-) delete mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/EmptyBodyRequest.java diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/EmptyBodyRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/EmptyBodyRequest.java deleted file mode 100644 index 0ed72b476378f..0000000000000 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/EmptyBodyRequest.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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; - -import org.elasticsearch.common.xcontent.ToXContentObject; -import org.elasticsearch.common.xcontent.XContentBuilder; - -import java.io.IOException; - -public class EmptyBodyRequest implements Validatable, ToXContentObject { - - @Override - public final XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - return builder.startObject().endObject(); - } - -} 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 b6a4729e0dfed..f6145d94b905f 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 @@ -170,8 +170,7 @@ public void disableUserAsync(DisableUserRequest request, RequestOptions options, } /** - * Authenticate the current user (pertaining to request headers) - * and return all the information about the authenticated user. + * Authenticate the current user and return all the information about the authenticated user. * See * the docs for more. * @@ -184,8 +183,7 @@ public AuthenticateResponse authenticate(RequestOptions options) throws IOExcept } /** - * Authenticate the current user (pertaining to request headers) asynchronously - * and return all the information about the authenticated user. + * Authenticate the current user asynchronously and return all the information about the authenticated user. * See * the docs for more. * diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateRequest.java index 9a6f0674a3854..6cf94ea8204b6 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateRequest.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateRequest.java @@ -20,10 +20,18 @@ package org.elasticsearch.client.security; import org.apache.http.client.methods.HttpGet; -import org.elasticsearch.client.EmptyBodyRequest; import org.elasticsearch.client.Request; +import org.elasticsearch.client.Validatable; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; -public class AuthenticateRequest extends EmptyBodyRequest { +import java.io.IOException; + +/** + * Empty request object required to make the authenticate call. The authenticate call + * retrieves metadata about the authenticated user. + */ +public final class AuthenticateRequest implements Validatable, ToXContentObject { public static final AuthenticateRequest INSTANCE = new AuthenticateRequest(); private final Request request; @@ -36,4 +44,9 @@ public Request getRequest() { return request; } + @Override + public final XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + return builder.startObject().endObject(); + } + } diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java index d6a87dd385bf8..f48b15c21a283 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java @@ -24,7 +24,12 @@ import java.io.IOException; import java.util.Objects; -public class AuthenticateResponse { +/** + * The response for the authenticate call. The user object is the only field + * of this response. It contains all user metadata which Elasticsearch uses + * to map roles, etc. + */ +public final class AuthenticateResponse { private final User user; diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/User.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/User.java index 77c26a6d7a9dd..d4f7699d734f4 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/User.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/User.java @@ -37,7 +37,7 @@ /** * An authenticated user */ -public class User { +public final class User { static final ParseField USERNAME = new ParseField("username"); static final ParseField ROLES = new ParseField("roles"); From a390c422a30d6b3e27e69588dd8d8bfd5e570ec9 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Sun, 21 Oct 2018 22:59:05 +0300 Subject: [PATCH 06/18] User reformat --- .../client/security/AuthenticateResponse.java | 1 + .../client/security/{ => support}/User.java | 54 +++++++++---------- .../SecurityDocumentationIT.java | 2 +- 3 files changed, 29 insertions(+), 28 deletions(-) rename client/rest-high-level/src/main/java/org/elasticsearch/client/security/{ => support}/User.java (82%) diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java index f48b15c21a283..8d45c4b9662b1 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java @@ -19,6 +19,7 @@ package org.elasticsearch.client.security; +import org.elasticsearch.client.security.support.User; import org.elasticsearch.common.xcontent.XContentParser; import java.io.IOException; diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/User.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/support/User.java similarity index 82% rename from client/rest-high-level/src/main/java/org/elasticsearch/client/security/User.java rename to client/rest-high-level/src/main/java/org/elasticsearch/client/security/support/User.java index d4f7699d734f4..452bf0aedc2d2 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/User.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/support/User.java @@ -17,7 +17,7 @@ * under the License. */ -package org.elasticsearch.client.security; +package org.elasticsearch.client.security.support; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.ParseField; @@ -32,7 +32,6 @@ import java.util.List; import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; -import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg; /** * An authenticated user @@ -41,45 +40,47 @@ public final class User { static final ParseField USERNAME = new ParseField("username"); static final ParseField ROLES = new ParseField("roles"); - static final ParseField FULL_NAME = new ParseField("full_name"); - static final ParseField EMAIL = new ParseField("email"); static final ParseField METADATA = new ParseField("metadata"); static final ParseField ENABLED = new ParseField("enabled"); + static final ParseField FULL_NAME = new ParseField("full_name"); + static final ParseField EMAIL = new ParseField("email"); @SuppressWarnings("unchecked") private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>("client_security_user", - a -> new User((String) a[0], ((List) a[1]).toArray(new String[0]), (String) a[2], (String) a[3], - (Map) a[4], (Boolean) a[5])); + a -> new User((String) a[0], ((List) a[1]).toArray(new String[0]), (Map) a[4], (Boolean) a[5], + (String) a[2], (String) a[3])); static { PARSER.declareString(constructorArg(), USERNAME); - PARSER.declareStringArray(optionalConstructorArg(), ROLES); - PARSER.declareStringOrNull(optionalConstructorArg(), FULL_NAME); - PARSER.declareStringOrNull(optionalConstructorArg(), EMAIL); - PARSER.>declareObject(optionalConstructorArg(), (parser, c) -> parser.map(), METADATA); + PARSER.declareStringArray(constructorArg(), ROLES); + PARSER.>declareObject(constructorArg(), (parser, c) -> parser.map(), METADATA); PARSER.declareBoolean(constructorArg(), ENABLED); + PARSER.declareStringOrNull(constructorArg(), FULL_NAME); + PARSER.declareStringOrNull(constructorArg(), EMAIL); } private final String username; private final String[] roles; private final Map metadata; private final boolean enabled; - @Nullable private final String fullName; @Nullable private final String email; - private User(String username, @Nullable String[] roles, @Nullable String fullName, @Nullable String email, - @Nullable Map metadata, Boolean enabled) { + private User(String username, String[] roles, Map metadata, boolean enabled, + @Nullable String fullName, @Nullable String email) { + assert username != null; + assert roles != null; + assert metadata != null; this.username = username; - this.roles = roles == null ? Strings.EMPTY_ARRAY : roles; + this.roles = roles; + this.metadata = Collections.unmodifiableMap(metadata); + this.enabled = enabled; this.fullName = fullName; this.email = email; - this.metadata = metadata != null ? Collections.unmodifiableMap(metadata) : Collections.emptyMap(); - this.enabled = enabled; } /** * @return The principal of this user - effectively serving as the - * unique identity of of the user. + * unique identity of the user. Can never be {@code null}. */ public String principal() { return this.username; @@ -88,7 +89,7 @@ public String principal() { /** * @return The roles this user is associated with. The roles are * identified by their unique names and each represents as - * set of permissions + * set of permissions. Can never be {@code null}. */ public String[] roles() { return this.roles; @@ -101,6 +102,13 @@ public Map metadata() { return metadata; } + /** + * @return whether the user is enabled or not + */ + public boolean enabled() { + return enabled; + } + /** * @return The full name of this user. May be {@code null}. */ @@ -115,22 +123,14 @@ public Map metadata() { return email; } - /** - * @return whether the user is enabled or not - */ - public boolean enabled() { - return enabled; - } - @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("User[username=").append(username); sb.append(",roles=[").append(Strings.arrayToCommaDelimitedString(roles)).append("]"); + sb.append(",metadata=").append(metadata); sb.append(",fullName=").append(fullName); sb.append(",email=").append(email); - sb.append(",metadata="); - sb.append(metadata); sb.append("]"); return sb.toString(); } 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 c9793aff58a5b..2a31e25e2f999 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 @@ -37,11 +37,11 @@ import org.elasticsearch.client.security.PutUserRequest; import org.elasticsearch.client.security.PutUserResponse; import org.elasticsearch.client.security.RefreshPolicy; -import org.elasticsearch.client.security.User; import org.elasticsearch.client.security.EmptyResponse; import org.elasticsearch.client.security.support.expressiondsl.RoleMapperExpression; import org.elasticsearch.client.security.support.expressiondsl.fields.FieldRoleMapperExpression; import org.elasticsearch.client.security.support.CertificateInfo; +import org.elasticsearch.client.security.support.User; import org.elasticsearch.client.security.support.expressiondsl.expressions.AnyRoleMapperExpression; import org.hamcrest.Matchers; From 4d69fec36c5079df70a3fa4060c91abc2f2ac06e Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Sun, 21 Oct 2018 23:29:35 +0300 Subject: [PATCH 07/18] move User to security.user --- .../org/elasticsearch/client/security/AuthenticateResponse.java | 2 +- .../elasticsearch/client/security/{support => user}/User.java | 2 +- .../client/documentation/SecurityDocumentationIT.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename client/rest-high-level/src/main/java/org/elasticsearch/client/security/{support => user}/User.java (99%) diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java index 8d45c4b9662b1..70a965df4ca3d 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java @@ -19,7 +19,7 @@ package org.elasticsearch.client.security; -import org.elasticsearch.client.security.support.User; +import org.elasticsearch.client.security.user.User; import org.elasticsearch.common.xcontent.XContentParser; import java.io.IOException; diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/support/User.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/User.java similarity index 99% rename from client/rest-high-level/src/main/java/org/elasticsearch/client/security/support/User.java rename to client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/User.java index 452bf0aedc2d2..5534e80c1beb5 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/support/User.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/User.java @@ -17,7 +17,7 @@ * under the License. */ -package org.elasticsearch.client.security.support; +package org.elasticsearch.client.security.user; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.ParseField; 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 cae4e2144021a..6687486228d42 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 @@ -46,8 +46,8 @@ import org.elasticsearch.client.security.EmptyResponse; import org.elasticsearch.client.security.support.expressiondsl.RoleMapperExpression; import org.elasticsearch.client.security.support.expressiondsl.fields.FieldRoleMapperExpression; +import org.elasticsearch.client.security.user.User; import org.elasticsearch.client.security.support.CertificateInfo; -import org.elasticsearch.client.security.support.User; import org.elasticsearch.client.security.support.expressiondsl.expressions.AnyRoleMapperExpression; import org.elasticsearch.client.security.support.expressiondsl.fields.FieldRoleMapperExpression; import org.elasticsearch.common.Strings; From 54a047954d7c2a08526968dd4d87ca147ac1b8ac Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Mon, 22 Oct 2018 16:58:32 +0300 Subject: [PATCH 08/18] Move enabled out of User --- .../client/security/AuthenticateResponse.java | 78 +++++++++++++++++-- .../client/security/user/User.java | 42 +--------- .../SecurityDocumentationIT.java | 6 +- .../security/AuthenticateResponseTests.java | 65 ++++++++++++++++ 4 files changed, 139 insertions(+), 52 deletions(-) create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/security/AuthenticateResponseTests.java diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java index 70a965df4ca3d..6f8a3c6f31015 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java @@ -20,28 +20,73 @@ package org.elasticsearch.client.security; import org.elasticsearch.client.security.user.User; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import java.io.IOException; +import java.util.List; +import java.util.Map; import java.util.Objects; +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg; + /** - * The response for the authenticate call. The user object is the only field - * of this response. It contains all user metadata which Elasticsearch uses - * to map roles, etc. + * The response for the authenticate call. The response contains two fields: a + * user field and a boolean flag signaling if the user is enabled or not. The + * user object contains all user metadata which Elasticsearch uses to map roles, + * etc. */ -public final class AuthenticateResponse { +public final class AuthenticateResponse implements ToXContentObject { + + static final ParseField USERNAME = new ParseField("username"); + static final ParseField ROLES = new ParseField("roles"); + static final ParseField METADATA = new ParseField("metadata"); + static final ParseField FULL_NAME = new ParseField("full_name"); + static final ParseField EMAIL = new ParseField("email"); + static final ParseField ENABLED = new ParseField("enabled"); + + @SuppressWarnings("unchecked") + private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( + "client_security_authenticate_response", + a -> new AuthenticateResponse(new User((String) a[0], ((List) a[1]).toArray(new String[0]), (Map) a[2], + (String) a[3], (String) a[4]), (Boolean) a[5])); + static { + PARSER.declareString(constructorArg(), USERNAME); + PARSER.declareStringArray(constructorArg(), ROLES); + PARSER.>declareObject(constructorArg(), (parser, c) -> parser.map(), METADATA); + PARSER.declareStringOrNull(optionalConstructorArg(), FULL_NAME); + PARSER.declareStringOrNull(optionalConstructorArg(), EMAIL); + PARSER.declareBoolean(constructorArg(), ENABLED); + } private final User user; + private final boolean enabled; - public AuthenticateResponse(User user) { + public AuthenticateResponse(User user, boolean enabled) { this.user = user; + this.enabled = enabled; } + /** + * @return The effective user. This is the authenticated user, or, when + * submitting requests on behalf of other users, it is the + * impersonated user. + */ public User getUser() { return user; } + /** + * @return whether the user is enabled or not + */ + public boolean enabled() { + return enabled; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -51,15 +96,32 @@ public boolean equals(Object o) { return false; } final AuthenticateResponse that = (AuthenticateResponse) o; - return user.equals(that.user); + return user.equals(that.user) && enabled == that.enabled; } @Override public int hashCode() { - return Objects.hash(user); + return Objects.hash(user, enabled); } public static AuthenticateResponse fromXContent(XContentParser parser) throws IOException { - return new AuthenticateResponse(User.fromXContent(parser)); + return PARSER.parse(parser, null); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field(USERNAME.getPreferredName(), user.principal()); + builder.field(ROLES.getPreferredName(), user.roles()); + builder.field(METADATA.getPreferredName(), user.metadata()); + if (user.fullName() != null) { + builder.field(FULL_NAME.getPreferredName(), user.fullName()); + } + if (user.email() != null) { + builder.field(EMAIL.getPreferredName(), user.email()); + } + builder.field(ENABLED.getPreferredName(), enabled); + builder.endObject(); + return builder; } } diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/User.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/User.java index 5534e80c1beb5..d47e971b09dc0 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/User.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/User.java @@ -20,60 +20,31 @@ package org.elasticsearch.client.security.user; 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.XContentParser; -import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.Map; -import java.util.List; -import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; /** * An authenticated user */ public final class User { - static final ParseField USERNAME = new ParseField("username"); - static final ParseField ROLES = new ParseField("roles"); - static final ParseField METADATA = new ParseField("metadata"); - static final ParseField ENABLED = new ParseField("enabled"); - static final ParseField FULL_NAME = new ParseField("full_name"); - static final ParseField EMAIL = new ParseField("email"); - - @SuppressWarnings("unchecked") - private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>("client_security_user", - a -> new User((String) a[0], ((List) a[1]).toArray(new String[0]), (Map) a[4], (Boolean) a[5], - (String) a[2], (String) a[3])); - static { - PARSER.declareString(constructorArg(), USERNAME); - PARSER.declareStringArray(constructorArg(), ROLES); - PARSER.>declareObject(constructorArg(), (parser, c) -> parser.map(), METADATA); - PARSER.declareBoolean(constructorArg(), ENABLED); - PARSER.declareStringOrNull(constructorArg(), FULL_NAME); - PARSER.declareStringOrNull(constructorArg(), EMAIL); - } - private final String username; private final String[] roles; private final Map metadata; - private final boolean enabled; @Nullable private final String fullName; @Nullable private final String email; - private User(String username, String[] roles, Map metadata, boolean enabled, - @Nullable String fullName, @Nullable String email) { + public User(String username, String[] roles, Map metadata, @Nullable String fullName, @Nullable String email) { assert username != null; assert roles != null; assert metadata != null; this.username = username; this.roles = roles; this.metadata = Collections.unmodifiableMap(metadata); - this.enabled = enabled; this.fullName = fullName; this.email = email; } @@ -102,13 +73,6 @@ public Map metadata() { return metadata; } - /** - * @return whether the user is enabled or not - */ - public boolean enabled() { - return enabled; - } - /** * @return The full name of this user. May be {@code null}. */ @@ -171,8 +135,4 @@ public int hashCode() { return result; } - public static User 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 6687486228d42..165b778f93588 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 @@ -43,13 +43,11 @@ import org.elasticsearch.client.security.PutUserRequest; import org.elasticsearch.client.security.PutUserResponse; import org.elasticsearch.client.security.RefreshPolicy; -import org.elasticsearch.client.security.EmptyResponse; import org.elasticsearch.client.security.support.expressiondsl.RoleMapperExpression; import org.elasticsearch.client.security.support.expressiondsl.fields.FieldRoleMapperExpression; import org.elasticsearch.client.security.user.User; import org.elasticsearch.client.security.support.CertificateInfo; import org.elasticsearch.client.security.support.expressiondsl.expressions.AnyRoleMapperExpression; -import org.elasticsearch.client.security.support.expressiondsl.fields.FieldRoleMapperExpression; import org.elasticsearch.common.Strings; import org.elasticsearch.common.xcontent.XContentBuilder; import org.hamcrest.Matchers; @@ -273,7 +271,7 @@ public void testAuthenticate() throws Exception { assertThat(user.fullName(), nullValue()); assertThat(user.email(), nullValue()); assertThat(user.metadata().isEmpty(), is(true)); - assertThat(user.enabled(), is(true)); + assertThat(response.enabled(), is(true)); } { @@ -297,6 +295,8 @@ public void onFailure(Exception e) { // tag::authenticate-execute-async client.security().authenticateAsync(RequestOptions.DEFAULT, listener); // <1> // end::authenticate-execute-async + + assertTrue(latch.await(30L, TimeUnit.SECONDS)); } } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/AuthenticateResponseTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/AuthenticateResponseTests.java new file mode 100644 index 0000000000000..6497dae38bfaf --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/AuthenticateResponseTests.java @@ -0,0 +1,65 @@ +/* + * 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.security.user.User; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.test.AbstractXContentTestCase; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public class AuthenticateResponseTests extends AbstractXContentTestCase { + + @Override + protected AuthenticateResponse createTestInstance() { + final String username = randomAlphaOfLengthBetween(1, 4); + final String[] roles = generateRandomStringArray(4, 4, false, true); + final Map metadata; + metadata = new HashMap<>(); + if (randomBoolean()) { + metadata.put("string", null); + } else { + metadata.put("string", randomAlphaOfLengthBetween(0, 4)); + } + if (randomBoolean()) { + metadata.put("string_list", null); + } else { + metadata.put("string_list", Arrays.asList(generateRandomStringArray(4, 4, false, true))); + } + final String fullName = randomFrom(random(), null, randomAlphaOfLengthBetween(0, 4)); + final String email = randomFrom(random(), null, randomAlphaOfLengthBetween(0, 4)); + final boolean enabled = randomBoolean(); + return new AuthenticateResponse(new User(username, roles, metadata, fullName, email), enabled); + } + + @Override + protected AuthenticateResponse doParseInstance(XContentParser parser) throws IOException { + return AuthenticateResponse.fromXContent(parser); + } + + @Override + protected boolean supportsUnknownFields() { + return false; + } + +} From 47dbb3aa9669bc87b9bdd7f1b05986a9bf3c4e88 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Tue, 23 Oct 2018 00:51:32 +0300 Subject: [PATCH 09/18] Completion in sight! --- .../client/security/AuthenticateRequest.java | 2 +- .../client/security/AuthenticateResponse.java | 2 +- .../client/security/user/User.java | 2 +- .../client/ESRestHighLevelClientTestCase.java | 26 ++++++ .../org/elasticsearch/client/SecurityIT.java | 89 +++++++++++++++++++ .../SecurityDocumentationIT.java | 4 +- 6 files changed, 120 insertions(+), 5 deletions(-) create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityIT.java diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateRequest.java index 6cf94ea8204b6..7a322963fcfce 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateRequest.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateRequest.java @@ -45,7 +45,7 @@ public Request getRequest() { } @Override - public final XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { return builder.startObject().endObject(); } diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java index 6f8a3c6f31015..08cf24adb2ea3 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java @@ -111,7 +111,7 @@ public static AuthenticateResponse fromXContent(XContentParser parser) throws IO @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(); - builder.field(USERNAME.getPreferredName(), user.principal()); + builder.field(USERNAME.getPreferredName(), user.username()); builder.field(ROLES.getPreferredName(), user.roles()); builder.field(METADATA.getPreferredName(), user.metadata()); if (user.fullName() != null) { diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/User.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/User.java index d47e971b09dc0..6e684d9383e71 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/User.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/User.java @@ -53,7 +53,7 @@ public User(String username, String[] roles, Map metadata, @Null * @return The principal of this user - effectively serving as the * unique identity of the user. Can never be {@code null}. */ - public String principal() { + public String username() { return this.username; } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/ESRestHighLevelClientTestCase.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/ESRestHighLevelClientTestCase.java index 07c0d818bfa85..ca6713b4296a9 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/ESRestHighLevelClientTestCase.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/ESRestHighLevelClientTestCase.java @@ -76,16 +76,42 @@ protected static Resp execute(Req request, SyncMethod syn } } + /** + * Executes the provided request using either the sync method or its async + * variant, both provided as functions. This variant is used for when the + * request does not have a body (data is conveyed by path and headers). + */ + protected static Resp execute(SyncMethodNoRequest syncMethodNoRequest, AsyncMethodNoRequest asyncMethodNoRequest, + RequestOptions requestOptions) throws IOException { + if (randomBoolean()) { + return syncMethodNoRequest.execute(requestOptions); + } else { + PlainActionFuture future = PlainActionFuture.newFuture(); + asyncMethodNoRequest.execute(requestOptions, future); + return future.actionGet(); + } + } + @FunctionalInterface protected interface SyncMethod { Response execute(Request request, RequestOptions options) throws IOException; } + @FunctionalInterface + protected interface SyncMethodNoRequest { + Response execute(RequestOptions options) throws IOException; + } + @FunctionalInterface protected interface AsyncMethod { void execute(Request request, RequestOptions options, ActionListener listener); } + @FunctionalInterface + protected interface AsyncMethodNoRequest { + void execute(RequestOptions options, ActionListener listener); + } + private static class HighLevelClient extends RestHighLevelClient { private HighLevelClient(RestClient restClient) { super(restClient, (client) -> {}, Collections.emptyList()); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityIT.java new file mode 100644 index 0000000000000..bf6e90ce64b5f --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityIT.java @@ -0,0 +1,89 @@ +/* + * 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; + +import org.elasticsearch.client.security.AuthenticateResponse; +import org.elasticsearch.client.security.PutUserRequest; +import org.elasticsearch.client.security.PutUserResponse; +import org.elasticsearch.client.security.RefreshPolicy; +import org.elasticsearch.common.CharArrays; + +import java.util.Arrays; +import java.util.Base64; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.contains; + +public class SecurityIT extends ESRestHighLevelClientTestCase { + + public void testAuthenticate() throws Exception { + final SecurityClient securityClient = highLevelClient().security(); + final PutUserRequest putUserRequest = randomPutUserRequest(); + final PutUserResponse putUserResponse = execute(putUserRequest, securityClient::putUser, securityClient::putUserAsync); + assertThat(putUserResponse.isCreated(), is(true)); + + // correct password authenticate + final String correctBasicAuthHeader = basicAuthHeader(putUserRequest.getUsername(), putUserRequest.getPassword()); + final AuthenticateResponse correctAuthenticateResponse = execute(securityClient::authenticate, securityClient::authenticateAsync, + authorizationRequestOptions(correctBasicAuthHeader)); + assertThat(correctAuthenticateResponse.getUser().username(), is(putUserRequest.getUsername())); + // nothing to see here (switched because easier to write) + assertThat(putUserRequest.getRoles(), contains(correctAuthenticateResponse.getUser().roles())); + } + + // run as + + private static PutUserRequest randomPutUserRequest() { + final String username = randomAlphaOfLengthBetween(1, 4); + final char[] password = randomAlphaOfLengthBetween(6, 10).toCharArray(); + final List roles = Arrays.asList(generateRandomStringArray(4, 4, false, true)); + final String fullName = randomFrom(random(), null, randomAlphaOfLengthBetween(0, 4)); + final String email = randomFrom(random(), null, randomAlphaOfLengthBetween(0, 4)); + final boolean enabled = randomBoolean(); + final Map metadata; + metadata = new HashMap<>(); + if (randomBoolean()) { + metadata.put("string", null); + } else { + metadata.put("string", randomAlphaOfLengthBetween(0, 4)); + } + if (randomBoolean()) { + metadata.put("string_list", null); + } else { + metadata.put("string_list", Arrays.asList(generateRandomStringArray(4, 4, false, true))); + } + return new PutUserRequest(username, password, roles, fullName, email, enabled, metadata, RefreshPolicy.IMMEDIATE); + } + + private static String basicAuthHeader(String username, char[] password) { + final String concat = new StringBuilder().append(username).append(':').append(password).toString(); + final byte[] concatBytes = CharArrays.toUtf8Bytes(concat.toCharArray()); + return "Basic " + Base64.getEncoder().encodeToString(concatBytes); + } + + private static RequestOptions authorizationRequestOptions(String authorizationHeader) { + final RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder(); + builder.addHeader("Authorization", authorizationHeader); + return builder.build(); + } +} 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 165b778f93588..c73b7f9da0412 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 @@ -266,7 +266,7 @@ public void testAuthenticate() throws Exception { User user = response.getUser(); // <1> //end::authenticate-response - assertThat(user.principal(), is("test_user")); + assertThat(user.username(), is("test_user")); assertThat(user.roles(), equalTo(new String[] {"superuser"})); assertThat(user.fullName(), nullValue()); assertThat(user.email(), nullValue()); @@ -381,7 +381,7 @@ public void onFailure(Exception e) { } } - public void testChangePassword() throws Exception { + public void testChangePassword() throws Exception { RestHighLevelClient client = highLevelClient(); char[] password = new char[]{'p', 'a', 's', 's', 'w', 'o', 'r', 'd'}; char[] newPassword = new char[]{'n', 'e', 'w', 'p', 'a', 's', 's', 'w', 'o', 'r', 'd'}; From af68c9f827e3ee60b7a56c0bbbd75116102b80e5 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Tue, 23 Oct 2018 01:19:50 +0300 Subject: [PATCH 10/18] Looking good! --- .../client/security/AuthenticateResponse.java | 2 +- .../client/security/user/User.java | 20 ++++++++----------- .../org/elasticsearch/client/SecurityIT.java | 20 +++++++++++++------ .../security/AuthenticateResponseTests.java | 3 ++- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java index 08cf24adb2ea3..bee0e417a6dff 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java @@ -52,7 +52,7 @@ public final class AuthenticateResponse implements ToXContentObject { @SuppressWarnings("unchecked") private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( "client_security_authenticate_response", - a -> new AuthenticateResponse(new User((String) a[0], ((List) a[1]).toArray(new String[0]), (Map) a[2], + a -> new AuthenticateResponse(new User((String) a[0], ((List) a[1]), (Map) a[2], (String) a[3], (String) a[4]), (Boolean) a[5])); static { PARSER.declareString(constructorArg(), USERNAME); diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/User.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/User.java index 6e684d9383e71..34a6ce760b796 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/User.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/User.java @@ -22,9 +22,10 @@ import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Strings; -import java.util.Arrays; import java.util.Collections; +import java.util.List; import java.util.Map; +import java.util.Objects; /** @@ -33,12 +34,12 @@ public final class User { private final String username; - private final String[] roles; + private final List roles; private final Map metadata; @Nullable private final String fullName; @Nullable private final String email; - public User(String username, String[] roles, Map metadata, @Nullable String fullName, @Nullable String email) { + public User(String username, List roles, Map metadata, @Nullable String fullName, @Nullable String email) { assert username != null; assert roles != null; assert metadata != null; @@ -62,7 +63,7 @@ public String username() { * identified by their unique names and each represents as * set of permissions. Can never be {@code null}. */ - public String[] roles() { + public List roles() { return this.roles; } @@ -91,7 +92,7 @@ public Map metadata() { public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("User[username=").append(username); - sb.append(",roles=[").append(Strings.arrayToCommaDelimitedString(roles)).append("]"); + sb.append(",roles=[").append(Strings.collectionToCommaDelimitedString(roles)).append("]"); sb.append(",metadata=").append(metadata); sb.append(",fullName=").append(fullName); sb.append(",email=").append(email); @@ -113,7 +114,7 @@ public boolean equals(Object o) { if (!username.equals(user.username)) { return false; } - if (!Arrays.equals(roles, user.roles)) { + if (!roles.equals(user.roles)) { return false; } if (!metadata.equals(user.metadata)) { @@ -127,12 +128,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - int result = username.hashCode(); - result = 31 * result + Arrays.hashCode(roles); - result = 31 * result + metadata.hashCode(); - result = 31 * result + (fullName != null ? fullName.hashCode() : 0); - result = 31 * result + (email != null ? email.hashCode() : 0); - return result; + return Objects.hash(username, roles, metadata, fullName, email); } } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityIT.java index bf6e90ce64b5f..e4848f15feb2a 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityIT.java @@ -33,6 +33,7 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.empty; public class SecurityIT extends ESRestHighLevelClientTestCase { @@ -41,14 +42,21 @@ public void testAuthenticate() throws Exception { final PutUserRequest putUserRequest = randomPutUserRequest(); final PutUserResponse putUserResponse = execute(putUserRequest, securityClient::putUser, securityClient::putUserAsync); assertThat(putUserResponse.isCreated(), is(true)); - + // correct password authenticate final String correctBasicAuthHeader = basicAuthHeader(putUserRequest.getUsername(), putUserRequest.getPassword()); final AuthenticateResponse correctAuthenticateResponse = execute(securityClient::authenticate, securityClient::authenticateAsync, authorizationRequestOptions(correctBasicAuthHeader)); + assertThat(correctAuthenticateResponse.getUser().username(), is(putUserRequest.getUsername())); - // nothing to see here (switched because easier to write) - assertThat(putUserRequest.getRoles(), contains(correctAuthenticateResponse.getUser().roles())); + if (putUserRequest.getRoles().isEmpty()) { + assertThat(correctAuthenticateResponse.getUser().roles(), is(empty())); + } else { + assertThat(correctAuthenticateResponse.getUser().roles(), contains(putUserRequest.getRoles().toArray())); + } + assertThat(correctAuthenticateResponse.getUser().metadata(), is(putUserRequest.getMetadata())); + assertThat(correctAuthenticateResponse.getUser().fullName(), is(putUserRequest.getFullName())); + assertThat(correctAuthenticateResponse.getUser().email(), is(putUserRequest.getEmail())); } // run as @@ -56,9 +64,9 @@ public void testAuthenticate() throws Exception { private static PutUserRequest randomPutUserRequest() { final String username = randomAlphaOfLengthBetween(1, 4); final char[] password = randomAlphaOfLengthBetween(6, 10).toCharArray(); - final List roles = Arrays.asList(generateRandomStringArray(4, 4, false, true)); - final String fullName = randomFrom(random(), null, randomAlphaOfLengthBetween(0, 4)); - final String email = randomFrom(random(), null, randomAlphaOfLengthBetween(0, 4)); + final List roles = Arrays.asList(generateRandomStringArray(3, 3, false, true)); + final String fullName = randomFrom(random(), null, randomAlphaOfLengthBetween(0, 3)); + final String email = randomFrom(random(), null, randomAlphaOfLengthBetween(0, 3)); final boolean enabled = randomBoolean(); final Map metadata; metadata = new HashMap<>(); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/AuthenticateResponseTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/AuthenticateResponseTests.java index 6497dae38bfaf..8fc59d42e4a3b 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/AuthenticateResponseTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/AuthenticateResponseTests.java @@ -26,6 +26,7 @@ import java.io.IOException; import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; public class AuthenticateResponseTests extends AbstractXContentTestCase { @@ -33,7 +34,7 @@ public class AuthenticateResponseTests extends AbstractXContentTestCase roles = Arrays.asList(generateRandomStringArray(4, 4, false, true)); final Map metadata; metadata = new HashMap<>(); if (randomBoolean()) { From 82af3dca21cf3adac71993fc19338340c7695a8b Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Tue, 23 Oct 2018 03:06:42 +0300 Subject: [PATCH 11/18] Simple test --- .../org/elasticsearch/client/SecurityIT.java | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityIT.java index e4848f15feb2a..74a4d58e2bf77 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityIT.java @@ -19,6 +19,8 @@ package org.elasticsearch.client; +import org.apache.http.client.methods.HttpDelete; +import org.elasticsearch.ElasticsearchStatusException; import org.elasticsearch.client.security.AuthenticateResponse; import org.elasticsearch.client.security.PutUserRequest; import org.elasticsearch.client.security.PutUserResponse; @@ -33,41 +35,50 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.empty; public class SecurityIT extends ESRestHighLevelClientTestCase { public void testAuthenticate() throws Exception { final SecurityClient securityClient = highLevelClient().security(); - final PutUserRequest putUserRequest = randomPutUserRequest(); + // test fixture: put enabled user + final PutUserRequest putUserRequest = randomPutUserRequest(true); final PutUserResponse putUserResponse = execute(putUserRequest, securityClient::putUser, securityClient::putUserAsync); assertThat(putUserResponse.isCreated(), is(true)); - // correct password authenticate - final String correctBasicAuthHeader = basicAuthHeader(putUserRequest.getUsername(), putUserRequest.getPassword()); - final AuthenticateResponse correctAuthenticateResponse = execute(securityClient::authenticate, securityClient::authenticateAsync, - authorizationRequestOptions(correctBasicAuthHeader)); + // authenticate correctly + final String basicAuthHeader = basicAuthHeader(putUserRequest.getUsername(), putUserRequest.getPassword()); + final AuthenticateResponse authenticateResponse = execute(securityClient::authenticate, securityClient::authenticateAsync, + authorizationRequestOptions(basicAuthHeader)); - assertThat(correctAuthenticateResponse.getUser().username(), is(putUserRequest.getUsername())); + assertThat(authenticateResponse.getUser().username(), is(putUserRequest.getUsername())); if (putUserRequest.getRoles().isEmpty()) { - assertThat(correctAuthenticateResponse.getUser().roles(), is(empty())); + assertThat(authenticateResponse.getUser().roles(), is(empty())); } else { - assertThat(correctAuthenticateResponse.getUser().roles(), contains(putUserRequest.getRoles().toArray())); + assertThat(authenticateResponse.getUser().roles(), contains(putUserRequest.getRoles().toArray())); } - assertThat(correctAuthenticateResponse.getUser().metadata(), is(putUserRequest.getMetadata())); - assertThat(correctAuthenticateResponse.getUser().fullName(), is(putUserRequest.getFullName())); - assertThat(correctAuthenticateResponse.getUser().email(), is(putUserRequest.getEmail())); + assertThat(authenticateResponse.getUser().metadata(), is(putUserRequest.getMetadata())); + assertThat(authenticateResponse.getUser().fullName(), is(putUserRequest.getFullName())); + assertThat(authenticateResponse.getUser().email(), is(putUserRequest.getEmail())); + assertThat(authenticateResponse.enabled(), is(true)); + + // delete user + final Request deleteUserRequest = new Request(HttpDelete.METHOD_NAME, "/_xpack/security/user/" + putUserRequest.getUsername()); + highLevelClient().getLowLevelClient().performRequest(deleteUserRequest); + + // authentication no longer works + ElasticsearchStatusException e = expectThrows(ElasticsearchStatusException.class, () -> execute(securityClient::authenticate, + securityClient::authenticateAsync, authorizationRequestOptions(basicAuthHeader))); + assertThat(e.getMessage(), containsString("unable to authenticate user [" + putUserRequest.getUsername() + "]")); } - - // run as - private static PutUserRequest randomPutUserRequest() { + private static PutUserRequest randomPutUserRequest(boolean enabled) { final String username = randomAlphaOfLengthBetween(1, 4); final char[] password = randomAlphaOfLengthBetween(6, 10).toCharArray(); final List roles = Arrays.asList(generateRandomStringArray(3, 3, false, true)); final String fullName = randomFrom(random(), null, randomAlphaOfLengthBetween(0, 3)); final String email = randomFrom(random(), null, randomAlphaOfLengthBetween(0, 3)); - final boolean enabled = randomBoolean(); final Map metadata; metadata = new HashMap<>(); if (randomBoolean()) { From 66c390e2d6326938790c101e86c98d5e249e2dba Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Tue, 23 Oct 2018 03:18:41 +0300 Subject: [PATCH 12/18] Fix SecurityDocumentationIT --- .../client/documentation/SecurityDocumentationIT.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 c73b7f9da0412..65e0c52413685 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 @@ -60,7 +60,7 @@ import java.util.concurrent.TimeUnit; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.nullValue; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; @@ -267,7 +267,7 @@ public void testAuthenticate() throws Exception { //end::authenticate-response assertThat(user.username(), is("test_user")); - assertThat(user.roles(), equalTo(new String[] {"superuser"})); + assertThat(user.roles(), contains(new String[] {"superuser"})); assertThat(user.fullName(), nullValue()); assertThat(user.email(), nullValue()); assertThat(user.metadata().isEmpty(), is(true)); From 83f9bf1cb0e0dd7c59ed6b0ab95bb7646ace8831 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Tue, 23 Oct 2018 03:34:48 +0300 Subject: [PATCH 13/18] empty lines --- .../client/documentation/SecurityDocumentationIT.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 65e0c52413685..29d1ad413b38f 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 @@ -366,7 +366,6 @@ public void onFailure(Exception e) { // <2> } }; - // end::get-certificates-execute-listener // Replace the empty listener by a blocking listener in test @@ -393,6 +392,7 @@ public void testChangePassword() throws Exception { //tag::change-password-execute ChangePasswordRequest request = new ChangePasswordRequest("change_password_user", newPassword, RefreshPolicy.NONE); EmptyResponse response = client.security().changePassword(request, RequestOptions.DEFAULT); + //end::change-password-execute assertNotNull(response); } @@ -404,18 +404,22 @@ public void testChangePassword() throws Exception { public void onResponse(EmptyResponse emptyResponse) { // <1> } + @Override public void onFailure(Exception e) { // <2> } }; //end::change-password-execute-listener + // Replace the empty listener by a blocking listener in test final CountDownLatch latch = new CountDownLatch(1); listener = new LatchedActionListener<>(listener, latch); + //tag::change-password-execute-async client.security().changePasswordAsync(request, RequestOptions.DEFAULT, listener); // <1> //end::change-password-execute-async + assertTrue(latch.await(30L, TimeUnit.SECONDS)); } } From a9e5ea9662a308449b329891dd33973681f441b2 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Mon, 29 Oct 2018 15:31:19 +0200 Subject: [PATCH 14/18] Merge foo --- .../main/java/org/elasticsearch/client/SecurityClient.java | 5 +++-- .../client/documentation/SecurityDocumentationIT.java | 6 +----- 2 files changed, 4 insertions(+), 7 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 fa4956ca7be8f..5203306147f1a 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 @@ -216,7 +216,7 @@ public void disableUserAsync(DisableUserRequest request, RequestOptions options, * Authenticate the current user and return all the information about the authenticated user. * See * the docs for more. - * + * * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized * @return the responsee from the authenticate user call */ @@ -229,7 +229,7 @@ public AuthenticateResponse authenticate(RequestOptions options) throws IOExcept * Authenticate the current user asynchronously and return all the information about the authenticated user. * See * the docs for more. - * + * * @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 */ @@ -238,6 +238,7 @@ public void authenticateAsync(RequestOptions options, ActionListener * the docs for more. 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 97350f7c9c61f..b2386c9fab729 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 @@ -419,16 +419,12 @@ public void onFailure(Exception e) { // Replace the empty listener by a blocking listener in test final CountDownLatch latch = new CountDownLatch(1); listener = new LatchedActionListener<>(listener, latch); - // tag::authenticate-execute-async client.security().authenticateAsync(RequestOptions.DEFAULT, listener); // <1> // end::authenticate-execute-async - // tag::clear-roles-cache-execute-async - client.security().clearRolesCacheAsync(request, RequestOptions.DEFAULT, listener); // <1> - // end::clear-roles-cache-execute-async - assertTrue(latch.await(30L, TimeUnit.SECONDS)); + } } From 14b21a425f18b29d7182cdce6c43a8637b3f770c Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Mon, 29 Oct 2018 16:20:38 +0200 Subject: [PATCH 15/18] AuthenticateResponse no ToXContentObject --- .../client/security/AuthenticateRequest.java | 11 +---- .../client/security/AuthenticateResponse.java | 20 +------- .../client/security/user/User.java | 15 +++--- .../client/ESRestHighLevelClientTestCase.java | 4 +- .../security/AuthenticateResponseTests.java | 48 +++++++++++++------ 5 files changed, 46 insertions(+), 52 deletions(-) diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateRequest.java index 7a322963fcfce..b3b95e3ae0cf3 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateRequest.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateRequest.java @@ -22,16 +22,12 @@ import org.apache.http.client.methods.HttpGet; import org.elasticsearch.client.Request; import org.elasticsearch.client.Validatable; -import org.elasticsearch.common.xcontent.ToXContentObject; -import org.elasticsearch.common.xcontent.XContentBuilder; - -import java.io.IOException; /** * Empty request object required to make the authenticate call. The authenticate call * retrieves metadata about the authenticated user. */ -public final class AuthenticateRequest implements Validatable, ToXContentObject { +public final class AuthenticateRequest implements Validatable { public static final AuthenticateRequest INSTANCE = new AuthenticateRequest(); private final Request request; @@ -44,9 +40,4 @@ public Request getRequest() { return request; } - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - return builder.startObject().endObject(); - } - } diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java index bee0e417a6dff..62f1cc0955bd1 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateResponse.java @@ -22,8 +22,6 @@ import org.elasticsearch.client.security.user.User; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.xcontent.ConstructingObjectParser; -import org.elasticsearch.common.xcontent.ToXContentObject; -import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import java.io.IOException; @@ -40,7 +38,7 @@ * user object contains all user metadata which Elasticsearch uses to map roles, * etc. */ -public final class AuthenticateResponse implements ToXContentObject { +public final class AuthenticateResponse { static final ParseField USERNAME = new ParseField("username"); static final ParseField ROLES = new ParseField("roles"); @@ -108,20 +106,4 @@ public static AuthenticateResponse fromXContent(XContentParser parser) throws IO return PARSER.parse(parser, null); } - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject(); - builder.field(USERNAME.getPreferredName(), user.username()); - builder.field(ROLES.getPreferredName(), user.roles()); - builder.field(METADATA.getPreferredName(), user.metadata()); - if (user.fullName() != null) { - builder.field(FULL_NAME.getPreferredName(), user.fullName()); - } - if (user.email() != null) { - builder.field(EMAIL.getPreferredName(), user.email()); - } - builder.field(ENABLED.getPreferredName(), enabled); - builder.endObject(); - return builder; - } } diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/User.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/User.java index 34a6ce760b796..977780b46b79b 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/User.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/User.java @@ -22,8 +22,8 @@ import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Strings; +import java.util.Collection; import java.util.Collections; -import java.util.List; import java.util.Map; import java.util.Objects; @@ -34,15 +34,16 @@ public final class User { private final String username; - private final List roles; + private final Collection roles; private final Map metadata; @Nullable private final String fullName; @Nullable private final String email; - public User(String username, List roles, Map metadata, @Nullable String fullName, @Nullable String email) { - assert username != null; - assert roles != null; - assert metadata != null; + public User(String username, Collection roles, Map metadata, @Nullable String fullName, + @Nullable String email) { + Objects.requireNonNull(username, "`username` cannot be null"); + Objects.requireNonNull(roles, "`roles` cannot be null. Pass an empty collection instead."); + Objects.requireNonNull(roles, "`metadata` cannot be null. Pass an empty map instead."); this.username = username; this.roles = roles; this.metadata = Collections.unmodifiableMap(metadata); @@ -63,7 +64,7 @@ public String username() { * identified by their unique names and each represents as * set of permissions. Can never be {@code null}. */ - public List roles() { + public Collection roles() { return this.roles; } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/ESRestHighLevelClientTestCase.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/ESRestHighLevelClientTestCase.java index ca6713b4296a9..586cf7339a871 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/ESRestHighLevelClientTestCase.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/ESRestHighLevelClientTestCase.java @@ -78,8 +78,8 @@ protected static Resp execute(Req request, SyncMethod syn /** * Executes the provided request using either the sync method or its async - * variant, both provided as functions. This variant is used for when the - * request does not have a body (data is conveyed by path and headers). + * variant, both provided as functions. This variant is used when the call does + * not have a request object (only headers and the request path). */ protected static Resp execute(SyncMethodNoRequest syncMethodNoRequest, AsyncMethodNoRequest asyncMethodNoRequest, RequestOptions requestOptions) throws IOException { diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/AuthenticateResponseTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/AuthenticateResponseTests.java index 8fc59d42e4a3b..c8b44ddd7e7b4 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/AuthenticateResponseTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/AuthenticateResponseTests.java @@ -20,8 +20,8 @@ package org.elasticsearch.client.security; import org.elasticsearch.client.security.user.User; -import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.test.AbstractXContentTestCase; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.test.ESTestCase; import java.io.IOException; import java.util.Arrays; @@ -29,9 +29,22 @@ import java.util.List; import java.util.Map; -public class AuthenticateResponseTests extends AbstractXContentTestCase { +import static org.elasticsearch.test.AbstractXContentTestCase.xContentTester; + +public class AuthenticateResponseTests extends ESTestCase { + + public void testFromXContent() throws IOException { + xContentTester( + this::createParser, + this::createTestInstance, + this::toXContent, + AuthenticateResponse::fromXContent) + .supportsUnknownFields(false) + .randomFieldsExcludeFilter(field -> + field.endsWith("status.current_position")) + .test(); + } - @Override protected AuthenticateResponse createTestInstance() { final String username = randomAlphaOfLengthBetween(1, 4); final List roles = Arrays.asList(generateRandomStringArray(4, 4, false, true)); @@ -52,15 +65,22 @@ protected AuthenticateResponse createTestInstance() { final boolean enabled = randomBoolean(); return new AuthenticateResponse(new User(username, roles, metadata, fullName, email), enabled); } - - @Override - protected AuthenticateResponse doParseInstance(XContentParser parser) throws IOException { - return AuthenticateResponse.fromXContent(parser); - } - - @Override - protected boolean supportsUnknownFields() { - return false; + + private void toXContent(AuthenticateResponse response, XContentBuilder builder) throws IOException { + final User user = response.getUser(); + final boolean enabled = response.enabled(); + builder.startObject(); + builder.field(AuthenticateResponse.USERNAME.getPreferredName(), user.username()); + builder.field(AuthenticateResponse.ROLES.getPreferredName(), user.roles()); + builder.field(AuthenticateResponse.METADATA.getPreferredName(), user.metadata()); + if (user.fullName() != null) { + builder.field(AuthenticateResponse.FULL_NAME.getPreferredName(), user.fullName()); + } + if (user.email() != null) { + builder.field(AuthenticateResponse.EMAIL.getPreferredName(), user.email()); + } + builder.field(AuthenticateResponse.ENABLED.getPreferredName(), enabled); + builder.endObject(); } - + } From b0a3d15922deb430ab033a04812ac0f9035d81fd Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Mon, 29 Oct 2018 17:42:09 +0200 Subject: [PATCH 16/18] asciidoc --- .../SecurityDocumentationIT.java | 3 +- .../high-level/security/authenticate.asciidoc | 50 +++++++++++-------- .../high-level/supported-apis.asciidoc | 4 +- 3 files changed, 35 insertions(+), 22 deletions(-) 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 b2386c9fab729..42c4a67ba952a 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 @@ -391,6 +391,7 @@ public void testAuthenticate() throws Exception { //tag::authenticate-response User user = response.getUser(); // <1> + boolean enabled = response.enabled(); // <2> //end::authenticate-response assertThat(user.username(), is("test_user")); @@ -398,7 +399,7 @@ public void testAuthenticate() throws Exception { assertThat(user.fullName(), nullValue()); assertThat(user.email(), nullValue()); assertThat(user.metadata().isEmpty(), is(true)); - assertThat(response.enabled(), is(true)); + assertThat(enabled, is(true)); } { diff --git a/docs/java-rest/high-level/security/authenticate.asciidoc b/docs/java-rest/high-level/security/authenticate.asciidoc index 630020c008ba6..e50c64bf9d0f5 100644 --- a/docs/java-rest/high-level/security/authenticate.asciidoc +++ b/docs/java-rest/high-level/security/authenticate.asciidoc @@ -1,40 +1,50 @@ -[[java-rest-high-security-authenticate]] + +-- +:api: authenticate +:response: AuthenticateResponse +-- + +[id="{upid}-{api}"] === Authenticate API -[[java-rest-high-security-authenticate-execution]] +[id="{upid}-{api}-sync"] ==== Execution -To authenticate and retrieve information about a user can be performed +Authenticating and retrieving information about a user can be performed using the `security().authenticate()` method: ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- -include-tagged::{doc-tests}/SecurityDocumentationIT.java[authenticate-execute] +include-tagged::{doc-tests-file}[{api}-execute] -------------------------------------------------- -This method does not require a request object. +This method does not require a request object. The client waits for the ++{response}+ to be returned before continuing with code execution. -[[java-rest-high-security-authenticate-response]] +[id="{upid}-{api}-response"] ==== Response -The returned `AuthenticateResponse` contains a single field, `user`. This field +The returned +{response}+ contains two fields. Firstly, the `user` field , accessed with `getUser`, contains all the information about this -authenticated user. +authenticated user. The other field, `enabled`, tells if this user is actually +usable or has been temporalily deactivated. ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- -include-tagged::{doc-tests}/SecurityDocumentationIT.java[authenticate-response] +include-tagged::{doc-tests-file}[{api}-response] -------------------------------------------------- -<1> `getUser` retrieves the `User` instance containing the information. +<1> `getUser` retrieves the `User` instance containing the information, +see {javadoc-client}/security/user/User.html. +<2> `enabled` tells if this user is usable or is deactivated. -[[java-rest-high-security-authenticate-async]] +[id="{upid}-{api}-async"] ==== Asynchronous Execution This request can also be executed asynchronously: ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- -include-tagged::{doc-tests}/SecurityDocumentationIT.java[authenticate-execute-async] +include-tagged::{doc-tests-file}[{api}-execute-async] -------------------------------------------------- <1> The `ActionListener` to use when the execution completes. This method does not require a request object. @@ -44,13 +54,13 @@ has completed the `ActionListener` is called back using the `onResponse` method if the execution completed successfully or using the `onFailure` method if it failed. - A typical listener for a `AuthenticateResponse` looks like: +A typical listener for a +{response}+ looks like: - ["source","java",subs="attributes,callouts,macros"] - -------------------------------------------------- - include-tagged::{doc-tests}/SecurityDocumentationIT.java[authenticate-execute-listener] - -------------------------------------------------- - <1> Called when the execution completed successfully. The response is - provided as an argument. - <2> Called in case of a failure. The exception is provided as an argument. +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests-file}[{api}-execute-listener] +-------------------------------------------------- +<1> Called when the execution completed successfully. The response is +provided as an argument. +<2> Called in case of a failure. The 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..4411a6b375f91 100644 --- a/docs/java-rest/high-level/supported-apis.asciidoc +++ b/docs/java-rest/high-level/supported-apis.asciidoc @@ -327,6 +327,7 @@ The Java High Level REST Client supports the following Security APIs: * <> * <> * <<{upid}-clear-roles-cache>> +* <<{upid}-authenticate>> * <> * <> * <> @@ -339,6 +340,7 @@ include::security/disable-user.asciidoc[] include::security/change-password.asciidoc[] include::security/delete-role.asciidoc[] include::security/clear-roles-cache.asciidoc[] +include::security/authenticate.asciidoc[] include::security/get-certificates.asciidoc[] include::security/put-role-mapping.asciidoc[] include::security/get-role-mappings.asciidoc[] @@ -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 1edce33165c9527995849425675d6f06937e59aa Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Mon, 29 Oct 2018 18:35:15 +0200 Subject: [PATCH 17/18] Nits and `testEqualsAndHashCode` --- .../SecurityDocumentationIT.java | 3 +- .../security/AuthenticateResponseTests.java | 45 ++++++++++++++++++- 2 files changed, 44 insertions(+), 4 deletions(-) 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 42c4a67ba952a..4849228dc529d 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 @@ -425,7 +425,6 @@ public void onFailure(Exception e) { // end::authenticate-execute-async assertTrue(latch.await(30L, TimeUnit.SECONDS)); - } } @@ -567,8 +566,8 @@ public void testChangePassword() throws Exception { //tag::change-password-execute ChangePasswordRequest request = new ChangePasswordRequest("change_password_user", newPassword, RefreshPolicy.NONE); EmptyResponse response = client.security().changePassword(request, RequestOptions.DEFAULT); - //end::change-password-execute + assertNotNull(response); } { diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/AuthenticateResponseTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/AuthenticateResponseTests.java index c8b44ddd7e7b4..c92dc467dc874 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/AuthenticateResponseTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/AuthenticateResponseTests.java @@ -22,9 +22,12 @@ import org.elasticsearch.client.security.user.User; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.EqualsHashCodeTestUtils; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -40,11 +43,15 @@ public void testFromXContent() throws IOException { this::toXContent, AuthenticateResponse::fromXContent) .supportsUnknownFields(false) - .randomFieldsExcludeFilter(field -> - field.endsWith("status.current_position")) .test(); } + public void testEqualsAndHashCode() { + final AuthenticateResponse reponse = createTestInstance(); + EqualsHashCodeTestUtils.checkEqualsAndHashCode(reponse, this::copy, + this::mutate); + } + protected AuthenticateResponse createTestInstance() { final String username = randomAlphaOfLengthBetween(1, 4); final List roles = Arrays.asList(generateRandomStringArray(4, 4, false, true)); @@ -82,5 +89,39 @@ private void toXContent(AuthenticateResponse response, XContentBuilder builder) builder.field(AuthenticateResponse.ENABLED.getPreferredName(), enabled); builder.endObject(); } + + private AuthenticateResponse copy(AuthenticateResponse response) { + final User originalUser = response.getUser(); + final User copyUser = new User(originalUser.username(), originalUser.roles(), originalUser.metadata(), originalUser.fullName(), originalUser.email()); + return new AuthenticateResponse(copyUser, response.enabled()); + } + private AuthenticateResponse mutate(AuthenticateResponse response) { + final User originalUser = response.getUser(); + switch (randomIntBetween(1, 6)) { + case 1: + return new AuthenticateResponse(new User(originalUser.username() + "wrong", originalUser.roles(), originalUser.metadata(), + originalUser.fullName(), originalUser.email()), response.enabled()); + case 2: + final Collection wrongRoles = new ArrayList<>(originalUser.roles()); + wrongRoles.add(randomAlphaOfLengthBetween(1, 4)); + return new AuthenticateResponse(new User(originalUser.username(), wrongRoles, originalUser.metadata(), + originalUser.fullName(), originalUser.email()), response.enabled()); + case 3: + final Map wrongMetadata = new HashMap<>(originalUser.metadata()); + wrongMetadata.put("wrong_string", randomAlphaOfLengthBetween(0, 4)); + return new AuthenticateResponse(new User(originalUser.username(), originalUser.roles(), wrongMetadata, + originalUser.fullName(), originalUser.email()), response.enabled()); + case 4: + return new AuthenticateResponse(new User(originalUser.username(), originalUser.roles(), originalUser.metadata(), + originalUser.fullName() + "wrong", originalUser.email()), response.enabled()); + case 5: + return new AuthenticateResponse(new User(originalUser.username(), originalUser.roles(), originalUser.metadata(), + originalUser.fullName(), originalUser.email() + "wrong"), response.enabled()); + case 6: + return new AuthenticateResponse(new User(originalUser.username(), originalUser.roles(), originalUser.metadata(), + originalUser.fullName(), originalUser.email()), !response.enabled()); + } + throw new IllegalStateException("Bad random number"); + } } From 93ecccf03870e3b8292f013bbb5e48cd11a6b54c Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Wed, 31 Oct 2018 01:24:38 +0200 Subject: [PATCH 18/18] AuthenticateRequest new request every time! --- .../client/security/AuthenticateRequest.java | 4 +--- .../client/security/AuthenticateResponseTests.java | 9 +++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateRequest.java index b3b95e3ae0cf3..2aefa97cb8bf1 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateRequest.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/AuthenticateRequest.java @@ -30,14 +30,12 @@ public final class AuthenticateRequest implements Validatable { public static final AuthenticateRequest INSTANCE = new AuthenticateRequest(); - private final Request request; private AuthenticateRequest() { - request = new Request(HttpGet.METHOD_NAME, "/_xpack/security/_authenticate"); } public Request getRequest() { - return request; + return new Request(HttpGet.METHOD_NAME, "/_xpack/security/_authenticate"); } } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/AuthenticateResponseTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/AuthenticateResponseTests.java index c92dc467dc874..ce813f5ecf59c 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/AuthenticateResponseTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/AuthenticateResponseTests.java @@ -35,7 +35,7 @@ import static org.elasticsearch.test.AbstractXContentTestCase.xContentTester; public class AuthenticateResponseTests extends ESTestCase { - + public void testFromXContent() throws IOException { xContentTester( this::createParser, @@ -72,7 +72,7 @@ protected AuthenticateResponse createTestInstance() { final boolean enabled = randomBoolean(); return new AuthenticateResponse(new User(username, roles, metadata, fullName, email), enabled); } - + private void toXContent(AuthenticateResponse response, XContentBuilder builder) throws IOException { final User user = response.getUser(); final boolean enabled = response.enabled(); @@ -92,10 +92,11 @@ private void toXContent(AuthenticateResponse response, XContentBuilder builder) private AuthenticateResponse copy(AuthenticateResponse response) { final User originalUser = response.getUser(); - final User copyUser = new User(originalUser.username(), originalUser.roles(), originalUser.metadata(), originalUser.fullName(), originalUser.email()); + final User copyUser = new User(originalUser.username(), originalUser.roles(), originalUser.metadata(), originalUser.fullName(), + originalUser.email()); return new AuthenticateResponse(copyUser, response.enabled()); } - + private AuthenticateResponse mutate(AuthenticateResponse response) { final User originalUser = response.getUser(); switch (randomIntBetween(1, 6)) {