Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9446b19
No tests!!! No.
albertzaharovits Sep 7, 2018
216d625
Merge branch 'master' into hlrc_authenticate
albertzaharovits Sep 9, 2018
52fe8ba
Authenticate is done??
albertzaharovits Sep 9, 2018
fdde4c3
RestHighLevelClient tests
albertzaharovits Sep 9, 2018
090eef6
LOL
albertzaharovits Sep 9, 2018
4443657
Merge branch 'master' into hlrc_authenticate
albertzaharovits Oct 19, 2018
d2977ce
Addressed some feedback
albertzaharovits Oct 19, 2018
a390c42
User reformat
albertzaharovits Oct 21, 2018
4b5bb5e
Merge branch 'master' into hlrc_authenticate
albertzaharovits Oct 21, 2018
4d69fec
move User to security.user
albertzaharovits Oct 21, 2018
54a0479
Move enabled out of User
albertzaharovits Oct 22, 2018
47dbb3a
Completion in sight!
albertzaharovits Oct 22, 2018
af68c9f
Looking good!
albertzaharovits Oct 22, 2018
82af3dc
Simple test
albertzaharovits Oct 23, 2018
66c390e
Fix SecurityDocumentationIT
albertzaharovits Oct 23, 2018
83f9bf1
empty lines
albertzaharovits Oct 23, 2018
97841e2
Merge branch 'master' into hlrc_authenticate
albertzaharovits Oct 23, 2018
2de05b7
Merge branch 'master' into hlrc_authenticate
albertzaharovits Oct 29, 2018
a9e5ea9
Merge foo
albertzaharovits Oct 29, 2018
14b21a4
AuthenticateResponse no ToXContentObject
albertzaharovits Oct 29, 2018
b0a3d15
asciidoc
albertzaharovits Oct 29, 2018
1edce33
Nits and `testEqualsAndHashCode`
albertzaharovits Oct 29, 2018
93ecccf
AuthenticateRequest new request every time!
albertzaharovits Oct 30, 2018
75dfd7b
Merge branch 'master' into hlrc_authenticate
albertzaharovits Oct 31, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
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.ChangePasswordRequest;
import org.elasticsearch.client.security.ClearRolesCacheRequest;
import org.elasticsearch.client.security.ClearRolesCacheResponse;
Expand Down Expand Up @@ -210,6 +212,32 @@ public void disableUserAsync(DisableUserRequest request, RequestOptions options,
EmptyResponse::fromXContent, listener, emptySet());
}

/**
* Authenticate the current user and return all the information about the authenticated user.
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-authenticate.html">
* the docs</a> 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 asynchronously and return all the information about the authenticated user.
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-authenticate.html">
* the docs</a> 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<AuthenticateResponse> listener) {
restHighLevelClient.performRequestAsyncAndParseEntity(AuthenticateRequest.INSTANCE, AuthenticateRequest::getRequest, options,
AuthenticateResponse::fromXContent, listener, emptySet());
}

/**
* Clears the native roles cache for a set of roles.
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-clear-role-cache.html">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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.Request;
import org.elasticsearch.client.Validatable;

/**
* Empty request object required to make the authenticate call. The authenticate call
* retrieves metadata about the authenticated user.
*/
public final class AuthenticateRequest implements Validatable {

public static final AuthenticateRequest INSTANCE = new AuthenticateRequest();

private AuthenticateRequest() {
}

public Request getRequest() {
return new Request(HttpGet.METHOD_NAME, "/_xpack/security/_authenticate");
}

}
Copy link
Contributor Author

@albertzaharovits albertzaharovits Sep 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The request is a singleton, so there is no need for a "request converter function". The converted is a member of the singleton.

Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* 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.ParseField;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
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 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 {

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<AuthenticateResponse, Void> PARSER = new ConstructingObjectParser<>(
"client_security_authenticate_response",
a -> new AuthenticateResponse(new User((String) a[0], ((List<String>) a[1]), (Map<String, Object>) a[2],
(String) a[3], (String) a[4]), (Boolean) a[5]));
static {
PARSER.declareString(constructorArg(), USERNAME);
PARSER.declareStringArray(constructorArg(), ROLES);
PARSER.<Map<String, Object>>declareObject(constructorArg(), (parser, c) -> parser.map(), METADATA);
PARSER.declareStringOrNull(optionalConstructorArg(), FULL_NAME);
PARSER.declareStringOrNull(optionalConstructorArg(), EMAIL);
Copy link
Contributor Author

@albertzaharovits albertzaharovits Oct 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hub-cap do we have any a convention for null over an optional fields, or the other way around? I think not. I only made it work if it parses both ways.

Copy link
Contributor Author

@albertzaharovits albertzaharovits Oct 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hub-cap Not really applicable in this case, but, is there a general way about how to test that server side entities are serialized to client side ones and vice-versa. Actually, how do we make sure a server response is not breaking the client parsers. Specifically, in this case, what if roles becomes null or an optional field on the server code. It would break the client parser. Right now this would be catched by the Integ test, as we can control for that in the test, but this is not generally true. Even more so because client integ tests are slim because they overlap with the rest API integ tests.

Am I missing the perspective here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We dont have that yet. @vladimirdolzhenko has a PR open to help out with this.

PARSER.declareBoolean(constructorArg(), ENABLED);
}

private final User user;
private final boolean enabled;

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) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final AuthenticateResponse that = (AuthenticateResponse) o;
return user.equals(that.user) && enabled == that.enabled;
}

