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:
parent
a36121a725
commit
e4c8851a24
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
));
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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")));
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue