Convert Realm.authenticate to provide a richer result (elastic/x-pack-elasticsearch#1932)

This allows for messages to be returned, and distinguishes between 4 different results:
- I have authenticated the user
- I don't know how to authenticate that user. Try another realm.
- I tried to authenticate the user, but failed. Try another realm.
- I tried to authenticate the user, but failed. Fail the authentication attempt.

Original commit: elastic/x-pack-elasticsearch@f796949cfb
This commit is contained in:
Tim Vernum 2017-07-13 14:24:08 +10:00 committed by GitHub
parent a36121a725
commit e4c8851a24
23 changed files with 590 additions and 374 deletions

View File

@ -0,0 +1,130 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.authc;
import java.util.Objects;
import org.elasticsearch.common.inject.internal.Nullable;
import org.elasticsearch.xpack.security.user.User;
/**
* Represents the result of an authentication attempt.
* This allows a {@link Realm} to respond in 3 different ways (without needing to
* resort to {@link org.elasticsearch.action.ActionListener#onFailure(Exception)})
* <ol>
* <li>Successful authentication of a user</li>
* <li>Unable to authenticate user, try another realm (optionally with a diagnostic message)</li>
* <li>Unable to authenticate user, terminate authentication (with an error message)</li>
* </ol>
*/
public final class AuthenticationResult {
private static final AuthenticationResult NOT_HANDLED = new AuthenticationResult(Status.CONTINUE, null, null, null);
public enum Status {
SUCCESS,
CONTINUE,
TERMINATE,
}
private final Status status;
private final User user;
private final String message;
private final Exception exception;
private AuthenticationResult(Status status, @Nullable User user, @Nullable String message, @Nullable Exception exception) {
this.status = status;
this.user = user;
this.message = message;
this.exception = exception;
}
public Status getStatus() {
return status;
}
public User getUser() {
return user;
}
public String getMessage() {
return message;
}
public Exception getException() {
return exception;
}
/**
* Creates an {@code AuthenticationResult} that indicates that the supplied {@link User}
* has been successfully authenticated.
* <p>
* The {@link #getStatus() status} is set to {@link Status#SUCCESS}.
* </p><p>
* Neither the {@link #getMessage() message} nor {@link #getException() exception} are populated.
* </p>
* @param user The user that was authenticated. Cannot be {@code null}.
*/
public static AuthenticationResult success(User user) {
Objects.requireNonNull(user);
return new AuthenticationResult(Status.SUCCESS, user, null, null);
}
/**
* Creates an {@code AuthenticationResult} that indicates that the realm did not handle the
* authentication request in any way, and has no failure messages.
* <p>
* The {@link #getStatus() status} is set to {@link Status#CONTINUE}.
* </p><p>
* The {@link #getMessage() message}, {@link #getException() exception}, and {@link #getUser() user} are all set to {@code null}.
* </p>
*/
public static AuthenticationResult notHandled() {
return NOT_HANDLED;
}
/**
* Creates an {@code AuthenticationResult} that indicates that the realm attempted to handle the authentication request but was
* unsuccessful. The reason for the failure is given in the supplied message and optional exception.
* <p>
* The {@link #getStatus() status} is set to {@link Status#CONTINUE}.
* </p><p>
* The {@link #getUser() user} is not populated.
* </p>
*/
public static AuthenticationResult unsuccessful(String message, @Nullable Exception cause) {
Objects.requireNonNull(message);
return new AuthenticationResult(Status.CONTINUE, null, message, cause);
}
/**
* Creates an {@code AuthenticationResult} that indicates that the realm attempted to handle the authentication request, was
* unsuccessful and wants to terminate this authentication request.
* The reason for the failure is given in the supplied message and optional exception.
* <p>
* The {@link #getStatus() status} is set to {@link Status#TERMINATE}.
* </p><p>
* The {@link #getUser() user} is not populated.
* </p>
*/
public static AuthenticationResult terminate(String message, @Nullable Exception cause) {
return new AuthenticationResult(Status.TERMINATE, null, message, cause);
}
public boolean isAuthenticated() {
return status == Status.SUCCESS;
}
@Override
public String toString() {
return "AuthenticationResult{" +
"status=" + status +
", user=" + user +
", message=" + message +
", exception=" + exception +
'}';
}
}

View File

@ -5,6 +5,13 @@
*/
package org.elasticsearch.xpack.security.authc;
import java.net.InetSocketAddress;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.apache.logging.log4j.util.Supplier;
import org.elasticsearch.ElasticsearchSecurityException;
@ -27,15 +34,10 @@ import org.elasticsearch.xpack.security.audit.AuditTrail;
import org.elasticsearch.xpack.security.audit.AuditTrailService;
import org.elasticsearch.xpack.security.authc.Authentication.RealmRef;
import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken;
import org.elasticsearch.xpack.security.support.Exceptions;
import org.elasticsearch.xpack.security.user.AnonymousUser;
import org.elasticsearch.xpack.security.user.User;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import static org.elasticsearch.xpack.security.Security.setting;
/**
@ -170,7 +172,7 @@ public class AuthenticationService extends AbstractComponent {
* <li>look for a user token</li>
* <li>token extraction {@link #extractToken(Consumer)}</li>
* <li>token authentication {@link #consumeToken(AuthenticationToken)}</li>
* <li>user lookup for run as if necessary {@link #consumeUser(User)} and
* <li>user lookup for run as if necessary {@link #consumeUser(User, Map)} and
* {@link #lookupRunAsUser(User, String, Consumer)}</li>
* <li>write authentication into the context {@link #finishAuthentication(User)}</li>
* </ol>
@ -255,8 +257,8 @@ public class AuthenticationService extends AbstractComponent {
/**
* Consumes the {@link AuthenticationToken} provided by the caller. In the case of a {@code null} token, {@link #handleNullToken()}
* is called. In the case of a {@code non-null} token, the realms are iterated over and the first realm that returns a non-null
* {@link User} is the authenticating realm and iteration is stopped. This user is then passed to {@link #consumeUser(User)} if no
* exception was caught while trying to authenticate the token
* {@link User} is the authenticating realm and iteration is stopped. This user is then passed to {@link #consumeUser(User, Map)}
* if no exception was caught while trying to authenticate the token
*/
private void consumeToken(AuthenticationToken token) {
if (token == null) {
@ -264,22 +266,33 @@ public class AuthenticationService extends AbstractComponent {
} else {
authenticationToken = token;
final List<Realm> realmsList = realms.asList();
final Map<Realm, Tuple<String, Exception>> messages = new LinkedHashMap<>();
final BiConsumer<Realm, ActionListener<User>> realmAuthenticatingConsumer = (realm, userListener) -> {
if (realm.supports(authenticationToken)) {
realm.authenticate(authenticationToken, ActionListener.wrap((user) -> {
if (user == null) {
// the user was not authenticated, call this so we can audit the correct event
request.realmAuthenticationFailed(authenticationToken, realm.name());
} else {
realm.authenticate(authenticationToken, ActionListener.wrap((result) -> {
assert result != null : "Realm " + realm + " produced a null authentication result";
if (result.getStatus() == AuthenticationResult.Status.SUCCESS) {
// user was authenticated, populate the authenticated by information
authenticatedBy = new RealmRef(realm.name(), realm.type(), nodeName);
userListener.onResponse(result.getUser());
} else {
// the user was not authenticated, call this so we can audit the correct event
request.realmAuthenticationFailed(authenticationToken, realm.name());
if (result.getStatus() == AuthenticationResult.Status.TERMINATE) {
logger.info("Authentication of [{}] was terminated by realm [{}] - {}",
authenticationToken.principal(), realm.name(), result.getMessage());
userListener.onFailure(Exceptions.authenticationError(result.getMessage(), result.getException()));
} else {
if (result.getMessage() != null) {
messages.put(realm, new Tuple<>(result.getMessage(), result.getException()));
}
userListener.onResponse(null);
}
}
userListener.onResponse(user);
}, (ex) -> {
logger.warn(
"An error occurred while attempting to authenticate [{}] against realm [{}] - {}",
authenticationToken.principal(), realm.name(), ex);
logger.debug("Authentication failed due to exception", ex);
logger.warn(new ParameterizedMessage(
"An error occurred while attempting to authenticate [{}] against realm [{}]",
authenticationToken.principal(), realm.name()), ex);
userListener.onFailure(ex);
}), request);
} else {
@ -287,7 +300,8 @@ public class AuthenticationService extends AbstractComponent {
}
};
final IteratingActionListener<User, Realm> authenticatingListener =
new IteratingActionListener<>(ActionListener.wrap(this::consumeUser,
new IteratingActionListener<>(ActionListener.wrap(
(user) -> consumeUser(user, messages),
(e) -> listener.onFailure(request.exceptionProcessingRequest(e, token))),
realmAuthenticatingConsumer, realmsList, threadContext);
try {
@ -342,10 +356,9 @@ public class AuthenticationService extends AbstractComponent {
* functionality is in use. When run as is not in use, {@link #finishAuthentication(User)} is called, otherwise we try to lookup
* the run as user in {@link #lookupRunAsUser(User, String, Consumer)}
*/
private void consumeUser(User user) {
private void consumeUser(User user, Map<Realm, Tuple<String, Exception>> messages) {
if (user == null) {
final Map<Realm, Tuple<String, Exception>> failureDetails = Realm.getAuthenticationFailureDetails(threadContext);
failureDetails.forEach((realm, tuple) -> {
messages.forEach((realm, tuple) -> {
final String message = tuple.v1();
final String cause = tuple.v2() == null ? "" : " (Caused by " + tuple.v2() + ")";
logger.warn("Authentication to realm {} failed - {}{}", realm.name(), message, cause);

View File

@ -5,18 +5,14 @@
*/
package org.elasticsearch.xpack.security.authc;
import java.util.HashMap;
import java.util.Map;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.inject.internal.Nullable;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.xpack.security.user.User;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* An authentication mechanism to which the default authentication {@link org.elasticsearch.xpack.security.authc.AuthenticationService
* service } delegates the authentication process. Different realms may be defined, each may be based on different
@ -24,8 +20,6 @@ import java.util.Map;
*/
public abstract class Realm implements Comparable<Realm> {
private static final String AUTHENTICATION_FAILURES_KEY = "_xpack_security_auth_failures";
protected final Logger logger;
protected final String type;
protected RealmConfig config;
@ -77,16 +71,35 @@ public abstract class Realm implements Comparable<Realm> {
public abstract AuthenticationToken token(ThreadContext context);
/**
* Authenticates the given token in an asynchronous fashion. A successful authentication will call the
* {@link ActionListener#onResponse} with the User associated with the given token. An unsuccessful authentication calls
* with {@code null} on the argument.
* Authenticates the given token in an asynchronous fashion.
* <p>
* A successful authentication will call {@link ActionListener#onResponse} with a
* {@link AuthenticationResult#success successful} result, which includes the user associated with the given token.
* <br>
* If the realm does not support, or cannot handle the token, it will call {@link ActionListener#onResponse} with a
* {@link AuthenticationResult#notHandled not-handled} result.
* This can include cases where the token identifies as user that is not known by this realm.
* <br>
* If the realm can handle the token, but authentication failed it will typically call {@link ActionListener#onResponse} with a
* {@link AuthenticationResult#unsuccessful failure} result, which includes a diagnostic message regarding the failure.
* This can include cases where the token identifies a valid user, but has an invalid password.
* <br>
* If the realm wishes to assert that it has the exclusive right to handle the provided token, but authentication was not successful
* it typically call {@link ActionListener#onResponse} with a
* {@link AuthenticationResult#terminate termination} result, which includes a diagnostic message regarding the failure.
* This can include cases where the token identifies a valid user, but has an invalid password and no other realm is allowed to
* authenticate that user.
* </p>
* <p>
* The remote address should be {@code null} if the request initiated from the local node.
* </p>
*
* The remote address should be null if the request initiated from the local node.
* @param token The authentication token
* @param listener The listener to pass the authentication result to
* @param incomingRequest the request that is being authenticated
*/
public abstract void authenticate(AuthenticationToken token, ActionListener<User> listener, IncomingRequest incomingRequest);
public abstract void authenticate(AuthenticationToken token, ActionListener<AuthenticationResult> listener,
IncomingRequest incomingRequest);
/**
* Looks up the user identified the String identifier. A successful lookup will call the {@link ActionListener#onResponse}
@ -117,35 +130,11 @@ public abstract class Realm implements Comparable<Realm> {
/**
* Constructs a realm which will be used for authentication.
*
* @param config The configuration for the realm
* @throws Exception an exception may be thrown if there was an error during realm creation
*/
Realm create(RealmConfig config) throws Exception;
}
/**
* Provides a mechanism for a realm to report errors that were handled within a realm, but may
* provide useful diagnostics about why authentication failed.
*/
protected final void setFailedAuthenticationDetails(String message, @Nullable Exception cause) {
final ThreadContext threadContext = config.threadContext();
Map<Realm, Tuple<String, Exception>> failures = threadContext.getTransient(AUTHENTICATION_FAILURES_KEY);
if (failures == null) {
failures = new LinkedHashMap<>();
threadContext.putTransient(AUTHENTICATION_FAILURES_KEY, failures);
}
failures.put(this, new Tuple<>(message, cause));
}
/**
* Retrieves any authentication failures messages that were set using {@link #setFailedAuthenticationDetails(String, Exception)}
*/
static Map<Realm, Tuple<String, Exception>> getAuthenticationFailureDetails(ThreadContext threadContext) {
final Map<Realm, Tuple<String, Exception>> failures = threadContext.getTransient(AUTHENTICATION_FAILURES_KEY);
if (failures == null) {
return Collections.emptyMap();
}
return failures;
}
}

View File

@ -7,6 +7,7 @@ package org.elasticsearch.xpack.security.authc.esnative;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.xpack.security.authc.AuthenticationResult;
import org.elasticsearch.xpack.security.authc.IncomingRequest;
import org.elasticsearch.xpack.security.authc.RealmConfig;
import org.elasticsearch.xpack.security.authc.support.CachingUsernamePasswordRealm;
@ -35,7 +36,8 @@ public class NativeRealm extends CachingUsernamePasswordRealm {
}
@Override
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<User> listener, IncomingRequest incomingRequest) {
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<AuthenticationResult> listener,
IncomingRequest incomingRequest) {
userStore.verifyPassword(token.principal(), token.credentials(), listener);
}

View File

@ -42,6 +42,7 @@ import org.elasticsearch.xpack.security.action.realm.ClearRealmCacheResponse;
import org.elasticsearch.xpack.security.action.user.ChangePasswordRequest;
import org.elasticsearch.xpack.security.action.user.DeleteUserRequest;
import org.elasticsearch.xpack.security.action.user.PutUserRequest;
import org.elasticsearch.xpack.security.authc.AuthenticationResult;
import org.elasticsearch.xpack.security.authc.ContainerSettings;
import org.elasticsearch.xpack.security.authc.support.Hasher;
import org.elasticsearch.xpack.security.client.SecurityClient;
@ -512,14 +513,14 @@ public class NativeUsersStore extends AbstractComponent {
* @param username username to lookup the user by
* @param password the plaintext password to verify
*/
void verifyPassword(String username, final SecureString password, ActionListener<User> listener) {
void verifyPassword(String username, final SecureString password, ActionListener<AuthenticationResult> listener) {
getUserAndPassword(username, ActionListener.wrap((userAndPassword) -> {
if (userAndPassword == null || userAndPassword.passwordHash() == null) {
listener.onResponse(null);
} else if (hasher.verify(password, userAndPassword.passwordHash())) {
listener.onResponse(userAndPassword.user());
listener.onResponse(AuthenticationResult.success(userAndPassword.user()));
} else {
listener.onResponse(null);
listener.onResponse(AuthenticationResult.unsuccessful("Password authentication failed for " + username, null));
}
}, listener::onFailure));
}

View File

@ -23,6 +23,7 @@ import org.elasticsearch.xpack.XPackSettings;
import org.elasticsearch.xpack.security.InternalClient;
import org.elasticsearch.xpack.security.Security;
import org.elasticsearch.xpack.security.SecurityLifecycleService;
import org.elasticsearch.xpack.security.authc.AuthenticationResult;
import org.elasticsearch.xpack.security.action.user.ChangePasswordAction;
import org.elasticsearch.xpack.security.action.user.ChangePasswordRequest;
import org.elasticsearch.xpack.security.action.user.ChangePasswordResponse;
@ -88,7 +89,8 @@ public class ReservedRealm extends CachingUsernamePasswordRealm {
}
@Override
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<User> listener, IncomingRequest incomingRequest) {
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<AuthenticationResult> listener,
IncomingRequest incomingRequest) {
if (incomingRequest.getType() != IncomingRequest.RequestType.REST) {
doAuthenticate(token, listener, false);
} else {
@ -105,14 +107,14 @@ public class ReservedRealm extends CachingUsernamePasswordRealm {
}
}
private void doAuthenticate(UsernamePasswordToken token, ActionListener<User> listener, boolean acceptEmptyPassword) {
private void doAuthenticate(UsernamePasswordToken token, ActionListener<AuthenticationResult> listener, boolean acceptEmptyPassword) {
if (realmEnabled == false) {
listener.onResponse(null);
listener.onResponse(AuthenticationResult.notHandled());
} else if (isReserved(token.principal(), config.globalSettings()) == false) {
listener.onResponse(null);
listener.onResponse(AuthenticationResult.notHandled());
} else {
getUserInfo(token.principal(), ActionListener.wrap((userInfo) -> {
Runnable action;
AuthenticationResult result;
if (userInfo != null) {
try {
if (userInfo.hasEmptyPassword) {
@ -120,21 +122,18 @@ public class ReservedRealm extends CachingUsernamePasswordRealm {
// Accepting the OLD_DEFAULT_PASSWORD_HASH is a transition step. We do not want to support
// this in a release.
if (isSetupMode(token.principal(), acceptEmptyPassword) == false) {
action = () -> listener.onFailure(Exceptions.authenticationError("failed to authenticate user [{}]",
token.principal()));
result = AuthenticationResult.terminate("failed to authenticate user [" + token.principal() + "]", null);
} else if (verifyPassword(userInfo, token)
|| Hasher.BCRYPT.verify(token.credentials(), OLD_DEFAULT_PASSWORD_HASH)) {
action = () -> listener.onResponse(getUser(token.principal(), userInfo));
result = AuthenticationResult.success(getUser(token.principal(), userInfo));
} else {
action = () -> listener.onFailure(Exceptions.authenticationError("failed to authenticate user [{}]",
token.principal()));
result = AuthenticationResult.terminate("failed to authenticate user [" + token.principal() + "]", null);
}
} else if (verifyPassword(userInfo, token)) {
final User user = getUser(token.principal(), userInfo);
action = () -> listener.onResponse(user);
result = AuthenticationResult.success(user);
} else {
action = () -> listener.onFailure(Exceptions.authenticationError("failed to authenticate user [{}]",
token.principal()));
result = AuthenticationResult.terminate("failed to authenticate user [" + token.principal() + "]", null);
}
} finally {
if (userInfo.passwordHash != EMPTY_PASSWORD_HASH && userInfo.passwordHash != OLD_DEFAULT_PASSWORD_HASH) {
@ -142,11 +141,10 @@ public class ReservedRealm extends CachingUsernamePasswordRealm {
}
}
} else {
action = () -> listener.onFailure(Exceptions.authenticationError("failed to authenticate user [{}]",
token.principal()));
result = AuthenticationResult.terminate("failed to authenticate user [" + token.principal() + "]", null);
}
// we want the finally block to clear out the chars before we proceed further so we execute the action here
action.run();
// we want the finally block to clear out the chars before we proceed further so we handle the result here
listener.onResponse(result);
}, listener::onFailure));
}
}

View File

@ -11,6 +11,7 @@ import java.util.Set;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.security.authc.AuthenticationResult;
import org.elasticsearch.xpack.security.authc.IncomingRequest;
import org.elasticsearch.xpack.security.authc.RealmConfig;
import org.elasticsearch.xpack.security.authc.support.CachingUsernamePasswordRealm;
@ -38,13 +39,13 @@ public class FileRealm extends CachingUsernamePasswordRealm {
}
@Override
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<User> listener, IncomingRequest incomingRequest) {
if (userPasswdStore.verifyPassword(token.principal(), token.credentials())) {
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<AuthenticationResult> listener,
IncomingRequest incomingRequest) {
final AuthenticationResult result = userPasswdStore.verifyPassword(token.principal(), token.credentials(), () -> {
String[] roles = userRolesStore.roles(token.principal());
listener.onResponse(new User(token.principal(), roles));
} else {
listener.onResponse(null);
}
return new User(token.principal(), roles);
});
listener.onResponse(result);
}
@Override

View File

@ -18,11 +18,13 @@ import org.elasticsearch.watcher.FileWatcher;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.XPackPlugin;
import org.elasticsearch.xpack.XPackSettings;
import org.elasticsearch.xpack.security.authc.AuthenticationResult;
import org.elasticsearch.xpack.security.authc.RealmConfig;
import org.elasticsearch.xpack.security.authc.support.Hasher;
import org.elasticsearch.xpack.security.support.NoOpLogger;
import org.elasticsearch.xpack.security.support.Validation;
import org.elasticsearch.xpack.security.support.Validation.Users;
import org.elasticsearch.xpack.security.user.User;
import java.io.IOException;
import java.io.PrintWriter;
@ -78,16 +80,15 @@ public class FileUserPasswdStore {
return users.size();
}
public boolean verifyPassword(String username, SecureString password) {
public AuthenticationResult verifyPassword(String username, SecureString password, java.util.function.Supplier<User> user) {
char[] hash = users.get(username);
if (hash == null) {
return false;
return AuthenticationResult.notHandled();
}
if (hasher.verify(password, hash) == false) {
logger.debug("User [{}] exists in file but authentication failed", username);
return false;
return AuthenticationResult.unsuccessful("Password authentication failed for " + username, null);
}
return true;
return AuthenticationResult.success(user.get());
}
public boolean userExists(String username) {

View File

@ -28,6 +28,7 @@ import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.threadpool.ThreadPool.Names;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.security.authc.AuthenticationResult;
import org.elasticsearch.xpack.security.authc.IncomingRequest;
import org.elasticsearch.xpack.security.authc.RealmConfig;
import org.elasticsearch.xpack.security.authc.RealmSettings;
@ -142,7 +143,8 @@ public final class LdapRealm extends CachingUsernamePasswordRealm {
* This user will then be passed to the listener
*/
@Override
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<User> listener, IncomingRequest incomingRequest) {
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<AuthenticationResult> listener,
IncomingRequest incomingRequest) {
// we submit to the threadpool because authentication using LDAP will execute blocking I/O for a bind request and we don't want
// network threads stuck waiting for a socket to connect. After the bind, then all interaction with LDAP should be async
final CancellableLdapRunnable cancellableLdapRunnable = new CancellableLdapRunnable(listener,
@ -153,17 +155,19 @@ public final class LdapRealm extends CachingUsernamePasswordRealm {
}
@Override
protected void doLookupUser(String username, ActionListener<User> listener) {
protected void doLookupUser(String username, ActionListener<User> userActionListener) {
if (sessionFactory.supportsUnauthenticatedSession()) {
// we submit to the threadpool because authentication using LDAP will execute blocking I/O for a bind request and we don't want
// network threads stuck waiting for a socket to connect. After the bind, then all interaction with LDAP should be async
final CancellableLdapRunnable cancellableLdapRunnable = new CancellableLdapRunnable(listener,
final ActionListener<AuthenticationResult> sessionListener = ActionListener.wrap(AuthenticationResult::getUser,
userActionListener::onFailure);
final CancellableLdapRunnable cancellableLdapRunnable = new CancellableLdapRunnable(userActionListener,
() -> sessionFactory.unauthenticatedSession(username,
contextPreservingListener(new LdapSessionActionListener("lookup", username, listener))), logger);
contextPreservingListener(new LdapSessionActionListener("lookup", username, sessionListener))), logger);
threadPool.generic().execute(cancellableLdapRunnable);
threadPool.schedule(executionTimeout, Names.SAME, cancellableLdapRunnable::maybeTimeout);
} else {
listener.onResponse(null);
userActionListener.onResponse(null);
}
}
@ -188,7 +192,8 @@ public final class LdapRealm extends CachingUsernamePasswordRealm {
return usage;
}
private static void buildUser(LdapSession session, String username, ActionListener<User> listener, UserRoleMapper roleMapper) {
private static void buildUser(LdapSession session, String username, ActionListener<AuthenticationResult> listener,
UserRoleMapper roleMapper) {
if (session == null) {
listener.onResponse(null);
} else {
@ -210,8 +215,8 @@ public final class LdapRealm extends CachingUsernamePasswordRealm {
roles -> {
IOUtils.close(session);
String[] rolesArray = roles.toArray(new String[roles.size()]);
listener.onResponse(
new User(username, rolesArray, null, null, metadata, true)
listener.onResponse(AuthenticationResult.success(
new User(username, rolesArray, null, null, metadata, true))
);
}, onFailure
));
@ -236,21 +241,21 @@ public final class LdapRealm extends CachingUsernamePasswordRealm {
private final AtomicReference<LdapSession> ldapSessionAtomicReference = new AtomicReference<>();
private String action;
private final String username;
private final ActionListener<User> userActionListener;
private final ActionListener<AuthenticationResult> resultListener;
LdapSessionActionListener(String action, String username, ActionListener<User> userActionListener) {
LdapSessionActionListener(String action, String username, ActionListener<AuthenticationResult> resultListener) {
this.action = action;
this.username = username;
this.userActionListener = userActionListener;
this.resultListener = resultListener;
}
@Override
public void onResponse(LdapSession session) {
if (session == null) {
userActionListener.onResponse(null);
resultListener.onResponse(null);
} else {
ldapSessionAtomicReference.set(session);
buildUser(session, username, userActionListener, roleMapper);
buildUser(session, username, resultListener, roleMapper);
}
}
@ -262,8 +267,7 @@ public final class LdapRealm extends CachingUsernamePasswordRealm {
if (logger.isDebugEnabled()) {
logger.debug(new ParameterizedMessage("Exception occurred during {} for {}", action, LdapRealm.this), e);
}
setFailedAuthenticationDetails(action + " failed", e);
userActionListener.onResponse(null);
resultListener.onResponse(AuthenticationResult.unsuccessful(action + " failed", e));
}
}
@ -276,11 +280,11 @@ public final class LdapRealm extends CachingUsernamePasswordRealm {
static class CancellableLdapRunnable extends AbstractRunnable {
private final Runnable in;
private final ActionListener<User> listener;
private final ActionListener<?> listener;
private final Logger logger;
private final AtomicReference<LdapRunnableState> state = new AtomicReference<>(LdapRunnableState.AWAITING_EXECUTION);
CancellableLdapRunnable(ActionListener<User> listener, Runnable in, Logger logger) {
CancellableLdapRunnable(ActionListener<?> listener, Runnable in, Logger logger) {
this.listener = listener;
this.in = in;
this.logger = logger;

View File

@ -5,6 +5,18 @@
*/
package org.elasticsearch.xpack.security.authc.pki;
import javax.net.ssl.X509TrustManager;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.apache.logging.log4j.util.Supplier;
@ -17,29 +29,18 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.env.Environment;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.security.authc.IncomingRequest;
import org.elasticsearch.xpack.security.authc.support.UserRoleMapper;
import org.elasticsearch.xpack.security.authc.support.mapper.CompositeRoleMapper;
import org.elasticsearch.xpack.security.authc.support.mapper.NativeRoleMappingStore;
import org.elasticsearch.xpack.ssl.CertUtils;
import org.elasticsearch.xpack.ssl.SSLConfigurationSettings;
import org.elasticsearch.xpack.security.user.User;
import org.elasticsearch.xpack.security.authc.AuthenticationResult;
import org.elasticsearch.xpack.security.authc.AuthenticationToken;
import org.elasticsearch.xpack.security.authc.IncomingRequest;
import org.elasticsearch.xpack.security.authc.Realm;
import org.elasticsearch.xpack.security.authc.RealmConfig;
import org.elasticsearch.xpack.security.authc.RealmSettings;
import javax.net.ssl.X509TrustManager;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.elasticsearch.xpack.security.authc.support.UserRoleMapper;
import org.elasticsearch.xpack.security.authc.support.mapper.CompositeRoleMapper;
import org.elasticsearch.xpack.security.authc.support.mapper.NativeRoleMappingStore;
import org.elasticsearch.xpack.security.user.User;
import org.elasticsearch.xpack.ssl.CertUtils;
import org.elasticsearch.xpack.ssl.SSLConfigurationSettings;
public class PkiRealm extends Realm {
@ -82,17 +83,19 @@ public class PkiRealm extends Realm {
}
@Override
public void authenticate(AuthenticationToken authToken, ActionListener<User> listener, IncomingRequest incomingRequest) {
public void authenticate(AuthenticationToken authToken, ActionListener<AuthenticationResult> listener,
IncomingRequest incomingRequest) {
X509AuthenticationToken token = (X509AuthenticationToken)authToken;
if (isCertificateChainTrusted(trustManager, token, logger) == false) {
listener.onResponse(null);
listener.onResponse(AuthenticationResult.unsuccessful("Certificate for " + token.dn() + " is not trusted", null));
} else {
final Map<String, Object> metadata = Collections.singletonMap("pki_dn", token.dn());
final UserRoleMapper.UserData user = new UserRoleMapper.UserData(token.principal(),
token.dn(), Collections.emptySet(), metadata, this.config);
roleMapper.resolveRoles(user, ActionListener.wrap(
roles -> listener.onResponse(new User(token.principal(),
roles.toArray(new String[roles.size()]), null, null, metadata, true)),
roles -> listener.onResponse(AuthenticationResult.success(
new User(token.principal(), roles.toArray(new String[roles.size()]), null, null, metadata, true)
)),
listener::onFailure
));
}

View File

@ -5,23 +5,25 @@
*/
package org.elasticsearch.xpack.security.authc.support;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.common.cache.Cache;
import org.elasticsearch.common.cache.CacheBuilder;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.xpack.security.authc.AuthenticationResult;
import org.elasticsearch.xpack.security.authc.AuthenticationToken;
import org.elasticsearch.xpack.security.authc.IncomingRequest;
import org.elasticsearch.xpack.security.authc.RealmConfig;
import org.elasticsearch.xpack.security.user.User;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
public abstract class CachingUsernamePasswordRealm extends UsernamePasswordRealm implements CachingRealm {
public static final Setting<String> CACHE_HASH_ALGO_SETTING = Setting.simpleString("cache.hash_algo", Setting.Property.NodeScope);
@ -73,7 +75,8 @@ public abstract class CachingUsernamePasswordRealm extends UsernamePasswordRealm
* @param incomingRequest the request that is being authenticated
*/
@Override
public final void authenticate(AuthenticationToken authToken, ActionListener<User> listener, IncomingRequest incomingRequest) {
public final void authenticate(AuthenticationToken authToken, ActionListener<AuthenticationResult> listener,
IncomingRequest incomingRequest) {
UsernamePasswordToken token = (UsernamePasswordToken) authToken;
try {
if (cache == null) {
@ -87,69 +90,74 @@ public abstract class CachingUsernamePasswordRealm extends UsernamePasswordRealm
}
}
private void authenticateWithCache(UsernamePasswordToken token, ActionListener<User> listener, IncomingRequest incomingRequest) {
private void authenticateWithCache(UsernamePasswordToken token, ActionListener<AuthenticationResult> listener,
IncomingRequest incomingRequest) {
UserWithHash userWithHash = cache.get(token.principal());
if (userWithHash == null) {
if (logger.isDebugEnabled()) {
logger.debug("user [{}] not found in cache for realm [{}], proceeding with normal authentication",
token.principal(), name());
}
doAuthenticateAndCache(token, ActionListener.wrap((user) -> {
if (user != null) {
doAuthenticateAndCache(token, ActionListener.wrap((result) -> {
if (result.isAuthenticated()) {
final User user = result.getUser();
logger.debug("realm [{}] authenticated user [{}], with roles [{}]", name(), token.principal(), user.roles());
}
listener.onResponse(user);
listener.onResponse(result);
}, listener::onFailure), incomingRequest);
} else if (userWithHash.hasHash()) {
if (userWithHash.verify(token.credentials())) {
if (userWithHash.user.enabled()) {
User user = userWithHash.user;
logger.debug("realm [{}] authenticated user [{}], with roles [{}]", name(), token.principal(), user.roles());
listener.onResponse(user);
listener.onResponse(AuthenticationResult.success(user));
} else {
// We successfully authenticated, but the cached user is disabled.
// Reload the primary record to check whether the user is still disabled
cache.invalidate(token.principal());
doAuthenticateAndCache(token, ActionListener.wrap((user) -> {
if (user != null) {
doAuthenticateAndCache(token, ActionListener.wrap((result) -> {
if (result.isAuthenticated()) {
final User user = result.getUser();
logger.debug("realm [{}] authenticated user [{}] (enabled:{}), with roles [{}]", name(), token.principal(),
user.enabled(), user.roles());
}
listener.onResponse(user);
listener.onResponse(result);
}, listener::onFailure), incomingRequest);
}
} else {
cache.invalidate(token.principal());
doAuthenticateAndCache(token, ActionListener.wrap((user) -> {
if (user != null) {
doAuthenticateAndCache(token, ActionListener.wrap((result) -> {
if (result.isAuthenticated()) {
final User user = result.getUser();
logger.debug("cached user's password changed. realm [{}] authenticated user [{}], with roles [{}]",
name(), token.principal(), user.roles());
}
listener.onResponse(user);
listener.onResponse(result);
}, listener::onFailure), incomingRequest);
}
} else {
cache.invalidate(token.principal());
doAuthenticateAndCache(token, ActionListener.wrap((user) -> {
if (user != null) {
doAuthenticateAndCache(token, ActionListener.wrap((result) -> {
if (result.isAuthenticated()) {
final User user = result.getUser();
logger.debug("cached user came from a lookup and could not be used for authentication. " +
"realm [{}] authenticated user [{}] with roles [{}]", name(), token.principal(), user.roles());
}
listener.onResponse(user);
listener.onResponse(result);
}, listener::onFailure), incomingRequest);
}
}
private void doAuthenticateAndCache(UsernamePasswordToken token, ActionListener<User> listener, IncomingRequest incomingRequest) {
ActionListener<User> wrapped = ActionListener.wrap((user) -> {
if (user == null) {
listener.onResponse(null);
} else {
UserWithHash userWithHash = new UserWithHash(user, token.credentials(), hasher);
private void doAuthenticateAndCache(UsernamePasswordToken token, ActionListener<AuthenticationResult> listener,
IncomingRequest incomingRequest) {
ActionListener<AuthenticationResult> wrapped = ActionListener.wrap((result) -> {
Objects.requireNonNull(result, "AuthenticationResult cannot be null");
if (result.getStatus() == AuthenticationResult.Status.SUCCESS) {
UserWithHash userWithHash = new UserWithHash(result.getUser(), token.credentials(), hasher);
// it doesn't matter if we already computed it elsewhere
cache.put(token.principal(), userWithHash);
listener.onResponse(user);
}
listener.onResponse(result);
}, listener::onFailure);
doAuthenticate(token, wrapped, incomingRequest);
@ -162,7 +170,8 @@ public abstract class CachingUsernamePasswordRealm extends UsernamePasswordRealm
return stats;
}
protected abstract void doAuthenticate(UsernamePasswordToken token, ActionListener<User> listener, IncomingRequest incomingRequest);
protected abstract void doAuthenticate(UsernamePasswordToken token, ActionListener<AuthenticationResult> listener,
IncomingRequest incomingRequest);
@Override
public final void lookupUser(String username, ActionListener<User> listener) {

View File

@ -18,6 +18,7 @@ import org.elasticsearch.test.SecurityIntegTestCase;
import org.elasticsearch.test.SecuritySettingsSource;
import org.elasticsearch.xpack.security.action.realm.ClearRealmCacheRequest;
import org.elasticsearch.xpack.security.action.realm.ClearRealmCacheResponse;
import org.elasticsearch.xpack.security.authc.AuthenticationResult;
import org.elasticsearch.xpack.security.authc.IncomingRequest;
import org.elasticsearch.xpack.security.authc.Realm;
import org.elasticsearch.xpack.security.authc.Realms;
@ -234,9 +235,9 @@ public class ClearRealmsCacheTests extends SecurityIntegTestCase {
Map<String, Map<Realm, User>> users = new HashMap<>();
for (Realm realm : realms) {
for (String username : usernames) {
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
realm.authenticate(tokens.get(username), future, mock(IncomingRequest.class));
User user = future.actionGet();
User user = future.actionGet().getUser();
assertThat(user, notNullValue());
Map<Realm, User> realmToUser = users.get(username);
if (realmToUser == null) {
@ -251,9 +252,9 @@ public class ClearRealmsCacheTests extends SecurityIntegTestCase {
for (String username : usernames) {
for (Realm realm : realms) {
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
realm.authenticate(tokens.get(username), future, mock(IncomingRequest.class));
User user = future.actionGet();
User user = future.actionGet().getUser();
assertThat(user, sameInstance(users.get(username).get(realm)));
}
}
@ -264,9 +265,9 @@ public class ClearRealmsCacheTests extends SecurityIntegTestCase {
// now, user_a should have been evicted, but user_b should still be cached
for (String username : usernames) {
for (Realm realm : realms) {
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
realm.authenticate(tokens.get(username), future, mock(IncomingRequest.class));
User user = future.actionGet();
User user = future.actionGet().getUser();
assertThat(user, notNullValue());
scenario.assertEviction(users.get(username).get(realm), user);
}

View File

@ -5,6 +5,18 @@
*/
package org.elasticsearch.xpack.security.authc;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.time.Clock;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.lucene.util.SetOnce;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ElasticsearchSecurityException;
@ -46,18 +58,6 @@ import org.elasticsearch.xpack.security.user.User;
import org.junit.After;
import org.junit.Before;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.time.Clock;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.elasticsearch.test.SecurityTestsUtils.assertAuthenticationException;
import static org.elasticsearch.xpack.security.support.Exceptions.authenticationError;
import static org.hamcrest.Matchers.arrayContaining;
@ -876,7 +876,11 @@ public class AuthenticationServiceTests extends ESTestCase {
private void mockAuthenticate(Realm realm, AuthenticationToken token, User user) {
doAnswer((i) -> {
ActionListener listener = (ActionListener) i.getArguments()[1];
listener.onResponse(user);
if (user == null) {
listener.onResponse(AuthenticationResult.notHandled());
} else {
listener.onResponse(AuthenticationResult.success(user));
}
return null;
}).when(realm).authenticate(eq(token), any(ActionListener.class), any(IncomingRequest.class));
}

View File

@ -436,8 +436,9 @@ public class RealmsTests extends ESTestCase {
}
@Override
public void authenticate(AuthenticationToken token, ActionListener<User> listener, IncomingRequest incomingRequest) {
listener.onResponse(null);
public void authenticate(AuthenticationToken token, ActionListener<AuthenticationResult> listener,
IncomingRequest incomingRequest) {
listener.onResponse(AuthenticationResult.notHandled());
}
@Override

View File

@ -5,9 +5,18 @@
*/
package org.elasticsearch.xpack.security.authc.esnative;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutionException;
import java.util.function.Predicate;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionFuture;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.PlainActionFuture;
import org.elasticsearch.common.SuppressForbidden;
@ -18,6 +27,7 @@ import org.elasticsearch.env.Environment;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.XPackSettings;
import org.elasticsearch.xpack.security.SecurityLifecycleService;
import org.elasticsearch.xpack.security.authc.AuthenticationResult;
import org.elasticsearch.xpack.security.action.user.ChangePasswordRequest;
import org.elasticsearch.xpack.security.authc.IncomingRequest;
import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore.ReservedUserInfo;
@ -32,17 +42,6 @@ import org.elasticsearch.xpack.security.user.User;
import org.junit.Before;
import org.mockito.ArgumentCaptor;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutionException;
import java.util.function.Predicate;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
@ -89,12 +88,12 @@ public class ReservedRealmTests extends ESTestCase {
new AnonymousUser(Settings.EMPTY), securityLifecycleService, new ThreadContext(Settings.EMPTY));
final String principal = ElasticUser.NAME;
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
InetSocketAddress address = new InetSocketAddress(InetAddress.getLocalHost(), 100);
when(incomingRequest.getRemoteAddress()).thenReturn(address);
when(incomingRequest.getType()).thenReturn(IncomingRequest.RequestType.REST);
reservedRealm.authenticate(new UsernamePasswordToken(principal, EMPTY_PASSWORD), future, incomingRequest);
assertThat(future.get().enabled(), equalTo(false));
assertThat(future.get().getUser().enabled(), equalTo(false));
}
public void testDisableDefaultPasswordAuthentication() throws Throwable {
@ -106,19 +105,9 @@ public class ReservedRealmTests extends ESTestCase {
final ReservedRealm reservedRealm = new ReservedRealm(environment, settings, usersStore, anonymousUser,
securityLifecycleService, new ThreadContext(Settings.EMPTY));
final ActionListener<User> listener = new ActionListener<User>() {
@Override
public void onResponse(User user) {
fail("Authentication should have failed because default-password is not allowed");
}
@Override
public void onFailure(Exception e) {
assertThat(e, instanceOf(ElasticsearchSecurityException.class));
assertThat(e.getMessage(), containsString("failed to authenticate"));
}
};
PlainActionFuture<AuthenticationResult> listener = new PlainActionFuture<>();
reservedRealm.doAuthenticate(new UsernamePasswordToken(expected.principal(), EMPTY_PASSWORD), listener, incomingRequest);
assertFailedAuthentication(listener, expected.principal());
}
public void testElasticEmptyPasswordAuthenticationFailsFromNonLocalhost() throws Throwable {
@ -130,15 +119,13 @@ public class ReservedRealmTests extends ESTestCase {
new ReservedRealm(mock(Environment.class), settings, usersStore,
new AnonymousUser(Settings.EMPTY), securityLifecycleService, new ThreadContext(Settings.EMPTY));
PlainActionFuture<User> listener = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> listener = new PlainActionFuture<>();
InetSocketAddress address = new InetSocketAddress(InetAddress.getByName("128.9.8.1"), 100);
when(incomingRequest.getRemoteAddress()).thenReturn(address);
reservedRealm.doAuthenticate(new UsernamePasswordToken(principal, EMPTY_PASSWORD), listener, incomingRequest);
ElasticsearchSecurityException actual = expectThrows(ElasticsearchSecurityException.class, listener::actionGet);
assertThat(actual.getMessage(), containsString("failed to authenticate user [" + principal));
assertFailedAuthentication(listener, expected.principal());
}
@SuppressForbidden(reason = "allow getting localhost")
@ -151,7 +138,7 @@ public class ReservedRealmTests extends ESTestCase {
new ReservedRealm(mock(Environment.class), settings, usersStore,
new AnonymousUser(Settings.EMPTY), securityLifecycleService, new ThreadContext(Settings.EMPTY));
PlainActionFuture<User> listener = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> listener = new PlainActionFuture<>();
InetSocketAddress address = new InetSocketAddress(InetAddress.getLocalHost(), 100);
@ -159,7 +146,7 @@ public class ReservedRealmTests extends ESTestCase {
when(incomingRequest.getType()).thenReturn(IncomingRequest.RequestType.REST);
reservedRealm.doAuthenticate(new UsernamePasswordToken(principal, EMPTY_PASSWORD), listener, incomingRequest);
User user = listener.actionGet();
User user = listener.actionGet().getUser();
assertEquals(expected, user);
assertNotEquals(new ElasticUser(true, false), user);
@ -177,10 +164,11 @@ public class ReservedRealmTests extends ESTestCase {
final User expected = randomFrom(new ElasticUser(true), new KibanaUser(true), new LogstashSystemUser(true));
final String principal = expected.principal();
PlainActionFuture<User> listener = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> listener = new PlainActionFuture<>();
reservedRealm.doAuthenticate(new UsernamePasswordToken(principal, EMPTY_PASSWORD), listener, mock(IncomingRequest.class));
final User authenticated = listener.actionGet();
assertNull(authenticated);
final AuthenticationResult result = listener.actionGet();
assertThat(result.getStatus(), is(AuthenticationResult.Status.CONTINUE));
assertNull(result.getUser());
verifyZeroInteractions(usersStore);
}
@ -192,7 +180,7 @@ public class ReservedRealmTests extends ESTestCase {
verifySuccessfulAuthentication(false);
}
private void verifySuccessfulAuthentication(boolean enabled) {
private void verifySuccessfulAuthentication(boolean enabled) throws Exception {
final Settings settings = Settings.builder().put(ACCEPT_DEFAULT_PASSWORDS, randomBoolean()).build();
final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), settings, usersStore,
new AnonymousUser(settings), securityLifecycleService, new ThreadContext(Settings.EMPTY));
@ -207,10 +195,9 @@ public class ReservedRealmTests extends ESTestCase {
}).when(usersStore).getReservedUserInfo(eq(principal), any(ActionListener.class));
// test empty password
final PlainActionFuture<User> listener = new PlainActionFuture<>();
final PlainActionFuture<AuthenticationResult> listener = new PlainActionFuture<>();
reservedRealm.doAuthenticate(new UsernamePasswordToken(principal, EMPTY_PASSWORD), listener, incomingRequest);
ElasticsearchSecurityException expected = expectThrows(ElasticsearchSecurityException.class, listener::actionGet);
assertThat(expected.getMessage(), containsString("failed to authenticate user [" + principal));
assertFailedAuthentication(listener, expectedUser.principal());
// the realm assumes it owns the hashed password so it fills it with 0's
doAnswer((i) -> {
@ -220,9 +207,9 @@ public class ReservedRealmTests extends ESTestCase {
}).when(usersStore).getReservedUserInfo(eq(principal), any(ActionListener.class));
// test new password
final PlainActionFuture<User> authListener = new PlainActionFuture<>();
final PlainActionFuture<AuthenticationResult> authListener = new PlainActionFuture<>();
reservedRealm.doAuthenticate(new UsernamePasswordToken(principal, newPassword), authListener, incomingRequest);
final User authenticated = authListener.actionGet();
final User authenticated = authListener.actionGet().getUser();
assertEquals(expectedUser, authenticated);
assertThat(expectedUser.enabled(), is(enabled));
@ -349,30 +336,36 @@ public class ReservedRealmTests extends ESTestCase {
}
@SuppressForbidden(reason = "allow getting localhost")
public void testFailedAuthentication() throws UnknownHostException {
public void testFailedAuthentication() throws Exception {
final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore,
new AnonymousUser(Settings.EMPTY), securityLifecycleService, new ThreadContext(Settings.EMPTY));
InetSocketAddress address = new InetSocketAddress(InetAddress.getLocalHost(), 100);
// maybe cache a successful auth
if (randomBoolean()) {
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
IncomingRequest r = mock(IncomingRequest.class);
when(r.getRemoteAddress()).thenReturn(address);
when(r.getType()).thenReturn(IncomingRequest.RequestType.REST);
reservedRealm.authenticate(new UsernamePasswordToken(ElasticUser.NAME, EMPTY_PASSWORD), future, r);
User user = future.actionGet();
User user = future.actionGet().getUser();
assertEquals(new ElasticUser(true, true), user);
}
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
IncomingRequest r = mock(IncomingRequest.class);
when(r.getRemoteAddress()).thenReturn(address);
when(r.getType()).thenReturn(IncomingRequest.RequestType.REST);
reservedRealm.authenticate(new UsernamePasswordToken(ElasticUser.NAME, new SecureString("foobar".toCharArray())), future, r);
ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, future::actionGet);
assertThat(e.getMessage(), containsString("failed to authenticate"));
assertFailedAuthentication(future, ElasticUser.NAME);
}
private void assertFailedAuthentication(PlainActionFuture<AuthenticationResult> future, String principal) throws Exception {
final AuthenticationResult result = future.get();
assertThat(result.getStatus(), is(AuthenticationResult.Status.TERMINATE));
assertThat(result.getMessage(), containsString("failed to authenticate"));
assertThat(result.getMessage(), containsString(principal));
}
@SuppressWarnings("unchecked")

View File

@ -5,22 +5,25 @@
*/
package org.elasticsearch.xpack.security.authc.file;
import java.util.Locale;
import java.util.Map;
import java.util.function.Supplier;
import org.elasticsearch.action.support.PlainActionFuture;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.env.Environment;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.security.authc.AuthenticationResult;
import org.elasticsearch.xpack.security.authc.IncomingRequest;
import org.elasticsearch.xpack.security.authc.RealmConfig;
import org.elasticsearch.xpack.security.authc.support.Hasher;
import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken;
import org.elasticsearch.xpack.security.user.User;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.junit.Before;
import java.util.Locale;
import java.util.Map;
import org.mockito.stubbing.Answer;
import static org.hamcrest.Matchers.arrayContaining;
import static org.hamcrest.Matchers.equalTo;
@ -29,6 +32,8 @@ import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.sameInstance;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
@ -37,6 +42,12 @@ import static org.mockito.Mockito.when;
public class FileRealmTests extends ESTestCase {
private static final Answer<AuthenticationResult> VERIFY_PASSWORD_ANSWER = inv -> {
assertThat(inv.getArguments().length, is(3));
Supplier<User> supplier = (Supplier<User>) inv.getArguments()[2];
return AuthenticationResult.success(supplier.get());
};
private FileUserPasswdStore userPasswdStore;
private FileUserRolesStore userRolesStore;
private Settings globalSettings;
@ -49,13 +60,16 @@ public class FileRealmTests extends ESTestCase {
}
public void testAuthenticate() throws Exception {
when(userPasswdStore.verifyPassword("user1", new SecureString("test123"))).thenReturn(true);
when(userPasswdStore.verifyPassword(eq("user1"), eq(new SecureString("test123")), any(Supplier.class)))
.thenAnswer(VERIFY_PASSWORD_ANSWER);
when(userRolesStore.roles("user1")).thenReturn(new String[] { "role1", "role2" });
RealmConfig config = new RealmConfig("file-test", Settings.EMPTY, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
FileRealm realm = new FileRealm(config, userPasswdStore, userRolesStore);
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
realm.authenticate(new UsernamePasswordToken("user1", new SecureString("test123")), future, mock(IncomingRequest.class));
User user = future.actionGet();
final AuthenticationResult result = future.actionGet();
assertThat(result.getStatus(), is(AuthenticationResult.Status.SUCCESS));
User user = result.getUser();
assertThat(user, notNullValue());
assertThat(user.principal(), equalTo("user1"));
assertThat(user.roles(), notNullValue());
@ -68,15 +82,16 @@ public class FileRealmTests extends ESTestCase {
.put("cache.hash_algo", Hasher.values()[randomIntBetween(0, Hasher.values().length - 1)].name().toLowerCase(Locale.ROOT))
.build();
RealmConfig config = new RealmConfig("file-test", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
when(userPasswdStore.verifyPassword("user1", new SecureString("test123"))).thenReturn(true);
when(userPasswdStore.verifyPassword(eq("user1"), eq(new SecureString("test123")), any(Supplier.class)))
.thenAnswer(VERIFY_PASSWORD_ANSWER);
when(userRolesStore.roles("user1")).thenReturn(new String[]{"role1", "role2"});
FileRealm realm = new FileRealm(config, userPasswdStore, userRolesStore);
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
realm.authenticate(new UsernamePasswordToken("user1", new SecureString("test123")), future, mock(IncomingRequest.class));
User user1 = future.actionGet();
User user1 = future.actionGet().getUser();
future = new PlainActionFuture<>();
realm.authenticate(new UsernamePasswordToken("user1", new SecureString("test123")), future, mock(IncomingRequest.class));
User user2 = future.actionGet();
User user2 = future.actionGet().getUser();
assertThat(user1, sameInstance(user2));
}
@ -84,43 +99,45 @@ public class FileRealmTests extends ESTestCase {
RealmConfig config = new RealmConfig("file-test", Settings.EMPTY, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
userPasswdStore = spy(new UserPasswdStore(config));
userRolesStore = spy(new UserRolesStore(config));
doReturn(true).when(userPasswdStore).verifyPassword("user1", new SecureString("test123"));
when(userPasswdStore.verifyPassword(eq("user1"), eq(new SecureString("test123")), any(Supplier.class)))
.thenAnswer(VERIFY_PASSWORD_ANSWER);
doReturn(new String[] { "role1", "role2" }).when(userRolesStore).roles("user1");
FileRealm realm = new FileRealm(config, userPasswdStore, userRolesStore);
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
realm.authenticate(new UsernamePasswordToken("user1", new SecureString("test123")), future, mock(IncomingRequest.class));
User user1 = future.actionGet();
User user1 = future.actionGet().getUser();
future = new PlainActionFuture<>();
realm.authenticate(new UsernamePasswordToken("user1", new SecureString("test123")), future, mock(IncomingRequest.class));
User user2 = future.actionGet();
User user2 = future.actionGet().getUser();
assertThat(user1, sameInstance(user2));
userPasswdStore.notifyRefresh();
future = new PlainActionFuture<>();
realm.authenticate(new UsernamePasswordToken("user1", new SecureString("test123")), future, mock(IncomingRequest.class));
User user3 = future.actionGet();
User user3 = future.actionGet().getUser();
assertThat(user2, not(sameInstance(user3)));
future = new PlainActionFuture<>();
realm.authenticate(new UsernamePasswordToken("user1", new SecureString("test123")), future, mock(IncomingRequest.class));
User user4 = future.actionGet();
User user4 = future.actionGet().getUser();
assertThat(user3, sameInstance(user4));
userRolesStore.notifyRefresh();
future = new PlainActionFuture<>();
realm.authenticate(new UsernamePasswordToken("user1", new SecureString("test123")), future, mock(IncomingRequest.class));
User user5 = future.actionGet();
User user5 = future.actionGet().getUser();
assertThat(user4, not(sameInstance(user5)));
future = new PlainActionFuture<>();
realm.authenticate(new UsernamePasswordToken("user1", new SecureString("test123")), future, mock(IncomingRequest.class));
User user6 = future.actionGet();
User user6 = future.actionGet().getUser();
assertThat(user5, sameInstance(user6));
}
public void testToken() throws Exception {
RealmConfig config = new RealmConfig("file-test", Settings.EMPTY, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
when(userPasswdStore.verifyPassword("user1", new SecureString("test123"))).thenReturn(true);
when(userPasswdStore.verifyPassword(eq("user1"), eq(new SecureString("test123")), any(Supplier.class)))
.thenAnswer(VERIFY_PASSWORD_ANSWER);
when(userRolesStore.roles("user1")).thenReturn(new String[]{"role1", "role2"});
FileRealm realm = new FileRealm(config, userPasswdStore, userRolesStore);

View File

@ -5,22 +5,6 @@
*/
package org.elasticsearch.xpack.security.authc.file;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.threadpool.TestThreadPool;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.security.audit.logfile.CapturingLogger;
import org.elasticsearch.xpack.security.authc.RealmConfig;
import org.elasticsearch.xpack.security.authc.support.Hasher;
import org.elasticsearch.xpack.XPackPlugin;
import org.junit.After;
import org.junit.Before;
import java.io.BufferedWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@ -34,6 +18,24 @@ import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.threadpool.TestThreadPool;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.XPackPlugin;
import org.elasticsearch.xpack.security.audit.logfile.CapturingLogger;
import org.elasticsearch.xpack.security.authc.AuthenticationResult;
import org.elasticsearch.xpack.security.authc.RealmConfig;
import org.elasticsearch.xpack.security.authc.support.Hasher;
import org.elasticsearch.xpack.security.user.User;
import org.junit.After;
import org.junit.Before;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;
@ -91,8 +93,11 @@ public class FileUserPasswdStoreTests extends ESTestCase {
FileUserPasswdStore store = new FileUserPasswdStore(config, watcherService, latch::countDown);
User user = new User("bcrypt");
assertThat(store.userExists("bcrypt"), is(true));
assertThat(store.verifyPassword("bcrypt", new SecureString("test123")), is(true));
AuthenticationResult result = store.verifyPassword("bcrypt", new SecureString("test123"), () -> user);
assertThat(result.getStatus(), is(AuthenticationResult.Status.SUCCESS));
assertThat(result.getUser(), is(user));
watcherService.start();
@ -106,7 +111,9 @@ public class FileUserPasswdStoreTests extends ESTestCase {
}
assertThat(store.userExists("foobar"), is(true));
assertThat(store.verifyPassword("foobar", new SecureString("barfoo")), is(true));
result = store.verifyPassword("foobar", new SecureString("barfoo"), () -> user);
assertThat(result.getStatus(), is(AuthenticationResult.Status.SUCCESS));
assertThat(result.getUser(), is(user));
}
public void testStore_AutoReload_WithParseFailures() throws Exception {
@ -126,7 +133,10 @@ public class FileUserPasswdStoreTests extends ESTestCase {
FileUserPasswdStore store = new FileUserPasswdStore(config, watcherService, latch::countDown);
assertTrue(store.verifyPassword("bcrypt", new SecureString("test123")));
User user = new User("bcrypt");
final AuthenticationResult result = store.verifyPassword("bcrypt", new SecureString("test123"), () -> user);
assertThat(result.getStatus(), is(AuthenticationResult.Status.SUCCESS));
assertThat(result.getUser(), is(user));
watcherService.start();

View File

@ -19,6 +19,7 @@ import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.env.Environment;
import org.elasticsearch.xpack.security.authc.AuthenticationResult;
import org.elasticsearch.xpack.security.authc.IncomingRequest;
import org.elasticsearch.xpack.security.authc.ldap.ActiveDirectorySessionFactory.DownLevelADAuthenticator;
import org.elasticsearch.xpack.security.authc.ldap.ActiveDirectorySessionFactory.UpnADAuthenticator;
@ -139,9 +140,11 @@ public class ActiveDirectoryRealmTests extends ESTestCase {
DnRoleMapper roleMapper = new DnRoleMapper(LdapRealm.AD_TYPE, config, resourceWatcherService);
LdapRealm realm = new LdapRealm(LdapRealm.AD_TYPE, config, sessionFactory, roleMapper, threadPool);
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
realm.authenticate(new UsernamePasswordToken("CN=ironman", new SecureString(PASSWORD)), future, mock(IncomingRequest.class));
User user = future.actionGet();
final AuthenticationResult result = future.actionGet();
assertThat(result.getStatus(), is(AuthenticationResult.Status.SUCCESS));
final User user = result.getUser();
assertThat(user, is(notNullValue()));
assertThat(user.roles(), arrayContaining(containsString("Avengers")));
}
@ -154,9 +157,9 @@ public class ActiveDirectoryRealmTests extends ESTestCase {
LdapRealm realm = new LdapRealm(LdapRealm.AD_TYPE, config, sessionFactory, roleMapper, threadPool);
// Thor does not have a UPN of form CN=Thor@ad.test.elasticsearch.com
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
realm.authenticate(new UsernamePasswordToken("CN=Thor", new SecureString(PASSWORD)), future, mock(IncomingRequest.class));
User user = future.actionGet();
User user = future.actionGet().getUser();
assertThat(user, is(notNullValue()));
assertThat(user.roles(), arrayContaining(containsString("Avengers")));
}
@ -170,7 +173,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase {
return urls.toArray(Strings.EMPTY_ARRAY);
}
public void testAuthenticateCachesSuccesfulAuthentications() throws Exception {
public void testAuthenticateCachesSuccessfulAuthentications() throws Exception {
Settings settings = settings();
RealmConfig config = new RealmConfig("testAuthenticateCachesSuccesfulAuthentications", settings, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings));
ActiveDirectorySessionFactory sessionFactory = spy(new ActiveDirectorySessionFactory(config, sslService));
@ -179,7 +182,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase {
int count = randomIntBetween(2, 10);
for (int i = 0; i < count; i++) {
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
realm.authenticate(new UsernamePasswordToken("CN=ironman", new SecureString(PASSWORD)), future, mock(IncomingRequest.class));
future.actionGet();
}
@ -197,7 +200,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase {
int count = randomIntBetween(2, 10);
for (int i = 0; i < count; i++) {
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
realm.authenticate(new UsernamePasswordToken("CN=ironman", new SecureString(PASSWORD)), future, mock(IncomingRequest.class));
future.actionGet();
}
@ -215,7 +218,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase {
int count = randomIntBetween(2, 10);
for (int i = 0; i < count; i++) {
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
realm.authenticate(new UsernamePasswordToken("CN=ironman", new SecureString(PASSWORD)), future, mock(IncomingRequest.class));
future.actionGet();
}
@ -227,7 +230,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase {
roleMapper.notifyRefresh();
for (int i = 0; i < count; i++) {
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
realm.authenticate(new UsernamePasswordToken("CN=ironman", new SecureString(PASSWORD)), future, mock(IncomingRequest.class));
future.actionGet();
}
@ -244,9 +247,9 @@ public class ActiveDirectoryRealmTests extends ESTestCase {
DnRoleMapper roleMapper = new DnRoleMapper(LdapRealm.AD_TYPE, config, resourceWatcherService);
LdapRealm realm = new LdapRealm(LdapRealm.AD_TYPE, config, sessionFactory, roleMapper, threadPool);
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
realm.authenticate(new UsernamePasswordToken("CN=ironman", new SecureString(PASSWORD)), future, mock(IncomingRequest.class));
User user = future.actionGet();
User user = future.actionGet().getUser();
assertThat(user, is(notNullValue()));
assertThat(user.roles(), arrayContaining(equalTo("group_role")));
}
@ -260,9 +263,9 @@ public class ActiveDirectoryRealmTests extends ESTestCase {
DnRoleMapper roleMapper = new DnRoleMapper(LdapRealm.AD_TYPE, config, resourceWatcherService);
LdapRealm realm = new LdapRealm(LdapRealm.AD_TYPE, config, sessionFactory, roleMapper, threadPool);
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
realm.authenticate(new UsernamePasswordToken("CN=Thor", new SecureString(PASSWORD)), future, mock(IncomingRequest.class));
User user = future.actionGet();
User user = future.actionGet().getUser();
assertThat(user, is(notNullValue()));
assertThat(user.roles(), arrayContainingInAnyOrder(equalTo("group_role"), equalTo("user_role")));
}

View File

@ -5,6 +5,10 @@
*/
package org.elasticsearch.xpack.security.authc.ldap;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import com.unboundid.ldap.sdk.LDAPURL;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.PlainActionFuture;
@ -12,6 +16,10 @@ import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.env.Environment;
import org.elasticsearch.threadpool.TestThreadPool;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.security.authc.AuthenticationResult;
import org.elasticsearch.xpack.security.authc.IncomingRequest;
import org.elasticsearch.xpack.security.authc.RealmConfig;
import org.elasticsearch.xpack.security.authc.ldap.support.LdapSearchScope;
@ -21,18 +29,11 @@ import org.elasticsearch.xpack.security.authc.support.CachingUsernamePasswordRea
import org.elasticsearch.xpack.security.authc.support.DnRoleMapper;
import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken;
import org.elasticsearch.xpack.security.user.User;
import org.elasticsearch.threadpool.TestThreadPool;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.ssl.SSLService;
import org.elasticsearch.xpack.ssl.VerificationMode;
import org.junit.After;
import org.junit.Before;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory.URLS_SETTING;
import static org.hamcrest.Matchers.arrayContaining;
import static org.hamcrest.Matchers.contains;
@ -86,9 +87,11 @@ public class LdapRealmTests extends LdapTestCase {
LdapRealm ldap = new LdapRealm(LdapRealm.LDAP_TYPE, config, ldapFactory, buildGroupAsRoleMapper(resourceWatcherService),
threadPool);
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, new SecureString(PASSWORD)), future, mock(IncomingRequest.class));
User user = future.actionGet();
final AuthenticationResult result = future.actionGet();
assertThat(result.getStatus(), is(AuthenticationResult.Status.SUCCESS));
User user = result.getUser();
assertThat(user, notNullValue());
assertThat(user.roles(), arrayContaining("HMS Victory"));
assertThat(user.metadata(), notNullValue());
@ -109,9 +112,11 @@ public class LdapRealmTests extends LdapTestCase {
LdapRealm ldap =
new LdapRealm(LdapRealm.LDAP_TYPE, config, ldapFactory, buildGroupAsRoleMapper(resourceWatcherService), threadPool);
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, new SecureString(PASSWORD)), future, mock(IncomingRequest.class));
User user = future.actionGet();
final AuthenticationResult result = future.actionGet();
assertThat(result.getStatus(), is(AuthenticationResult.Status.SUCCESS));
User user = result.getUser();
assertThat(user, notNullValue());
assertThat("For roles " + Arrays.toString(user.roles()), user.roles(), arrayContaining("HMS Victory"));
assertThat(user.metadata(), notNullValue());
@ -132,12 +137,14 @@ public class LdapRealmTests extends LdapTestCase {
ldapFactory = spy(ldapFactory);
LdapRealm ldap =
new LdapRealm(LdapRealm.LDAP_TYPE, config, ldapFactory, buildGroupAsRoleMapper(resourceWatcherService), threadPool);
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, new SecureString(PASSWORD)), future, mock(IncomingRequest.class));
future.actionGet();
assertThat(future.actionGet().getStatus(), is(AuthenticationResult.Status.SUCCESS));
future = new PlainActionFuture<>();
ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, new SecureString(PASSWORD)), future, mock(IncomingRequest.class));
future.actionGet();
assertThat(future.actionGet().getStatus(), is(AuthenticationResult.Status.SUCCESS));
//verify one and only one session -> caching is working
verify(ldapFactory, times(1)).session(anyString(), any(SecureString.class), any(ActionListener.class));
@ -155,7 +162,7 @@ public class LdapRealmTests extends LdapTestCase {
DnRoleMapper roleMapper = buildGroupAsRoleMapper(resourceWatcherService);
ldapFactory = spy(ldapFactory);
LdapRealm ldap = new LdapRealm(LdapRealm.LDAP_TYPE, config, ldapFactory, roleMapper, threadPool);
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, new SecureString(PASSWORD)), future, mock(IncomingRequest.class));
future.actionGet();
future = new PlainActionFuture<>();
@ -188,7 +195,7 @@ public class LdapRealmTests extends LdapTestCase {
ldapFactory = spy(ldapFactory);
LdapRealm ldap =
new LdapRealm(LdapRealm.LDAP_TYPE, config, ldapFactory, buildGroupAsRoleMapper(resourceWatcherService), threadPool);
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, new SecureString(PASSWORD)), future, mock(IncomingRequest.class));
future.actionGet();
future = new PlainActionFuture<>();
@ -282,9 +289,11 @@ public class LdapRealmTests extends LdapTestCase {
LdapRealm ldap = new LdapRealm(LdapRealm.LDAP_TYPE, config, ldapFactory,
new DnRoleMapper(LdapRealm.LDAP_TYPE, config, resourceWatcherService), threadPool);
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
ldap.authenticate(new UsernamePasswordToken("Horatio Hornblower", new SecureString(PASSWORD)), future, mock(IncomingRequest.class));
User user = future.actionGet();
final AuthenticationResult result = future.actionGet();
assertThat(result.getStatus(), is(AuthenticationResult.Status.SUCCESS));
User user = result.getUser();
assertThat(user, notNullValue());
assertThat(user.roles(), arrayContaining("avenger"));
}
@ -306,10 +315,14 @@ public class LdapRealmTests extends LdapTestCase {
LdapRealm ldap = new LdapRealm(LdapRealm.LDAP_TYPE, config, ldapFactory, buildGroupAsRoleMapper(resourceWatcherService),
threadPool);
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, new SecureString(PASSWORD)), future, mock(IncomingRequest.class));
User user = future.actionGet();
assertThat(user, nullValue());
final AuthenticationResult result = future.actionGet();
assertThat(result.getStatus(), is(AuthenticationResult.Status.CONTINUE));
assertThat(result.getUser(), nullValue());
assertThat(result.getMessage(), is("authenticate failed"));
assertThat(result.getException(), notNullValue());
assertThat(result.getException().getMessage(), containsString("UnknownHostException"));
}
public void testUsageStats() throws Exception {

View File

@ -24,6 +24,7 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.env.Environment;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.security.authc.AuthenticationResult;
import org.elasticsearch.xpack.security.authc.IncomingRequest;
import org.elasticsearch.xpack.security.authc.RealmConfig;
import org.elasticsearch.xpack.security.authc.support.UserRoleMapper;
@ -104,9 +105,11 @@ public class PkiRealmTests extends ESTestCase {
return null;
}).when(roleMapper).resolveRoles(any(UserRoleMapper.UserData.class), any(ActionListener.class));
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
realm.authenticate(token, future, mock(IncomingRequest.class));
User user = future.actionGet();
final AuthenticationResult result = future.actionGet();
assertThat(result.getStatus(), is(AuthenticationResult.Status.SUCCESS));
User user = result.getUser();
assertThat(user, is(notNullValue()));
assertThat(user.principal(), is("Elasticsearch Test Node"));
assertThat(user.roles(), is(notNullValue()));
@ -128,9 +131,9 @@ public class PkiRealmTests extends ESTestCase {
threadContext.putTransient(PkiRealm.PKI_CERT_HEADER_NAME, new X509Certificate[] { certificate });
X509AuthenticationToken token = realm.token(threadContext);
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
realm.authenticate(token, future, mock(IncomingRequest.class));
User user = future.actionGet();
User user = future.actionGet().getUser();
assertThat(user, is(notNullValue()));
assertThat(user.principal(), is("elasticsearch"));
assertThat(user.roles(), is(notNullValue()));
@ -159,9 +162,9 @@ public class PkiRealmTests extends ESTestCase {
threadContext.putTransient(PkiRealm.PKI_CERT_HEADER_NAME, new X509Certificate[] { certificate });
X509AuthenticationToken token = realm.token(threadContext);
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
realm.authenticate(token, future, mock(IncomingRequest.class));
User user = future.actionGet();
User user = future.actionGet().getUser();
assertThat(user, is(notNullValue()));
assertThat(user.principal(), is("Elasticsearch Test Node"));
assertThat(user.roles(), is(notNullValue()));
@ -190,9 +193,9 @@ public class PkiRealmTests extends ESTestCase {
threadContext.putTransient(PkiRealm.PKI_CERT_HEADER_NAME, new X509Certificate[] { certificate });
X509AuthenticationToken token = realm.token(threadContext);
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
realm.authenticate(token, future, mock(IncomingRequest.class));
User user = future.actionGet();
User user = future.actionGet().getUser();
assertThat(user, is(nullValue()));
}

View File

@ -13,6 +13,7 @@ import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.SecuritySettingsSource;
import org.elasticsearch.xpack.security.authc.AuthenticationResult;
import org.elasticsearch.xpack.security.authc.IncomingRequest;
import org.elasticsearch.xpack.security.authc.Realm;
import org.elasticsearch.xpack.security.authc.RealmConfig;
@ -58,8 +59,9 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
RealmConfig config = new RealmConfig("test_realm", settings, globalSettings, new ThreadContext(Settings.EMPTY));
CachingUsernamePasswordRealm realm = new CachingUsernamePasswordRealm("test", config) {
@Override
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<User> listener, IncomingRequest incomingRequest) {
listener.onResponse(new User("username", new String[]{"r1", "r2", "r3"}));
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<AuthenticationResult> listener,
IncomingRequest incomingRequest) {
listener.onResponse(AuthenticationResult.success(new User("username", new String[]{"r1", "r2", "r3"})));
}
@Override
@ -74,7 +76,7 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
public void testAuthCache() {
AlwaysAuthenticateCachingRealm realm = new AlwaysAuthenticateCachingRealm(globalSettings);
SecureString pass = new SecureString("pass");
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
realm.authenticate(new UsernamePasswordToken("a", pass), future, mock(IncomingRequest.class));
future.actionGet();
future = new PlainActionFuture<>();
@ -130,33 +132,37 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
public void testLookupAndAuthCache() {
AlwaysAuthenticateCachingRealm realm = new AlwaysAuthenticateCachingRealm(globalSettings);
// lookup first
PlainActionFuture<User> future = new PlainActionFuture<>();
realm.lookupUser("a", future);
User lookedUp = future.actionGet();
PlainActionFuture<User> lookupFuture = new PlainActionFuture<>();
realm.lookupUser("a", lookupFuture);
User lookedUp = lookupFuture.actionGet();
assertThat(realm.lookupInvocationCounter.intValue(), is(1));
assertThat(realm.authInvocationCounter.intValue(), is(0));
assertThat(lookedUp.roles(), arrayContaining("lookupRole1", "lookupRole2"));
// now authenticate
future = new PlainActionFuture<>();
realm.authenticate(new UsernamePasswordToken("a", new SecureString("pass")), future, mock(IncomingRequest.class));
User user = future.actionGet();
PlainActionFuture<AuthenticationResult> authFuture = new PlainActionFuture<>();
realm.authenticate(new UsernamePasswordToken("a", new SecureString("pass")), authFuture, mock(IncomingRequest.class));
AuthenticationResult authResult = authFuture.actionGet();
assertThat(authResult.getStatus(), is(AuthenticationResult.Status.SUCCESS));
User user = authResult.getUser();
assertThat(realm.lookupInvocationCounter.intValue(), is(1));
assertThat(realm.authInvocationCounter.intValue(), is(1));
assertThat(user.roles(), arrayContaining("testRole1", "testRole2"));
assertThat(user, not(sameInstance(lookedUp)));
// authenticate a different user first
future = new PlainActionFuture<>();
realm.authenticate(new UsernamePasswordToken("b", new SecureString("pass")), future, mock(IncomingRequest.class));
user = future.actionGet();
authFuture = new PlainActionFuture<>();
realm.authenticate(new UsernamePasswordToken("b", new SecureString("pass")), authFuture, mock(IncomingRequest.class));
authResult = authFuture.actionGet();
assertThat(authResult.getStatus(), is(AuthenticationResult.Status.SUCCESS));
user = authResult.getUser();
assertThat(realm.lookupInvocationCounter.intValue(), is(1));
assertThat(realm.authInvocationCounter.intValue(), is(2));
assertThat(user.roles(), arrayContaining("testRole1", "testRole2"));
//now lookup b
future = new PlainActionFuture<>();
realm.lookupUser("b", future);
lookedUp = future.actionGet();
lookupFuture = new PlainActionFuture<>();
realm.lookupUser("b", lookupFuture);
lookedUp = lookupFuture.actionGet();
assertThat(realm.lookupInvocationCounter.intValue(), is(1));
assertThat(realm.authInvocationCounter.intValue(), is(2));
assertThat(user, sameInstance(lookedUp));
@ -169,7 +175,7 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
SecureString pass1 = new SecureString("pass");
SecureString pass2 = new SecureString("password");
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
realm.authenticate(new UsernamePasswordToken(user, pass1), future, mock(IncomingRequest.class));
future.actionGet();
future = new PlainActionFuture<>();
@ -195,9 +201,9 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
String user = "testUser";
SecureString password = new SecureString("password");
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
realm.authenticate(new UsernamePasswordToken(user, password), future, mock(IncomingRequest.class));
assertThat(future.actionGet().enabled(), equalTo(false));
assertThat(future.actionGet().getUser().enabled(), equalTo(false));
assertThat(realm.authInvocationCounter.intValue(), is(1));
@ -205,14 +211,14 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
future = new PlainActionFuture<>();
realm.authenticate(new UsernamePasswordToken(user, password), future, mock(IncomingRequest.class));
future.actionGet();
assertThat(future.actionGet().enabled(), equalTo(true));
assertThat(future.actionGet().getUser().enabled(), equalTo(true));
assertThat(realm.authInvocationCounter.intValue(), is(2));
future = new PlainActionFuture<>();
realm.authenticate(new UsernamePasswordToken(user, password), future, mock(IncomingRequest.class));
future.actionGet();
assertThat(future.actionGet().enabled(), equalTo(true));
assertThat(future.actionGet().getUser().enabled(), equalTo(true));
assertThat(realm.authInvocationCounter.intValue(), is(2));
}
@ -228,9 +234,9 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
final UsernamePasswordToken authToken = new UsernamePasswordToken("the-user", new SecureString("the-password"));
// authenticate
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
realm.authenticate(authToken, future, mock(IncomingRequest.class));
final User user1 = future.actionGet();
final User user1 = future.actionGet().getUser();
assertThat(user1.roles(), arrayContaining("testRole1", "testRole2"));
assertThat(realm.authInvocationCounter.intValue(), is(1));
@ -239,7 +245,7 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
// authenticate
future = new PlainActionFuture<>();
realm.authenticate(authToken, future, mock(IncomingRequest.class));
final User user2 = future.actionGet();
final User user2 = future.actionGet().getUser();
assertThat(user2.roles(), arrayContaining("testRole1", "testRole2"));
assertThat(user2, not(sameInstance(user1)));
assertThat(realm.authInvocationCounter.intValue(), is(2));
@ -254,12 +260,12 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
AlwaysAuthenticateCachingRealm realm = new AlwaysAuthenticateCachingRealm(config);
final UsernamePasswordToken authToken = new UsernamePasswordToken("the-user", new SecureString("the-password"));
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
// authenticate
realm.authenticate(authToken, future, mock(IncomingRequest.class));
final long start = System.currentTimeMillis();
final User user1 = future.actionGet();
final User user1 = future.actionGet().getUser();
assertThat(realm.authInvocationCounter.intValue(), is(1));
// After 100 ms (from the original start time), authenticate (read from cache). We don't care about the result
@ -278,7 +284,7 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
sleepUntil(start + 300);
future = new PlainActionFuture<>();
realm.authenticate(authToken, future, mock(IncomingRequest.class));
final User user2 = future.actionGet();
final User user2 = future.actionGet().getUser();
assertThat(user2, not(sameInstance(user1)));
// Due to slow VMs etc, the cache might have expired more than once during the test, but we can accept that.
// We have other tests that verify caching works - this test just checks that it expires even when there are repeated reads.
@ -294,9 +300,9 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
public void testAuthenticateContract() throws Exception {
Realm realm = new FailingAuthenticationRealm(Settings.EMPTY, globalSettings);
PlainActionFuture<User> future = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
realm.authenticate(new UsernamePasswordToken("user", new SecureString("pass")), future, mock(IncomingRequest.class));
User user = future.actionGet();
User user = future.actionGet().getUser();
assertThat(user, nullValue());
realm = new ThrowingAuthenticationRealm(Settings.EMPTY, globalSettings);
@ -329,12 +335,13 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
RealmConfig config = new RealmConfig("test_realm", Settings.EMPTY, globalSettings, new ThreadContext(Settings.EMPTY));
final CachingUsernamePasswordRealm realm = new CachingUsernamePasswordRealm("test", config) {
@Override
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<User> listener, IncomingRequest incomingRequest) {
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<AuthenticationResult> listener,
IncomingRequest incomingRequest) {
// do something slow
if (BCrypt.checkpw(token.credentials(), passwordHash)) {
listener.onResponse(new User(username, new String[]{"r1", "r2", "r3"}));
listener.onResponse(AuthenticationResult.success(new User(username, new String[]{"r1", "r2", "r3"})));
} else {
listener.onResponse(null);
listener.onResponse(AuthenticationResult.unsuccessful("Incorrect password", null));
}
}
@ -359,15 +366,15 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
for (int i = 0; i < numberOfIterations; i++) {
UsernamePasswordToken token = new UsernamePasswordToken(username, invalidPassword ? randomPassword : password);
realm.authenticate(token, ActionListener.wrap((user) -> {
if (invalidPassword && user != null) {
throw new RuntimeException("invalid password led to an authenticated user: " + user.toString());
} else if (invalidPassword == false && user == null) {
throw new RuntimeException("proper password led to a null user!");
realm.authenticate(token, ActionListener.wrap((result) -> {
if (invalidPassword && result.isAuthenticated()) {
throw new RuntimeException("invalid password led to an authenticated user: " + result);
} else if (invalidPassword == false && result.isAuthenticated() == false) {
throw new RuntimeException("proper password led to an unauthenticated result: " + result);
}
}, (e) -> {
logger.error("caught exception", e);
fail("unexpected exception");
fail("unexpected exception - " + e);
}), mock(IncomingRequest.class));
}
@ -392,7 +399,8 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
RealmConfig config = new RealmConfig("test_realm", Settings.EMPTY, globalSettings, new ThreadContext(Settings.EMPTY));
final CachingUsernamePasswordRealm realm = new CachingUsernamePasswordRealm("test", config) {
@Override
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<User> listener, IncomingRequest incomingRequest) {
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<AuthenticationResult> listener,
IncomingRequest incomingRequest) {
listener.onFailure(new UnsupportedOperationException("authenticate should not be called!"));
}
@ -446,8 +454,9 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
}
@Override
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<User> listener, IncomingRequest incomingRequest) {
listener.onResponse(null);
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<AuthenticationResult> listener,
IncomingRequest incomingRequest) {
listener.onResponse(AuthenticationResult.notHandled());
}
@Override
@ -463,7 +472,8 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
}
@Override
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<User> listener, IncomingRequest incomingRequest) {
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<AuthenticationResult> listener,
IncomingRequest incomingRequest) {
listener.onFailure(new RuntimeException("whatever exception"));
}
@ -493,10 +503,11 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
}
@Override
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<User> listener, IncomingRequest incomingRequest) {
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<AuthenticationResult> listener,
IncomingRequest incomingRequest) {
authInvocationCounter.incrementAndGet();
final User user = new User(token.principal(), new String[]{"testRole1", "testRole2"}, null, null, emptyMap(), usersEnabled);
listener.onResponse(user);
listener.onResponse(AuthenticationResult.success(user));
}
@Override
@ -516,9 +527,10 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase {
}
@Override
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<User> listener, IncomingRequest incomingRequest) {
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<AuthenticationResult> listener,
IncomingRequest incomingRequest) {
authInvocationCounter.incrementAndGet();
listener.onResponse(new User(token.principal(), new String[]{"testRole1", "testRole2"}));
listener.onResponse(AuthenticationResult.success(new User(token.principal(), new String[]{"testRole1", "testRole2"})));
}
@Override

View File

@ -8,6 +8,7 @@ package org.elasticsearch.example.realm;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.xpack.security.authc.AuthenticationResult;
import org.elasticsearch.xpack.security.authc.IncomingRequest;
import org.elasticsearch.xpack.security.authc.support.CharArrays;
import org.elasticsearch.xpack.security.user.User;
@ -49,13 +50,18 @@ public class CustomRealm extends Realm {
}
@Override
public void authenticate(AuthenticationToken authToken, ActionListener<User> listener, IncomingRequest incomingRequest) {
public void authenticate(AuthenticationToken authToken, ActionListener<AuthenticationResult> listener,
IncomingRequest incomingRequest) {
UsernamePasswordToken token = (UsernamePasswordToken)authToken;
final String actualUser = token.principal();
if (KNOWN_USER.equals(actualUser) && CharArrays.constantTimeEquals(token.credentials().getChars(), KNOWN_PW.getChars())) {
listener.onResponse(new User(actualUser, ROLES));
if (KNOWN_USER.equals(actualUser)) {
if (CharArrays.constantTimeEquals(token.credentials().getChars(), KNOWN_PW.getChars())) {
listener.onResponse(AuthenticationResult.success(new User(actualUser, ROLES)));
} else {
listener.onResponse(null);
listener.onResponse(AuthenticationResult.unsuccessful("Invalid password for user " + actualUser, null));
}
} else {
listener.onResponse(AuthenticationResult.notHandled());
}
}

View File

@ -10,6 +10,7 @@ import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.env.Environment;
import org.elasticsearch.xpack.security.authc.AuthenticationResult;
import org.elasticsearch.xpack.security.authc.IncomingRequest;
import org.elasticsearch.xpack.security.user.User;
import org.elasticsearch.xpack.security.authc.RealmConfig;
@ -27,9 +28,9 @@ public class CustomRealmTests extends ESTestCase {
CustomRealm realm = new CustomRealm(new RealmConfig("test", Settings.EMPTY, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings)));
SecureString password = CustomRealm.KNOWN_PW.clone();
UsernamePasswordToken token = new UsernamePasswordToken(CustomRealm.KNOWN_USER, password);
PlainActionFuture<User> plainActionFuture = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> plainActionFuture = new PlainActionFuture<>();
realm.authenticate(token, plainActionFuture, mock(IncomingRequest.class));
User user = plainActionFuture.actionGet();
User user = plainActionFuture.actionGet().getUser();
assertThat(user, notNullValue());
assertThat(user.roles(), equalTo(CustomRealm.ROLES));
assertThat(user.principal(), equalTo(CustomRealm.KNOWN_USER));
@ -40,8 +41,9 @@ public class CustomRealmTests extends ESTestCase {
CustomRealm realm = new CustomRealm(new RealmConfig("test", Settings.EMPTY, globalSettings, new Environment(globalSettings), new ThreadContext(globalSettings)));
SecureString password = CustomRealm.KNOWN_PW.clone();
UsernamePasswordToken token = new UsernamePasswordToken(CustomRealm.KNOWN_USER + "1", password);
PlainActionFuture<User> plainActionFuture = new PlainActionFuture<>();
PlainActionFuture<AuthenticationResult> plainActionFuture = new PlainActionFuture<>();
realm.authenticate(token, plainActionFuture, mock(IncomingRequest.class));
assertThat(plainActionFuture.actionGet(), nullValue());
final AuthenticationResult result = plainActionFuture.actionGet();
assertThat(result.getStatus(), equalTo(AuthenticationResult.Status.CONTINUE));
}
}