@Override
public int hashCode() {
return Objects.hash(user, enabled);
}

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

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

package org.elasticsearch.client.security.user;

import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings;

import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;


/**
* An authenticated user
*/
public final class User {

private final String username;
private final Collection<String> roles;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a Collection now instead of List.

private final Map<String, Object> metadata;
@Nullable private final String fullName;
@Nullable private final String email;

public User(String username, Collection<String> roles, Map<String, Object> 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);
this.fullName = fullName;
this.email = email;
}

/**
* @return The principal of this user - effectively serving as the
* unique identity of the user. Can never be {@code null}.
*/
public String username() {
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. Can never be {@code null}.
*/
public Collection<String> roles() {
return this.roles;
}

/**
* @return The metadata that is associated with this user. Can never be {@code null}.
*/
public Map<String, Object> 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;
}

@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("User[username=").append(username);
sb.append(",roles=[").append(Strings.collectionToCommaDelimitedString(roles)).append("]");
sb.append(",metadata=").append(metadata);
sb.append(",fullName=").append(fullName);
sb.append(",email=").append(email);
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 (!roles.equals(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() {
return Objects.hash(username, roles, metadata, fullName, email);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -84,16 +84,42 @@ protected static <Req, Resp> Resp execute(Req request, SyncMethod<Req, Resp> syn
}
}

/**
* Executes the provided request using either the sync method or its async
* 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> Resp execute(SyncMethodNoRequest<Resp> syncMethodNoRequest, AsyncMethodNoRequest<Resp> asyncMethodNoRequest,
RequestOptions requestOptions) throws IOException {
if (randomBoolean()) {
return syncMethodNoRequest.execute(requestOptions);
} else {
PlainActionFuture<Resp> future = PlainActionFuture.newFuture();
asyncMethodNoRequest.execute(requestOptions, future);
return future.actionGet();
}
}

@FunctionalInterface
protected interface SyncMethod<Request, Response> {
Response execute(Request request, RequestOptions options) throws IOException;
}

@FunctionalInterface
protected interface SyncMethodNoRequest<Response> {
Response execute(RequestOptions options) throws IOException;
}

@FunctionalInterface
protected interface AsyncMethod<Request, Response> {
void execute(Request request, RequestOptions options, ActionListener<Response> listener);
}

@FunctionalInterface
protected interface AsyncMethodNoRequest<Response> {
void execute(RequestOptions options, ActionListener<Response> listener);
}

private static class HighLevelClient extends RestHighLevelClient {
private HighLevelClient(RestClient restClient) {
super(restClient, (client) -> {}, Collections.emptyList());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -719,7 +719,7 @@ public void testApiNamingConventions() throws Exception {
methods.containsKey(apiName.substring(0, apiName.length() - 6)));
assertThat("async method [" + method + "] should return void", method.getReturnType(), equalTo(Void.TYPE));
assertEquals("async method [" + method + "] should not throw any exceptions", 0, method.getExceptionTypes().length);
if (apiName.equals("security.get_ssl_certificates_async")) {
if (apiName.equals("security.authenticate_async") || apiName.equals("security.get_ssl_certificates_async")) {
assertEquals(2, method.getParameterTypes().length);
assertThat(method.getParameterTypes()[0], equalTo(RequestOptions.class));
assertThat(method.getParameterTypes()[1], equalTo(ActionListener.class));
Expand All @@ -744,7 +744,8 @@ public void testApiNamingConventions() throws Exception {

assertEquals("incorrect number of exceptions for method [" + method + "]", 1, method.getExceptionTypes().length);
//a few methods don't accept a request object as argument
if (apiName.equals("ping") || apiName.equals("info") || apiName.equals("security.get_ssl_certificates")) {
if (apiName.equals("ping") || apiName.equals("info") || apiName.equals("security.get_ssl_certificates")
|| apiName.equals("security.authenticate")) {
assertEquals("incorrect number of arguments for method [" + method + "]", 1, method.getParameterTypes().length);
assertThat("the parameter to method [" + method + "] is the wrong type",
method.getParameterTypes()[0], equalTo(RequestOptions.class));
Expand Down
Loading